diff --git a/CHANGELOG.md b/CHANGELOG.md index 281e07f..cee2935 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ ## [Unreleased] +## [1.1.0] - 2025-11-22 + +- Show VCS changes in IDE popup + ## [1.0.11] - 2025-11-19 - Fix bug with time tracking after project closure @@ -54,7 +58,8 @@ - Support IntelliJ Platform 2024.3.5 -[Unreleased]: https://github.com/codeclocker/codeclocker-intellij-plugin/compare/v1.0.11...HEAD +[Unreleased]: https://github.com/codeclocker/codeclocker-intellij-plugin/compare/v1.1.0...HEAD +[1.1.0]: https://github.com/codeclocker/codeclocker-intellij-plugin/compare/v1.0.11...v1.1.0 [1.0.11]: https://github.com/codeclocker/codeclocker-intellij-plugin/compare/v1.0.10...v1.0.11 [1.0.10]: https://github.com/codeclocker/codeclocker-intellij-plugin/compare/v1.0.9...v1.0.10 [1.0.9]: https://github.com/codeclocker/codeclocker-intellij-plugin/compare/v1.0.8...v1.0.9 diff --git a/gradle.properties b/gradle.properties index 04be97b..d781cca 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,7 +4,7 @@ pluginGroup = com.codeclocker pluginName = CodeClocker pluginRepositoryUrl = https://github.com/codeclocker/codeclocker-intellij-plugin # SemVer format -> https://semver.org -pluginVersion = 1.0.11 +pluginVersion = 1.1.0 # Supported build number ranges and IntelliJ Platform versions -> https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html pluginSinceBuild = 242 diff --git a/src/main/java/com/codeclocker/plugin/intellij/git/ChangesTrackingCheckinHandler.java b/src/main/java/com/codeclocker/plugin/intellij/git/ChangesTrackingCheckinHandler.java index 9daff0e..44d8c38 100644 --- a/src/main/java/com/codeclocker/plugin/intellij/git/ChangesTrackingCheckinHandler.java +++ b/src/main/java/com/codeclocker/plugin/intellij/git/ChangesTrackingCheckinHandler.java @@ -1,7 +1,7 @@ package com.codeclocker.plugin.intellij.git; import com.codeclocker.plugin.intellij.git.LineDifferenceCalculator.LineDifferenceResult; -import com.codeclocker.plugin.intellij.services.ChangesActivityTracker; +import com.codeclocker.plugin.intellij.services.vcs.ChangesActivityTracker; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; import com.intellij.openapi.vcs.CheckinProjectPanel; diff --git a/src/main/java/com/codeclocker/plugin/intellij/git/GitCommitStatsListener.java b/src/main/java/com/codeclocker/plugin/intellij/git/GitCommitStatsListener.java index d97b214..325f12a 100644 --- a/src/main/java/com/codeclocker/plugin/intellij/git/GitCommitStatsListener.java +++ b/src/main/java/com/codeclocker/plugin/intellij/git/GitCommitStatsListener.java @@ -1,6 +1,6 @@ package com.codeclocker.plugin.intellij.git; -import com.codeclocker.plugin.intellij.services.ChangesActivityTracker; +import com.codeclocker.plugin.intellij.services.vcs.ChangesActivityTracker; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.vcs.CheckinProjectPanel; import com.intellij.openapi.vcs.changes.CommitContext; diff --git a/src/main/java/com/codeclocker/plugin/intellij/reporting/DataReportingTask.java b/src/main/java/com/codeclocker/plugin/intellij/reporting/DataReportingTask.java index e134ef2..23d0cc4 100644 --- a/src/main/java/com/codeclocker/plugin/intellij/reporting/DataReportingTask.java +++ b/src/main/java/com/codeclocker/plugin/intellij/reporting/DataReportingTask.java @@ -11,10 +11,10 @@ import com.codeclocker.plugin.intellij.apikey.ApiKeyLifecycle; import com.codeclocker.plugin.intellij.config.Config; import com.codeclocker.plugin.intellij.config.ConfigProvider; -import com.codeclocker.plugin.intellij.services.ChangesActivityTracker; import com.codeclocker.plugin.intellij.services.ChangesSample; import com.codeclocker.plugin.intellij.services.TimeSpentPerProjectLogger; import com.codeclocker.plugin.intellij.services.TimeSpentPerProjectSample; +import com.codeclocker.plugin.intellij.services.vcs.ChangesActivityTracker; import com.codeclocker.plugin.intellij.subscription.CheckSubscriptionStateHttpClient; import com.fasterxml.jackson.core.JsonProcessingException; import com.intellij.openapi.Disposable; diff --git a/src/main/java/com/codeclocker/plugin/intellij/services/TimeTrackerWidgetService.java b/src/main/java/com/codeclocker/plugin/intellij/services/TimeTrackerWidgetService.java index e1a38c9..54d30f6 100644 --- a/src/main/java/com/codeclocker/plugin/intellij/services/TimeTrackerWidgetService.java +++ b/src/main/java/com/codeclocker/plugin/intellij/services/TimeTrackerWidgetService.java @@ -1,8 +1,8 @@ package com.codeclocker.plugin.intellij.services; -import static com.codeclocker.plugin.intellij.services.ChangesActivityTracker.GLOBAL_ADDITIONS; -import static com.codeclocker.plugin.intellij.services.ChangesActivityTracker.GLOBAL_REMOVALS; import static com.codeclocker.plugin.intellij.services.TimeSpentPerProjectLogger.GLOBAL_STOP_WATCH; +import static com.codeclocker.plugin.intellij.services.vcs.ChangesActivityTracker.GLOBAL_ADDITIONS; +import static com.codeclocker.plugin.intellij.services.vcs.ChangesActivityTracker.GLOBAL_REMOVALS; import com.codeclocker.plugin.intellij.stopwatch.SafeStopWatch; import com.codeclocker.plugin.intellij.widget.TimeTrackerWidget; diff --git a/src/main/java/com/codeclocker/plugin/intellij/services/ChangesActivityTracker.java b/src/main/java/com/codeclocker/plugin/intellij/services/vcs/ChangesActivityTracker.java similarity index 54% rename from src/main/java/com/codeclocker/plugin/intellij/services/ChangesActivityTracker.java rename to src/main/java/com/codeclocker/plugin/intellij/services/vcs/ChangesActivityTracker.java index 45d1eb1..2400c16 100644 --- a/src/main/java/com/codeclocker/plugin/intellij/services/ChangesActivityTracker.java +++ b/src/main/java/com/codeclocker/plugin/intellij/services/vcs/ChangesActivityTracker.java @@ -1,5 +1,7 @@ -package com.codeclocker.plugin.intellij.services; +package com.codeclocker.plugin.intellij.services.vcs; +import com.codeclocker.plugin.intellij.services.ChangesSample; +import com.intellij.openapi.diagnostic.Logger; import java.util.HashMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -10,11 +12,15 @@ public class ChangesActivityTracker { + private static final Logger LOG = Logger.getInstance(ChangesActivityTracker.class); + public static final AtomicLong GLOBAL_ADDITIONS = new AtomicLong(0); public static final AtomicLong GLOBAL_REMOVALS = new AtomicLong(0); private final Map> fileNameByChangesSample = new ConcurrentHashMap<>(); + private final Map projectChangesCounters = + new ConcurrentHashMap<>(); private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); public void incrementAdditions( @@ -24,6 +30,12 @@ public void incrementAdditions( lock.lock(); GLOBAL_ADDITIONS.addAndGet(additions); + // Update per-project counters + projectChangesCounters + .computeIfAbsent(project, p -> new ProjectChangesCounters(0, 0)) + .additions() + .addAndGet(additions); + fileNameByChangesSample .computeIfAbsent(project, p -> new ConcurrentHashMap<>()) .computeIfAbsent(filePath, f -> ChangesSample.create(extension)) @@ -39,6 +51,12 @@ public void incrementRemovals(String project, String fileName, String extension, lock.lock(); GLOBAL_REMOVALS.addAndGet(removals); + // Update per-project counters + projectChangesCounters + .computeIfAbsent(project, p -> new ProjectChangesCounters(0, 0)) + .removals() + .addAndGet(removals); + fileNameByChangesSample .computeIfAbsent(project, p -> new ConcurrentHashMap<>()) .computeIfAbsent(fileName, f -> ChangesSample.create(extension)) @@ -60,4 +78,28 @@ public Map> drain() { lock.unlock(); } } + + public ProjectChangesCounters getProjectChanges(String projectName) { + ProjectChangesCounters counters = projectChangesCounters.get(projectName); + if (counters == null) { + return new ProjectChangesCounters(0, 0); + } + return new ProjectChangesCounters(counters.additions().get(), counters.removals().get()); + } + + public void initializeProjectChanges(String projectName, long additions, long removals) { + projectChangesCounters + .computeIfAbsent(projectName, p -> new ProjectChangesCounters(additions, removals)) + .initialize(additions, removals); + LOG.debug( + "Initialized VCS counters for project {} with additions: {}, removals: {}", + projectName, + additions, + removals); + } + + public void clearAllProjectChanges() { + LOG.debug("Clearing all per-project VCS counters"); + projectChangesCounters.clear(); + } } diff --git a/src/main/java/com/codeclocker/plugin/intellij/services/vcs/ProjectChangesCounters.java b/src/main/java/com/codeclocker/plugin/intellij/services/vcs/ProjectChangesCounters.java new file mode 100644 index 0000000..3cfd10d --- /dev/null +++ b/src/main/java/com/codeclocker/plugin/intellij/services/vcs/ProjectChangesCounters.java @@ -0,0 +1,27 @@ +package com.codeclocker.plugin.intellij.services.vcs; + +import java.util.concurrent.atomic.AtomicLong; + +public class ProjectChangesCounters { + + private final AtomicLong additions = new AtomicLong(0); + private final AtomicLong removals = new AtomicLong(0); + + public ProjectChangesCounters(long initialAdditions, long initialRemovals) { + this.additions.set(initialAdditions); + this.removals.set(initialRemovals); + } + + public void initialize(long initialAdditions, long initialRemovals) { + this.additions.set(initialAdditions); + this.removals.set(initialRemovals); + } + + public AtomicLong additions() { + return additions; + } + + public AtomicLong removals() { + return removals; + } +} diff --git a/src/main/java/com/codeclocker/plugin/intellij/widget/TimeTrackerInitializer.java b/src/main/java/com/codeclocker/plugin/intellij/widget/TimeTrackerInitializer.java index 0640ffb..6d55d75 100644 --- a/src/main/java/com/codeclocker/plugin/intellij/widget/TimeTrackerInitializer.java +++ b/src/main/java/com/codeclocker/plugin/intellij/widget/TimeTrackerInitializer.java @@ -1,8 +1,8 @@ package com.codeclocker.plugin.intellij.widget; -import static com.codeclocker.plugin.intellij.services.ChangesActivityTracker.GLOBAL_ADDITIONS; -import static com.codeclocker.plugin.intellij.services.ChangesActivityTracker.GLOBAL_REMOVALS; import static com.codeclocker.plugin.intellij.services.TimeSpentPerProjectLogger.GLOBAL_STOP_WATCH; +import static com.codeclocker.plugin.intellij.services.vcs.ChangesActivityTracker.GLOBAL_ADDITIONS; +import static com.codeclocker.plugin.intellij.services.vcs.ChangesActivityTracker.GLOBAL_REMOVALS; import static org.apache.commons.collections.MapUtils.isEmpty; import static org.apache.commons.lang3.StringUtils.isBlank; @@ -10,7 +10,9 @@ import com.codeclocker.plugin.intellij.config.Config; import com.codeclocker.plugin.intellij.reporting.DailyTimeHttpClient; import com.codeclocker.plugin.intellij.reporting.DailyTimeHttpClient.DailyTimeResponse; +import com.codeclocker.plugin.intellij.reporting.DailyTimeHttpClient.ProjectStats; import com.codeclocker.plugin.intellij.services.TimeTrackerWidgetService; +import com.codeclocker.plugin.intellij.services.vcs.ChangesActivityTracker; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; @@ -134,10 +136,17 @@ private static void initializeAllProjectWidgets( LOG.debug( "Initialized GLOBAL_ADDITIONS: {}, GLOBAL_REMOVALS: {}", totalAdditions, totalRemovals); + initializeVcsChanges(projectStats); + initializeCodingTime(projectStats, totalTime); + + initialized = true; + } + + private static void initializeCodingTime(Map projectStats, long totalTime) { Project[] openProjects = ProjectManager.getInstance().getOpenProjects(); for (Project project : openProjects) { String projectName = project.getName(); - DailyTimeHttpClient.ProjectStats stats = projectStats.get(projectName); + ProjectStats stats = projectStats.get(projectName); long initialSeconds = stats != null ? stats.timeSpentSeconds() : 0L; TimeTrackerWidgetService service = project.getService(TimeTrackerWidgetService.class); @@ -150,8 +159,18 @@ private static void initializeAllProjectWidgets( totalTime); } } + } - initialized = true; + private static void initializeVcsChanges(Map projectStats) { + ChangesActivityTracker changesTracker = + ApplicationManager.getApplication().getService(ChangesActivityTracker.class); + changesTracker.clearAllProjectChanges(); + + for (Map.Entry entry : projectStats.entrySet()) { + String projectName = entry.getKey(); + ProjectStats stats = entry.getValue(); + changesTracker.initializeProjectChanges(projectName, stats.additions(), stats.removals()); + } } private static synchronized void startRetryTask() { diff --git a/src/main/java/com/codeclocker/plugin/intellij/widget/TimeTrackerPopup.java b/src/main/java/com/codeclocker/plugin/intellij/widget/TimeTrackerPopup.java index ba7d22e..0b03f42 100644 --- a/src/main/java/com/codeclocker/plugin/intellij/widget/TimeTrackerPopup.java +++ b/src/main/java/com/codeclocker/plugin/intellij/widget/TimeTrackerPopup.java @@ -1,13 +1,16 @@ package com.codeclocker.plugin.intellij.widget; import static com.codeclocker.plugin.intellij.HubHost.HUB_UI_HOST; -import static com.codeclocker.plugin.intellij.services.ChangesActivityTracker.GLOBAL_ADDITIONS; -import static com.codeclocker.plugin.intellij.services.ChangesActivityTracker.GLOBAL_REMOVALS; +import static com.codeclocker.plugin.intellij.services.vcs.ChangesActivityTracker.GLOBAL_ADDITIONS; +import static com.codeclocker.plugin.intellij.services.vcs.ChangesActivityTracker.GLOBAL_REMOVALS; import static org.apache.commons.lang3.StringUtils.isNotBlank; import com.codeclocker.plugin.intellij.apikey.ApiKeyPersistence; import com.codeclocker.plugin.intellij.apikey.EnterApiKeyAction; +import com.codeclocker.plugin.intellij.services.vcs.ChangesActivityTracker; +import com.codeclocker.plugin.intellij.services.vcs.ProjectChangesCounters; import com.intellij.ide.BrowserUtil; +import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.popup.JBPopupFactory; import com.intellij.openapi.ui.popup.ListPopup; @@ -24,10 +27,15 @@ public class TimeTrackerPopup { private static final String ADD_API_KEY = "Add API Key"; public static ListPopup create(Project project, String totalTime, String projectTime) { + ChangesActivityTracker tracker = + ApplicationManager.getApplication().getService(ChangesActivityTracker.class); + ProjectChangesCounters projectChanges = tracker.getProjectChanges(project.getName()); + List items = new ArrayList<>(); items.add("Total: " + totalTime); items.add(project.getName() + ": " + projectTime); - items.add("Committed Lines: " + getFormattedVcsChanges()); + items.add("Total: " + getFormattedVcsChanges()); + items.add(project.getName() + ": " + formatProjectVcsChanges(projectChanges)); items.add(OPEN_DETAILED_VIEW); boolean hasApiKey = isNotBlank(ApiKeyPersistence.getApiKey()); @@ -36,7 +44,7 @@ public static ListPopup create(Project project, String totalTime, String project } BaseListPopupStep step = - new BaseListPopupStep<>("Coding Activity Today", items) { + new BaseListPopupStep<>("Activity Today", items) { @Override public boolean isSelectable(String value) { return OPEN_DETAILED_VIEW.equals(value) || ADD_API_KEY.equals(value); @@ -59,7 +67,19 @@ public boolean hasSubstep(String selectedValue) { @Override public @Nullable ListSeparator getSeparatorAbove(String value) { - return OPEN_DETAILED_VIEW.equals(value) ? new ListSeparator() : null; + if (OPEN_DETAILED_VIEW.equals(value)) { + return new ListSeparator(); + } + + if (value.contains("Total: ") && value.contains("/")) { + return new ListSeparator("Committed Lines"); + } + + if (value.contains("Total: ") && !value.contains("/")) { + return new ListSeparator("Coding Time"); + } + + return null; } }; @@ -69,4 +89,8 @@ public boolean hasSubstep(String selectedValue) { public static String getFormattedVcsChanges() { return String.format("+%d / -%d", GLOBAL_ADDITIONS.get(), GLOBAL_REMOVALS.get()); } + + private static String formatProjectVcsChanges(ProjectChangesCounters changes) { + return String.format("+%d / -%d", changes.additions().get(), changes.removals().get()); + } } diff --git a/src/main/java/com/codeclocker/plugin/intellij/widget/TimeTrackerWidget.java b/src/main/java/com/codeclocker/plugin/intellij/widget/TimeTrackerWidget.java index f23b3f7..e7655dd 100644 --- a/src/main/java/com/codeclocker/plugin/intellij/widget/TimeTrackerWidget.java +++ b/src/main/java/com/codeclocker/plugin/intellij/widget/TimeTrackerWidget.java @@ -80,6 +80,7 @@ public Icon getIcon() { public String getTooltipText() { String totalTime = service.getFormattedTotalTime(); String projectTime = service.getFormattedProjectTime(); + return "Total coding time today: " + totalTime + ". Time on " diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 8a8aecc..ba1a150 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -14,7 +14,7 @@ + serviceImplementation="com.codeclocker.plugin.intellij.services.vcs.ChangesActivityTracker"/>