├── .idea ├── copyright │ └── profiles_settings.xml ├── vcs.xml ├── inspectionProfiles │ ├── profiles_settings.xml │ └── Project_Default.xml ├── modules.xml ├── compiler.xml └── misc.xml ├── gtm-intellij.iml ├── src └── io │ └── edgeg │ └── gtm │ └── intellij │ ├── GTMVisibleAreaListener.java │ ├── GTMEditorMouseListener.java │ ├── GitTimeMetric.java │ ├── GTMConfig.java │ ├── GTMProject.java │ ├── GTMStatusWidget.java │ └── GTMRecord.java ├── LICENSE ├── .gitignore ├── resources └── META-INF │ └── plugin.xml └── README.md /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /gtm-intellij.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/io/edgeg/gtm/intellij/GTMVisibleAreaListener.java: -------------------------------------------------------------------------------- 1 | package io.edgeg.gtm.intellij; 2 | 3 | import com.intellij.openapi.editor.event.VisibleAreaEvent; 4 | import com.intellij.openapi.editor.event.VisibleAreaListener; 5 | import com.intellij.openapi.fileEditor.FileDocumentManager; 6 | import com.intellij.openapi.vfs.VirtualFile; 7 | 8 | class GTMVisibleAreaListener implements VisibleAreaListener { 9 | 10 | @Override 11 | public void visibleAreaChanged(VisibleAreaEvent visibleAreaEvent) { 12 | final FileDocumentManager instance = FileDocumentManager.getInstance(); 13 | final VirtualFile file = instance.getFile(visibleAreaEvent.getEditor().getDocument()); 14 | if (file != null) { 15 | GTMRecord.record(file.getPath(), visibleAreaEvent.getEditor().getProject()); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Michael Schenk and Craig Wohlfeil 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/io/edgeg/gtm/intellij/GTMEditorMouseListener.java: -------------------------------------------------------------------------------- 1 | package io.edgeg.gtm.intellij; 2 | 3 | import com.intellij.openapi.editor.event.EditorMouseEvent; 4 | import com.intellij.openapi.editor.event.EditorMouseListener; 5 | import com.intellij.openapi.fileEditor.FileDocumentManager; 6 | import com.intellij.openapi.vfs.VirtualFile; 7 | 8 | class GTMEditorMouseListener implements EditorMouseListener { 9 | 10 | @Override 11 | public void mousePressed(EditorMouseEvent editorMouseEvent) { 12 | final FileDocumentManager instance = FileDocumentManager.getInstance(); 13 | final VirtualFile file = instance.getFile(editorMouseEvent.getEditor().getDocument()); 14 | if (file != null) { 15 | GTMRecord.record(file.getPath(), editorMouseEvent.getEditor().getProject()); 16 | } 17 | } 18 | 19 | @Override 20 | public void mouseClicked(EditorMouseEvent editorMouseEvent) { 21 | } 22 | 23 | @Override 24 | public void mouseReleased(EditorMouseEvent editorMouseEvent) { 25 | } 26 | 27 | @Override 28 | public void mouseEntered(EditorMouseEvent editorMouseEvent) { 29 | } 30 | 31 | @Override 32 | public void mouseExited(EditorMouseEvent editorMouseEvent) { 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/io/edgeg/gtm/intellij/GitTimeMetric.java: -------------------------------------------------------------------------------- 1 | package io.edgeg.gtm.intellij; 2 | 3 | import com.intellij.openapi.components.ApplicationComponent; 4 | import com.intellij.openapi.editor.EditorFactory; 5 | import com.intellij.openapi.editor.event.EditorMouseListener; 6 | import com.intellij.openapi.editor.event.VisibleAreaListener; 7 | import org.jetbrains.annotations.NotNull; 8 | 9 | public class GitTimeMetric implements ApplicationComponent { 10 | private EditorMouseListener mouseListener = new GTMEditorMouseListener(); 11 | private VisibleAreaListener visibleAreaListener = new GTMVisibleAreaListener(); 12 | 13 | public GitTimeMetric() { 14 | } 15 | 16 | public void initComponent() { 17 | EditorFactory.getInstance().getEventMulticaster().addEditorMouseListener(mouseListener); 18 | EditorFactory.getInstance().getEventMulticaster().addVisibleAreaListener(visibleAreaListener); 19 | } 20 | 21 | public void disposeComponent() { 22 | EditorFactory.getInstance().getEventMulticaster().removeEditorMouseListener(mouseListener); 23 | EditorFactory.getInstance().getEventMulticaster().removeVisibleAreaListener(visibleAreaListener); 24 | } 25 | 26 | @NotNull 27 | @Override 28 | public String getComponentName() { 29 | return "GitTimeMetric"; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/io/edgeg/gtm/intellij/GTMConfig.java: -------------------------------------------------------------------------------- 1 | package io.edgeg.gtm.intellij; 2 | 3 | import com.intellij.openapi.components.PersistentStateComponent; 4 | import com.intellij.openapi.components.State; 5 | import com.intellij.openapi.components.Storage; 6 | import com.intellij.openapi.components.StoragePathMacros; 7 | import com.intellij.openapi.diagnostic.Logger; 8 | import com.intellij.util.xmlb.XmlSerializerUtil; 9 | import org.jetbrains.annotations.Nullable; 10 | 11 | @State( 12 | name = "GTMAppSettings", 13 | storages = { 14 | @Storage(StoragePathMacros.WORKSPACE_FILE) 15 | } 16 | ) 17 | 18 | public class GTMConfig implements PersistentStateComponent { 19 | static Logger LOG = Logger.getInstance("#io.edgeg.gtm.intellij"); 20 | private static GTMConfig CFG = null; 21 | 22 | Boolean statusEnabled = true; 23 | 24 | String gtmNotFound = 25 | "Git Time Metric (GTM) executable not found.\n" + 26 | "Install GTM and/or update your system path.\n" + 27 | "Make sure to restart after installing GTM.\n\n" + 28 | "See https://github.com/git-time-metric/gtm"; 29 | 30 | String gtmVerOutdated = 31 | "Git Time Metric (GTM) executable is out of date.\n" + 32 | "The plug-in may not work properly.\n" + 33 | "Please install the latest GTM version and restart.\n\n" + 34 | "See https://github.com/git-time-metric/gtm"; 35 | 36 | static GTMConfig getInstance() { 37 | if (CFG == null) { 38 | CFG = new GTMConfig(); 39 | } 40 | return CFG; 41 | } 42 | 43 | @Nullable 44 | @Override 45 | public GTMConfig getState() { 46 | return this; 47 | } 48 | 49 | @Override 50 | public void loadState(GTMConfig state) { 51 | XmlSerializerUtil.copyBean(state, this); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # 2 | # OS X 3 | # 4 | .DS_Store 5 | 6 | # 7 | # Java 8 | # 9 | 10 | *.class 11 | 12 | # Mobile Tools for Java (J2ME) 13 | .mtj.tmp/ 14 | 15 | # Package Files # 16 | *.jar 17 | *.war 18 | *.ear 19 | 20 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 21 | hs_err_pid* 22 | 23 | # 24 | # Gradle 25 | # 26 | 27 | .gradle 28 | build/ 29 | 30 | # Ignore Gradle GUI config 31 | gradle-app.setting 32 | 33 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 34 | !gradle-wrapper.jar 35 | 36 | # Cache of project 37 | .gradletasknamecache 38 | 39 | # 40 | # JetBrains 41 | # 42 | 43 | *.iml 44 | 45 | ## Directory-based project format: 46 | .idea/ 47 | 48 | ## File-based project format: 49 | *.ipr 50 | *.iws 51 | 52 | ## Plugin-specific files: 53 | 54 | # IntelliJ 55 | /out/ 56 | 57 | 58 | .gtm/ 59 | ### https://raw.github.com/github/gitignore/10eb19db07e36f5b8b26315ec90e27c5e82a4b47/Global/JetBrains.gitignore 60 | 61 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 62 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 63 | 64 | # User-specific stuff: 65 | .idea/workspace.xml 66 | .idea/tasks.xml 67 | .idea/dictionaries 68 | .idea/vcs.xml 69 | .idea/jsLibraryMappings.xml 70 | 71 | # Sensitive or high-churn files: 72 | .idea/dataSources.ids 73 | .idea/dataSources.xml 74 | .idea/dataSources.local.xml 75 | .idea/sqlDataSources.xml 76 | .idea/dynamic.xml 77 | .idea/uiDesigner.xml 78 | 79 | # Gradle: 80 | .idea/gradle.xml 81 | .idea/libraries 82 | 83 | # Mongo Explorer plugin: 84 | .idea/mongoSettings.xml 85 | 86 | ## File-based project format: 87 | *.iws 88 | 89 | ## Plugin-specific files: 90 | 91 | # IntelliJ 92 | /out/ 93 | 94 | # mpeltonen/sbt-idea plugin 95 | .idea_modules/ 96 | 97 | # JIRA plugin 98 | atlassian-ide-plugin.xml 99 | 100 | # Crashlytics plugin (for Android Studio and IntelliJ) 101 | com_crashlytics_export_strings.xml 102 | crashlytics.properties 103 | crashlytics-build.properties 104 | fabric.properties 105 | 106 | 107 | /.gtm/ 108 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /resources/META-INF/plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | io.edgeg.gtm.intellij 3 | Git Time Metric 4 | 1.0.13 5 | Git Time Metric 6 | 7 | 9 | Simple, seamless, lightweight time tracking for all your git projects

10 | 11 | Git Time Metrics (GTM) is a tool to automatically track time spent reading and working on code that you store in a Git repository.
12 | By installing GTM and using supported plug-ins for your favorite editors, you can immediately realize better insight into how you are spending your time and on what files.

13 | 14 | Installation

15 | 16 | Installing GTM is a two step process. First, it's recommended you install the GTM executable that the plug-in integrates with and then install the JetBrains GTM plug-in. Please submit an issue if you have any problems and/or questions.

17 | 18 | 1. Follow the Getting Started section to install the GTM executable for your operating system.

19 | 20 | 2. Install the plug-in from your JetBrains IDE, select Preferences -> Plugins -> Browse Repositories... and search for "Git Time Metric".

21 | 22 | 23 | Note - to enable time tracking for a Git repository, you need to initialize it with gtm init otherwise it will be ignored by GTM. This is done via the command line. You can run this within the JetBrains IDE terminal.

24 | 25 | 26 | > cd /path/to/your/project
27 | > gtm init
28 |

29 | 30 | To report a bug, please submit an issue on the GitHub Page.
31 | 32 | Consult the README and Wiki for more information. 33 | 34 | ]]>
35 | 36 | 37 | 38 | 39 | 40 | 41 | com.intellij.modules.lang 42 | 43 | 44 | 45 | io.edgeg.gtm.intellij.GitTimeMetric 46 | 47 | 48 | 49 | 50 | 51 | io.edgeg.gtm.intellij.GTMProject 52 | 53 | 54 | 55 |
56 | -------------------------------------------------------------------------------- /src/io/edgeg/gtm/intellij/GTMProject.java: -------------------------------------------------------------------------------- 1 | package io.edgeg.gtm.intellij; 2 | 3 | import com.intellij.openapi.components.AbstractProjectComponent; 4 | import com.intellij.openapi.project.Project; 5 | import com.intellij.openapi.ui.popup.Balloon; 6 | import com.intellij.openapi.ui.popup.JBPopupFactory; 7 | import com.intellij.openapi.wm.StatusBar; 8 | import com.intellij.openapi.wm.WindowManager; 9 | import com.intellij.ui.awt.RelativePoint; 10 | import org.jetbrains.annotations.NotNull; 11 | 12 | import static com.intellij.openapi.ui.MessageType.ERROR; 13 | import static com.intellij.openapi.ui.MessageType.WARNING; 14 | 15 | public class GTMProject extends AbstractProjectComponent { 16 | private GTMStatusWidget myStatusWidget; 17 | 18 | public GTMProject(@NotNull Project project) { 19 | super(project); 20 | } 21 | 22 | @Override 23 | public void initComponent() { 24 | myStatusWidget = GTMStatusWidget.create(myProject); 25 | } 26 | 27 | @Override 28 | public void disposeComponent() { 29 | uninstallGtmWidget(); 30 | } 31 | 32 | @Override 33 | public void projectOpened() { 34 | installGtmWidget(); 35 | } 36 | 37 | @Override 38 | public void projectClosed() { 39 | uninstallGtmWidget(); 40 | } 41 | 42 | private void installGtmWidget() { 43 | StatusBar statusBar = WindowManager.getInstance().getStatusBar(myProject); 44 | if (statusBar != null) { 45 | statusBar.addWidget(myStatusWidget, myProject); 46 | myStatusWidget.installed(); 47 | if (!GTMRecord.initGtmExePath()) { 48 | JBPopupFactory.getInstance() 49 | .createHtmlTextBalloonBuilder(GTMConfig.getInstance().gtmNotFound, ERROR, null) 50 | .setFadeoutTime(30000) 51 | .createBalloon() 52 | .show(RelativePoint.getSouthEastOf(statusBar.getComponent()), 53 | Balloon.Position.atRight); 54 | return; 55 | } 56 | if (!GTMRecord.checkVersion()) { 57 | JBPopupFactory.getInstance() 58 | .createHtmlTextBalloonBuilder(GTMConfig.getInstance().gtmVerOutdated, WARNING, null) 59 | .setFadeoutTime(30000) 60 | .createBalloon() 61 | .show(RelativePoint.getSouthEastOf(statusBar.getComponent()), 62 | Balloon.Position.atRight); 63 | } 64 | } 65 | } 66 | 67 | private void uninstallGtmWidget() { 68 | StatusBar statusBar = WindowManager.getInstance().getStatusBar(myProject); 69 | if (statusBar != null) { 70 | statusBar.removeWidget(myStatusWidget.ID()); 71 | myStatusWidget.uninstalled(); 72 | } 73 | 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
GTM Logo
2 |
Git Time Metric
3 | 4 | ### JetBrains Git Time Metrics (GTM) plug-in 5 | 6 | #### IntelliJ IDEA, PyCharm, WebStorm, AppCode, RubyMine, PhpStorm, AndroidStudio Plug-ins 7 | 8 | #### Simple, seamless, lightweight time tracking for all your git projects 9 | 10 | Git Time Metrics (GTM) is a tool to automatically track time spent reading and working on code that you store in a Git repository. By installing GTM and using supported plug-ins for your favorite editors, you can immediately realize better insight into how you are spending your time and on what files. 11 | 12 | # Installation 13 | 14 | Installing GTM is a two step process. First, it's recommended you install the GTM executable that the plug-in integrates with and then install the JetBrains GTM plug-in. Please submit an issue if you have any problems and/or questions. 15 | 16 | 1. Follow the [Getting Started](https://github.com/git-time-metric/gtm/blob/master/README.md) section to install the GTM executable for your operating system. This plug-in looks for the gtm executable in the following locations: 17 |
18 |
Windows Search Path
19 |
%PATH%;\ProgramFiles\gtm;\ProgramFiles(x86)\gtm;<homedir>\gtm
20 |
*nix Search Path (includes macOS)
21 |
$PATH:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin:~/bin:~/local/bin
22 |
23 | 2. Install the plug-in from your JetBrains IDE, select `Preferences` -> `Plugins` -> `Browse Repositories...` and search for `Git Time Metric`. 24 | 25 | **Note** - to enable time tracking for a Git repository, you need to initialize it with `gtm init` otherwise it will be ignored by GTM. This is done via the command line. You can run this within the JetBrains IDE terminal. 26 | ``` 27 | > cd /path/to/your/project 28 | > gtm init 29 | ``` 30 | 31 | Consult the [README](https://github.com/git-time-metric/gtm/blob/master/README.md) and [Wiki](https://github.com/git-time-metric/gtm/wiki) for more information. 32 | 33 | # Features 34 | 35 | ### Status Bar 36 | 37 | In the status bar see your total time spent for in-process work (uncommitted). 38 | 39 | ![](https://cloud.githubusercontent.com/assets/630550/16890959/329120bc-4ab9-11e6-930f-051522e7aacb.png) 40 | 41 | **Note** - the time shown is based on the file's path and the Git repository it belongs to. You can have several files open that belong to different Git repositories. The status bar will display the time for the current file's Git repository. Also keep in mind, a Git repository must be initialized for time tracking in order to track time. 42 | 43 | ### Command Line Interface 44 | 45 | Use the command line to report on time logged for your commits. 46 | 47 | Here are some examples of insights GTM can provide you. 48 | 49 | ##### $ gtm report -last-month 50 |
51 | 52 | ##### $ gtm report -last-month -format summary 53 |
54 | 55 | ##### $ gtm report -last-month -format timeline-hours 56 |

57 | 58 | GTM is automatic, seamless and lightweight. There is no need to remember to start and stop timers. It runs on occasion to capture activity triggered by your editor. The time metrics are stored locally with the git repository as [Git notes](https://git-scm.com/docs/git-notes) and can be pushed to the remote repository. 59 | 60 | # Support 61 | 62 | To report a bug, please submit an issue on the [GitHub Page](https://github.com/git-time-metric/gtm-jetbrains-plugin/issues) 63 | 64 | Consult the [README](https://github.com/git-time-metric/gtm/blob/master/README.md) and [Wiki](https://github.com/git-time-metric/gtm/wiki) for more information. 65 | -------------------------------------------------------------------------------- /src/io/edgeg/gtm/intellij/GTMStatusWidget.java: -------------------------------------------------------------------------------- 1 | package io.edgeg.gtm.intellij; 2 | 3 | import com.intellij.ide.ui.UISettings; 4 | import com.intellij.ide.ui.UISettingsListener; 5 | import com.intellij.openapi.fileEditor.FileEditorManager; 6 | import com.intellij.openapi.fileEditor.FileEditorManagerEvent; 7 | import com.intellij.openapi.project.Project; 8 | import com.intellij.openapi.vfs.VirtualFile; 9 | import com.intellij.openapi.wm.StatusBar; 10 | import com.intellij.openapi.wm.StatusBarWidget; 11 | import com.intellij.openapi.wm.impl.status.EditorBasedWidget; 12 | import com.intellij.util.Consumer; 13 | import com.intellij.util.ui.UIUtil; 14 | import org.apache.commons.lang.StringUtils; 15 | import org.jetbrains.annotations.NotNull; 16 | import org.jetbrains.annotations.Nullable; 17 | 18 | import java.awt.*; 19 | import java.awt.event.MouseEvent; 20 | import java.util.Objects; 21 | import java.util.concurrent.atomic.AtomicBoolean; 22 | 23 | public class GTMStatusWidget extends EditorBasedWidget implements StatusBarWidget.Multiframe, StatusBarWidget.TextPresentation { 24 | private static final String id = GTMStatusWidget.class.getName(); 25 | private final AtomicBoolean opened = new AtomicBoolean(); 26 | private String myText = ""; 27 | 28 | private GTMStatusWidget(@NotNull Project project) { 29 | super(project); 30 | } 31 | 32 | private void runUpdateLater() { 33 | UIUtil.invokeLaterIfNeeded(() -> { 34 | if (opened.get()) { 35 | runUpdate(); 36 | } 37 | }); 38 | } 39 | 40 | static GTMStatusWidget create(@NotNull Project project) { 41 | return new GTMStatusWidget(project); 42 | } 43 | 44 | @Override 45 | public StatusBarWidget copy() { 46 | return new GTMStatusWidget(myProject); 47 | } 48 | 49 | @NotNull 50 | @Override 51 | public String ID() { 52 | return id; 53 | } 54 | 55 | @Nullable 56 | @Override 57 | public WidgetPresentation getPresentation(@NotNull PlatformType platformType) { 58 | return this; 59 | } 60 | 61 | @NotNull 62 | @Override 63 | public String getText() { 64 | return myText; 65 | } 66 | 67 | @NotNull 68 | @Override 69 | public String getMaxPossibleText() { 70 | return ""; 71 | } 72 | 73 | void setText(String txt) { 74 | if (txt == null) { return; } 75 | if (Objects.equals(txt,"")) { myText = "GTM"; return; } 76 | myText = txt.replaceAll("\\s*\\d*s\\s*$", ""); 77 | myText = "GTM: " + StringUtils.join(myText.split("\\s+"), " "); 78 | } 79 | 80 | @Override 81 | public float getAlignment() { 82 | return Component.LEFT_ALIGNMENT; 83 | } 84 | 85 | @Nullable 86 | @Override 87 | public String getTooltipText() { 88 | if (!GTMRecord.gtmExeFound) { return GTMConfig.getInstance().gtmNotFound; } 89 | if (!GTMRecord.gtmVersionOK) { return GTMConfig.getInstance().gtmVerOutdated; } 90 | return "Git Time Metric (GTM)"; 91 | } 92 | 93 | @Override 94 | public void fileOpened(@NotNull FileEditorManager source, @NotNull VirtualFile file) { 95 | runUpdate(); 96 | } 97 | 98 | @Override 99 | public void fileClosed(@NotNull FileEditorManager source, @NotNull VirtualFile file) { 100 | runUpdate(); 101 | } 102 | 103 | @Override 104 | public void selectionChanged(@NotNull FileEditorManagerEvent event) { 105 | runUpdate(); 106 | } 107 | 108 | void updateStatusBar() { 109 | if (myStatusBar != null) { 110 | myStatusBar.updateWidget(ID()); 111 | } 112 | } 113 | 114 | private void runUpdate() { 115 | updateStatusBar(); 116 | } 117 | 118 | @Nullable 119 | @Override 120 | public Consumer getClickConsumer() { 121 | return mouseEvent -> runUpdate(); 122 | } 123 | 124 | @Override 125 | public void install(@NotNull StatusBar statusBar) { 126 | super.install(statusBar); 127 | myConnection.subscribe(UISettingsListener.TOPIC, uiSettings -> runUpdateLater()); 128 | } 129 | 130 | void installed() { 131 | opened.compareAndSet(false, true); 132 | } 133 | 134 | void uninstalled() { 135 | opened.compareAndSet(true, false); 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/io/edgeg/gtm/intellij/GTMRecord.java: -------------------------------------------------------------------------------- 1 | package io.edgeg.gtm.intellij; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import com.intellij.openapi.wm.StatusBar; 5 | import com.intellij.openapi.wm.WindowManager; 6 | import org.apache.commons.lang.StringUtils; 7 | 8 | import java.io.BufferedReader; 9 | import java.io.File; 10 | import java.io.IOException; 11 | import java.io.InputStreamReader; 12 | import java.nio.file.Paths; 13 | import java.util.Objects; 14 | import java.util.concurrent.ExecutorService; 15 | import java.util.concurrent.Executors; 16 | import java.util.concurrent.Future; 17 | 18 | class GTMRecord { 19 | private static final String GTM_VER_REQ = ">= 1.2.5"; 20 | 21 | private static final Long RECORD_MIN_THRESHOLD = 30000L; // 30 seconds 22 | private static final String RECORD_COMMAND = "record"; 23 | private static final String STATUS_OPTION = "--status"; 24 | private static final String VERIFY_COMMAND = "verify"; 25 | 26 | private static String gtmExePath = null; 27 | private static String lastRecordPath = null; 28 | private static Long lastRecordTime = null; 29 | 30 | static Boolean gtmExeFound = false; 31 | static Boolean gtmVersionOK = true; 32 | private static GTMConfig cfg = new GTMConfig(); 33 | 34 | private static ExecutorService executor = Executors.newSingleThreadExecutor(); 35 | private static Future recordTask; 36 | private static Long lastRunTime = null; 37 | private static final Long MAX_RUN_TIME = 2000L; // 2 seconds 38 | 39 | static void record(String path, Project project) { 40 | Runnable r = new Runnable() { 41 | public void run() { 42 | runRecord(path, project); 43 | } 44 | }; 45 | // GTMConfig.LOG.info(String.format( "Submit record %s", path)); 46 | submitRecord(r); 47 | } 48 | 49 | private static synchronized void submitRecord(Runnable r) { 50 | 51 | // is there a task running 52 | if (recordTask != null && !recordTask.isDone()) { 53 | // make sure it's not a hung process 54 | if (lastRunTime != null && System.currentTimeMillis() - lastRunTime > MAX_RUN_TIME) { 55 | // process is hung, cancel it 56 | recordTask.cancel(true); 57 | GTMConfig.LOG.warn("Record task was hung, task cancelled"); 58 | recordTask = executor.submit(r); 59 | } 60 | return; 61 | } 62 | 63 | // only submit a task if there isn't one running already 64 | if (recordTask == null || recordTask.isDone()) { 65 | recordTask = executor.submit(r); 66 | } 67 | } 68 | 69 | private static void runRecord(String path, Project project) { 70 | // GTMConfig.LOG.info(String.format("Run record %s", path)); 71 | 72 | String status; 73 | if (StringUtils.isBlank(path)) return; 74 | if (!gtmExeFound) { 75 | status = "Error!"; 76 | } else { 77 | try { 78 | Long currentTime = System.currentTimeMillis(); 79 | if (Objects.equals(lastRecordPath, path)) { 80 | if (lastRecordTime != null && currentTime - lastRecordTime <= RECORD_MIN_THRESHOLD) { 81 | return; 82 | } 83 | } 84 | lastRecordPath = path; 85 | lastRecordTime = currentTime; 86 | if (cfg.statusEnabled) { 87 | 88 | // GTMConfig.LOG.info( 89 | // String.format( 90 | // "Executing %s %s %s %s", 91 | // gtmExePath, RECORD_COMMAND, STATUS_OPTION, path)); 92 | 93 | Process process = new ProcessBuilder(gtmExePath, RECORD_COMMAND, STATUS_OPTION, path).start(); 94 | lastRunTime = System.currentTimeMillis(); 95 | BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); 96 | StringBuilder builder = new StringBuilder(); 97 | String line; 98 | while (null != (line = reader.readLine())) { 99 | builder.append(line); 100 | } 101 | status = builder.toString(); 102 | } else { 103 | 104 | // GTMConfig.LOG.info( 105 | // String.format( 106 | // "Executing %s %s %s", 107 | // gtmExePath, RECORD_COMMAND, path)); 108 | 109 | Process process = new ProcessBuilder(gtmExePath, RECORD_COMMAND, path).start(); 110 | lastRunTime = System.currentTimeMillis(); 111 | status = ""; 112 | } 113 | } catch (IOException e) { 114 | status = "Error!"; 115 | if (initGtmExePath()) { 116 | checkVersion(); 117 | } 118 | GTMConfig.LOG.warn(String.format( 119 | "Error executing %s %s %s", gtmExePath, RECORD_COMMAND, path), e); 120 | } 121 | } 122 | if (project != null) { 123 | StatusBar statusBar = WindowManager.getInstance().getStatusBar(project); 124 | GTMStatusWidget widget = (GTMStatusWidget) statusBar.getWidget(GTMStatusWidget.class.getName()); 125 | if (widget != null) { 126 | widget.setText(status); 127 | widget.updateStatusBar(); 128 | } 129 | } 130 | } 131 | 132 | static Boolean initGtmExePath() { 133 | String gtmExeName = System.getProperty("os.name").startsWith("Windows") ? "gtm.exe" : "gtm"; 134 | String[] gtmPath; 135 | StringBuilder pathVar = new StringBuilder(System.getenv("PATH")); 136 | 137 | if (System.getProperty("os.name").startsWith("Windows")) { 138 | // Setup an additional Windows user path 139 | String userWinBin = System.getProperty("user.home") + File.separator + "gtm"; 140 | gtmPath = new String[]{ 141 | Paths.get(System.getenv("ProgramFiles"), "gtm").toString(), 142 | Paths.get(System.getenv("ProgramFiles(x86)"), "gtm").toString(), 143 | userWinBin}; 144 | } else { 145 | // Setup additional common *nix user paths 146 | String userBin = System.getProperty("user.home") + File.separator + "bin"; 147 | String userLocalBin = System.getProperty("user.home") + File.separator + "local" + File.separator + "bin"; 148 | gtmPath = new String[]{"/usr/bin", "/bin", "/usr/sbin", "/sbin", "/usr/local/bin/", userBin, userLocalBin}; 149 | } 150 | 151 | for (String aGtmPath : gtmPath) { 152 | if (!pathVar.toString().contains(aGtmPath)) { 153 | pathVar.append(File.pathSeparator).append(aGtmPath); 154 | } 155 | } 156 | 157 | String result = null; 158 | String[] pathDirs = pathVar.toString().split(File.pathSeparator); 159 | for (String pathDir : pathDirs) { 160 | File exeFile = Paths.get(pathDir).resolve(gtmExeName).toFile(); 161 | if (exeFile.getAbsoluteFile().exists() && exeFile.getAbsoluteFile().canExecute()) { 162 | result = exeFile.getAbsolutePath(); 163 | break; 164 | } 165 | } 166 | gtmExeFound = (result != null); 167 | gtmExePath = result; 168 | if (!gtmExeFound) { 169 | GTMConfig.LOG.warn("Unable to find executable gtm in PATH"); 170 | } 171 | return gtmExeFound; 172 | } 173 | 174 | static Boolean checkVersion() { 175 | try { 176 | Process process = new ProcessBuilder(gtmExePath, VERIFY_COMMAND, GTM_VER_REQ).start(); 177 | BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); 178 | StringBuilder builder = new StringBuilder(); 179 | String line; 180 | while ( (line = reader.readLine()) != null) { 181 | builder.append(line); 182 | } 183 | gtmVersionOK = (Objects.equals(builder.toString(), "true")); 184 | } catch (IOException e) { 185 | GTMConfig.LOG.warn(String.format( 186 | "Error executing %s %s %s", gtmExePath, VERIFY_COMMAND, GTM_VER_REQ), e); 187 | gtmVersionOK = false; 188 | } 189 | return gtmVersionOK; 190 | } 191 | } 192 | --------------------------------------------------------------------------------