498 Commits

Author SHA1 Message Date
Eric Amodio
016c561ead Preps v4.0.0-beta.2 2017-06-07 12:55:07 -04:00
Eric Amodio
9cf86a41ec Adds setting to explicitly control telemetry
Disables zone.js monkey patching by application insights
2017-06-07 12:52:56 -04:00
Eric Amodio
4eb1c3e36a Renames interpolation method 2017-06-07 12:52:56 -04:00
Eric Amodio
95e0a6c71b Adds code lens range to debug info 2017-06-07 12:52:56 -04:00
Eric Amodio
35ca8106c9 Adds customizable code lens strings 2017-06-07 12:52:56 -04:00
Eric Amodio
948a75de79 Don't wait for settings message before continuing 2017-06-07 12:52:56 -04:00
Eric Amodio
4b0891b949 Adds settings migration support 2017-06-07 12:52:56 -04:00
Eric Amodio
a9d94868e7 Preps v4.0.0-beta 2017-06-07 12:52:56 -04:00
Eric Amodio
3c45c7e049 Anchors the code lens to the end of the line 2017-06-07 12:51:00 -04:00
Eric Amodio
ba0d55d5d4 Updates dependencies 2017-06-07 12:50:59 -04:00
Eric Amodio
d2dc172042 Attempts to fix #80 - on line with link, annotation gets underlined 2017-06-07 12:49:52 -04:00
Eric Amodio
e5e582d300 Fixes #81 - Current line annotation is too sticky 2017-06-07 12:49:52 -04:00
Eric Amodio
7c9e4b911c Adds better formatting of settings 2017-06-07 12:49:52 -04:00
Eric Amodio
42fdf9f327 Updates screenshots 2017-06-07 12:49:52 -04:00
Eric Amodio
547d50fed6 Adds another screenshot to README 2017-06-07 12:49:52 -04:00
Eric Amodio
6c33686335 Adds vscode issue TODO 2017-06-07 12:49:52 -04:00
Eric Amodio
28355d41b6 Adds more screenshots to README 2017-06-07 12:49:52 -04:00
Eric Amodio
6e5bb2343e Preps v4.0.0-alpha.2 2017-06-07 12:49:51 -04:00
Eric Amodio
d01c592533 Adds welcome message for first-time users 2017-06-07 12:48:22 -04:00
Eric Amodio
37e48ded2d Major refactor/rework -- many new features and breaking changes
Adds all-new, beautiful, highly customizable and themeable, file blame annotations
Adds all-new configurability and themeability to the current line blame annotations
Adds all-new configurability to the status bar blame information
Adds all-new configurability over which commands are added to which menus via the `gitlens.advanced.menus` setting
Adds better configurability over where Git code lens will be shown -- both by default and per language
Adds an all-new `changes` (diff) hover annotation to the current line - provides instant access to the line's previous version
Adds `Toggle Line Blame Annotations` command (`gitlens.toggleLineBlame`) - toggles the current line blame annotations on and off
Adds `Show Line Blame Annotations` command (`gitlens.showLineBlame`) - shows the current line blame annotations
Adds `Toggle File Blame Annotations` command (`gitlens.toggleFileBlame`) - toggles the file blame annotations on and off
Adds `Show File Blame Annotations` command (`gitlens.showFileBlame`) - shows the file blame annotations
Adds `Open File in Remote` command (`gitlens.openFileInRemote`) to the `editor/title` context menu
Adds `Open Repo in Remote` command (`gitlens.openRepoInRemote`) to the `editor/title` context menu
Changes the position of the `Open File in Remote` command (`gitlens.openFileInRemote`) in the context menus - now in the `navigation` group
Changes the `Toggle Git Code Lens` command (`gitlens.toggleCodeLens`) to always toggle the Git code lens on and off
Removes the on-demand `trailing` file blame annotations -- didn't work out and just ended up with a ton of visual noise
Removes `Toggle Blame Annotations` command (`gitlens.toggleBlame`) - replaced by the `Toggle File Blame Annotations` command (`gitlens.toggleFileBlame`)
Removes `Show Blame Annotations` command (`gitlens.showBlame`) - replaced by the `Show File Blame Annotations` command (`gitlens.showFileBlame`)
2017-06-07 12:48:22 -04:00
Eric Amodio
e3e7605268 Preps v3.6.1 2017-06-07 12:43:22 -04:00
Eric Amodio
f16c3857e5 Fixes zone.js monkey patching by application-insights 2017-06-07 12:42:33 -04:00
Eric Amodio
5298511bb9 Preps v3.6.0 2017-06-02 18:45:04 -04:00
Eric Amodio
4400ab1da9 Updates dependencies 2017-06-02 15:12:57 -04:00
Eric Amodio
6a9977b954 Fixes issue where the wrong diff line could be shown 2017-06-02 15:12:31 -04:00
Eric Amodio
c0d5f55baa Preps v3.6.0-beta 2017-05-27 04:33:27 -04:00
Eric Amodio
68f6ae8f3a Updates dependencies 2017-05-27 04:32:42 -04:00
Eric Amodio
53c691898f Changes behavior of diffWithWorking to always does what it says
Compares the current file with the working tree -- if the current file *is* the working file, it will show a `File matches the working tree` message
2017-05-27 04:26:51 -04:00
Eric Amodio
365af9c54b Changes behavior of diffWithPrevious to always does what it says
Compares the current file with the previous commit to that file
2017-05-27 04:26:12 -04:00
Eric Amodio
55b1a66ec0 Adds 'gitlens.diffWithWorking' status bar command option
Changes 'gitlens.diffWithPrevious' status bar command option behavior
2017-05-27 04:25:12 -04:00
Eric Amodio
522e5a49a2 Renames 'Compare with *' in quick pick menus
Renames 'Compare File with Previous Commit' command
Renames 'Compare Line with Previous Commit' command
Renames 'Compare Line with Working Tree' command
2017-05-27 04:20:25 -04:00
Eric Amodio
e99febb52d Adds diff info to active line hover always 2017-05-27 02:39:10 -04:00
Eric Amodio
2036c8abaf Preps v3.5.1 2017-05-25 01:39:16 -04:00
Eric Amodio
021a5b833a Fixes #71 - blame invalid on external edit 2017-05-25 01:31:24 -04:00
Eric Amodio
f1042de9c7 Stops some code lens actions when uncommitted 2017-05-25 01:30:28 -04:00
Eric Amodio
efd3d40aa8 Switches to use GitUris in more places 2017-05-25 01:05:18 -04:00
Eric Amodio
9c7062020e Fixes issue with blame on versioned files
Stops falling back to the cached blame of the working file
Handles git scheme urls properly
2017-05-25 00:49:36 -04:00
Eric Amodio
9da80c121b Debounces other active line events 2017-05-25 00:35:12 -04:00
Eric Amodio
5380724323 Preps v3.5.0 2017-05-24 23:01:28 -04:00
Eric Amodio
77651701aa Removes insiders flag from stash commands 2017-05-24 22:59:41 -04:00
Eric Amodio
bb834f2e0a Removes insiders flag from remotes 2017-05-24 22:58:54 -04:00
Eric Amodio
535e627048 Preps v3.5.0-beta.2 2017-05-24 13:02:02 -04:00
Eric Amodio
8a74950708 Fixes #40 - encoding issues
This is only a partial fix, as vscode doesn't allow enough controls to fix everything
2017-05-24 12:52:47 -04:00
Eric Amodio
3502bdf6c7 Adds more linting rules
Fixes lint issues
2017-05-23 18:48:04 -04:00
Eric Amodio
23db83832c Preps v3.5.0-beta 2017-05-23 01:30:58 -04:00
Eric Amodio
807b1f1ea3 Fixes #73 - doesn't work with Chinese filenames 2017-05-23 01:30:16 -04:00
Eric Amodio
6232b8087f Reworks CHANGELOG to http://keepachangelog.com spec 2017-05-22 23:58:56 -04:00
Eric Amodio
b783bfdf10 Updates dependencies 2017-05-22 21:15:01 -04:00
Eric Amodio
b8322f19cd Preps v3.5.0-beta 2017-05-22 18:03:29 -04:00
Eric Amodio
8623e661ca Changes the start of line decoration to be both only 2017-05-22 18:03:08 -04:00
Eric Amodio
c3bd17edee Adds new open in remote commands to README 2017-05-22 16:29:10 -04:00
Eric Amodio
b17640ba57 Changes commit search shortcut in README 2017-05-22 16:28:47 -04:00
Eric Amodio
f6a18458df Updates dependencies 2017-05-22 16:22:30 -04:00
Eric Amodio
e1b1b737d9 Constrains the active line hover to the start/end of a line 2017-05-22 16:17:20 -04:00
Eric Amodio
19e523d6e4 Adds diff info to the active line hover for uncommitted changes 2017-05-22 16:16:17 -04:00
Eric Amodio
ff1597d64f Refactors git command caching
Now caching many more commands to reduce git/parsing roundtrips and increase performance
2017-05-22 16:14:33 -04:00
Eric Amodio
49cc681520 Prepopulates commit search to the current line commit
If there is an active editor, otherwise will fall back to the clipboard
2017-05-22 16:08:07 -04:00
Eric Amodio
6d1a1ca346 Adds gitlens.openBranchInRemote command
Adds gitlens.openRepoInRemote command
2017-05-22 16:06:45 -04:00
Eric Amodio
49fa9b5078 Fixes issues with missing repoPath
Allows commit search without an active editor
2017-05-22 16:04:46 -04:00
Eric Amodio
ec6ca8188c Changes to Uncommitted change 2017-05-22 15:54:10 -04:00
Eric Amodio
034a01c7d1 Turns off console hijacking by appinsights 2017-05-17 00:35:30 -04:00
Eric Amodio
15ebde445d Change appinsights to load dynamically
appinsights seems to monkey patch even if explicitly disabled, so instead just only load it if needed
2017-05-16 00:38:08 -04:00
Eric Amodio
f184feda2c Removes the need for the outputLevel in debug 2017-05-15 23:16:25 -04:00
Eric Amodio
d35074ecf8 Changes file alt+right to be a diff on commit details quick pick
Changes file alt+right to be a diff on repo status quick pick
2017-05-15 03:09:54 -04:00
Eric Amodio
4d62e5cdad Reorders settings 2017-05-15 02:01:52 -04:00
Eric Amodio
ce5ff1eb47 Reduces git calls on known untrackables 2017-05-15 01:50:09 -04:00
Eric Amodio
3a38f6ae39 Increases debounce on cursor movement to reduce lag 2017-05-15 01:44:41 -04:00
Eric Amodio
3e403a2d5c Changes stash icons
Moves stash number from label to description
2017-05-14 03:27:08 -04:00
Eric Amodio
83fff1590d Fixes incorrect file selection on commit file details 2017-05-14 03:02:41 -04:00
Eric Amodio
27425e3deb Fixes typo in stash parsing 2017-05-14 03:02:09 -04:00
Eric Amodio
dbdb77c2c1 Fixes left alignment priority
Adds live update support for status bar alignment
Adds values to description
Adds to README
2017-05-14 02:18:22 -04:00
Zack Schuster
263bdc728a Makes status bar alignment configurable 2017-05-14 02:03:04 -04:00
Eric Amodio
0ef1e509a6 Moves menu group down 2017-05-14 01:51:08 -04:00
Eric Amodio
9a35fc6b47 Changes commit search shortcut to alt+/ 2017-05-14 01:50:00 -04:00
Eric Amodio
078f3fd482 Updates dependencies 2017-05-14 01:49:33 -04:00
Eric Amodio
33ef9687f2 Adds clipboard default into commit search 2017-05-14 01:48:53 -04:00
Eric Amodio
361789f859 Fixes incorrect command 2017-05-14 01:48:26 -04:00
Eric Amodio
1acc183621 Refactors commands to use typed args objects 2017-05-14 01:48:07 -04:00
Eric Amodio
ee29596d45 Enables typescript strict mode
Fixes all the compile/lint issues
2017-05-11 02:18:04 -04:00
Eric Amodio
90245b1111 Excludes images from package (except the icons) 2017-05-07 02:01:38 -04:00
Eric Amodio
a69c83f64f Adds ExtensionOutputChannelName to constants 2017-05-07 02:00:04 -04:00
Eric Amodio
6f04514a27 Updates to vscode v1.12 2017-05-07 01:58:53 -04:00
Eric Amodio
6eccb2a8da Updates dependencies 2017-05-07 01:58:19 -04:00
Eric Amodio
d86b18bc7a Preps v3.4.9 2017-05-03 00:36:14 -04:00
Eric Amodio
3f5798357e Updates dependencies 2017-05-03 00:36:14 -04:00
Eric Amodio
23065e1177 Adds better support for deleted files 2017-05-03 00:36:07 -04:00
Eric Amodio
dba7c2ea52 Updates the issue template with more instructions 2017-05-03 00:26:01 -04:00
Eric Amodio
8f40ba8542 Preps v3.4.8 2017-05-02 03:05:54 -04:00
Eric Amodio
b477b34278 Changes display name in the marketplace to Git Lens because of the marketplace search ranking algorithm 2017-05-02 03:04:55 -04:00
Eric Amodio
a8b0aaa3e3 Preps v3.4.6 2017-05-01 14:22:18 -04:00
Eric Amodio
071bfdb82e Adds better support for deleted files
Will now open the file from the previous commit
2017-05-01 14:04:20 -04:00
Eric Amodio
474741aeb4 Fixes incorrect file selection when showing commit details 2017-05-01 14:04:20 -04:00
Eric Amodio
187f7881ca Adds safety for debounced timing 2017-05-01 14:04:10 -04:00
Eric Amodio
14b9d93807 Saves the found git path to avoid constant searchs 2017-05-01 13:18:13 -04:00
Eric Amodio
ba346da54b Adds linting to compile step
Reorgs tasks
Fixes lint issues
2017-05-01 13:17:50 -04:00
Eric Amodio
564856853a Pins & updates dependencies 2017-04-30 16:05:33 -04:00
Eric Amodio
3e0ad47698 Fixes typescript syntax error 2017-04-22 09:28:09 -04:00
Eric Amodio
675050d923 Adds error logging 2017-04-22 09:27:44 -04:00
Eric Amodio
6af753c0ae Renames *.advanced.codeLens.debug to *.codeLens.debug
Renames *.advanced.debug to *.debug
Renames *.output.level to *.outputLevel
2017-04-22 09:27:30 -04:00
Eric Amodio
bb59f2899a Adds const for ExtensionKey 2017-04-22 09:19:55 -04:00
Eric Amodio
cfafa8c020 Updates dependencies 2017-04-22 09:16:29 -04:00
Eric Amodio
f99ba89a4b Stops Git from leaking out of GitService 2017-04-14 00:29:57 -04:00
Eric Amodio
8f2ec85c6b Stops throwing for a common error case 2017-04-13 23:56:12 -04:00
Eric Amodio
b58c48841f Only reset code lens if its config changes 2017-04-13 14:40:02 -04:00
Eric Amodio
d3800a1ae8 Adds an issue template 2017-04-13 00:50:43 -04:00
Eric Amodio
4a379511d5 Preps v3.4.5 - more fun with the marketplace :( 2017-04-13 00:23:36 -04:00
Eric Amodio
e5a94bb2ff Preps v3.4.4 - re-adds more tags 2017-04-12 21:32:17 -04:00
Eric Amodio
224200e32a Reduces to only 5 allowed tags 2017-04-12 21:25:59 -04:00
Eric Amodio
a285fb5505 Preps v3.4.2 - marketplace changes for better ranking 🤞 2017-04-12 21:23:51 -04:00
Eric Amodio
cd5faa1336 Preps v3.4.1 2017-04-12 18:54:03 -04:00
Eric Amodio
c09deb54b1 Removes keywords as they seem to hurt more than help 2017-04-12 18:53:48 -04:00
Eric Amodio
bc747e9d5f Preps v3.4.0 2017-04-12 18:37:40 -04:00
Eric Amodio
ce0842f41c Allows toggle CodeLens command to work when set to auto 2017-04-12 18:32:21 -04:00
Eric Amodio
0cf107a817 Removes Emitter from event emitter names 2017-04-12 18:21:13 -04:00
Eric Amodio
5fc567372c Fixes compile errors from Typescript upgrade 2017-04-12 18:18:46 -04:00
Eric Amodio
0875cc12d5 Adds control over the active line annotation colors 2017-04-12 17:06:39 -04:00
Eric Amodio
04a6574f16 Stops certain keybindings from being too context sensative 2017-04-12 17:03:18 -04:00
Eric Amodio
25696ffa94 Crops left edge to remove the unwanted noise 2017-04-12 16:44:07 -04:00
Eric Amodio
feea0e62fc Adds more content & cosmentic changes 2017-04-12 16:41:57 -04:00
Eric Amodio
c5993fe145 Completely overhauls messaging & README 2017-04-12 16:23:45 -04:00
Eric Amodio
67dfcb6226 Updates dependencies 2017-04-12 16:22:45 -04:00
Eric Amodio
7c0bc78574 Adds File into file based comparision commands 2017-04-12 16:22:24 -04:00
Eric Amodio
8fb61e7ab5 Switches the order of Show Commit Search 2017-04-12 16:21:25 -04:00
Eric Amodio
b38f357bc2 Fixes another copy/paste description issue 2017-04-12 16:20:54 -04:00
Eric Amodio
6db6eff6b7 Fixes issue with commands missing from the palette 2017-04-12 11:00:56 -04:00
Eric Amodio
c05fcdc1bd Removes bogus command 2017-04-12 10:59:37 -04:00
Eric Amodio
d7b28e1a56 Fixes copy/paste error in the setting description 2017-04-12 10:59:03 -04:00
Eric Amodio
065300be06 Fixes issue with open commit in remote not working 2017-04-12 10:58:14 -04:00
The Gitter Badger
9afa06c00d Adds a Gitter chat badge to README.md (#64)
Adds Gitter badge
2017-04-11 02:43:58 -04:00
Eric Amodio
99aa4ee74b Preps v3.3.3 2017-04-10 13:12:13 -04:00
Eric Amodio
89474a2aa6 Fixes issue with newlines in commit messages 2017-04-10 13:10:25 -04:00
Eric Amodio
35694303c4 Preps v3.3.2 2017-04-10 12:53:56 -04:00
Eric Amodio
a5be6f0533 Closes #63 - Switch commit message and author in commit pick list 2017-04-10 12:50:23 -04:00
Eric Amodio
e5653d1c66 Removes obsolete character settings 2017-04-10 12:38:28 -04:00
Eric Amodio
7643e338c5 Preps v3.3.1 2017-04-09 11:01:31 -04:00
Eric Amodio
0854e0bcfb Changes sha terminology to commit id 2017-04-09 10:58:42 -04:00
Eric Amodio
2a9b274920 Changes search prefixes
Fixes issue with author searching
2017-04-09 10:58:28 -04:00
Eric Amodio
82bd510593 Preps v3.3.0 2017-04-09 01:25:10 -04:00
Eric Amodio
b334d5ff66 Adds stashed changes command to repo status 2017-04-09 01:22:02 -04:00
Eric Amodio
9fed706fd2 Adds commit search command to branch history 2017-04-09 01:19:14 -04:00
Eric Amodio
4707b0640d Adds go back support to stash apply, delete, & save 2017-04-09 01:18:45 -04:00
Eric Amodio
3aab904aaf Fixes go back support 2017-04-09 01:17:32 -04:00
Eric Amodio
0d19224a97 Fixes issue with the current & go back commands 2017-04-09 01:16:19 -04:00
Eric Amodio
a7dc29a9aa Adds search commits command
Search by message, author, file pattern, or sha
2017-04-09 00:05:15 -04:00
Eric Amodio
7cb1b9d0f1 Fixes #59 - Updates command context to opened file state
Removes insiders restriction from Open in Remote commands
2017-04-08 15:07:51 -04:00
Eric Amodio
b70ffbdeee Adds ability to suppress update notifications 2017-04-08 14:49:41 -04:00
Eric Amodio
9292fc23a8 Preps v3.2.1 2017-04-07 11:10:36 -04:00
Eric Amodio
6a3428e6d2 Fixes #57 - no more blank message w/o a diff.tool 2017-04-07 11:09:15 -04:00
Eric Amodio
4c4d21741e Prep v3.2.0 2017-04-03 22:52:06 -04:00
Eric Amodio
d59f4ef6dc Changes diff to compare 2017-04-01 02:49:13 -04:00
Eric Amodio
0c13050387 Addresses #57 - adds warning if no diff.tool 2017-04-01 02:48:53 -04:00
Eric Amodio
3b195b6be2 Fixes missing context 2017-04-01 02:32:39 -04:00
Eric Amodio
b961646c95 Adds support for single files 2017-04-01 02:08:12 -04:00
Eric Amodio
d3af67b21b Attempts to fix #58 - work with sub-modules
Also fixes issue with nested repos
2017-04-01 00:58:09 -04:00
Eric Amodio
7ce5a396a1 Updates remotes context based on the active editor 2017-04-01 00:54:02 -04:00
Eric Amodio
c36bc2692d Changes remotes to be cached by repoPath 2017-04-01 00:53:20 -04:00
Eric Amodio
276c24ac24 Adds fromFileStatus to GitUri 2017-03-30 00:56:48 -04:00
Eric Amodio
aff8aff011 Preps v3.1.0 2017-03-29 02:09:28 -04:00
Eric Amodio
ab2bf893e1 Fixes incorrect counts in upstream status 2017-03-29 01:57:46 -04:00
Eric Amodio
d8564c215c Adds experimental 'Stash Changes' command
Adds experimental 'Stash Changes' to stash list
Adds experimental 'Stash Unstaged Changes' to stash list
2017-03-29 01:50:16 -04:00
Eric Amodio
e3c9fd53ca Changes quick pick item structure of the stash list 2017-03-29 01:50:15 -04:00
Eric Amodio
640a11b3cb Changes commit type repo to branch 2017-03-29 01:50:15 -04:00
Eric Amodio
9aadf73407 Removes some commands when dealing with a stash 2017-03-29 01:50:15 -04:00
Eric Amodio
7b63d4f437 Uses stash name in place of a shorten sha 2017-03-29 01:50:15 -04:00
Eric Amodio
0686c882c8 Use 'stashed changes' terminology 2017-03-29 01:50:15 -04:00
Eric Amodio
0dda92cf8d Uses shortSha in git uris 2017-03-29 01:50:15 -04:00
Eric Amodio
dcbf70682e Changes placeholder for consistency with other quick picks 2017-03-29 01:50:15 -04:00
Eric Amodio
2a70e5b4cb Adds newlines to console output
(not sure when this changed)
2017-03-29 01:50:15 -04:00
Eric Amodio
9945ee6d94 Adds 'Show Stashed Changes` command
Adds experimental 'Apply Stashed Changes' command
Adds experimental 'Delete Stashed Changes' to stashed changes quick pick
2017-03-29 01:49:26 -04:00
Eric Amodio
19fe22f061 Switches to use repoPath on GitService 2017-03-28 16:42:35 -04:00
Eric Amodio
8b0748608d Renames commands/commands to commands/common
Renames quickpicks/quickpicks to quickpicks/common
Moves git quick picks into common and other quick pick files
2017-03-28 16:19:57 -04:00
Eric Amodio
aa39792843 Moves type to GitCommit for better consistency 2017-03-28 15:10:00 -04:00
Eric Amodio
073353dcda Refactors log parsing (a tiny bit) 2017-03-28 11:31:22 -04:00
Eric Amodio
296e562d9f Unifies file status model 2017-03-28 10:41:32 -04:00
Eric Amodio
d0b4c2fd5c Preps v3.0.5 2017-03-28 02:13:20 -04:00
Eric Amodio
8a0e27b7df Adds line support to Open File in Remote command 2017-03-28 02:12:15 -04:00
Eric Amodio
a724244914 Updates dependencies 2017-03-28 01:29:09 -04:00
Eric Amodio
851522f593 Adds GitLab remote link support
Adds Bitbucket remote link support
Adds Visual Studio Team Services remote link support
2017-03-28 01:28:03 -04:00
Eric Amodio
ab417eadbe Fixes error when there is no branches yet 2017-03-28 01:26:53 -04:00
Eric Amodio
9071b55026 Adds renamed file info into quick pick description 2017-03-28 00:35:06 -04:00
Eric Amodio
6d8a37a10f Fixes #56 - Handle file names with spaces 2017-03-28 00:24:55 -04:00
Eric Amodio
9d176db16a Preps v3.0.4 2017-03-27 13:59:00 -04:00
Eric Amodio
e7604ed863 Tweaks telemetry a bit 2017-03-27 13:53:50 -04:00
Eric Amodio
e1190d50a4 Stops re-throw of know errors 2017-03-27 12:08:53 -04:00
Eric Amodio
5c2712bcb7 Uses branch rather than attempting to use sha 2017-03-27 12:08:35 -04:00
Eric Amodio
61f002420a Switches to non-strict iso date format for compatibility
Older git versions don't support strict iso dates
2017-03-27 11:44:35 -04:00
Eric Amodio
f632829822 Comments out unused props 2017-03-27 11:43:46 -04:00
Eric Amodio
de8ce58e09 Preps v3.0.3 2017-03-27 11:03:16 -04:00
Eric Amodio
46ff70e969 Fixes #55 - adds fallback for previous git versions
Reverts git version requirement to >= 2.2.0
2017-03-27 11:01:50 -04:00
Eric Amodio
758d331e69 Fixes parsing issue with merge commits 2017-03-27 10:55:47 -04:00
Eric Amodio
ace0ac7018 Preps v3.0.2 2017-03-27 03:38:00 -04:00
Eric Amodio
81cfec1cac Updates required git version 2017-03-27 03:37:10 -04:00
Eric Amodio
29ef718f64 Preps v3.0.1 2017-03-27 02:30:17 -04:00
Eric Amodio
4e67a84531 Adds basic telemetry 2017-03-27 02:18:44 -04:00
Eric Amodio
291c53cd19 Refactors commands to utilize a common base 2017-03-27 02:17:09 -04:00
Eric Amodio
e0b3dcd484 Refactors Logger.error to take the Error object 2017-03-27 02:15:38 -04:00
Eric Amodio
c500293032 Removes emptry string defaults 2017-03-27 02:11:55 -04:00
Eric Amodio
e1349bb1c6 Preps v3.0.0 2017-03-26 14:18:49 -04:00
Eric Amodio
e33fd00119 Updates dependencies 2017-03-26 13:52:14 -04:00
Eric Amodio
3856cfd110 Fixes issues with merge commits 2017-03-26 13:50:04 -04:00
Eric Amodio
8b5eed4714 Fixes issues with next commit navigation (renames)
Adds sha to log model to know if it is a full log or not
2017-03-25 00:44:10 -04:00
Eric Amodio
c10a79a7ee Consolidates certain getLogForFile usage patterns into getLogCommit 2017-03-24 17:15:07 -04:00
Eric Amodio
ee40dc6325 Allows dynamic switching of insiders 2017-03-24 14:25:37 -04:00
Eric Amodio
0aab16357c Rearranges context menu commands 2017-03-24 14:10:52 -04:00
Eric Amodio
e859f697ec Uses current branch when opening remote file
Adds current branch name to quick pick description
2017-03-24 13:32:32 -04:00
Eric Amodio
e906bfcb9e Hides remote commands when there are no remotes 2017-03-24 11:41:20 -04:00
Eric Amodio
a90d3f98de Changes config name to insiders 2017-03-24 11:32:47 -04:00
Eric Amodio
964cf2d5dd Stops caching on reverse log 2017-03-24 03:38:31 -04:00
Eric Amodio
ea2adcd919 Hides commands from palette when unavailable 2017-03-24 03:38:10 -04:00
Eric Amodio
0feaf285cd Renames hosting to remote 2017-03-24 03:37:22 -04:00
Eric Amodio
4d1cfd6413 Adds experimental commands for Open in GitHub 2017-03-24 03:00:18 -04:00
Eric Amodio
4f84c03275 Adds experimental support for Open in GitHub 2017-03-24 01:40:09 -04:00
Eric Amodio
ba69a19eeb Removes default export 2017-03-23 17:33:11 -04:00
Eric Amodio
5a0a2f3b61 Adds update notification
Adds don't show again option to invalid git version notification
2017-03-22 18:08:16 -04:00
Eric Amodio
0a7a88c302 Updates clean working tree message 2017-03-22 15:56:55 -04:00
Eric Amodio
1fb2826f5c Fixes pluralization 2017-03-22 15:51:45 -04:00
Eric Amodio
b9fb4bea56 Preps v2.13.0 2017-03-22 03:16:06 -04:00
Eric Amodio
9867e7065d Adds Show Branch History command
Renames Show Repository History to Show Current Branch History
Doesn't migrate data yet
2017-03-22 03:09:13 -04:00
Eric Amodio
43e4337358 Fixes more issues with paths :( 2017-03-22 03:04:52 -04:00
Eric Amodio
671f1ca6c1 Reworks upstream status info in quick pick 2017-03-22 01:32:37 -04:00
Eric Amodio
dd0762152f Adds file status roll up to changed files quick pick item 2017-03-22 00:56:58 -04:00
Eric Amodio
823e93080e Stops parsing at maxCount (with reverse) 2017-03-22 00:56:35 -04:00
Eric Amodio
33debb6bb2 Fixes parsing issue with commits with no files 2017-03-22 00:55:58 -04:00
Eric Amodio
0c348cdb0f Fixes exception when commit has no file 2017-03-22 00:55:01 -04:00
Eric Amodio
2f0a25720a Removes unused data from git log 2017-03-22 00:54:29 -04:00
Eric Amodio
4e3ccd9581 Fixes issues with paths on Windows 2017-03-22 00:50:06 -04:00
Eric Amodio
97f88489a4 Fixes pathing issues on Windows 2017-03-21 14:01:36 -04:00
Eric Amodio
3e84ec88e2 Removes non-working alt command 2017-03-21 14:01:24 -04:00
Eric Amodio
a5d1d74d7b Adds show last quick pick command 2017-03-20 12:05:45 -04:00
Eric Amodio
d53caa2137 Adds upstream status info to status quick pick (wip) 2017-03-19 01:57:44 -04:00
Eric Amodio
49a8896571 Updates dependencies 2017-03-19 01:30:16 -04:00
Eric Amodio
7a51946eda Adds working filename detection method
Adds get current branch method
Fixes diff with working tree when file was renamed
Fixes various quick pick commands when file was renamed
Adds branch support to ShowQuickRepoHistory
Adds branch info to repo quick pick placeholder
Adds Show Branch History to commit limited branch history quick pick
Adds Show File History to commit limited file history quick pick
Removes conditional display of commit details on commit file details quick pick
Removes conditional display of show file history on commit file details quick pick
Fixed #30 - Diff with Working Tree fails from repo/commit quickpick list if file was renamed (and the commit was before the rename)
2017-03-19 01:29:38 -04:00
Eric Amodio
ef74ae0950 Refactors git models & parsers
Adds full git status parsing
Adds git status info into status quick pick
Switches to async/await in file blame/log
2017-03-19 00:36:51 -04:00
Eric Amodio
14eebbba15 Changes Repository History to Branch History 2017-03-19 00:23:05 -04:00
Eric Amodio
1c5e627f8e Fixes issues with caching disabled 2017-03-18 11:22:33 -04:00
Eric Amodio
343d2f9be1 Adds branch quick pick to directory compare command 2017-03-18 11:06:30 -04:00
Eric Amodio
73bbbc1d5f Adds compare with branch command
Adds branches quick pick
2017-03-18 02:01:25 -04:00
Eric Amodio
164cb2bfe0 Refactors GitService to mostly use GitUris 2017-03-18 01:15:50 -04:00
Eric Amodio
b51d25829b Adds support for ranged quick file history
Fixes ranged diffWithPrevious command execution via CodeLens
2017-03-17 19:14:57 -04:00
Eric Amodio
9a0ce83260 Renames git methods to align better with git commands 2017-03-17 19:12:34 -04:00
Eric Amodio
036f0639aa Removes commented out code 2017-03-17 19:10:03 -04:00
Eric Amodio
14604435db Changes check-in to commit 2017-03-17 19:09:45 -04:00
Eric Amodio
7c01f40b3c Shortens shas for display in explorer UIs 2017-03-17 19:07:31 -04:00
Eric Amodio
dad85b3c0a Adds arbitrary branch support to getVersionedFile[Text] 2017-03-17 16:28:26 -04:00
Eric Amodio
332b2c3b91 Renames GitProvider to GitService 2017-03-17 14:12:09 -04:00
Eric Amodio
f6bde72baf Fixes #50 - add CodeLens excludes for html & vue files 2017-03-15 15:54:11 -04:00
Eric Amodio
6074a06fc9 Adds marketplace badges 2017-03-15 13:04:11 -04:00
Eric Amodio
937c56e554 Preps v2.12.1 2017-03-15 12:39:50 -04:00
Eric Amodio
b6ce03b806 Updates dependencies 2017-03-15 12:21:53 -04:00
Eric Amodio
4898c11eb5 Adds setting to control CodeLens debug info 2017-03-15 12:21:37 -04:00
Eric Amodio
9c2269b9f1 Adds parent sha parsing 2017-03-15 12:06:54 -04:00
Eric Amodio
2ef6c37c89 Fixes issue showing repo history w/ no active editor 2017-03-15 12:00:33 -04:00
Eric Amodio
564a0b8f64 Merge pull request #49 from nobitagit/typo-quickfix
Fix small typo in Blame Annotation
2017-03-13 11:59:52 -04:00
Aurelio Ogliari
b61e714110 Fix small typo in Blame Annotation 2017-03-13 15:46:59 +00:00
Eric Amodio
a58d643ca1 Preps v2.12.0 2017-03-13 00:34:33 -04:00
Eric Amodio
3656d4e8a4 Adds keyboard support to page in repo/file quick picks 2017-03-13 00:15:14 -04:00
Eric Amodio
3e815f6bf8 Switches to not show merge commits on file history 2017-03-12 03:07:11 -04:00
Eric Amodio
487fb5197c Adds paging support to repo/file quick picks
Adds keyboard support to page in repo/file quick picks
Adds progress indicator for repo/file quick picks
Completely reworks keyboard scopes
2017-03-12 01:15:44 -05:00
Eric Amodio
89a2471736 Changes GitUri to have the full path (like normal Uris) 2017-03-12 01:12:49 -05:00
Eric Amodio
b40ad7eced Tightens up debugging CodeLens 2017-03-11 16:01:45 -05:00
Eric Amodio
7aefd178c2 Adds paging support to repo/file history quick picks (wip) 2017-03-11 15:58:21 -05:00
Eric Amodio
a2a3f1a81e Adds commit navigation in quick pick lists via alt+, alt+.
Reworks keyboard context
2017-03-11 04:14:47 -05:00
Eric Amodio
f499bffbc6 Stops repo log from limiting to a single sha
Gets log reverse to work
2017-03-11 04:10:20 -05:00
Eric Amodio
3435ada188 Adds fileNames property
Changes fileName to be the first file if multiples
2017-03-11 00:42:18 -05:00
Eric Amodio
1b21893d72 Deals with merge commits in next sha tracking 2017-03-11 00:41:36 -05:00
Eric Amodio
a37a80d704 Exposes Keys
Adds keyboard logging
2017-03-11 00:40:52 -05:00
Eric Amodio
e7fedb3c51 Removes unnecessary public 2017-03-11 00:37:00 -05:00
Eric Amodio
762fa545c7 Switches everything to use full shas 2017-03-10 22:26:48 -05:00
Eric Amodio
df838e883a Changes shortcut keys for diff with previous commands
Adds diff with next command
Fixes #45 - Keyboard Shortcut collision with Project Manager
Preps v2.11.2
2017-03-10 13:55:42 -05:00
Eric Amodio
0480477136 Consolidates setContext into commands
Adds context for toggling CodeLens
2017-03-10 12:37:20 -05:00
Eric Amodio
88c09bec6d Preps v2.11.1 2017-03-10 12:03:33 -05:00
Eric Amodio
53da45fe4f Beefs up whitespace toggling robustness 2017-03-10 12:01:34 -05:00
Eric Amodio
edcb13aaaa Adds blame and active line annotation support to git diff split view (right side)
Adds command (compare, copy sha/message, etc) support to git diff split view (right side)
2017-03-10 12:01:02 -05:00
Eric Amodio
eaea44872c Refactors commit quick pick commands
Splits showQuickCommitDetails into showQuickCommitDetails and showQuickCommitFileDetails
Adds closeUnchangedFiles command
Adds openChangedFiles command
Adds diffDirectory command
Adds contextual description to the `go back` commands
Fixes #44 by adding a warning message about Git version requirements
Fixes intermittent errors when adding active line annotations
Fixes intermittent errors when opening multiple files via quick picks
Updates dependencies
Preps v2.11.0
2017-03-09 02:22:38 -05:00
Eric Amodio
1c29fa3f33 Preps v2.10.1 2017-03-05 15:50:13 -05:00
Eric Amodio
037446f38e Fixes #43 - File-level CodeLens must use full file blame 2017-03-05 15:45:01 -05:00
Eric Amodio
c05fd0b976 Adds debugging info to CodeLens 2017-03-05 15:42:11 -05:00
Eric Amodio
f1ec0ec0b2 Fixes issue with output channel logging 2017-03-05 15:41:16 -05:00
Eric Amodio
0958c152fe Removes single quote escaping as it is no longer needed 2017-03-05 11:04:48 -05:00
Eric Amodio
9f91f851c9 Preps v2.10.0 2017-03-03 12:48:06 -05:00
Eric Amodio
aeab246e0a Fixes issue with undo not causing annotations to reappear
Might address #42
2017-03-03 12:47:13 -05:00
Eric Amodio
4da21c3cc1 Adds blame and active line annotation support to git diff split view
Adds command (compare, copy sha/message, etc) support to git diff split view
Fixes #41 - Toggle Blame annotations on compare files page
2017-03-03 12:43:50 -05:00
Eric Amodio
a3a4a5bc49 Fixes missing diff line w/ previous in context menu 2017-03-03 11:31:25 -05:00
Eric Amodio
c5c1e24c62 Fixes issue with disabled caching 2017-03-03 11:29:56 -05:00
Eric Amodio
bddf89bc35 Updates README 2017-03-03 03:43:02 -05:00
Eric Amodio
1519898dfa Changes behaviors when file has unsaved changes:
- Status bar blame information will hide
  - CodeLens change to a `Cannot determine...` message and become unclickable
  - Many menu choices and commands will hide
Fixes #36 - Blame information is invalid when a file has unsaved changes
Fixed #38 - Toggle Blame Annotation button shows even when it isn't valid
Preps v2.9.0
2017-03-03 03:35:29 -05:00
Eric Amodio
d389a7b588 Stops using default exports 2017-03-03 02:44:07 -05:00
Eric Amodio
2e2462dd46 Fixes #39 - adds date options for status bar blame
Adds data formatting option to blame annotations
Preps v2.8.2
2017-03-02 12:49:52 -05:00
Eric Amodio
cf04a982e4 Updates date setting information 2017-03-02 10:53:39 -05:00
Eric Amodio
628f0c3cc1 Preps v2.8.1 2017-03-01 14:21:13 -05:00
Eric Amodio
d502694ba6 Fixes 'Compare with *' commands failing w/ no active editor 2017-03-01 14:20:01 -05:00
Eric Amodio
098d356652 Preps v2.8.0 2017-03-01 02:15:13 -05:00
Eric Amodio
6bff9a8f7b Fixes issue with CodeLens on version files 2017-03-01 02:05:30 -05:00
Eric Amodio
796730c22f Allows line blame annotations on gitlens-git schemes 2017-03-01 02:05:10 -05:00
Eric Amodio
5bdb5a1847 Fixes issue with incorrect uris 2017-03-01 02:04:15 -05:00
Eric Amodio
f837de7430 Fixes issue with copy to clipboard with no active editor 2017-03-01 01:33:12 -05:00
Eric Amodio
3caeb2ca9b Adds diffStatus command to use with finding renames 2017-03-01 01:27:38 -05:00
Eric Amodio
5e19cc6adf Fixes filename parsing with git-log
Adds originalFilename support to git-log enrichment
2017-03-01 01:27:12 -05:00
Eric Amodio
74845f979b Fixes #34 - Open file should open the selected version of the file
Renames current Open File command to  Open Working File
Renames current Open Files command to  Open Working Files
Adds new Open File command to open the commit version of the file
Adds new Open Files command to open the commit version of the files
2017-03-01 01:26:27 -05:00
Eric Amodio
a98f400375 Adds alt+left and alt+right keyboarding for quickpicks 2017-03-01 01:17:15 -05:00
Eric Amodio
1a244c6296 Fixes pinned editor issues 2017-03-01 01:14:15 -05:00
Eric Amodio
dfd0f8c365 Refactors command & quickpick imports 2017-03-01 01:13:53 -05:00
Eric Amodio
226e98dd04 Adds xclip dependency to README 2017-02-28 11:19:19 -05:00
Eric Amodio
3c8fe9efb8 Refactors quick pick lists 2017-02-28 01:58:14 -05:00
Eric Amodio
41fd06e491 Preps v2.7.1 2017-02-27 11:15:00 -05:00
Eric Amodio
ccd0ad67a3 Adds proper support for multiline commit messages
Fixes #33 - commit messages needs to be escaped
2017-02-27 11:13:42 -05:00
Eric Amodio
aff36efeca Preps v2.7.0 2017-02-27 02:55:05 -05:00
Eric Amodio
0f0a653c4c Adds status information to log commits
Adds status info to commit details quick pick
2017-02-27 02:50:33 -05:00
Eric Amodio
73c58bc923 Changes Show Commit History to Show File History
Changes Show Previous Commit History to Show Previous File History
2017-02-27 00:59:30 -05:00
Eric Amodio
5ffd4753bd Changes debugging output to be only when verbose 2017-02-27 00:58:54 -05:00
Eric Amodio
05ff9804bc Fixes issue with . showing in the path of quick picks 2017-02-27 00:58:21 -05:00
Eric Amodio
00e9660227 Fixes issue with repository status without changes 2017-02-27 00:53:53 -05:00
Eric Amodio
66d11c37e2 Adds clipboard copy sha to commit files quick pick list
Adds clipboard copy message to commit files quick pick list
2017-02-27 00:08:53 -05:00
Eric Amodio
780423b195 Fixes logging to clean up on extension deactivate 2017-02-26 23:50:29 -05:00
Eric Amodio
a552332234 Preps v2.6.0 2017-02-25 02:23:14 -05:00
Eric Amodio
0a4cdd81eb Adds new show repository status command 2017-02-25 02:20:23 -05:00
Eric Amodio
bcfb0cd24d Fixes repoPath issue 2017-02-25 02:19:56 -05:00
Eric Amodio
a2f4c4c953 Moves Commands into commands file 2017-02-25 02:19:40 -05:00
Eric Amodio
237e464c44 Updates dependencies 2017-02-22 21:32:10 -05:00
Eric Amodio
7100c8f938 Preps v2.5.6 2017-02-22 04:26:12 -05:00
Eric Amodio
85826eb091 Fixes #32 - 00000000 Uncommitted changes distracting 2017-02-22 04:25:21 -05:00
Eric Amodio
b530c3d6a2 Updates dependencies 2017-02-22 01:49:39 -05:00
Eric Amodio
b81f2f2fa0 Preps v2.5.5 2017-02-22 01:48:31 -05:00
Eric Amodio
add76fc5fc Fixes #25 - blame isn't updated on git operations 2017-02-22 01:46:37 -05:00
Eric Amodio
ecaf7cd45c Adds tslint to package ignore 2017-02-22 01:14:02 -05:00
Eric Amodio
7bdeae8d83 Preps v2.5.4 2017-02-20 13:35:15 -05:00
Eric Amodio
b637678d34 Fixes extra spacing 2017-02-20 13:34:31 -05:00
Eric Amodio
84bc19318d Preps v2.5.3 2017-02-20 13:16:31 -05:00
Eric Amodio
9da3d26fc6 Fixes #27 unicode in annotations is broken 2017-02-20 11:15:56 -05:00
Eric Amodio
a6d321590d Updates deprecated property 2017-02-19 18:25:16 -05:00
Eric Amodio
de98e6bef4 Changes to console runner 2017-02-19 18:20:48 -05:00
Eric Amodio
f21a228ed5 Changes uncommitted placeholder text 2017-02-18 17:43:41 -05:00
Eric Amodio
7600a8e86e Updates CHANGELOG for v2.5.2 2017-02-18 17:24:16 -05:00
Eric Amodio
1263064150 Changes behavior of showing uncommit changes
Now shows the previous commit details
2017-02-18 17:20:28 -05:00
Eric Amodio
cf8c07f49e Deals better with uncommitted commits 2017-02-18 17:18:11 -05:00
Eric Amodio
4a88edd5ca Optimizes performance of git-log 2017-02-18 17:17:08 -05:00
Eric Amodio
fc09e3a700 Stops clearing broken blame on save
Clears broken blame on close
2017-02-18 16:30:53 -05:00
Eric Amodio
dda00c75c9 Changes Not Committed Yet to Uncommitted 2017-02-18 16:30:13 -05:00
Eric Amodio
1062188733 Preps v2.5.2 2017-02-18 02:11:05 -05:00
Eric Amodio
dc26d90ebc Adds Open Files command to commit files quick pick
Adds Open File command to commit quick pick
2017-02-18 02:08:43 -05:00
Eric Amodio
217e349ac1 Preps for v2.5.1 2017-02-17 22:48:58 -05:00
Eric Amodio
9adb14aec8 Removes dead commented out code 2017-02-17 22:48:46 -05:00
Eric Amodio
5eb4a778e8 Changes behavior of showQuickFileHistory
Executes showQuickRepoHistory if there is no active editor
2017-02-17 22:22:55 -05:00
Eric Amodio
46039dbf24 Changes behavior of copyShaToClipboard
Copies the sha of the most recent commit to the repository if there is no active editor
2017-02-17 22:22:21 -05:00
Eric Amodio
d8d221a624 Removes unused image 2017-02-17 22:21:17 -05:00
Eric Amodio
120f5ae6fc Fixes disable issue with shortcut keys 2017-02-17 22:21:07 -05:00
Eric Amodio
360c38e536 Adds copyMessageToClipboard command
Adds copyMessageToClipboard to the editor content menu
Adds copyMessageToClipboard to showQuickCommitDetails quick pick
2017-02-17 22:20:25 -05:00
Eric Amodio
29c16b13bf Updates README with updated previews 2017-02-17 01:19:04 -05:00
Eric Amodio
66039e8dbe Adds new preview images 2017-02-17 01:08:52 -05:00
Eric Amodio
9c74224387 Preps for v2.5.0 2017-02-16 23:38:01 -05:00
Eric Amodio
c4c8aa3bd3 Changes default quick pick commit details
Show commands rather than file set
2017-02-16 23:37:22 -05:00
Eric Amodio
28fced1b80 Adds showQuickCommitDetails command to CodeLens
Adds showQuickRepoHistory command to CodeLens
Adds showQuickCommitDetails command to the status bar
Adds showQuickRepoHistory command to the status bar
Changes recent CodeLens default command to showQuickCommitDetails
Changes status bar default command to showQuickCommitDetails
2017-02-16 23:33:09 -05:00
Eric Amodio
a4d55bcbcc Raises brightness/darkness of the line annotation 2017-02-16 22:48:55 -05:00
Eric Amodio
ca5845018f Watches for blame annotation changes
So that it can hide/restore the hover blame annotation if needed
2017-02-16 22:42:35 -05:00
Eric Amodio
0147645dc9 Preps for v2.2.1 2017-02-16 22:10:29 -05:00
Eric Amodio
46e355e02e Updates README 2017-02-16 22:09:09 -05:00
Eric Amodio
d9e638df01 Adds config setting to control quickpick closing 2017-02-16 21:54:00 -05:00
Eric Amodio
1a47935719 Changes the default cmd of recent change codelens 2017-02-16 21:35:07 -05:00
Eric Amodio
9ac9a83fbf Fixes statusbar tooltips 2017-02-16 21:33:46 -05:00
Eric Amodio
305674d71a Fixes pathing issue on Windows 2017-02-16 21:15:39 -05:00
Eric Amodio
afe8d38fdb Preps for v2.2.0 2017-02-16 17:11:40 -05:00
Eric Amodio
cd581f5c56 Adds copy sha to clipboard to commit quickpick
Adds show changed files to commit quickpick
Changes ordering of commit quickpick list
2017-02-16 17:10:19 -05:00
Eric Amodio
af5b8b7e09 Changes behavior of CodeLens showQuickFileHistory
It now opens commit details directly
2017-02-16 17:06:37 -05:00
Eric Amodio
bf176301af Adds diffWithPrevious to editor context menu
Adds diffWithWorking to editor context menu
Renames diffWithPrevious for better clarity with new behavior
Renames diffWithWorking, diffLineWithPrevious, & diffLineWithWorking for better clarity
2017-02-16 12:28:57 -05:00
Eric Amodio
51ef25f366 Renames & changes some quickpicks for clarity 2017-02-16 12:26:51 -05:00
Eric Amodio
b17c43ba9a Changes previous diff to working diff if uncommit 2017-02-16 12:25:48 -05:00
Eric Amodio
4d0c18f330 Fixes #31 - Disable gitlens if no .git folder 2017-02-16 10:43:28 -05:00
Eric Amodio
0d7633c78a Fixes repo quickpick choices fail w/ no editors 2017-02-16 10:41:50 -05:00
Eric Amodio
8594a5dd38 Refactors the quickpick menus
Consolidated lots of duplicate functionality
go back navigation should be robust now
2017-02-16 04:07:54 -05:00
Eric Amodio
beb6740120 Adds icons to quickpick commands
Adds file history choices to commit quickpick
Adds repo history choice to file history quickpick
Adds commit message to quickpick placeholder in a few places
2017-02-15 14:15:33 -05:00
Eric Amodio
6504b7822c Fixes codelens updating when config changes 2017-02-15 13:55:33 -05:00
Eric Amodio
25aa1fe3ca Preps for v2.1.1 2017-02-13 23:21:05 -05:00
Eric Amodio
9b92c48759 Fixes overzealous updating on document changes 2017-02-13 21:19:16 -05:00
Eric Amodio
46f325e9a0 Preps for v2.1.0 2017-02-13 13:41:59 -05:00
Eric Amodio
48b33d47ca Adds helpful placeholder text to repo quickpick
Moves show all quickpick item to be first
2017-02-13 13:41:39 -05:00
Eric Amodio
5f147f6262 Attempted fix for bad filename to diff w/ working
Tried to find the most recent filename given a commit, but git doesn't seem to want to cooperate
2017-02-13 13:19:55 -05:00
Eric Amodio
d5d0c3a28d Fixes issues with diff with previous
Wouldn't always grab the correct commit
2017-02-13 11:42:54 -05:00
Eric Amodio
3ef538b713 Adds show commit details to context menu 2017-02-13 10:21:38 -05:00
Eric Amodio
310a547581 Adds setting to control blame annotation highlight
Fixes #24
2017-02-13 01:46:58 -05:00
Eric Amodio
6641233284 Converts images to svg
Adds blame toggle to the editor toolbar
2017-02-13 00:55:47 -05:00
Eric Amodio
aaa1b78d29 Adds copy sha to clipboard - fixes #28
Rearranges menu structure
2017-02-12 23:33:30 -05:00
Eric Amodio
f66ba92e3c Renames statusBar to more generic activeLine 2017-02-12 23:30:30 -05:00
Eric Amodio
4e9e6000ec Updates dependencies 2017-02-12 12:06:22 -05:00
Eric Amodio
6d4703affb Adds better support for following renames (wip) 2017-02-10 04:11:49 -05:00
Eric Amodio
9a8045b6f2 Fixes exception trapping 2017-02-10 04:10:55 -05:00
Eric Amodio
b0fbbc718c Switches log to get full history from git 2017-02-10 04:09:40 -05:00
Eric Amodio
e12ec7093c Adds new command to show quick commit info 2017-02-10 04:09:00 -05:00
Eric Amodio
615f55574b Adds go back choice to quickpick menus 2017-02-10 04:05:45 -05:00
Eric Amodio
8111190d84 Adds shortcut keys for diff commands 2017-02-10 04:02:07 -05:00
Eric Amodio
80aa43a84b Fixes issues with annotation character settings
Fixes #29 - Commit info tooltip duplicated for current line when blame is enabled
Improves performance of navigating line when active line annotations & statusbar blame are enabled
2017-02-10 03:07:16 -05:00
Eric Amodio
3389024a2a Preps for 2.0.2 2017-02-08 12:25:50 -05:00
Eric Amodio
62c5094b06 Adds control over annotation characters 2017-02-08 12:18:34 -05:00
Eric Amodio
e1cf928992 Adds constants for common css characters 2017-02-08 11:53:29 -05:00
Eric Amodio
f53ecf8a7e Fixes errors when selecting the last blank line 2017-02-08 11:42:54 -05:00
Eric Amodio
30248a7f97 Enables whitespace toggle when using ligatures 2017-02-08 11:41:16 -05:00
Eric Amodio
c259f94d3e Bump v2.0.1 2017-02-07 11:45:18 -05:00
Eric Amodio
9a394de681 Fixes #26 line annotation is wrong after delete
https://github.com/eamodio/vscode-gitlens/issues/26
2017-02-07 11:38:37 -05:00
Eric Amodio
a18eaaaee6 Bumps to 2.0 2017-02-06 16:04:52 -05:00
Eric Amodio
d181065d03 Hides inline annotations if the doc is dirty 2017-02-06 16:04:31 -05:00
Eric Amodio
964cfd363e Fixes updating CodeLens after file save
Fixes updating active line annotations after file save
2017-02-06 15:56:39 -05:00
Eric Amodio
b0257ca040 Re-adds context menu for diffLineWithPrevious
Re-adds context menu for diffLineWithWorking
2017-02-06 15:55:30 -05:00
Eric Amodio
9edf629c3b Reworks the design of hover annotations
Combines activeline annotation settings
2017-02-06 14:59:08 -05:00
Eric Amodio
dfacc2bf6e Adds new trailing annotation mode
Adds message setting to annotations
Adds active line annotations & setting
2017-02-06 13:14:38 -05:00
Eric Amodio
589ec6cef3 Fixes calling restore() without override() 2017-02-06 13:13:04 -05:00
Eric Amodio
50cf8861cd Fixes missing clean up 2017-02-06 13:12:46 -05:00
Eric Amodio
15c81ef36f Adds advanced setting for toggling whitespace
This is in-case it is still needed (it if off by default)
2017-02-05 15:44:30 -05:00
Eric Amodio
7352a70662 Removes unneeded complexity w/ whitespace toggle 2017-02-05 15:25:48 -05:00
Eric Amodio
79ad799511 Fixes whitespace toggling 2017-02-05 14:32:04 -05:00
Eric Amodio
2b907788dd Adds whitespace style changes 2017-02-05 13:31:05 -05:00
Eric Amodio
25007f7b69 Bumps version for 1.4.3 2017-01-17 10:31:36 -05:00
Eric Amodio
ea375f478f Fixes issue with latest insiders build - namespaces DocumentScheme
Changes from git to gitlens-git
2017-01-17 10:29:26 -05:00
Eric Amodio
6c536e9360 Adds logging for #22 - Cannot read property 'sha' of undefined 2017-01-17 10:27:38 -05:00
Eric Amodio
ca7f39629d Splits GitUri into its own file 2017-01-16 14:29:27 -05:00
Eric Amodio
6fefab6d64 Updates dependencies 2017-01-16 12:58:00 -05:00
Eric Amodio
ff8b184bef Fixes issue with file history working tree compare
Would occur when the filename had changed
2017-01-16 12:55:22 -05:00
Eric Amodio
d2ad2b6a0f Fixes #20: adds gitlens.advanced.gitignore.enabled
Nested .gitignore files can cause blame to fail with a repo within another repo
2017-01-10 08:41:08 -05:00
Eric Amodio
79e5370a13 Updates copyright 2017-01-03 01:51:24 -05:00
Eric Amodio
a6f5fa59e4 Fixes a typescript bug 2017-01-02 00:45:18 -05:00
Eric Amodio
680d31d43d Adds shortcut for gitlens.showQuickFileHistory
Adds shortcut for gitlens.showQuickRepoHistory
Adds gitlens.advanced.maxQuickHistory to limit the number of quick history entries to show
Adds gitlens.diffLineWithPrevious as alt context menu item for gitlens.diffWithPrevious
Adds gitlens.diffLineWithWorking as alt context menu item for gitlens.diffWithWorking
Adds gitlens.showFileHistory as alt context menu item for gitlens.showQuickFileHistory
Removes context menu for gitlens.diffLineWithPrevious
Removes context menu for gitlens.diffLineWithWorking
Replaces gitlens.menus.fileDiff.enabled & gitlens.menus.lineDiff.enabled with gitlens.menus.diff.enabled
2017-01-02 00:39:26 -05:00
Eric Amodio
567533622c Adds new iterator methods 2017-01-02 00:07:01 -05:00
Eric Amodio
a52b0c9b73 Switches to use as rather than <> 2017-01-02 00:06:42 -05:00
Eric Amodio
4e65eb6344 Renames Diff commands for better clarity
Removes `Git` from the commands as it feels unnecessary
Reorders the context menu commands
Adds `Diff Commit with Working Tree` to the explorer context menu (assuming `gitlens.menus.fileDiff.enabled` is `true`)
Adds `Diff Commit with Working Tree` & `Diff Commit with Previous` to the editor title context menu (assuming `gitlens.menus.fileDiff.enabled` is `true`)
2016-12-07 02:47:27 -05:00
Eric Amodio
72ef5e2902 Adds support for blame & log on compare files
Allows for deep navigation through git history
2016-12-04 00:41:42 -05:00
Eric Amodio
1a208b8691 Fixes issue in command description 2016-12-02 01:52:57 -05:00
Eric Amodio
39aa6180ca Updates some @type packages
Fixes typo and utilizes new range method
2016-12-01 23:59:05 -05:00
Eric Amodio
a8ab3a5af4 Preps for 1.2 release 2016-11-25 14:13:00 -05:00
Eric Amodio
615485cd21 Adds compare options to repository history
Adds compare options to file history
Fixes issue with repository history compare with commits with multiple files
2016-11-25 14:12:39 -05:00
Eric Amodio
a91afffbb2 Adds logging for #18 2016-11-23 18:44:13 -05:00
Eric Amodio
5f0acc2693 Allows showQuickRepoHistory w/o opened editor
It falls back to the folder repository
2016-11-23 16:48:44 -05:00
Eric Amodio
d3ffabd76b Adds new gitlens.showQuickFileHistory command
Adds new gitlens.showQuickRepoHistory command
Adds gitlens.showQuickFileHistory option to the settings
Removes git.viewFileHistory option
Changes the gitlens.statusBar.command settings default to gitlens.showQuickFileHistory
2016-11-23 02:43:01 -05:00
Eric Amodio
5cb0053a05 Fixes incorrect 'Unable to find Git' message 2016-11-17 01:05:03 -05:00
Eric Amodio
968eb6e7eb More readme changes 2016-11-12 17:06:03 -05:00
Eric Amodio
438dad437f More readme changes 2016-11-12 17:01:30 -05:00
Eric Amodio
60b464ca22 Fixes truncated title on marketplace
Removes preview flag
2016-11-12 16:55:39 -05:00
Eric Amodio
354b823d7d Preparing for a 1.0 release
Fixes #10 vscode/#11485 makes gitlens unusable with whitespace rendering
Fixes #11 Not showing details of commits
Fixes #12 command 'gitlens.XXX' not found
Fixes #13 Handle exception when you open a file that does not exist in a git repository
Fixes #14 Remove logs from production build
2016-11-12 16:41:46 -05:00
Eric Amodio
e9db04f0c7 Removes unnecessary subscriptions on invalid blame
Removes duplicate lint rule
Switches on-demand CodeLens to be a global toggle
2016-11-12 16:36:10 -05:00
Eric Amodio
e31a1a3473 Adds logging for git location 2016-11-12 16:36:10 -05:00
Eric Amodio
46e2378779 Fixes issue with unencoded chars in uri 2016-11-12 16:36:10 -05:00
Eric Amodio
fee9562dd1 Optimizes range slice calculations in CodeLens 2016-11-12 04:17:16 -05:00
Eric Amodio
9c0a38958e Switches to use GitUris 2016-11-12 03:54:21 -05:00
Eric Amodio
a3895d27ab Removes git-blame document scheme
Removes git-blame content provider
Fixes some CodeLens issues
Adds support for git uris in more places
Adds git content CodeLens provider
2016-11-12 03:15:27 -05:00
Eric Amodio
638a6dc838 Adds support for git commands on scheme=git
Rewrites blame annotation controller and provider - fixes whitespace issues, reduces overhead, and provides better performance
Rewrites status bar blame support - reduces overhead and provides better performance
Adds showFileHistory command to status bar
Renames showHistory to showFileHistory
Fixes log to use iso 8601 for dates
2016-11-12 01:25:42 -05:00
Eric Amodio
7ace9cb152 Adds support for custom git installation locations
Also gracefully deals with the times when git isn't in the PATH
2016-11-10 18:33:28 -05:00
Eric Amodio
f4410be30a Adds error messages for failed operations
Adds showHistory command support to CodeLens
Fixes and improve the showHistory explorer
Refactoring
2016-11-10 03:22:58 -05:00
Eric Amodio
562afeaaad Adds gitlens.diffWithPrevious command to the explore context menu
Adds output channel logging, controlled by the `gitlens.advanced.output.level` setting
Removes all debug logging, unless the `gitlens.advanced.output.debug` settings it on
2016-11-08 03:38:33 -05:00
Eric Amodio
409be335f9 1.0 wip 2016-11-03 03:09:33 -04:00
Eric Amodio
8df6b80725 Updates to latest vscode extension template
Removes typings (using npm instead)
Fixes some promise catches
2016-09-29 16:06:48 -04:00
Eric Amodio
d2d72f0d54 Fixes another off-by-one issue when diffing with caching
Refactored commands and blame annotations
2016-09-26 00:55:54 -04:00
Eric Amodio
23b2c679a9 Fixes off-by-one issues with blame annotations without caching and when diffing with a previous version 2016-09-22 23:00:09 -06:00
Eric Amodio
157928311e Adds more protection for uncommitted lines
Adds better uncommitted hover message in blame annotations
2016-09-21 10:55:16 -04:00
Eric Amodio
b047fbc394 Adds catch to blameLine to avoid strange issues
Removes Git: from all the commands
Removes unused enricher
2016-09-21 09:46:29 -04:00
Eric Amodio
7a4dcae8c7 Fixes #7 - loading issue on Linux 2016-09-21 02:38:25 -04:00
Eric Amodio
a734ffe9ed Updates README with more details 2016-09-21 02:19:12 -04:00
Eric Amodio
834b4904db Adds blame information in the statusBar
Add new StatusBar settings -- see **Extension Settings** above for details
Renames the `gitlens.codeLens.recentChange.command` & `gitlens.codeLens.authors.command` settings options (to align with command names)
Adds new `gitlens.diffWithPrevious` option to the `gitlens.codeLens.recentChange.command` & `gitlens.codeLens.authors.command` settings
Fixes Diff with Previous when the selection is uncommited
Removes `gitlens.blame.annotation.useCodeActions` setting and behavior
2016-09-21 02:06:23 -04:00
Eric Amodio
c4b8637946 Fixes #7 - missing spawn-rx dependency (argh!) 2016-09-20 11:34:40 -04:00
Eric Amodio
20df7732be Fixes #7 - missing lodash dependency 2016-09-20 10:20:47 -04:00
Eric Amodio
30b1fba8d4 Reduces the size of the vsix 2016-09-19 21:53:13 -04:00
Eric Amodio
7b4dd77fbc Adds new CodeLens visibility & location settings
Adds new command to toggle CodeLens on and off when `gitlens.codeLens.visibility` is set to `ondemand`
2016-09-19 21:28:52 -04:00
Eric Amodio
14e9c5b4fa Fixes blame explorer failing to load content 2016-09-19 21:28:16 -04:00
Eric Amodio
69c4d44b49 Fixes a slew of issues because of the Fix for #1 2016-09-19 05:14:45 -04:00
Eric Amodio
7ebdaa0775 Preps 0.2.0 release 2016-09-19 04:28:02 -04:00
Eric Amodio
ff01054f90 Fixes #1 Support blame on files outside repo
Replaces blame regex parsing with more robust parser (also use s--incremental instead of --porcelain)
Stops throwing on git blame errors (too many are common)
Fixes issues with Diff with Previous command
Fixes issues with blame explorer code lens -- with previous commits
Fixes issues with compact blame annotations -- skips blank lines
2016-09-19 04:11:46 -04:00
Eric Amodio
c69a160ea5 Refactors git modules - unify under gitProvider
Adds advanced setting to enable blame cache
Fixes codelens settings to actually work
2016-09-19 00:24:19 -04:00
Eric Amodio
05865d014e Fixes truncated description on marketplace 2016-09-15 12:40:26 -04:00
Eric Amodio
cb81a823e6 Only 5 key words allowed 2016-09-15 12:34:09 -04:00
Eric Amodio
7a604191a7 Fixes issue where original filename got lost
Fixes file path normalization
2016-09-15 12:33:43 -04:00
Eric Amodio
b617a90c5b Better marketplace presence 2016-09-15 11:41:33 -04:00
Eric Amodio
2be76ed8a8 Fixes #4 2016-09-15 10:50:45 -04:00
Eric Amodio
dcb789f58d Adds many new settings
Adds new blame annotation styles (compact & expanded)
Cleaned up blame annotations
Fixes issue with invalid repoPath on first start
2016-09-15 05:03:46 -04:00
Eric Amodio
f34686de36 Preps 0.0.7 release 2016-09-14 13:32:28 -04:00
Eric Amodio
0ee09d9d87 Fixes #4 - Absolute paths fail on Windows due to backslash
Hopefully for real this time
2016-09-14 13:31:57 -04:00
Eric Amodio
7fa0b9d01b Fixes #5 - Finding first non-white-space fails sometimes 2016-09-14 13:30:52 -04:00
Eric Amodio
fba6def3e4 Adds .gitignore checks to reduce blame calls
Caches failed blames to reduce blame calls
Only clear failed blames from cache on change/save
Add better error messages and handling
2016-09-14 13:30:14 -04:00
Eric Amodio
dfd17a8f17 Preps 0.0.6 release 2016-09-14 02:32:52 -04:00
Eric Amodio
d70a47201c Fixes #4 - Absolute paths fail on Windows due to backslash 2016-09-14 02:29:22 -04:00
Eric Amodio
e0cf335811 Fixes #2 - Adds better error logging 2016-09-14 02:28:20 -04:00
Eric Amodio
87215850b7 Fixes #4 - Absolute paths fail on Windows due to backslash 2016-09-14 00:55:48 -04:00
Eric Amodio
a2eee23d7f Scrolls to the correct position in the diff 2016-09-14 00:55:24 -04:00
Eric Amodio
19c0e46729 Fixes some issues with uncommited blames
Automatically turns off blame only when required now
2016-09-07 15:45:42 -04:00
Eric Amodio
26ce5f7d53 Fixes failure when filename changes in history
Removes CodeLens from fields and single-line properties to reduce visual noise
2016-09-07 12:28:00 -04:00
Eric Amodio
67e1a6b78f Adds repository info 2016-09-05 18:28:04 -04:00
Eric Amodio
fbcd0a9cd6 Changes description to be more generic 2016-09-05 17:52:43 -04:00
Eric Amodio
e929db0106 Preps for 0.0.4 release 2016-09-05 17:49:25 -04:00
Eric Amodio
b7920f3c3d Fixes (read: hacks) blame with visible whitespace
Adds diff menu commands always
Attempts to move the diff file to the correct line number
Fixes code action provider -- works again
Deletes deprecated code
2016-09-05 16:40:38 -04:00
Eric Amodio
d04696ac1d Adds code actions to open diffs
Adds context menus for toggling blame
Adds context menus for opening diffs
More refactoring and many bug fixes
2016-09-04 21:46:40 -04:00
Eric Amodio
ebb1085562 Switches to porcelain blame format
Provides more data (commit message, previous commit, etc)
2016-09-04 03:43:35 -04:00
Eric Amodio
47ce5c5132 Adds author count + leader CodeLens
Upgrades to TypeScript 2
Lots of refactoring and many bug fixes
2016-09-04 00:49:02 -04:00
Eric Amodio
f08339335d Adds full blame UI support 2016-09-02 21:24:53 -04:00
Eric Amodio
f4d3d1718d Reverses diff ordering
Only adds blame code lens within the specified range
2016-09-02 00:53:13 -04:00
Eric Amodio
ea33560f14 Removes hard dependency on donjayamanne.githistory
Provides optional additional code lens
2016-08-31 21:24:03 -04:00
Eric Amodio
70cc92ddd6 Adds CodeLens for Diff'ing in blame
Other fixes and refactoring
2016-08-31 21:15:06 -04:00
Eric Amodio
0e064f15c7 Reworks git abstraction and acccess
Adds commands module
Adds git commit message to blame hover decorator
2016-08-31 17:20:53 -04:00
Eric Amodio
9964ea691b Fixes issue with executing blame command
And minor other positioning issues
2016-08-31 15:55:33 -04:00
Eric Amodio
92beca2542 Fixes issues with file renames
And other git related edge cases
2016-08-31 15:03:22 -04:00
Eric Amodio
0ccac8da8c Preps 0.0.2 release 2016-08-31 05:05:09 -04:00
Eric Amodio
a22b8b1ddd Reworks location processing
Decoupled from the CodeLens and less processing before it is required
2016-08-31 05:03:20 -04:00
Eric Amodio
84becec23f Moves lens start char to center on symbol
Also helps move lens after other lenses
2016-08-31 03:54:03 -04:00
Eric Amodio
c395a583b7 Renamed to GitLens
Reworked Uri scheme to drastically reduce encoded data (big perf improvement)
2016-08-31 03:34:16 -04:00
156 changed files with 15848 additions and 614 deletions

8
.vscode/launch.json vendored
View File

@@ -10,8 +10,8 @@
"args": ["--extensionDevelopmentPath=${workspaceRoot}" ], "args": ["--extensionDevelopmentPath=${workspaceRoot}" ],
"stopOnEntry": false, "stopOnEntry": false,
"sourceMaps": true, "sourceMaps": true,
"outDir": "${workspaceRoot}/out/src", "outFiles": ["${workspaceRoot}/out/src/**/*.js"],
"preLaunchTask": "npm" "preLaunchTask": "compile"
}, },
{ {
"name": "Launch Tests", "name": "Launch Tests",
@@ -21,8 +21,8 @@
"args": ["--extensionDevelopmentPath=${workspaceRoot}", "--extensionTestsPath=${workspaceRoot}/out/test" ], "args": ["--extensionDevelopmentPath=${workspaceRoot}", "--extensionTestsPath=${workspaceRoot}/out/test" ],
"stopOnEntry": false, "stopOnEntry": false,
"sourceMaps": true, "sourceMaps": true,
"outDir": "${workspaceRoot}/out/test", "outFiles": ["${workspaceRoot}/out/test/**/*.js"],
"preLaunchTask": "npm" "preLaunchTask": "compile"
} }
] ]
} }

12
.vscode/settings.json vendored
View File

@@ -1,12 +1,14 @@
// Place your settings in this file to overwrite default and user settings.
{ {
"editor.insertSpaces": true,
"files.exclude": { "files.exclude": {
"out": false, // set this to true to hide the "out" folder with the compiled JS files "node_modules": true,
"node_modules": false "out": true
}, },
"files.trimTrailingWhitespace": true,
"search.exclude": { "search.exclude": {
"out": true, // set this to false to include "out" folder in search results "node_modules": true,
"node_modules": true "out": true
}, },
"javascript.format.insertSpaceAfterFunctionKeywordForAnonymousFunctions": false,
"typescript.tsdk": "./node_modules/typescript/lib" // we want to use the TS server from our node_modules folder to control its version "typescript.tsdk": "./node_modules/typescript/lib" // we want to use the TS server from our node_modules folder to control its version
} }

48
.vscode/tasks.json vendored
View File

@@ -8,23 +8,37 @@
// A task runner that calls a custom npm script that compiles the extension. // A task runner that calls a custom npm script that compiles the extension.
{ {
"version": "0.1.0", "version": "2.0.0",
// we want to run npm
"command": "npm",
// the command is a shell script
"isShellCommand": true,
// show the output window only if unrecognized errors occur.
"showOutput": "silent", "showOutput": "silent",
"tasks": [
// we run the custom script "compile" as defined in package.json {
"args": ["run", "compile", "--loglevel", "silent"], "taskName": "compile",
"command": "npm run compile --silent",
// The tsc compiler is started in watching mode "isBuildCommand": true,
"isWatching": true, "isShellCommand": true,
"problemMatcher": [
// use the standard tsc in watch mode problem matcher to find compile problems in the output. "$tsc",
{
"base": "$tslint5",
"fileLocation": "relative"
}
]
},
{
"taskName": "lint",
"command": "npm run lint --silent",
"isShellCommand": true,
"problemMatcher": {
"base": "$tslint5",
"fileLocation": "relative"
}
},
{
"taskName": "watch",
"command": "npm run watch --silent",
"isBackground": true,
"isShellCommand": true,
"problemMatcher": "$tsc-watch" "problemMatcher": "$tsc-watch"
} }
]
}

View File

@@ -1,9 +1,11 @@
!images/*.svg
images/**
.vscode/** .vscode/**
typings/** .vscode-test/**
out/test/** out/test/**
test/** test/**
src/** src/**
**/*.map **/*.map
.gitignore .gitignore
tsconfig.json tsconfig.json
vsc-extension-quickstart.md tslint.json

762
CHANGELOG.md Normal file
View File

@@ -0,0 +1,762 @@
# Change Log
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/).
## [4.0.0-beta.2] - 2017-06-07
### Added
- Adds all-new, beautiful, highly customizable and themeable, file blame annotations
- Can now fully customize the [layout and content](https://github.com/eamodio/vscode-gitlens/blob/develop/README.md#file-blame-annotation-settings), as well as the [theme](https://github.com/eamodio/vscode-gitlens/blob/develop/README.md#theme-settings)
- Adds all-new configurability and themeability to the current line blame annotations
- Can now fully customize the [layout and content](https://github.com/eamodio/vscode-gitlens/blob/develop/README.md#line-blame-annotation-settings), as well as the [theme](https://github.com/eamodio/vscode-gitlens/blob/develop/README.md#theme-settings)
- Adds all-new configurability to the status bar blame information
- Can now fully customize the [layout and content](https://github.com/eamodio/vscode-gitlens/blob/develop/README.md#status-bar-settings)
- Adds all-new [configurability](https://github.com/eamodio/vscode-gitlens/blob/develop/README.md#advanced-settings) over which commands are added to which menus via the `gitlens.advanced.menus` setting
- Adds better [configurability](https://github.com/eamodio/vscode-gitlens/blob/develop/README.md#code-lens-settings) over where Git code lens will be shown -- both by default and per language
- Adds an all-new `changes` (diff) hover annotation to the current line - provides instant access to the line's previous version
- Adds `Toggle Line Blame Annotations` command (`gitlens.toggleLineBlame`) - toggles the current line blame annotations on and off
- Adds `Show Line Blame Annotations` command (`gitlens.showLineBlame`) - shows the current line blame annotations
- Adds `Toggle File Blame Annotations` command (`gitlens.toggleFileBlame`) - toggles the file blame annotations on and off
- Adds `Show File Blame Annotations` command (`gitlens.showFileBlame`) - shows the file blame annotations
- Adds `Open File in Remote` command (`gitlens.openFileInRemote`) to the `editor/title` context menu
- Adds `Open Repo in Remote` command (`gitlens.openRepoInRemote`) to the `editor/title` context menu
- Adds `gitlens.strings.*` settings to allow for the customization of certain strings displayed
- Adds `gitlens.theme.*` settings to allow for the theming of certain elements
- Adds `gitlens.advanced.telemetry.enabled` settings to explicitly opt-in or out of telemetry, but still ultimately honors the `telemetry.enableTelemetry` setting
### Changed
- (BREAKING) Almost all of the GitLens settings have either been renamed, removed, or otherwise changed - see the [README](https://github.com/eamodio/vscode-gitlens/blob/develop/README.md#extension-settings)`
- Changes the positioning of the Git code lens to try to be at the end of any other code lens on the same line
- Changes the position of the `Open File in Remote` command (`gitlens.openFileInRemote`) in the context menus - now in the `navigation` group
- Changes the `Toggle Git Code Lens` command (`gitlens.toggleCodeLens`) to always toggle the Git code lens on and off
### Removed
- Removes the on-demand `trailing` file blame annotations -- didn't work out and just ended up with a ton of visual noise
- Removes `Toggle Blame Annotations` command (`gitlens.toggleBlame`) - replaced by the `Toggle File Blame Annotations` command (`gitlens.toggleFileBlame`)
- Removes `Show Blame Annotations` command (`gitlens.showBlame`) - replaced by the `Show File Blame Annotations` command (`gitlens.showFileBlame`)
### Fixed
- Fixes [#81](https://github.com/eamodio/vscode-gitlens/issues/81) - Current line annotation feels too sticky
- Fixes issues with the zone.js monkey patching done by application insights (telemetry) - disables all the monkey patching
## [3.6.1] - 2017-06-07
### Fixed
- Fixes issues with the zone.js monkey patching done by application insights (telemetry) - disables all the monkey patching
## [3.6.0] - 2017-06-02
### Added
- Adds diff information (the line's previous version) into the active line hover
- Adds a `gitlens.diffWithWorking` status bar command option - compares the current line commit with the working tree
### Changed
- Changes the behavior of the `Compare File with Working Tree` command (`gitlens.diffWithWorking`) - always does what it says :)
- Compares the current file with the working tree -- if the current file *is* the working file, it will show a `File matches the working tree` message
- Changes the behavior of the `Compare File with Previous` command (`gitlens.diffWithPrevious`) - always does what it says :)
- Compares the current file with the previous commit to that file
- Changes the behavior of the `gitlens.diffWithPrevious` status bar command option - compares the current line commit with the previous
- Renames `Compare File with Previous Commit` command to `Compare File with Previous`
- Renames `Compare Line with Previous Commit` command to `Compare Line Commit with Previous`
- Renames `Compare Line with Working Tree` command to `Compare Line Commit with Working Tree`
- Renames `Compare with Previous Commit` in quick pick menus to `Compare File with Previous`
- Renames `Compare with Working Tree` in quick pick menus to `Compare File with Working Tree`
### Fixed
- Fixes [#79](https://github.com/eamodio/vscode-gitlens/issues/79) - Application insights package breaks GitLens + eslint
## [3.5.1] - 2017-05-25
### Changed
- Changes certain code lens actions to be unavailable (unclickable) when the commit referenced is uncommitted - avoids unwanted error messages
- Debounces more events when tracking the active line to further reduce lag
### Fixed
- Fixes [#71](https://github.com/eamodio/vscode-gitlens/issues/71) - Blame information is invalid when a file has changed outside of vscode
- Fixes issue with showing the incorrect blame for versioned files (i.e. files on the left of a diff, etc)
## [3.5.0] - 2017-05-24
### Added
- Improves performance
- Reduces the number of git calls on known "untrackables"
- Caches many more git commands to reduce git command roundtrips and parsing
- Increases the debounce (delay) on cursor movement to reduce lag when navigating around a file
- Adds diff information (the line's previous version) into the active line hover when the current line is uncommitted
- Adds `gitlens.statusBar.alignment` settings to control the alignment of the status bar -- thanks to [PR #72](https://github.com/eamodio/vscode-gitlens/pull/72) by Zack Schuster ([@zackschuster](https://github.com/zackschuster))!
- Adds `Open Branch in Remote` command (`gitlens.openBranchInRemote`) - opens the current branch commits in the supported remote service
- Adds `Open Repository in Remote` command (`gitlens.openRepoInRemote`) - opens the repository in the supported remote service
- Adds `Stash Changes` option to stashed changes quick pick menu -- no longer hidden behind the `"gitlens.insiders": true` setting
- Adds `Stash Unstaged Changes` option to stashed changes quick pick menu -- no longer hidden behind the `"gitlens.insiders": true` setting
- Adds `Apply Stashed Changes` command (`gitlens.stashApply`) to apply the selected stashed changes to the working tree -- no longer hidden behind the `"gitlens.insiders": true` setting
- Adds `Stash Changes` command (`gitlens.stashSave`) to stash any working tree changes -- no longer hidden behind the `"gitlens.insiders": true` setting
- Adds support to the `Search commits` command (`gitlens.showCommitSearch`) to work without any active editor
- Adds commit search pre-population -- if there is an active editor it will use the commit sha of the current line commit, otherwise it will use the current clipboard
### Changed
- Changes `Open File in Remote` and `Open Line Commit in Remote` commands to actually work for everyone (part of their implementation was still behind the `gitlens.insiders` setting)
- Changes the active line hover to only show at the beginning and end of a line if `gitlens.blame.annotation.activeLine` is `both`
- Changes `alt+f` shortcut to `alt+/` for the `Search commits` command (`gitlens.showCommitSearch`)
- Changes `alt+right` on commit details quick pick menu to execute the `Compare File with Previous Commit` command (`gitlens.diffWithPrevious`) when a file is selected
- Changes `alt+right` on repository status quick pick menu to execute the `Compare File with Previous Commit` command (`gitlens.diffWithPrevious`) when a file is selected
- Refactors command argument passing to allow for future inclusion into the SCM menus
### Fixed
- Fixes [#73](https://github.com/eamodio/vscode-gitlens/issues/73) - GitLens doesn't work with Chinese filenames
- Fixes [#40](https://github.com/eamodio/vscode-gitlens/issues/40) - Encoding issues
- Given the limitations of the vscode api, I'm unable to fix all the encoding issues, but many of them should now be squashed
- `files.encoding` is now honored for the cases where the encoding cannot currently be gleaned
- Fixes incorrect file selection from the commit details quick pick menu
- Fixes incorrect command execution when using `"gitlens.statusBar.command": "gitlens.showQuickRepoHistory"`
- Fixes a bunch of issues that were revealed by enabling Typescript `strict` mode
## [3.4.9] - 2017-05-03
### Added
- Adds better support for deleted files when choosing `Open Changed Files` via in quick pick menus - now opens the file revision from the previous commit
- Adds better support for deleted files when using `alt+right arrow` shortcut on the commit details quick pick menu - now opens the file revision from the previous commit
### Changed
- Removes deleted files when choosing `Open Working Changed Files` via in quick pick menus
## [3.4.8] - 2017-05-02
### Changed
- Changes display name in the marketplace to `Git Lens` because of the marketplace search ranking algorithm
## [3.4.6] - 2017-05-01
### Added
- Adds better support for deleted files when choosing `Open File` via in quick pick menus - now opens the file revision from the previous commit
- Adds better support for deleted files when choosing `Open File in Remote` via in quick pick menus - now opens the file revision from the previous commit
- Improves performance by caching the git path to avoid lookups on every git command
### Changed
- Renames `gitlens.advanced.codeLens.debug` setting to `gitlens.codeLens.debug`
- Renames `gitlens.advanced.debug` setting to `gitlens.debug`
- Renames `gitlens.output.level` setting to `gitlens.outputLevel`
### Fixed
- Fixes incorrect file selection when showing commit details quick pick menu
- Fixes timing error on startup
## [3.4.5] - 2017-04-13
### Added
- Completely overhauls the [GitLens documentation](https://github.com/eamodio/vscode-gitlens/blob/master/README.md) and messaging -- make sure to check it out to see all the powerful features GitLen provides!
- Adds `gitlens.blame.annotation.activeLineDarkColor` & `gitlens.blame.annotation.activeLineLightColor` settings to control the colors of the active line blame annotation
### Changed
- Changes `Toggle Git Code Lens` command to work when `gitlens.codeLens.visibility` is set to `auto` (the default)
- Renames `Compare with...` command to `Compare File with...`
- Renames `Compare with Next Commit` command to `Compare File with Next Commit`
- Renames `Compare with Previous Commit` command to `Compare File with Previous Commit`
- Renames `Compare with Previous Commit` command to `Compare File with Previous Commit`
- Renames `Compare with Working Tree` command to `Compare File with Working Tree`
### Fixed
- Fixes issue with `Open Commit in Remote` not working
- Fixes issue with many commands missing from the `Command Palette`
## [3.3.3] - 2017-04-10
### Fixed
- Fixes issue with newlines in commit messages in the file/branch/stash history quick pick menus (truncates and adds an ellipse icon)
## [3.3.2] - 2017-04-10
### Removed
- Removes `gitlens.blame.annotation.characters.*` settings since they were added to deal with unicode bugs in a previous version of vscode
### Fixed
- Closes [#63](https://github.com/eamodio/vscode-gitlens/issues/63) - Switch commit message and author in commit pick list. Also reduces clutter in the commit quick pick menus
## [3.3.1] - 2017-04-09
### Changed
- Changes commit search prefixes -- no prefix for message search, `@` for author, `:` for file pattern, `#` for commit id
- Changes `sha` terminology to `commit id` in the UI
### Fixed
- Fixes issues with author searching
## [3.3.0] - 2017-04-09
### Added
- Adds `Search commits` command (`gitlens.showCommitSearch`) to allow commit searching by message, author, file pattern, or sha
- Adds `alt+f` shortcut for the `Search commits` command (`gitlens.showCommitSearch`)
- Adds `Show Commit Search` command to the branch history quick pick menu
- Adds `Show Stashed Changes` command to the repository status quick pick menu
- Adds a `Don't Show Again` option to the GitLen update notification
### Changed
- Changes `Open x in Remote` commands to be no longer hidden behind the `gitlens.insiders` setting
### Fixed
- Fixes [#59](https://github.com/eamodio/vscode-gitlens/issues/59) - Context menu shows gitlens commands even if folder/file is not under git
## [3.2.1]
### Fixed
- Fixes [#57](https://github.com/eamodio/vscode-gitlens/issues/57) - No more blank message if `diff.tool` is missing
## [3.2.0]
### Added
- Adds support for single files opened in vscode -- you are no longer required to open a folder for GitLens to work
### Fixed
- Fixes [#57](https://github.com/eamodio/vscode-gitlens/issues/57) - Warn on directory compare when there is no diff tool configured
- Fixes [#58](https://github.com/eamodio/vscode-gitlens/issues/58) - Work with git sub-modules
- Fixes issue with `Open * in Remote` commands with nested repositories and non-git workspace root folder
## [3.1.0]
### Added
- Adds `Show Stashed Changes` command (`gitlens.showQuickStashList`) to open a quick pick menu of all the stashed changes
- Adds insiders `Stash Changes` option to stashed changes quick pick menu -- enabled via `"gitlens.insiders": true`
- Adds insiders `Stash Unstaged Changes` option to stashed changes quick pick menu
- Adds insiders `Apply Stashed Changes` command (`gitlens.stashApply`) to apply the selected stashed changes to the working tree
- Adds insiders `Stash Changes` command (`gitlens.stashSave`) to stash any working tree changes
### Fixed
- Fixes incorrect counts in upstream status
## [3.0.5]
### Added
- Adds additional insiders support for GitLab, Bitbucket, and Visual Studio Team Services to the `Open x in Remote` commands and quick pick menus -- enabled via `"gitlens.insiders": true`
- Adds insiders line support to `Open File in Remote` command (`gitlens.openFileInRemote`)
- Adds original file name for renamed files to the repository status and commit details quick pick menu
### Fixed
- Fixes [#56](https://github.com/eamodio/vscode-gitlens/issues/56) - Handle file names with spaces
## [3.0.4]
### Changed
- Changes telemetry a bit to reduce noise
### Fixed
- Fixes common telemetry error by switching to non-strict iso dates (since they are only available in later git versions)
## [3.0.3]
### Added
- Adds a fallback to work with Git version prior to `2.11.0` -- terribly sorry for the inconvenience :(
### Fixed
- Fixes [#55](https://github.com/eamodio/vscode-gitlens/issues/55) - reverts Git requirement back to `2.2.0`
- Fixes issues with parsing merge commits
## [3.0.2]
### Changed
- Changes required Git version to `2.11.0`
## [3.0.1]
### Added
- Adds basic telemetry -- honors the vscode telemetry configuration setting
## [3.0.0]
### Added
- Adds insiders support for `Open in GitHub` to the relevant quick pick menus -- enabled via `"gitlens.insiders": true`
- Adds insiders `Open Line Commit in Remote` command (`gitlens.openCommitInRemote`) to open the current commit in the remote service (currently only GitHub)
- Adds insiders `Open File in Remote` command (`gitlens.openFileInRemote`) to open the current file in the remote service (currently only GitHub)
- Adds an update notification for feature releases
- Adds `Show Branch History` command (`gitlens.showQuickBranchHistory`) to show the history of the selected branch
- Adds `Show Last Opened Quick Pick` command (`gitlens.showLastQuickPick`) to re-open the previously opened quick pick menu - helps to get back to previous context
- Adds `alt+-` shortcut for the `Show Last Opened Quick Pick` command (`gitlens.showLastQuickPick`)
- Adds upstream status information (if available) to the repository status pick pick
- Adds file status rollup information to the repository status pick pick
- Adds file status rollup information to the commit details quick pick menu
- Adds `Compare with...` (`gitlens.diffWithBranch`) command to compare working file to another branch (via branch quick pick menu)
- Adds branch quick pick menu to `Directory Compare` (`gitlens.diffDirectory`) command
- Adds support for `gitlens.showQuickFileHistory` command execution via code lens to limit results to the code lens block
- Adds current branch to branch quick pick menu placeholder
- Adds `Show Branch History` command to the branch history quick pick menu when showing only limited commits (e.g. starting at a specified commit)
- Adds `Show File History` command to the file history quick pick menu when showing only limited commits (e.g. starting at a specified commit)
- Adds `Don't Show Again` option to the unsupported git version notification
### Changed
- Changes `Show Repository History` command to `Show Current Branch History`
- Changes `Repository History` terminology to `Branch History`
### Fixed
- Fixes issue with `gitlens.diffWithPrevious` command execution via code lens when the code lens was not at the document/file level
- Fixes issue where full shas were displayed on the file/blame history explorers
- Fixes [#30](https://github.com/eamodio/vscode-gitlens/issues/30) - Diff with Working Tree fails from repo/commit quickpick list if file was renamed (and the commit was before the rename)
- Fixes various other quick pick menu command issues when a file was renamed
- Fixes various issues when caching is disabled
- Fixes issues with parsing commits history
- Fixes various issues with merge commits
## [2.12.2]
### Fixed
- Fixes [#50](https://github.com/eamodio/vscode-gitlens/issues/50) - excludes container-level code lens from `html` and `vue` language files
## [2.12.1]
### Added
- Adds `gitlens.advanced.codeLens.debug` setting to control whether or not to show debug information in code lens
### Fixed
- Fixes issue where `gitlens.showQuickRepoHistory` command fails to open when there is no active editor
## [2.12.0]
### Added
- Adds progress indicator for the `gitlens.showQuickFileHistory` & `gitlens.showQuickRepoHistory` quick pick menus
- Adds paging support to the `gitlens.showQuickFileHistory` & `gitlens.showQuickRepoHistory` quick pick menus
- Adds `Show Previous Commits` command
- Adds `Show Next Commits` command
- Adds keyboard page navigation via `alt+,` (previous) & `alt+.` (next) on the `gitlens.showQuickFileHistory` & `gitlens.showQuickRepoHistory` quick pick menus
- Adds keyboard commit navigation via `alt+,` (previous) & `alt+.` (next) on the `gitlens.showQuickCommitDetails` & `gitlens.showQuickCommitFileDetails` quick pick menus
### Changed
- Changes behavior of `gitlens.showQuickFileHistory` & `gitlens.showFileHistory` to no longer show merge commits
- Changes `gitlens.copyShaToClipboard` to copy the full sha, rather than short sha
- Changes internal tracking to use full sha (rather than short sha)
## [2.11.2]
### Added
- Adds `gitlens.diffWithNext` command to open a diff with the next commit
- Adds `alt+.` shortcut for the `gitlens.diffWithNext` command
### Changed
- Changes `shift+alt+p` shortcut to `alt+,` for the `gitlens.diffWithPrevious` command
- Changes `alt+p` shortcut to `shift+alt+,` for the `gitlens.diffLineWithPrevious` command
### Removed
- Removes `gitlens.toggleCodeLens` from Command Palette when not available
- Removes `gitlens.toggleCodeLens` shortcut key when not available
### Fixed
- Fixes (#45)[https://github.com/eamodio/vscode-gitlens/issues/45] - Keyboard Shortcut collision with Project Manager
## [2.11.1]
### Added
- Adds blame and active line annotation support to git diff split view (right side)
- Adds command (compare, copy sha/message, etc) support to git diff split view (right side)
### Fixed
- Fixes intermittent issues when toggling whitespace for blame annotations
## [2.11.0]
### Added
- Adds `gitlens.showQuickCommitFileDetails` command to show a quick pick menu of details for a file commit
- Adds `gitlens.showQuickCommitFileDetails` command to code lens
- Adds `gitlens.showQuickCommitFileDetails` command to the status bar
- Adds `gitlens.closeUnchangedFiles` command to close any editors that don't have uncommitted changes
- Adds `gitlens.openChangedFiles` command to open all files that have uncommitted changes
- Adds `Directory Compare` (`gitlens.diffDirectory`) command to open the configured git difftool to compare directory versions
- Adds `Directory Compare with Previous Commit` command on the `gitlens.showQuickCommitDetails` quick pick menu
- Adds `Directory Compare with Working Tree` command on the `gitlens.showQuickCommitDetails` quick pick menu
- Adds a `Changed Files` grouping on the `gitlens.showQuickCommitDetails` quick pick menu
- Adds a `Close Unchanged Files` command on the `gitlens.showQuickRepoStatus` quick pick menu
- Adds a contextual description to the `go back` command in quick pick menus
### Changed
- Changes layout of the `gitlens.showQuickRepoStatus` quick pick menu for better clarity
- Changes behavior of `gitlens.showQuickCommitDetails` to show commit a quick pick menu of details for a commit
- Changes default of `gitlens.codeLens.recentChange.command` to be `gitlens.showQuickCommitFileDetails` (though there is no visible behavior change)
- Renames `Open Files` to `Open Changed Files` on the `gitlens.showQuickCommitDetails` quick pick menu
- Renames `Open Working Files` to `Open Changed Working Files` on the `gitlens.showQuickCommitDetails` quick pick menu
- Renames `Show Changed Files` to `Show Commit Details` on the `gitlens.showQuickCommitFileDetails` quick pick menu
- Renames `Open Files` to `Open Changed Files` on the `gitlens.showQuickRepoStatus` quick pick menu
### Fixed
- Fixes [#44](https://github.com/eamodio/vscode-gitlens/issues/43) by adding a warning message about Git version requirements
- Fixes intermittent errors when adding active line annotations
- Fixes intermittent errors when opening multiple files via quick pick menus
## [2.10.1]
### Fixed
- Fixes [#43](https://github.com/eamodio/vscode-gitlens/issues/43) - File-level code lens isn't using the blame of the whole file as it should
- Fixes issue with single quotes (') in annotations
- Fixes output channel logging (also adds more debug information to code lens -- when enabled)
## [2.10.0]
### Added
- Adds blame and active line annotation support to git diff split view
- Adds command (compare, copy sha/message, etc) support to git diff split view
### Fixed
- Fixes startup failure if caching was disabled
- Fixes missing `Compare Line with Previous Commit` context menu item
- Fixes [#41](https://github.com/eamodio/vscode-gitlens/issues/41) - Toggle Blame annotations on compare files page
- Fixes issue with undo (to a saved state) not causing annotations to reappear properly
- Attempts to fix [#42](https://github.com/eamodio/vscode-gitlens/issues/42) - Cursor on Uncommitted message
## [2.9.0]
### Changed
- To accomodate the realization that blame information is invalid when a file has unsaved changes, the following behavior changes have been made
- Status bar blame information will hide
- Code lens change to a `Cannot determine...` message and become unclickable
- Many menu choices and commands will hide
### Fixed
- Fixes [#38](https://github.com/eamodio/vscode-gitlens/issues/38) - Toggle Blame Annotation button shows even when it isn't valid
- Fixes [#36](https://github.com/eamodio/vscode-gitlens/issues/36) - Blame information is invalid when a file has unsaved changes
## [2.8.2]
### Added
- Adds `gitlens.blame.annotation.dateFormat` to specify how absolute commit dates will be shown in the blame annotations
- Adds `gitlens.statusBar.date` to specify whether and how the commit date will be shown in the blame status bar
- Adds `gitlens.statusBar.dateFormat` to specify how absolute commit dates will be shown in the blame status bar
### Fixed
- Fixes [#39](https://github.com/eamodio/vscode-gitlens/issues/39) - Add date format options for status bar blame
## [2.8.1]
### Fixed
- Fixes issue where `Compare with *` commands fail to open when there is no active editor
## [2.8.0]
### Added
- Adds new `Open File` command on the `gitlens.showQuickCommitDetails` quick pick menu to open the commit version of the file
- Adds new `Open File` command on the `gitlens.showQuickCommitDetails` quick pick menu to open the commit version of the files
- Adds `alt+left` keyboard shortcut in quick pick menus to `go back`
- Adds `alt+right` keyboard shortcut in quick pick menus to execute the currently selected item while keeping the quick pick menu open (in most cases)
- `alt+right` keyboard shortcut on commit details file name, will open the commit version of the file
### Changed
- Indents the file statuses on the `gitlens.showQuickCommitDetails` quick pick menu
- Renames `Open File` to `Open Working File` on the `gitlens.showQuickCommitDetails` quick pick menu
- Renames `Open File` and `Open Working Files` on the `gitlens.showQuickCommitDetails` quick pick menu
- Reorders some quick pick menus
### Fixed
- Fixes [#34](https://github.com/eamodio/vscode-gitlens/issues/34) - Open file should open the selected version of the file
- Fixes some issue where some editors opened by the quickpick would not be opened in preview tabs
- Fixes issue where copy to clipboard commands would fail if there was no active editor
- Fixes issue where active line annotations would show for opened versioned files
- Fixes issue where code lens compare commands on opened versioned files would fail
## [2.7.1]
### Added
- Adds proper support for multi-line commit messages
### Fixed
- Fixes [#33](https://github.com/eamodio/vscode-gitlens/issues/33) - Commit message styled as title in popup, when message starts with hash symbol
## [2.7.0]
### Added
- Adds file status icons (added, modified, deleted, etc) to the `gitlens.showQuickCommitDetails` quick pick menu
- Adds `Copy Commit Sha to Clipboard` command to commit files quick pick menu
- Adds `Copy Commit Message to Clipboard` command to commit files quick pick menu
### Changed
- Changes `Show Commit History` to `Show File History` on the `gitlens.showQuickCommitDetails` quick pick menu
- Changes `Show Previous Commit History` to `Show Previous File History` on the `gitlens.showQuickCommitDetails` quick pick menu
### Fixed
- Fixes issue with repository status when there are no changes
- Fixes issue with `.` showing in the path of quick pick menus
- Fixes logging to clean up on extension deactivate
## [2.6.0]
### Added
- Adds `gitlens.showQuickRepoStatus` command to show a quick pick menu of files changed including status icons (added, modified, deleted, etc)
- Adds `alt+s` shortcut for the `gitlens.showQuickRepoStatus` command
## [2.5.6]
### Fixed
- Fixes [#32](https://github.com/eamodio/vscode-gitlens/issues/32) - 00000000 Uncommitted changes distracting
## [2.5.5]
### Fixed
- Fixes [#25](https://github.com/eamodio/vscode-gitlens/issues/25) - Blame information isn't updated after git operations (commit, reset, etc)
## [2.5.4]
### Fixed
- Fixes extra spacing in annotations
## [2.5.3]
### Fixed
- Fixes [#27](https://github.com/eamodio/vscode-gitlens/issues/27) - Annotations are broken in vscode insider build
## [2.5.2]
### Added
- Adds `Open File` command to `gitlens.showQuickCommitDetails` quick pick menu
- Adds `Open Files` command to `gitlens.showQuickCommitDetails` quick pick menu
- Improves performance of git-log operations in `gitlens.diffWithPrevious` and `gitlens.diffWithWorking` commands
### Changed
- Changes `Not Committed Yet` author for uncommitted changes to `Uncommitted`
### Fixed
- Fixes showing `gitlens.showQuickCommitDetails` quick pick menu for uncommitted changes -- now shows the previous commit details
## [2.5.1]
### Added
- Adds `gitlens.copyMessageToClipboard` command to copy commit message to the clipboard
- Adds `gitlens.copyMessageToClipboard` to the editor content menu
- Adds `Copy Commit Message to Clipboard` command to `gitlens.showQuickCommitDetails` quick pick menu
### Changed
- Changes behavior of `gitlens.copyShaToClipboard` to copy the sha of the most recent commit to the repository if there is no active editor
- Changes behavior of `gitlens.showQuickFileHistory` to execute `gitlens.showQuickRepoHistory` if there is no active editor
### Fixed
- Fixes issue where shortcut keys weren't disabled if GitLens was disabled
## [2.5.0]
### Added
- Overhauls the `gitlens.showQuickRepoHistory`, `gitlens.showQuickFileHistory`, and `gitlens.showQuickCommitDetails` quick pick menus
- Adds `Show Repository History` command to `gitlens.showQuickFileHistory` quick pick menu
- Adds `Show Previous Commits History` command to `gitlens.showQuickCommitDetails` quick pick menu
- Adds `Show Commits History` command to `gitlens.showQuickCommitDetails` quick pick menu
- Adds `Copy Commit Sha to Clipboard` command to `gitlens.showQuickCommitDetails` quick pick menu
- Adds `Show Changed Files` command to `gitlens.showQuickCommitDetails` quick pick menu
- Adds more robust `go back` navigation in quick pick menus
- Adds commit message to placeholder text of many quick pick menus
- Adds icons for some commands
- Adds `gitlens.diffWithPrevious` command to the editor content menu
- Adds `gitlens.diffWithWorking` command to the editor content menu
- Adds `gitlens.showQuickRepoHistory` and `gitlens.showQuickCommitDetails` commands to code lens
- Adds `gitlens.showQuickRepoHistory` and `gitlens.showQuickCommitDetails` commands to the status bar
### Changed
- Changes the default command of `gitlens.codeLens.recentChange.command` to `gitlens.showQuickCommitDetails`
- Changes the default command of `gitlens.statusBar.command` to `gitlens.showQuickCommitDetails`
- Changes behavior of `gitlens.showQuickCommitDetails` to show commit commands rather than file set (use `Show Changed Files` command to get to the file set)
- Changes `gitlens.diffWithPrevious` command to behave as `gitlens.diffWithWorking` if the file has uncommitted changes
- Renames `gitlens.diffWithPrevious` command from `Diff Commit with Previous` to `Compare with Previous Commit`
- Renames `gitlens.diffLineWithPrevious` command from `Diff Commit (line) with Previous` to `Compare Line with Previous Commit`
- Renames `gitlens.diffWithWorking` command from `Diff Commit with Working Tree` to `Compare with Working Tree`
- Renames `gitlens.diffLineWithWorking` command from `Diff Commit (line) with Working Tree` to `Compare Line with Working Tree`
### Fixed
- Fixes issues with certain git commands not working on Windows
- Fixes [#31](https://github.com/eamodio/vscode-gitlens/issues/31) - Disable gitlens if the project does not have `.git` folder
- Fixes issue where quick pick menus could fail if there was no active editor
- Fixes code lens not updating in response to configuration changes
## [2.1.1]
### Fixed
- Fixes overzealous active line annotation updating on document changes
## [2.1.0]
### Added
- Adds a new GitLens logo and changes all images to svg
- Adds `alt+p` shortcut for the `gitlens.diffLineWithPrevious` command
- Adds `shift+alt+p` shortcut for the `gitlens.diffWithPrevious` command
- Adds `alt+w` shortcut for the `gitlens.diffLineWithWorking` command
- Adds `shift+alt+w` shortcut for the `gitlens.diffWithWorking` command
- Adds `gitlens.copyShaToClipboard` command to copy commit sha to the clipboard ([#28](https://github.com/eamodio/vscode-gitlens/issues/28))
- Adds `gitlens.showQuickCommitDetails` command to show a quick pick menu of details for a commit
- Adds `go back` choice to `gitlens.showQuickCommitDetails`, `gitlens.showQuickFileHistory`, and `gitlens.showQuickRepoHistory` quick pick menus
- Adds `gitlens.blame.annotation.highlight` to specify whether and how to highlight blame annotations ([#24](https://github.com/eamodio/vscode-gitlens/issues/24))
- Greatly improves performance of line navigation when either active line annotations or status bar blame is enabled
### Fixed
- Fixes [#29](https://github.com/eamodio/vscode-gitlens/issues/29) - Commit info tooltip duplicated for current line when blame is enabled
- Fixes issue where sometimes the commit history shown wasn't complete
- Fixes issues with `gitlens.diffLineWithPrevious` and `gitlens.diffWithPrevious` not following renames properly
- Fixes issues with `gitlens.diffLineWithPrevious` and `gitlens.diffWithPrevious` not always grabbing the correct commit
## [2.0.2]
### Added
- Adds auto-enable of whitespace toggling when using font-ligatures because of [vscode issue](https://github.com/Microsoft/vscode/issues/11485)
- Adds `gitlens.blame.annotation.characters.*` settings to provide some control over how annotations are displayed
### Fixed
- Fixes [#22](https://github.com/eamodio/vscode-gitlens/issues/22) - Cannot read property 'sha' of undefined
## [2.0.1]
### Fixed
- Fixes [#26](https://github.com/eamodio/vscode-gitlens/issues/26) - Active line annotation doesn't disappear properly after delete
## [2.0.0]
### Added
- Adds `gitlens.blame.annotation.activeLine` to specify whether and how to show blame annotations on the active line
- Adds full commit message (rather than just summary) to active line hover if `gitlens.blame.annotation.activeLine` is not `off`
- Adds new `trailing` blame annotation style -- adds annotations after the code lines rather than before
- Adds `gitlens.blame.annotation.message` to show the commit message in `expanded` and `trailing` blame annotation styles
- Adds support for relative dates in blame annotations. Use `gitlens.blame.annotation.date`
- Re-adds context menu for `gitlens.diffLineWithPrevious` -- since [vscode issue](https://github.com/Microsoft/vscode/issues/15395)
- Re-adds context menu for `gitlens.diffLineWithWorking` -- since [vscode issue](https://github.com/Microsoft/vscode/issues/15395)
### Changed
- Changes the design of hover annotations -- much cleaner now
- Disables automatic whitespace toggling by default as it is seemingly no longer needed as [vscode issue](https://github.com/Microsoft/vscode/issues/11485) seems fixed. It can be re-enabled with `gitlens.advanced.toggleWhitespace.enabled`
### Fixed
- Fixes issue where the status bar blame would get stuck switching between editors
- Fixes issue where code lens aren't updated properly after a file is saved
## [1.4.3]
### Added
- Adds some logging to hopefully trap [#22](https://github.com/eamodio/vscode-gitlens/issues/22) - Cannot read property 'sha' of undefined
### Fixed
- Fixes issue with the latest insiders build (1.9.0-insider f67f87c5498d9361c0b29781c341fd032815314b) where there is a collision of document schemes
## [1.4.2]
### Fixed
- Fixes issue where file history wouldn't compare correctly to working tree if the filename had changed
## [1.4.1]
### Added
- Adds `gitlens.advanced.gitignore.enabled` to enable/disable .gitignore parsing. Addresses [#20](https://github.com/eamodio/vscode-gitlens/issues/20) - Nested .gitignore files can cause blame to fail with a repo within another repo
## [1.4.0]
### Added
- Adds `alt+h` shortcut for the `gitlens.showQuickFileHistory` command
- Adds `shift+alt+h` shortcut for the `gitlens.showQuickRepoHistory` command
- Adds `gitlens.advanced.maxQuickHistory` to limit the number of quick history entries to show (for better performance); Defaults to 200
- Adds `gitlens.diffLineWithPrevious` as `alt` context menu item for `gitlens.diffWithPrevious`
- Adds `gitlens.diffLineWithWorking` as `alt` context menu item for `gitlens.diffWithWorking`
- Adds `gitlens.showFileHistory` as `alt` context menu item for `gitlens.showQuickFileHistory`
### Removed
- Removes context menu for `gitlens.diffLineWithPrevious` -- since it is now the `alt` of `gitlens.diffWithPrevious`
- Removes context menu for `gitlens.diffLineWithWorking` -- since it is now the `alt` of `gitlens.diffWithWorking`
- Replaces `gitlens.menus.fileDiff.enabled` and `gitlens.menus.lineDiff.enabled` with `gitlens.menus.diff.enabled` -- since the switch between file and line diff is now controlled by the `alt` key
## [1.3.1]
### Added
- Adds `Diff Commit with Working Tree` to the explorer context menu (assuming `gitlens.menus.fileDiff.enabled` is `true`)
- Adds `Diff Commit with Working Tree` & `Diff Commit with Previous` to the editor title context menu (assuming `gitlens.menus.fileDiff.enabled` is `true`)
### Changed
- Renames `Diff` commands for better clarity
- Removes `Git` from the commands as it feels unnecessary
- Reorders the context menu commands
## [1.3.0]
### Added
- Adds support for blame and history (log) on files opened via compare commands -- allows for deep navigation through git history
## [1.2.0]
### Added
- Adds compare (working vs previous) options to repository history
- Adds compare (working vs previous) options to file history
### Fixed
- Fixes issue with repository history compare with commits with multiple files
## [1.1.1]
### Added
- Adds logging for tracking [#18](https://github.com/eamodio/vscode-gitlens/issues/18) - GitLens only displayed for some files
### Changed
- Changes `gitlens.showQuickRepoHistory` command to run without an open editor (falls back to the folder repository)
## [1.1.0]
### Added
- Adds new `gitlens.showQuickFileHistory` command to show the file history in a quick-pick list (palette)
- Adds new `gitlens.showQuickRepoHistory` command to show the repository history in a quick-pick list (palette)
- Adds `gitlens.showQuickFileHistory` option to the `gitlens.codeLens.recentChange.command`, `gitlens.codeLens.authors.command`, and `gitlens.statusBar.command` settings
### Changed
- Changes the `gitlens.statusBar.command` settings default to `gitlens.showQuickFileHistory` instead of `gitlens.toggleBlame`
### Removed
- Removes `git.viewFileHistory` option from the `gitlens.codeLens.recentChange.command`, `gitlens.codeLens.authors.command`, and `gitlens.statusBar.command` settings
## [1.0.2]
### Fixed
- Fixes [#16](https://github.com/eamodio/vscode-gitlens/issues/16) - incorrect 'Unable to find Git' message
## [1.0.0]
### Added
- Adds support for git history (log)!
- Adds support for blame annotations and git commands on file revisions
- Adds ability to show multiple blame annotation at the same time (one per vscode editor)
- Adds new `gitlens.showFileHistory` command to open the history explorer
- Adds new `gitlens.showFileHistory` option to the `gitlens.codeLens.recentChange.command`, `gitlens.codeLens.authors.command`, and `gitlens.statusBar.command` settings
- Adds per-language code lens location customization using the `gitlens.codeLens.languageLocations` setting
- Adds new `gitlens.diffLineWithPrevious` command for line sensitive diffs
- Adds new `gitlens.diffLineWithWorking` command for line sensitive diffs
- Adds `gitlens.diffWithPrevious` command to the explorer context menu
- Adds output channel logging, controlled by the `gitlens.advanced.output.level` setting
- Improves performance of the code lens support
- Improves performance (significantly) when only showing code lens at the document level
- Improves performance of status bar blame support
### Changed
- Switches on-demand code lens to be a global toggle (rather than per file)
- Complete rewrite of the blame annotation provider to reduce overhead and provide better performance
- Changes `gitlens.diffWithPrevious` command to always be file sensitive diffs
- Changes `gitlens.diffWithWorking` command to always be file sensitive diffs
- Removes all debug logging, unless the `gitlens.advanced.debug` settings it on
### Fixed
- Fixes many (most?) issues with whitespace toggling (required because of https://github.com/Microsoft/vscode/issues/11485)
- Fixes issue where blame annotations would not be cleared properly when switching between open files
## [0.5.5]
### Fixed
- Fixes another off-by-one issue when diffing with caching
## [0.5.4]
### Fixed
- Fixes off-by-one issues with blame annotations without caching and when diffing with a previous version
## [0.5.3]
### Added
- Adds better uncommitted hover message in blame annotations
- Adds more protection for dealing with uncommitted lines
## [0.5.2]
### Fixed
- Fixes loading issue on Linux
## [0.5.1]
### Added
- Adds blame information in the status bar
- Add new status bar settings -- see **Extension Settings** for details
- Adds new `gitlens.diffWithPrevious` option to the `gitlens.codeLens.recentChange.command` & `gitlens.codeLens.authors.command` settings
### Changed
- Renames the `gitlens.codeLens.recentChange.command` & `gitlens.codeLens.authors.command` settings options (to align with command names)
### Removed
- Removes `gitlens.blame.annotation.useCodeActions` setting and behavior
### Fixed
- Fixes Diff with Previous when the selection is uncommitted
## [0.3.3]
### Fixed
- Fixes [#7](https://github.com/eamodio/vscode-gitlens/issues/7) - missing spawn-rx dependency (argh!)
## [0.3.2]
### Fixed
- Fixes [#7](https://github.com/eamodio/vscode-gitlens/issues/7) - missing lodash dependency
## [0.3.1]
### Added
- Adds new code lens visibility & location settings -- see **Extension Settings** for details
- Adds new command to toggle code lens on and off when `gitlens.codeLens.visibility` is set to `ondemand`
## [0.2.0]
### Changed
- Replaces blame regex parsing with a more robust parser
### Fixed
- Fixes [#1](https://github.com/eamodio/vscode-gitlens/issues/1) - Support blame on files outside the workspace repository
- Fixes failures with Diff with Previous command
- Fixes issues with blame explorer code lens when dealing with previous commits
- Fixes display issues with compact blame annotations (now skips blank lines)
## [0.1.3]
### Added
- Improved blame annotations, now with sha and author by default
- Add new blame annotation styles -- compact and expanded (default)
- Adds many new configuration settings; see **Extension Settings** for details
## [0.0.7]
### Added
- Adds .gitignore checks to reduce the number of blame calls
### Fixed
- Fixes [#4](https://github.com/eamodio/vscode-gitlens/issues/4) - Absolute paths fail on Windows due to backslash (Really!)
- Fixes [#5](https://github.com/eamodio/vscode-gitlens/issues/5) - Finding first non-white-space fails sometimes
## [0.0.6]
### Added
- Adds attempt to scroll to the correct position when opening a diff
### Fixed
- Fixes [#2](https://github.com/eamodio/vscode-gitlens/issues/2) - [request] Provide some debug info when things fail
- Fixes [#4](https://github.com/eamodio/vscode-gitlens/issues/4) - Absolute paths fail on Windows due to backslash
## [0.0.5]
### Changed
- Removes code lens from fields and single-line properties to reduce visual noise
- Automatically turns off blame only when required now
### Fixed
- Fixes issues where filename changes in history would cause diffs to fails
- Fixes some issues with uncommitted blames
## [0.0.4]
### Added
- Candidate for preview release on the vscode marketplace.
## [0.0.1]
### Added
- Initial release but still heavily a work in progress.

12
ISSUE_TEMPLATE.md Normal file
View File

@@ -0,0 +1,12 @@
<!--
If you are encountering an issue that says `See output channel for more details`, please enable output channel logging by setting `"gitlens.outputLevel": "verbose"` in your settings.json. This will enable logging to the GitLens channel in the Output pane. Once enabled, please attempt to reproduce the issue (if possible) and attach the relevant log lines from the GitLens channel.
-->
- GitLens Version:
- VSCode Version:
- OS Version:
Steps to Reproduce:
1.
2.

View File

@@ -1,6 +1,6 @@
The MIT License (MIT) The MIT License (MIT)
Copyright (c) 2016 Eric Amodio Copyright (c) 2016-2017 Eric Amodio
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

322
README.md
View File

@@ -1,31 +1,327 @@
# Git CodeLens [![](https://vsmarketplacebadge.apphb.com/version/eamodio.gitlens.svg)](https://marketplace.visualstudio.com/items?itemName=eamodio.gitlens)
[![](https://vsmarketplacebadge.apphb.com/installs/eamodio.gitlens.svg)](https://marketplace.visualstudio.com/items?itemName=eamodio.gitlens)
[![](https://vsmarketplacebadge.apphb.com/rating/eamodio.gitlens.svg)](https://marketplace.visualstudio.com/items?itemName=eamodio.gitlens)
[![Chat at https://gitter.im/vscode-gitlens/Lobby](https://badges.gitter.im/vscode-gitlens/Lobby.svg)](https://gitter.im/vscode-gitlens/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
Provides Git blame (and history eventually) CodeLens for many supported Visual Studio Code languages (in theory -- the language must support symbol searching). # GitLens
GitLens **supercharges** the built-in Visual Studio Code Git capabilities. It helps you to **visualize code authorship** at a glance via Git blame annotations and code lens, **seamlessly navigate and explore** the history of a file or branch, **gain valuable insights** via powerful comparision commands, and so much more.
GitLens provides an unobtrusive blame annotation at the end of the current line, a status bar item showing the commit information (author and date, by default) of the current line, code lens showing the most recent commit and # of authors of the file and/or code block, and many commands for exploring commits and histories, comparing and navigating revisions, stash access, repository status, and more. GitLens is also [highly customizable](#extension-settings) to meet your specific needs — find code lens intrusive or the current line blame annotation distracting — no problem, it is easy to [turn them off or change how they behave](#extension-settings).
## Previews
#### Featuring code lens, file blame annotations, and navigation and exploration via quick pick menus
![GitLens preview 1](https://raw.githubusercontent.com/eamodio/vscode-git-codelens/develop/images/gitlens-preview1.gif)
#### Featuring current line blame annotation and hovers, status bar commit details, quick pick menus, compare with previous, and more
![GitLens preview 2](https://raw.githubusercontent.com/eamodio/vscode-git-codelens/develop/images/gitlens-preview2.gif)
## Features ## Features
Provides CodeLens with the author and date of the last check-in. #### Git Blame Annotations
> ![CodeLens](https://raw.githubusercontent.com/eamodio/vscode-git-codelens/master/images/preview-codelens.png) - Adds an unobtrusive, highly [customizable](#line-blame-annotation-settings) and [themeable](#theme-settings), **Git blame annotation** to the end of the current line ([optional](#line-blame-annotation-settings), on by default)
Clicking on a CodeLens opens a blame "explorer" with the commits and changed lines in the right pane and the commit (file) contents on the left. ![Line Blame Annotation](https://raw.githubusercontent.com/eamodio/vscode-git-codelens/develop/images/screenshot-line-blame-annotation.png)
- Contains the author, date, and message of the line's most recent commit, by [default](#line-blame-annotation-settings)
- Also adds a `details` hover annotation to the current line annotation which provides more commit details ([optional](#line-blame-annotation-settings), on by default)
- Also adds a `changes` (diff) hover annotation to the current line annotation which provides **instant** access to the line's previous version ([optional](#line-blame-annotation-settings), on by default)
> ![Blame Explorer](https://raw.githubusercontent.com/eamodio/vscode-git-codelens/master/images/preview-blame.png) ![Line Blame Annotations](https://raw.githubusercontent.com/eamodio/vscode-git-codelens/develop/images/screenshot-line-blame-annotations.png)
## Requirements - Adds on-demand, beautiful, highly [customizable](#file-blame-annotation-settings) and [themeable](#theme-settings), **Git blame annotations** of the whole file
Must be using Git and it must be in your path. ![File Blame Annotation](https://raw.githubusercontent.com/eamodio/vscode-git-codelens/develop/images/screenshot-file-blame-annotations.png)
- Choose between `gutter` (default) and `hover` [annotation styles](#file-blame-annotation-settings)
- Contains the commit message and date, by [default](#file-blame-annotation-settings)
- Also adds a `details` hover annotation to the line's annotation which provides more commit details ([optional](#file-blame-annotation-settings), on by default)
- Adds [customizable](#status-bar-settings) **blame information** about the current line to the **status bar** ([optional](#status-bar-settings), on by default)
![Status Bar Blame](https://raw.githubusercontent.com/eamodio/vscode-git-codelens/develop/images/screenshot-status-bar.png)
- Contains the commit author and date, by [default](#status-bar-settings)
- Clicking the status bar item will, by [default](#status-bar-settings), show a **commit details quick pick menu** with commands for comparing, navigating and exploring commits, and more
- Provides [customizable](#status-bar-settings) click behavior — choose between one of the following
- Toggle file blame annotations on and off
- Toggle code lens on and off
- Compare the line commit with the previous commit
- Compare the line commit with the working tree
- Show a quick pick menu with details and commands for the commit (default)
- Show a quick pick menu with file details and commands for the commit
- Show a quick pick menu with the commit history of the file
- Show a quick pick menu with the commit history of the current branch
- Adds a `Toggle File Blame Annotations` command (`gitlens.toggleFileBlame`) with a shortcut of `alt+b` to toggle the file blame annotations on and off
- Also adds a `Show File Blame Annotations` command (`gitlens.showFileBlame`)
- Adds a `Toggle Line Blame Annotations` command (`gitlens.toggleLineBlame`) to toggle the current line blame annotations on and off
- Also adds a `Show Line Blame Annotations` command (`gitlens.showLineBlame`)
#### Git Code Lens
- Adds **code lens** to the top of the file and on code blocks ([optional](#code-lens-settings), on by default)
![Git Code Lens](https://raw.githubusercontent.com/eamodio/vscode-git-codelens/develop/images/screenshot-code-lens.png)
- **Recent Change** — author and date of the most recent commit for the file or code block
- Clicking the code lens will, by [default](#code-lens-settings), show a **commit file details quick pick menu** with commands for comparing, navigating and exploring commits, and more
- **Authors** — number of authors of the file or code block and the most prominent author (if there is more than one)
- Clicking the code lens will, by [default](#code-lens-settings), toggle the file Git blame annotations on and off of the whole file
- Will be hidden if the author of the most recent commit is also the only author of the file or block, to avoid duplicate information and reduce visual noise
- Provides [customizable](#code-lens-settings) click behavior for each code lens — choose between one of the following
- Toggle file blame annotations on and off
- Compare the commit with the previous commit
- Show a quick pick menu with details and commands for the commit
- Show a quick pick menu with file details and commands for the commit
- Show a quick pick menu with the commit history of the file
- Show a quick pick menu with the commit history of the current branch
- Adds a `Toggle Git Code Lens` command (`gitlens.toggleCodeLens`) with a shortcut of `shift+alt+b` to toggle the code lens on and off
#### Powerful Comparison Tools
- Effortlessly navigate between comparisions via the `alt+,` and `alt+.` shortcut keys to go back and forth through a file's revisions
- Provides easy access to the following comparison commands via the `Command Palette` as well as in context via the many provided quick pick menus
- Adds a `Directory Compare` command (`gitlens.diffDirectory`) to open the configured Git difftool to compare directories between branches
- Adds a `Compare File with...` command (`gitlens.diffWithBranch`) to compare the active file with the same file on the selected branch
- Adds a `Compare File with Next Commit` command (`gitlens.diffWithNext`) with a shortcut of `alt+.` to compare the active file/diff with the next commit revision
- Adds a `Compare File with Previous` command (`gitlens.diffWithPrevious`) with a shortcut of `alt+,` to compare the active file/diff with the previous commit revision
- Adds a `Compare Line Commit with Previous` command (`gitlens.diffLineWithPrevious`) with a shortcut of `shift+alt+,` to compare the active file/diff with the previous line commit revision
- Adds a `Compare File with Working Tree` command (`gitlens.diffWithWorking`) with a shortcut of `shift+alt+w` to compare the most recent commit revision of the active file/diff with the working tree
- Adds a `Compare Line Commit with Working Tree` command (`gitlens.diffLineWithWorking`) with a shortcut of `alt+w` to compare the commit revision of the active line with the working tree
#### Navigate and Explore
- Adds a `Search Commits` command (`gitlens.showCommitSearch`) with a shortcut of `alt+/` to search for commits by message, author, file(s), or commit id
- Adds commands to open files, commits, branches, and the repository in the supported remote services, currently **BitBucket, GitHub, GitLab, and Visual Studio Team Services** — only available if a Git upstream service is configured in the repository
- `Open Branch in Remote` command (`gitlens.openBranchInRemote`) — opens the current branch commits in the supported remote service
- `Open Line Commit in Remote` command (`gitlens.openCommitInRemote`) — opens the commit revision of the active line in the supported remote service
- `Open File in Remote` command (`gitlens.openFileInRemote`) — opens the active file/revision in the supported remote service
- `Open Repository in Remote` command (`gitlens.openRepoInRemote`) — opens the repository in the supported remote service
- Adds a `Show Current Branch History` command (`gitlens.showQuickRepoHistory`) with a shortcut of `shift+alt+h` to show a paged **branch history quick pick menu** of the current branch for exploring its commit history
![Branch History Quick Pick Menu](https://raw.githubusercontent.com/eamodio/vscode-git-codelens/master/images/screenshot-branch-history.png)
- Provides entries to `Show Commit Search` and `Open Branch in <remote-service>` when available
- Navigate back to the previous quick pick menu via `alt+left arrow`, if available
- Navigate pages via `alt+,` and `alt+.` to go backward and forward respectively
- Adds a `Show Branch History` command (`gitlens.showQuickBranchHistory`) to show a paged **branch history quick pick menu** of the selected branch for exploring its commit history
- Provides the same features as `Show Current Branch History` above
- Adds a `Show File History` command (`gitlens.showQuickFileHistory`) to show a paged **file history quick pick menu** of the active file for exploring its commit history
![File History Quick Pick Menu](https://raw.githubusercontent.com/eamodio/vscode-git-codelens/master/images/screenshot-file-history.png)
- Provides entries to `Show Branch History` and `Open File in <remote-service>` when available
- Navigate back to the previous quick pick menu via `alt+left arrow`, if available
- Navigate pages via `alt+,` and `alt+.` to go backward and forward respectively
- Adds a `Show Commit Details` command (`gitlens.showQuickCommitDetails`) to show a **commit details quick pick menu** of the most recent commit of the active file
![Commit Details Quick Pick Menu](https://raw.githubusercontent.com/eamodio/vscode-git-codelens/master/images/screenshot-commit-details.png)
- Quickly see the set of files changed in the commit, complete with status indicators for adds, changes, renames, and deletes
- Provides entries to `Copy to Clipboard`, `Directory Compare`, `Open Changed Files`, `Open File in <remote-service>` when available, and more
- Navigate back to the previous quick pick menu via `alt+left arrow`, if available
- Use the `alt+right arrow` shortcut on an entry to execute it without closing the quick pick menu, if possible — commands that open windows outside of VS Code will still close the quick pick menu unless [`"gitlens.advanced.quickPick.closeOnFocusOut": false`](#extension-settings) is set
- Use the `alt+right arrow` shortcut on a file entry in the `Changed Files` section to preview the current revision of the while leaving the quick pick menu open
- NOTE: Once [vscode issue #10568](https://github.com/Microsoft/vscode/issues/10568) is resolved this will change to preview the comparison of the current revision with the previous one
- Adds a `Show Line Commit Details` command (`gitlens.showQuickCommitFileDetails`) with a shortcut of `alt+c` to show a **file commit details quick pick menu** of the most recent commit of the active file
![Line Commit Details Quick Pick Menu](https://raw.githubusercontent.com/eamodio/vscode-git-codelens/master/images/screenshot-commit-file-details.png)
- Provides entries to `Show Commit Details`, `Show File History`, `Compare File with...`, `Copy to Clipboard`, `Open File`, `Open File in <remote-service>` when available, and more
- Navigate back to the previous quick pick menu via `alt+left arrow`, if available
- Use the `alt+right arrow` shortcut on an entry to execute it without closing the quick pick menu, if possible — commands that open windows outside of VS Code will still close the quick pick menu unless [`"gitlens.advanced.quickPick.closeOnFocusOut": false`](#extension-settings) is set
- Adds a `Show Repository Status` command (`gitlens.showQuickRepoStatus`) with a shortcut of `alt+s` to show a **repository status quick pick menu** for visualizing the current repository status
![Repository Status Quick Pick Menu](https://raw.githubusercontent.com/eamodio/vscode-git-codelens/master/images/screenshot-repo-status.png)
- Quickly see upstream status (if an Git upstream is configured) — complete with ahead and behind information
- If you are ahead of the upstream, an entry will be shown with the number of commits ahead. Chosing it will show a limited **branch history quick pick menu** containing just the commits ahead of the upstream
- If you are behind the upstream, an entry will be shown with the number of commits behind. Chosing it will show a limited **branch history quick pick menu** containing just the commits behind the upstream
- Quickly see all working changes, both staged and unstaged, complete with status indicators for adds, changes, renames, and deletes
- Provides entries to `Show Stashed Changes`, `Open Changed Files`, and `Close Unchanged Files`
- Use the `alt+right arrow` shortcut on an entry to execute it without closing the quick pick menu, if possible — commands that open windows outside of VS Code will still close the quick pick menu unless [`"gitlens.advanced.quickPick.closeOnFocusOut": false`](#extension-settings) is set
- Use the `alt+right arrow` shortcut on a file entry in the `Staged Files` or `Unstaged Files` sections to preview the working file while leaving the quick pick menu open
- NOTE: Once [vscode issue #10568](https://github.com/Microsoft/vscode/issues/10568) is resolved this will change to preview the comparison of the working file with the previous revision
- Adds a `Show Stashed Changes` command (`gitlens.showQuickStashList`) to show a **stashed changes quick pick menu** for exploring your repository stash history
![Stashed Changes Quick Pick Menu](https://raw.githubusercontent.com/eamodio/vscode-git-codelens/master/images/screenshot-stash-list.png)
- Provides entries to `Stash Changes`
- Navigate back to the previous quick pick menu via `alt+left arrow`, if available
- Chosing a stash entry shows a **stash details quick pick menu** which is very similar to the **commit details quick pick menu** above
![Stash Details Quick Pick Menu](https://raw.githubusercontent.com/eamodio/vscode-git-codelens/master/images/screenshot-stash-details.png)
- Quickly see the set of files changed in the stash, complete with status indicators for adds, changes, renames, and deletes
- Provides entries to `Copy Message to Clipboard`, `Directory Compare`, and `Open Changed Files`
- Provides entries to `Apply Stashed Changes` and `Delete Stashed Changes` — both require a confirmation
- Navigate back to the previous quick pick menu via `alt+left arrow`, if available
- Use the `alt+right arrow` shortcut on an entry to execute it without closing the quick pick menu, if possible — commands that open windows outside of VS Code will still close the quick pick menu unless [`"gitlens.advanced.quickPick.closeOnFocusOut": false`](#extension-settings) is set
- Use the `alt+right arrow` shortcut on a file entry in the `Changed Files` section to preview the current revision of the while leaving the quick pick menu open
- NOTE: Once [vscode issue #10568](https://github.com/Microsoft/vscode/issues/10568) is resolved this will change to preview the comparison of the current revision with the previous one
- Adds a `Show Last Opened Quick Pick` command (`gitlens.showLastQuickPick`) with a shortcut of `alt+-` to quickly get back to where you were when the last GitLens quick pick menu closed
- Adds a `Open File History Explorer` command (`gitlens.showFileHistory`) to show a **file history explorer** (peek style) to visualize the history of a file
- Likely to be deprecated in a future release, add your voice to [#66](https://github.com/eamodio/vscode-gitlens/issues/66) if you feel it should not be removed
- Adds a `Open Blame History Explorer` command (`gitlens.showBlameHistory`) to show a **blame history explorer** (peek style) to visualize the blame history of a file or code block
- Likely to be deprecated in a future release, add your voice to [#66](https://github.com/eamodio/vscode-gitlens/issues/66) if you feel it should not be removed
#### And More
- Adds a `Copy Commit ID to Clipboard` command (`gitlens.copyShaToClipboard`) to copy the commit id (sha) of the active line to the clipboard
- Adds a `Copy Commit Message to Clipboard` command (`gitlens.copyMessageToClipboard`) to copy the commit message of the active line to the clipboard
- Adds a `Open Changed Files` command (`gitlens.openChangedFiles`) to open any files with working tree changes
- Adds a `Close Unchanged Files` command (`gitlens.closeUnchangedFiles`) to close any files without working tree changes
- Adds a `Apply Stashed Changes` command (`gitlens.stashApply`) to chose a stash entry to apply to the working tree from a quick pick menu
- Adds a `Stash Changes` command (`gitlens.stashSave`) to save any working tree changes to the stash — can optionally provide a stash message
## Insiders
Add [`"gitlens.insiders": true`](#general-extension-settings) to your settings to join the insiders channel and get early access to upcoming features. Be aware that because this provides early access expect there to be issues.
## Extension Settings ## Extension Settings
None yet. GitLens is highly customizable and provides many configuration settings to allow the personalization of almost all features
### General Settings
|Name | Description
|-----|------------
|`gitlens.insiders`|Opts into the insiders channel -- provides access to upcoming features
|`gitlens.outputLevel`|Specifies how much (if any) output will be sent to the GitLens output channel
### Blame Annotation Settings
#### File Blame Annotation Settings
|Name | Description
|-----|------------
|`gitlens.blame.file.annotationType`|Specifies the type of blame annotations that will be shown for the current file<br />`gutter` - adds an annotation to the beginning of each line<br />`hover` - shows annotations when hovering over each line
|`gitlens.blame.file.lineHighlight.enabled`|Specifies whether or not to highlight lines associated with the current line
|`gitlens.blame.file.lineHighlight.locations`|Specifies where the associated line highlights will be shown<br />`gutter` - adds a gutter glyph<br />`line` - adds a full-line highlight background color<br />`overviewRuler` - adds a decoration to the overviewRuler (scroll bar)
|`gitlens.annotations.file.gutter.format`|Specifies the format of the gutter blame annotations<br />Available tokens<br />`${id}` - commit id<br />`${author}` - commit author<br />`${message}` - commit message<br />`${ago}` - relative commit date (e.g. 1 day ago)<br />`${date}` - formatted commit date (format specified by `gitlens.annotations.file.gutter.dateFormat`)<br />`${authorAgo}` - commit author, relative commit date<br />See https://github.com/eamodio/vscode-gitlens/wiki/Advanced-Formatting for advanced formatting
|`gitlens.annotations.file.gutter.dateFormat`|Specifies how to format absolute dates (using the `${date}` token) in gutter blame annotations<br />See https://momentjs.com/docs/#/displaying/format/ for valid formats
|`gitlens.annotations.file.gutter.compact`|Specifies whether or not to compact (deduplicate) matching adjacent gutter blame annotations
|`gitlens.annotations.file.gutter.heatmap.enabled`|Specifies whether or not to provide a heatmap indicator in the gutter blame annotations
|`gitlens.annotations.file.gutter.heatmap.location`|Specifies where the heatmap indicators will be shown in the gutter blame annotations<br />`left` - adds a heatmap indicator on the left edge of the gutter blame annotations<br />`right` - adds a heatmap indicator on the right edge of the gutter blame annotations
|`gitlens.annotations.file.gutter.hover.details`|Specifies whether or not to provide a commit details hover annotation over the gutter blame annotations
|`gitlens.annotations.file.gutter.hover.wholeLine`|Specifies whether or not to trigger hover annotations over the whole line
|`gitlens.annotations.file.hover.heatmap.enabled`|Specifies whether or not to provide heatmap indicators on the left edge of each line
|`gitlens.annotations.file.hover.wholeLine`|Specifies whether or not to trigger hover annotations over the whole line
#### Line Blame Annotation Settings
|Name | Description
|-----|------------
|`gitlens.blame.line.enabled`|Specifies whether or not to provide a blame annotation for the current line
|`gitlens.blame.line.annotationType`|Specifies the type of blame annotations that will be shown for the current line<br />`trailing` - adds an annotation to the end of the current line<br />`hover` - shows annotations when hovering over the current line
|`gitlens.annotations.line.trailing.format`|Specifies the format of the trailing blame annotations<br />Available tokens<br />`${id}` - commit id<br />`${author}` - commit author<br />`${message}` - commit message<br />`${ago}` - relative commit date (e.g. 1 day ago)<br />`${date}` - formatted commit date (format specified by `gitlens.annotations.line.trailing.dateFormat`)<br />`${authorAgo}` - commit author, relative commit date<br />See https://github.com/eamodio/vscode-gitlens/wiki/Advanced-Formatting for advanced formatting
|`gitlens.annotations.line.trailing.dateFormat`|Specifies how to format absolute dates (using the `${date}` token) in trailing blame annotations<br />See https://momentjs.com/docs/#/displaying/format/ for valid formats
|`gitlens.annotations.line.trailing.hover.details`|Specifies whether or not to provide a commit details hover annotation over the trailing blame annotations
|`gitlens.annotations.line.trailing.hover.changes`|Specifies whether or not to provide a changes (diff) hover annotation over the trailing blame annotations
|`gitlens.annotations.line.trailing.hover.wholeLine`|Specifies whether or not to trigger hover annotations over the whole line
|`gitlens.annotations.line.hover.details`|Specifies whether or not to provide a commit details hover annotation for the current line
|`gitlens.annotations.line.hover.changes`|Specifies whether or not to provide a changes (diff) hover annotation for the current line
### Code Lens Settings
|Name | Description
|-----|------------
|`gitlens.codeLens.enabled`|Specifies whether or not to provide any Git code lens
|`gitlens.codeLens.recentChange.enabled`|Specifies whether or not to show a `recent change` code lens showing the author and date of the most recent commit for the file or code block
|`gitlens.codeLens.recentChange.command`|Specifies the command to be executed when the `recent change` code lens is clicked<br />`gitlens.toggleFileBlame` - toggles file blame annotations<br />`gitlens.showBlameHistory` - opens the blame history explorer<br />`gitlens.showFileHistory` - opens the file history explorer<br />`gitlens.diffWithPrevious` - compares the current committed file with the previous commit<br />`gitlens.showQuickCommitDetails` - shows a commit details quick pick<br />`gitlens.showQuickCommitFileDetails` - shows a commit file details quick pick<br />`gitlens.showQuickFileHistory` - shows a file history quick pick<br />`gitlens.showQuickRepoHistory` - shows a branch history quick pick
|`gitlens.codeLens.authors.enabled`|Specifies whether or not to show an `authors` code lens showing number of authors of the file or code block and the most prominent author (if there is more than one)
|`gitlens.codeLens.authors.command`|Specifies the command to be executed when the `authors` code lens is clicked<br />`gitlens.toggleFileBlame` - toggles file blame annotations<br />`gitlens.showBlameHistory` - opens the blame history explorer<br />`gitlens.showFileHistory` - opens the file history explorer<br />`gitlens.diffWithPrevious` - compares the current committed file with the previous commit<br />`gitlens.showQuickCommitDetails` - shows a commit details quick pick<br />`gitlens.showQuickCommitFileDetails` - shows a commit file details quick pick<br />`gitlens.showQuickFileHistory` - shows a file history quick pick<br />`gitlens.showQuickRepoHistory` - shows a branch history quick pick
|`gitlens.codeLens.locations`|Specifies where Git code lens will be shown in the document<br />`document` - adds code lens at the top of the document<br />`containers` - adds code lens at the start of container-like symbols (modules, classes, interfaces, etc)<br />`blocks` - adds code lens at the start of block-like symbols (functions, methods, properties, etc) lines<br />`custom` - adds code lens at the start of symbols contained in `gitlens.codeLens.locationCustomSymbols`
|`gitlens.codeLens.customLocationSymbols`|Specifies the set of document symbols where Git code lens will be shown in the document
|`gitlens.codeLens.perLanguageLocations`|Specifies where Git code lens will be shown in the document for the specified languages
### Status Bar Settings
|Name | Description
|-----|------------
|`gitlens.statusBar.enabled`|Specifies whether or not to provide blame information on the status bar
|`gitlens.statusBar.alignment`|Specifies the blame alignment in the status bar<br />`left` - align to the left, `right` - align to the right
|`gitlens.statusBar.command`|Specifies the command to be executed when the blame status bar item is clicked<br />`gitlens.toggleFileBlame` - toggles file blame annotations<br />`gitlens.showBlameHistory` - opens the blame history explorer<br />`gitlens.showFileHistory` - opens the file history explorer<br />`gitlens.diffWithPrevious` - compares the current line commit with the previous<br />`gitlens.diffWithWorking` - compares the current line commit with the working tree<br />`gitlens.toggleCodeLens` - toggles Git code lens<br />`gitlens.showQuickCommitDetails` - shows a commit details quick pick<br />`gitlens.showQuickCommitFileDetails` - shows a commit file details quick pick<br />`gitlens.showQuickFileHistory` - shows a file history quick pick<br />`gitlens.showQuickRepoHistory` - shows a branch history quick pick
|`gitlens.statusBar.format`|Specifies the format of the blame information on the status bar<br />Available tokens<br />`${id}` - commit id<br />`${author}` - commit author<br />`${message}` - commit message<br />`${ago}` - relative commit date (e.g. 1 day ago)<br />`${date}` - formatted commit date (format specified by `gitlens.statusBar.dateFormat`)<br />See https://github.com/eamodio/vscode-gitlens/wiki/Advanced-Formatting for advanced formatting
|`gitlens.statusBar.dateFormat`|Specifies the date format of absolute dates shown in the blame information on the status bar<br />See https://momentjs.com/docs/#/displaying/format/ for valid formats
### Strings Settings
|Name | Description
|-----|------------
|`gitlens.strings.codeLens.unsavedChanges.recentChangeAndAuthors`|Specifies the string to be shown in place of both the `recent change` and `authors` code lens when there are unsaved changes
|`gitlens.strings.codeLens.unsavedChanges.recentChangeOnly`|Specifies the string to be shown in place of the `recent change` code lens when there are unsaved changes
|`gitlens.strings.codeLens.unsavedChanges.authorsOnly`|Specifies the string to be shown in place of the `authors` code lens when there are unsaved changes
### Theme Settings
|Name | Description
|-----|------------
|`gitlens.theme.annotations.file.gutter.separateLines`|Specifies whether or not gutter blame annotations will be separated by a small gap
|`gitlens.theme.annotations.file.gutter.dark.backgroundColor`|Specifies the dark theme background color of the gutter blame annotations
|`gitlens.theme.annotations.file.gutter.light.backgroundColor`|Specifies the light theme background color of the gutter blame annotations
|`gitlens.theme.annotations.file.gutter.dark.foregroundColor`|Specifies the dark theme foreground color of the gutter blame annotations
|`gitlens.theme.annotations.file.gutter.light.foregroundColor`|Specifies the light theme foreground color of the gutter blame annotations
|`gitlens.theme.annotations.file.gutter.dark.uncommittedForegroundColor`|Specifies the dark theme foreground color of an uncommitted line in the gutter blame annotations
|`gitlens.theme.annotations.file.gutter.light.uncommittedForegroundColor`|Specifies the light theme foreground color of an uncommitted line in the gutter blame annotations
|`gitlens.theme.annotations.file.hover.separateLines`|Specifies whether or not hover blame annotations will be separated by a small gap (if heatmap is enabled)
|`gitlens.theme.annotations.line.trailing.dark.backgroundColor`|Specifies the dark theme background color of the trailing blame annotation
|`gitlens.theme.annotations.line.trailing.light.backgroundColor`|Specifies the light theme background color of the trailing blame annotation
|`gitlens.theme.annotations.line.trailing.dark.foregroundColor`|Specifies the dark theme foreground color of the trailing blame annotation
|`gitlens.theme.annotations.line.trailing.light.foregroundColor`|Specifies the light theme foreground color of the trailing blame annotation
|`gitlens.theme.lineHighlight.dark.backgroundColor`|Specifies the dark theme background color of the associated line highlights in blame annotations. Must be a valid css color
|`gitlens.theme.lineHighlight.light.backgroundColor`|Specifies the light theme background color of the associated line highlights in blame annotations. Must be a valid css color
|`gitlens.theme.lineHighlight.dark.overviewRulerColor`|Specifies the dark theme overview ruler color of the associated line highlights in blame annotations
|`gitlens.theme.lineHighlight.light.overviewRulerColor`|Specifies the light theme overview ruler color of the associated line highlights in blame annotations
### Advanced Settings
|Name | Description
|-----|------------
|`gitlens.advanced.toggleWhitespace.enabled`|Specifies whether or not to toggle whitespace off then showing blame annotations (*may* be required by certain fonts/themes)
|`gitlens.advanced.telemetry.enabled`|Specifies whether or not to enable GitLens telemetry (even if enabled still abides by the overall `telemetry.enableTelemetry` setting
|`gitlens.advanced.menus`|Specifies which commands will be added to which menus
|`gitlens.advanced.caching.enabled`|Specifies whether git output will be cached
|`gitlens.advanced.caching.maxLines`|Specifies the threshold for caching larger documents
|`gitlens.advanced.git`|Specifies the git path to use
|`gitlens.advanced.gitignore.enabled`|Specifies whether or not to parse the root .gitignore file for better performance (i.e. avoids blaming excluded files)
|`gitlens.advanced.maxQuickHistory`|Specifies the maximum number of QuickPick history entries to show
|`gitlens.advanced.quickPick.closeOnFocusOut`|Specifies whether or not to close the QuickPick menu when focus is lost
## Known Issues ## Known Issues
Too many to count -- this is still very much a work in progress. - If the `Copy to * clipboard` commands don't work on Linux -- `xclip` needs to be installed. You can install it via `sudo apt-get install xclip`
- Visible whitespace causes issues ([vscode issue #11485](https://github.com/Microsoft/vscode/issues/11485)) with the `expanded` and `compact` blame annotation styles when using a non-monospace font -- set `"gitlens.advanced.toggleWhitespace.enabled": true` if you are using a non-monospace font
## Release Notes ## Contributors
### 0.0.1 A big thanks to the people that have contributed to this project:
Initial release but still heavily a work in progress. - Aurelio Ogliari ([@nobitagit](https://github.com/nobitagit)) — [contributions](https://github.com/eamodio/vscode-gitlens/commits?author=nobitagit)
- Zack Schuster ([@zackschuster](https://github.com/zackschuster)) — [contributions](https://github.com/eamodio/vscode-gitlens/commits?author=zackschuster)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

6
images/blame-dark.svg Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="18px" height="18px" viewBox="0 0 18 18" xml:space="preserve">
<g>
<rect fill="#00bcf2" fill-opacity="0.6" x="7" y="0" width="3" height="18"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 312 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

6
images/blame-light.svg Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="18px" height="18px" viewBox="0 0 18 18" xml:space="preserve">
<g>
<rect fill="#00bcf2" fill-opacity="0.6" x="7" y="0" width="3" height="18"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 313 B

10
images/git-icon-dark.svg Normal file
View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 93 93" xml:space="preserve">
<g>
<path fill="#FFFFFF" fill-opacity="0.74" d="M90,41.8L49.9,1.7c-2.3-2.3-6.1-2.3-8.4,0L33.2,10l10.6,10.6c2.5-0.8,5.3-0.3,7.2,1.7c2,2,2.5,4.8,1.7,7.3
l10.2,10.2c2.5-0.8,5.3-0.3,7.3,1.7c2.8,2.7,2.8,7.2,0,10s-7.2,2.8-10,0c-2.1-2.1-2.6-5.1-1.5-7.7l-9.5-9.5v25
c0.7,0.3,1.3,0.8,1.9,1.3c2.8,2.7,2.8,7.2,0,10c-2.8,2.7-7.2,2.7-10,0c-2.8-2.8-2.8-7.2,0-10c0.7-0.7,1.5-1.2,2.3-1.5V33.8
c-0.8-0.3-1.6-0.9-2.3-1.5c-2.1-2.1-2.6-5.1-1.5-7.7L29.2,14.2L1.7,41.7c-2.3,2.3-2.3,6.1,0,8.4l40.1,40.1c2.3,2.3,6.1,2.3,8.4,0
l39.9-39.9C92.4,47.9,92.4,44.1,90,41.8z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 815 B

10
images/git-icon-light.svg Normal file
View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 93 93" xml:space="preserve">
<g>
<path fill="#000000" fill-opacity="0.6" d="M90,41.8L49.9,1.7c-2.3-2.3-6.1-2.3-8.4,0L33.2,10l10.6,10.6c2.5-0.8,5.3-0.3,7.2,1.7c2,2,2.5,4.8,1.7,7.3
l10.2,10.2c2.5-0.8,5.3-0.3,7.3,1.7c2.8,2.7,2.8,7.2,0,10s-7.2,2.8-10,0c-2.1-2.1-2.6-5.1-1.5-7.7l-9.5-9.5v25
c0.7,0.3,1.3,0.8,1.9,1.3c2.8,2.7,2.8,7.2,0,10c-2.8,2.7-7.2,2.7-10,0c-2.8-2.8-2.8-7.2,0-10c0.7-0.7,1.5-1.2,2.3-1.5V33.8
c-0.8-0.3-1.6-0.9-2.3-1.5c-2.1-2.1-2.6-5.1-1.5-7.7L29.2,14.2L1.7,41.7c-2.3,2.3-2.3,6.1,0,8.4l40.1,40.1c2.3,2.3,6.1,2.3,8.4,0
l39.9-39.9C92.4,47.9,92.4,44.1,90,41.8z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 814 B

10
images/git-icon.svg Normal file
View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 93 93" xml:space="preserve">
<g>
<path fill="#F05133" d="M90,41.8L49.9,1.7c-2.3-2.3-6.1-2.3-8.4,0L33.2,10l10.6,10.6c2.5-0.8,5.3-0.3,7.2,1.7c2,2,2.5,4.8,1.7,7.3
l10.2,10.2c2.5-0.8,5.3-0.3,7.3,1.7c2.8,2.7,2.8,7.2,0,10s-7.2,2.8-10,0c-2.1-2.1-2.6-5.1-1.5-7.7l-9.5-9.5v25
c0.7,0.3,1.3,0.8,1.9,1.3c2.8,2.7,2.8,7.2,0,10c-2.8,2.7-7.2,2.7-10,0c-2.8-2.8-2.8-7.2,0-10c0.7-0.7,1.5-1.2,2.3-1.5V33.8
c-0.8-0.3-1.6-0.9-2.3-1.5c-2.1-2.1-2.6-5.1-1.5-7.7L29.2,14.2L1.7,41.7c-2.3,2.3-2.3,6.1,0,8.4l40.1,40.1c2.3,2.3,6.1,2.3,8.4,0
l39.9-39.9C92.4,47.9,92.4,44.1,90,41.8z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 795 B

18
images/gitlens-icon.svg Normal file
View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 93 93" style="enable-background:new 0 0 93 93;" xml:space="preserve">
<g>
<path fill="#F05133" d="M90,41.8L49.9,1.7c-2.3-2.3-6.1-2.3-8.4,0L33.2,10l10.6,10.6c2.5-0.8,5.3-0.3,7.2,1.7c2,2,2.5,4.8,1.7,7.3
l10.2,10.2c2.5-0.8,5.3-0.3,7.3,1.7c2.8,2.7,2.8,7.2,0,10s-7.2,2.8-10,0c-2.1-2.1-2.6-5.1-1.5-7.7l-9.5-9.5v25
c0.7,0.3,1.3,0.8,1.9,1.3c2.8,2.7,2.8,7.2,0,10c-2.8,2.7-7.2,2.7-10,0c-2.8-2.8-2.8-7.2,0-10c0.7-0.7,1.5-1.2,2.3-1.5V33.8
c-0.8-0.3-1.6-0.9-2.3-1.5c-2.1-2.1-2.6-5.1-1.5-7.7L29.2,14.2L1.7,41.7c-2.3,2.3-2.3,6.1,0,8.4l40.1,40.1c2.3,2.3,6.1,2.3,8.4,0
l39.9-39.9C92.4,47.9,92.4,44.1,90,41.8z"/>
</g>
<g>
<path fill="#FFFFFF" d="M38.1,56.9l-9-9c1.2-1.6,1.9-3.5,1.9-5.6c0-5.2-4.2-9.4-9.4-9.4s-9.4,4.2-9.4,9.4s4.2,9.4,9.4,9.4
c2.1,0,4.1-0.7,5.6-1.9l9,9c0.3,0.3,0.6,0.4,0.9,0.4c0.3,0,0.7-0.1,0.9-0.4C38.6,58.2,38.6,57.4,38.1,56.9z M21.6,49.7
c-4.1,0-7.5-3.4-7.5-7.5s3.4-7.5,7.5-7.5s7.5,3.4,7.5,7.5S25.7,49.7,21.6,49.7z"/>
<path fill="#FFFFFF" d="M27,41.7c0.1,0,0.2,0,0.2,0c0.5-0.1,0.8-0.6,0.7-1.2c-0.5-1.7-1.5-3.1-3.1-4c-1.5-0.9-3.3-1.1-5-0.7
c-0.5,0.1-0.8,0.6-0.7,1.2c0.1,0.5,0.6,0.8,1.2,0.7c1.2-0.3,2.5-0.2,3.6,0.5c1.1,0.6,1.9,1.6,2.2,2.9C26.2,41.4,26.6,41.7,27,41.7z"/>
<circle fill="#FFFFFF" cx="27.1" cy="43.9" r="0.9"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
images/gitlens-preview1.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 MiB

BIN
images/gitlens-preview2.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 185 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

1947
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,517 @@
/*
In 0.15.33 `setOfflineMode` is missing from the interfaces `ApplicationInsights` and `Channel`.
When a version > 0.15.33 of @types/applicationinsights is available on npm, this file can be
deleted and the dev dependency can be resurrected in `package.json`.
*/
// Type definitions for Application Insights v0.15.12
// Project: https://github.com/Microsoft/ApplicationInsights-node.js
// Definitions by: Scott Southwood <https://github.com/scsouthw/>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
interface AutoCollectConsole {
constructor(client: Client): AutoCollectConsole;
enable(isEnabled: boolean): void;
isInitialized(): boolean;
}
interface AutoCollectExceptions {
constructor(client: Client): AutoCollectExceptions;
isInitialized(): boolean;
enable(isEnabled: boolean): void;
}
interface AutoCollectPerformance {
constructor(client: Client): AutoCollectPerformance;
enable(isEnabled: boolean): void;
isInitialized(): boolean;
}
interface AutoCollectRequests {
constructor(client: Client): AutoCollectRequests;
enable(isEnabled: boolean): void;
isInitialized(): boolean;
}
declare namespace ContractsModule {
enum DataPointType {
Measurement = 0,
Aggregation = 1,
}
enum DependencyKind {
SQL = 0,
Http = 1,
Other = 2,
}
enum DependencySourceType {
Undefined = 0,
Aic = 1,
Apmc = 2,
}
enum SessionState {
Start = 0,
End = 1,
}
enum SeverityLevel {
Verbose = 0,
Information = 1,
Warning = 2,
Error = 3,
Critical = 4,
}
interface ContextTagKeys {
applicationVersion: string;
applicationBuild: string;
deviceId: string;
deviceIp: string;
deviceLanguage: string;
deviceLocale: string;
deviceModel: string;
deviceNetwork: string;
deviceOEMName: string;
deviceOS: string;
deviceOSVersion: string;
deviceRoleInstance: string;
deviceRoleName: string;
deviceScreenResolution: string;
deviceType: string;
deviceMachineName: string;
locationIp: string;
operationId: string;
operationName: string;
operationParentId: string;
operationRootId: string;
operationSyntheticSource: string;
operationIsSynthetic: string;
sessionId: string;
sessionIsFirst: string;
sessionIsNew: string;
userAccountAcquisitionDate: string;
userAccountId: string;
userAgent: string;
userId: string;
userStoreRegion: string;
sampleRate: string;
internalSdkVersion: string;
internalAgentVersion: string;
constructor(): ContextTagKeys;
}
interface Domain {
ver: number;
properties: any;
constructor(): Domain;
}
interface Data<TDomain extends ContractsModule.Domain> {
baseType: string;
baseData: TDomain;
constructor(): Data<TDomain>;
}
interface Envelope {
ver: number;
name: string;
time: string;
sampleRate: number;
seq: string;
iKey: string;
flags: number;
deviceId: string;
os: string;
osVer: string;
appId: string;
appVer: string;
userId: string;
tags: {
[key: string]: string;
};
data: Data<Domain>;
constructor(): Envelope;
}
interface EventData extends ContractsModule.Domain {
ver: number;
name: string;
properties: any;
measurements: any;
constructor(): EventData;
}
interface MessageData extends ContractsModule.Domain {
ver: number;
message: string;
severityLevel: ContractsModule.SeverityLevel;
properties: any;
constructor(): MessageData;
}
interface ExceptionData extends ContractsModule.Domain {
ver: number;
handledAt: string;
exceptions: ExceptionDetails[];
severityLevel: ContractsModule.SeverityLevel;
problemId: string;
crashThreadId: number;
properties: any;
measurements: any;
constructor(): ExceptionData;
}
interface StackFrame {
level: number;
method: string;
assembly: string;
fileName: string;
line: number;
constructor(): StackFrame;
}
interface ExceptionDetails {
id: number;
outerId: number;
typeName: string;
message: string;
hasFullStack: boolean;
stack: string;
parsedStack: StackFrame[];
constructor(): ExceptionDetails;
}
interface DataPoint {
name: string;
kind: ContractsModule.DataPointType;
value: number;
count: number;
min: number;
max: number;
stdDev: number;
constructor(): DataPoint;
}
interface MetricData extends ContractsModule.Domain {
ver: number;
metrics: DataPoint[];
properties: any;
constructor(): MetricData;
}
interface PageViewData extends ContractsModule.EventData {
ver: number;
url: string;
name: string;
duration: string;
properties: any;
measurements: any;
constructor(): PageViewData;
}
interface PageViewPerfData extends ContractsModule.PageViewData {
ver: number;
url: string;
perfTotal: string;
name: string;
duration: string;
networkConnect: string;
sentRequest: string;
receivedResponse: string;
domProcessing: string;
properties: any;
measurements: any;
constructor(): PageViewPerfData;
}
interface RemoteDependencyData extends ContractsModule.Domain {
ver: number;
name: string;
kind: ContractsModule.DataPointType;
value: number;
count: number;
min: number;
max: number;
stdDev: number;
dependencyKind: ContractsModule.DependencyKind;
success: boolean;
async: boolean;
dependencySource: ContractsModule.DependencySourceType;
commandName: string;
dependencyTypeName: string;
properties: any;
constructor(): RemoteDependencyData;
}
interface AjaxCallData extends ContractsModule.PageViewData {
ver: number;
url: string;
ajaxUrl: string;
name: string;
duration: string;
requestSize: number;
responseSize: number;
timeToFirstByte: string;
timeToLastByte: string;
callbackDuration: string;
responseCode: string;
success: boolean;
properties: any;
measurements: any;
constructor(): AjaxCallData;
}
interface RequestData extends ContractsModule.Domain {
ver: number;
id: string;
name: string;
startTime: string;
duration: string;
responseCode: string;
success: boolean;
httpMethod: string;
url: string;
properties: any;
measurements: any;
constructor(): RequestData;
}
interface SessionStateData extends ContractsModule.Domain {
ver: number;
state: ContractsModule.SessionState;
constructor(): SessionStateData;
}
interface PerformanceCounterData extends ContractsModule.Domain {
ver: number;
categoryName: string;
counterName: string;
instanceName: string;
kind: DataPointType;
count: number;
min: number;
max: number;
stdDev: number;
value: number;
properties: any;
constructor(): PerformanceCounterData;
}
}
interface Channel {
constructor(isDisabled: () => boolean, getBatchSize: () => number, getBatchIntervalMs: () => number, sender: Sender): Channel;
/**
* Add a telemetry item to the send buffer
*/
send(envelope: ContractsModule.Envelope): void;
handleCrash(envelope: ContractsModule.Envelope): void;
/**
* Immediately send buffered data
*/
triggerSend(isNodeCrashing?: boolean): void;
/**
*
*/
setOfflineMode(value: boolean, resentIntervall?: number): void;
}
interface Client {
config: Config;
context: Context;
commonProperties: {
[key: string]: string;
};
channel: Channel;
/**
* Constructs a new client of the client
* @param iKey the instrumentation key to use (read from environment variable if not specified)
*/
constructor(iKey?: string): Client;
/**
* Log a user action or other occurrence.
* @param name A string to identify this event in the portal.
* @param properties map[string, string] - additional data used to filter events and metrics in the portal. Defaults to empty.
* @param measurements map[string, number] - metrics associated with this event, displayed in Metrics Explorer on the portal. Defaults to empty.
*/
trackEvent(name: string, properties?: {
[key: string]: string;
}, measurements?: {
[key: string]: number;
}): void;
/**
* Log a trace message
* @param message A string to identify this event in the portal.
* @param properties map[string, string] - additional data used to filter events and metrics in the portal. Defaults to empty.
*/
trackTrace(message: string, severityLevel?: ContractsModule.SeverityLevel, properties?: {
[key: string]: string;
}): void;
/**
* Log an exception you have caught.
* @param exception An Error from a catch clause, or the string error message.
* @param properties map[string, string] - additional data used to filter events and metrics in the portal. Defaults to empty.
* @param measurements map[string, number] - metrics associated with this event, displayed in Metrics Explorer on the portal. Defaults to empty.
*/
trackException(exception: Error, properties?: {
[key: string]: string;
}): void;
/**
* Log a numeric value that is not associated with a specific event. Typically used to send regular reports of performance indicators.
* To send a single measurement, use just the first two parameters. If you take measurements very frequently, you can reduce the
* telemetry bandwidth by aggregating multiple measurements and sending the resulting average at intervals.
* @param name A string that identifies the metric.
* @param value The value of the metric
* @param count the number of samples used to get this value
* @param min the min sample for this set
* @param max the max sample for this set
* @param stdDev the standard deviation of the set
*/
trackMetric(name: string, value: number, count?: number, min?: number, max?: number, stdDev?: number, properties?: {
[key: string]: string;
}): void;
/**
* Log an incoming http request to your server. The request data will be tracked during the response "finish" event if it is successful or the request "error"
* event if it fails. The request duration is automatically calculated as the timespan between when the trackRequest method was called, and when the response "finish"
* or request "error" events were fired.
* @param request The http.IncomingMessage object to track
* @param response The http.ServerResponse object for this request
* @param properties map[string, string] - additional data used to filter requests in the portal. Defaults to empty.
*/
trackRequest(request: any /* http.IncomingMessage */, response: any /* http.ServerResponse */, properties?: {
[key: string]: string;
}): void;
/**
* Log an incoming http request to your server. The request data is tracked synchronously rather than waiting for the response "finish"" or request "error"" events.
* Use this if you need your request telemetry to respect custom app insights operation and user context (for example if you set any appInsights.client.context.tags).
* @param request The http.IncomingMessage object to track
* @param response The http.ServerResponse object for this request
* @param ellapsedMilliseconds The duration for this request. Defaults to 0.
* @param properties map[string, string] - additional data used to filter requests in the portal. Defaults to empty.
* @param error An error that was returned for this request if it was unsuccessful. Defaults to null.
*/
trackRequestSync(request: any /*http.IncomingMessage */, response: any /*http.ServerResponse */, ellapsedMilliseconds?: number, properties?: {
[key: string]: string;
}, error?: any): void;
/**
* Log information about a dependency of your app. Typically used to track the time database calls or outgoing http requests take from your server.
* @param name The name of the dependency (i.e. "myDatabse")
* @param commandname The name of the command executed on the dependency
* @param elapsedTimeMs The amount of time in ms that the dependency took to return the result
* @param success True if the dependency succeeded, false otherwise
* @param dependencyTypeName The type of the dependency (i.e. "SQL" "HTTP"). Defaults to empty.
* @param properties map[string, string] - additional data used to filter events and metrics in the portal. Defaults to empty.
* @param dependencyKind ContractsModule.DependencyKind of this dependency. Defaults to Other.
* @param async True if the dependency was executed asynchronously, false otherwise. Defaults to false
* @param dependencySource ContractsModule.DependencySourceType of this dependency. Defaults to Undefined.
*/
trackDependency(name: string, commandName: string, elapsedTimeMs: number, success: boolean, dependencyTypeName?: string, properties?: {}, dependencyKind?: any, async?: boolean, dependencySource?: number): void;
/**
* Immediately send all queued telemetry.
*/
sendPendingData(callback?: (response: string) => void): void;
getEnvelope(data: ContractsModule.Data<ContractsModule.Domain>, tagOverrides?: {
[key: string]: string;
}): ContractsModule.Envelope;
/**
* Generic track method for all telemetry types
* @param data the telemetry to send
* @param tagOverrides the context tags to use for this telemetry which overwrite default context values
*/
track(data: ContractsModule.Data<ContractsModule.Domain>, tagOverrides?: {
[key: string]: string;
}): void;
}
interface Config {
instrumentationKey: string;
sessionRenewalMs: number;
sessionExpirationMs: number;
endpointUrl: string;
maxBatchSize: number;
maxBatchIntervalMs: number;
disableAppInsights: boolean;
constructor(instrumentationKey?: string): Config;
}
interface Context {
keys: ContractsModule.ContextTagKeys;
tags: {
[key: string]: string;
};
constructor(packageJsonPath?: string): Context;
}
interface Sender {
constructor(getUrl: () => string, onSuccess?: (response: string) => void, onError?: (error: Error) => void): Sender;
send(payload: any/* Buffer */): void;
saveOnCrash(payload: string): void;
/**
* enable caching events locally on error
*/
enableCacheOnError(): void;
/**
* disable caching events locally on error
*/
disableCacheOnError(): void;
}
/**
* The singleton meta interface for the default client of the client. This interface is used to setup/start and configure
* the auto-collection behavior of the application insights module.
*/
interface ApplicationInsights {
client: Client;
/**
* Initializes a client with the given instrumentation key, if this is not specified, the value will be
* read from the environment variable APPINSIGHTS_INSTRUMENTATIONKEY
* @returns {ApplicationInsights/Client} a new client
*/
getClient(instrumentationKey?: string): Client;
/**
* Initializes the default client of the client and sets the default configuration
* @param instrumentationKey the instrumentation key to use. Optional, if this is not specified, the value will be
* read from the environment variable APPINSIGHTS_INSTRUMENTATIONKEY
* @returns {ApplicationInsights} this interface
*/
setup(instrumentationKey?: string): ApplicationInsights;
/**
* Starts automatic collection of telemetry. Prior to calling start no telemetry will be collected
* @returns {ApplicationInsights} this interface
*/
start(): ApplicationInsights;
/**
* Sets the state of console tracking (enabled by default)
* @param value if true console activity will be sent to Application Insights
* @returns {ApplicationInsights} this interface
*/
setAutoCollectConsole(value: boolean): ApplicationInsights;
/**
* Sets the state of dependency tracking (enabled by default)
* @param value if true dependencies will be sent to Application Insights
* @returns {ApplicationInsights} this interface
*/
setAutoCollectDependencies(value: boolean): ApplicationInsights;
/**
* Sets the state of exception tracking (enabled by default)
* @param value if true uncaught exceptions will be sent to Application Insights
* @returns {ApplicationInsights} this interface
*/
setAutoCollectExceptions(value: boolean): ApplicationInsights;
/**
* Sets the state of performance tracking (enabled by default)
* @param value if true performance counters will be collected every second and sent to Application Insights
* @returns {ApplicationInsights} this interface
*/
setAutoCollectPerformance(value: boolean): ApplicationInsights;
/**
* Sets the state of request tracking (enabled by default)
* @param value if true requests will be sent to Application Insights
* @returns {ApplicationInsights} this interface
*/
setAutoCollectRequests(value: boolean): ApplicationInsights;
/**
* Enables verbose debug logging
* @returns {ApplicationInsights} this interface
*/
enableVerboseLogging(): ApplicationInsights;
/**
*
*/
setOfflineMode(value: boolean, resentIntervall?: number): ApplicationInsights;
/**
*
*/
setAutoDependencyCorrelation(value: boolean): ApplicationInsights;
}
declare module "applicationinsights" {
const applicationinsights: ApplicationInsights;
export = applicationinsights;
}

10
src/@types/ignore/index.d.ts vendored Normal file
View File

@@ -0,0 +1,10 @@
declare module "ignore" {
namespace ignore {
interface Ignore {
add(patterns: string | Array<string> | Ignore): Ignore;
filter(paths: Array<string>): Array<string>;
}
}
function ignore(): ignore.Ignore;
export = ignore;
}

13
src/@types/spawn-rx/index.d.ts vendored Normal file
View File

@@ -0,0 +1,13 @@
/// <reference path="../../../node_modules/rxjs/Observable.d.ts" />
declare module "spawn-rx" {
import { Observable } from 'rxjs/Observable';
namespace spawnrx {
function findActualExecutable(exe: string, args?: Array<string> | undefined): { cmd: string, args: Array<string> };
function spawnDetached(exe: string, params?: Array<string> | undefined, opts?: Object | undefined): Observable<string>;
function spawn(exe: string, params?: Array<string> | undefined, opts?: Object | undefined): Observable<string>;
function spawnDetachedPromise(exe: string, params?: Array<string> | undefined, opts?: Object | undefined): Promise<string>;
function spawnPromise(exe: string, params?: Array<string> | undefined, opts?: Object | undefined): Promise<string>;
}
export = spawnrx;
}

View File

@@ -0,0 +1,59 @@
'use strict';
import { commands, Disposable, TextEditor, window } from 'vscode';
import { BuiltInCommands } from './constants';
export class ActiveEditorTracker extends Disposable {
private _disposable: Disposable;
private _resolver: ((value?: TextEditor | PromiseLike<TextEditor>) => void) | undefined;
constructor() {
super(() => this.dispose());
this._disposable = window.onDidChangeActiveTextEditor(e => this._resolver && this._resolver(e));
}
dispose() {
this._disposable && this._disposable.dispose();
}
async awaitClose(timeout: number = 500): Promise<TextEditor> {
this.close();
return this.wait(timeout);
}
async awaitNext(timeout: number = 500): Promise<TextEditor> {
this.next();
return this.wait(timeout);
}
async close(): Promise<{} | undefined> {
return commands.executeCommand(BuiltInCommands.CloseActiveEditor);
}
async next(): Promise<{} | undefined> {
return commands.executeCommand(BuiltInCommands.NextEditor);
}
async wait(timeout: number = 500): Promise<TextEditor> {
const editor = await new Promise<TextEditor>((resolve, reject) => {
let timer: any;
this._resolver = (editor: TextEditor) => {
if (timer) {
clearTimeout(timer as any);
timer = 0;
resolve(editor);
}
};
timer = setTimeout(() => {
resolve(window.activeTextEditor);
timer = 0;
}, timeout) as any;
});
this._resolver = undefined;
return editor;
}
}

View File

@@ -0,0 +1,284 @@
'use strict';
import { Functions, Objects } from '../system';
import { DecorationRenderOptions, Disposable, Event, EventEmitter, ExtensionContext, OverviewRulerLane, TextDocument, TextDocumentChangeEvent, TextEditor, TextEditorDecorationType, TextEditorViewColumnChangeEvent, window, workspace } from 'vscode';
import { AnnotationProviderBase } from './annotationProvider';
import { TextDocumentComparer, TextEditorComparer } from '../comparers';
import { BlameLineHighlightLocations, ExtensionKey, FileAnnotationType, IConfig, themeDefaults } from '../configuration';
import { BlameabilityChangeEvent, GitContextTracker, GitService, GitUri } from '../gitService';
import { GutterBlameAnnotationProvider } from './gutterBlameAnnotationProvider';
import { HoverBlameAnnotationProvider } from './hoverBlameAnnotationProvider';
import { Logger } from '../logger';
import { WhitespaceController } from './whitespaceController';
export const Decorations = {
annotation: window.createTextEditorDecorationType({
isWholeLine: true,
textDecoration: 'none'
} as DecorationRenderOptions),
highlight: undefined as TextEditorDecorationType | undefined
};
export class AnnotationController extends Disposable {
private _onDidToggleAnnotations = new EventEmitter<void>();
get onDidToggleAnnotations(): Event<void> {
return this._onDidToggleAnnotations.event;
}
private _annotationsDisposable: Disposable | undefined;
private _annotationProviders: Map<number, AnnotationProviderBase> = new Map();
private _config: IConfig;
private _disposable: Disposable;
private _whitespaceController: WhitespaceController | undefined;
constructor(private context: ExtensionContext, private git: GitService, private gitContextTracker: GitContextTracker) {
super(() => this.dispose());
this._onConfigurationChanged();
const subscriptions: Disposable[] = [];
subscriptions.push(workspace.onDidChangeConfiguration(this._onConfigurationChanged, this));
this._disposable = Disposable.from(...subscriptions);
}
dispose() {
this._annotationProviders.forEach(async (p, i) => await this.clear(i));
Decorations.annotation && Decorations.annotation.dispose();
Decorations.highlight && Decorations.highlight.dispose();
this._annotationsDisposable && this._annotationsDisposable.dispose();
this._whitespaceController && this._whitespaceController.dispose();
this._disposable && this._disposable.dispose();
}
private _onConfigurationChanged() {
let toggleWhitespace = workspace.getConfiguration(`${ExtensionKey}.advanced.toggleWhitespace`).get<boolean>('enabled');
if (!toggleWhitespace) {
// Until https://github.com/Microsoft/vscode/issues/11485 is fixed we need to toggle whitespace for non-monospace fonts and ligatures
// TODO: detect monospace font
toggleWhitespace = workspace.getConfiguration('editor').get<boolean>('fontLigatures');
}
if (toggleWhitespace && !this._whitespaceController) {
this._whitespaceController = new WhitespaceController();
}
else if (!toggleWhitespace && this._whitespaceController) {
this._whitespaceController.dispose();
this._whitespaceController = undefined;
}
const cfg = workspace.getConfiguration().get<IConfig>(ExtensionKey)!;
const cfgHighlight = cfg.blame.file.lineHighlight;
const cfgTheme = cfg.theme.lineHighlight;
let changed = false;
if (!Objects.areEquivalent(cfgHighlight, this._config && this._config.blame.file.lineHighlight) ||
!Objects.areEquivalent(cfgTheme, this._config && this._config.theme.lineHighlight)) {
changed = true;
Decorations.highlight && Decorations.highlight.dispose();
if (cfgHighlight.enabled) {
Decorations.highlight = window.createTextEditorDecorationType({
gutterIconSize: 'contain',
isWholeLine: true,
overviewRulerLane: OverviewRulerLane.Right,
dark: {
backgroundColor: cfgHighlight.locations.includes(BlameLineHighlightLocations.Line)
? cfgTheme.dark.backgroundColor || themeDefaults.lineHighlight.dark.backgroundColor
: undefined,
gutterIconPath: cfgHighlight.locations.includes(BlameLineHighlightLocations.Gutter)
? this.context.asAbsolutePath('images/blame-dark.svg')
: undefined,
overviewRulerColor: cfgHighlight.locations.includes(BlameLineHighlightLocations.OverviewRuler)
? cfgTheme.dark.overviewRulerColor || themeDefaults.lineHighlight.dark.overviewRulerColor
: undefined
},
light: {
backgroundColor: cfgHighlight.locations.includes(BlameLineHighlightLocations.Line)
? cfgTheme.light.backgroundColor || themeDefaults.lineHighlight.light.backgroundColor
: undefined,
gutterIconPath: cfgHighlight.locations.includes(BlameLineHighlightLocations.Gutter)
? this.context.asAbsolutePath('images/blame-light.svg')
: undefined,
overviewRulerColor: cfgHighlight.locations.includes(BlameLineHighlightLocations.OverviewRuler)
? cfgTheme.light.overviewRulerColor || themeDefaults.lineHighlight.light.overviewRulerColor
: undefined
}
});
}
else {
Decorations.highlight = undefined;
}
}
if (!Objects.areEquivalent(cfg.blame.file, this._config && this._config.blame.file) ||
!Objects.areEquivalent(cfg.annotations, this._config && this._config.annotations) ||
!Objects.areEquivalent(cfg.theme.annotations, this._config && this._config.theme.annotations)) {
changed = true;
}
this._config = cfg;
if (changed) {
// Since the configuration has changed -- reset any visible annotations
for (const provider of this._annotationProviders.values()) {
if (provider === undefined) continue;
provider.reset();
}
}
}
async clear(column: number) {
const provider = this._annotationProviders.get(column);
if (!provider) return;
this._annotationProviders.delete(column);
await provider.dispose();
if (this._annotationProviders.size === 0) {
Logger.log(`Remove listener registrations for annotations`);
this._annotationsDisposable && this._annotationsDisposable.dispose();
this._annotationsDisposable = undefined;
}
this._onDidToggleAnnotations.fire();
}
getAnnotationType(editor: TextEditor): FileAnnotationType | undefined {
const provider = this.getProvider(editor);
return provider === undefined ? undefined : provider.annotationType;
}
getProvider(editor: TextEditor): AnnotationProviderBase | undefined {
if (!editor || !editor.document || !this.git.isEditorBlameable(editor)) return undefined;
return this._annotationProviders.get(editor.viewColumn || -1);
}
async showAnnotations(editor: TextEditor, type: FileAnnotationType, shaOrLine?: string | number): Promise<boolean> {
if (!editor || !editor.document || !this.git.isEditorBlameable(editor)) return false;
const currentProvider = this._annotationProviders.get(editor.viewColumn || -1);
if (currentProvider && TextEditorComparer.equals(currentProvider.editor, editor)) {
await currentProvider.selection(shaOrLine);
return true;
}
const gitUri = await GitUri.fromUri(editor.document.uri, this.git);
let provider: AnnotationProviderBase | undefined = undefined;
switch (type) {
case FileAnnotationType.Gutter:
provider = new GutterBlameAnnotationProvider(this.context, editor, Decorations.annotation, Decorations.highlight, this._whitespaceController, this.git, gitUri);
break;
case FileAnnotationType.Hover:
provider = new HoverBlameAnnotationProvider(this.context, editor, Decorations.annotation, Decorations.highlight, this._whitespaceController, this.git, gitUri);
break;
}
if (provider === undefined || !(await provider.validate())) return false;
if (currentProvider) {
await this.clear(currentProvider.editor.viewColumn || -1);
}
if (!this._annotationsDisposable && this._annotationProviders.size === 0) {
Logger.log(`Add listener registrations for annotations`);
const subscriptions: Disposable[] = [];
subscriptions.push(window.onDidChangeVisibleTextEditors(Functions.debounce(this._onVisibleTextEditorsChanged, 100), this));
subscriptions.push(window.onDidChangeTextEditorViewColumn(this._onTextEditorViewColumnChanged, this));
subscriptions.push(workspace.onDidChangeTextDocument(this._onTextDocumentChanged, this));
subscriptions.push(workspace.onDidCloseTextDocument(this._onTextDocumentClosed, this));
subscriptions.push(this.gitContextTracker.onDidBlameabilityChange(this._onBlameabilityChanged, this));
this._annotationsDisposable = Disposable.from(...subscriptions);
}
this._annotationProviders.set(editor.viewColumn || -1, provider);
if (await provider.provideAnnotation(shaOrLine)) {
this._onDidToggleAnnotations.fire();
return true;
}
return false;
}
async toggleAnnotations(editor: TextEditor, type: FileAnnotationType, shaOrLine?: string | number): Promise<boolean> {
if (!editor || !editor.document || !this.git.isEditorBlameable(editor)) return false;
const provider = this._annotationProviders.get(editor.viewColumn || -1);
if (provider === undefined) return this.showAnnotations(editor, type, shaOrLine);
await this.clear(provider.editor.viewColumn || -1);
return false;
}
private _onBlameabilityChanged(e: BlameabilityChangeEvent) {
if (e.blameable || !e.editor) return;
for (const [key, p] of this._annotationProviders) {
if (!TextDocumentComparer.equals(p.document, e.editor.document)) continue;
Logger.log('BlameabilityChanged:', `Clear annotations for column ${key}`);
this.clear(key);
}
}
private _onTextDocumentChanged(e: TextDocumentChangeEvent) {
for (const [key, p] of this._annotationProviders) {
if (!TextDocumentComparer.equals(p.document, e.document)) continue;
// TODO: Rework this once https://github.com/Microsoft/vscode/issues/27231 is released in v1.13
// We have to defer because isDirty is not reliable inside this event
setTimeout(() => {
// If the document is dirty all is fine, just kick out since the GitContextTracker will handle it
if (e.document.isDirty) return;
// If the document isn't dirty, it is very likely this event was triggered by an outside edit of this document
// Which means the document has been reloaded and the annotations have been removed, so we need to update (clear) our state tracking
Logger.log('TextDocumentChanged:', `Clear annotations for column ${key}`);
this.clear(key);
}, 1);
}
}
private _onTextDocumentClosed(e: TextDocument) {
for (const [key, p] of this._annotationProviders) {
if (!TextDocumentComparer.equals(p.document, e)) continue;
Logger.log('TextDocumentClosed:', `Clear annotations for column ${key}`);
this.clear(key);
}
}
private async _onTextEditorViewColumnChanged(e: TextEditorViewColumnChangeEvent) {
const viewColumn = e.viewColumn || -1;
Logger.log('TextEditorViewColumnChanged:', `Clear annotations for column ${viewColumn}`);
await this.clear(viewColumn);
for (const [key, p] of this._annotationProviders) {
if (!TextEditorComparer.equals(p.editor, e.textEditor)) continue;
Logger.log('TextEditorViewColumnChanged:', `Clear annotations for column ${key}`);
await this.clear(key);
}
}
private async _onVisibleTextEditorsChanged(e: TextEditor[]) {
if (e.every(_ => _.document.uri.scheme === 'inmemory')) return;
for (const [key, p] of this._annotationProviders) {
if (e.some(_ => TextEditorComparer.equals(p.editor, _))) continue;
Logger.log('VisibleTextEditorsChanged:', `Clear annotations for column ${key}`);
this.clear(key);
}
}
}

View File

@@ -0,0 +1,74 @@
'use strict';
import { Functions } from '../system';
import { Disposable, ExtensionContext, TextDocument, TextEditor, TextEditorDecorationType, TextEditorSelectionChangeEvent, window, workspace } from 'vscode';
import { TextDocumentComparer } from '../comparers';
import { ExtensionKey, FileAnnotationType, IConfig } from '../configuration';
import { WhitespaceController } from './whitespaceController';
export abstract class AnnotationProviderBase extends Disposable {
public annotationType: FileAnnotationType;
public document: TextDocument;
protected _config: IConfig;
protected _disposable: Disposable;
constructor(context: ExtensionContext, public editor: TextEditor, protected decoration: TextEditorDecorationType, protected highlightDecoration: TextEditorDecorationType | undefined, protected whitespaceController: WhitespaceController | undefined) {
super(() => this.dispose());
this.document = this.editor.document;
this._config = workspace.getConfiguration().get<IConfig>(ExtensionKey)!;
const subscriptions: Disposable[] = [];
subscriptions.push(window.onDidChangeTextEditorSelection(this._onTextEditorSelectionChanged, this));
this._disposable = Disposable.from(...subscriptions);
}
async dispose() {
await this.clear();
this._disposable && this._disposable.dispose();
}
private async _onTextEditorSelectionChanged(e: TextEditorSelectionChangeEvent) {
if (!TextDocumentComparer.equals(this.document, e.textEditor && e.textEditor.document)) return;
return this.selection(e.selections[0].active.line);
}
async clear() {
if (this.editor !== undefined) {
try {
this.editor.setDecorations(this.decoration, []);
this.highlightDecoration && this.editor.setDecorations(this.highlightDecoration, []);
// I have no idea why the decorators sometimes don't get removed, but if they don't try again with a tiny delay
if (this.highlightDecoration !== undefined) {
await Functions.wait(1);
if (this.highlightDecoration === undefined) return;
this.editor.setDecorations(this.highlightDecoration, []);
}
}
catch (ex) { }
}
// HACK: Until https://github.com/Microsoft/vscode/issues/11485 is fixed -- restore whitespace
this.whitespaceController && await this.whitespaceController.restore();
}
async reset() {
await this.clear();
this._config = workspace.getConfiguration().get<IConfig>(ExtensionKey)!;
await this.provideAnnotation(this.editor === undefined ? undefined : this.editor.selection.active.line);
}
abstract async provideAnnotation(shaOrLine?: string | number): Promise<boolean>;
abstract async selection(shaOrLine?: string | number): Promise<void>;
abstract async validate(): Promise<boolean>;
}

View File

@@ -0,0 +1,191 @@
import { DecorationInstanceRenderOptions, DecorationOptions, ThemableDecorationRenderOptions } from 'vscode';
import { IThemeConfig, themeDefaults } from '../configuration';
import { CommitFormatter, GitCommit, GitService, GitUri, ICommitFormatOptions } from '../gitService';
import * as moment from 'moment';
interface IHeatmapConfig {
enabled: boolean;
location?: 'left' | 'right';
}
interface IRenderOptions {
uncommittedForegroundColor?: {
dark: string;
light: string;
};
before?: DecorationInstanceRenderOptions & ThemableDecorationRenderOptions & { height?: string };
dark?: DecorationInstanceRenderOptions;
light?: DecorationInstanceRenderOptions;
}
export const endOfLineIndex = 1000000;
export class Annotations {
static applyHeatmap(decoration: DecorationOptions, date: Date, now: moment.Moment) {
const color = this._getHeatmapColor(now, date);
(decoration.renderOptions!.before! as any).borderColor = color;
}
private static _getHeatmapColor(now: moment.Moment, date: Date) {
const days = now.diff(moment(date), 'days');
if (days <= 2) return '#ffeca7';
if (days <= 7) return '#ffdd8c';
if (days <= 14) return '#ffdd7c';
if (days <= 30) return '#fba447';
if (days <= 60) return '#f68736';
if (days <= 90) return '#f37636';
if (days <= 180) return '#ca6632';
if (days <= 365) return '#c0513f';
if (days <= 730) return '#a2503a';
return '#793738';
}
static async changesHover(commit: GitCommit, line: number, uri: GitUri, git: GitService): Promise<DecorationOptions> {
let message: string | undefined = undefined;
if (commit.isUncommitted) {
const [previous, current] = await git.getDiffForLine(uri, line + uri.offset);
message = CommitFormatter.toHoverDiff(commit, previous, current);
}
else if (commit.previousSha !== undefined) {
const [previous, current] = await git.getDiffForLine(uri, line + uri.offset, commit.previousSha);
message = CommitFormatter.toHoverDiff(commit, previous, current);
}
return {
hoverMessage: message
} as DecorationOptions;
}
static detailsHover(commit: GitCommit): DecorationOptions {
const message = CommitFormatter.toHoverAnnotation(commit);
return {
hoverMessage: message
} as DecorationOptions;
}
static gutter(commit: GitCommit, format: string, dateFormatOrFormatOptions: string | null | ICommitFormatOptions, renderOptions: IRenderOptions, compact: boolean): DecorationOptions {
let content = `\u00a0${CommitFormatter.fromTemplate(format, commit, dateFormatOrFormatOptions)}\u00a0`;
if (compact) {
content = '\u00a0'.repeat(content.length);
}
return {
renderOptions: {
before: {
...renderOptions.before,
...{
contentText: content
}
},
dark: {
before: commit.isUncommitted
? { ...renderOptions.dark, ...{ color: renderOptions.uncommittedForegroundColor!.dark } }
: { ...renderOptions.dark }
},
light: {
before: commit.isUncommitted
? { ...renderOptions.light, ...{ color: renderOptions.uncommittedForegroundColor!.light } }
: { ...renderOptions.light }
}
} as DecorationInstanceRenderOptions
} as DecorationOptions;
}
static gutterRenderOptions(cfgTheme: IThemeConfig, heatmap: IHeatmapConfig): IRenderOptions {
const cfgFileTheme = cfgTheme.annotations.file.gutter;
let borderStyle = undefined;
let borderWidth = undefined;
if (heatmap.enabled) {
borderStyle = 'solid';
borderWidth = heatmap.location === 'left' ? '0 0 0 2px' : '0 2px 0 0';
}
return {
uncommittedForegroundColor: {
dark: cfgFileTheme.dark.uncommittedForegroundColor || cfgFileTheme.dark.foregroundColor || themeDefaults.annotations.file.gutter.dark.foregroundColor,
light: cfgFileTheme.light.uncommittedForegroundColor || cfgFileTheme.light.foregroundColor || themeDefaults.annotations.file.gutter.light.foregroundColor
},
before: {
borderStyle: borderStyle,
borderWidth: borderWidth,
height: cfgFileTheme.separateLines ? 'calc(100% - 1px)' : '100%',
margin: '0 26px 0 0',
textDecoration: 'none'
},
dark: {
backgroundColor: cfgFileTheme.dark.backgroundColor || undefined,
color: cfgFileTheme.dark.foregroundColor || themeDefaults.annotations.file.gutter.dark.foregroundColor
} as DecorationInstanceRenderOptions,
light: {
backgroundColor: cfgFileTheme.light.backgroundColor || undefined,
color: cfgFileTheme.light.foregroundColor || themeDefaults.annotations.file.gutter.light.foregroundColor
} as DecorationInstanceRenderOptions
} as IRenderOptions;
}
static hover(commit: GitCommit, renderOptions: IRenderOptions, heatmap: boolean): DecorationOptions {
return {
hoverMessage: CommitFormatter.toHoverAnnotation(commit),
renderOptions: heatmap ? { before: { ...renderOptions.before } } : undefined
} as DecorationOptions;
}
static hoverRenderOptions(cfgTheme: IThemeConfig, heatmap: IHeatmapConfig): IRenderOptions {
if (!heatmap.enabled) return { before: undefined };
return {
before: {
borderStyle: 'solid',
borderWidth: '0 0 0 2px',
contentText: '\u200B',
height: cfgTheme.annotations.file.hover.separateLines ? 'calc(100% - 1px)' : '100%',
margin: '0 26px 0 0',
textDecoration: 'none'
}
} as IRenderOptions;
}
static trailing(commit: GitCommit, format: string, dateFormat: string | null, cfgTheme: IThemeConfig): DecorationOptions {
const message = CommitFormatter.fromTemplate(format, commit, dateFormat);
return {
renderOptions: {
after: {
contentText: `\u00a0${message}\u00a0`
},
dark: {
after: {
backgroundColor: cfgTheme.annotations.line.trailing.dark.backgroundColor || undefined,
color: cfgTheme.annotations.line.trailing.dark.foregroundColor || themeDefaults.annotations.line.trailing.dark.foregroundColor
}
},
light: {
after: {
backgroundColor: cfgTheme.annotations.line.trailing.light.backgroundColor || undefined,
color: cfgTheme.annotations.line.trailing.light.foregroundColor || themeDefaults.annotations.line.trailing.light.foregroundColor
}
}
} as DecorationInstanceRenderOptions
} as DecorationOptions;
}
static withRange(decoration: DecorationOptions, start?: number, end?: number): DecorationOptions {
let range = decoration.range;
if (start !== undefined) {
range = range.with({
start: range.start.with({ character: start })
});
}
if (end !== undefined) {
range = range.with({
end: range.end.with({ character: end })
});
}
return { ...decoration, ...{ range: range } };
}
}

View File

@@ -0,0 +1,82 @@
'use strict';
import { Iterables } from '../system';
import { ExtensionContext, Range, TextEditor, TextEditorDecorationType } from 'vscode';
import { AnnotationProviderBase } from './annotationProvider';
import { GitService, GitUri, IGitBlame } from '../gitService';
import { WhitespaceController } from './whitespaceController';
export abstract class BlameAnnotationProviderBase extends AnnotationProviderBase {
protected _blame: Promise<IGitBlame>;
constructor(context: ExtensionContext, editor: TextEditor, decoration: TextEditorDecorationType, highlightDecoration: TextEditorDecorationType | undefined, whitespaceController: WhitespaceController | undefined, protected git: GitService, protected uri: GitUri) {
super(context, editor, decoration, highlightDecoration, whitespaceController);
this._blame = this.git.getBlameForFile(this.uri);
}
async selection(shaOrLine?: string | number, blame?: IGitBlame) {
if (!this.highlightDecoration) return;
if (blame === undefined) {
blame = await this._blame;
if (!blame || !blame.lines.length) return;
}
const offset = this.uri.offset;
let sha: string | undefined = undefined;
if (typeof shaOrLine === 'string') {
sha = shaOrLine;
}
else if (typeof shaOrLine === 'number') {
const line = shaOrLine - offset;
if (line >= 0) {
const commitLine = blame.lines[line];
sha = commitLine && commitLine.sha;
}
}
else {
sha = Iterables.first(blame.commits.values()).sha;
}
if (!sha) {
this.editor.setDecorations(this.highlightDecoration, []);
return;
}
const highlightDecorationRanges = blame.lines
.filter(l => l.sha === sha)
.map(l => this.editor.document.validateRange(new Range(l.line + offset, 0, l.line + offset, 1000000)));
this.editor.setDecorations(this.highlightDecoration, highlightDecorationRanges);
}
async validate(): Promise<boolean> {
const blame = await this._blame;
return blame !== undefined && blame.lines.length !== 0;
}
protected async getBlame(requiresWhitespaceHack: boolean): Promise<IGitBlame | undefined> {
let whitespacePromise: Promise<void> | undefined;
// HACK: Until https://github.com/Microsoft/vscode/issues/11485 is fixed -- override whitespace (turn off)
if (requiresWhitespaceHack) {
whitespacePromise = this.whitespaceController && this.whitespaceController.override();
}
let blame: IGitBlame;
if (whitespacePromise) {
[blame] = await Promise.all([this._blame, whitespacePromise]);
}
else {
blame = await this._blame;
}
if (!blame || !blame.lines.length) {
this.whitespaceController && await this.whitespaceController.restore();
return undefined;
}
return blame;
}
}

View File

@@ -0,0 +1,69 @@
'use strict';
import { DecorationOptions, ExtensionContext, Position, Range, TextEditor, TextEditorDecorationType } from 'vscode';
import { AnnotationProviderBase } from './annotationProvider';
import { GitService, GitUri } from '../gitService';
import { WhitespaceController } from './whitespaceController';
export class DiffAnnotationProvider extends AnnotationProviderBase {
constructor(context: ExtensionContext, editor: TextEditor, decoration: TextEditorDecorationType, highlightDecoration: TextEditorDecorationType | undefined, whitespaceController: WhitespaceController | undefined, private git: GitService, private uri: GitUri) {
super(context, editor, decoration, highlightDecoration, whitespaceController);
}
async provideAnnotation(shaOrLine?: string | number): Promise<boolean> {
// let sha1: string | undefined = undefined;
// let sha2: string | undefined = undefined;
// if (shaOrLine === undefined) {
// const commit = await this.git.getLogCommit(this.uri.repoPath, this.uri.fsPath, { previous: true });
// if (commit === undefined) return false;
// sha1 = commit.previousSha;
// }
// else if (typeof shaOrLine === 'string') {
// sha1 = shaOrLine;
// }
// else {
// const blame = await this.git.getBlameForLine(this.uri, shaOrLine);
// if (blame === undefined) return false;
// sha1 = blame.commit.previousSha;
// sha2 = blame.commit.sha;
// }
// if (sha1 === undefined) return false;
const commit = await this.git.getLogCommit(this.uri.repoPath, this.uri.fsPath, { previous: true });
if (commit === undefined) return false;
const diff = await this.git.getDiffForFile(this.uri, commit.previousSha);
if (diff === undefined) return false;
const decorators: DecorationOptions[] = [];
for (const chunk of diff.chunks) {
let count = chunk.currentStart - 2;
for (const change of chunk.current) {
if (change === undefined) continue;
count++;
if (change.state === 'unchanged') continue;
decorators.push({
range: new Range(new Position(count, 0), new Position(count, 0))
} as DecorationOptions);
}
}
this.editor.setDecorations(this.decoration, decorators);
return true;
}
async selection(shaOrLine?: string | number): Promise<void> {
}
async validate(): Promise<boolean> {
return true;
}
}

View File

@@ -0,0 +1,76 @@
'use strict';
import { Strings } from '../system';
import { DecorationOptions, Range } from 'vscode';
import { BlameAnnotationProviderBase } from './blameAnnotationProvider';
import { Annotations, endOfLineIndex } from './annotations';
import { FileAnnotationType } from '../configuration';
import { ICommitFormatOptions } from '../gitService';
import * as moment from 'moment';
export class GutterBlameAnnotationProvider extends BlameAnnotationProviderBase {
async provideAnnotation(shaOrLine?: string | number, type?: FileAnnotationType): Promise<boolean> {
this.annotationType = FileAnnotationType.Gutter;
const blame = await this.getBlame(true);
if (blame === undefined) return false;
const cfg = this._config.annotations.file.gutter;
// Precalculate the formatting options so we don't need to do it on each iteration
const tokenOptions = Strings.getTokensFromTemplate(cfg.format)
.reduce((map, token) => {
map[token.key] = token.options;
return map;
}, {} as { [token: string]: ICommitFormatOptions });
const options: ICommitFormatOptions = {
dateFormat: cfg.dateFormat,
tokenOptions: tokenOptions
};
const now = moment();
const offset = this.uri.offset;
let previousLine: string | undefined = undefined;
const renderOptions = Annotations.gutterRenderOptions(this._config.theme, cfg.heatmap);
const decorations: DecorationOptions[] = [];
for (const l of blame.lines) {
const commit = blame.commits.get(l.sha);
if (commit === undefined) continue;
const line = l.line + offset;
const gutter = Annotations.gutter(commit, cfg.format, options, renderOptions, cfg.compact && previousLine === l.sha);
if (cfg.compact) {
const isEmptyOrWhitespace = this.document.lineAt(line).isEmptyOrWhitespace;
previousLine = isEmptyOrWhitespace ? undefined : l.sha;
}
if (cfg.heatmap.enabled) {
Annotations.applyHeatmap(gutter, commit.date, now);
}
const firstNonWhitespace = this.editor.document.lineAt(line).firstNonWhitespaceCharacterIndex;
gutter.range = this.editor.document.validateRange(new Range(line, 0, line, firstNonWhitespace));
decorations.push(gutter);
if (cfg.hover.details) {
const details = Annotations.detailsHover(commit);
details.range = cfg.hover.wholeLine
? this.editor.document.validateRange(new Range(line, 0, line, endOfLineIndex))
: gutter.range;
decorations.push(details);
}
}
if (decorations.length) {
this.editor.setDecorations(this.decoration, decorations);
}
this.selection(shaOrLine, blame);
return true;
}
}

View File

@@ -0,0 +1,49 @@
'use strict';
import { DecorationOptions, Range } from 'vscode';
import { BlameAnnotationProviderBase } from './blameAnnotationProvider';
import { Annotations, endOfLineIndex } from './annotations';
import { FileAnnotationType } from '../configuration';
import * as moment from 'moment';
export class HoverBlameAnnotationProvider extends BlameAnnotationProviderBase {
async provideAnnotation(shaOrLine?: string | number): Promise<boolean> {
this.annotationType = FileAnnotationType.Hover;
const blame = await this.getBlame(this._config.annotations.file.hover.heatmap.enabled);
if (blame === undefined) return false;
const cfg = this._config.annotations.file.hover;
const now = moment();
const offset = this.uri.offset;
const renderOptions = Annotations.hoverRenderOptions(this._config.theme, cfg.heatmap);
const decorations: DecorationOptions[] = [];
for (const l of blame.lines) {
const commit = blame.commits.get(l.sha);
if (commit === undefined) continue;
const line = l.line + offset;
const hover = Annotations.hover(commit, renderOptions, cfg.heatmap.enabled);
const endIndex = cfg.wholeLine ? endOfLineIndex : this.editor.document.lineAt(line).firstNonWhitespaceCharacterIndex;
hover.range = this.editor.document.validateRange(new Range(line, 0, line, endIndex));
if (cfg.heatmap.enabled) {
Annotations.applyHeatmap(hover, commit.date, now);
}
decorations.push(hover);
}
if (decorations.length) {
this.editor.setDecorations(this.decoration, decorations);
}
this.selection(shaOrLine, blame);
return true;
}
}

View File

@@ -0,0 +1,150 @@
'use strict';
import { Disposable, workspace } from 'vscode';
import { Logger } from '../logger';
interface ConfigurationInspection {
key: string;
defaultValue?: string;
globalValue?: string;
workspaceValue?: string;
}
enum SettingLocation {
workspace,
global,
default
}
class RenderWhitespaceConfiguration {
constructor(public inspection: ConfigurationInspection) { }
get location(): SettingLocation {
if (this.inspection.workspaceValue) return SettingLocation.workspace;
if (this.inspection.globalValue) return SettingLocation.global;
return SettingLocation.default;
}
get overrideRequired() {
return this.value != null && this.value !== 'none';
}
get value(): string | undefined {
return this.inspection.workspaceValue || this.inspection.globalValue || this.inspection.defaultValue;
}
update(replacement: ConfigurationInspection): boolean {
let override = false;
switch (this.location) {
case SettingLocation.workspace:
this.inspection.defaultValue = replacement.defaultValue;
this.inspection.globalValue = replacement.globalValue;
if (replacement.workspaceValue !== 'none') {
this.inspection.workspaceValue = replacement.workspaceValue;
override = true;
}
break;
case SettingLocation.global:
this.inspection.defaultValue = replacement.defaultValue;
this.inspection.workspaceValue = replacement.workspaceValue;
if (replacement.globalValue !== 'none') {
this.inspection.globalValue = replacement.globalValue;
override = true;
}
break;
case SettingLocation.default:
this.inspection.globalValue = replacement.globalValue;
this.inspection.workspaceValue = replacement.workspaceValue;
if (replacement.defaultValue !== 'none') {
this.inspection.defaultValue = replacement.defaultValue;
override = true;
}
break;
}
return override;
}
}
export class WhitespaceController extends Disposable {
private _configuration: RenderWhitespaceConfiguration;
private _count: number = 0;
private _disposable: Disposable;
private _disposed: boolean = false;
constructor() {
super(() => this.dispose());
const subscriptions: Disposable[] = [];
subscriptions.push(workspace.onDidChangeConfiguration(this._onConfigurationChanged, this));
this._disposable = Disposable.from(...subscriptions);
this._onConfigurationChanged();
}
async dispose() {
this._disposed = true;
if (this._count !== 0) {
await this._restoreWhitespace();
this._count = 0;
}
}
private _onConfigurationChanged() {
if (this._disposed) return;
const inspection = workspace.getConfiguration('editor').inspect<string>('renderWhitespace')!;
if (!this._count) {
this._configuration = new RenderWhitespaceConfiguration(inspection);
return;
}
if (this._configuration.update(inspection)) {
// Since we were currently overriding whitespace, re-override
setTimeout(() => this._overrideWhitespace(), 1);
}
}
async override() {
if (this._disposed) return;
Logger.log(`Request whitespace override; count=${this._count}`);
this._count++;
if (this._count === 1 && this._configuration.overrideRequired) {
// Override whitespace (turn off)
await this._overrideWhitespace();
}
}
private async _overrideWhitespace() {
Logger.log(`Override whitespace`);
const cfg = workspace.getConfiguration('editor');
return cfg.update('renderWhitespace', 'none', this._configuration.location === SettingLocation.global);
}
async restore() {
if (this._disposed || this._count === 0) return;
Logger.log(`Request whitespace restore; count=${this._count}`);
this._count--;
if (this._count === 0 && this._configuration.overrideRequired) {
// restore whitespace
await this._restoreWhitespace();
}
}
private async _restoreWhitespace() {
Logger.log(`Restore whitespace`);
const cfg = workspace.getConfiguration('editor');
return cfg.update('renderWhitespace',
this._configuration.location === SettingLocation.default
? undefined
: this._configuration.value,
this._configuration.location === SettingLocation.global);
}
}

View File

@@ -1,136 +0,0 @@
'use strict';
import {CancellationToken, CodeLens, CodeLensProvider, commands, Location, Position, Range, SymbolInformation, SymbolKind, TextDocument, Uri} from 'vscode';
import {Commands, VsCodeCommands} from './constants';
import {IGitBlameLine, gitBlame} from './git';
import {toGitBlameUri} from './gitBlameUri';
import * as moment from 'moment';
export class GitBlameCodeLens extends CodeLens {
constructor(private blame: Promise<IGitBlameLine[]>, public repoPath: string, public fileName: string, private blameRange: Range, range: Range) {
super(range);
}
getBlameLines(): Promise<IGitBlameLine[]> {
return this.blame.then(allLines => allLines.slice(this.blameRange.start.line, this.blameRange.end.line + 1));
}
static toUri(lens: GitBlameCodeLens, index: number, line: IGitBlameLine, lines: IGitBlameLine[], commits: string[]): Uri {
return toGitBlameUri(Object.assign({ repoPath: lens.repoPath, index: index, range: lens.blameRange, lines: lines, commits: commits }, line));
}
}
export class GitHistoryCodeLens extends CodeLens {
constructor(public repoPath: string, public fileName: string, range: Range) {
super(range);
}
// static toUri(lens: GitHistoryCodeLens, index: number): Uri {
// return toGitBlameUri(Object.assign({ repoPath: lens.repoPath, index: index, range: lens.blameRange, lines: lines }, line));
// }
}
export default class GitCodeLensProvider implements CodeLensProvider {
constructor(public repoPath: string) { }
provideCodeLenses(document: TextDocument, token: CancellationToken): CodeLens[] | Thenable<CodeLens[]> {
// TODO: Should I wait here?
const blame = gitBlame(document.fileName);
return (commands.executeCommand(VsCodeCommands.ExecuteDocumentSymbolProvider, document.uri) as Promise<SymbolInformation[]>).then(symbols => {
let lenses: CodeLens[] = [];
symbols.forEach(sym => this._provideCodeLens(document, sym, blame, lenses));
// Check if we have a lens for the whole document -- if not add one
if (!lenses.find(l => l.range.start.line === 0 && l.range.end.line === 0)) {
const docRange = document.validateRange(new Range(0, 1000000, 1000000, 1000000));
lenses.push(new GitBlameCodeLens(blame, this.repoPath, document.fileName, docRange, new Range(0, 0, 0, docRange.start.character)));
lenses.push(new GitHistoryCodeLens(this.repoPath, document.fileName, docRange.with(new Position(docRange.start.line, docRange.start.character + 1))));
}
return lenses;
});
}
private _provideCodeLens(document: TextDocument, symbol: SymbolInformation, blame: Promise<IGitBlameLine[]>, lenses: CodeLens[]): void {
switch (symbol.kind) {
case SymbolKind.Package:
case SymbolKind.Module:
case SymbolKind.Class:
case SymbolKind.Interface:
case SymbolKind.Constructor:
case SymbolKind.Method:
case SymbolKind.Property:
case SymbolKind.Field:
case SymbolKind.Function:
case SymbolKind.Enum:
break;
default:
return;
}
const line = document.lineAt(symbol.location.range.start);
lenses.push(new GitBlameCodeLens(blame, this.repoPath, document.fileName, symbol.location.range, line.range.with(new Position(line.range.start.line, line.firstNonWhitespaceCharacterIndex))));
lenses.push(new GitHistoryCodeLens(this.repoPath, document.fileName, line.range.with(new Position(line.range.start.line, line.firstNonWhitespaceCharacterIndex + 1))));
}
resolveCodeLens(lens: CodeLens, token: CancellationToken): Thenable<CodeLens> {
if (lens instanceof GitBlameCodeLens) return this._resolveGitBlameCodeLens(lens, token);
if (lens instanceof GitHistoryCodeLens) return this._resolveGitHistoryCodeLens(lens, token);
}
_resolveGitBlameCodeLens(lens: GitBlameCodeLens, token: CancellationToken): Thenable<CodeLens> {
return new Promise<CodeLens>((resolve, reject) => {
lens.getBlameLines().then(lines => {
if (!lines.length) {
console.error('No blame lines found', lens);
reject(null);
return;
}
let recentLine = lines[0];
let locations: Location[] = [];
if (lines.length > 1) {
let sorted = lines.sort((a, b) => b.date.getTime() - a.date.getTime());
recentLine = sorted[0];
// console.log(lens.fileName, 'Blame lines:', sorted);
let map: Map<string, IGitBlameLine[]> = new Map();
sorted.forEach(l => {
let item = map.get(l.sha);
if (item) {
item.push(l);
} else {
map.set(l.sha, [l]);
}
});
const commits = Array.from(map.keys());
Array.from(map.values()).forEach((lines, i) => {
const uri = GitBlameCodeLens.toUri(lens, i + 1, lines[0], lines, commits);
lines.forEach(l => locations.push(new Location(uri, new Position(l.originalLine, 0))));
});
} else {
locations = [new Location(GitBlameCodeLens.toUri(lens, 1, recentLine, lines, [recentLine.sha]), lens.range.start)];
}
lens.command = {
title: `${recentLine.author}, ${moment(recentLine.date).fromNow()}`,
command: Commands.ShowBlameHistory,
arguments: [Uri.file(lens.fileName), lens.range.start, locations]
};
resolve(lens);
});
});//.catch(ex => Promise.reject(ex)); // TODO: Figure out a better way to stop the codelens from appearing
}
_resolveGitHistoryCodeLens(lens: GitHistoryCodeLens, token: CancellationToken): Thenable<CodeLens> {
// TODO: Play with this more -- get this to open the correct diff to the right place
lens.command = {
title: `View History`,
command: 'git.viewFileHistory', // viewLineHistory
arguments: [Uri.file(lens.fileName)]
};
return Promise.resolve(lens);
}
}

40
src/commands.ts Normal file
View File

@@ -0,0 +1,40 @@
'use strict';
export * from './commands/common';
export * from './commands/keyboard';
export * from './commands/closeUnchangedFiles';
export * from './commands/copyMessageToClipboard';
export * from './commands/copyShaToClipboard';
export * from './commands/diffDirectory';
export * from './commands/diffLineWithPrevious';
export * from './commands/diffLineWithWorking';
export * from './commands/diffWithBranch';
export * from './commands/diffWithNext';
export * from './commands/diffWithPrevious';
export * from './commands/diffWithWorking';
export * from './commands/openChangedFiles';
export * from './commands/openBranchInRemote';
export * from './commands/openCommitInRemote';
export * from './commands/openFileInRemote';
export * from './commands/openInRemote';
export * from './commands/openRepoInRemote';
export * from './commands/showBlameHistory';
export * from './commands/showFileBlame';
export * from './commands/showFileHistory';
export * from './commands/showLastQuickPick';
export * from './commands/showLineBlame';
export * from './commands/showQuickCommitDetails';
export * from './commands/showQuickCommitFileDetails';
export * from './commands/showCommitSearch';
export * from './commands/showQuickFileHistory';
export * from './commands/showQuickBranchHistory';
export * from './commands/showQuickCurrentBranchHistory';
export * from './commands/showQuickRepoStatus';
export * from './commands/showQuickStashList';
export * from './commands/stashApply';
export * from './commands/stashDelete';
export * from './commands/stashSave';
export * from './commands/toggleCodeLens';
export * from './commands/toggleFileBlame';
export * from './commands/toggleLineBlame';

View File

@@ -0,0 +1,71 @@
'use strict';
import { TextEditor, Uri, window } from 'vscode';
import { ActiveEditorTracker } from '../activeEditorTracker';
import { ActiveEditorCommand, Commands, getCommandUri } from './common';
import { TextEditorComparer, UriComparer } from '../comparers';
import { GitService } from '../gitService';
import { Logger } from '../logger';
export interface CloseUnchangedFilesCommandArgs {
uris?: Uri[];
}
export class CloseUnchangedFilesCommand extends ActiveEditorCommand {
constructor(private git: GitService) {
super(Commands.CloseUnchangedFiles);
}
async execute(editor: TextEditor, uri?: Uri, args: CloseUnchangedFilesCommandArgs = {}) {
uri = getCommandUri(uri, editor);
try {
if (args.uris === undefined) {
const repoPath = await this.git.getRepoPathFromUri(uri);
if (!repoPath) return window.showWarningMessage(`Unable to close unchanged files`);
const status = await this.git.getStatusForRepo(repoPath);
if (status === undefined) return window.showWarningMessage(`Unable to close unchanged files`);
args.uris = status.files.map(_ => _.Uri);
}
const editorTracker = new ActiveEditorTracker();
let active = window.activeTextEditor;
let editor = active;
do {
if (editor !== undefined) {
if ((editor.document !== undefined && editor.document.isDirty) ||
args.uris.some(_ => UriComparer.equals(_, editor!.document && editor!.document.uri))) {
// If we didn't start with a valid editor, set one once we find it
if (active === undefined) {
active = editor;
}
editor = await editorTracker.awaitNext(500);
}
else {
if (active === editor) {
active = undefined;
}
editor = await editorTracker.awaitClose(500);
}
}
else {
if (active === editor) {
active = undefined;
}
editor = await editorTracker.awaitClose(500);
}
} while ((active === undefined && editor === undefined) || !TextEditorComparer.equals(active, editor, { useId: true, usePosition: true }));
editorTracker.dispose();
return undefined;
}
catch (ex) {
Logger.error(ex, 'CloseUnchangedFilesCommand');
return window.showErrorMessage(`Unable to close unchanged files. See output channel for more details`);
}
}
}

168
src/commands/common.ts Normal file
View File

@@ -0,0 +1,168 @@
'use strict';
import { commands, Disposable, TextDocumentShowOptions, TextEditor, TextEditorEdit, Uri, window, workspace } from 'vscode';
import { BuiltInCommands } from '../constants';
import { Logger } from '../logger';
import { Telemetry } from '../telemetry';
export type Commands = 'gitlens.closeUnchangedFiles' | 'gitlens.copyMessageToClipboard' | 'gitlens.copyShaToClipboard' |
'gitlens.diffDirectory' | 'gitlens.diffWithBranch' | 'gitlens.diffWithNext' | 'gitlens.diffWithPrevious' | 'gitlens.diffLineWithPrevious' | 'gitlens.diffWithWorking' | 'gitlens.diffLineWithWorking' |
'gitlens.openChangedFiles' | 'gitlens.openBranchInRemote' | 'gitlens.openCommitInRemote' | 'gitlens.openFileInRemote' | 'gitlens.openInRemote' | 'gitlens.openRepoInRemote' |
'gitlens.showBlameHistory' | 'gitlens.showCommitSearch' | 'gitlens.showFileBlame' | 'gitlens.showFileHistory' |
'gitlens.showLastQuickPick' | 'gitlens.showLineBlame' | 'gitlens.showQuickBranchHistory' |
'gitlens.showQuickCommitDetails' | 'gitlens.showQuickCommitFileDetails' |
'gitlens.showQuickFileHistory' | 'gitlens.showQuickRepoHistory' |
'gitlens.showQuickRepoStatus' | 'gitlens.showQuickStashList' |
'gitlens.stashApply' | 'gitlens.stashDelete' | 'gitlens.stashSave' |
'gitlens.toggleCodeLens' | 'gitlens.toggleFileBlame' | 'gitlens.toggleLineBlame';
export const Commands = {
CloseUnchangedFiles: 'gitlens.closeUnchangedFiles' as Commands,
CopyMessageToClipboard: 'gitlens.copyMessageToClipboard' as Commands,
CopyShaToClipboard: 'gitlens.copyShaToClipboard' as Commands,
DiffDirectory: 'gitlens.diffDirectory' as Commands,
DiffWithBranch: 'gitlens.diffWithBranch' as Commands,
DiffWithNext: 'gitlens.diffWithNext' as Commands,
DiffWithPrevious: 'gitlens.diffWithPrevious' as Commands,
DiffLineWithPrevious: 'gitlens.diffLineWithPrevious' as Commands,
DiffWithWorking: 'gitlens.diffWithWorking' as Commands,
DiffLineWithWorking: 'gitlens.diffLineWithWorking' as Commands,
OpenChangedFiles: 'gitlens.openChangedFiles' as Commands,
OpenBranchInRemote: 'gitlens.openBranchInRemote' as Commands,
OpenCommitInRemote: 'gitlens.openCommitInRemote' as Commands,
OpenFileInRemote: 'gitlens.openFileInRemote' as Commands,
OpenInRemote: 'gitlens.openInRemote' as Commands,
OpenRepoInRemote: 'gitlens.openRepoInRemote' as Commands,
ShowFileBlame: 'gitlens.showFileBlame' as Commands,
ShowLineBlame: 'gitlens.showLineBlame' as Commands,
ShowBlameHistory: 'gitlens.showBlameHistory' as Commands,
ShowCommitSearch: 'gitlens.showCommitSearch' as Commands,
ShowFileHistory: 'gitlens.showFileHistory' as Commands,
ShowLastQuickPick: 'gitlens.showLastQuickPick' as Commands,
ShowQuickCommitDetails: 'gitlens.showQuickCommitDetails' as Commands,
ShowQuickCommitFileDetails: 'gitlens.showQuickCommitFileDetails' as Commands,
ShowQuickFileHistory: 'gitlens.showQuickFileHistory' as Commands,
ShowQuickBranchHistory: 'gitlens.showQuickBranchHistory' as Commands,
ShowQuickCurrentBranchHistory: 'gitlens.showQuickRepoHistory' as Commands,
ShowQuickRepoStatus: 'gitlens.showQuickRepoStatus' as Commands,
ShowQuickStashList: 'gitlens.showQuickStashList' as Commands,
StashApply: 'gitlens.stashApply' as Commands,
StashDelete: 'gitlens.stashDelete' as Commands,
StashSave: 'gitlens.stashSave' as Commands,
ToggleFileBlame: 'gitlens.toggleFileBlame' as Commands,
ToggleLineBlame: 'gitlens.toggleLineBlame' as Commands,
ToggleCodeLens: 'gitlens.toggleCodeLens' as Commands
};
export function getCommandUri(uri?: Uri, editor?: TextEditor): Uri | undefined {
if (uri instanceof Uri) return uri;
if (editor === undefined || editor.document === undefined) return undefined;
return editor.document.uri;
}
export type CommandContext = 'gitlens:canToggleCodeLens' | 'gitlens:enabled' | 'gitlens:hasRemotes' | 'gitlens:isBlameable' | 'gitlens:isRepository' | 'gitlens:isTracked' | 'gitlens:key';
export const CommandContext = {
CanToggleCodeLens: 'gitlens:canToggleCodeLens' as CommandContext,
Enabled: 'gitlens:enabled' as CommandContext,
HasRemotes: 'gitlens:hasRemotes' as CommandContext,
IsBlameable: 'gitlens:isBlameable' as CommandContext,
IsRepository: 'gitlens:isRepository' as CommandContext,
IsTracked: 'gitlens:isTracked' as CommandContext,
Key: 'gitlens:key' as CommandContext
};
export function setCommandContext(key: CommandContext | string, value: any) {
return commands.executeCommand(BuiltInCommands.SetContext, key, value);
}
export abstract class Command extends Disposable {
private _disposable: Disposable;
constructor(protected command: Commands) {
super(() => this.dispose());
this._disposable = commands.registerCommand(command, this._execute, this);
}
dispose() {
this._disposable && this._disposable.dispose();
}
protected _execute(...args: any[]): any {
Telemetry.trackEvent(this.command);
return this.execute(...args);
}
abstract execute(...args: any[]): any;
}
export abstract class EditorCommand extends Disposable {
private _disposable: Disposable;
constructor(public readonly command: Commands) {
super(() => this.dispose());
this._disposable = commands.registerTextEditorCommand(command, this._execute, this);
}
dispose() {
this._disposable && this._disposable.dispose();
}
private _execute(editor: TextEditor, edit: TextEditorEdit, ...args: any[]): any {
Telemetry.trackEvent(this.command);
return this.execute(editor, edit, ...args);
}
abstract execute(editor: TextEditor, edit: TextEditorEdit, ...args: any[]): any;
}
export abstract class ActiveEditorCommand extends Command {
constructor(public readonly command: Commands) {
super(command);
}
protected _execute(...args: any[]): any {
return super._execute(window.activeTextEditor, ...args);
}
abstract execute(editor: TextEditor, ...args: any[]): any;
}
let lastCommand: { command: string, args: any[] } | undefined = undefined;
export function getLastCommand() {
return lastCommand;
}
export abstract class ActiveEditorCachedCommand extends ActiveEditorCommand {
constructor(public readonly command: Commands) {
super(command);
}
protected _execute(...args: any[]): any {
lastCommand = {
command: this.command,
args: args
};
return super._execute(...args);
}
abstract execute(editor: TextEditor, ...args: any[]): any;
}
export async function openEditor(uri: Uri, options?: TextDocumentShowOptions): Promise<TextEditor | undefined> {
try {
const defaults: TextDocumentShowOptions = {
preserveFocus: false,
preview: true,
viewColumn: (window.activeTextEditor && window.activeTextEditor.viewColumn) || 1
};
const document = await workspace.openTextDocument(uri);
return window.showTextDocument(document, { ...defaults, ...(options || {}) });
}
catch (ex) {
Logger.error(ex, 'openEditor');
return undefined;
}
}

View File

@@ -0,0 +1,78 @@
'use strict';
import { Iterables } from '../system';
import { TextEditor, Uri, window } from 'vscode';
import { ActiveEditorCommand, Commands, getCommandUri } from './common';
import { GitService, GitUri } from '../gitService';
import { Logger } from '../logger';
import { copy } from 'copy-paste';
export interface CopyMessageToClipboardCommandArgs {
message?: string;
sha?: string;
}
export class CopyMessageToClipboardCommand extends ActiveEditorCommand {
constructor(private git: GitService) {
super(Commands.CopyMessageToClipboard);
}
async execute(editor: TextEditor, uri?: Uri, args: CopyMessageToClipboardCommandArgs = {}): Promise<any> {
uri = getCommandUri(uri, editor);
try {
// If we don't have an editor then get the message of the last commit to the branch
if (uri === undefined) {
if (!this.git.repoPath) return undefined;
const log = await this.git.getLogForRepo(this.git.repoPath, undefined, 1);
if (!log) return undefined;
args.message = Iterables.first(log.commits.values()).message;
copy(args.message);
return undefined;
}
const gitUri = await GitUri.fromUri(uri, this.git);
if (args.message === undefined) {
if (args.sha === undefined) {
if (editor !== undefined && editor.document !== undefined && editor.document.isDirty) return undefined;
const line = (editor && editor.selection.active.line) || gitUri.offset;
const blameline = line - gitUri.offset;
if (blameline < 0) return undefined;
try {
const blame = await this.git.getBlameForLine(gitUri, blameline);
if (!blame) return undefined;
if (blame.commit.isUncommitted) return undefined;
args.sha = blame.commit.sha;
if (!gitUri.repoPath) {
gitUri.repoPath = blame.commit.repoPath;
}
}
catch (ex) {
Logger.error(ex, 'CopyMessageToClipboardCommand', `getBlameForLine(${blameline})`);
return window.showErrorMessage(`Unable to copy message. See output channel for more details`);
}
}
// Get the full commit message -- since blame only returns the summary
const commit = await this.git.getLogCommit(gitUri.repoPath, gitUri.fsPath, args.sha);
if (!commit) return undefined;
args.message = commit.message;
}
copy(args.message);
return undefined;
}
catch (ex) {
Logger.error(ex, 'CopyMessageToClipboardCommand');
return window.showErrorMessage(`Unable to copy message. See output channel for more details`);
}
}
}

View File

@@ -0,0 +1,64 @@
'use strict';
import { Iterables } from '../system';
import { TextEditor, Uri, window } from 'vscode';
import { ActiveEditorCommand, Commands, getCommandUri } from './common';
import { GitService, GitUri } from '../gitService';
import { Logger } from '../logger';
import { copy } from 'copy-paste';
export interface CopyShaToClipboardCommandArgs {
sha?: string;
}
export class CopyShaToClipboardCommand extends ActiveEditorCommand {
constructor(private git: GitService) {
super(Commands.CopyShaToClipboard);
}
async execute(editor: TextEditor, uri?: Uri, args: CopyShaToClipboardCommandArgs = {}): Promise<any> {
uri = getCommandUri(uri, editor);
try {
// If we don't have an editor then get the sha of the last commit to the branch
if (uri === undefined) {
if (!this.git.repoPath) return undefined;
const log = await this.git.getLogForRepo(this.git.repoPath, undefined, 1);
if (!log) return undefined;
args.sha = Iterables.first(log.commits.values()).sha;
copy(args.sha);
return undefined;
}
const gitUri = await GitUri.fromUri(uri, this.git);
if (args.sha === undefined) {
if (editor !== undefined && editor.document !== undefined && editor.document.isDirty) return undefined;
const line = (editor && editor.selection.active.line) || gitUri.offset;
const blameline = line - gitUri.offset;
if (blameline < 0) return undefined;
try {
const blame = await this.git.getBlameForLine(gitUri, blameline);
if (!blame) return undefined;
args.sha = blame.commit.sha;
}
catch (ex) {
Logger.error(ex, 'CopyShaToClipboardCommand', `getBlameForLine(${blameline})`);
return window.showErrorMessage(`Unable to copy commit id. See output channel for more details`);
}
}
copy(args.sha);
return undefined;
}
catch (ex) {
Logger.error(ex, 'CopyShaToClipboardCommand');
return window.showErrorMessage(`Unable to copy commit id. See output channel for more details`);
}
}
}

View File

@@ -0,0 +1,58 @@
'use strict';
import { Iterables } from '../system';
import { commands, TextEditor, Uri, window } from 'vscode';
import { ActiveEditorCommand, Commands, getCommandUri } from './common';
import { BuiltInCommands } from '../constants';
import { GitService } from '../gitService';
import { Logger } from '../logger';
import { BranchesQuickPick, CommandQuickPickItem } from '../quickPicks';
export interface DiffDirectoryCommandCommandArgs {
shaOrBranch1?: string;
shaOrBranch2?: string;
}
export class DiffDirectoryCommand extends ActiveEditorCommand {
constructor(private git: GitService) {
super(Commands.DiffDirectory);
}
async execute(editor: TextEditor, uri?: Uri, args: DiffDirectoryCommandCommandArgs = {}): Promise<any> {
const diffTool = await this.git.getConfig('diff.tool');
if (!diffTool) {
const result = await window.showWarningMessage(`Unable to open directory compare because there is no Git diff tool configured`, 'View Git Docs');
if (!result) return undefined;
return commands.executeCommand(BuiltInCommands.Open, Uri.parse('https://git-scm.com/docs/git-config#git-config-difftool'));
}
uri = getCommandUri(uri, editor);
try {
const repoPath = await this.git.getRepoPathFromUri(uri);
if (!repoPath) return window.showWarningMessage(`Unable to open directory compare`);
if (!args.shaOrBranch1) {
const branches = await this.git.getBranches(repoPath);
const current = Iterables.find(branches, _ => _.current);
if (current == null) return window.showWarningMessage(`Unable to open directory compare`);
const pick = await BranchesQuickPick.show(branches, `Compare ${current.name} to \u2026`);
if (pick === undefined) return undefined;
if (pick instanceof CommandQuickPickItem) return pick.execute();
args.shaOrBranch1 = pick.branch.name;
if (args.shaOrBranch1 === undefined) return undefined;
}
this.git.openDirectoryDiff(repoPath, args.shaOrBranch1, args.shaOrBranch2);
return undefined;
}
catch (ex) {
Logger.error(ex, 'DiffDirectoryCommand');
return window.showErrorMessage(`Unable to open directory compare. See output channel for more details`);
}
}
}

View File

@@ -0,0 +1,89 @@
'use strict';
import { commands, TextDocumentShowOptions, TextEditor, Uri, window } from 'vscode';
import { ActiveEditorCommand, Commands, getCommandUri } from './common';
import { BuiltInCommands } from '../constants';
import { DiffWithPreviousCommandArgs } from './diffWithPrevious';
import { DiffWithWorkingCommandArgs } from './diffWithWorking';
import { GitCommit, GitService, GitUri } from '../gitService';
import { Logger } from '../logger';
import * as path from 'path';
export interface DiffLineWithPreviousCommandArgs {
commit?: GitCommit;
line?: number;
showOptions?: TextDocumentShowOptions;
}
export class DiffLineWithPreviousCommand extends ActiveEditorCommand {
constructor(private git: GitService) {
super(Commands.DiffLineWithPrevious);
}
async execute(editor: TextEditor, uri?: Uri, args: DiffLineWithPreviousCommandArgs = {}): Promise<any> {
uri = getCommandUri(uri, editor);
if (uri === undefined) return undefined;
const gitUri = await GitUri.fromUri(uri, this.git);
args.line = args.line || (editor === undefined ? gitUri.offset : editor.selection.active.line);
if (args.commit === undefined || GitService.isUncommitted(args.commit.sha)) {
if (editor !== undefined && editor.document !== undefined && editor.document.isDirty) return undefined;
const blameline = args.line - gitUri.offset;
if (blameline < 0) return undefined;
try {
const blame = await this.git.getBlameForLine(gitUri, blameline);
if (blame === undefined) return window.showWarningMessage(`Unable to open compare. File is probably not under source control`);
args.commit = blame.commit;
// If we don't have a sha or the current commit matches the blame, show the previous
if (gitUri.sha === undefined || gitUri.sha === args.commit.sha) {
return commands.executeCommand(Commands.DiffWithPrevious, new GitUri(uri, args.commit), {
line: args.line,
showOptions: args.showOptions
} as DiffWithPreviousCommandArgs);
}
// If the line is uncommitted, find the previous commit and treat it as a DiffWithWorking
if (args.commit.isUncommitted) {
uri = args.commit.uri;
args.commit = new GitCommit(args.commit.type, args.commit.repoPath, args.commit.previousSha!, args.commit.previousFileName!, args.commit.author, args.commit.date, args.commit.message);
args.line = (blame.line.line + 1) + gitUri.offset;
return commands.executeCommand(Commands.DiffWithWorking, uri, {
commit: args.commit,
line: args.line,
showOptions: args.showOptions
} as DiffWithWorkingCommandArgs);
}
}
catch (ex) {
Logger.error(ex, 'DiffWithPreviousLineCommand', `getBlameForLine(${blameline})`);
return window.showErrorMessage(`Unable to open compare. See output channel for more details`);
}
}
try {
const [rhs, lhs] = await Promise.all([
this.git.getVersionedFile(gitUri.repoPath, gitUri.fsPath, gitUri.sha!),
this.git.getVersionedFile(args.commit.repoPath, args.commit.uri.fsPath, args.commit.sha)
]);
await commands.executeCommand(BuiltInCommands.Diff,
Uri.file(lhs),
Uri.file(rhs),
`${path.basename(args.commit.uri.fsPath)} (${args.commit.shortSha}) \u2194 ${path.basename(gitUri.fsPath)} (${gitUri.shortSha})`,
args.showOptions);
// TODO: Figure out how to focus the left pane
return await commands.executeCommand(BuiltInCommands.RevealLine, { lineNumber: args.line, at: 'center' });
}
catch (ex) {
Logger.error(ex, 'DiffWithPreviousLineCommand', 'getVersionedFile');
return window.showErrorMessage(`Unable to open compare. See output channel for more details`);
}
}
}

View File

@@ -0,0 +1,52 @@
'use strict';
import { commands, TextDocumentShowOptions, TextEditor, Uri, window } from 'vscode';
import { ActiveEditorCommand, Commands, getCommandUri } from './common';
import { DiffWithWorkingCommandArgs } from './diffWithWorking';
import { GitCommit, GitService, GitUri } from '../gitService';
import { Logger } from '../logger';
export interface DiffLineWithWorkingCommandArgs {
commit?: GitCommit;
line?: number;
showOptions?: TextDocumentShowOptions;
}
export class DiffLineWithWorkingCommand extends ActiveEditorCommand {
constructor(private git: GitService) {
super(Commands.DiffLineWithWorking);
}
async execute(editor: TextEditor, uri?: Uri, args: DiffLineWithWorkingCommandArgs = {}): Promise<any> {
uri = getCommandUri(uri, editor);
if (uri === undefined) return undefined;
const gitUri = await GitUri.fromUri(uri, this.git);
args.line = args.line || (editor === undefined ? gitUri.offset : editor.selection.active.line);
if (args.commit === undefined || GitService.isUncommitted(args.commit.sha)) {
if (editor !== undefined && editor.document !== undefined && editor.document.isDirty) return undefined;
const blameline = args.line - gitUri.offset;
if (blameline < 0) return undefined;
try {
const blame = await this.git.getBlameForLine(gitUri, blameline);
if (blame === undefined) return window.showWarningMessage(`Unable to open compare. File is probably not under source control`);
args.commit = blame.commit;
// If the line is uncommitted, find the previous commit
if (args.commit.isUncommitted) {
args.commit = new GitCommit(args.commit.type, args.commit.repoPath, args.commit.previousSha!, args.commit.previousFileName!, args.commit.author, args.commit.date, args.commit.message);
args.line = blame.line.line + 1 + gitUri.offset;
}
}
catch (ex) {
Logger.error(ex, 'DiffLineWithWorkingCommand', `getBlameForLine(${blameline})`);
return window.showErrorMessage(`Unable to open compare. See output channel for more details`);
}
}
return commands.executeCommand(Commands.DiffWithWorking, uri, args as DiffWithWorkingCommandArgs);
}
}

View File

@@ -0,0 +1,58 @@
'use strict';
import { commands, TextDocumentShowOptions, TextEditor, Uri, window } from 'vscode';
import { ActiveEditorCommand, Commands, getCommandUri } from './common';
import { BuiltInCommands } from '../constants';
import { GitService, GitUri } from '../gitService';
import { Logger } from '../logger';
import { BranchesQuickPick, CommandQuickPickItem } from '../quickPicks';
import * as path from 'path';
export interface DiffWithBranchCommandArgs {
line?: number;
showOptions?: TextDocumentShowOptions;
goBackCommand?: CommandQuickPickItem;
}
export class DiffWithBranchCommand extends ActiveEditorCommand {
constructor(private git: GitService) {
super(Commands.DiffWithBranch);
}
async execute(editor: TextEditor, uri?: Uri, args: DiffWithBranchCommandArgs = {}): Promise<any> {
uri = getCommandUri(uri, editor);
if (uri === undefined) return undefined;
args.line = args.line || (editor === undefined ? 0 : editor.selection.active.line);
const gitUri = await GitUri.fromUri(uri, this.git);
if (!gitUri.repoPath) return window.showWarningMessage(`Unable to open branch compare`);
const branches = await this.git.getBranches(gitUri.repoPath);
const pick = await BranchesQuickPick.show(branches, `Compare ${path.basename(gitUri.fsPath)} to \u2026`, args.goBackCommand);
if (pick === undefined) return undefined;
if (pick instanceof CommandQuickPickItem) return pick.execute();
const branch = pick.branch.name;
if (branch === undefined) return undefined;
try {
const compare = await this.git.getVersionedFile(gitUri.repoPath, gitUri.fsPath, branch);
await commands.executeCommand(BuiltInCommands.Diff,
Uri.file(compare),
gitUri.fileUri(),
`${path.basename(gitUri.fsPath)} (${branch}) \u2194 ${path.basename(gitUri.fsPath)}`,
args.showOptions);
// TODO: Figure out how to focus the left pane
return await commands.executeCommand(BuiltInCommands.RevealLine, { lineNumber: args.line, at: 'center' });
}
catch (ex) {
Logger.error(ex, 'DiffWithBranchCommand', 'getVersionedFile');
return window.showErrorMessage(`Unable to open branch compare. See output channel for more details`);
}
}
}

View File

@@ -0,0 +1,73 @@
'use strict';
import { Iterables } from '../system';
import { commands, Range, TextDocumentShowOptions, TextEditor, Uri, window } from 'vscode';
import { ActiveEditorCommand, Commands, getCommandUri } from './common';
import { BuiltInCommands } from '../constants';
import { GitLogCommit, GitService, GitUri } from '../gitService';
import { Logger } from '../logger';
import * as path from 'path';
export interface DiffWithNextCommandArgs {
commit?: GitLogCommit;
line?: number;
range?: Range;
showOptions?: TextDocumentShowOptions;
}
export class DiffWithNextCommand extends ActiveEditorCommand {
constructor(private git: GitService) {
super(Commands.DiffWithNext);
}
async execute(editor: TextEditor, uri?: Uri, args: DiffWithNextCommandArgs = {}): Promise<any> {
uri = getCommandUri(uri, editor);
if (uri === undefined) return undefined;
args.line = args.line || (editor === undefined ? 0 : editor.selection.active.line);
if (args.commit === undefined || !(args.commit instanceof GitLogCommit) || args.range !== undefined) {
const gitUri = await GitUri.fromUri(uri, this.git);
try {
// If the sha is missing or the file is uncommitted, treat it as a DiffWithWorking
if (gitUri.sha === undefined && await this.git.isFileUncommitted(gitUri)) {
return commands.executeCommand(Commands.DiffWithWorking, uri);
}
const sha = args.commit === undefined ? gitUri.sha : args.commit.sha;
const log = await this.git.getLogForFile(gitUri.repoPath, gitUri.fsPath, undefined, sha ? undefined : 2, args.range!);
if (log === undefined) return window.showWarningMessage(`Unable to open compare. File is probably not under source control`);
args.commit = (sha && log.commits.get(sha)) || Iterables.first(log.commits.values());
}
catch (ex) {
Logger.error(ex, 'DiffWithNextCommand', `getLogForFile(${gitUri.repoPath}, ${gitUri.fsPath})`);
return window.showErrorMessage(`Unable to open compare. See output channel for more details`);
}
}
if (args.commit.nextSha === undefined) return commands.executeCommand(Commands.DiffWithWorking, uri);
try {
const [rhs, lhs] = await Promise.all([
this.git.getVersionedFile(args.commit.repoPath, args.commit.nextUri.fsPath, args.commit.nextSha),
this.git.getVersionedFile(args.commit.repoPath, args.commit.uri.fsPath, args.commit.sha)
]);
await commands.executeCommand(BuiltInCommands.Diff,
Uri.file(lhs),
Uri.file(rhs),
`${path.basename(args.commit.uri.fsPath)} (${args.commit.shortSha}) \u2194 ${path.basename(args.commit.nextUri.fsPath)} (${args.commit.nextShortSha})`,
args.showOptions);
// TODO: Figure out how to focus the left pane
return await commands.executeCommand(BuiltInCommands.RevealLine, { lineNumber: args.line, at: 'center' });
}
catch (ex) {
Logger.error(ex, 'DiffWithNextCommand', 'getVersionedFile');
return window.showErrorMessage(`Unable to open compare. See output channel for more details`);
}
}
}

View File

@@ -0,0 +1,73 @@
'use strict';
import { Iterables } from '../system';
import { commands, Range, TextDocumentShowOptions, TextEditor, Uri, window } from 'vscode';
import { ActiveEditorCommand, Commands, getCommandUri } from './common';
import { BuiltInCommands } from '../constants';
import { DiffWithWorkingCommandArgs } from './diffWithWorking';
import { GitCommit, GitService, GitUri } from '../gitService';
import { Logger } from '../logger';
import * as moment from 'moment';
import * as path from 'path';
export interface DiffWithPreviousCommandArgs {
commit?: GitCommit;
line?: number;
range?: Range;
showOptions?: TextDocumentShowOptions;
}
export class DiffWithPreviousCommand extends ActiveEditorCommand {
constructor(private git: GitService) {
super(Commands.DiffWithPrevious);
}
async execute(editor: TextEditor, uri?: Uri, args: DiffWithPreviousCommandArgs = {}): Promise<any> {
uri = getCommandUri(uri, editor);
if (uri === undefined) return undefined;
args.line = args.line || (editor === undefined ? 0 : editor.selection.active.line);
if (args.commit === undefined || (args.commit.type !== 'file') || args.range !== undefined) {
const gitUri = await GitUri.fromUri(uri, this.git);
try {
const sha = args.commit === undefined ? gitUri.sha : args.commit.sha;
const log = await this.git.getLogForFile(gitUri.repoPath, gitUri.fsPath, undefined, sha ? undefined : 2, args.range!);
if (log === undefined) return window.showWarningMessage(`Unable to open compare. File is probably not under source control`);
args.commit = (sha && log.commits.get(sha)) || Iterables.first(log.commits.values());
// If the sha is missing, treat it as a DiffWithWorking
if (gitUri.sha === undefined) return commands.executeCommand(Commands.DiffWithWorking, uri, { commit: args.commit, showOptions: args.showOptions } as DiffWithWorkingCommandArgs);
}
catch (ex) {
Logger.error(ex, 'DiffWithPreviousCommand', `getLogForFile(${gitUri.repoPath}, ${gitUri.fsPath})`);
return window.showErrorMessage(`Unable to open compare. See output channel for more details`);
}
}
if (args.commit.previousSha === undefined) return window.showInformationMessage(`Commit ${args.commit.shortSha} (${args.commit.author}, ${moment(args.commit.date).fromNow()}) has no previous commit`);
try {
const [rhs, lhs] = await Promise.all([
this.git.getVersionedFile(args.commit.repoPath, args.commit.uri.fsPath, args.commit.sha),
this.git.getVersionedFile(args.commit.repoPath, args.commit.previousUri.fsPath, args.commit.previousSha)
]);
await commands.executeCommand(BuiltInCommands.Diff,
Uri.file(lhs),
Uri.file(rhs),
`${path.basename(args.commit.previousUri.fsPath)} (${args.commit.previousShortSha}) \u2194 ${path.basename(args.commit.uri.fsPath)} (${args.commit.shortSha})`,
args.showOptions);
// TODO: Figure out how to focus the left pane
return await commands.executeCommand(BuiltInCommands.RevealLine, { lineNumber: args.line, at: 'center' });
}
catch (ex) {
Logger.error(ex, 'DiffWithPreviousCommand', 'getVersionedFile');
return window.showErrorMessage(`Unable to open compare. See output channel for more details`);
}
}
}

View File

@@ -0,0 +1,64 @@
'use strict';
import { commands, TextDocumentShowOptions, TextEditor, Uri, window } from 'vscode';
import { ActiveEditorCommand, Commands, getCommandUri } from './common';
import { BuiltInCommands } from '../constants';
import { GitCommit, GitService, GitUri } from '../gitService';
import { Logger } from '../logger';
import * as path from 'path';
export interface DiffWithWorkingCommandArgs {
commit?: GitCommit;
line?: number;
showOptions?: TextDocumentShowOptions;
}
export class DiffWithWorkingCommand extends ActiveEditorCommand {
constructor(private git: GitService) {
super(Commands.DiffWithWorking);
}
async execute(editor: TextEditor, uri?: Uri, args: DiffWithWorkingCommandArgs = {}): Promise<any> {
uri = getCommandUri(uri, editor);
if (uri === undefined) return undefined;
args.line = args.line || (editor === undefined ? 0 : editor.selection.active.line);
if (args.commit === undefined || GitService.isUncommitted(args.commit.sha)) {
const gitUri = await GitUri.fromUri(uri, this.git);
// If the sha is missing, just let the user know the file matches
if (gitUri.sha === undefined) return window.showInformationMessage(`File matches the working tree`);
try {
args.commit = await this.git.getLogCommit(gitUri.repoPath, gitUri.fsPath, gitUri.sha, { firstIfMissing: true });
if (args.commit === undefined) return window.showWarningMessage(`Unable to open compare. File is probably not under source control`);
}
catch (ex) {
Logger.error(ex, 'DiffWithWorkingCommand', `getLogCommit(${gitUri.repoPath}, ${gitUri.fsPath}, ${gitUri.sha})`);
return window.showErrorMessage(`Unable to open compare. See output channel for more details`);
}
}
const gitUri = await GitUri.fromUri(uri, this.git);
const workingFileName = await this.git.findWorkingFileName(gitUri.repoPath, gitUri.fsPath);
if (workingFileName === undefined) return undefined;
try {
const compare = await this.git.getVersionedFile(args.commit.repoPath, args.commit.uri.fsPath, args.commit.sha);
await commands.executeCommand(BuiltInCommands.Diff,
Uri.file(compare),
Uri.file(path.resolve(gitUri.repoPath, workingFileName)),
`${path.basename(args.commit.uri.fsPath)} (${args.commit.shortSha}) \u2194 ${path.basename(workingFileName)}`,
args.showOptions);
// TODO: Figure out how to focus the left pane
return await commands.executeCommand(BuiltInCommands.RevealLine, { lineNumber: args.line, at: 'center' });
}
catch (ex) {
Logger.error(ex, 'DiffWithWorkingCommand', 'getVersionedFile');
return window.showErrorMessage(`Unable to open compare. See output channel for more details`);
}
}
}

140
src/commands/keyboard.ts Normal file
View File

@@ -0,0 +1,140 @@
'use strict';
import { commands, Disposable } from 'vscode';
import { CommandContext, setCommandContext } from './common';
import { ExtensionKey } from '../constants';
import { QuickPickItem } from '../quickPicks';
import { Logger } from '../logger';
const keyNoopCommand = Object.create(null) as QuickPickItem;
export { keyNoopCommand as KeyNoopCommand };
export declare type Keys = 'left' | 'right' | ',' | '.';
export const keys: Keys[] = [
'left',
'right',
',',
'.'
];
export declare interface KeyMapping {
[id: string]: (QuickPickItem | (() => Promise<QuickPickItem>) | undefined);
}
const mappings: KeyMapping[] = [];
let _instance: Keyboard;
export class KeyboardScope extends Disposable {
constructor(private mapping: KeyMapping) {
super(() => this.dispose());
for (const key in mapping) {
mapping[key] = mapping[key] || keyNoopCommand;
}
}
async dispose() {
const index = mappings.indexOf(this.mapping);
Logger.log('KeyboardScope.dispose', mappings.length, index);
if (index === (mappings.length - 1)) {
mappings.pop();
await this.updateKeyCommandsContext(mappings[mappings.length - 1]);
}
else {
mappings.splice(index, 1);
}
}
async begin() {
mappings.push(this.mapping);
await this.updateKeyCommandsContext(this.mapping);
return this;
}
async clearKeyCommand(key: Keys) {
const mapping = mappings[mappings.length - 1];
if (mapping !== this.mapping || !mapping[key]) return;
Logger.log('KeyboardScope.clearKeyCommand', mappings.length, key);
mapping[key] = undefined;
await setCommandContext(`${CommandContext.Key}:${key}`, false);
}
async setKeyCommand(key: Keys, command: QuickPickItem | (() => Promise<QuickPickItem>)) {
const mapping = mappings[mappings.length - 1];
if (mapping !== this.mapping) return;
Logger.log('KeyboardScope.setKeyCommand', mappings.length, key, !!mapping[key]);
if (!mapping[key]) {
mapping[key] = command;
await setCommandContext(`${CommandContext.Key}:${key}`, true);
}
else {
mapping[key] = command;
}
}
private async updateKeyCommandsContext(mapping: KeyMapping) {
const promises = [];
for (const key of keys) {
promises.push(setCommandContext(`${CommandContext.Key}:${key}`, !!(mapping && mapping[key])));
}
await Promise.all(promises);
}
}
export class Keyboard extends Disposable {
static get instance(): Keyboard {
return _instance;
}
private _disposable: Disposable;
constructor() {
super(() => this.dispose());
const subscriptions: Disposable[] = [];
for (const key of keys) {
subscriptions.push(commands.registerCommand(`${ExtensionKey}.key.${key}`, () => this.execute(key), this));
}
this._disposable = Disposable.from(...subscriptions);
_instance = this;
}
dispose() {
this._disposable && this._disposable.dispose();
}
async beginScope(mapping?: KeyMapping): Promise<KeyboardScope> {
Logger.log('Keyboard.beginScope', mappings.length);
return await new KeyboardScope(mapping ? Object.assign(Object.create(null), mapping) : Object.create(null)).begin();
}
async execute(key: Keys): Promise<{} | undefined> {
if (!mappings.length) return undefined;
try {
const mapping = mappings[mappings.length - 1];
let command = mapping[key] as QuickPickItem | (() => Promise<QuickPickItem>);
if (typeof command === 'function') {
command = await command();
}
if (!command || typeof command.onDidPressKey !== 'function') return undefined;
Logger.log('Keyboard.execute', key);
return await command.onDidPressKey(key);
}
catch (ex) {
Logger.error(ex, 'Keyboard.execute');
return undefined;
}
}
}

View File

@@ -0,0 +1,55 @@
'use strict';
import { Arrays } from '../system';
import { commands, TextEditor, TextEditorEdit, Uri, window } from 'vscode';
import { ActiveEditorCommand, Commands, getCommandUri } from './common';
import { GitService, GitUri } from '../gitService';
import { Logger } from '../logger';
import { BranchesQuickPick, CommandQuickPickItem } from '../quickPicks';
import { OpenInRemoteCommandArgs } from './openInRemote';
export interface OpenBranchInRemoteCommandArgs {
branch?: string;
}
export class OpenBranchInRemoteCommand extends ActiveEditorCommand {
constructor(private git: GitService) {
super(Commands.OpenBranchInRemote);
}
async execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri, args: OpenBranchInRemoteCommandArgs = {}) {
uri = getCommandUri(uri, editor);
const gitUri = uri && await GitUri.fromUri(uri, this.git);
const repoPath = gitUri === undefined ? this.git.repoPath : gitUri.repoPath;
if (!repoPath) return undefined;
try {
if (args.branch === undefined) {
const branches = await this.git.getBranches(repoPath);
const pick = await BranchesQuickPick.show(branches, `Show history for branch\u2026`);
if (pick === undefined) return undefined;
if (pick instanceof CommandQuickPickItem) return undefined;
args.branch = pick.branch.name;
if (args.branch === undefined) return undefined;
}
const remotes = Arrays.uniqueBy(await this.git.getRemotes(repoPath), _ => _.url, _ => !!_.provider);
return commands.executeCommand(Commands.OpenInRemote, uri, {
resource: {
type: 'branch',
branch: args.branch
},
remotes
} as OpenInRemoteCommandArgs);
}
catch (ex) {
Logger.error(ex, 'OpenBranchInRemoteCommandArgs');
return window.showErrorMessage(`Unable to open branch in remote provider. See output channel for more details`);
}
}
}

View File

@@ -0,0 +1,42 @@
'use strict';
import { TextDocumentShowOptions, TextEditor, Uri, window } from 'vscode';
import { ActiveEditorCommand, Commands, getCommandUri, openEditor } from './common';
import { GitService } from '../gitService';
import { Logger } from '../logger';
export interface OpenChangedFilesCommandArgs {
uris?: Uri[];
}
export class OpenChangedFilesCommand extends ActiveEditorCommand {
constructor(private git: GitService) {
super(Commands.OpenChangedFiles);
}
async execute(editor: TextEditor, uri?: Uri, args: OpenChangedFilesCommandArgs = {}) {
uri = getCommandUri(uri, editor);
try {
if (args.uris === undefined) {
const repoPath = await this.git.getRepoPathFromUri(uri);
if (!repoPath) return window.showWarningMessage(`Unable to open changed files`);
const status = await this.git.getStatusForRepo(repoPath);
if (status === undefined) return window.showWarningMessage(`Unable to open changed files`);
args.uris = status.files.filter(_ => _.status !== 'D').map(_ => _.Uri);
}
for (const uri of args.uris) {
await openEditor(uri, { preserveFocus: true, preview: false } as TextDocumentShowOptions);
}
return undefined;
}
catch (ex) {
Logger.error(ex, 'OpenChangedFilesCommand');
return window.showErrorMessage(`Unable to open changed files. See output channel for more details`);
}
}
}

View File

@@ -0,0 +1,52 @@
'use strict';
import { Arrays } from '../system';
import { commands, TextEditor, TextEditorEdit, Uri, window } from 'vscode';
import { ActiveEditorCommand, Commands, getCommandUri } from './common';
import { GitCommit, GitService, GitUri } from '../gitService';
import { Logger } from '../logger';
import { OpenInRemoteCommandArgs } from './openInRemote';
export class OpenCommitInRemoteCommand extends ActiveEditorCommand {
constructor(private git: GitService) {
super(Commands.OpenCommitInRemote);
}
async execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri) {
uri = getCommandUri(uri, editor);
if (uri === undefined) return undefined;
if (editor !== undefined && editor.document !== undefined && editor.document.isDirty) return undefined;
const gitUri = await GitUri.fromUri(uri, this.git);
if (!gitUri.repoPath) return undefined;
const line = editor === undefined ? gitUri.offset : editor.selection.active.line;
try {
const blameline = line - gitUri.offset;
if (blameline < 0) return undefined;
const blame = await this.git.getBlameForLine(gitUri, blameline);
if (blame === undefined) return window.showWarningMessage(`Unable to open commit in remote provider. File is probably not under source control`);
let commit = blame.commit;
// If the line is uncommitted, find the previous commit
if (commit.isUncommitted) {
commit = new GitCommit(commit.type, commit.repoPath, commit.previousSha!, commit.previousFileName!, commit.author, commit.date, commit.message);
}
const remotes = Arrays.uniqueBy(await this.git.getRemotes(gitUri.repoPath), _ => _.url, _ => !!_.provider);
return commands.executeCommand(Commands.OpenInRemote, uri, {
resource: {
type: 'commit',
sha: commit.sha
},
remotes
} as OpenInRemoteCommandArgs);
}
catch (ex) {
Logger.error(ex, 'OpenCommitInRemoteCommand');
return window.showErrorMessage(`Unable to open commit in remote provider. See output channel for more details`);
}
}
}

View File

@@ -0,0 +1,44 @@
'use strict';
import { Arrays } from '../system';
import { commands, Range, TextEditor, TextEditorEdit, Uri, window } from 'vscode';
import { ActiveEditorCommand, Commands, getCommandUri } from './common';
import { GitService, GitUri } from '../gitService';
import { Logger } from '../logger';
import { OpenInRemoteCommandArgs } from './openInRemote';
export class OpenFileInRemoteCommand extends ActiveEditorCommand {
constructor(private git: GitService) {
super(Commands.OpenFileInRemote);
}
async execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri) {
uri = getCommandUri(uri, editor);
if (uri === undefined) return undefined;
const gitUri = await GitUri.fromUri(uri, this.git);
if (!gitUri.repoPath) return undefined;
const branch = await this.git.getBranch(gitUri.repoPath);
try {
const remotes = Arrays.uniqueBy(await this.git.getRemotes(gitUri.repoPath), _ => _.url, _ => !!_.provider);
const range = editor === undefined ? undefined : new Range(editor.selection.start.with({ line: editor.selection.start.line + 1 }), editor.selection.end.with({ line: editor.selection.end.line + 1 }));
return commands.executeCommand(Commands.OpenInRemote, uri, {
resource: {
type: 'file',
branch: branch === undefined ? 'Current' : branch.name,
fileName: gitUri.getRelativePath(),
range: range,
sha: gitUri.sha
},
remotes
} as OpenInRemoteCommandArgs);
}
catch (ex) {
Logger.error(ex, 'OpenFileInRemoteCommand');
return window.showErrorMessage(`Unable to open file in remote provider. See output channel for more details`);
}
}
}

View File

@@ -0,0 +1,93 @@
'use strict';
import { TextEditor, Uri, window } from 'vscode';
import { ActiveEditorCommand, Commands, getCommandUri } from './common';
import { GitLogCommit, GitRemote, RemoteResource } from '../gitService';
import { Logger } from '../logger';
import { CommandQuickPickItem, OpenRemoteCommandQuickPickItem, RemotesQuickPick } from '../quickPicks';
export interface OpenInRemoteCommandArgs {
remotes?: GitRemote[];
resource?: RemoteResource;
goBackCommand?: CommandQuickPickItem;
}
export class OpenInRemoteCommand extends ActiveEditorCommand {
constructor() {
super(Commands.OpenInRemote);
}
async execute(editor: TextEditor, uri?: Uri, args: OpenInRemoteCommandArgs = {}) {
uri = getCommandUri(uri, editor);
try {
if (args.remotes === undefined || args.resource === undefined) return undefined;
if (args.remotes.length === 1) {
const command = new OpenRemoteCommandQuickPickItem(args.remotes[0], args.resource);
return command.execute();
}
let placeHolder = '';
switch (args.resource.type) {
case 'branch':
// Check to see if the remote is in the branch
const index = args.resource.branch.indexOf('/');
if (index >= 0) {
const remoteName = args.resource.branch.substring(0, index);
const remote = args.remotes.find(r => r.name === remoteName);
if (remote !== undefined) {
args.resource.branch = args.resource.branch.substring(index + 1);
args.remotes = [remote];
}
}
placeHolder = `open ${args.resource.branch} branch in\u2026`;
break;
case 'commit':
const shortSha = args.resource.sha.substring(0, 8);
placeHolder = `open commit ${shortSha} in\u2026`;
break;
case 'file':
if (args.resource.commit !== undefined && args.resource.commit instanceof GitLogCommit) {
if (args.resource.commit.status === 'D') {
args.resource.sha = args.resource.commit.previousSha;
placeHolder = `open ${args.resource.fileName} \u00a0\u2022\u00a0 ${args.resource.commit.previousShortSha} in\u2026`;
}
else {
args.resource.sha = args.resource.commit.sha;
placeHolder = `open ${args.resource.fileName} \u00a0\u2022\u00a0 ${args.resource.commit.shortSha} in\u2026`;
}
}
else {
const shortFileSha = args.resource.sha === undefined ? '' : args.resource.sha.substring(0, 8);
const shaSuffix = shortFileSha ? ` \u00a0\u2022\u00a0 ${shortFileSha}` : '';
placeHolder = `open ${args.resource.fileName}${shaSuffix} in\u2026`;
}
break;
case 'working-file':
placeHolder = `open ${args.resource.fileName} in\u2026`;
break;
}
if (args.remotes.length === 1) {
const command = new OpenRemoteCommandQuickPickItem(args.remotes[0], args.resource);
return command.execute();
}
const pick = await RemotesQuickPick.show(args.remotes, placeHolder, args.resource, args.goBackCommand);
if (pick === undefined) return undefined;
return pick.execute();
}
catch (ex) {
Logger.error(ex, 'OpenInRemoteCommand');
return window.showErrorMessage(`Unable to open in remote provider. See output channel for more details`);
}
}
}

View File

@@ -0,0 +1,37 @@
'use strict';
import { Arrays } from '../system';
import { commands, TextEditor, TextEditorEdit, Uri, window } from 'vscode';
import { ActiveEditorCommand, Commands, getCommandUri } from './common';
import { GitService, GitUri } from '../gitService';
import { Logger } from '../logger';
import { OpenInRemoteCommandArgs } from './openInRemote';
export class OpenRepoInRemoteCommand extends ActiveEditorCommand {
constructor(private git: GitService) {
super(Commands.OpenRepoInRemote);
}
async execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri) {
uri = getCommandUri(uri, editor);
const gitUri = uri && await GitUri.fromUri(uri, this.git);
const repoPath = gitUri === undefined ? this.git.repoPath : gitUri.repoPath;
if (!repoPath) return undefined;
try {
const remotes = Arrays.uniqueBy(await this.git.getRemotes(repoPath), _ => _.url, _ => !!_.provider);
return commands.executeCommand(Commands.OpenInRemote, uri, {
resource: {
type: 'repo'
},
remotes
} as OpenInRemoteCommandArgs);
}
catch (ex) {
Logger.error(ex, 'OpenRepoInRemoteCommand');
return window.showErrorMessage(`Unable to open repository in remote provider. See output channel for more details`);
}
}
}

View File

@@ -0,0 +1,44 @@
'use strict';
import { commands, Position, Range, TextEditor, TextEditorEdit, Uri, window } from 'vscode';
import { Commands, EditorCommand, getCommandUri } from './common';
import { BuiltInCommands } from '../constants';
import { GitService, GitUri } from '../gitService';
import { Logger } from '../logger';
export interface ShowBlameHistoryCommandArgs {
line?: number;
position?: Position;
range?: Range;
sha?: string;
}
export class ShowBlameHistoryCommand extends EditorCommand {
constructor(private git: GitService) {
super(Commands.ShowBlameHistory);
}
async execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri, args: ShowBlameHistoryCommandArgs = {}) {
uri = getCommandUri(uri, editor);
if (uri === undefined) return undefined;
if (args.range == null || args.position == null) {
// If the command is executed manually -- treat it as a click on the root lens (i.e. show blame for the whole file)
args.range = editor.document.validateRange(new Range(0, 0, 1000000, 1000000));
args.position = editor.document.validateRange(new Range(0, 0, 0, 1000000)).start;
}
const gitUri = await GitUri.fromUri(uri, this.git);
try {
const locations = await this.git.getBlameLocations(gitUri, args.range, args.sha, args.line);
if (locations === undefined) return window.showWarningMessage(`Unable to show blame history. File is probably not under source control`);
return commands.executeCommand(BuiltInCommands.ShowReferences, uri, args.position, locations);
}
catch (ex) {
Logger.error(ex, 'ShowBlameHistoryCommand', 'getBlameLocations');
return window.showErrorMessage(`Unable to show blame history. See output channel for more details`);
}
}
}

View File

@@ -0,0 +1,145 @@
'use strict';
import { commands, InputBoxOptions, TextEditor, Uri, window } from 'vscode';
import { ActiveEditorCachedCommand, Commands, getCommandUri } from './common';
import { GitRepoSearchBy, GitService, GitUri } from '../gitService';
import { Logger } from '../logger';
import { CommandQuickPickItem, CommitsQuickPick } from '../quickPicks';
import { ShowQuickCommitDetailsCommandArgs } from './showQuickCommitDetails';
import { paste } from 'copy-paste';
const searchByRegex = /^([@:#])/;
const searchByMap = new Map<string, GitRepoSearchBy>([
['@', GitRepoSearchBy.Author],
[':', GitRepoSearchBy.Files],
['#', GitRepoSearchBy.Sha]
]);
export interface ShowCommitSearchCommandArgs {
search?: string;
searchBy?: GitRepoSearchBy;
goBackCommand?: CommandQuickPickItem;
}
export class ShowCommitSearchCommand extends ActiveEditorCachedCommand {
constructor(private git: GitService) {
super(Commands.ShowCommitSearch);
}
async execute(editor: TextEditor, uri?: Uri, args: ShowCommitSearchCommandArgs = {}) {
uri = getCommandUri(uri, editor);
const gitUri = uri === undefined ? undefined : await GitUri.fromUri(uri, this.git);
const repoPath = gitUri === undefined ? this.git.repoPath : gitUri.repoPath;
if (!repoPath) return window.showWarningMessage(`Unable to show commit search`);
if (!args.search || args.searchBy == null) {
try {
if (!args.search) {
if (editor !== undefined && gitUri !== undefined) {
const line = editor.selection.active.line - gitUri.offset;
const blameLine = await this.git.getBlameForLine(gitUri, line);
if (blameLine !== undefined) {
args.search = `#${blameLine.commit.shortSha}`;
}
}
if (!args.search) {
args.search = await new Promise<string>((resolve, reject) => {
paste((err: Error, content: string) => resolve(err ? '' : content));
});
}
}
}
catch (ex) {
Logger.error(ex, 'ShowCommitSearchCommand', 'search prefetch failed');
}
args.search = await window.showInputBox({
value: args.search,
prompt: `Please enter a search string`,
placeHolder: `search by message, author (use @<name>), files (use :<pattern>), or commit id (use #<sha>)`
} as InputBoxOptions);
if (args.search === undefined) return args.goBackCommand === undefined ? undefined : args.goBackCommand.execute();
const match = searchByRegex.exec(args.search);
if (match && match[1]) {
args.searchBy = searchByMap.get(match[1]);
args.search = args.search.substring((args.search[1] === ' ') ? 2 : 1);
}
else if (GitService.isSha(args.search)) {
args.searchBy = GitRepoSearchBy.Sha;
}
else {
args.searchBy = GitRepoSearchBy.Message;
}
}
try {
if (args.searchBy === undefined) {
args.searchBy = GitRepoSearchBy.Message;
}
const log = await this.git.getLogForRepoSearch(repoPath, args.search, args.searchBy);
if (log === undefined) return undefined;
let originalSearch: string | undefined = undefined;
let placeHolder: string | undefined = undefined;
switch (args.searchBy) {
case GitRepoSearchBy.Author:
originalSearch = `@${args.search}`;
placeHolder = `commits with author matching '${args.search}'`;
break;
case GitRepoSearchBy.Files:
originalSearch = `:${args.search}`;
placeHolder = `commits with files matching '${args.search}'`;
break;
case GitRepoSearchBy.Message:
originalSearch = args.search;
placeHolder = `commits with message matching '${args.search}'`;
break;
case GitRepoSearchBy.Sha:
originalSearch = `#${args.search}`;
placeHolder = `commits with id matching '${args.search}'`;
break;
}
// Create a command to get back to here
const currentCommand = new CommandQuickPickItem({
label: `go back \u21A9`,
description: `\u00a0 \u2014 \u00a0\u00a0 to commit search`
}, Commands.ShowCommitSearch, [
uri,
{
search: originalSearch,
goBackCommand: args.goBackCommand
} as ShowCommitSearchCommandArgs
]);
const pick = await CommitsQuickPick.show(this.git, log, placeHolder!, currentCommand);
if (pick === undefined) return undefined;
if (pick instanceof CommandQuickPickItem) return pick.execute();
return commands.executeCommand(Commands.ShowQuickCommitDetails,
new GitUri(pick.commit.uri, pick.commit),
{
sha: pick.commit.sha,
commit: pick.commit,
goBackCommand: new CommandQuickPickItem({
label: `go back \u21A9`,
description: `\u00a0 \u2014 \u00a0\u00a0 to search for ${placeHolder}`
}, Commands.ShowCommitSearch, [
uri,
args
])
} as ShowQuickCommitDetailsCommandArgs);
}
catch (ex) {
Logger.error(ex, 'ShowCommitSearchCommand');
return window.showErrorMessage(`Unable to find commits. See output channel for more details`);
}
}
}

View File

@@ -0,0 +1,35 @@
'use strict';
import { TextEditor, TextEditorEdit, Uri, window, workspace } from 'vscode';
import { AnnotationController } from '../annotations/annotationController';
import { Commands, EditorCommand } from './common';
import { ExtensionKey, FileAnnotationType, IConfig } from '../configuration';
import { Logger } from '../logger';
export interface ShowFileBlameCommandArgs {
sha?: string;
type?: FileAnnotationType;
}
export class ShowFileBlameCommand extends EditorCommand {
constructor(private annotationController: AnnotationController) {
super(Commands.ShowFileBlame);
}
async execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri, args: ShowFileBlameCommandArgs = {}): Promise<any> {
if (editor !== undefined && editor.document !== undefined && editor.document.isDirty) return undefined;
try {
if (args.type === undefined) {
const cfg = workspace.getConfiguration().get<IConfig>(ExtensionKey)!;
args.type = cfg.blame.file.annotationType;
}
return this.annotationController.showAnnotations(editor, args.type, args.sha !== undefined ? args.sha : editor.selection.active.line);
}
catch (ex) {
Logger.error(ex, 'ShowFileBlameCommand');
return window.showErrorMessage(`Unable to show file blame annotations. See output channel for more details`);
}
}
}

View File

@@ -0,0 +1,42 @@
'use strict';
import { commands, Position, Range, TextEditor, TextEditorEdit, Uri, window } from 'vscode';
import { Commands, EditorCommand, getCommandUri } from './common';
import { BuiltInCommands } from '../constants';
import { GitService, GitUri } from '../gitService';
import { Logger } from '../logger';
export interface ShowFileHistoryCommandArgs {
line?: number;
position?: Position;
sha?: string;
}
export class ShowFileHistoryCommand extends EditorCommand {
constructor(private git: GitService) {
super(Commands.ShowFileHistory);
}
async execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri, args: ShowFileHistoryCommandArgs = {}) {
uri = getCommandUri(uri, editor);
if (uri === undefined) return undefined;
if (args.position == null) {
// If the command is executed manually -- treat it as a click on the root lens (i.e. show blame for the whole file)
args.position = editor.document.validateRange(new Range(0, 0, 0, 1000000)).start;
}
const gitUri = await GitUri.fromUri(uri, this.git);
try {
const locations = await this.git.getLogLocations(gitUri, args.sha, args.line);
if (locations === undefined) return window.showWarningMessage(`Unable to show file history. File is probably not under source control`);
return commands.executeCommand(BuiltInCommands.ShowReferences, uri, args.position, locations);
}
catch (ex) {
Logger.error(ex, 'ShowFileHistoryCommand', 'getLogLocations');
return window.showErrorMessage(`Unable to show file history. See output channel for more details`);
}
}
}

View File

@@ -0,0 +1,24 @@
'use strict';
import { commands, window } from 'vscode';
import { Command, Commands, getLastCommand } from './common';
import { Logger } from '../logger';
export class ShowLastQuickPickCommand extends Command {
constructor() {
super(Commands.ShowLastQuickPick);
}
async execute() {
const command = getLastCommand();
if (!command) return undefined;
try {
return commands.executeCommand(command.command, ...command.args);
}
catch (ex) {
Logger.error(ex, 'ShowLastQuickPickCommand');
return window.showErrorMessage(`Unable to show last quick pick. See output channel for more details`);
}
}
}

View File

@@ -0,0 +1,34 @@
'use strict';
import { TextEditor, TextEditorEdit, Uri, window, workspace } from 'vscode';
import { CurrentLineController } from '../currentLineController';
import { Commands, EditorCommand } from './common';
import { ExtensionKey, IConfig, LineAnnotationType } from '../configuration';
import { Logger } from '../logger';
export interface ShowLineBlameCommandArgs {
type?: LineAnnotationType;
}
export class ShowLineBlameCommand extends EditorCommand {
constructor(private currentLineController: CurrentLineController) {
super(Commands.ShowLineBlame);
}
async execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri, args: ShowLineBlameCommandArgs = {}): Promise<any> {
if (editor !== undefined && editor.document !== undefined && editor.document.isDirty) return undefined;
try {
if (args.type === undefined) {
const cfg = workspace.getConfiguration().get<IConfig>(ExtensionKey)!;
args.type = cfg.blame.line.annotationType;
}
return this.currentLineController.showAnnotations(editor, args.type);
}
catch (ex) {
Logger.error(ex, 'ShowLineBlameCommand');
return window.showErrorMessage(`Unable to show line blame annotations. See output channel for more details`);
}
}
}

View File

@@ -0,0 +1,90 @@
'use strict';
import { commands, TextEditor, Uri, window } from 'vscode';
import { ActiveEditorCachedCommand, Commands, getCommandUri } from './common';
import { GitService, GitUri, IGitLog } from '../gitService';
import { Logger } from '../logger';
import { BranchesQuickPick, BranchHistoryQuickPick, CommandQuickPickItem } from '../quickPicks';
import { ShowQuickCommitDetailsCommandArgs } from './showQuickCommitDetails';
export interface ShowQuickBranchHistoryCommandArgs {
branch?: string;
log?: IGitLog;
maxCount?: number;
goBackCommand?: CommandQuickPickItem;
nextPageCommand?: CommandQuickPickItem;
}
export class ShowQuickBranchHistoryCommand extends ActiveEditorCachedCommand {
constructor(private git: GitService) {
super(Commands.ShowQuickBranchHistory);
}
async execute(editor: TextEditor, uri?: Uri, args: ShowQuickBranchHistoryCommandArgs = {}) {
uri = getCommandUri(uri, editor);
const gitUri = uri && await GitUri.fromUri(uri, this.git);
if (args.maxCount == null) {
args.maxCount = this.git.config.advanced.maxQuickHistory;
}
let progressCancellation = args.branch === undefined ? undefined : BranchHistoryQuickPick.showProgress(args.branch);
try {
const repoPath = gitUri === undefined ? this.git.repoPath : gitUri.repoPath;
if (!repoPath) return window.showWarningMessage(`Unable to show branch history`);
if (args.branch === undefined) {
const branches = await this.git.getBranches(repoPath);
const pick = await BranchesQuickPick.show(branches, `Show history for branch\u2026`);
if (pick === undefined) return undefined;
if (pick instanceof CommandQuickPickItem) return pick.execute();
args.branch = pick.branch.name;
if (args.branch === undefined) return undefined;
progressCancellation = BranchHistoryQuickPick.showProgress(args.branch);
}
if (args.log === undefined) {
args.log = await this.git.getLogForRepo(repoPath, (gitUri && gitUri.sha) || args.branch, args.maxCount);
if (args.log === undefined) return window.showWarningMessage(`Unable to show branch history`);
}
if (progressCancellation !== undefined && progressCancellation.token.isCancellationRequested) return undefined;
const pick = await BranchHistoryQuickPick.show(this.git, args.log, gitUri, args.branch, progressCancellation!, args.goBackCommand, args.nextPageCommand);
if (pick === undefined) return undefined;
if (pick instanceof CommandQuickPickItem) return pick.execute();
// Create a command to get back to here
const currentCommand = new CommandQuickPickItem({
label: `go back \u21A9`,
description: `\u00a0 \u2014 \u00a0\u00a0 to \u00a0$(git-branch) ${args.branch} history`
}, Commands.ShowQuickBranchHistory, [
uri,
args
]);
return commands.executeCommand(Commands.ShowQuickCommitDetails,
new GitUri(pick.commit.uri, pick.commit),
{
sha: pick.commit.sha,
commit: pick.commit,
repoLog: args.log,
goBackCommand: currentCommand
} as ShowQuickCommitDetailsCommandArgs);
}
catch (ex) {
Logger.error(ex, 'ShowQuickBranchHistoryCommand');
return window.showErrorMessage(`Unable to show branch history. See output channel for more details`);
}
finally {
progressCancellation && progressCancellation.dispose();
}
}
}

View File

@@ -0,0 +1,116 @@
'use strict';
import { commands, TextEditor, Uri, window } from 'vscode';
import { ActiveEditorCachedCommand, Commands, getCommandUri } from './common';
import { GitCommit, GitLogCommit, GitService, GitUri, IGitLog } from '../gitService';
import { Logger } from '../logger';
import { CommandQuickPickItem, CommitDetailsQuickPick, CommitWithFileStatusQuickPickItem } from '../quickPicks';
import { ShowQuickCommitFileDetailsCommandArgs } from './showQuickCommitFileDetails';
import * as path from 'path';
export interface ShowQuickCommitDetailsCommandArgs {
sha?: string;
commit?: GitCommit | GitLogCommit;
repoLog?: IGitLog;
goBackCommand?: CommandQuickPickItem;
}
export class ShowQuickCommitDetailsCommand extends ActiveEditorCachedCommand {
constructor(private git: GitService) {
super(Commands.ShowQuickCommitDetails);
}
async execute(editor: TextEditor, uri?: Uri, args: ShowQuickCommitDetailsCommandArgs = {}) {
uri = getCommandUri(uri, editor);
if (uri === undefined) return undefined;
const gitUri = await GitUri.fromUri(uri, this.git);
let repoPath = gitUri.repoPath;
let workingFileName = path.relative(repoPath || '', gitUri.fsPath);
if (args.sha === undefined) {
if (editor === undefined) return undefined;
const blameline = editor.selection.active.line - gitUri.offset;
if (blameline < 0) return undefined;
try {
const blame = await this.git.getBlameForLine(gitUri, blameline);
if (blame === undefined) return window.showWarningMessage(`Unable to show commit details. File is probably not under source control`);
args.sha = blame.commit.isUncommitted ? blame.commit.previousSha : blame.commit.sha;
repoPath = blame.commit.repoPath;
workingFileName = blame.commit.fileName;
args.commit = blame.commit;
}
catch (ex) {
Logger.error(ex, 'ShowQuickCommitDetailsCommand', `getBlameForLine(${blameline})`);
return window.showErrorMessage(`Unable to show commit details. See output channel for more details`);
}
}
try {
if (args.commit === undefined || (args.commit.type !== 'branch' && args.commit.type !== 'stash')) {
if (args.repoLog !== undefined) {
args.commit = args.repoLog.commits.get(args.sha!);
// If we can't find the commit, kill the repoLog
if (args.commit === undefined) {
args.repoLog = undefined;
}
}
if (args.repoLog === undefined) {
const log = await this.git.getLogForRepo(repoPath!, args.sha, 2);
if (log === undefined) return window.showWarningMessage(`Unable to show commit details`);
args.commit = log.commits.get(args.sha!);
}
}
if (args.commit === undefined) return window.showWarningMessage(`Unable to show commit details`);
if (args.commit.workingFileName === undefined) {
args.commit.workingFileName = workingFileName;
}
if (args.goBackCommand === undefined) {
// Create a command to get back to the branch history
args.goBackCommand = new CommandQuickPickItem({
label: `go back \u21A9`,
description: `\u00a0 \u2014 \u00a0\u00a0 to branch history`
}, Commands.ShowQuickCurrentBranchHistory, [
new GitUri(args.commit.uri, args.commit)
]);
}
// Create a command to get back to where we are right now
const currentCommand = new CommandQuickPickItem({
label: `go back \u21A9`,
description: `\u00a0 \u2014 \u00a0\u00a0 to details of \u00a0$(git-commit) ${args.commit.shortSha}`
}, Commands.ShowQuickCommitDetails, [
new GitUri(args.commit.uri, args.commit),
args
]);
const pick = await CommitDetailsQuickPick.show(this.git, args.commit as GitLogCommit, uri, args.goBackCommand, currentCommand, args.repoLog);
if (pick === undefined) return undefined;
if (!(pick instanceof CommitWithFileStatusQuickPickItem)) return pick.execute();
return commands.executeCommand(Commands.ShowQuickCommitFileDetails,
pick.gitUri,
{
commit: args.commit,
sha: pick.sha,
goBackCommand: currentCommand
} as ShowQuickCommitFileDetailsCommandArgs);
}
catch (ex) {
Logger.error(ex, 'ShowQuickCommitDetailsCommand');
return window.showErrorMessage(`Unable to show commit details. See output channel for more details`);
}
}
}

View File

@@ -0,0 +1,116 @@
'use strict';
import { TextEditor, Uri, window } from 'vscode';
import { ActiveEditorCachedCommand, Commands, getCommandUri } from './common';
import { GitCommit, GitLogCommit, GitService, GitUri, IGitLog } from '../gitService';
import { Logger } from '../logger';
import { CommandQuickPickItem, CommitFileDetailsQuickPick } from '../quickPicks';
import { ShowQuickCommitDetailsCommandArgs } from './showQuickCommitDetails';
import * as path from 'path';
export interface ShowQuickCommitFileDetailsCommandArgs {
sha?: string;
commit?: GitCommit | GitLogCommit;
fileLog?: IGitLog;
goBackCommand?: CommandQuickPickItem;
}
export class ShowQuickCommitFileDetailsCommand extends ActiveEditorCachedCommand {
constructor(private git: GitService) {
super(Commands.ShowQuickCommitFileDetails);
}
async execute(editor: TextEditor, uri?: Uri, args: ShowQuickCommitFileDetailsCommandArgs = {}) {
uri = getCommandUri(uri, editor);
if (uri === undefined) return undefined;
let workingFileName = args.commit && args.commit.workingFileName;
const gitUri = await GitUri.fromUri(uri, this.git);
if (args.sha === undefined) {
if (editor === undefined) return undefined;
const blameline = editor.selection.active.line - gitUri.offset;
if (blameline < 0) return undefined;
try {
const blame = await this.git.getBlameForLine(gitUri, blameline);
if (blame === undefined) return window.showWarningMessage(`Unable to show commit file details. File is probably not under source control`);
args.sha = blame.commit.isUncommitted ? blame.commit.previousSha : blame.commit.sha;
args.commit = blame.commit;
workingFileName = path.relative(args.commit.repoPath, gitUri.fsPath);
}
catch (ex) {
Logger.error(ex, 'ShowQuickCommitFileDetailsCommand', `getBlameForLine(${blameline})`);
return window.showErrorMessage(`Unable to show commit file details. See output channel for more details`);
}
}
try {
if (args.commit === undefined || args.commit.type !== 'file') {
if (args.commit !== undefined) {
workingFileName = undefined;
}
if (args.fileLog !== undefined) {
args.commit = args.fileLog.commits.get(args.sha!);
// If we can't find the commit, kill the fileLog
if (args.commit === undefined) {
args.fileLog = undefined;
}
}
if (args.fileLog === undefined) {
args.commit = await this.git.getLogCommit(args.commit ? args.commit.repoPath : gitUri.repoPath, gitUri.fsPath, args.sha, { previous: true });
if (args.commit === undefined) return window.showWarningMessage(`Unable to show commit file details`);
}
}
if (args.commit === undefined) return window.showWarningMessage(`Unable to show commit file details`);
// Attempt to the most recent commit -- so that we can find the real working filename if there was a rename
args.commit.workingFileName = workingFileName;
args.commit.workingFileName = await this.git.findWorkingFileName(args.commit);
const shortSha = args.sha!.substring(0, 8);
if (args.goBackCommand === undefined) {
// Create a command to get back to the commit details
args.goBackCommand = new CommandQuickPickItem({
label: `go back \u21A9`,
description: `\u00a0 \u2014 \u00a0\u00a0 to details of \u00a0$(git-commit) ${shortSha}`
}, Commands.ShowQuickCommitDetails, [
new GitUri(args.commit.uri, args.commit),
{
commit: args.commit,
sha: args.sha
} as ShowQuickCommitDetailsCommandArgs
]);
}
// Create a command to get back to where we are right now
const currentCommand = new CommandQuickPickItem({
label: `go back \u21A9`,
description: `\u00a0 \u2014 \u00a0\u00a0 to details of \u00a0$(file-text) ${path.basename(args.commit.fileName)} in \u00a0$(git-commit) ${shortSha}`
}, Commands.ShowQuickCommitFileDetails, [
new GitUri(args.commit.uri, args.commit),
args
]);
const pick = await CommitFileDetailsQuickPick.show(this.git, args.commit as GitLogCommit, uri, args.goBackCommand, currentCommand, args.fileLog);
if (pick === undefined) return undefined;
if (pick instanceof CommandQuickPickItem) return pick.execute();
return undefined;
}
catch (ex) {
Logger.error(ex, 'ShowQuickCommitFileDetailsCommand');
return window.showErrorMessage(`Unable to show commit file details. See output channel for more details`);
}
}
}

View File

@@ -0,0 +1,41 @@
'use strict';
import { commands, TextEditor, Uri, window } from 'vscode';
import { ActiveEditorCachedCommand, Commands, getCommandUri } from './common';
import { ShowQuickBranchHistoryCommandArgs } from './showQuickBranchHistory';
import { GitService } from '../gitService';
import { Logger } from '../logger';
import { CommandQuickPickItem } from '../quickPicks';
export interface ShowQuickCurrentBranchHistoryCommandArgs {
goBackCommand?: CommandQuickPickItem;
}
export class ShowQuickCurrentBranchHistoryCommand extends ActiveEditorCachedCommand {
constructor(private git: GitService) {
super(Commands.ShowQuickCurrentBranchHistory);
}
async execute(editor: TextEditor, uri?: Uri, args: ShowQuickCurrentBranchHistoryCommandArgs = {}) {
uri = getCommandUri(uri, editor);
try {
const repoPath = await this.git.getRepoPathFromUri(uri);
if (!repoPath) return window.showWarningMessage(`Unable to show branch history`);
const branch = await this.git.getBranch(repoPath);
if (branch === undefined) return undefined;
return commands.executeCommand(Commands.ShowQuickBranchHistory,
uri,
{
branch: branch.name,
goBackCommand: args.goBackCommand
} as ShowQuickBranchHistoryCommandArgs);
}
catch (ex) {
Logger.error(ex, 'ShowQuickCurrentBranchHistoryCommand');
return window.showErrorMessage(`Unable to show branch history. See output channel for more details`);
}
}
}

View File

@@ -0,0 +1,75 @@
'use strict';
import { commands, Range, TextEditor, Uri, window } from 'vscode';
import { ActiveEditorCachedCommand, Commands, getCommandUri } from './common';
import { GitService, GitUri, IGitLog } from '../gitService';
import { Logger } from '../logger';
import { CommandQuickPickItem, FileHistoryQuickPick } from '../quickPicks';
import { ShowQuickCommitFileDetailsCommandArgs } from './showQuickCommitFileDetails';
import * as path from 'path';
export interface ShowQuickFileHistoryCommandArgs {
log?: IGitLog;
maxCount?: number;
range?: Range;
goBackCommand?: CommandQuickPickItem;
nextPageCommand?: CommandQuickPickItem;
}
export class ShowQuickFileHistoryCommand extends ActiveEditorCachedCommand {
constructor(private git: GitService) {
super(Commands.ShowQuickFileHistory);
}
async execute(editor: TextEditor, uri?: Uri, args: ShowQuickFileHistoryCommandArgs = {}) {
uri = getCommandUri(uri, editor);
if (uri === undefined) return commands.executeCommand(Commands.ShowQuickCurrentBranchHistory);
const gitUri = await GitUri.fromUri(uri, this.git);
if (args.maxCount == null) {
args.maxCount = this.git.config.advanced.maxQuickHistory;
}
const progressCancellation = FileHistoryQuickPick.showProgress(gitUri);
try {
if (args.log === undefined) {
args.log = await this.git.getLogForFile(gitUri.repoPath, gitUri.fsPath, gitUri.sha, args.maxCount, args.range);
if (args.log === undefined) return window.showWarningMessage(`Unable to show file history. File is probably not under source control`);
}
if (progressCancellation.token.isCancellationRequested) return undefined;
const pick = await FileHistoryQuickPick.show(this.git, args.log, gitUri, progressCancellation, args.goBackCommand, args.nextPageCommand);
if (pick === undefined) return undefined;
if (pick instanceof CommandQuickPickItem) return pick.execute();
// Create a command to get back to where we are right now
const currentCommand = new CommandQuickPickItem({
label: `go back \u21A9`,
description: `\u00a0 \u2014 \u00a0\u00a0 to history of \u00a0$(file-text) ${path.basename(pick.commit.fileName)}${gitUri.sha ? ` from \u00a0$(git-commit) ${gitUri.shortSha}` : ''}`
}, Commands.ShowQuickFileHistory, [
uri,
args
]);
return commands.executeCommand(Commands.ShowQuickCommitFileDetails,
new GitUri(pick.commit.uri, pick.commit),
{
commit: pick.commit,
fileLog: args.log,
sha: pick.commit.sha,
goBackCommand: currentCommand
} as ShowQuickCommitFileDetailsCommandArgs);
}
catch (ex) {
Logger.error(ex, 'ShowQuickFileHistoryCommand');
return window.showErrorMessage(`Unable to show file history. See output channel for more details`);
}
finally {
progressCancellation.dispose();
}
}
}

View File

@@ -0,0 +1,40 @@
'use strict';
import { TextEditor, Uri, window } from 'vscode';
import { ActiveEditorCachedCommand, Commands, getCommandUri } from './common';
import { GitService } from '../gitService';
import { Logger } from '../logger';
import { CommandQuickPickItem, RepoStatusQuickPick } from '../quickPicks';
export interface ShowQuickRepoStatusCommandArgs {
goBackCommand?: CommandQuickPickItem;
}
export class ShowQuickRepoStatusCommand extends ActiveEditorCachedCommand {
constructor(private git: GitService) {
super(Commands.ShowQuickRepoStatus);
}
async execute(editor: TextEditor, uri?: Uri, args: ShowQuickRepoStatusCommandArgs = {}) {
uri = getCommandUri(uri, editor);
try {
const repoPath = await this.git.getRepoPathFromUri(uri);
if (!repoPath) return window.showWarningMessage(`Unable to show repository status`);
const status = await this.git.getStatusForRepo(repoPath);
if (status === undefined) return window.showWarningMessage(`Unable to show repository status`);
const pick = await RepoStatusQuickPick.show(status, args.goBackCommand);
if (pick === undefined) return undefined;
if (pick instanceof CommandQuickPickItem) return pick.execute();
return undefined;
}
catch (ex) {
Logger.error(ex, 'ShowQuickRepoStatusCommand');
return window.showErrorMessage(`Unable to show repository status. See output channel for more details`);
}
}
}

View File

@@ -0,0 +1,58 @@
'use strict';
import { commands, TextEditor, Uri, window } from 'vscode';
import { ActiveEditorCachedCommand, Commands, getCommandUri } from './common';
import { GitService, GitUri } from '../gitService';
import { Logger } from '../logger';
import { CommandQuickPickItem, StashListQuickPick } from '../quickPicks';
import { ShowQuickCommitDetailsCommandArgs } from './showQuickCommitDetails';
export interface ShowQuickStashListCommandArgs {
goBackCommand?: CommandQuickPickItem;
}
export class ShowQuickStashListCommand extends ActiveEditorCachedCommand {
constructor(private git: GitService) {
super(Commands.ShowQuickStashList);
}
async execute(editor: TextEditor, uri?: Uri, args: ShowQuickStashListCommandArgs = {}) {
uri = getCommandUri(uri, editor);
try {
const repoPath = await this.git.getRepoPathFromUri(uri);
if (!repoPath) return window.showWarningMessage(`Unable to show stashed changes`);
const stash = await this.git.getStashList(repoPath);
if (stash === undefined) return window.showWarningMessage(`Unable to show stashed changes`);
// Create a command to get back to here
const currentCommand = new CommandQuickPickItem({
label: `go back \u21A9`,
description: `\u00a0 \u2014 \u00a0\u00a0 to stashed changes`
}, Commands.ShowQuickStashList, [
uri,
{
goBackCommand: args.goBackCommand
} as ShowQuickStashListCommandArgs
]);
const pick = await StashListQuickPick.show(this.git, stash, 'list', args.goBackCommand, currentCommand);
if (pick === undefined) return undefined;
if (pick instanceof CommandQuickPickItem) return pick.execute();
return commands.executeCommand(Commands.ShowQuickCommitDetails,
new GitUri(pick.commit.uri, pick.commit),
{
commit: pick.commit,
sha: pick.commit.sha,
goBackCommand: currentCommand
} as ShowQuickCommitDetailsCommandArgs);
}
catch (ex) {
Logger.error(ex, 'ShowQuickStashListCommand');
return window.showErrorMessage(`Unable to show stashed changes. See output channel for more details`);
}
}
}

View File

@@ -0,0 +1,63 @@
'use strict';
import { MessageItem, window } from 'vscode';
import { GitService, GitStashCommit } from '../gitService';
import { Command, Commands } from './common';
import { CommitQuickPickItem, StashListQuickPick } from '../quickPicks';
import { Logger } from '../logger';
import { CommandQuickPickItem } from '../quickPicks';
export interface StashApplyCommandArgs {
confirm?: boolean;
deleteAfter?: boolean;
stashItem?: { stashName: string, message: string };
goBackCommand?: CommandQuickPickItem;
}
export class StashApplyCommand extends Command {
constructor(private git: GitService) {
super(Commands.StashApply);
}
async execute(args: StashApplyCommandArgs = { confirm: true, deleteAfter: false }) {
if (!this.git.repoPath) return undefined;
if (args.stashItem === undefined || args.stashItem.stashName === undefined) {
const stash = await this.git.getStashList(this.git.repoPath);
if (stash === undefined) return window.showInformationMessage(`There are no stashed changes`);
const currentCommand = new CommandQuickPickItem({
label: `go back \u21A9`,
description: `\u00a0 \u2014 \u00a0\u00a0 to apply stashed changes`
}, Commands.StashApply, [args]);
const pick = await StashListQuickPick.show(this.git, stash, 'apply', args.goBackCommand, currentCommand);
if (pick === undefined || !(pick instanceof CommitQuickPickItem)) return args.goBackCommand === undefined ? undefined : args.goBackCommand.execute();
args.goBackCommand = currentCommand;
args.stashItem = pick.commit as GitStashCommit;
}
try {
if (args.confirm) {
const message = args.stashItem.message.length > 80 ? `${args.stashItem.message.substring(0, 80)}\u2026` : args.stashItem.message;
const result = await window.showWarningMessage(`Apply stashed changes '${message}' to your working tree?`, { title: 'Yes, delete after applying' } as MessageItem, { title: 'Yes' } as MessageItem, { title: 'No', isCloseAffordance: true } as MessageItem);
if (result === undefined || result.title === 'No') return args.goBackCommand === undefined ? undefined : args.goBackCommand.execute();
args.deleteAfter = result.title !== 'Yes';
}
return await this.git.stashApply(this.git.repoPath, args.stashItem.stashName, args.deleteAfter);
}
catch (ex) {
Logger.error(ex, 'StashApplyCommand');
if (ex.message.includes('Your local changes to the following files would be overwritten by merge')) {
return window.showErrorMessage(`Unable to apply stash. Your working tree changes would be overwritten.`);
}
else {
return window.showErrorMessage(`Unable to apply stash. See output channel for more details`);
}
}
}
}

View File

@@ -0,0 +1,44 @@
'use strict';
import { MessageItem, window } from 'vscode';
import { GitService } from '../gitService';
import { Command, Commands } from './common';
import { Logger } from '../logger';
import { CommandQuickPickItem } from '../quickPicks';
export interface StashDeleteCommandArgs {
confirm?: boolean;
stashItem?: { stashName: string, message: string };
goBackCommand?: CommandQuickPickItem;
}
export class StashDeleteCommand extends Command {
constructor(private git: GitService) {
super(Commands.StashDelete);
}
async execute(args: StashDeleteCommandArgs = { confirm: true }) {
if (!this.git.repoPath) return undefined;
if (args.stashItem === undefined || args.stashItem.stashName === undefined) return undefined;
if (args.confirm === undefined) {
args.confirm = true;
}
try {
if (args.confirm) {
const message = args.stashItem.message.length > 80 ? `${args.stashItem.message.substring(0, 80)}\u2026` : args.stashItem.message;
const result = await window.showWarningMessage(`Delete stashed changes '${message}'?`, { title: 'Yes' } as MessageItem, { title: 'No', isCloseAffordance: true } as MessageItem);
if (result === undefined || result.title !== 'Yes') return args.goBackCommand === undefined ? undefined : args.goBackCommand.execute();
}
return await this.git.stashDelete(this.git.repoPath, args.stashItem.stashName);
}
catch (ex) {
Logger.error(ex, 'StashDeleteCommand');
return window.showErrorMessage(`Unable to delete stash. See output channel for more details`);
}
}
}

44
src/commands/stashSave.ts Normal file
View File

@@ -0,0 +1,44 @@
'use strict';
import { InputBoxOptions, window } from 'vscode';
import { GitService } from '../gitService';
import { Command, Commands } from './common';
import { Logger } from '../logger';
import { CommandQuickPickItem } from '../quickPicks';
export interface StashSaveCommandArgs {
message?: string;
unstagedOnly?: boolean;
goBackCommand?: CommandQuickPickItem;
}
export class StashSaveCommand extends Command {
constructor(private git: GitService) {
super(Commands.StashSave);
}
async execute(args: StashSaveCommandArgs = { unstagedOnly : false }) {
if (!this.git.repoPath) return undefined;
if (args.unstagedOnly === undefined) {
args.unstagedOnly = false;
}
try {
if (args.message == null) {
args.message = await window.showInputBox({
prompt: `Please provide a stash message`,
placeHolder: `Stash message`
} as InputBoxOptions);
if (args.message === undefined) return args.goBackCommand === undefined ? undefined : args.goBackCommand.execute();
}
return await this.git.stashSave(this.git.repoPath, args.message, args.unstagedOnly);
}
catch (ex) {
Logger.error(ex, 'StashSaveCommand');
return window.showErrorMessage(`Unable to save stash. See output channel for more details`);
}
}
}

View File

@@ -0,0 +1,15 @@
'use strict';
import { TextEditor, TextEditorEdit } from 'vscode';
import { Commands, EditorCommand } from './common';
import { GitService } from '../gitService';
export class ToggleCodeLensCommand extends EditorCommand {
constructor(private git: GitService) {
super(Commands.ToggleCodeLens);
}
execute(editor: TextEditor, edit: TextEditorEdit) {
return this.git.toggleCodeLens(editor);
}
}

View File

@@ -0,0 +1,35 @@
'use strict';
import { TextEditor, TextEditorEdit, Uri, window, workspace } from 'vscode';
import { AnnotationController } from '../annotations/annotationController';
import { Commands, EditorCommand } from './common';
import { ExtensionKey, FileAnnotationType, IConfig } from '../configuration';
import { Logger } from '../logger';
export interface ToggleFileBlameCommandArgs {
sha?: string;
type?: FileAnnotationType;
}
export class ToggleFileBlameCommand extends EditorCommand {
constructor(private annotationController: AnnotationController) {
super(Commands.ToggleFileBlame);
}
async execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri, args: ToggleFileBlameCommandArgs = {}): Promise<any> {
if (editor !== undefined && editor.document !== undefined && editor.document.isDirty) return undefined;
try {
if (args.type === undefined) {
const cfg = workspace.getConfiguration().get<IConfig>(ExtensionKey)!;
args.type = cfg.blame.file.annotationType;
}
return this.annotationController.toggleAnnotations(editor, args.type, args.sha !== undefined ? args.sha : editor.selection.active.line);
}
catch (ex) {
Logger.error(ex, 'ToggleFileBlameCommand');
return window.showErrorMessage(`Unable to toggle file blame annotations. See output channel for more details`);
}
}
}

View File

@@ -0,0 +1,34 @@
'use strict';
import { TextEditor, TextEditorEdit, Uri, window, workspace } from 'vscode';
import { CurrentLineController } from '../currentLineController';
import { Commands, EditorCommand } from './common';
import { ExtensionKey, IConfig, LineAnnotationType } from '../configuration';
import { Logger } from '../logger';
export interface ToggleLineBlameCommandArgs {
type?: LineAnnotationType;
}
export class ToggleLineBlameCommand extends EditorCommand {
constructor(private currentLineController: CurrentLineController) {
super(Commands.ToggleLineBlame);
}
async execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri, args: ToggleLineBlameCommandArgs = {}): Promise<any> {
if (editor !== undefined && editor.document !== undefined && editor.document.isDirty) return undefined;
try {
if (args.type === undefined) {
const cfg = workspace.getConfiguration().get<IConfig>(ExtensionKey)!;
args.type = cfg.blame.line.annotationType;
}
return this.currentLineController.toggleAnnotations(editor, args.type);
}
catch (ex) {
Logger.error(ex, 'ToggleLineBlameCommand');
return window.showErrorMessage(`Unable to toggle line blame annotations. See output channel for more details`);
}
}
}

53
src/comparers.ts Normal file
View File

@@ -0,0 +1,53 @@
'use strict';
import { TextDocument, TextEditor, Uri } from 'vscode';
abstract class Comparer<T> {
abstract equals(lhs: T, rhs: T): boolean;
}
class UriComparer extends Comparer<Uri> {
equals(lhs: Uri | undefined, rhs: Uri | undefined) {
if (lhs === undefined && rhs === undefined) return true;
if (lhs === undefined || rhs === undefined) return false;
return lhs.scheme === rhs.scheme && lhs.fsPath === rhs.fsPath;
}
}
class TextDocumentComparer extends Comparer<TextDocument> {
equals(lhs: TextDocument | undefined, rhs: TextDocument | undefined) {
if (lhs === undefined && rhs === undefined) return true;
if (lhs === undefined || rhs === undefined) return false;
return uriComparer.equals(lhs.uri, rhs.uri);
}
}
class TextEditorComparer extends Comparer<TextEditor> {
equals(lhs: TextEditor | undefined, rhs: TextEditor | undefined, options: { useId: boolean, usePosition: boolean } = { useId: false, usePosition: false }) {
if (lhs === undefined && rhs === undefined) return true;
if (lhs === undefined || rhs === undefined) return false;
if (options.usePosition && (lhs.viewColumn !== rhs.viewColumn)) return false;
if (options.useId && (!lhs.document || !rhs.document)) {
if ((lhs as any)._id !== (rhs as any)._id) return false;
return true;
}
return textDocumentComparer.equals(lhs.document, rhs.document);
}
}
const textDocumentComparer = new TextDocumentComparer();
const textEditorComparer = new TextEditorComparer();
const uriComparer = new UriComparer();
export {
textDocumentComparer as TextDocumentComparer,
textEditorComparer as TextEditorComparer,
uriComparer as UriComparer
};

307
src/configuration.ts Normal file
View File

@@ -0,0 +1,307 @@
'use strict';
import { Commands } from './commands';
import { OutputLevel } from './logger';
export { ExtensionKey } from './constants';
export type BlameLineHighlightLocations = 'gutter' | 'line' | 'overviewRuler';
export const BlameLineHighlightLocations = {
Gutter: 'gutter' as BlameLineHighlightLocations,
Line: 'line' as BlameLineHighlightLocations,
OverviewRuler: 'overviewRuler' as BlameLineHighlightLocations
};
export type CodeLensCommand = 'gitlens.toggleFileBlame' | 'gitlens.showBlameHistory' | 'gitlens.showFileHistory' | 'gitlens.diffWithPrevious' | 'gitlens.showQuickCommitDetails' | 'gitlens.showQuickCommitFileDetails' | 'gitlens.showQuickFileHistory' | 'gitlens.showQuickRepoHistory';
export const CodeLensCommand = {
BlameAnnotate: Commands.ToggleFileBlame as CodeLensCommand,
ShowBlameHistory: Commands.ShowBlameHistory as CodeLensCommand,
ShowFileHistory: Commands.ShowFileHistory as CodeLensCommand,
DiffWithPrevious: Commands.DiffWithPrevious as CodeLensCommand,
ShowQuickCommitDetails: Commands.ShowQuickCommitDetails as CodeLensCommand,
ShowQuickCommitFileDetails: Commands.ShowQuickCommitFileDetails as CodeLensCommand,
ShowQuickFileHistory: Commands.ShowQuickFileHistory as CodeLensCommand,
ShowQuickCurrentBranchHistory: Commands.ShowQuickCurrentBranchHistory as CodeLensCommand
};
export type CodeLensLocations = 'document' | 'containers' | 'blocks' | 'custom';
export const CodeLensLocations = {
Document: 'document' as CodeLensLocations,
Containers: 'containers' as CodeLensLocations,
Blocks: 'blocks' as CodeLensLocations,
Custom: 'custom' as CodeLensLocations
};
export type FileAnnotationType = 'gutter' | 'hover';
export const FileAnnotationType = {
Gutter: 'gutter' as FileAnnotationType,
Hover: 'hover' as FileAnnotationType
};
export type LineAnnotationType = 'trailing' | 'hover';
export const LineAnnotationType = {
Trailing: 'trailing' as LineAnnotationType,
Hover: 'hover' as LineAnnotationType
};
export type StatusBarCommand = 'gitlens.toggleFileBlame' | 'gitlens.showBlameHistory' | 'gitlens.showFileHistory' | 'gitlens.toggleCodeLens' | 'gitlens.diffWithPrevious' | 'gitlens.diffWithWorking' | 'gitlens.showQuickCommitDetails' | 'gitlens.showQuickCommitFileDetails' | 'gitlens.showQuickFileHistory' | 'gitlens.showQuickRepoHistory';
export const StatusBarCommand = {
BlameAnnotate: Commands.ToggleFileBlame as StatusBarCommand,
ShowBlameHistory: Commands.ShowBlameHistory as StatusBarCommand,
ShowFileHistory: Commands.ShowFileHistory as CodeLensCommand,
DiffWithPrevious: Commands.DiffWithPrevious as StatusBarCommand,
DiffWithWorking: Commands.DiffWithWorking as StatusBarCommand,
ToggleCodeLens: Commands.ToggleCodeLens as StatusBarCommand,
ShowQuickCommitDetails: Commands.ShowQuickCommitDetails as StatusBarCommand,
ShowQuickCommitFileDetails: Commands.ShowQuickCommitFileDetails as StatusBarCommand,
ShowQuickFileHistory: Commands.ShowQuickFileHistory as StatusBarCommand,
ShowQuickCurrentBranchHistory: Commands.ShowQuickCurrentBranchHistory as StatusBarCommand
};
export interface IAdvancedConfig {
caching: {
enabled: boolean;
maxLines: number;
};
git: string;
gitignore: {
enabled: boolean;
};
maxQuickHistory: number;
menus: {
explorerContext: {
fileDiff: boolean;
history: boolean;
remote: boolean;
};
editorContext: {
blame: boolean;
copy: boolean;
details: boolean;
fileDiff: boolean;
history: boolean;
lineDiff: boolean;
remote: boolean;
};
editorTitle: {
blame: boolean;
fileDiff: boolean;
history: boolean;
status: boolean;
};
editorTitleContext: {
blame: boolean;
fileDiff: boolean;
history: boolean;
remote: boolean;
};
};
quickPick: {
closeOnFocusOut: boolean;
};
telemetry: {
enabled: boolean;
};
toggleWhitespace: {
enabled: boolean;
};
}
export interface ICodeLensLanguageLocation {
language: string | undefined;
locations: CodeLensLocations[];
customSymbols?: string[];
}
export interface IThemeConfig {
annotations: {
file: {
gutter: {
separateLines: boolean;
dark: {
backgroundColor: string | null;
foregroundColor: string;
uncommittedForegroundColor: string | null;
};
light: {
backgroundColor: string | null;
foregroundColor: string;
uncommittedForegroundColor: string | null;
};
};
hover: {
separateLines: boolean;
};
};
line: {
trailing: {
dark: {
backgroundColor: string | null;
foregroundColor: string;
};
light: {
backgroundColor: string | null;
foregroundColor: string;
};
};
};
};
lineHighlight: {
dark: {
backgroundColor: string;
overviewRulerColor: string;
};
light: {
backgroundColor: string;
overviewRulerColor: string;
};
};
}
export const themeDefaults: IThemeConfig = {
annotations: {
file: {
gutter: {
separateLines: true,
dark: {
backgroundColor: null,
foregroundColor: 'rgb(190, 190, 190)',
uncommittedForegroundColor: null
},
light: {
backgroundColor: null,
foregroundColor: 'rgb(116, 116, 116)',
uncommittedForegroundColor: null
}
},
hover: {
separateLines: false
}
},
line: {
trailing: {
dark: {
backgroundColor: null,
foregroundColor: 'rgba(153, 153, 153, 0.35)'
},
light: {
backgroundColor: null,
foregroundColor: 'rgba(153, 153, 153, 0.35)'
}
}
}
},
lineHighlight: {
dark: {
backgroundColor: 'rgba(0, 188, 242, 0.2)',
overviewRulerColor: 'rgba(0, 188, 242, 0.6)'
},
light: {
backgroundColor: 'rgba(0, 188, 242, 0.2)',
overviewRulerColor: 'rgba(0, 188, 242, 0.6)'
}
}
};
export interface IConfig {
annotations: {
file: {
gutter: {
format: string;
dateFormat: string;
compact: boolean;
heatmap: {
enabled: boolean;
location: 'left' | 'right';
};
hover: {
details: boolean;
wholeLine: boolean;
};
};
hover: {
heatmap: {
enabled: boolean;
};
wholeLine: boolean;
};
};
line: {
hover: {
details: boolean;
changes: boolean;
};
trailing: {
format: string;
dateFormat: string;
hover: {
changes: boolean;
details: boolean;
wholeLine: boolean;
};
};
};
};
blame: {
file: {
annotationType: FileAnnotationType;
lineHighlight: {
enabled: boolean;
locations: BlameLineHighlightLocations[];
};
};
line: {
enabled: boolean;
annotationType: LineAnnotationType;
};
};
codeLens: {
enabled: boolean;
recentChange: {
enabled: boolean;
command: CodeLensCommand;
};
authors: {
enabled: boolean;
command: CodeLensCommand;
};
locations: CodeLensLocations[];
customLocationSymbols: string[];
perLanguageLocations: ICodeLensLanguageLocation[];
debug: boolean;
};
statusBar: {
enabled: boolean;
alignment: 'left' | 'right';
command: StatusBarCommand;
format: string;
dateFormat: string;
};
strings: {
codeLens: {
unsavedChanges: {
recentChangeAndAuthors: string;
recentChangeOnly: string;
authorsOnly: string;
};
};
};
theme: IThemeConfig;
debug: boolean;
insiders: boolean;
outputLevel: OutputLevel;
advanced: IAdvancedConfig;
}

View File

@@ -1,15 +1,40 @@
export type Commands = 'git.action.showBlameHistory'; 'use strict';
export const Commands = {
ShowBlameHistory: 'git.action.showBlameHistory' as Commands
}
export type DocumentSchemes = 'gitblame'; export const ExtensionId = 'gitlens';
export const ExtensionKey = ExtensionId;
export const ExtensionOutputChannelName = 'GitLens';
export const QualifiedExtensionId = `eamodio.${ExtensionId}`;
export const ApplicationInsightsKey = 'a9c302f8-6483-4d01-b92c-c159c799c679';
export type BuiltInCommands = 'cursorMove' | 'editor.action.showReferences' | 'editor.action.toggleRenderWhitespace' | 'editorScroll' | 'revealLine' | 'setContext' | 'vscode.diff' | 'vscode.executeDocumentSymbolProvider' | 'vscode.executeCodeLensProvider' | 'vscode.open' | 'vscode.previewHtml' | 'workbench.action.closeActiveEditor' | 'workbench.action.nextEditor';
export const BuiltInCommands = {
CloseActiveEditor: 'workbench.action.closeActiveEditor' as BuiltInCommands,
CursorMove: 'cursorMove' as BuiltInCommands,
Diff: 'vscode.diff' as BuiltInCommands,
EditorScroll: 'editorScroll' as BuiltInCommands,
ExecuteDocumentSymbolProvider: 'vscode.executeDocumentSymbolProvider' as BuiltInCommands,
ExecuteCodeLensProvider: 'vscode.executeCodeLensProvider' as BuiltInCommands,
Open: 'vscode.open' as BuiltInCommands,
NextEditor: 'workbench.action.nextEditor' as BuiltInCommands,
PreviewHtml: 'vscode.previewHtml' as BuiltInCommands,
RevealLine: 'revealLine' as BuiltInCommands,
SetContext: 'setContext' as BuiltInCommands,
ShowReferences: 'editor.action.showReferences' as BuiltInCommands,
ToggleRenderWhitespace: 'editor.action.toggleRenderWhitespace' as BuiltInCommands
};
export type DocumentSchemes = 'file' | 'git' | 'gitlens-git';
export const DocumentSchemes = { export const DocumentSchemes = {
GitBlame: 'gitblame' as DocumentSchemes File: 'file' as DocumentSchemes,
} Git: 'git' as DocumentSchemes,
GitLensGit: 'gitlens-git' as DocumentSchemes
};
export type VsCodeCommands = 'vscode.executeDocumentSymbolProvider' | 'editor.action.showReferences'; export type WorkspaceState = 'repoPath' | 'suppressGitVersionWarning' | 'suppressUpdateNotice' | 'suppressWelcomeNotice';
export const VsCodeCommands = { export const WorkspaceState = {
ExecuteDocumentSymbolProvider: 'vscode.executeDocumentSymbolProvider' as VsCodeCommands, GitLensVersion: 'gitlensVersion' as WorkspaceState,
ShowReferences: 'editor.action.showReferences' as VsCodeCommands SuppressGitVersionWarning: 'suppressGitVersionWarning' as WorkspaceState,
} SuppressUpdateNotice: 'suppressUpdateNotice' as WorkspaceState,
SuppressWelcomeNotice: 'suppressWelcomeNotice' as WorkspaceState
};

View File

@@ -1,118 +0,0 @@
'use strict';
import {Disposable, EventEmitter, ExtensionContext, OverviewRulerLane, Range, TextEditor, TextEditorDecorationType, TextDocumentContentProvider, Uri, window, workspace} from 'vscode';
import {DocumentSchemes} from './constants';
import {gitGetVersionText} from './git';
import {fromGitBlameUri, IGitBlameUriData} from './gitBlameUri';
import * as moment from 'moment';
export default class GitBlameContentProvider implements TextDocumentContentProvider {
static scheme = DocumentSchemes.GitBlame;
private _blameDecoration: TextEditorDecorationType;
private _onDidChange = new EventEmitter<Uri>();
// private _subscriptions: Disposable;
// private _dataMap: Map<string, IGitBlameUriData>;
constructor(context: ExtensionContext) {
this._blameDecoration = window.createTextEditorDecorationType({
dark: {
backgroundColor: 'rgba(255, 255, 255, 0.15)',
gutterIconPath: context.asAbsolutePath('images/blame-dark.png'),
overviewRulerColor: 'rgba(255, 255, 255, 0.75)',
},
light: {
backgroundColor: 'rgba(0, 0, 0, 0.15)',
gutterIconPath: context.asAbsolutePath('images/blame-light.png'),
overviewRulerColor: 'rgba(0, 0, 0, 0.75)',
},
gutterIconSize: 'contain',
overviewRulerLane: OverviewRulerLane.Right,
isWholeLine: true
});
// this._dataMap = new Map();
// this._subscriptions = Disposable.from(
// window.onDidChangeActiveTextEditor(e => e ? console.log(e.document.uri) : console.log('active missing')),
// workspace.onDidOpenTextDocument(d => {
// let data = this._dataMap.get(d.uri.toString());
// if (!data) return;
// // TODO: This only works on the first load -- not after since it is cached
// this._tryAddBlameDecorations(d.uri, data);
// }),
// workspace.onDidCloseTextDocument(d => {
// this._dataMap.delete(d.uri.toString());
// })
// );
}
dispose() {
this._onDidChange.dispose();
// this._subscriptions && this._subscriptions.dispose();
}
get onDidChange() {
return this._onDidChange.event;
}
public update(uri: Uri) {
this._onDidChange.fire(uri);
}
provideTextDocumentContent(uri: Uri): string | Thenable<string> {
const data = fromGitBlameUri(uri);
// this._dataMap.set(uri.toString(), data);
//const editor = this._findEditor(Uri.file(join(data.repoPath, data.file)));
return gitGetVersionText(data.repoPath, data.sha, data.file).then(text => {
this.update(uri);
// TODO: This only works on the first load -- not after since it is cached
this._tryAddBlameDecorations(uri, data);
// TODO: This needs to move to selection somehow to show on the main file editor
//this._addBlameDecorations(editor, data);
return text;
});
// return gitGetVersionFile(data.repoPath, data.sha, data.file).then(dst => {
// let uri = Uri.parse(`file:${dst}`)
// return workspace.openTextDocument(uri).then(doc => {
// this.update(uri);
// return doc.getText();
// });
// });
}
private _findEditor(uri: Uri): TextEditor {
let uriString = uri.toString();
// TODO: This is a big hack :)
const matcher = (e: any) => (e && e._documentData && e._documentData._uri && e._documentData._uri.toString()) === uriString;
if (matcher(window.activeTextEditor)) {
return window.activeTextEditor;
}
return window.visibleTextEditors.find(matcher);
}
private _tryAddBlameDecorations(uri: Uri, data: IGitBlameUriData) {
// Needs to be on a timer for some reason because we won't find the editor otherwise -- is there an event?
let handle = setInterval(() => {
let editor = this._findEditor(uri);
if (editor) {
clearInterval(handle);
editor.setDecorations(this._blameDecoration, data.lines.map(l => {
return {
range: editor.document.validateRange(new Range(l.originalLine, 0, l.originalLine, 1000000)),
hoverMessage: `${moment(l.date).format('MMMM Do, YYYY hh:MMa')}\n${l.author}\n${l.sha}`
};
}));
}
}, 200);
}
// private _addBlameDecorations(editor: TextEditor, data: IGitBlameUriData) {
// editor.setDecorations(this._blameDecoration, data.lines.map(l => editor.document.validateRange(new Range(l.line, 0, l.line, 1000000))));
// }
}

View File

@@ -0,0 +1,441 @@
'use strict';
import { Functions, Objects } from './system';
import { DecorationOptions, DecorationRenderOptions, Disposable, ExtensionContext, Range, StatusBarAlignment, StatusBarItem, TextEditor, TextEditorDecorationType, TextEditorSelectionChangeEvent, window, workspace } from 'vscode';
import { AnnotationController } from './annotations/annotationController';
import { Annotations, endOfLineIndex } from './annotations/annotations';
import { Commands } from './commands';
import { TextEditorComparer } from './comparers';
import { FileAnnotationType, IConfig, LineAnnotationType, StatusBarCommand } from './configuration';
import { DocumentSchemes, ExtensionKey } from './constants';
import { BlameabilityChangeEvent, CommitFormatter, GitCommit, GitContextTracker, GitService, GitUri, IGitCommitLine } from './gitService';
const annotationDecoration: TextEditorDecorationType = window.createTextEditorDecorationType({
after: {
margin: '0 0 0 3em',
textDecoration: 'none'
}
} as DecorationRenderOptions);
export class CurrentLineController extends Disposable {
private _activeEditorLineDisposable: Disposable | undefined;
private _blameable: boolean;
private _config: IConfig;
private _currentLine: number = -1;
private _disposable: Disposable;
private _editor: TextEditor | undefined;
private _isAnnotating: boolean = false;
private _statusBarItem: StatusBarItem | undefined;
private _updateBlameDebounced: (line: number, editor: TextEditor) => Promise<void>;
private _uri: GitUri;
constructor(context: ExtensionContext, private git: GitService, private gitContextTracker: GitContextTracker, private annotationController: AnnotationController) {
super(() => this.dispose());
this._updateBlameDebounced = Functions.debounce(this._updateBlame, 250);
this._onConfigurationChanged();
const subscriptions: Disposable[] = [];
subscriptions.push(workspace.onDidChangeConfiguration(this._onConfigurationChanged, this));
subscriptions.push(git.onDidChangeGitCache(this._onGitCacheChanged, this));
subscriptions.push(annotationController.onDidToggleAnnotations(this._onAnnotationsToggled, this));
this._disposable = Disposable.from(...subscriptions);
}
dispose() {
this._clearAnnotations(this._editor, true);
this._activeEditorLineDisposable && this._activeEditorLineDisposable.dispose();
this._statusBarItem && this._statusBarItem.dispose();
this._disposable && this._disposable.dispose();
}
private _onConfigurationChanged() {
const cfg = workspace.getConfiguration().get<IConfig>(ExtensionKey)!;
let changed = false;
if (!Objects.areEquivalent(cfg.blame.line, this._config && this._config.blame.line) ||
!Objects.areEquivalent(cfg.annotations.line.trailing, this._config && this._config.annotations.line.trailing) ||
!Objects.areEquivalent(cfg.annotations.line.hover, this._config && this._config.annotations.line.hover) ||
!Objects.areEquivalent(cfg.theme.annotations.line.trailing, this._config && this._config.theme.annotations.line.trailing)) {
changed = true;
this._clearAnnotations(this._editor);
}
if (!Objects.areEquivalent(cfg.statusBar, this._config && this._config.statusBar)) {
changed = true;
if (cfg.statusBar.enabled) {
const alignment = cfg.statusBar.alignment !== 'left' ? StatusBarAlignment.Right : StatusBarAlignment.Left;
if (this._statusBarItem !== undefined && this._statusBarItem.alignment !== alignment) {
this._statusBarItem.dispose();
this._statusBarItem = undefined;
}
this._statusBarItem = this._statusBarItem || window.createStatusBarItem(alignment, alignment === StatusBarAlignment.Right ? 1000 : 0);
this._statusBarItem.command = cfg.statusBar.command;
}
else if (!cfg.statusBar.enabled && this._statusBarItem) {
this._statusBarItem.dispose();
this._statusBarItem = undefined;
}
}
this._config = cfg;
if (!changed) return;
const trackCurrentLine = cfg.statusBar.enabled || cfg.blame.line.enabled;
if (trackCurrentLine && !this._activeEditorLineDisposable) {
const subscriptions: Disposable[] = [];
subscriptions.push(window.onDidChangeActiveTextEditor(this._onActiveTextEditorChanged, this));
subscriptions.push(window.onDidChangeTextEditorSelection(this._onTextEditorSelectionChanged, this));
subscriptions.push(this.gitContextTracker.onDidBlameabilityChange(this._onBlameabilityChanged, this));
this._activeEditorLineDisposable = Disposable.from(...subscriptions);
}
else if (!trackCurrentLine && this._activeEditorLineDisposable) {
this._activeEditorLineDisposable.dispose();
this._activeEditorLineDisposable = undefined;
}
this._onActiveTextEditorChanged(window.activeTextEditor);
}
private isEditorBlameable(editor: TextEditor | undefined): boolean {
if (editor === undefined || editor.document === undefined) return false;
if (!this.git.isTrackable(editor.document.uri)) return false;
if (editor.document.isUntitled && editor.document.uri.scheme === DocumentSchemes.File) return false;
return this.git.isEditorBlameable(editor);
}
private async _onActiveTextEditorChanged(editor: TextEditor | undefined) {
this._currentLine = -1;
this._clearAnnotations(this._editor);
if (editor === undefined || !this.isEditorBlameable(editor)) {
this.clear(editor);
this._editor = undefined;
return;
}
this._blameable = editor !== undefined && editor.document !== undefined && !editor.document.isDirty;
this._editor = editor;
this._uri = await GitUri.fromUri(editor.document.uri, this.git);
const maxLines = this._config.advanced.caching.maxLines;
// If caching is on and the file is small enough -- kick off a blame for the whole file
if (this._config.advanced.caching.enabled && (maxLines <= 0 || editor.document.lineCount <= maxLines)) {
this.git.getBlameForFile(this._uri);
}
this._updateBlameDebounced(editor.selection.active.line, editor);
}
private _onBlameabilityChanged(e: BlameabilityChangeEvent) {
this._blameable = e.blameable;
if (!e.blameable || !this._editor) {
this.clear(e.editor);
return;
}
// Make sure this is for the editor we are tracking
if (!TextEditorComparer.equals(this._editor, e.editor)) return;
this._updateBlameDebounced(this._editor.selection.active.line, this._editor);
}
private _onAnnotationsToggled() {
this._onActiveTextEditorChanged(window.activeTextEditor);
}
private _onGitCacheChanged() {
this._onActiveTextEditorChanged(window.activeTextEditor);
}
private async _onTextEditorSelectionChanged(e: TextEditorSelectionChangeEvent): Promise<void> {
// Make sure this is for the editor we are tracking
if (!this._blameable || !TextEditorComparer.equals(this._editor, e.textEditor)) return;
const line = e.selections[0].active.line;
if (line === this._currentLine) return;
this._currentLine = line;
if (!this._uri && e.textEditor !== undefined) {
this._uri = await GitUri.fromUri(e.textEditor.document.uri, this.git);
}
this._clearAnnotations(e.textEditor);
this._updateBlameDebounced(line, e.textEditor);
}
private async _updateBlame(line: number, editor: TextEditor) {
line = line - this._uri.offset;
let commit: GitCommit | undefined = undefined;
let commitLine: IGitCommitLine | undefined = undefined;
// Since blame information isn't valid when there are unsaved changes -- don't show any status
if (this._blameable && line >= 0) {
const blameLine = await this.git.getBlameForLine(this._uri, line);
commitLine = blameLine === undefined ? undefined : blameLine.line;
commit = blameLine === undefined ? undefined : blameLine.commit;
}
if (commit !== undefined && commitLine !== undefined) {
this.show(commit, commitLine, editor);
}
else {
this.clear(editor);
}
}
async clear(editor: TextEditor | undefined) {
this._clearAnnotations(editor, true);
this._statusBarItem && this._statusBarItem.hide();
}
private async _clearAnnotations(editor: TextEditor | undefined, force: boolean = false) {
if (editor === undefined || (!this._isAnnotating && !force)) return;
editor.setDecorations(annotationDecoration, []);
this._isAnnotating = false;
if (!force) return;
// I have no idea why the decorators sometimes don't get removed, but if they don't try again with a tiny delay
await Functions.wait(1);
editor.setDecorations(annotationDecoration, []);
}
async show(commit: GitCommit, blameLine: IGitCommitLine, editor: TextEditor) {
// I have no idea why I need this protection -- but it happens
if (editor.document === undefined) return;
this._updateStatusBar(commit);
await this._updateAnnotations(commit, blameLine, editor);
}
async showAnnotations(editor: TextEditor, type: LineAnnotationType) {
if (editor === undefined) return;
const cfg = this._config.blame.line;
if (!cfg.enabled || cfg.annotationType !== type) {
cfg.enabled = true;
cfg.annotationType = type;
await this._clearAnnotations(editor);
await this._updateBlame(editor.selection.active.line, editor);
}
}
async toggleAnnotations(editor: TextEditor, type: LineAnnotationType) {
if (editor === undefined) return;
const cfg = this._config.blame.line;
cfg.enabled = !cfg.enabled;
cfg.annotationType = type;
await this._clearAnnotations(editor);
await this._updateBlame(editor.selection.active.line, editor);
}
private async _updateAnnotations(commit: GitCommit, blameLine: IGitCommitLine, editor: TextEditor) {
const cfg = this._config.blame.line;
if (!cfg.enabled) return;
const line = blameLine.line + this._uri.offset;
const decorationOptions: DecorationOptions[] = [];
let showChanges = false;
let showChangesStartIndex = 0;
let showChangesInStartingWhitespace = false;
let showDetails = false;
let showDetailsStartIndex = 0;
let showDetailsInStartingWhitespace = false;
switch (cfg.annotationType) {
case LineAnnotationType.Trailing: {
const cfgAnnotations = this._config.annotations.line.trailing;
showChanges = cfgAnnotations.hover.changes;
showDetails = cfgAnnotations.hover.details;
if (cfgAnnotations.hover.wholeLine) {
showChangesStartIndex = 0;
showChangesInStartingWhitespace = false;
showDetailsStartIndex = 0;
showDetailsInStartingWhitespace = false;
}
else {
showChangesStartIndex = endOfLineIndex;
showChangesInStartingWhitespace = true;
showDetailsStartIndex = endOfLineIndex;
showDetailsInStartingWhitespace = true;
}
const decoration = Annotations.trailing(commit, cfgAnnotations.format, cfgAnnotations.dateFormat, this._config.theme);
decoration.range = editor.document.validateRange(new Range(line, endOfLineIndex, line, endOfLineIndex));
decorationOptions.push(decoration);
break;
}
case LineAnnotationType.Hover: {
const cfgAnnotations = this._config.annotations.line.hover;
showChanges = cfgAnnotations.changes;
showChangesStartIndex = 0;
showChangesInStartingWhitespace = false;
showDetails = cfgAnnotations.details;
showDetailsStartIndex = 0;
showDetailsInStartingWhitespace = false;
break;
}
}
if (showDetails || showChanges) {
const annotationType = this.annotationController.getAnnotationType(editor);
const firstNonWhitespace = editor.document.lineAt(line).firstNonWhitespaceCharacterIndex;
switch (annotationType) {
case FileAnnotationType.Gutter: {
const cfgHover = this._config.annotations.file.gutter.hover;
if (cfgHover.details) {
showDetailsInStartingWhitespace = false;
if (cfgHover.wholeLine) {
// Avoid double annotations if we are showing the whole-file hover blame annotations
showDetails = false;
}
else {
if (showDetailsStartIndex === 0) {
showDetailsStartIndex = firstNonWhitespace === 0 ? 1 : firstNonWhitespace;
}
if (showChangesStartIndex === 0) {
showChangesInStartingWhitespace = true;
showChangesStartIndex = firstNonWhitespace === 0 ? 1 : firstNonWhitespace;
}
}
}
break;
}
case FileAnnotationType.Hover: {
const cfgHover = this._config.annotations.file.hover;
showDetailsInStartingWhitespace = false;
if (cfgHover.wholeLine) {
// Avoid double annotations if we are showing the whole-file hover blame annotations
showDetails = false;
showChangesStartIndex = 0;
}
else {
if (showDetailsStartIndex === 0) {
showDetailsStartIndex = firstNonWhitespace === 0 ? 1 : firstNonWhitespace;
}
if (showChangesStartIndex === 0) {
showChangesInStartingWhitespace = true;
showChangesStartIndex = firstNonWhitespace === 0 ? 1 : firstNonWhitespace;
}
}
break;
}
}
if (showDetails) {
// Get the full commit message -- since blame only returns the summary
let logCommit: GitCommit | undefined = undefined;
if (!commit.isUncommitted) {
logCommit = await this.git.getLogCommit(this._uri.repoPath, this._uri.fsPath, commit.sha);
}
// I have no idea why I need this protection -- but it happens
if (editor.document === undefined) return;
const decoration = Annotations.detailsHover(logCommit || commit);
decoration.range = editor.document.validateRange(new Range(line, showDetailsStartIndex, line, endOfLineIndex));
decorationOptions.push(decoration);
if (showDetailsInStartingWhitespace && showDetailsStartIndex !== 0) {
decorationOptions.push(Annotations.withRange(decoration, 0, firstNonWhitespace));
}
}
if (showChanges) {
const decoration = await Annotations.changesHover(commit, line, this._uri, this.git);
// I have no idea why I need this protection -- but it happens
if (editor.document === undefined) return;
decoration.range = editor.document.validateRange(new Range(line, showChangesStartIndex, line, endOfLineIndex));
decorationOptions.push(decoration);
if (showChangesInStartingWhitespace && showChangesStartIndex !== 0) {
decorationOptions.push(Annotations.withRange(decoration, 0, firstNonWhitespace));
}
}
}
if (decorationOptions.length) {
editor.setDecorations(annotationDecoration, decorationOptions);
this._isAnnotating = true;
}
}
private _updateStatusBar(commit: GitCommit) {
const cfg = this._config.statusBar;
if (!cfg.enabled || this._statusBarItem === undefined) return;
this._statusBarItem.text = `$(git-commit) ${CommitFormatter.fromTemplate(cfg.format, commit, cfg.dateFormat)}`;
switch (cfg.command) {
case StatusBarCommand.BlameAnnotate:
this._statusBarItem.tooltip = 'Toggle Blame Annotations';
break;
case StatusBarCommand.ShowBlameHistory:
this._statusBarItem.tooltip = 'Open Blame History Explorer';
break;
case StatusBarCommand.ShowFileHistory:
this._statusBarItem.tooltip = 'Open File History Explorer';
break;
case StatusBarCommand.DiffWithPrevious:
this._statusBarItem.command = Commands.DiffLineWithPrevious;
this._statusBarItem.tooltip = 'Compare Line Commit with Previous';
break;
case StatusBarCommand.DiffWithWorking:
this._statusBarItem.command = Commands.DiffLineWithWorking;
this._statusBarItem.tooltip = 'Compare Line Commit with Working Tree';
break;
case StatusBarCommand.ToggleCodeLens:
this._statusBarItem.tooltip = 'Toggle Git CodeLens';
break;
case StatusBarCommand.ShowQuickCommitDetails:
this._statusBarItem.tooltip = 'Show Commit Details';
break;
case StatusBarCommand.ShowQuickCommitFileDetails:
this._statusBarItem.tooltip = 'Show Line Commit Details';
break;
case StatusBarCommand.ShowQuickFileHistory:
this._statusBarItem.tooltip = 'Show File History';
break;
case StatusBarCommand.ShowQuickCurrentBranchHistory:
this._statusBarItem.tooltip = 'Show Branch History';
break;
}
this._statusBarItem.show();
}
}

View File

@@ -1,21 +0,0 @@
// 'use strict';
// import {CancellationToken, CodeLens, commands, DefinitionProvider, Position, Location, TextDocument, Uri} from 'vscode';
// import {GitCodeLens} from './codeLensProvider';
// export default class GitDefinitionProvider implements DefinitionProvider {
// public provideDefinition(document: TextDocument, position: Position, token: CancellationToken): Promise<Location> {
// return (commands.executeCommand('vscode.executeCodeLensProvider', document.uri) as Promise<CodeLens[]>).then(lenses => {
// let matches: CodeLens[] = [];
// lenses.forEach(lens => {
// if (lens instanceof GitCodeLens && lens.blameRange.contains(position)) {
// matches.push(lens);
// }
// });
// if (matches.length) {
// return new Location(Uri.parse(), position);
// }
// return null;
// });
// }
// }

View File

@@ -1,33 +1,300 @@
'use strict'; 'use strict';
import {commands, DocumentSelector, ExtensionContext, languages, workspace} from 'vscode'; import { Objects } from './system';
import GitCodeLensProvider from './codeLensProvider'; import { commands, ExtensionContext, extensions, languages, Uri, window, workspace } from 'vscode';
import GitContentProvider from './contentProvider'; import { AnnotationController } from './annotations/annotationController';
import {gitRepoPath} from './git'; import { CommandContext, setCommandContext } from './commands';
import {Commands, VsCodeCommands} from './constants'; import { CloseUnchangedFilesCommand, OpenChangedFilesCommand } from './commands';
import { OpenBranchInRemoteCommand, OpenCommitInRemoteCommand, OpenFileInRemoteCommand, OpenInRemoteCommand, OpenRepoInRemoteCommand } from './commands';
import { CopyMessageToClipboardCommand, CopyShaToClipboardCommand } from './commands';
import { DiffDirectoryCommand, DiffLineWithPreviousCommand, DiffLineWithWorkingCommand, DiffWithBranchCommand, DiffWithNextCommand, DiffWithPreviousCommand, DiffWithWorkingCommand} from './commands';
import { ShowFileBlameCommand, ShowLineBlameCommand, ToggleFileBlameCommand, ToggleLineBlameCommand } from './commands';
import { ShowBlameHistoryCommand, ShowFileHistoryCommand } from './commands';
import { ShowLastQuickPickCommand } from './commands';
import { ShowQuickBranchHistoryCommand, ShowQuickCurrentBranchHistoryCommand, ShowQuickFileHistoryCommand } from './commands';
import { ShowCommitSearchCommand, ShowQuickCommitDetailsCommand, ShowQuickCommitFileDetailsCommand } from './commands';
import { ShowQuickRepoStatusCommand, ShowQuickStashListCommand } from './commands';
import { StashApplyCommand, StashDeleteCommand, StashSaveCommand } from './commands';
import { ToggleCodeLensCommand } from './commands';
import { Keyboard } from './commands';
import { BlameLineHighlightLocations, CodeLensLocations, IConfig, LineAnnotationType } from './configuration';
import { ApplicationInsightsKey, BuiltInCommands, ExtensionKey, QualifiedExtensionId, WorkspaceState } from './constants';
import { CurrentLineController } from './currentLineController';
import { GitContentProvider } from './gitContentProvider';
import { GitContextTracker, GitService } from './gitService';
import { GitRevisionCodeLensProvider } from './gitRevisionCodeLensProvider';
import { Logger } from './logger';
import { Telemetry } from './telemetry';
// this method is called when your extension is activated // this method is called when your extension is activated
export function activate(context: ExtensionContext) { export async function activate(context: ExtensionContext) {
// Workspace not using a folder. No access to git repo. Logger.configure(context);
if (!workspace.rootPath) { Telemetry.configure(ApplicationInsightsKey);
console.warn('Git CodeLens inactive: no rootPath');
const gitlens = extensions.getExtension(QualifiedExtensionId)!;
const gitlensVersion = gitlens.packageJSON.version;
const rootPath = workspace.rootPath && workspace.rootPath.replace(/\\/g, '/');
Logger.log(`GitLens(v${gitlensVersion}) active: ${rootPath}`);
const cfg = workspace.getConfiguration().get<IConfig>(ExtensionKey)!;
const gitPath = cfg.advanced.git;
try {
await GitService.getGitPath(gitPath);
}
catch (ex) {
Logger.error(ex, 'Extension.activate');
if (ex.message.includes('Unable to find git')) {
await window.showErrorMessage(`GitLens was unable to find Git. Please make sure Git is installed. Also ensure that Git is either in the PATH, or that 'gitlens.advanced.git' is pointed to its installed location.`);
}
setCommandContext(CommandContext.Enabled, false);
return; return;
} }
console.log(`Git CodeLens active: ${workspace.rootPath}`); const repoPath = await GitService.getRepoPath(rootPath);
gitRepoPath(workspace.rootPath).then(repoPath => { const gitVersion = GitService.getGitVersion();
context.subscriptions.push(workspace.registerTextDocumentContentProvider(GitContentProvider.scheme, new GitContentProvider(context))); Logger.log(`Git version: ${gitVersion}`);
context.subscriptions.push(commands.registerCommand(Commands.ShowBlameHistory, (...args) => { const telemetryContext: { [id: string]: any } = Object.create(null);
return commands.executeCommand(VsCodeCommands.ShowReferences, ...args); telemetryContext.version = gitlensVersion;
})); telemetryContext['git.version'] = gitVersion;
Telemetry.setContext(telemetryContext);
const selector: DocumentSelector = { scheme: 'file' }; await migrateSettings(context);
context.subscriptions.push(languages.registerCodeLensProvider(selector, new GitCodeLensProvider(repoPath))); notifyOnUnsupportedGitVersion(context, gitVersion);
}).catch(reason => console.warn(reason)); notifyOnNewGitLensVersion(context, gitlensVersion);
await context.globalState.update(WorkspaceState.GitLensVersion, gitlensVersion);
const git = new GitService(context, repoPath);
context.subscriptions.push(git);
const gitContextTracker = new GitContextTracker(git);
context.subscriptions.push(gitContextTracker);
context.subscriptions.push(workspace.registerTextDocumentContentProvider(GitContentProvider.scheme, new GitContentProvider(context, git)));
context.subscriptions.push(languages.registerCodeLensProvider(GitRevisionCodeLensProvider.selector, new GitRevisionCodeLensProvider(context, git)));
const annotationController = new AnnotationController(context, git, gitContextTracker);
context.subscriptions.push(annotationController);
const currentLineController = new CurrentLineController(context, git, gitContextTracker, annotationController);
context.subscriptions.push(currentLineController);
context.subscriptions.push(new Keyboard());
context.subscriptions.push(new CloseUnchangedFilesCommand(git));
context.subscriptions.push(new OpenChangedFilesCommand(git));
context.subscriptions.push(new CopyMessageToClipboardCommand(git));
context.subscriptions.push(new CopyShaToClipboardCommand(git));
context.subscriptions.push(new DiffDirectoryCommand(git));
context.subscriptions.push(new DiffLineWithPreviousCommand(git));
context.subscriptions.push(new DiffLineWithWorkingCommand(git));
context.subscriptions.push(new DiffWithBranchCommand(git));
context.subscriptions.push(new DiffWithNextCommand(git));
context.subscriptions.push(new DiffWithPreviousCommand(git));
context.subscriptions.push(new DiffWithWorkingCommand(git));
context.subscriptions.push(new OpenBranchInRemoteCommand(git));
context.subscriptions.push(new OpenCommitInRemoteCommand(git));
context.subscriptions.push(new OpenFileInRemoteCommand(git));
context.subscriptions.push(new OpenInRemoteCommand());
context.subscriptions.push(new OpenRepoInRemoteCommand(git));
context.subscriptions.push(new ShowFileBlameCommand(annotationController));
context.subscriptions.push(new ShowLineBlameCommand(currentLineController));
context.subscriptions.push(new ToggleFileBlameCommand(annotationController));
context.subscriptions.push(new ToggleLineBlameCommand(currentLineController));
context.subscriptions.push(new ShowBlameHistoryCommand(git));
context.subscriptions.push(new ShowFileHistoryCommand(git));
context.subscriptions.push(new ShowLastQuickPickCommand());
context.subscriptions.push(new ShowQuickBranchHistoryCommand(git));
context.subscriptions.push(new ShowQuickCurrentBranchHistoryCommand(git));
context.subscriptions.push(new ShowQuickCommitDetailsCommand(git));
context.subscriptions.push(new ShowQuickCommitFileDetailsCommand(git));
context.subscriptions.push(new ShowCommitSearchCommand(git));
context.subscriptions.push(new ShowQuickFileHistoryCommand(git));
context.subscriptions.push(new ShowQuickRepoStatusCommand(git));
context.subscriptions.push(new ShowQuickStashListCommand(git));
context.subscriptions.push(new StashApplyCommand(git));
context.subscriptions.push(new StashDeleteCommand(git));
context.subscriptions.push(new StashSaveCommand(git));
context.subscriptions.push(new ToggleCodeLensCommand(git));
Telemetry.trackEvent('initialized', Objects.flatten(cfg, 'config', true));
} }
// this method is called when your extension is deactivated // this method is called when your extension is deactivated
export function deactivate() { export function deactivate() { }
async function migrateSettings(context: ExtensionContext) {
const previousVersion = context.globalState.get<string>(WorkspaceState.GitLensVersion);
if (previousVersion === undefined) return;
const [major] = previousVersion.split('.');
if (parseInt(major, 10) >= 4) return;
try {
const cfg = workspace.getConfiguration(ExtensionKey);
const prevCfg = workspace.getConfiguration().get<any>(ExtensionKey)!;
if (prevCfg.blame !== undefined && prevCfg.blame.annotation !== undefined) {
switch (prevCfg.blame.annotation.activeLine) {
case 'off':
await cfg.update('blame.line.enabled', false, true);
break;
case 'hover':
await cfg.update('blame.line.annotationType', LineAnnotationType.Hover, true);
break;
}
if (prevCfg.blame.annotation.activeLineDarkColor != null) {
await cfg.update('theme.annotations.line.trailing.dark.foregroundColor', prevCfg.blame.annotation.activeLineDarkColor, true);
}
if (prevCfg.blame.annotation.activeLineLightColor != null) {
await cfg.update('theme.annotations.line.trailing.light.foregroundColor', prevCfg.blame.annotation.activeLineLightColor, true);
}
switch (prevCfg.blame.annotation.highlight) {
case 'none':
await cfg.update('blame.file.lineHighlight.enabled', false);
break;
case 'gutter':
await cfg.update('blame.file.lineHighlight.locations', [BlameLineHighlightLocations.Gutter, BlameLineHighlightLocations.OverviewRuler], true);
break;
case 'line':
await cfg.update('blame.file.lineHighlight.locations', [BlameLineHighlightLocations.Line, BlameLineHighlightLocations.OverviewRuler], true);
break;
case 'both':
}
if (prevCfg.blame.annotation.dateFormat != null) {
await cfg.update('annotations.file.gutter.dateFormat', prevCfg.blame.annotation.dateFormat, true);
await cfg.update('annotations.line.trailing.dateFormat', prevCfg.blame.annotation.dateFormat, true);
}
}
if (prevCfg.codeLens !== undefined) {
switch (prevCfg.codeLens.visibility) {
case 'ondemand':
case 'off':
await cfg.update('codeLens.enabled', false);
}
switch (prevCfg.codeLens.location) {
case 'all':
await cfg.update('codeLens.locations', [CodeLensLocations.Document, CodeLensLocations.Containers, CodeLensLocations.Blocks], true);
break;
case 'document+containers':
await cfg.update('codeLens.locations', [CodeLensLocations.Document, CodeLensLocations.Containers], true);
break;
case 'document':
await cfg.update('codeLens.locations', [CodeLensLocations.Document], true);
break;
case 'custom':
await cfg.update('codeLens.locations', [CodeLensLocations.Custom], true);
break;
}
if (prevCfg.codeLens.locationCustomSymbols != null) {
await cfg.update('codeLens.customLocationSymbols', prevCfg.codeLens.locationCustomSymbols, true);
}
}
if ((prevCfg.menus && prevCfg.menus.diff && prevCfg.menus.diff.enabled) === false) {
await cfg.update('advanced.menus', {
editorContext: {
blame: true,
copy: true,
details: true,
fileDiff: false,
history: true,
lineDiff: false,
remote: true
},
editorTitle: {
blame: true,
fileDiff: false,
history: true,
remote: true,
status: true
},
editorTitleContext: {
blame: true,
fileDiff: false,
history: true,
remote: true
},
explorerContext: {
fileDiff: false,
history: true,
remote: true
}
}, true);
}
switch (prevCfg.statusBar && prevCfg.statusBar.date) {
case 'off':
await cfg.update('statusBar.format', '${author}', true);
break;
case 'absolute':
await cfg.update('statusBar.format', '${author}, ${date}', true);
break;
}
}
catch (ex) {
Logger.error(ex, 'migrateSettings');
}
finally {
window.showInformationMessage(`GitLens v4 adds many new settings and removes a few old ones, so please review your settings to ensure they are configured properly.`);
}
}
async function notifyOnNewGitLensVersion(context: ExtensionContext, version: string) {
if (context.globalState.get(WorkspaceState.SuppressUpdateNotice, false)) return;
const previousVersion = context.globalState.get<string>(WorkspaceState.GitLensVersion);
if (!context.globalState.get(WorkspaceState.SuppressWelcomeNotice, false)) {
await context.globalState.update(WorkspaceState.SuppressWelcomeNotice, true);
if (previousVersion === undefined) {
const result = await window.showInformationMessage(`Thank you for choosing GitLens! GitLens is powerful, feature rich, and highly configurable, so please be sure to view the docs and tailor it to suit your needs.`, 'View Docs');
if (result === 'View Docs') {
// TODO: Reset before release
// commands.executeCommand(BuiltInCommands.Open, Uri.parse('https://marketplace.visualstudio.com/items/eamodio.gitlens'));
commands.executeCommand(BuiltInCommands.Open, Uri.parse('https://github.com/eamodio/vscode-gitlens/blob/develop/README.md'));
}
return;
}
}
if (previousVersion) {
const [major, minor] = version.split('.');
const [prevMajor, prevMinor] = previousVersion.split('.');
if (major === prevMajor && minor === prevMinor) return;
}
const result = await window.showInformationMessage(`GitLens has been updated to v${version}`, 'View Release Notes', `Don't Show Again`);
if (result === 'View Release Notes') {
// TODO: Reset before release
// commands.executeCommand(BuiltInCommands.Open, Uri.parse('https://marketplace.visualstudio.com/items/eamodio.gitlens/changelog'));
commands.executeCommand(BuiltInCommands.Open, Uri.parse('https://github.com/eamodio/vscode-gitlens/blob/develop/CHANGELOG.md'));
}
else if (result === `Don't Show Again`) {
context.globalState.update(WorkspaceState.SuppressUpdateNotice, true);
}
}
async function notifyOnUnsupportedGitVersion(context: ExtensionContext, version: string) {
if (context.globalState.get(WorkspaceState.SuppressGitVersionWarning, false)) return;
// If git is less than v2.2.0
if (!GitService.validateGitVersion(2, 2)) {
const result = await window.showErrorMessage(`GitLens requires a newer version of Git (>= 2.2.0) than is currently installed (${version}). Please install a more recent version of Git.`, `Don't Show Again`);
if (result === `Don't Show Again`) {
context.globalState.update(WorkspaceState.SuppressGitVersionWarning, true);
}
}
} }

View File

@@ -1,75 +0,0 @@
'use strict';
import {basename, dirname, extname} from 'path';
import * as fs from 'fs';
import * as tmp from 'tmp';
import {spawnPromise} from 'spawn-rx';
export declare interface IGitBlameLine {
sha: string;
file: string;
originalLine: number;
author: string;
date: Date;
line: number;
//code: string;
}
export function gitRepoPath(cwd) {
return gitCommand(cwd, 'rev-parse', '--show-toplevel').then(data => data.replace(/\r?\n|\r/g, ''));
}
const blameMatcher = /^([\^0-9a-fA-F]{8})\s([\S]*)\s+([0-9\S]+)\s\((.*)\s([0-9]{4}-[0-9]{2}-[0-9]{2}\s[0-9]{2}:[0-9]{2}:[0-9]{2}\s[-|+][0-9]{4})\s+([0-9]+)\)(.*)$/gm;
export function gitBlame(fileName: string) {
console.log('git', 'blame', '-fnw', '--root', '--', fileName);
return gitCommand(dirname(fileName), 'blame', '-fnw', '--root', '--', fileName).then(data => {
let lines: Array<IGitBlameLine> = [];
let m: Array<string>;
while ((m = blameMatcher.exec(data)) != null) {
lines.push({
sha: m[1],
file: m[2].trim(),
originalLine: parseInt(m[3], 10) - 1,
author: m[4].trim(),
date: new Date(m[5]),
line: parseInt(m[6], 10) - 1
//code: m[7]
});
}
return lines;
});
}
export function gitGetVersionFile(repoPath: string, sha: string, source: string): Promise<string> {
return new Promise<string>((resolve, reject) => {
gitCommand(repoPath, 'show', `${sha.replace('^', '')}:${source.replace(/\\/g, '/')}`).then(data => {
let ext = extname(source);
tmp.file({ prefix: `${basename(source, ext)}-${sha}_`, postfix: ext }, (err, destination, fd, cleanupCallback) => {
if (err) {
reject(err);
return;
}
console.log("File: ", destination);
console.log("Filedescriptor: ", fd);
fs.appendFile(destination, data, err => {
if (err) {
reject(err);
return;
}
resolve(destination);
});
});
});
});
}
export function gitGetVersionText(repoPath: string, sha: string, source: string) {
console.log('git', 'show', `${sha}:${source.replace(/\\/g, '/')}`);
return gitCommand(repoPath, 'show', `${sha}:${source.replace(/\\/g, '/')}`);
}
function gitCommand(cwd: string, ...args) {
return spawnPromise('git', args, { cwd: cwd });
}

View File

@@ -0,0 +1,160 @@
'use strict';
import { Strings } from '../../system';
import { GitCommit } from '../models/commit';
import { IGitDiffLine } from '../models/diff';
import * as moment from 'moment';
export interface ICommitFormatOptions {
dateFormat?: string | null;
tokenOptions?: {
ago?: Strings.ITokenOptions;
author?: Strings.ITokenOptions;
authorAgo?: Strings.ITokenOptions;
date?: Strings.ITokenOptions;
message?: Strings.ITokenOptions;
};
}
export class CommitFormatter {
private _options: ICommitFormatOptions;
constructor(private commit: GitCommit, options?: ICommitFormatOptions) {
options = options || {};
if (options.tokenOptions == null) {
options.tokenOptions = {};
}
if (options.dateFormat == null) {
options.dateFormat = 'MMMM Do, YYYY h:MMa';
}
this._options = options;
}
get ago() {
const ago = moment(this.commit.date).fromNow();
return this._padOrTruncate(ago, this._options.tokenOptions!.ago);
}
get author() {
const author = this.commit.author;
return this._padOrTruncate(author, this._options.tokenOptions!.author);
}
get authorAgo() {
const authorAgo = `${this.commit.author}, ${moment(this.commit.date).fromNow()}`;
return this._padOrTruncate(authorAgo, this._options.tokenOptions!.authorAgo);
}
get date() {
const date = moment(this.commit.date).format(this._options.dateFormat!);
return this._padOrTruncate(date, this._options.tokenOptions!.date);
}
get id() {
return this.commit.shortSha;
}
get message() {
const message = this.commit.isUncommitted ? 'Uncommitted change' : this.commit.message;
return this._padOrTruncate(message, this._options.tokenOptions!.message);
}
get sha() {
return this.id;
}
private collapsableWhitespace: number = 0;
private _padOrTruncate(s: string, options: Strings.ITokenOptions | undefined) {
// NOTE: the collapsable whitespace logic relies on the javascript template evaluation to be left to right
if (options === undefined) {
options = {
truncateTo: undefined,
padDirection: 'left',
collapseWhitespace: false
};
}
let max = options.truncateTo;
if (max === undefined) {
if (this.collapsableWhitespace === 0) return s;
// If we have left over whitespace make sure it gets re-added
const diff = this.collapsableWhitespace - s.length;
this.collapsableWhitespace = 0;
if (diff <= 0) return s;
if (options.truncateTo === undefined) return s;
return Strings.padLeft(s, diff);
}
max += this.collapsableWhitespace;
this.collapsableWhitespace = 0;
const diff = max - s.length;
if (diff > 0) {
if (options.collapseWhitespace) {
this.collapsableWhitespace = diff;
}
if (options.padDirection === 'left') return Strings.padLeft(s, max);
if (options.collapseWhitespace) {
max -= diff;
}
return Strings.padRight(s, max);
}
if (diff < 0) return Strings.truncate(s, max);
return s;
}
static fromTemplate(template: string, commit: GitCommit, dateFormat: string | null): string;
static fromTemplate(template: string, commit: GitCommit, options?: ICommitFormatOptions): string;
static fromTemplate(template: string, commit: GitCommit, dateFormatOrOptions?: string | null | ICommitFormatOptions): string;
static fromTemplate(template: string, commit: GitCommit, dateFormatOrOptions?: string | null | ICommitFormatOptions): string {
let options: ICommitFormatOptions | undefined = undefined;
if (dateFormatOrOptions == null || typeof dateFormatOrOptions === 'string') {
const tokenOptions = Strings.getTokensFromTemplate(template)
.reduce((map, token) => {
map[token.key] = token.options;
return map;
}, {} as { [token: string]: ICommitFormatOptions });
options = {
dateFormat: dateFormatOrOptions,
tokenOptions: tokenOptions
};
}
else {
options = dateFormatOrOptions;
}
return Strings.interpolate(template, new CommitFormatter(commit, options));
}
static toHoverAnnotation(commit: GitCommit, dateFormat: string = 'MMMM Do, YYYY h:MMa'): string | string[] {
const message = commit.isUncommitted ? '' : `\n\n> ${commit.message.replace(/\n/g, '\n>\n> ')}`;
return `\`${commit.shortSha}\` &nbsp; __${commit.author}__, ${moment(commit.date).fromNow()} &nbsp; _(${moment(commit.date).format(dateFormat)})_${message}`;
}
static toHoverDiff(commit: GitCommit, previous: IGitDiffLine | undefined, current: IGitDiffLine | undefined): string | undefined {
if (previous === undefined && current === undefined) return undefined;
const codeDiff = this._getCodeDiff(previous, current);
return commit.isUncommitted
? `\`Changes\` &nbsp; \u2014 &nbsp; _uncommitted_\n${codeDiff}`
: `\`Changes\` &nbsp; \u2014 &nbsp; \`${commit.previousShortSha}\` \u2194 \`${commit.shortSha}\`\n${codeDiff}`;
}
private static _getCodeDiff(previous: IGitDiffLine | undefined, current: IGitDiffLine | undefined): string {
return `\`\`\`
- ${previous === undefined ? '' : previous.line.trim()}
+ ${current === undefined ? '' : current.line.trim()}
\`\`\``;
}
}

345
src/git/git.ts Normal file
View File

@@ -0,0 +1,345 @@
'use strict';
import { findGitPath, IGit } from './gitLocator';
import { Logger } from '../logger';
import { spawnPromise } from 'spawn-rx';
import * as fs from 'fs';
import * as path from 'path';
import * as tmp from 'tmp';
import * as iconv from 'iconv-lite';
export { IGit };
export * from './models/models';
export * from './parsers/blameParser';
export * from './parsers/diffParser';
export * from './parsers/logParser';
export * from './parsers/stashParser';
export * from './parsers/statusParser';
export * from './remotes/provider';
let git: IGit;
// `--format=%H -%nauthor %an%nauthor-date %ai%ncommitter %cn%ncommitter-date %ci%nparents %P%nsummary %B%nfilename ?`
const defaultLogParams = [`log`, `--name-status`, `--full-history`, `-M`, `--date=iso8601`, `--format=%H -%nauthor %an%nauthor-date %ai%nparents %P%nsummary %B%nfilename ?`];
const defaultStashParams = [`stash`, `list`, `--name-status`, `--full-history`, `-M`, `--format=%H -%nauthor-date %ai%nreflog-selector %gd%nsummary %B%nfilename ?`];
let defaultEncoding = 'utf8';
export function setDefaultEncoding(encoding: string) {
defaultEncoding = iconv.encodingExists(encoding) ? encoding : 'utf8';
}
const GitWarnings = [
/Not a git repository/,
/is outside repository/,
/no such path/,
/does not have any commits/
];
async function gitCommand(options: { cwd: string, encoding?: string }, ...args: any[]) {
try {
// Fixes https://github.com/eamodio/vscode-gitlens/issues/73
// See https://stackoverflow.com/questions/4144417/how-to-handle-asian-characters-in-file-names-in-git-on-os-x
args.splice(0, 0, '-c', 'core.quotepath=false');
const opts = { encoding: 'utf8', ...options };
const s = await spawnPromise(git.path, args, { cwd: options.cwd, encoding: (opts.encoding === 'utf8') ? 'utf8' : 'binary' });
Logger.log('git', ...args, ` cwd='${options.cwd}'`);
if (opts.encoding === 'utf8' || opts.encoding === 'binary') return s;
return iconv.decode(Buffer.from(s, 'binary'), opts.encoding);
}
catch (ex) {
const msg = ex && ex.toString();
if (msg) {
for (const warning of GitWarnings) {
if (warning.test(msg)) {
Logger.warn('git', ...args, ` cwd='${options.cwd}'`, msg && `\n ${msg.replace(/\r?\n|\r/g, ' ')}`);
return '';
}
}
}
Logger.error(ex, 'git', ...args, ` cwd='${options.cwd}'`, msg && `\n ${msg.replace(/\r?\n|\r/g, ' ')}`);
throw ex;
}
}
export class Git {
static shaRegex = /^[0-9a-f]{40}( -)?$/;
static uncommittedRegex = /^[0]+$/;
static gitInfo(): IGit {
return git;
}
static async getGitPath(gitPath?: string): Promise<IGit> {
git = await findGitPath(gitPath);
Logger.log(`Git found: ${git.version} @ ${git.path === 'git' ? 'PATH' : git.path}`);
return git;
}
static async getRepoPath(cwd: string | undefined) {
if (cwd === undefined) return '';
const data = await gitCommand({ cwd }, 'rev-parse', '--show-toplevel');
if (!data) return '';
return data.replace(/\r?\n|\r/g, '').replace(/\\/g, '/');
}
static async getVersionedFile(repoPath: string | undefined, fileName: string, branchOrSha: string) {
const data = await Git.show(repoPath, fileName, branchOrSha, 'binary');
const suffix = Git.isSha(branchOrSha) ? branchOrSha.substring(0, 8) : branchOrSha;
const ext = path.extname(fileName);
return new Promise<string>((resolve, reject) => {
tmp.file({ prefix: `${path.basename(fileName, ext)}-${suffix}__`, postfix: ext },
(err, destination, fd, cleanupCallback) => {
if (err) {
reject(err);
return;
}
Logger.log(`getVersionedFile('${repoPath}', '${fileName}', ${branchOrSha}); destination=${destination}`);
fs.appendFile(destination, data, { encoding: 'binary' }, err => {
if (err) {
reject(err);
return;
}
resolve(destination);
});
});
});
}
static isSha(sha: string) {
return Git.shaRegex.test(sha);
}
static isUncommitted(sha: string) {
return Git.uncommittedRegex.test(sha);
}
static normalizePath(fileName: string, repoPath?: string) {
return fileName && fileName.replace(/\\/g, '/');
}
static splitPath(fileName: string, repoPath: string | undefined, extract: boolean = true): [string, string] {
if (repoPath) {
fileName = this.normalizePath(fileName);
repoPath = this.normalizePath(repoPath);
const normalizedRepoPath = (repoPath.endsWith('/') ? repoPath : `${repoPath}/`).toLowerCase();
if (fileName.toLowerCase().startsWith(normalizedRepoPath)) {
fileName = fileName.substring(normalizedRepoPath.length);
}
}
else {
repoPath = this.normalizePath(extract ? path.dirname(fileName) : repoPath!);
fileName = this.normalizePath(extract ? path.basename(fileName) : fileName);
}
return [ fileName, repoPath ];
}
static validateVersion(major: number, minor: number): boolean {
const [gitMajor, gitMinor] = git.version.split('.');
return (parseInt(gitMajor, 10) >= major && parseInt(gitMinor, 10) >= minor);
}
// Git commands
static blame(repoPath: string | undefined, fileName: string, sha?: string, startLine?: number, endLine?: number) {
const [file, root] = Git.splitPath(fileName, repoPath);
const params = [`blame`, `--root`, `--incremental`];
if (startLine != null && endLine != null) {
params.push(`-L ${startLine},${endLine}`);
}
if (sha) {
params.push(sha);
}
return gitCommand({ cwd: root }, ...params, `--`, file);
}
static branch(repoPath: string, all: boolean) {
const params = [`branch`];
if (all) {
params.push(`-a`);
}
return gitCommand({ cwd: repoPath }, ...params);
}
static async config_get(key: string, repoPath?: string) {
try {
return await gitCommand({ cwd: repoPath || '' }, `config`, `--get`, key);
}
catch (ex) {
return '';
}
}
static diff(repoPath: string, fileName: string, sha1?: string, sha2?: string, encoding?: string) {
const params = [`diff`, `--diff-filter=M`, `-M`];
if (sha1) {
params.push(sha1);
}
if (sha2) {
params.push(sha2);
}
return gitCommand({ cwd: repoPath, encoding: encoding || defaultEncoding }, ...params, '--', fileName);
}
static diff_nameStatus(repoPath: string, sha1?: string, sha2?: string) {
const params = [`diff`, `--name-status`, `-M`];
if (sha1) {
params.push(sha1);
}
if (sha2) {
params.push(sha2);
}
return gitCommand({ cwd: repoPath }, ...params);
}
static difftool_dirDiff(repoPath: string, sha1: string, sha2?: string) {
const params = [`difftool`, `--dir-diff`, sha1];
if (sha2) {
params.push(sha2);
}
return gitCommand({ cwd: repoPath }, ...params);
}
static log(repoPath: string, sha?: string, maxCount?: number, reverse: boolean = false) {
const params = [...defaultLogParams, `-m`];
if (maxCount && !reverse) {
params.push(`-n${maxCount}`);
}
if (sha) {
if (reverse) {
params.push(`--reverse`);
params.push(`--ancestry-path`);
params.push(`${sha}..HEAD`);
}
else {
params.push(sha);
}
}
return gitCommand({ cwd: repoPath }, ...params);
}
static log_file(repoPath: string, fileName: string, sha?: string, maxCount?: number, reverse: boolean = false, startLine?: number, endLine?: number) {
const [file, root] = Git.splitPath(fileName, repoPath);
const params = [...defaultLogParams, `--follow`];
if (maxCount && !reverse) {
params.push(`-n${maxCount}`);
}
// If we are looking for a specific sha don't exclude merge commits
if (!sha || maxCount! > 2) {
params.push(`--no-merges`);
}
else {
params.push(`-m`);
}
if (sha) {
if (reverse) {
params.push(`--reverse`);
params.push(`--ancestry-path`);
params.push(`${sha}..HEAD`);
}
else {
params.push(sha);
}
}
if (startLine != null && endLine != null) {
params.push(`-L ${startLine},${endLine}:${file}`);
}
params.push(`--`);
params.push(file);
return gitCommand({ cwd: root }, ...params);
}
static log_search(repoPath: string, search: string[] = [], maxCount?: number) {
const params = [...defaultLogParams, `-m`, `-i`];
if (maxCount) {
params.push(`-n${maxCount}`);
}
return gitCommand({ cwd: repoPath }, ...params, ...search);
}
static async ls_files(repoPath: string, fileName: string): Promise<string> {
try {
return await gitCommand({ cwd: repoPath }, 'ls-files', fileName);
}
catch (ex) {
return '';
}
}
static remote(repoPath: string): Promise<string> {
return gitCommand({ cwd: repoPath }, 'remote', '-v');
}
static remote_url(repoPath: string, remote: string): Promise<string> {
return gitCommand({ cwd: repoPath }, 'remote', 'get-url', remote);
}
static show(repoPath: string | undefined, fileName: string, branchOrSha: string, encoding?: string) {
const [file, root] = Git.splitPath(fileName, repoPath);
branchOrSha = branchOrSha.replace('^', '');
if (Git.isUncommitted(branchOrSha)) return Promise.reject(new Error(`sha=${branchOrSha} is uncommitted`));
return gitCommand({ cwd: root, encoding: encoding || defaultEncoding }, 'show', `${branchOrSha}:./${file}`);
}
static stash_apply(repoPath: string, stashName: string, deleteAfter: boolean) {
if (!stashName) return undefined;
return gitCommand({ cwd: repoPath }, 'stash', deleteAfter ? 'pop' : 'apply', stashName);
}
static stash_delete(repoPath: string, stashName: string) {
if (!stashName) return undefined;
return gitCommand({ cwd: repoPath }, 'stash', 'drop', stashName);
}
static stash_list(repoPath: string) {
return gitCommand({ cwd: repoPath }, ...defaultStashParams);
}
static stash_save(repoPath: string, message?: string, unstagedOnly: boolean = false) {
const params = [`stash`, `save`, `--include-untracked`];
if (unstagedOnly) {
params.push(`--keep-index`);
}
if (message) {
params.push(message);
}
return gitCommand({ cwd: repoPath }, ...params);
}
static status(repoPath: string, porcelainVersion: number = 1): Promise<string> {
const porcelain = porcelainVersion >= 2 ? `--porcelain=v${porcelainVersion}` : '--porcelain';
return gitCommand({ cwd: repoPath }, 'status', porcelain, '--branch');
}
static status_file(repoPath: string, fileName: string, porcelainVersion: number = 1): Promise<string> {
const [file, root] = Git.splitPath(fileName, repoPath);
const porcelain = porcelainVersion >= 2 ? `--porcelain=v${porcelainVersion}` : '--porcelain';
return gitCommand({ cwd: root }, 'status', porcelain, file);
}
}

View File

@@ -0,0 +1,164 @@
'use strict';
import { Disposable, Event, EventEmitter, TextDocument, TextDocumentChangeEvent, TextEditor, window, workspace } from 'vscode';
import { CommandContext, setCommandContext } from '../commands';
import { TextDocumentComparer } from '../comparers';
import { GitService, GitUri } from '../gitService';
import { Logger } from '../logger';
export interface BlameabilityChangeEvent {
blameable: boolean;
editor: TextEditor | undefined;
}
export class GitContextTracker extends Disposable {
private _onDidBlameabilityChange = new EventEmitter<BlameabilityChangeEvent>();
get onDidBlameabilityChange(): Event<BlameabilityChangeEvent> {
return this._onDidBlameabilityChange.event;
}
private _disposable: Disposable;
private _documentChangeDisposable: Disposable | undefined;
private _editor: TextEditor | undefined;
private _gitEnabled: boolean;
private _isBlameable: boolean;
constructor(private git: GitService) {
super(() => this.dispose());
const subscriptions: Disposable[] = [];
subscriptions.push(window.onDidChangeActiveTextEditor(this._onActiveTextEditorChanged, this));
subscriptions.push(workspace.onDidChangeConfiguration(this._onConfigurationChanged, this));
subscriptions.push(workspace.onDidSaveTextDocument(this._onTextDocumentSaved, this));
subscriptions.push(this.git.onDidBlameFail(this._onBlameFailed, this));
this._disposable = Disposable.from(...subscriptions);
setCommandContext(CommandContext.IsRepository, !!this.git.repoPath);
this._onConfigurationChanged();
this._onActiveTextEditorChanged(window.activeTextEditor);
}
dispose() {
this._disposable && this._disposable.dispose();
this._documentChangeDisposable && this._documentChangeDisposable.dispose();
}
_onConfigurationChanged() {
const gitEnabled = workspace.getConfiguration('git').get<boolean>('enabled', true);
if (this._gitEnabled !== gitEnabled) {
this._gitEnabled = gitEnabled;
setCommandContext(CommandContext.Enabled, gitEnabled);
this._onActiveTextEditorChanged(window.activeTextEditor);
}
}
private _onActiveTextEditorChanged(editor: TextEditor | undefined) {
this._editor = editor;
this._updateContext(this._gitEnabled ? editor : undefined);
this._subscribeToDocumentChanges();
}
private _onBlameFailed(key: string) {
if (this._editor === undefined || this._editor.document === undefined || this._editor.document.uri === undefined) return;
if (key !== this.git.getCacheEntryKey(this._editor.document.uri)) return;
this._updateBlameability(false);
}
private _onTextDocumentChanged(e: TextDocumentChangeEvent) {
if (!TextDocumentComparer.equals(this._editor && this._editor.document, e && e.document)) return;
// Can't unsubscribe here because undo doesn't trigger any other event
// this._unsubscribeToDocumentChanges();
// this.updateBlameability(false);
// TODO: Rework this once https://github.com/Microsoft/vscode/issues/27231 is released in v1.13
// We have to defer because isDirty is not reliable inside this event
setTimeout(() => this._updateBlameability(!e.document.isDirty), 1);
}
private _onTextDocumentSaved(e: TextDocument) {
if (!TextDocumentComparer.equals(this._editor && this._editor.document, e)) return;
// Don't need to resubscribe as we aren't unsubscribing on document changes anymore
// this._subscribeToDocumentChanges();
this._updateBlameability(!e.isDirty);
}
private _subscribeToDocumentChanges() {
this._unsubscribeToDocumentChanges();
this._documentChangeDisposable = workspace.onDidChangeTextDocument(this._onTextDocumentChanged, this);
}
private _unsubscribeToDocumentChanges() {
this._documentChangeDisposable && this._documentChangeDisposable.dispose();
this._documentChangeDisposable = undefined;
}
private async _updateContext(editor: TextEditor | undefined) {
try {
const gitUri = editor === undefined ? undefined : await GitUri.fromUri(editor.document.uri, this.git);
await Promise.all([
this._updateEditorContext(gitUri, editor),
this._updateContextHasRemotes(gitUri)
]);
}
catch (ex) {
Logger.error(ex, 'GitEditorTracker._updateContext');
}
}
private async _updateContextHasRemotes(uri: GitUri | undefined) {
try {
let hasRemotes = false;
if (uri && this.git.isTrackable(uri)) {
const repoPath = uri.repoPath || this.git.repoPath;
if (repoPath) {
const remotes = await this.git.getRemotes(repoPath);
hasRemotes = remotes.length !== 0;
}
}
setCommandContext(CommandContext.HasRemotes, hasRemotes);
}
catch (ex) {
Logger.error(ex, 'GitEditorTracker._updateContextHasRemotes');
}
}
private async _updateEditorContext(uri: GitUri | undefined, editor: TextEditor | undefined) {
try {
const tracked = uri === undefined ? false : await this.git.isTracked(uri);
setCommandContext(CommandContext.IsTracked, tracked);
let blameable = tracked && (editor !== undefined && editor.document !== undefined && !editor.document.isDirty);
if (blameable) {
blameable = uri === undefined ? false : await this.git.getBlameability(uri);
}
this._updateBlameability(blameable, true);
}
catch (ex) {
Logger.error(ex, 'GitEditorTracker._updateEditorContext');
}
}
private _updateBlameability(blameable: boolean, force: boolean = false) {
if (!force && this._isBlameable === blameable) return;
try {
setCommandContext(CommandContext.IsBlameable, blameable);
this._onDidBlameabilityChange.fire({
blameable: blameable,
editor: this._editor
});
}
catch (ex) {
Logger.error(ex, 'GitEditorTracker._updateBlameability');
}
}
}

80
src/git/gitLocator.ts Normal file
View File

@@ -0,0 +1,80 @@
'use strict';
import { findActualExecutable, spawnPromise } from 'spawn-rx';
import * as path from 'path';
export interface IGit {
path: string;
version: string;
}
function parseVersion(raw: string): string {
return raw.replace(/^git version /, '');
}
async function findSpecificGit(path: string): Promise<IGit> {
const version = await spawnPromise(path, ['--version']);
// If needed, let's update our path to avoid the search on every command
if (!path || path === 'git') {
path = findActualExecutable(path, ['--version']).cmd;
}
return {
path,
version: parseVersion(version.trim())
};
}
async function findGitDarwin(): Promise<IGit> {
try {
let path = await spawnPromise('which', ['git']);
path = path.replace(/^\s+|\s+$/g, '');
if (path !== '/usr/bin/git') {
return findSpecificGit(path);
}
try {
await spawnPromise('xcode-select', ['-p']);
return findSpecificGit(path);
}
catch (ex) {
if (ex.code === 2) {
return Promise.reject(new Error('Unable to find git'));
}
return findSpecificGit(path);
}
}
catch (ex) {
return Promise.reject(new Error('Unable to find git'));
}
}
function findSystemGitWin32(basePath: string): Promise<IGit> {
if (!basePath) return Promise.reject(new Error('Unable to find git'));
return findSpecificGit(path.join(basePath, 'Git', 'cmd', 'git.exe'));
}
function findGitWin32(): Promise<IGit> {
return findSystemGitWin32(process.env['ProgramW6432'])
.then(null, () => findSystemGitWin32(process.env['ProgramFiles(x86)']))
.then(null, () => findSystemGitWin32(process.env['ProgramFiles']))
.then(null, () => findSpecificGit('git'));
}
export async function findGitPath(path?: string): Promise<IGit> {
try {
return await findSpecificGit(path || 'git');
}
catch (ex) {
try {
switch (process.platform) {
case 'darwin': return await findGitDarwin();
case 'win32': return await findGitWin32();
default: return Promise.reject('Unable to find git');
}
}
catch (ex) {
return Promise.reject(new Error('Unable to find git'));
}
}
}

121
src/git/gitUri.ts Normal file
View File

@@ -0,0 +1,121 @@
'use strict';
import { Uri } from 'vscode';
import { DocumentSchemes } from '../constants';
import { GitService, IGitStatusFile } from '../gitService';
import * as path from 'path';
export class GitUri extends Uri {
offset: number;
repoPath?: string | undefined;
sha?: string | undefined;
constructor(uri?: Uri, commit?: IGitCommitInfo);
constructor(uri?: Uri, repoPath?: string);
constructor(uri?: Uri, commitOrRepoPath?: IGitCommitInfo | string);
constructor(uri?: Uri, commitOrRepoPath?: IGitCommitInfo | string) {
super();
if (!uri) return;
const base = this as any;
base._scheme = uri.scheme;
base._authority = uri.authority;
base._path = uri.path;
base._query = uri.query;
base._fragment = uri.fragment;
this.offset = 0;
if (uri.scheme === DocumentSchemes.GitLensGit) {
const data = GitService.fromGitContentUri(uri);
base._fsPath = path.resolve(data.repoPath, data.originalFileName || data.fileName);
this.offset = (data.decoration && data.decoration.split('\n').length) || 0;
if (!GitService.isUncommitted(data.sha)) {
this.sha = data.sha;
this.repoPath = data.repoPath;
}
}
else if (commitOrRepoPath) {
if (typeof commitOrRepoPath === 'string') {
this.repoPath = commitOrRepoPath;
}
else {
const commit = commitOrRepoPath;
base._fsPath = path.resolve(commit.repoPath, commit.originalFileName || commit.fileName);
if (commit.repoPath !== undefined) {
this.repoPath = commit.repoPath;
}
if (commit.sha !== undefined && !GitService.isUncommitted(commit.sha)) {
this.sha = commit.sha;
}
}
}
}
get shortSha() {
return this.sha && this.sha.substring(0, 8);
}
fileUri() {
return Uri.file(this.sha ? this.path : this.fsPath);
}
getFormattedPath(separator: string = ' \u00a0\u2022\u00a0 '): string {
let directory = path.dirname(this.fsPath);
if (this.repoPath) {
directory = path.relative(this.repoPath, directory);
}
directory = GitService.normalizePath(directory);
return (!directory || directory === '.')
? path.basename(this.fsPath)
: `${path.basename(this.fsPath)}${separator}${directory}`;
}
getRelativePath(): string {
return GitService.normalizePath(path.relative(this.repoPath || '', this.fsPath));
}
static async fromUri(uri: Uri, git: GitService) {
if (uri instanceof GitUri) return uri;
if (!git.isTrackable(uri)) return new GitUri(uri, git.repoPath);
// If this is a git uri, assume it is showing the most recent commit
if (uri.scheme === DocumentSchemes.Git) {
const commit = await git.getLogCommit(undefined, uri.fsPath);
if (commit !== undefined) return new GitUri(uri, commit);
}
const gitUri = git.getGitUriForFile(uri);
if (gitUri) return gitUri;
return new GitUri(uri, (await git.getRepoPathFromFile(uri.fsPath)) || git.repoPath);
}
static fromFileStatus(status: IGitStatusFile, repoPath: string, original?: boolean): GitUri;
static fromFileStatus(status: IGitStatusFile, commit: IGitCommitInfo, original?: boolean): GitUri;
static fromFileStatus(status: IGitStatusFile, repoPathOrCommit: string | IGitCommitInfo, original: boolean = false): GitUri {
const repoPath = typeof repoPathOrCommit === 'string' ? repoPathOrCommit : repoPathOrCommit.repoPath;
const uri = Uri.file(path.resolve(repoPath, original ? status.originalFileName || status.fileName : status.fileName));
return new GitUri(uri, repoPathOrCommit);
}
}
export interface IGitCommitInfo {
fileName: string;
repoPath: string;
sha?: string;
originalFileName?: string;
}
export interface IGitUriData {
sha: string;
fileName: string;
repoPath: string;
originalFileName?: string;
index?: number;
decoration?: string;
}

25
src/git/models/blame.ts Normal file
View File

@@ -0,0 +1,25 @@
'use strict';
import { GitCommit, IGitAuthor, IGitCommitLine } from './commit';
export interface IGitBlame {
repoPath: string;
authors: Map<string, IGitAuthor>;
commits: Map<string, GitCommit>;
lines: IGitCommitLine[];
}
export interface IGitBlameLine {
author: IGitAuthor;
commit: GitCommit;
line: IGitCommitLine;
}
export interface IGitBlameLines extends IGitBlame {
allLines: IGitCommitLine[];
}
export interface IGitBlameCommitLines {
author: IGitAuthor;
commit: GitCommit;
lines: IGitCommitLine[];
}

29
src/git/models/branch.ts Normal file
View File

@@ -0,0 +1,29 @@
'use strict';
export class GitBranch {
current: boolean;
name: string;
remote: boolean;
constructor(branch: string) {
branch = branch.trim();
if (branch.startsWith('* ')) {
branch = branch.substring(2);
this.current = true;
}
if (branch.startsWith('remotes/')) {
branch = branch.substring(8);
this.remote = true;
}
const index = branch.indexOf(' ');
if (index !== -1) {
branch = branch.substring(0, index);
}
this.name = branch;
}
}

Some files were not shown because too many files have changed in this diff Show More