92 Commits

Author SHA1 Message Date
Eric Amodio
6ed0778a02 Preps v4.5.0-beta 2017-08-27 15:44:04 -04:00
Eric Amodio
68c17206e5 Adds a file history explorer view 2017-08-27 04:22:49 -04:00
Eric Amodio
d6c84061f9 Defaults stashes format to ${filePath}
Adds message when there are no stashes
Cleans up the stash explorer
2017-08-27 04:15:21 -04:00
Eric Amodio
208d549c1c Reworks ExplorerNode base class
Adds TextExplorerNode for messages
2017-08-27 04:10:02 -04:00
Eric Amodio
6518279937 Adds ${filePath} support to status file formatting 2017-08-27 03:58:00 -04:00
Eric Amodio
c03634fafc Splits code lens out of GitService 2017-08-26 15:36:32 -04:00
Eric Amodio
3ba1fe18ee Updates dependencies 2017-08-26 15:08:19 -04:00
Eric Amodio
4db2b116c2 Preps v4.4.1 2017-08-23 21:01:16 -04:00
Eric Amodio
b40579f4b2 Updates dependencies 2017-08-23 21:01:01 -04:00
Eric Amodio
15c981ba06 Fixes #114 - Stylus file jumpiness 2017-08-23 21:00:29 -04:00
Eric Amodio
c2f78a7242 Preps v4.4.0 2017-08-18 11:58:23 -04:00
Eric Amodio
d3013a03d4 Preps v4.4.0-beta 2017-08-17 03:30:28 -04:00
Eric Amodio
323148d9ab Disables whitespace toggling by default 2017-08-17 03:24:09 -04:00
Eric Amodio
03fefaad3b Disables current line blame annotations when debugging 2017-08-17 03:22:07 -04:00
Eric Amodio
f11c00ceda Changes the annotation line separator rendering 2017-08-17 02:23:35 -04:00
Eric Amodio
0e338308c6 Fixes jumpiness when opening a diff 2017-08-17 01:18:00 -04:00
Eric Amodio
e39bd5dad3 Removes setting & command to show/hide stashes view 2017-08-16 23:33:27 -04:00
Eric Amodio
00cdd2e375 Adds "on" state to file annotation command
Adds "progress" state to file annotation command
2017-08-15 01:35:50 -04:00
Eric Amodio
787e0e6aa1 Updates dependencies 2017-08-14 20:52:45 -04:00
Eric Amodio
46554e93f3 Changes gitter to slack 2017-08-02 10:51:23 -04:00
Eric Amodio
4160234962 Add slack chat badge 2017-08-02 10:49:55 -04:00
Eric Amodio
0bd31099ce Keeps the hover info in the whitespace area at the start of the current line 2017-07-28 22:00:01 -04:00
Eric Amodio
a272f8f6a4 Preps v4.3.3 2017-07-28 17:37:08 -04:00
Eric Amodio
e10d7eb648 Increases performance of blame annotations
especially for larger file
2017-07-28 17:09:34 -04:00
Eric Amodio
9d9c3181f7 Fixes #107 - Double-byte characters break blame layout 2017-07-27 00:56:33 -04:00
Eric Amodio
68a42fd0c7 Adds progress indicator for long annotation operations 2017-07-26 23:26:07 -04:00
Eric Amodio
28db0bf2f5 Updates to v2.0 task schema 2017-07-26 23:25:32 -04:00
Eric Amodio
5f85388db7 Preps v4.3.2
Fixes #118 - stopped working on latest insiders build
2017-07-20 22:37:51 +02:00
Eric Amodio
4d167c3cdd Fixes lint issues 2017-07-20 22:19:58 +02:00
Eric Amodio
4826e99986 Updates dependencies 2017-07-20 22:14:13 +02:00
Johannes Rieken
459a8dce1d mixin state of super type
As discussed, this should be ok for Insiders and Stable and accommodate for our URI changes.
2017-07-20 21:54:37 +02:00
Eric Amodio
2650a84105 Preps v4.3.1 2017-07-03 09:50:31 -04:00
Eric Amodio
a2cd57cf95 Fixes #108 - hides stash explorer by default
Adds stash explorer toggle command
2017-07-03 09:50:12 -04:00
Eric Amodio
62a148c156 Adds rebornix thank you 2017-07-03 02:33:37 -04:00
Eric Amodio
be682c5def Preps v4.3.0 2017-07-03 02:30:06 -04:00
Eric Amodio
12d310bdf1 Adds git stashes screenshot 2017-07-03 02:23:13 -04:00
Eric Amodio
af4f433972 Adds stash commands similar to SCM 2017-07-03 02:00:15 -04:00
Eric Amodio
52275215fe Adds content uri from GitUri 2017-07-03 01:59:02 -04:00
Eric Amodio
71edb1b99a Stops error message with stash apply conflict 2017-07-03 01:57:26 -04:00
Eric Amodio
7f3fdb48fd Removes insiders flag from stash view 2017-07-02 23:16:55 -04:00
Eric Amodio
81a9092455 Removes stash list in explorer command
Rename Show File History in Explorer back to Open File History Explorer
Removes the git explorer view (for now)
2017-07-02 23:12:01 -04:00
Eric Amodio
d288985c26 Adapts stash commands to new structure for views
Adds onDidChangeRepo event to GitService
Refreshes stash view when repo changes
2017-07-02 22:34:55 -04:00
rebornix
b81d873a34 WIP of Stash Explorer commands
# Conflicts:
#	src/commands/stashApply.ts
2017-07-02 22:34:05 -04:00
Eric Amodio
c96a659e9e Fixes uris on nodes 2017-07-02 22:34:05 -04:00
Eric Amodio
d193676502 Adds status icons to explorer views
Adds templating support to explorer views
2017-07-02 22:33:56 -04:00
Eric Amodio
6410a274d5 Renames explorer views
Adds refresh commands for explorer views
Adds context menu commands to both explorer views
2017-07-02 22:33:09 -04:00
Eric Amodio
272bb4db45 Sets line to avoid diff jumping 2017-07-02 22:33:09 -04:00
Eric Amodio
7fbee675f0 Spits branch and status nodes into separate files
Removes status node for now
2017-07-02 22:33:09 -04:00
Eric Amodio
bc21272409 Defers command construction until later
Gets a properly constructed commit to improve performance of opening the diff
2017-07-02 22:32:59 -04:00
Eric Amodio
da450a614d Renames explorerNodes 2017-07-02 22:32:05 -04:00
rebornix
0e4664bab1 Rename Stash Explorer 2017-07-02 22:32:04 -04:00
rebornix
1b4800571d extract stash explorer 2017-07-02 22:32:04 -04:00
Eric Amodio
c812a56eac Adds an experimental custom view (wip) 2017-07-02 22:31:04 -04:00
Eric Amodio
3081632815 Updates dependencies (typescript)
Fixes newly detected typescript errors
2017-07-02 22:23:54 -04:00
Eric Amodio
3d32d86998 Renames onDidBlameabilityChange for consistency 2017-06-29 01:41:10 -04:00
Eric Amodio
0a9559f5a5 Reworks commanding structure for less redundancy
Adds command args copying when needed
2017-06-28 01:06:20 -04:00
Eric Amodio
8693c87523 Updates dependencies 2017-06-27 18:34:28 -04:00
Eric Amodio
54c077eb8d Switches to png icon 2017-06-27 01:45:13 -04:00
Eric Amodio
ee124b5325 Preps v4.2.0 2017-06-27 01:34:00 -04:00
Eric Amodio
63a0646c8f Adds commands to the SCM menus 2017-06-27 01:34:00 -04:00
Eric Amodio
cb360c5a3d Renames Compare File with... command 2017-06-27 01:34:00 -04:00
Eric Amodio
3bf5f23c66 Adds diffWithRevision command 2017-06-27 01:34:00 -04:00
Eric Amodio
1751987868 Reworks more commands deal with context 2017-06-27 01:33:59 -04:00
Eric Amodio
9179b70875 Avoids re-setting the line if it is already set 2017-06-27 01:33:59 -04:00
Eric Amodio
93ea2c7145 Fixes another infinite loop in close command 2017-06-27 01:33:59 -04:00
Eric Amodio
f9275a8e1a Reworks command structure with context 2017-06-27 01:33:59 -04:00
Eric Amodio
ceb9c5e126 Removes "Line" from some command titles 2017-06-27 01:33:53 -04:00
Eric Amodio
e42af14b11 Adds status formatter 2017-06-26 23:54:50 -04:00
Eric Amodio
bcfa2cb4d7 Renames icon references to octicon 2017-06-26 23:53:18 -04:00
Eric Amodio
16963ee054 Fixes typo 2017-06-26 23:48:44 -04:00
Eric Amodio
db1bdaa778 Updates excludes 2017-06-26 23:47:31 -04:00
Eric Amodio
ccac13088c Fixes #103 - toggled line annotation state is sometimes forgotten
Often happened when also toggling file annotations
2017-06-26 23:37:16 -04:00
Eric Amodio
899a69357c Fixes a few spelling errors
Adds a better description to blame.line.enabled to be clearer about its behavior
Adds a better description to codeLens.enabled to be clearer about its behavior
2017-06-26 22:49:23 -04:00
Eric Amodio
c78c3c7525 Preps v4.1.4 2017-06-25 13:26:33 -04:00
Eric Amodio
1cbf0b2c73 Updates dependencies 2017-06-25 13:26:33 -04:00
Eric Amodio
191a158b3b Optimizes diff with previous command 2017-06-25 13:26:33 -04:00
Eric Amodio
f553980146 Attempts to fix #99
undo/redo spawns too many git processes
2017-06-25 13:26:19 -04:00
Eric Amodio
a306ba2b98 Stops trying to set the line if we don't have one 2017-06-25 00:29:11 -04:00
Eric Amodio
1cdd94cb46 Adds a with method to commit 2017-06-24 17:54:19 -04:00
Eric Amodio
12caa017a9 Fixes issues with changes showing wrong diff
Refactors diff parsing
2017-06-24 17:52:52 -04:00
Eric Amodio
ad790b274b Preps v4.1.3 2017-06-20 01:30:05 -04:00
Eric Amodio
55ef004332 Fixes changes hover not showing the correct diff when showing recent changes annotations 2017-06-19 23:23:20 -04:00
Eric Amodio
329d5a496d Preps v4.1.2 2017-06-15 23:54:43 -04:00
Eric Amodio
6b4642d8c8 Fixes #96 - stops opening external diff if configured 2017-06-15 23:44:41 -04:00
Eric Amodio
45aa9b17ac Changes to use enum for glyphs 2017-06-13 22:34:51 -04:00
Eric Amodio
9b908e27ba Preps v4.1.1 2017-06-13 03:21:49 -04:00
Eric Amodio
0eb6026a82 Fixes missing icons 2017-06-13 03:18:14 -04:00
Eric Amodio
5823457ea0 Removes invalid signature 2017-06-13 03:15:15 -04:00
Eric Amodio
64b929fd5b Preps v4.1.0 2017-06-13 01:33:23 -04:00
Eric Amodio
a618b7efe6 Adds ability to esc out of file annotations 2017-06-13 01:31:13 -04:00
Eric Amodio
a2903ce4a9 Refactors formatters
Moves annotation messages from formatter to annotations
Moves icons into dark/light folders
2017-06-13 00:14:10 -04:00
Eric Amodio
12debe583c Cleans up more duplicate code 2017-06-12 12:25:35 -04:00
134 changed files with 4523 additions and 1510 deletions

View File

@@ -1,13 +1,13 @@
{ {
"editor.insertSpaces": true, "editor.insertSpaces": true,
"files.exclude": { "files.exclude": {
"node_modules": true, "**/node_modules": true,
"out": true "**/out": true
}, },
"files.trimTrailingWhitespace": true, "files.trimTrailingWhitespace": true,
"search.exclude": { "search.exclude": {
"node_modules": true, "**/node_modules": true,
"out": true "**/out": true
}, },
"javascript.format.insertSpaceAfterFunctionKeywordForAnonymousFunctions": false, "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

25
.vscode/tasks.json vendored
View File

@@ -9,13 +9,18 @@
// 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": "2.0.0", "version": "2.0.0",
"showOutput": "silent", "presentation": {
"echo": true,
"reveal": "always",
"focus": false,
"panel": "shared"
},
"tasks": [ "tasks": [
{ {
"taskName": "compile", "label": "compile",
"command": "npm run compile --silent", "type": "npm",
"isBuildCommand": true, "script": "compile",
"isShellCommand": true, "group": "build",
"problemMatcher": [ "problemMatcher": [
"$tsc", "$tsc",
{ {
@@ -25,19 +30,17 @@
] ]
}, },
{ {
"taskName": "lint", "type": "npm",
"command": "npm run lint --silent", "script": "lint",
"isShellCommand": true,
"problemMatcher": { "problemMatcher": {
"base": "$tslint5", "base": "$tslint5",
"fileLocation": "relative" "fileLocation": "relative"
} }
}, },
{ {
"taskName": "watch", "type": "npm",
"command": "npm run watch --silent", "script": "watch",
"isBackground": true, "isBackground": true,
"isShellCommand": true,
"problemMatcher": "$tsc-watch" "problemMatcher": "$tsc-watch"
} }
] ]

View File

@@ -1,4 +1,6 @@
!images/*.svg !images/dark/**
!images/light/**
!images/gitlens-icon.png
images/** images/**
.vscode/** .vscode/**
.vscode-test/** .vscode-test/**

View File

@@ -4,13 +4,129 @@ 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/). The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/).
## [4.1.0-beta.2] - 2017-06-12 ## [4.5.0-beta] - 2017-08-27
### Added
- Adds an all-new `Git File History` explorer to the Explorer activity -- enabled via `"gitlens.insiders": true`
- Shows the commit history of the active file -- automatically tracks the active editor
- Provides toolbar buttons to `Refresh`
- Provides a context menu with `Open Changes`, `Compare File with Working Tree`, `Open File`, `Open File Revision`, `Open File in Remote`, `Open File Revision in Remote`, and `Show Commit Details` commands
- Adds a `No stashed changes` message to the `Git Stashes` explorer when there are no stashes
- Adds `${filePath}` token to file formatting
### Changed
- Changes `gitlens.stashExplorer.stashFileFormat` setting to defaults to `${filePath}` for better separator handling
## [4.4.1] - 2017-08-23
## Fixed
- Fixes [#114](https://github.com/eamodio/vscode-gitlens/issues/114) - Stylus files makes code lens freak out
## [4.4.0] - 2017-08-18
## Added
- Adds a progress indicator to the `Toggle File Blame Annotations` command (`gitlens.toggleFileBlame`) icon -- pulses while annotations are computed
- Adds an active state to the `Toggle File Blame Annotations` command (`gitlens.toggleFileBlame`) icon -- turns orange while the annotations are visible
- Adds automatic disabling of the current line blame annotations when starting a debug session and will restore them when the debug session ends -- can still be manually toggled via the `Toggle Line Blame Annotations` command (`gitlens.toggleLineBlame`)
## Changed
- Changes chat links from Gitter to [Slack](https://join.slack.com/t/vscode-gitlens/shared_invite/MjIxOTgxNDE3NzM0LTE1MDE2Nzk1MTgtMjkwMmZjMzcxNQ)
- Changes the look of the line separators on the gutter blame annotations
- Changes the `gitlens.advanced.toggleWhitespace.enabled` configuration setting to default to `false` -- thanks to the awesome work in vscode by Alexandru Dima ([@alexandrudima](https://github.com/alexandrudima)) this is no longer required!
## Removed
- Removes unneeded `gitlens.stashExplorer.enabled` configuration setting since users can add or remove custom views natively now
- Removes unneeded `Toggle Git Stashes Explorer` command (`gitlens.stashExplorer.toggle`) since users can add or remove custom views natively now
- Removes the `gitlens.theme.annotations.file.hover.separateLines` configuration setting
## Fixed
- Fixes jumpiness when opening a diff to a certain line
## [4.3.3] - 2017-07-28
## Added
- Adds progress indicator for when computing annotations takes a while
## Changed
- Optimizes performance of the providing blame annotations, especially for large files (saw a 3.5x improvement on some files)
## Fixed
- Fixes [#107](https://github.com/eamodio/vscode-gitlens/issues/107) - Double-byte characters break blame layout (still requires proper font support)
## [4.3.2] - 2017-07-20
## Fixed
- Fixes [#118](https://github.com/eamodio/vscode-gitlens/issues/118) - GitLens stopped working on latest insiders build -- thanks to [PR #121](https://github.com/eamodio/vscode-gitlens/pull/121) by Johannes Rieken ([@jrieken](https://github.com/jrieken))
## [4.3.1] - 2017-07-03
## Added
- Adds `gitlens.stashExplorer.enabled` setting to specify whether or not to show the `Git Stashes` explorer
- Adds `Toggle Git Stashes Explorer` command (`gitlens.stashExplorer.toggle`) - toggles the `Git Stashes` explorer on and off
## Changed
- Hides the `Git Stashes` explorer by default
## Fixed
- Fixes [#108](https://github.com/eamodio/vscode-gitlens/issues/108) - Option to remove stash explorer from the main explorer?
## [4.3.0] - 2017-07-03
## Added
- Adds `Git Stashes` view to the Explorer activity
![Git Stashes view](https://raw.githubusercontent.com/eamodio/vscode-gitlens/master/images/screenshot-git-stashes.png)
- Shows all of the stashed changes in the repository
- Provides toolbar buttons to `Stash Changes` and `Refresh`
- Provides a context menu with `Apply Stashed Changes` and `Delete Stashed Changes` commands - both require a confirmation
- Expand each stash to quickly see the set of files changed, complete with status indicators for adds, changes, renames, and deletes
- Provides a context menu with `Open Changes`, `Open File`, `Open Stashed File`, `Open File in Remote`, and `Compare File with Working Tree` commands
## [4.2.0] - 2017-06-27
## Added
- Adds `Compare File with Revision...` command (`gitlens.diffWithRevision`) - compare the active file with the selected revision of the same file
- Adds `Open Changed Files` command (`gitlens.openChangedFiles`) to the source control group context menu
- Adds `Close Unchanged Files` command (`gitlens.closeUnchangedFiles`) to the source control group context menu
- Adds `Open File in Remote` command (`gitlens.openFileInRemote`) to the source control resource context menu
- Adds `Compare File with Revision...` command (`gitlens.diffWithRevision`) to the source control resource context menu
- Adds `Show File History` command (`gitlens.showQuickFileHistory`) to the source control resource context menu
## Changed
- Renames `Compare File with...` command to `Compare File with Branch...`
- Renames `Open Line Commit in Remote` command to `Open Commit in Remote`
- Renames `Show Line Commit Details` command to `Show Commit File Details`
- Updates the description of `gitlens.blame.line.enabled` to be clearer about its behavior
- Updates the description of `gitlens.codeLens.enabled` to be clearer about its behavior
### Fixed
- Fixes [#103](https://github.com/eamodio/vscode-gitlens/issues/103) - Toggle file blame annotations disables line blame annotations if line blame annotations are off by default
- Fixes another infinite loop in the `Close Unchanged Files` command
## [4.1.4] - 2017-06-25
## Changed
- Optimizes performance of the `Compare with Previous` commands - also avoids trying to focus a line if we don't have one
### Fixed
- Fixes `changes` (diff) hover not showing the correct previous line (for real this time)
- Attempts to fix [#99](https://github.com/eamodio/vscode-gitlens/issues/99) - undo/redo spawns too many git processes
## [4.1.3] - 2017-06-20
### Fixed
- Fixes `changes` (diff) hover not showing the correct previous line when showing recent changes annotations of the whole-file
## [4.1.2] - 2017-06-15
### Fixed
- Fixes [#96](https://github.com/eamodio/vscode-gitlens/issues/96) - External diff command can be unintentionally triggered
## [4.1.1] - 2017-06-13
### Added
- Adds an `alt` command to the `Toggle File Blame Annotations` command button, which when you hold down `alt` and click it will execute the `Toggle Recent File Changes Annotations` command instead
### Fixed
- Fixes missing `Toggle File Blame Annotations` command icon
## [4.1.0] - 2017-06-13
### Added ### Added
- Adds all-new recent changes annotations of the whole-file - annotates and highlights all of lines changed in the most recent commit - Adds all-new recent changes annotations of the whole-file - annotates and highlights all of lines changed in the most recent commit
- Can customize the [layout](https://marketplace.visualstudio.com/items?itemName=eamodio.gitlens#file-recent-changes-annotation-settings), as well as the [theme](https://marketplace.visualstudio.com/items?itemName=eamodio.gitlens#theme-settings) - Can customize the [layout](https://marketplace.visualstudio.com/items?itemName=eamodio.gitlens#file-recent-changes-annotation-settings), as well as the [theme](https://marketplace.visualstudio.com/items?itemName=eamodio.gitlens#theme-settings)
- Adds `Toggle Recent File Changes Annotations` command (`gitlens.toggleFileRecentChanges`) - toggles the recent changes annotations on and off - Adds `Toggle Recent File Changes Annotations` command (`gitlens.toggleFileRecentChanges`) - toggles the recent changes annotations on and off
- Adds ability to press `Escape` to quickly toggle any whole-file annotations off
- Improves performance - Improves performance
- Optimized git output parsing to increase speed and reduce memory usage - Optimized git output parsing to increase speed and dramatically reduce memory usage
- Defers diff chunk parsing until it is actually required - Defers diff chunk parsing until it is actually required
- Adds `gitlens.defaultDateFormat` setting to specify how all absolute dates will be formatted by default - Adds `gitlens.defaultDateFormat` setting to specify how all absolute dates will be formatted by default

View File

@@ -1,11 +1,11 @@
[![](https://vsmarketplacebadge.apphb.com/version/eamodio.gitlens.svg)](https://marketplace.visualstudio.com/items?itemName=eamodio.gitlens) [![](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/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) [![](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) [![Chat at https://vscode-gitlens.slack.com/](https://raw.githubusercontent.com/eamodio/vscode-gitlens/develop/images/chat-badge.png)](https://join.slack.com/t/vscode-gitlens/shared_invite/MjIxOTgxNDE3NzM0LTE1MDE2Nzk1MTgtMjkwMmZjMzcxNQ)
# GitLens # 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 **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 comparison 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). 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).
@@ -33,6 +33,7 @@ GitLens provides an unobtrusive blame annotation at the end of the current line,
- Adds a `details` hover annotation to the line's annotation, which provides more commit details ([optional](#file-blame-annotation-settings), on by default) - Adds a `details` hover annotation to the line's annotation, which provides more commit details ([optional](#file-blame-annotation-settings), on by default)
- Adds a `heatmap` (age) indicator to the gutter annotations (on right edge by [default](#file-blame-annotation-settings)), which provides an easy, at-a-glance way to tell the age of a line ([optional](#file-blame-annotation-settings), on by default) - Adds a `heatmap` (age) indicator to the gutter annotations (on right edge by [default](#file-blame-annotation-settings)), which provides an easy, at-a-glance way to tell the age of a line ([optional](#file-blame-annotation-settings), on by default)
- Indicator ranges from bright yellow (newer) to dark brown (older) - Indicator ranges from bright yellow (newer) to dark brown (older)
- Press `Escape` to quickly toggle the annotations off
- Adds [customizable](#status-bar-settings) **blame information** about the current line to the **status bar** ([optional](#status-bar-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)
@@ -61,6 +62,7 @@ GitLens provides an unobtrusive blame annotation at the end of the current line,
- Highlights all of lines changed in the most recent commit - Highlights all of lines changed in the most recent commit
- Adds a `details` hover annotation to each line, which provides more commit details ([optional](#file-blame-annotation-settings), on by default) - Adds a `details` hover annotation to each line, which provides more commit details ([optional](#file-blame-annotation-settings), on by default)
- Adds a `changes` (diff) hover annotation to each line, which provides **instant** access to the line's previous version ([optional](#file-recent-changes-annotation-settings), on by default) - Adds a `changes` (diff) hover annotation to each line, which provides **instant** access to the line's previous version ([optional](#file-recent-changes-annotation-settings), on by default)
- Press `Escape` to quickly toggle the annotations off
- Adds `Toggle Recent File Changes Annotations` command (`gitlens.toggleFileRecentChanges`) to toggle the recent changes annotations on and off - Adds `Toggle Recent File Changes Annotations` command (`gitlens.toggleFileRecentChanges`) to toggle the recent changes annotations on and off
@@ -87,13 +89,13 @@ GitLens provides an unobtrusive blame annotation at the end of the current line,
### Powerful Comparison Tools ### Powerful Comparison Tools
- Effortlessly navigate between comparisions via the `alt+,` and `alt+.` shortcut keys to go back and forth through a file's revisions - Effortlessly navigate between comparisons 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 - 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 `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 Branch...` 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 Next Commit` command (`gitlens.diffWithNext`) with a shortcut of `alt+.` to compare the active file/diff with the next commit revision
@@ -101,17 +103,35 @@ GitLens provides an unobtrusive blame annotation at the end of the current line,
- 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 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 Revision...` command (`gitlens.diffWithRevision`) to compare the active file with the selected revision of the same file
- 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 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 - 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 ### Navigate and Explore
- Adds a [customizable](#git-file-history-explorer-settings) `Git File History` explorer to the Explorer activity -- currently [insiders](#insiders) only
- Shows the commit history of the active file -- automatically tracks the active editor
- Provides toolbar buttons to `Refresh`
- Provides a context menu with `Open Changes`, `Compare File with Working Tree`, `Open File`, `Open File Revision`, `Open File in Remote`, `Open File Revision in Remote`, and `Show Commit Details` commands
- Adds a [customizable](#git-stashes-explorer-settings) `Git Stashes` explorer to the Explorer activity
![Git Stashes explorer](https://raw.githubusercontent.com/eamodio/vscode-gitlens/master/images/screenshot-git-stashes.png)
- Shows all of the stashed changes in the repository
- Provides toolbar buttons to `Stash Changes` and `Refresh`
- Provides a context menu with `Apply Stashed Changes` and `Delete Stashed Changes` commands — both require a confirmation
- Expand each stash to quickly see the set of files changed, complete with status indicators for adds, changes, renames, and deletes
- Provides a context menu with `Open Changes`, `Open File`, `Open Stashed File`, `Open File in Remote`, and `Compare File with Working Tree` commands
- 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 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 - 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 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 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 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 - `Open Repository in Remote` command (`gitlens.openRepoInRemote`) — opens the repository in the supported remote service
@@ -142,12 +162,11 @@ GitLens provides an unobtrusive blame annotation at the end of the current line,
- Provides entries to `Copy to Clipboard`, `Directory Compare`, `Open Changed Files`, `Open File in <remote-service>` when available, and more - 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 - 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 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 - Use the `alt+right arrow` shortcut on a file entry in the `Changed Files` section to preview the comparison of the current revision with the previous one
- 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 - Adds a `Show Commit File 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-gitlens/master/images/screenshot-commit-file-details.png) ![Commit File Details Quick Pick Menu](https://raw.githubusercontent.com/eamodio/vscode-gitlens/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 - 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 - Navigate back to the previous quick pick menu via `alt+left arrow`, if available
@@ -158,13 +177,12 @@ GitLens provides an unobtrusive blame annotation at the end of the current line,
![Repository Status Quick Pick Menu](https://raw.githubusercontent.com/eamodio/vscode-gitlens/master/images/screenshot-repo-status.png) ![Repository Status Quick Pick Menu](https://raw.githubusercontent.com/eamodio/vscode-gitlens/master/images/screenshot-repo-status.png)
- Quickly see upstream status (if an Git upstream is configured) — complete with ahead and behind information - 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 ahead of the upstream, an entry will be shown with the number of commits ahead. Choosing 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 - If you are behind the upstream, an entry will be shown with the number of commits behind. Choosing 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 - 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` - 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 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 - Use the `alt+right arrow` shortcut on a file entry in the `Staged Files` or `Unstaged Files` sections to preview the comparison of the working file with the previous revision
- 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 - Adds a `Show Stashed Changes` command (`gitlens.showQuickStashList`) to show a **stashed changes quick pick menu** for exploring your repository stash history
@@ -173,7 +191,7 @@ GitLens provides an unobtrusive blame annotation at the end of the current line,
- Provides entries to `Stash Changes` - Provides entries to `Stash Changes`
- Navigate back to the previous quick pick menu via `alt+left arrow`, if available - 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 - Choosing 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-gitlens/master/images/screenshot-stash-details.png) ![Stash Details Quick Pick Menu](https://raw.githubusercontent.com/eamodio/vscode-gitlens/master/images/screenshot-stash-details.png)
@@ -182,8 +200,7 @@ GitLens provides an unobtrusive blame annotation at the end of the current line,
- Provides entries to `Apply Stashed Changes` and `Delete Stashed Changes` — both require a confirmation - 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 - 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 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 - Use the `alt+right arrow` shortcut on a file entry in the `Changed Files` section to preview the comparison of the current revision with the previous one
- 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 `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
@@ -246,7 +263,7 @@ GitLens is highly customizable and provides many configuration settings to allow
|Name | Description |Name | Description
|-----|------------ |-----|------------
|`gitlens.blame.line.enabled`|Specifies whether or not to provide a blame annotation for the current line |`gitlens.blame.line.enabled`|Specifies whether or not to provide a blame annotation for the current line, by default<br />Use the `gitlens.toggleLineBlame` command to toggle the annotations on and off for the current session
|`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.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.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.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
@@ -269,7 +286,7 @@ GitLens is highly customizable and provides many configuration settings to allow
|Name | Description |Name | Description
|-----|------------ |-----|------------
|`gitlens.codeLens.enabled`|Specifies whether or not to provide any Git code lens |`gitlens.codeLens.enabled`|Specifies whether or not to provide any Git code lens, by default<br />Use the `gitlens.toggleCodeLens` command to toggle the Git code lens on and off for the current session
|`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.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.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.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)
@@ -278,6 +295,19 @@ GitLens is highly customizable and provides many configuration settings to allow
|`gitlens.codeLens.customLocationSymbols`|Specifies the set of document symbols where Git code lens will be shown in the document |`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 |`gitlens.codeLens.perLanguageLocations`|Specifies where Git code lens will be shown in the document for the specified languages
### Git File History Explorer Settings
|Name | Description
|-----|------------
|`gitlens.fileHistoryExplorer.commitFormat`|Specifies the format of committed changes in the `Git File History` explorer <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 /> ${authorAgo} - commit author, relative commit date<br />See https://github.com/eamodio/vscode-gitlens/wiki/Advanced-Formatting for advanced formatting
### Git Stashes Explorer Settings
|Name | Description
|-----|------------
|`gitlens.stashExplorer.stashFormat`|Specifies the format of stashed changes in the `Git Stashes` explorer <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 /> ${authorAgo} - commit author, relative commit date<br />See https://github.com/eamodio/vscode-gitlens/wiki/Advanced-Formatting for advanced formatting
|`gitlens.stashExplorer.stashFileFormat`|Specifies the format of a stashed file in the `Git Stashes` explorer <br />Available tokens<br /> ${file} - file name<br /> ${filePath} - file name and path<br /> ${path} - file path
### Status Bar Settings ### Status Bar Settings
|Name | Description |Name | Description
@@ -300,14 +330,13 @@ GitLens is highly customizable and provides many configuration settings to allow
|Name | Description |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.separateLines`|Specifies whether or not gutter blame annotations will have line separators
|`gitlens.theme.annotations.file.gutter.dark.backgroundColor`|Specifies the dark theme background color of the gutter blame annotations |`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.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.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.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.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.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.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.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.dark.foregroundColor`|Specifies the dark theme foreground color of the trailing blame annotation
@@ -321,7 +350,6 @@ GitLens is highly customizable and provides many configuration settings to allow
|Name | Description |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.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.menus`|Specifies which commands will be added to which menus
|`gitlens.advanced.caching.enabled`|Specifies whether git output will be cached |`gitlens.advanced.caching.enabled`|Specifies whether git output will be cached
@@ -334,11 +362,12 @@ GitLens is highly customizable and provides many configuration settings to allow
## Known Issues ## Known Issues
- 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` - 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
## Contributors ## Contributors
A big thanks to the people that have contributed to this project: A big thanks to the people that have contributed to this project:
- Peng Lyu ([@rebornix](https://github.com/rebornix)) — [contributions](https://github.com/eamodio/vscode-gitlens/commits?author=rebornix))
- Aurelio Ogliari ([@nobitagit](https://github.com/nobitagit)) — [contributions](https://github.com/eamodio/vscode-gitlens/commits?author=nobitagit) - Aurelio Ogliari ([@nobitagit](https://github.com/nobitagit)) — [contributions](https://github.com/eamodio/vscode-gitlens/commits?author=nobitagit)
- Johannes Rieken ([@jrieken](https://github.com/jrieken)) — [contributions](https://github.com/eamodio/vscode-gitlens/commits?author=jrieken))
- Zack Schuster ([@zackschuster](https://github.com/zackschuster)) — [contributions](https://github.com/eamodio/vscode-gitlens/commits?author=zackschuster) - Zack Schuster ([@zackschuster](https://github.com/zackschuster)) — [contributions](https://github.com/eamodio/vscode-gitlens/commits?author=zackschuster)

BIN
images/chat-badge.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

88
images/chat-badge.svg Normal file
View File

@@ -0,0 +1,88 @@
<?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="92" height="20" xml:space="preserve">
<style type="text/css">
.white{fill:#FFF;}
.shadow{fill:#2e840e;}
</style>
<linearGradient id="b" x2="0" y2="100%">
<stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
<stop offset="1" stop-opacity=".1"/>
</linearGradient>
<clipPath id="a">
<rect width="92" height="20" rx="3" fill="#fff"/>
</clipPath>
<g clip-path="url(#a)">
<path fill="#555" d="M0 0h34v20H0z"/>
<path fill="#4c1" d="M34 0h58v20H34z"/>
<path fill="url(#b)" d="M0 0h92v20H0z"/>
</g>
<g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11">
<text x="17" y="15" fill="#010101" fill-opacity=".3">chat</text>
<text x="17" y="14">chat</text>
<g transform="scale(0.1) translate(340, -21)">
<path class="shadow" d="M263.5,105.5c3.9,1.7,4.2,3,1.1,8.8c-3.1,6-3.9,6.3-7.7,4.8c-4.8-2.1-11-3.6-15-3.6c-6.5,0-10.9,2.3-10.9,5.9
c0,11.7,37.4,5.4,37.4,30.4c0,12.6-10.8,21-27,21c-8.5,0-19-2.8-26.3-6.5c-3.6-1.8-3.8-2.9-0.7-8.9c2.7-5.3,3.6-5.8,7.5-4.2
c6.2,2.7,14,4.8,19.3,4.8c6,0,9.9-2.4,9.9-6c0-11.4-38.1-6-38.1-30.2c0-12.9,10.7-21.5,26.8-21.5
C247.4,100.2,256.9,102.5,263.5,105.5z"/>
<path class="shadow" d="M293.7,75.8V169c0,1.4-1.5,2.9-3.6,2.9h-9.8c-2.1,0-3.6-1.5-3.6-2.9V75.8c0-4.6,1.3-5,8.5-5
C293.4,70.7,293.7,71.3,293.7,75.8z"/>
<path class="shadow" d="M363.4,128.2v40.1c0,2.1-1.5,3.6-3.6,3.6h-9.7c-2.2,0-3.8-1.6-3.6-3.8l0.1-4.3c-5.3,5.8-12.8,8.9-20.3,8.9
c-14.6,0-24.4-8.5-24.4-21.1c0-13.4,11.1-22.5,27.7-22.5c6.3,0,12.1,1.1,16.8,3.1v-4.6c0-7.4-5.8-11.8-15.7-11.8
c-4.6,0-10.3,1.8-14.8,4.5c-3.5,2-4.3,1.8-8.1-3.8c-3.7-5.6-3.6-6.7,0-9c6.8-4.4,16.1-7.2,24.6-7.2
C351.4,100.2,363.4,110.6,363.4,128.2z M318.2,151.4c0,4.8,4,8,10.2,8c7.4,0,14.1-3.6,18-9.6v-6.3c-3.9-1.5-8.7-2.3-12.9-2.3
C324.3,141.2,318.2,145.5,318.2,151.4z"/>
<path class="shadow" d="M429.9,105.6c3.6,2.1,3.7,3.2-0.2,9.2c-3.7,5.7-4.3,6-8.2,4c-3-1.6-7.7-2.9-11.7-2.9
c-12.3,0-20.5,8.1-20.5,20.3c0,12.7,8.2,21.2,20.5,21.2c4.3,0,9.6-1.6,13.1-3.6c3.6-2.1,4.3-1.9,8.1,3.6c3.4,5.1,3.3,6.3,0.3,8.5
c-5.5,3.8-14.1,6.7-21.7,6.7c-22.7,0-37.9-14.6-37.9-36.4c0-21.7,15.2-36.1,38.1-36.1C416.6,100.2,424.8,102.6,429.9,105.6z"/>
<path class="shadow" d="M504.1,163.1c2.8,3.6,1.7,4.9-5.4,7.5c-7.2,2.6-8.2,2.5-10.8-0.9l-20.3-27.1l-9.1,8.8V169
c0,1.4-1.5,2.9-3.6,2.9H445c-2.1,0-3.6-1.5-3.6-2.9V75.8c0-4.6,1.3-5,8.5-5c8.2,0,8.5,0.6,8.5,5v53l27.8-26.7
c3.1-2.9,4.8-2.7,10.2,0.9c6,3.9,6.5,5,3.6,7.8L479.3,131L504.1,163.1z"/>
</g>
<g transform="scale(0.1) translate(340, -21)">
<rect x="115.9" y="126.3" transform="matrix(0.9482 -0.3176 0.3176 0.9482 -36.3582 46.5843)" class="shadow" width="17.6" height="17"/>
<g>
<rect x="115.9" y="126.3" transform="matrix(0.9482 -0.3176 0.3176 0.9482 -36.3582 46.5843)" class="shadow" width="17.6" height="17"/>
<path class="shadow" d="M182,117.5c-12.9-43-31.5-53-74.5-40.1s-53,31.5-40.1,74.5s31.5,53,74.5,40.1S194.9,160.5,182,117.5z
M160.3,145.4l-8.1,2.7l2.8,8.4c1.1,3.4-0.7,7.1-4.1,8.2c-0.7,0.2-1.5,0.4-2.2,0.3c-2.6-0.1-5.1-1.8-6-4.4l-2.8-8.4l-16.7,5.6
l2.8,8.4c1.1,3.4-0.7,7.1-4.1,8.2c-0.7,0.2-1.5,0.4-2.2,0.3c-2.6-0.1-5.1-1.8-6-4.4l-2.8-8.4l-8.1,2.7c-0.7,0.2-1.5,0.4-2.2,0.3
c-2.6-0.1-5.1-1.8-6-4.4c-1.1-3.4,0.7-7.1,4.1-8.2l8.1-2.7l-5.4-16.1l-8.1,2.7c-0.7,0.2-1.5,0.4-2.2,0.3c-2.6-0.1-5.1-1.8-6-4.4
c-1.1-3.4,0.7-7.1,4.1-8.2l8.1-2.7l-2.8-8.4c-1.1-3.4,0.7-7.1,4.1-8.2s7.1,0.7,8.2,4.1l2.8,8.4l16.7-5.6l-2.8-8.4
c-1.1-3.4,0.7-7.1,4.1-8.2s7.1,0.7,8.2,4.1l2.8,8.4l8.1-2.7c3.4-1.1,7.1,0.7,8.2,4.1c1.1,3.4-0.7,7.1-4.1,8.2l-8.1,2.7l5.4,16.1
l8.1-2.7c3.4-1.1,7.1,0.7,8.2,4.1C165.5,140.6,163.7,144.3,160.3,145.4z"/>
</g>
</g>
<g transform="scale(0.1) translate(340, -31)">
<path class="white" d="M263.5,105.5c3.9,1.7,4.2,3,1.1,8.8c-3.1,6-3.9,6.3-7.7,4.8c-4.8-2.1-11-3.6-15-3.6c-6.5,0-10.9,2.3-10.9,5.9
c0,11.7,37.4,5.4,37.4,30.4c0,12.6-10.8,21-27,21c-8.5,0-19-2.8-26.3-6.5c-3.6-1.8-3.8-2.9-0.7-8.9c2.7-5.3,3.6-5.8,7.5-4.2
c6.2,2.7,14,4.8,19.3,4.8c6,0,9.9-2.4,9.9-6c0-11.4-38.1-6-38.1-30.2c0-12.9,10.7-21.5,26.8-21.5
C247.4,100.2,256.9,102.5,263.5,105.5z"/>
<path class="white" d="M293.7,75.8V169c0,1.4-1.5,2.9-3.6,2.9h-9.8c-2.1,0-3.6-1.5-3.6-2.9V75.8c0-4.6,1.3-5,8.5-5
C293.4,70.7,293.7,71.3,293.7,75.8z"/>
<path class="white" d="M363.4,128.2v40.1c0,2.1-1.5,3.6-3.6,3.6h-9.7c-2.2,0-3.8-1.6-3.6-3.8l0.1-4.3c-5.3,5.8-12.8,8.9-20.3,8.9
c-14.6,0-24.4-8.5-24.4-21.1c0-13.4,11.1-22.5,27.7-22.5c6.3,0,12.1,1.1,16.8,3.1v-4.6c0-7.4-5.8-11.8-15.7-11.8
c-4.6,0-10.3,1.8-14.8,4.5c-3.5,2-4.3,1.8-8.1-3.8c-3.7-5.6-3.6-6.7,0-9c6.8-4.4,16.1-7.2,24.6-7.2
C351.4,100.2,363.4,110.6,363.4,128.2z M318.2,151.4c0,4.8,4,8,10.2,8c7.4,0,14.1-3.6,18-9.6v-6.3c-3.9-1.5-8.7-2.3-12.9-2.3
C324.3,141.2,318.2,145.5,318.2,151.4z"/>
<path class="white" d="M429.9,105.6c3.6,2.1,3.7,3.2-0.2,9.2c-3.7,5.7-4.3,6-8.2,4c-3-1.6-7.7-2.9-11.7-2.9
c-12.3,0-20.5,8.1-20.5,20.3c0,12.7,8.2,21.2,20.5,21.2c4.3,0,9.6-1.6,13.1-3.6c3.6-2.1,4.3-1.9,8.1,3.6c3.4,5.1,3.3,6.3,0.3,8.5
c-5.5,3.8-14.1,6.7-21.7,6.7c-22.7,0-37.9-14.6-37.9-36.4c0-21.7,15.2-36.1,38.1-36.1C416.6,100.2,424.8,102.6,429.9,105.6z"/>
<path class="white" d="M504.1,163.1c2.8,3.6,1.7,4.9-5.4,7.5c-7.2,2.6-8.2,2.5-10.8-0.9l-20.3-27.1l-9.1,8.8V169
c0,1.4-1.5,2.9-3.6,2.9H445c-2.1,0-3.6-1.5-3.6-2.9V75.8c0-4.6,1.3-5,8.5-5c8.2,0,8.5,0.6,8.5,5v53l27.8-26.7
c3.1-2.9,4.8-2.7,10.2,0.9c6,3.9,6.5,5,3.6,7.8L479.3,131L504.1,163.1z"/>
</g>
<g transform="scale(0.1) translate(340, -31)">
<rect x="115.9" y="126.3" transform="matrix(0.9482 -0.3176 0.3176 0.9482 -36.3582 46.5843)" class="white" width="17.6" height="17"/>
<g>
<rect x="115.9" y="126.3" transform="matrix(0.9482 -0.3176 0.3176 0.9482 -36.3582 46.5843)" class="white" width="17.6" height="17"/>
<path class="white" d="M182,117.5c-12.9-43-31.5-53-74.5-40.1s-53,31.5-40.1,74.5s31.5,53,74.5,40.1S194.9,160.5,182,117.5z
M160.3,145.4l-8.1,2.7l2.8,8.4c1.1,3.4-0.7,7.1-4.1,8.2c-0.7,0.2-1.5,0.4-2.2,0.3c-2.6-0.1-5.1-1.8-6-4.4l-2.8-8.4l-16.7,5.6
l2.8,8.4c1.1,3.4-0.7,7.1-4.1,8.2c-0.7,0.2-1.5,0.4-2.2,0.3c-2.6-0.1-5.1-1.8-6-4.4l-2.8-8.4l-8.1,2.7c-0.7,0.2-1.5,0.4-2.2,0.3
c-2.6-0.1-5.1-1.8-6-4.4c-1.1-3.4,0.7-7.1,4.1-8.2l8.1-2.7l-5.4-16.1l-8.1,2.7c-0.7,0.2-1.5,0.4-2.2,0.3c-2.6-0.1-5.1-1.8-6-4.4
c-1.1-3.4,0.7-7.1,4.1-8.2l8.1-2.7l-2.8-8.4c-1.1-3.4,0.7-7.1,4.1-8.2s7.1,0.7,8.2,4.1l2.8,8.4l16.7-5.6l-2.8-8.4
c-1.1-3.4,0.7-7.1,4.1-8.2s7.1,0.7,8.2,4.1l2.8,8.4l8.1-2.7c3.4-1.1,7.1,0.7,8.2,4.1c1.1,3.4-0.7,7.1-4.1,8.2l-8.1,2.7l5.4,16.1
l8.1-2.7c3.4-1.1,7.1,0.7,8.2,4.1C165.5,140.6,163.7,144.3,160.3,145.4z"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.1 KiB

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

View File

@@ -0,0 +1,12 @@
<?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">
<animate attributeName="fill" values="#FFFFFF;#F05133;#FFFFFF" dur="1s" repeatCount="indefinite"/>
</path>
</g>
</svg>

After

Width:  |  Height:  |  Size: 941 B

View File

Before

Width:  |  Height:  |  Size: 815 B

After

Width:  |  Height:  |  Size: 815 B

View File

Before

Width:  |  Height:  |  Size: 312 B

After

Width:  |  Height:  |  Size: 312 B

1
images/dark/icon-add.svg Normal file
View File

@@ -0,0 +1 @@
<svg width="16" height="16" xmlns="http://www.w3.org/2000/svg"><title>Layer 1</title><rect height="11" width="3" y="3" x="7" fill="#C5C5C5"/><rect height="3" width="11" y="7" x="3" fill="#C5C5C5"/></svg>

After

Width:  |  Height:  |  Size: 203 B

View File

@@ -0,0 +1,4 @@
<?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" width="256" height="256" viewBox="0 0 14 16" xml:space="preserve">
<path fill="#C5C5C5" d="M10.86 7c-.45-1.72-2-3-3.86-3-1.86 0-3.41 1.28-3.86 3H0v2h3.14c.45 1.72 2 3 3.86 3 1.86 0 3.41-1.28 3.86-3H14V7h-3.14zM7 10.2c-1.22 0-2.2-.98-2.2-2.2 0-1.22.98-2.2 2.2-2.2 1.22 0 2.2.98 2.2 2.2 0 1.22-.98 2.2-2.2 2.2z"></path>
</svg>

After

Width:  |  Height:  |  Size: 480 B

View File

@@ -0,0 +1,5 @@
<?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="16" height="16">
<path d="M13.451 5.609l-.579-.939-1.068.812-.076.094c-.335.415-.927 1.341-1.124 2.876l-.021.165.033.163.071.345c0 1.654-1.346 3-3 3a2.98 2.98 0 0 1-2.107-.868 2.98 2.98 0 0 1-.873-2.111 3.004 3.004 0 0 1 2.351-2.929v2.926s2.528-2.087 2.984-2.461h.012L13.115 4.1 8.196 0H7.059v2.404A6.759 6.759 0 0 0 .938 9.125c0 1.809.707 3.508 1.986 4.782a6.707 6.707 0 0 0 4.784 1.988 6.758 6.758 0 0 0 6.75-6.75 6.741 6.741 0 0 0-1.007-3.536z" fill="#2D2D30"/>
<path d="M12.6 6.134l-.094.071c-.269.333-.746 1.096-.91 2.375.057.277.092.495.092.545 0 2.206-1.794 4-4 4a3.986 3.986 0 0 1-2.817-1.164 3.987 3.987 0 0 1-1.163-2.815c0-2.206 1.794-4 4-4l.351.025v1.85S9.685 5.679 9.69 5.682l1.869-1.577-3.5-2.917v2.218l-.371-.03a5.75 5.75 0 0 0-4.055 9.826 5.75 5.75 0 0 0 9.826-4.056 5.725 5.725 0 0 0-.859-3.012z" fill="#C5C5C5"/>
</svg>

After

Width:  |  Height:  |  Size: 987 B

View File

@@ -0,0 +1,6 @@
<svg width="14px" height="14px" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<rect fill="#3c8746" x="0" y="0" width="100" height="100" rx="35" ry="35"/>
<text x="50" y="75" font-size="75" text-anchor="middle" style="font-family: Menlo, Monaco, Consolas, &quot;Droid Sans Mono&quot;, &quot;Inconsolata&quot;, &quot;Courier New&quot;, monospace, &quot;Droid Sans Fallback&quot;;" fill="white">
A
</text>
</svg>

After

Width:  |  Height:  |  Size: 431 B

View File

@@ -0,0 +1,6 @@
<svg width="14px" height="14px" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<rect fill="#7F4E7E" x="0" y="0" width="100" height="100" rx="35" ry="35"/>
<text x="50" y="75" font-size="75" text-anchor="middle" style="font-family: Menlo, Monaco, Consolas, &quot;Droid Sans Mono&quot;, &quot;Inconsolata&quot;, &quot;Courier New&quot;, monospace, &quot;Droid Sans Fallback&quot;;" fill="white">
C
</text>
</svg>

After

Width:  |  Height:  |  Size: 431 B

View File

@@ -0,0 +1,6 @@
<svg width="14px" height="14px" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<rect fill="#692C77" x="0" y="0" width="100" height="100" rx="35" ry="35"/>
<text x="50" y="75" font-size="75" text-anchor="middle" style="font-family: Menlo, Monaco, Consolas, &quot;Droid Sans Mono&quot;, &quot;Inconsolata&quot;, &quot;Courier New&quot;, monospace, &quot;Droid Sans Fallback&quot;;" fill="white">
C
</text>
</svg>

After

Width:  |  Height:  |  Size: 431 B

View File

@@ -0,0 +1,6 @@
<svg width="14px" height="14px" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<rect fill="#9E121D" x="0" y="0" width="100" height="100" rx="35" ry="35"/>
<text x="50" y="75" font-size="75" text-anchor="middle" style="font-family: Menlo, Monaco, Consolas, &quot;Droid Sans Mono&quot;, &quot;Inconsolata&quot;, &quot;Courier New&quot;, monospace, &quot;Droid Sans Fallback&quot;;" fill="white">
D
</text>
</svg>

After

Width:  |  Height:  |  Size: 431 B

View File

@@ -0,0 +1,6 @@
<svg width="14px" height="14px" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<rect fill="#969696" x="0" y="0" width="100" height="100" rx="35" ry="35"/>
<text x="50" y="75" font-size="75" text-anchor="middle" style="font-family: Menlo, Monaco, Consolas, &quot;Droid Sans Mono&quot;, &quot;Inconsolata&quot;, &quot;Courier New&quot;, monospace, &quot;Droid Sans Fallback&quot;;" fill="white">
I
</text>
</svg>

After

Width:  |  Height:  |  Size: 431 B

View File

@@ -0,0 +1,6 @@
<svg width="14px" height="14px" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<rect fill="#1B80B2" x="0" y="0" width="100" height="100" rx="35" ry="35"/>
<text x="50" y="75" font-size="75" text-anchor="middle" style="font-family: Menlo, Monaco, Consolas, &quot;Droid Sans Mono&quot;, &quot;Inconsolata&quot;, &quot;Courier New&quot;, monospace, &quot;Droid Sans Fallback&quot;;" fill="white">
M
</text>
</svg>

After

Width:  |  Height:  |  Size: 431 B

View File

@@ -0,0 +1,6 @@
<svg width="14px" height="14px" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<rect fill="#CC6633" x="0" y="0" width="100" height="100" rx="35" ry="35"/>
<text x="50" y="75" font-size="75" text-anchor="middle" style="font-family: Menlo, Monaco, Consolas, &quot;Droid Sans Mono&quot;, &quot;Inconsolata&quot;, &quot;Courier New&quot;, monospace, &quot;Droid Sans Fallback&quot;;" fill="white">
R
</text>
</svg>

After

Width:  |  Height:  |  Size: 431 B

View File

@@ -0,0 +1,6 @@
<svg width="14px" height="14px" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<rect fill="#6C6C6C" x="0" y="0" width="100" height="100" rx="35" ry="35"/>
<text x="50" y="75" font-size="75" text-anchor="middle" style="font-family: Menlo, Monaco, Consolas, &quot;Droid Sans Mono&quot;, &quot;Inconsolata&quot;, &quot;Courier New&quot;, monospace, &quot;Droid Sans Fallback&quot;;" fill="white">
U
</text>
</svg>

After

Width:  |  Height:  |  Size: 431 B

BIN
images/gitlens-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

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

View File

@@ -0,0 +1,13 @@
<?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">
<animate attributeName="fill" values="#000000;#F05133;#000000" dur="1s" repeatCount="indefinite"/>
</path>
</g>
</svg>

After

Width:  |  Height:  |  Size: 941 B

View File

Before

Width:  |  Height:  |  Size: 814 B

After

Width:  |  Height:  |  Size: 814 B

View File

Before

Width:  |  Height:  |  Size: 313 B

After

Width:  |  Height:  |  Size: 313 B

View File

@@ -0,0 +1 @@
<svg width="16" height="16" xmlns="http://www.w3.org/2000/svg"><title>Layer 1</title><rect height="11" width="3" y="3" x="7" fill="#424242"/><rect height="3" width="11" y="7" x="3" fill="#424242"/></svg>

After

Width:  |  Height:  |  Size: 203 B

View File

@@ -0,0 +1,4 @@
<?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" width="256" height="256" viewBox="0 0 14 16" xml:space="preserve">
<path fill="#6c6c6c" d="M10.86 7c-.45-1.72-2-3-3.86-3-1.86 0-3.41 1.28-3.86 3H0v2h3.14c.45 1.72 2 3 3.86 3 1.86 0 3.41-1.28 3.86-3H14V7h-3.14zM7 10.2c-1.22 0-2.2-.98-2.2-2.2 0-1.22.98-2.2 2.2-2.2 1.22 0 2.2.98 2.2 2.2 0 1.22-.98 2.2-2.2 2.2z"></path>
</svg>

After

Width:  |  Height:  |  Size: 480 B

View File

@@ -0,0 +1,5 @@
<?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="16" height="16">
<path d='M13.451 5.609l-.579-.939-1.068.812-.076.094c-.335.415-.927 1.341-1.124 2.876l-.021.165.033.163.071.345c0 1.654-1.346 3-3 3a2.98 2.98 0 0 1-2.107-.868 2.98 2.98 0 0 1-.873-2.111 3.004 3.004 0 0 1 2.351-2.929v2.926s2.528-2.087 2.984-2.461h.012L13.115 4.1 8.196 0H7.059v2.404A6.759 6.759 0 0 0 .938 9.125c0 1.809.707 3.508 1.986 4.782a6.707 6.707 0 0 0 4.784 1.988 6.758 6.758 0 0 0 6.75-6.75 6.741 6.741 0 0 0-1.007-3.536z' fill='#F6F6F6'/>
<path d='M12.6 6.134l-.094.071c-.269.333-.746 1.096-.91 2.375.057.277.092.495.092.545 0 2.206-1.794 4-4 4a3.986 3.986 0 0 1-2.817-1.164 3.987 3.987 0 0 1-1.163-2.815c0-2.206 1.794-4 4-4l.351.025v1.85S9.685 5.679 9.69 5.682l1.869-1.577-3.5-2.917v2.218l-.371-.03a5.75 5.75 0 0 0-4.055 9.826 5.75 5.75 0 0 0 9.826-4.056 5.725 5.725 0 0 0-.859-3.012z' fill='#424242'/>
</svg>

After

Width:  |  Height:  |  Size: 987 B

View File

@@ -0,0 +1,6 @@
<svg width="14px" height="14px" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<rect fill="#2d883e" x="0" y="0" width="100" height="100" rx="35" ry="35"/>
<text x="50" y="75" font-size="75" text-anchor="middle" style="font-family: Menlo, Monaco, Consolas, &quot;Droid Sans Mono&quot;, &quot;Inconsolata&quot;, &quot;Courier New&quot;, monospace, &quot;Droid Sans Fallback&quot;;" fill="white">
A
</text>
</svg>

After

Width:  |  Height:  |  Size: 431 B

View File

@@ -0,0 +1,6 @@
<svg width="14px" height="14px" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<rect fill="#9B4F96" x="0" y="0" width="100" height="100" rx="35" ry="35"/>
<text x="50" y="75" font-size="75" text-anchor="middle" style="font-family: Menlo, Monaco, Consolas, &quot;Droid Sans Mono&quot;, &quot;Inconsolata&quot;, &quot;Courier New&quot;, monospace, &quot;Droid Sans Fallback&quot;;" fill="white">
C
</text>
</svg>

After

Width:  |  Height:  |  Size: 431 B

View File

@@ -0,0 +1,6 @@
<svg width="14px" height="14px" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<rect fill="#682079" x="0" y="0" width="100" height="100" rx="35" ry="35"/>
<text x="50" y="75" font-size="75" text-anchor="middle" style="font-family: Menlo, Monaco, Consolas, &quot;Droid Sans Mono&quot;, &quot;Inconsolata&quot;, &quot;Courier New&quot;, monospace, &quot;Droid Sans Fallback&quot;;" fill="white">
C
</text>
</svg>

After

Width:  |  Height:  |  Size: 431 B

View File

@@ -0,0 +1,6 @@
<svg width="14px" height="14px" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<rect fill="#B9131A" x="0" y="0" width="100" height="100" rx="35" ry="35"/>
<text x="50" y="75" font-size="75" text-anchor="middle" style="font-family: Menlo, Monaco, Consolas, &quot;Droid Sans Mono&quot;, &quot;Inconsolata&quot;, &quot;Courier New&quot;, monospace, &quot;Droid Sans Fallback&quot;;" fill="white">
D
</text>
</svg>

After

Width:  |  Height:  |  Size: 431 B

View File

@@ -0,0 +1,6 @@
<svg width="14px" height="14px" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<rect fill="#969696" x="0" y="0" width="100" height="100" rx="35" ry="35"/>
<text x="50" y="75" font-size="75" text-anchor="middle" style="font-family: Menlo, Monaco, Consolas, &quot;Droid Sans Mono&quot;, &quot;Inconsolata&quot;, &quot;Courier New&quot;, monospace, &quot;Droid Sans Fallback&quot;;" fill="white">
I
</text>
</svg>

After

Width:  |  Height:  |  Size: 431 B

View File

@@ -0,0 +1,6 @@
<svg width="14px" height="14px" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<rect fill="#007ACC" x="0" y="0" width="100" height="100" rx="35" ry="35"/>
<text x="50" y="75" font-size="75" text-anchor="middle" style="font-family: Menlo, Monaco, Consolas, &quot;Droid Sans Mono&quot;, &quot;Inconsolata&quot;, &quot;Courier New&quot;, monospace, &quot;Droid Sans Fallback&quot;;" fill="white">
M
</text>
</svg>

After

Width:  |  Height:  |  Size: 431 B

View File

@@ -0,0 +1,6 @@
<svg width="14px" height="14px" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<rect fill="#CC6633" x="0" y="0" width="100" height="100" rx="35" ry="35"/>
<text x="50" y="75" font-size="75" text-anchor="middle" style="font-family: Menlo, Monaco, Consolas, &quot;Droid Sans Mono&quot;, &quot;Inconsolata&quot;, &quot;Courier New&quot;, monospace, &quot;Droid Sans Fallback&quot;;" fill="white">
R
</text>
</svg>

After

Width:  |  Height:  |  Size: 431 B

View File

@@ -0,0 +1,6 @@
<svg width="14px" height="14px" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<rect fill="#6C6C6C" x="0" y="0" width="100" height="100" rx="35" ry="35"/>
<text x="50" y="75" font-size="75" text-anchor="middle" style="font-family: Menlo, Monaco, Consolas, &quot;Droid Sans Mono&quot;, &quot;Inconsolata&quot;, &quot;Courier New&quot;, monospace, &quot;Droid Sans Fallback&quot;;" fill="white">
U
</text>
</svg>

After

Width:  |  Height:  |  Size: 431 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

1350
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,22 +1,22 @@
{ {
"name": "gitlens", "name": "gitlens",
"version": "4.1.0-beta.2", "version": "4.5.0-beta",
"author": { "author": {
"name": "Eric Amodio", "name": "Eric Amodio",
"email": "eamodio@gmail.com" "email": "eamodio@gmail.com"
}, },
"publisher": "eamodio", "publisher": "eamodio",
"engines": { "engines": {
"vscode": "^1.12.0" "vscode": "^1.15.0"
}, },
"license": "SEE LICENSE IN LICENSE", "license": "SEE LICENSE IN LICENSE",
"displayName": "Git Lens \u2014 git blame annotations, code lens, and more", "displayName": "Git Lens \u2014 git blame annotations, code lens, and more",
"description": "Supercharge Visual Studio Code's Git capabilities \u2014 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", "description": "Supercharge Visual Studio Code's Git capabilities \u2014 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 comparison commands, and so much more",
"badges": [ "badges": [
{ {
"url": "https://badges.gitter.im/vscode-gitlens/Lobby.svg", "url": "https://img.shields.io/badge/chat-on%20slack-brightgreen.svg",
"href": "https://gitter.im/vscode-gitlens/Lobby", "href": "https://join.slack.com/t/vscode-gitlens/shared_invite/MjIxOTgxNDE3NzM0LTE1MDE2Nzk1MTgtMjkwMmZjMzcxNQ",
"description": "Chat at https://gitter.im/vscode-gitlens/Lobby" "description": "Chat at https://vscode-gitlens.slack.com/"
} }
], ],
"categories": [ "categories": [
@@ -37,7 +37,7 @@
"color": "#56098c", "color": "#56098c",
"theme": "dark" "theme": "dark"
}, },
"icon": "images/gitlens-icon.svg", "icon": "images/gitlens-icon.png",
"preview": false, "preview": false,
"homepage": "https://github.com/eamodio/vscode-gitlens/blob/master/README.md", "homepage": "https://github.com/eamodio/vscode-gitlens/blob/master/README.md",
"bugs": { "bugs": {
@@ -209,7 +209,7 @@
"gitlens.blame.line.enabled": { "gitlens.blame.line.enabled": {
"type": "boolean", "type": "boolean",
"default": true, "default": true,
"description": "Specifies whether or not to provide a blame annotation for the current line" "description": "Specifies whether or not to provide a blame annotation for the current line, by default\nUse the `gitlens.toggleLineBlame` command to toggle the annotations on and off for the current session"
}, },
"gitlens.blame.line.annotationType": { "gitlens.blame.line.annotationType": {
"type": "string", "type": "string",
@@ -243,7 +243,7 @@
"gitlens.codeLens.enabled": { "gitlens.codeLens.enabled": {
"type": "boolean", "type": "boolean",
"default": true, "default": true,
"description": "Specifies whether or not to provide any Git code lens" "description": "Specifies whether or not to provide any Git code lens, by default\nUse the `gitlens.toggleCodeLens` command to toggle the Git code lens on and off for the current session"
}, },
"gitlens.codeLens.recentChange.enabled": { "gitlens.codeLens.recentChange.enabled": {
"type": "boolean", "type": "boolean",
@@ -351,6 +351,12 @@
"locations": [ "locations": [
"document" "document"
] ]
},
{
"language": "stylus",
"locations": [
"document"
]
} }
], ],
"items": { "items": {
@@ -407,6 +413,21 @@
"default": null, "default": null,
"description": "Specifies how all absolute dates will be formatted by default\nSee https://momentjs.com/docs/#/displaying/format/ for valid formats" "description": "Specifies how all absolute dates will be formatted by default\nSee https://momentjs.com/docs/#/displaying/format/ for valid formats"
}, },
"gitlens.fileHistoryExplorer.commitFormat": {
"type": "string",
"default": "${message} \u00a0\u2022\u00a0 ${authorAgo} \u00a0\u2022\u00a0 ${id}",
"description": "Specifies the format of committed changes in the `Git File History` explorer\nAvailable tokens\n ${id} - commit id\n ${author} - commit author\n ${message} - commit message\n ${ago} - relative commit date (e.g. 1 day ago)\n ${date} - formatted commit date (format specified by `gitlens.statusBar.dateFormat`)\n ${authorAgo} - commit author, relative commit date\nSee https://github.com/eamodio/vscode-gitlens/wiki/Advanced-Formatting for advanced formatting"
},
"gitlens.stashExplorer.stashFormat": {
"type": "string",
"default": "${message}",
"description": "Specifies the format of stashed changes in the `Git Stashes` explorer\nAvailable tokens\n ${id} - commit id\n ${author} - commit author\n ${message} - commit message\n ${ago} - relative commit date (e.g. 1 day ago)\n ${date} - formatted commit date (format specified by `gitlens.statusBar.dateFormat`)\n ${authorAgo} - commit author, relative commit date\nSee https://github.com/eamodio/vscode-gitlens/wiki/Advanced-Formatting for advanced formatting"
},
"gitlens.stashExplorer.stashFileFormat": {
"type": "string",
"default": "${filePath}",
"description": "Specifies the format of a stashed file in the `Git Stashes` explorer\nAvailable tokens\n ${file} - file name\n ${filePath} - file name and path\n ${path} - file path"
},
"gitlens.statusBar.enabled": { "gitlens.statusBar.enabled": {
"type": "boolean", "type": "boolean",
"default": true, "default": true,
@@ -498,11 +519,6 @@
"default": "rgba(0, 188, 242, 0.6)", "default": "rgba(0, 188, 242, 0.6)",
"description": "Specifies the light theme foreground color of an uncommitted line in the gutter blame annotations. Must be a valid css color" "description": "Specifies the light theme foreground color of an uncommitted line in the gutter blame annotations. Must be a valid css color"
}, },
"gitlens.theme.annotations.file.hover.separateLines": {
"type": "boolean",
"default": false,
"description": "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": { "gitlens.theme.annotations.line.trailing.dark.backgroundColor": {
"type": "string", "type": "string",
"default": null, "default": null,
@@ -738,7 +754,7 @@
}, },
"gitlens.advanced.toggleWhitespace.enabled": { "gitlens.advanced.toggleWhitespace.enabled": {
"type": "boolean", "type": "boolean",
"default": true, "default": false,
"description": "Specifies whether or not to toggle whitespace off then showing blame annotations (*may* be required by certain fonts/themes)" "description": "Specifies whether or not to toggle whitespace off then showing blame annotations (*may* be required by certain fonts/themes)"
} }
} }
@@ -751,7 +767,7 @@
}, },
{ {
"command": "gitlens.diffWithBranch", "command": "gitlens.diffWithBranch",
"title": "Compare File with...", "title": "Compare File with Branch...",
"category": "GitLens" "category": "GitLens"
}, },
{ {
@@ -769,6 +785,11 @@
"title": "Compare Line Commit with Previous", "title": "Compare Line Commit with Previous",
"category": "GitLens" "category": "GitLens"
}, },
{
"command": "gitlens.diffWithRevision",
"title": "Compare File with Revision...",
"category": "GitLens"
},
{ {
"command": "gitlens.diffWithWorking", "command": "gitlens.diffWithWorking",
"title": "Compare File with Working Tree", "title": "Compare File with Working Tree",
@@ -794,14 +815,36 @@
"title": "Toggle File Blame Annotations", "title": "Toggle File Blame Annotations",
"category": "GitLens", "category": "GitLens",
"icon": { "icon": {
"dark": "images/git-icon-dark.svg", "dark": "images/dark/git-icon.svg",
"light": "images/git-icon-light.svg" "light": "images/light/git-icon.svg"
}
},
{
"command": "gitlens.clearFileAnnotations",
"title": "Clear File Annotations",
"category": "GitLens",
"icon": {
"dark": "images/dark/git-icon-orange.svg",
"light": "images/light/git-icon-orange.svg"
}
},
{
"command": "gitlens.computingFileAnnotations",
"title": "Computing File Annotations...",
"category": "GitLens",
"icon": {
"dark": "images/dark/git-icon-progress.svg",
"light": "images/light/git-icon-progress.svg"
} }
}, },
{ {
"command": "gitlens.toggleFileRecentChanges", "command": "gitlens.toggleFileRecentChanges",
"title": "Toggle Recent File Changes Annotations", "title": "Toggle Recent File Changes Annotations",
"category": "GitLens" "category": "GitLens",
"icon": {
"dark": "images/dark/git-icon.svg",
"light": "images/light/git-icon.svg"
}
}, },
{ {
"command": "gitlens.toggleLineBlame", "command": "gitlens.toggleLineBlame",
@@ -840,7 +883,7 @@
}, },
{ {
"command": "gitlens.showQuickCommitFileDetails", "command": "gitlens.showQuickCommitFileDetails",
"title": "Show Line Commit Details", "title": "Show Commit File Details",
"category": "GitLens" "category": "GitLens"
}, },
{ {
@@ -895,7 +938,7 @@
}, },
{ {
"command": "gitlens.openCommitInRemote", "command": "gitlens.openCommitInRemote",
"title": "Open Line Commit in Remote", "title": "Open Commit in Remote",
"category": "GitLens" "category": "GitLens"
}, },
{ {
@@ -913,15 +956,96 @@
"title": "Apply Stashed Changes", "title": "Apply Stashed Changes",
"category": "GitLens" "category": "GitLens"
}, },
{
"command": "gitlens.stashDelete",
"title": "Delete Stashed Changes",
"category": "GitLens"
},
{ {
"command": "gitlens.stashSave", "command": "gitlens.stashSave",
"title": "Stash Changes", "title": "Stash Changes",
"category": "GitLens" "category": "GitLens",
"icon": {
"dark": "images/dark/icon-add.svg",
"light": "images/light/icon-add.svg"
}
}, },
{ {
"command": "gitlens.resetSuppressedWarnings", "command": "gitlens.resetSuppressedWarnings",
"title": "Reset Suppressed Warnings", "title": "Reset Suppressed Warnings",
"category": "GitLens" "category": "GitLens"
},
{
"command": "gitlens.gitExplorer.refresh",
"title": "Refresh",
"category": "GitLens",
"icon": {
"dark": "images/dark/icon-refresh.svg",
"light": "images/light/icon-refresh.svg"
}
},
{
"command": "gitlens.fileHistoryExplorer.refresh",
"title": "Refresh",
"category": "GitLens",
"icon": {
"dark": "images/dark/icon-refresh.svg",
"light": "images/light/icon-refresh.svg"
}
},
{
"command": "gitlens.fileHistoryExplorer.openChanges",
"title": "Open Changes",
"category": "GitLens"
},
{
"command": "gitlens.fileHistoryExplorer.openFile",
"title": "Open File",
"category": "GitLens"
},
{
"command": "gitlens.fileHistoryExplorer.openFileRevision",
"title": "Open File Revision",
"category": "GitLens"
},
{
"command": "gitlens.fileHistoryExplorer.openFileInRemote",
"title": "Open File in Remote",
"category": "GitLens"
},
{
"command": "gitlens.fileHistoryExplorer.openFileRevisionInRemote",
"title": "Open File Revision in Remote",
"category": "GitLens"
},
{
"command": "gitlens.stashExplorer.refresh",
"title": "Refresh",
"category": "GitLens",
"icon": {
"dark": "images/dark/icon-refresh.svg",
"light": "images/light/icon-refresh.svg"
}
},
{
"command": "gitlens.stashExplorer.openChanges",
"title": "Open Changes",
"category": "GitLens"
},
{
"command": "gitlens.stashExplorer.openFile",
"title": "Open File",
"category": "GitLens"
},
{
"command": "gitlens.stashExplorer.openStashedFile",
"title": "Open Stashed File",
"category": "GitLens"
},
{
"command": "gitlens.stashExplorer.openFileInRemote",
"title": "Open File in Remote",
"category": "GitLens"
} }
], ],
"menus": { "menus": {
@@ -946,6 +1070,10 @@
"command": "gitlens.diffLineWithPrevious", "command": "gitlens.diffLineWithPrevious",
"when": "gitlens:isBlameable" "when": "gitlens:isBlameable"
}, },
{
"command": "gitlens.diffWithRevision",
"when": "gitlens:isTracked"
},
{ {
"command": "gitlens.diffWithWorking", "command": "gitlens.diffWithWorking",
"when": "gitlens:isTracked" "when": "gitlens:isTracked"
@@ -966,6 +1094,14 @@
"command": "gitlens.toggleFileBlame", "command": "gitlens.toggleFileBlame",
"when": "gitlens:isBlameable" "when": "gitlens:isBlameable"
}, },
{
"command": "gitlens.clearFileAnnotations",
"when": "gitlens:annotationStatus == computed"
},
{
"command": "gitlens.computingFileAnnotations",
"when": "false"
},
{ {
"command": "gitlens.toggleFileRecentChanges", "command": "gitlens.toggleFileRecentChanges",
"when": "gitlens:isTracked" "when": "gitlens:isTracked"
@@ -1061,6 +1197,54 @@
{ {
"command": "gitlens.resetSuppressedWarnings", "command": "gitlens.resetSuppressedWarnings",
"when": "gitlens:enabled" "when": "gitlens:enabled"
},
{
"command": "gitlens.gitExplorer.refresh",
"when": "false"
},
{
"command": "gitlens.fileHistoryExplorer.refresh",
"when": "false"
},
{
"command": "gitlens.fileHistoryExplorer.openChanges",
"when": "false"
},
{
"command": "gitlens.fileHistoryExplorer.openFile",
"when": "false"
},
{
"command": "gitlens.fileHistoryExplorer.openFileRevision",
"when": "false"
},
{
"command": "gitlens.fileHistoryExplorer.openFileInRemote",
"when": "false"
},
{
"command": "gitlens.fileHistoryExplorer.openFileRevisionInRemote",
"when": "false"
},
{
"command": "gitlens.stashExplorer.refresh",
"when": "false"
},
{
"command": "gitlens.stashExplorer.openChanges",
"when": "false"
},
{
"command": "gitlens.stashExplorer.openFile",
"when": "false"
},
{
"command": "gitlens.stashExplorer.openStashedFile",
"when": "false"
},
{
"command": "gitlens.stashExplorer.openFileInRemote",
"when": "false"
} }
], ],
"editor/context": [ "editor/context": [
@@ -1118,7 +1302,18 @@
"editor/title": [ "editor/title": [
{ {
"command": "gitlens.toggleFileBlame", "command": "gitlens.toggleFileBlame",
"when": "gitlens:isBlameable && config.gitlens.advanced.menus.editorTitle.blame", "alt": "gitlens.toggleFileRecentChanges",
"when": "gitlens:isBlameable && !gitlens:annotationStatus && config.gitlens.advanced.menus.editorTitle.blame",
"group": "navigation@100"
},
{
"command": "gitlens.computingFileAnnotations",
"when": "gitlens:annotationStatus == computing && config.gitlens.advanced.menus.editorTitle.blame",
"group": "navigation@100"
},
{
"command": "gitlens.clearFileAnnotations",
"when": "gitlens:annotationStatus == computed && config.gitlens.advanced.menus.editorTitle.blame",
"group": "navigation@100" "group": "navigation@100"
}, },
{ {
@@ -1205,6 +1400,149 @@
"when": "gitlens:enabled && config.gitlens.advanced.menus.explorerContext.history", "when": "gitlens:enabled && config.gitlens.advanced.menus.explorerContext.history",
"group": "1_gitlens_1@1" "group": "1_gitlens_1@1"
} }
],
"scm/resourceGroup/context": [
{
"command": "gitlens.openChangedFiles",
"when": "gitlens:enabled",
"group": "1_gitlens@1"
},
{
"command": "gitlens.closeUnchangedFiles",
"when": "gitlens:enabled",
"group": "1_gitlens@2"
}
],
"scm/resourceState/context": [
{
"command": "gitlens.openFileInRemote",
"when": "gitlens:enabled && gitlens:hasRemotes",
"group": "navigation"
},
{
"command": "gitlens.diffWithRevision",
"when": "gitlens:enabled",
"group": "1_gitlens@1"
},
{
"command": "gitlens.showQuickFileHistory",
"when": "gitlens:enabled",
"group": "1_gitlens_1@1"
}
],
"view/title": [
{
"command": "gitlens.gitExplorer.refresh",
"when": "gitlens:enabled && view == gitlens.gitExplorer",
"group": "navigation"
},
{
"command": "gitlens.fileHistoryExplorer.refresh",
"when": "gitlens:enabled && view == gitlens.fileHistoryExplorer",
"group": "navigation"
},
{
"command": "gitlens.stashSave",
"when": "gitlens:enabled && view == gitlens.stashExplorer",
"group": "navigation@1"
},
{
"command": "gitlens.stashExplorer.refresh",
"when": "gitlens:enabled && view == gitlens.stashExplorer",
"group": "navigation@2"
}
],
"view/item/context": [
{
"command": "gitlens.openCommitInRemote",
"when": "gitlens:enabled && view == gitlens.gitExplorer && viewItem == commit",
"group": "1_gitlens@1"
},
{
"command": "gitlens.openFileInRemote",
"when": "gitlens:enabled && view == gitlens.gitExplorer && viewItem == commit-file",
"group": "1_gitlens@1"
},
{
"command": "gitlens.diffWithPrevious",
"when": "gitlens:enabled && view == gitlens.gitExplorer && viewItem == commit-file",
"group": "2_gitlens@1"
},
{
"command": "gitlens.diffWithWorking",
"when": "gitlens:enabled && view == gitlens.gitExplorer && viewItem == commit-file",
"group": "2_gitlens@2"
},
{
"command": "gitlens.fileHistoryExplorer.openChanges",
"when": "gitlens:enabled && view == gitlens.fileHistoryExplorer && viewItem == commit-file",
"group": "1_gitlens@1"
},
{
"command": "gitlens.diffWithWorking",
"when": "gitlens:enabled && view == gitlens.fileHistoryExplorer && viewItem == commit-file",
"group": "1_gitlens@2"
},
{
"command": "gitlens.fileHistoryExplorer.openFile",
"when": "gitlens:enabled && view == gitlens.fileHistoryExplorer && viewItem == commit-file",
"group": "2_gitlens@1"
},
{
"command": "gitlens.fileHistoryExplorer.openFileRevision",
"when": "gitlens:enabled && view == gitlens.fileHistoryExplorer && viewItem == commit-file",
"group": "2_gitlens@2"
},
{
"command": "gitlens.fileHistoryExplorer.openFileInRemote",
"when": "gitlens:enabled && view == gitlens.fileHistoryExplorer && viewItem == commit-file",
"group": "2_gitlens@3"
},
{
"command": "gitlens.fileHistoryExplorer.openFileRevisionInRemote",
"when": "gitlens:enabled && view == gitlens.fileHistoryExplorer && viewItem == commit-file",
"group": "2_gitlens@4"
},
{
"command": "gitlens.showQuickCommitDetails",
"when": "gitlens:enabled && view == gitlens.fileHistoryExplorer && viewItem == commit-file",
"group": "3_gitlens@1"
},
{
"command": "gitlens.stashApply",
"when": "gitlens:enabled && view == gitlens.stashExplorer && viewItem == stash-commit",
"group": "3_gitlens@1"
},
{
"command": "gitlens.stashDelete",
"when": "gitlens:enabled && view == gitlens.stashExplorer && viewItem == stash-commit",
"group": "3_gitlens@1"
},
{
"command": "gitlens.stashExplorer.openChanges",
"when": "gitlens:enabled && view == gitlens.stashExplorer && viewItem == commit-file",
"group": "1_gitlens@1"
},
{
"command": "gitlens.stashExplorer.openFile",
"when": "gitlens:enabled && view == gitlens.stashExplorer && viewItem == commit-file",
"group": "1_gitlens@2"
},
{
"command": "gitlens.stashExplorer.openStashedFile",
"when": "gitlens:enabled && view == gitlens.stashExplorer && viewItem == commit-file",
"group": "1_gitlens@3"
},
{
"command": "gitlens.stashExplorer.openFileInRemote",
"when": "gitlens:enabled && view == gitlens.stashExplorer && viewItem == commit-file",
"group": "1_gitlens@4"
},
{
"command": "gitlens.diffWithWorking",
"when": "gitlens:enabled && view == gitlens.stashExplorer && viewItem == commit-file",
"group": "2_gitlens@2"
}
] ]
}, },
"keybindings": [ "keybindings": [
@@ -1228,85 +1566,91 @@
"key": "alt+.", "key": "alt+.",
"when": "gitlens:key:." "when": "gitlens:key:."
}, },
{
"command": "gitlens.key.escape",
"key": "escape",
"when": "gitlens:key:escape"
},
{ {
"command": "gitlens.toggleFileBlame", "command": "gitlens.toggleFileBlame",
"key": "alt+b", "key": "alt+b",
"mac": "alt+b",
"when": "editorTextFocus && gitlens:isTracked" "when": "editorTextFocus && gitlens:isTracked"
}, },
{ {
"command": "gitlens.toggleCodeLens", "command": "gitlens.toggleCodeLens",
"key": "shift+alt+b", "key": "shift+alt+b",
"mac": "shift+alt+b",
"when": "editorTextFocus && gitlens:isTracked && gitlens:canToggleCodeLens" "when": "editorTextFocus && gitlens:isTracked && gitlens:canToggleCodeLens"
}, },
{ {
"command": "gitlens.showLastQuickPick", "command": "gitlens.showLastQuickPick",
"key": "alt+-", "key": "alt+-",
"mac": "alt+-",
"when": "gitlens:enabled" "when": "gitlens:enabled"
}, },
{ {
"command": "gitlens.showCommitSearch", "command": "gitlens.showCommitSearch",
"key": "alt+/", "key": "alt+/",
"mac": "alt+/",
"when": "gitlens:enabled" "when": "gitlens:enabled"
}, },
{ {
"command": "gitlens.showQuickFileHistory", "command": "gitlens.showQuickFileHistory",
"key": "alt+h", "key": "alt+h",
"mac": "alt+h",
"when": "gitlens:enabled" "when": "gitlens:enabled"
}, },
{ {
"command": "gitlens.showQuickRepoHistory", "command": "gitlens.showQuickRepoHistory",
"key": "shift+alt+h", "key": "shift+alt+h",
"mac": "shift+alt+h",
"when": "gitlens:enabled" "when": "gitlens:enabled"
}, },
{ {
"command": "gitlens.showQuickRepoStatus", "command": "gitlens.showQuickRepoStatus",
"key": "alt+s", "key": "alt+s",
"mac": "alt+s",
"when": "gitlens:enabled" "when": "gitlens:enabled"
}, },
{ {
"command": "gitlens.showQuickCommitFileDetails", "command": "gitlens.showQuickCommitFileDetails",
"key": "alt+c", "key": "alt+c",
"mac": "alt+c",
"when": "editorTextFocus && gitlens:enabled" "when": "editorTextFocus && gitlens:enabled"
}, },
{ {
"command": "gitlens.diffWithNext", "command": "gitlens.diffWithNext",
"key": "alt+.", "key": "alt+.",
"mac": "alt+.",
"when": "editorTextFocus && gitlens:isTracked" "when": "editorTextFocus && gitlens:isTracked"
}, },
{ {
"command": "gitlens.diffLineWithPrevious", "command": "gitlens.diffLineWithPrevious",
"key": "shift+alt+,", "key": "shift+alt+,",
"mac": "shift+alt+,",
"when": "editorTextFocus && gitlens:isTracked" "when": "editorTextFocus && gitlens:isTracked"
}, },
{ {
"command": "gitlens.diffWithPrevious", "command": "gitlens.diffWithPrevious",
"key": "alt+,", "key": "alt+,",
"mac": "alt+,",
"when": "editorTextFocus && gitlens:isTracked" "when": "editorTextFocus && gitlens:isTracked"
}, },
{ {
"command": "gitlens.diffLineWithWorking", "command": "gitlens.diffLineWithWorking",
"key": "alt+w", "key": "alt+w",
"mac": "alt+w",
"when": "editorTextFocus && gitlens:isTracked" "when": "editorTextFocus && gitlens:isTracked"
}, },
{ {
"command": "gitlens.diffWithWorking", "command": "gitlens.diffWithWorking",
"key": "shift+alt+w", "key": "shift+alt+w",
"mac": "shift+alt+w",
"when": "editorTextFocus && gitlens:isTracked" "when": "editorTextFocus && gitlens:isTracked"
} }
] ],
"views": {
"explorer": [
{
"id": "gitlens.fileHistoryExplorer",
"name": "Git File History",
"when": "gitlens:enabled && config.gitlens.insiders"
},
{
"id": "gitlens.stashExplorer",
"name": "Git Stashes",
"when": "gitlens:enabled"
}
]
}
}, },
"activationEvents": [ "activationEvents": [
"*" "*"
@@ -1323,27 +1667,28 @@
"vscode:prepublish": "npm install --no-save && npm run compile" "vscode:prepublish": "npm install --no-save && npm run compile"
}, },
"dependencies": { "dependencies": {
"applicationinsights": "0.20.1", "applicationinsights": "0.21.0",
"copy-paste": "1.3.0", "copy-paste": "1.3.0",
"iconv-lite": "0.4.17", "iconv-lite": "0.4.18",
"ignore": "3.3.3", "ignore": "3.3.4",
"lodash.debounce": "4.0.8", "lodash.debounce": "4.0.8",
"lodash.escaperegexp": "4.1.2", "lodash.escaperegexp": "4.1.2",
"lodash.isequal": "4.5.0", "lodash.isequal": "4.5.0",
"lodash.once": "4.1.1", "lodash.once": "4.1.1",
"moment": "2.18.1", "moment": "2.18.1",
"spawn-rx": "2.0.11", "spawn-rx": "2.0.11",
"tmp": "0.0.31" "string-width": "2.1.1",
"tmp": "0.0.33"
}, },
"devDependencies": { "devDependencies": {
"@types/copy-paste": "1.1.30", "@types/copy-paste": "1.1.30",
"@types/iconv-lite": "0.0.1", "@types/iconv-lite": "0.0.1",
"@types/mocha": "2.2.41", "@types/mocha": "2.2.42",
"@types/node": "7.0.29", "@types/node": "8.0.25",
"@types/tmp": "0.0.33", "@types/tmp": "0.0.33",
"mocha": "3.4.2", "mocha": "3.5.0",
"tslint": "5.4.3", "tslint": "5.7.0",
"typescript": "2.3.4", "typescript": "2.4.2",
"vscode": "1.1.0" "vscode": "1.1.5"
} }
} }

View File

@@ -1,15 +1,18 @@
'use strict'; 'use strict';
import { Functions, Objects } from '../system'; import { Functions, Objects } from '../system';
import { DecorationRenderOptions, Disposable, Event, EventEmitter, ExtensionContext, OverviewRulerLane, TextDocument, TextDocumentChangeEvent, TextEditor, TextEditorDecorationType, TextEditorViewColumnChangeEvent, window, workspace } from 'vscode'; import { DecorationRenderOptions, Disposable, Event, EventEmitter, ExtensionContext, OverviewRulerLane, Progress, ProgressLocation, TextDocument, TextDocumentChangeEvent, TextEditor, TextEditorDecorationType, TextEditorViewColumnChangeEvent, window, workspace } from 'vscode';
import { AnnotationProviderBase } from './annotationProvider'; import { AnnotationProviderBase } from './annotationProvider';
import { Keyboard, KeyboardScope, KeyCommand, Keys } from '../keyboard';
import { TextDocumentComparer, TextEditorComparer } from '../comparers'; import { TextDocumentComparer, TextEditorComparer } from '../comparers';
import { ExtensionKey, IConfig, LineHighlightLocations, themeDefaults } from '../configuration'; import { ExtensionKey, IConfig, LineHighlightLocations, themeDefaults } from '../configuration';
import { CommandContext, setCommandContext } from '../constants';
import { BlameabilityChangeEvent, GitContextTracker, GitService, GitUri } from '../gitService'; import { BlameabilityChangeEvent, GitContextTracker, GitService, GitUri } from '../gitService';
import { GutterBlameAnnotationProvider } from './gutterBlameAnnotationProvider'; import { GutterBlameAnnotationProvider } from './gutterBlameAnnotationProvider';
import { HoverBlameAnnotationProvider } from './hoverBlameAnnotationProvider'; import { HoverBlameAnnotationProvider } from './hoverBlameAnnotationProvider';
import { Logger } from '../logger'; import { Logger } from '../logger';
import { RecentChangesAnnotationProvider } from './recentChangesAnnotationProvider'; import { RecentChangesAnnotationProvider } from './recentChangesAnnotationProvider';
import { WhitespaceController } from './whitespaceController'; import { WhitespaceController } from './whitespaceController';
import * as path from 'path';
export type FileAnnotationType = 'gutter' | 'hover' | 'recentChanges'; export type FileAnnotationType = 'gutter' | 'hover' | 'recentChanges';
export const FileAnnotationType = { export const FileAnnotationType = {
@@ -113,7 +116,7 @@ export class AnnotationController extends Disposable {
? cfgTheme.dark.backgroundColor || themeDefaults.lineHighlight.dark.backgroundColor ? cfgTheme.dark.backgroundColor || themeDefaults.lineHighlight.dark.backgroundColor
: undefined, : undefined,
gutterIconPath: cfgBlameHighlight.locations.includes(LineHighlightLocations.Gutter) gutterIconPath: cfgBlameHighlight.locations.includes(LineHighlightLocations.Gutter)
? this.context.asAbsolutePath('images/blame-dark.svg') ? this.context.asAbsolutePath('images/dark/highlight-gutter.svg')
: undefined, : undefined,
overviewRulerColor: cfgBlameHighlight.locations.includes(LineHighlightLocations.OverviewRuler) overviewRulerColor: cfgBlameHighlight.locations.includes(LineHighlightLocations.OverviewRuler)
? cfgTheme.dark.overviewRulerColor || themeDefaults.lineHighlight.dark.overviewRulerColor ? cfgTheme.dark.overviewRulerColor || themeDefaults.lineHighlight.dark.overviewRulerColor
@@ -124,7 +127,7 @@ export class AnnotationController extends Disposable {
? cfgTheme.light.backgroundColor || themeDefaults.lineHighlight.light.backgroundColor ? cfgTheme.light.backgroundColor || themeDefaults.lineHighlight.light.backgroundColor
: undefined, : undefined,
gutterIconPath: cfgBlameHighlight.locations.includes(LineHighlightLocations.Gutter) gutterIconPath: cfgBlameHighlight.locations.includes(LineHighlightLocations.Gutter)
? this.context.asAbsolutePath('images/blame-light.svg') ? this.context.asAbsolutePath('images/light/highlight-gutter.svg')
: undefined, : undefined,
overviewRulerColor: cfgBlameHighlight.locations.includes(LineHighlightLocations.OverviewRuler) overviewRulerColor: cfgBlameHighlight.locations.includes(LineHighlightLocations.OverviewRuler)
? cfgTheme.light.overviewRulerColor || themeDefaults.lineHighlight.light.overviewRulerColor ? cfgTheme.light.overviewRulerColor || themeDefaults.lineHighlight.light.overviewRulerColor
@@ -147,7 +150,7 @@ export class AnnotationController extends Disposable {
? cfgTheme.dark.backgroundColor || themeDefaults.lineHighlight.dark.backgroundColor ? cfgTheme.dark.backgroundColor || themeDefaults.lineHighlight.dark.backgroundColor
: undefined, : undefined,
gutterIconPath: cfgChangesHighlight.locations.includes(LineHighlightLocations.Gutter) gutterIconPath: cfgChangesHighlight.locations.includes(LineHighlightLocations.Gutter)
? this.context.asAbsolutePath('images/blame-dark.svg') ? this.context.asAbsolutePath('images/dark/highlight-gutter.svg')
: undefined, : undefined,
overviewRulerColor: cfgChangesHighlight.locations.includes(LineHighlightLocations.OverviewRuler) overviewRulerColor: cfgChangesHighlight.locations.includes(LineHighlightLocations.OverviewRuler)
? cfgTheme.dark.overviewRulerColor || themeDefaults.lineHighlight.dark.overviewRulerColor ? cfgTheme.dark.overviewRulerColor || themeDefaults.lineHighlight.dark.overviewRulerColor
@@ -158,7 +161,7 @@ export class AnnotationController extends Disposable {
? cfgTheme.light.backgroundColor || themeDefaults.lineHighlight.light.backgroundColor ? cfgTheme.light.backgroundColor || themeDefaults.lineHighlight.light.backgroundColor
: undefined, : undefined,
gutterIconPath: cfgChangesHighlight.locations.includes(LineHighlightLocations.Gutter) gutterIconPath: cfgChangesHighlight.locations.includes(LineHighlightLocations.Gutter)
? this.context.asAbsolutePath('images/blame-light.svg') ? this.context.asAbsolutePath('images/light/highlight-gutter.svg')
: undefined, : undefined,
overviewRulerColor: cfgChangesHighlight.locations.includes(LineHighlightLocations.OverviewRuler) overviewRulerColor: cfgChangesHighlight.locations.includes(LineHighlightLocations.OverviewRuler)
? cfgTheme.light.overviewRulerColor || themeDefaults.lineHighlight.light.overviewRulerColor ? cfgTheme.light.overviewRulerColor || themeDefaults.lineHighlight.light.overviewRulerColor
@@ -193,13 +196,19 @@ export class AnnotationController extends Disposable {
async clear(column: number) { async clear(column: number) {
const provider = this._annotationProviders.get(column); const provider = this._annotationProviders.get(column);
if (!provider) return; if (provider === undefined) return;
this._annotationProviders.delete(column); this._annotationProviders.delete(column);
await provider.dispose(); await provider.dispose();
if (this._annotationProviders.size === 0) { if (this._annotationProviders.size === 0) {
Logger.log(`Remove listener registrations for annotations`); Logger.log(`Remove listener registrations for annotations`);
await setCommandContext(CommandContext.AnnotationStatus, undefined);
this._keyboardScope && this._keyboardScope.dispose();
this._keyboardScope = undefined;
this._annotationsDisposable && this._annotationsDisposable.dispose(); this._annotationsDisposable && this._annotationsDisposable.dispose();
this._annotationsDisposable = undefined; this._annotationsDisposable = undefined;
} }
@@ -207,17 +216,19 @@ export class AnnotationController extends Disposable {
this._onDidToggleAnnotations.fire(); this._onDidToggleAnnotations.fire();
} }
getAnnotationType(editor: TextEditor): FileAnnotationType | undefined { getAnnotationType(editor: TextEditor | undefined): FileAnnotationType | undefined {
const provider = this.getProvider(editor); const provider = this.getProvider(editor);
return provider === undefined ? undefined : provider.annotationType; return provider === undefined ? undefined : provider.annotationType;
} }
getProvider(editor: TextEditor): AnnotationProviderBase | undefined { getProvider(editor: TextEditor | undefined): AnnotationProviderBase | undefined {
if (!editor || !editor.document || !this.git.isEditorBlameable(editor)) return undefined; if (!editor || !editor.document || !this.git.isEditorBlameable(editor)) return undefined;
return this._annotationProviders.get(editor.viewColumn || -1); return this._annotationProviders.get(editor.viewColumn || -1);
} }
private _keyboardScope: KeyboardScope | undefined = undefined;
async showAnnotations(editor: TextEditor, type: FileAnnotationType, shaOrLine?: string | number): Promise<boolean> { async showAnnotations(editor: TextEditor, type: FileAnnotationType, shaOrLine?: string | number): Promise<boolean> {
if (!editor || !editor.document || !this.git.isEditorBlameable(editor)) return false; if (!editor || !editor.document || !this.git.isEditorBlameable(editor)) return false;
@@ -227,6 +238,50 @@ export class AnnotationController extends Disposable {
return true; return true;
} }
return window.withProgress({ location: ProgressLocation.Window }, async (progress: Progress<{ message: string }>) => {
await setCommandContext(CommandContext.AnnotationStatus, 'computing');
const computingAnnotations = this._showAnnotationsCore(currentProvider, editor, type, shaOrLine, progress);
const result = await computingAnnotations;
await setCommandContext(CommandContext.AnnotationStatus, result ? 'computed' : undefined);
return computingAnnotations;
});
}
private async _showAnnotationsCore(currentProvider: AnnotationProviderBase | undefined, editor: TextEditor, type: FileAnnotationType, shaOrLine?: string | number, progress?: Progress<{ message: string}>): Promise<boolean> {
if (progress !== undefined) {
let annotationsLabel = 'annotations';
switch (type) {
case FileAnnotationType.Gutter:
case FileAnnotationType.Hover:
annotationsLabel = 'blame annotations';
break;
case FileAnnotationType.RecentChanges:
annotationsLabel = 'recent changes annotations';
break;
}
progress!.report({ message: `Computing ${annotationsLabel} for ${path.basename(editor.document.fileName)}` });
}
// Allows pressing escape to exit the annotations
if (this._keyboardScope === undefined) {
this._keyboardScope = await Keyboard.instance.beginScope({
escape: {
onDidPressKey: (key: Keys) => {
const e = window.activeTextEditor;
if (e === undefined) return Promise.resolve(undefined);
this.clear(e.viewColumn || -1);
return Promise.resolve(undefined);
}
} as KeyCommand
});
}
const gitUri = await GitUri.fromUri(editor.document.uri, this.git); const gitUri = await GitUri.fromUri(editor.document.uri, this.git);
let provider: AnnotationProviderBase | undefined = undefined; let provider: AnnotationProviderBase | undefined = undefined;
@@ -258,7 +313,7 @@ export class AnnotationController extends Disposable {
subscriptions.push(window.onDidChangeTextEditorViewColumn(this._onTextEditorViewColumnChanged, this)); subscriptions.push(window.onDidChangeTextEditorViewColumn(this._onTextEditorViewColumnChanged, this));
subscriptions.push(workspace.onDidChangeTextDocument(this._onTextDocumentChanged, this)); subscriptions.push(workspace.onDidChangeTextDocument(this._onTextDocumentChanged, this));
subscriptions.push(workspace.onDidCloseTextDocument(this._onTextDocumentClosed, this)); subscriptions.push(workspace.onDidCloseTextDocument(this._onTextDocumentClosed, this));
subscriptions.push(this.gitContextTracker.onDidBlameabilityChange(this._onBlameabilityChanged, this)); subscriptions.push(this.gitContextTracker.onDidChangeBlameability(this._onBlameabilityChanged, this));
this._annotationsDisposable = Disposable.from(...subscriptions); this._annotationsDisposable = Disposable.from(...subscriptions);
} }
@@ -268,11 +323,12 @@ export class AnnotationController extends Disposable {
this._onDidToggleAnnotations.fire(); this._onDidToggleAnnotations.fire();
return true; return true;
} }
return false; return false;
} }
async toggleAnnotations(editor: TextEditor, type: FileAnnotationType, shaOrLine?: string | number): Promise<boolean> { async toggleAnnotations(editor: TextEditor, type: FileAnnotationType, shaOrLine?: string | number): Promise<boolean> {
if (!editor || !editor.document || type === FileAnnotationType.RecentChanges ? !this.git.isTrackable(editor.document.uri) : !this.git.isEditorBlameable(editor)) return false; if (!editor || !editor.document || (type === FileAnnotationType.RecentChanges ? !this.git.isTrackable(editor.document.uri) : !this.git.isEditorBlameable(editor))) return false;
const provider = this._annotationProviders.get(editor.viewColumn || -1); const provider = this._annotationProviders.get(editor.viewColumn || -1);
if (provider === undefined) return this.showAnnotations(editor, type, shaOrLine); if (provider === undefined) return this.showAnnotations(editor, type, shaOrLine);

View File

@@ -1,5 +1,5 @@
'use strict'; 'use strict';
import { Functions } from '../system'; // import { Functions } from '../system';
import { Disposable, ExtensionContext, TextDocument, TextEditor, TextEditorDecorationType, TextEditorSelectionChangeEvent, window, workspace } from 'vscode'; import { Disposable, ExtensionContext, TextDocument, TextEditor, TextEditorDecorationType, TextEditorSelectionChangeEvent, window, workspace } from 'vscode';
import { FileAnnotationType } from '../annotations/annotationController'; import { FileAnnotationType } from '../annotations/annotationController';
import { TextDocumentComparer } from '../comparers'; import { TextDocumentComparer } from '../comparers';
@@ -43,19 +43,12 @@ import { WhitespaceController } from './whitespaceController';
async clear() { async clear() {
if (this.editor !== undefined) { if (this.editor !== undefined) {
try { try {
if (this.decoration !== undefined) {
this.editor.setDecorations(this.decoration, []);
}
if (this.highlightDecoration !== undefined) { if (this.highlightDecoration !== undefined) {
this.editor.setDecorations(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.decoration !== undefined) {
await Functions.wait(1); this.editor.setDecorations(this.decoration, []);
if (this.highlightDecoration === undefined) return;
this.editor.setDecorations(this.highlightDecoration, []);
} }
} }
catch (ex) { } catch (ex) { }

View File

@@ -1,6 +1,8 @@
import { Strings } from '../system';
import { DecorationInstanceRenderOptions, DecorationOptions, ThemableDecorationRenderOptions } from 'vscode'; import { DecorationInstanceRenderOptions, DecorationOptions, ThemableDecorationRenderOptions } from 'vscode';
import { IThemeConfig, themeDefaults } from '../configuration'; import { IThemeConfig, themeDefaults } from '../configuration';
import { CommitFormatter, GitCommit, GitService, GitUri, ICommitFormatOptions } from '../gitService'; import { GlyphChars } from '../constants';
import { CommitFormatter, GitCommit, GitDiffChunkLine, GitService, GitUri, ICommitFormatOptions } from '../gitService';
import * as moment from 'moment'; import * as moment from 'moment';
interface IHeatmapConfig { interface IHeatmapConfig {
@@ -20,6 +22,8 @@ interface IRenderOptions {
} }
export const endOfLineIndex = 1000000; export const endOfLineIndex = 1000000;
const escapeMarkdownRegEx = /[`\>\#\*\_\-\+\.]/g;
// const sampleMarkdown = '## message `not code` *not important* _no underline_ \n> don\'t quote me \n- don\'t list me \n+ don\'t list me \n1. don\'t list me \nnot h1 \n=== \nnot h2 \n---\n***\n---\n___';
export class Annotations { export class Annotations {
@@ -43,16 +47,45 @@ export class Annotations {
return '#793738'; return '#793738';
} }
static getHoverMessage(commit: GitCommit, dateFormat: string | null): string | string[] {
if (dateFormat === null) {
dateFormat = 'MMMM Do, YYYY h:MMa';
}
let message = '';
if (!commit.isUncommitted) {
message = commit.message
// Escape markdown
.replace(escapeMarkdownRegEx, '\\$&')
// Escape markdown header (since the above regex won't match it)
.replace(/^===/gm, `${GlyphChars.ZeroWidthSpace}===`)
// Keep under the same block-quote
.replace(/\n/g, ' \n');
message = `\n\n> ${message}`;
}
return `\`${commit.shortSha}\` &nbsp; __${commit.author}__, ${moment(commit.date).fromNow()} &nbsp; _(${moment(commit.date).format(dateFormat)})_${message}`;
}
static getHoverDiffMessage(commit: GitCommit, chunkLine: GitDiffChunkLine | undefined): string | undefined {
if (chunkLine === undefined) return undefined;
const codeDiff = this._getCodeDiff(chunkLine);
return commit.isUncommitted
? `\`Changes\` &nbsp; ${GlyphChars.Dash} &nbsp; _uncommitted_\n${codeDiff}`
: `\`Changes\` &nbsp; ${GlyphChars.Dash} &nbsp; \`${commit.previousShortSha}\` ${GlyphChars.ArrowLeftRight} \`${commit.shortSha}\`\n${codeDiff}`;
}
private static _getCodeDiff(chunkLine: GitDiffChunkLine): string {
const previous = chunkLine.previous === undefined ? undefined : chunkLine.previous[0];
return `\`\`\`
- ${previous === undefined || previous.line === undefined ? '' : previous.line.trim()}
+ ${chunkLine.line === undefined ? '' : chunkLine.line.trim()}
\`\`\``;
}
static async changesHover(commit: GitCommit, line: number, uri: GitUri, git: GitService): Promise<DecorationOptions> { static async changesHover(commit: GitCommit, line: number, uri: GitUri, git: GitService): Promise<DecorationOptions> {
let message: string | undefined = undefined; const chunkLine = await git.getDiffForLine(uri, line + uri.offset, commit.isUncommitted ? undefined : commit.previousSha);
if (commit.isUncommitted) { const message = this.getHoverDiffMessage(commit, chunkLine);
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 { return {
hoverMessage: message hoverMessage: message
@@ -60,17 +93,14 @@ export class Annotations {
} }
static detailsHover(commit: GitCommit, dateFormat: string | null): DecorationOptions { static detailsHover(commit: GitCommit, dateFormat: string | null): DecorationOptions {
const message = CommitFormatter.toHoverAnnotation(commit, dateFormat); const message = this.getHoverMessage(commit, dateFormat);
return { return {
hoverMessage: message hoverMessage: message
} as DecorationOptions; } as DecorationOptions;
} }
static gutter(commit: GitCommit, format: string, dateFormatOrFormatOptions: string | null | ICommitFormatOptions, renderOptions: IRenderOptions, compact: boolean): DecorationOptions { static gutter(commit: GitCommit, format: string, dateFormatOrFormatOptions: string | null | ICommitFormatOptions, renderOptions: IRenderOptions): DecorationOptions {
let content = `\u00a0${CommitFormatter.fromTemplate(format, commit, dateFormatOrFormatOptions)}\u00a0`; const content = Strings.pad(CommitFormatter.fromTemplate(format, commit, dateFormatOrFormatOptions), 1, 1);
if (compact) {
content = '\u00a0'.repeat(content.length);
}
return { return {
renderOptions: { renderOptions: {
@@ -112,24 +142,25 @@ export class Annotations {
before: { before: {
borderStyle: borderStyle, borderStyle: borderStyle,
borderWidth: borderWidth, borderWidth: borderWidth,
height: cfgFileTheme.separateLines ? 'calc(100% - 1px)' : '100%', height: '100%',
margin: '0 26px 0 0', margin: '0 26px -1px 0'
textDecoration: 'none'
}, },
dark: { dark: {
backgroundColor: cfgFileTheme.dark.backgroundColor || undefined, backgroundColor: cfgFileTheme.dark.backgroundColor || undefined,
color: cfgFileTheme.dark.foregroundColor || themeDefaults.annotations.file.gutter.dark.foregroundColor color: cfgFileTheme.dark.foregroundColor || themeDefaults.annotations.file.gutter.dark.foregroundColor,
textDecoration: cfgFileTheme.separateLines ? 'overline solid rgba(0, 0, 0, .2)' : 'none'
} as DecorationInstanceRenderOptions, } as DecorationInstanceRenderOptions,
light: { light: {
backgroundColor: cfgFileTheme.light.backgroundColor || undefined, backgroundColor: cfgFileTheme.light.backgroundColor || undefined,
color: cfgFileTheme.light.foregroundColor || themeDefaults.annotations.file.gutter.light.foregroundColor color: cfgFileTheme.light.foregroundColor || themeDefaults.annotations.file.gutter.light.foregroundColor,
textDecoration: cfgFileTheme.separateLines ? 'overline solid rgba(0, 0, 0, .05)' : 'none'
} as DecorationInstanceRenderOptions } as DecorationInstanceRenderOptions
} as IRenderOptions; } as IRenderOptions;
} }
static hover(commit: GitCommit, renderOptions: IRenderOptions, heatmap: boolean, dateFormat: string | null): DecorationOptions { static hover(commit: GitCommit, renderOptions: IRenderOptions, heatmap: boolean, dateFormat: string | null): DecorationOptions {
return { return {
hoverMessage: CommitFormatter.toHoverAnnotation(commit, dateFormat), hoverMessage: this.getHoverMessage(commit, dateFormat),
renderOptions: heatmap ? { before: { ...renderOptions.before } } : undefined renderOptions: heatmap ? { before: { ...renderOptions.before } } : undefined
} as DecorationOptions; } as DecorationOptions;
} }
@@ -141,8 +172,8 @@ export class Annotations {
before: { before: {
borderStyle: 'solid', borderStyle: 'solid',
borderWidth: '0 0 0 2px', borderWidth: '0 0 0 2px',
contentText: '\u200B', contentText: GlyphChars.ZeroWidthSpace,
height: cfgTheme.annotations.file.hover.separateLines ? 'calc(100% - 1px)' : '100%', height: '100%',
margin: '0 26px 0 0', margin: '0 26px 0 0',
textDecoration: 'none' textDecoration: 'none'
} }
@@ -154,7 +185,7 @@ export class Annotations {
return { return {
renderOptions: { renderOptions: {
after: { after: {
contentText: `\u00a0${message}\u00a0` contentText: Strings.pad(message, 1, 1)
}, },
dark: { dark: {
after: { after: {

View File

@@ -7,7 +7,7 @@ import { WhitespaceController } from './whitespaceController';
export abstract class BlameAnnotationProviderBase extends AnnotationProviderBase { export abstract class BlameAnnotationProviderBase extends AnnotationProviderBase {
protected _blame: Promise<GitBlame>; protected _blame: Promise<GitBlame | undefined>;
constructor(context: ExtensionContext, editor: TextEditor, decoration: TextEditorDecorationType | undefined, highlightDecoration: TextEditorDecorationType | undefined, whitespaceController: WhitespaceController | undefined, protected git: GitService, protected uri: GitUri) { constructor(context: ExtensionContext, editor: TextEditor, decoration: TextEditorDecorationType | undefined, highlightDecoration: TextEditorDecorationType | undefined, whitespaceController: WhitespaceController | undefined, protected git: GitService, protected uri: GitUri) {
super(context, editor, decoration, highlightDecoration, whitespaceController); super(context, editor, decoration, highlightDecoration, whitespaceController);
@@ -63,7 +63,7 @@ export abstract class BlameAnnotationProviderBase extends AnnotationProviderBase
whitespacePromise = this.whitespaceController && this.whitespaceController.override(); whitespacePromise = this.whitespaceController && this.whitespaceController.override();
} }
let blame: GitBlame; let blame: GitBlame | undefined;
if (whitespacePromise) { if (whitespacePromise) {
[blame] = await Promise.all([this._blame, whitespacePromise]); [blame] = await Promise.all([this._blame, whitespacePromise]);
} }
@@ -71,7 +71,7 @@ export abstract class BlameAnnotationProviderBase extends AnnotationProviderBase
blame = await this._blame; blame = await this._blame;
} }
if (!blame || !blame.lines.length) { if (blame === undefined || !blame.lines.length) {
this.whitespaceController && await this.whitespaceController.restore(); this.whitespaceController && await this.whitespaceController.restore();
return undefined; return undefined;
} }

View File

@@ -2,9 +2,10 @@
import { Strings } from '../system'; import { Strings } from '../system';
import { DecorationOptions, Range } from 'vscode'; import { DecorationOptions, Range } from 'vscode';
import { FileAnnotationType } from './annotationController'; import { FileAnnotationType } from './annotationController';
import { BlameAnnotationProviderBase } from './blameAnnotationProvider';
import { Annotations, endOfLineIndex } from './annotations'; import { Annotations, endOfLineIndex } from './annotations';
import { ICommitFormatOptions } from '../gitService'; import { BlameAnnotationProviderBase } from './blameAnnotationProvider';
import { GlyphChars } from '../constants';
import { GitBlameCommit, ICommitFormatOptions } from '../gitService';
import * as moment from 'moment'; import * as moment from 'moment';
export class GutterBlameAnnotationProvider extends BlameAnnotationProviderBase { export class GutterBlameAnnotationProvider extends BlameAnnotationProviderBase {
@@ -15,12 +16,14 @@ export class GutterBlameAnnotationProvider extends BlameAnnotationProviderBase {
const blame = await this.getBlame(true); const blame = await this.getBlame(true);
if (blame === undefined) return false; if (blame === undefined) return false;
// console.time('Computing blame annotations...');
const cfg = this._config.annotations.file.gutter; const cfg = this._config.annotations.file.gutter;
// Precalculate the formatting options so we don't need to do it on each iteration // Precalculate the formatting options so we don't need to do it on each iteration
const tokenOptions = Strings.getTokensFromTemplate(cfg.format) const tokenOptions = Strings.getTokensFromTemplate(cfg.format)
.reduce((map, token) => { .reduce((map, token) => {
map[token.key] = token.options; map[token.key] = token.options as ICommitFormatOptions;
return map; return map;
}, {} as { [token: string]: ICommitFormatOptions }); }, {} as { [token: string]: ICommitFormatOptions });
@@ -31,37 +34,78 @@ export class GutterBlameAnnotationProvider extends BlameAnnotationProviderBase {
const now = moment(); const now = moment();
const offset = this.uri.offset; const offset = this.uri.offset;
let previousLine: string | undefined = undefined;
const renderOptions = Annotations.gutterRenderOptions(this._config.theme, cfg.heatmap); const renderOptions = Annotations.gutterRenderOptions(this._config.theme, cfg.heatmap);
const dateFormat = this._config.defaultDateFormat; const dateFormat = this._config.defaultDateFormat;
const separateLines = this._config.theme.annotations.file.gutter.separateLines;
const decorations: DecorationOptions[] = []; const decorations: DecorationOptions[] = [];
const document = this.document;
let commit: GitBlameCommit | undefined;
let compacted = false;
let details: DecorationOptions | undefined;
let gutter: DecorationOptions | undefined;
let previousSha: string | undefined;
for (const l of blame.lines) { for (const l of blame.lines) {
const commit = blame.commits.get(l.sha); commit = blame.commits.get(l.sha);
if (commit === undefined) continue; if (commit === undefined) continue;
const line = l.line + offset; const line = l.line + offset;
const gutter = Annotations.gutter(commit, cfg.format, options, renderOptions, cfg.compact && previousLine === l.sha); if (previousSha === l.sha) {
// Use a shallow copy of the previous decoration options
gutter = { ...gutter } as DecorationOptions;
if (cfg.compact && !compacted) {
// Since we are wiping out the contextText make sure to copy the objects
gutter.renderOptions = { ...gutter.renderOptions };
gutter.renderOptions.before = {
...gutter.renderOptions.before,
...{ contentText: GlyphChars.Space.repeat(gutter.renderOptions!.before!.contentText!.length) }
};
if (cfg.compact) { if (separateLines) {
const isEmptyOrWhitespace = this.document.lineAt(line).isEmptyOrWhitespace; gutter.renderOptions.dark = { ...gutter.renderOptions.dark };
previousLine = isEmptyOrWhitespace ? undefined : l.sha; gutter.renderOptions.dark.before = { ...gutter.renderOptions.dark.before, ...{ textDecoration: 'none' } };
gutter.renderOptions.light = { ...gutter.renderOptions.light };
gutter.renderOptions.light.before = { ...gutter.renderOptions.light.before, ...{ textDecoration: 'none' } };
}
compacted = true;
}
const endIndex = document.lineAt(line).firstNonWhitespaceCharacterIndex;
gutter.range = new Range(line, 0, line, endIndex);
decorations.push(gutter);
if (details !== undefined) {
details = { ...details } as DecorationOptions;
details.range = cfg.hover.wholeLine
? document.validateRange(new Range(line, 0, line, endOfLineIndex))
: gutter.range;
decorations.push(details);
}
continue;
} }
compacted = false;
previousSha = l.sha;
gutter = Annotations.gutter(commit, cfg.format, options, renderOptions);
if (cfg.heatmap.enabled) { if (cfg.heatmap.enabled) {
Annotations.applyHeatmap(gutter, commit.date, now); Annotations.applyHeatmap(gutter, commit.date, now);
} }
const firstNonWhitespace = this.editor.document.lineAt(line).firstNonWhitespaceCharacterIndex; const endIndex = document.lineAt(line).firstNonWhitespaceCharacterIndex;
gutter.range = this.editor.document.validateRange(new Range(line, 0, line, firstNonWhitespace)); gutter.range = new Range(line, 0, line, endIndex);
decorations.push(gutter); decorations.push(gutter);
if (cfg.hover.details) { if (cfg.hover.details) {
const details = Annotations.detailsHover(commit, dateFormat); details = Annotations.detailsHover(commit, dateFormat);
details.range = cfg.hover.wholeLine details.range = cfg.hover.wholeLine
? this.editor.document.validateRange(new Range(line, 0, line, endOfLineIndex)) ? document.validateRange(new Range(line, 0, line, endOfLineIndex))
: gutter.range; : gutter.range;
decorations.push(details); decorations.push(details);
} }
@@ -71,6 +115,8 @@ export class GutterBlameAnnotationProvider extends BlameAnnotationProviderBase {
this.editor.setDecorations(this.decoration!, decorations); this.editor.setDecorations(this.decoration!, decorations);
} }
// console.timeEnd('Computing blame annotations...');
this.selection(shaOrLine, blame); this.selection(shaOrLine, blame);
return true; return true;
} }

View File

@@ -1,8 +1,9 @@
'use strict'; 'use strict';
import { DecorationOptions, Range } from 'vscode'; import { DecorationOptions, Range } from 'vscode';
import { FileAnnotationType } from './annotationController'; import { FileAnnotationType } from './annotationController';
import { BlameAnnotationProviderBase } from './blameAnnotationProvider';
import { Annotations, endOfLineIndex } from './annotations'; import { Annotations, endOfLineIndex } from './annotations';
import { BlameAnnotationProviderBase } from './blameAnnotationProvider';
import { GitBlameCommit } from '../gitService';
import * as moment from 'moment'; import * as moment from 'moment';
export class HoverBlameAnnotationProvider extends BlameAnnotationProviderBase { export class HoverBlameAnnotationProvider extends BlameAnnotationProviderBase {
@@ -13,6 +14,8 @@ export class HoverBlameAnnotationProvider extends BlameAnnotationProviderBase {
const blame = await this.getBlame(this._config.annotations.file.hover.heatmap.enabled); const blame = await this.getBlame(this._config.annotations.file.hover.heatmap.enabled);
if (blame === undefined) return false; if (blame === undefined) return false;
// console.time('Computing blame annotations...');
const cfg = this._config.annotations.file.hover; const cfg = this._config.annotations.file.hover;
const now = moment(); const now = moment();
@@ -21,17 +24,26 @@ export class HoverBlameAnnotationProvider extends BlameAnnotationProviderBase {
const dateFormat = this._config.defaultDateFormat; const dateFormat = this._config.defaultDateFormat;
const decorations: DecorationOptions[] = []; const decorations: DecorationOptions[] = [];
const document = this.document;
let commit: GitBlameCommit | undefined;
let hover: DecorationOptions | undefined;
for (const l of blame.lines) { for (const l of blame.lines) {
const commit = blame.commits.get(l.sha); commit = blame.commits.get(l.sha);
if (commit === undefined) continue; if (commit === undefined) continue;
const line = l.line + offset; const line = l.line + offset;
const hover = Annotations.hover(commit, renderOptions, cfg.heatmap.enabled, dateFormat); hover = Annotations.hover(commit, renderOptions, cfg.heatmap.enabled, dateFormat);
const endIndex = cfg.wholeLine ? endOfLineIndex : this.editor.document.lineAt(line).firstNonWhitespaceCharacterIndex; if (cfg.wholeLine) {
hover.range = this.editor.document.validateRange(new Range(line, 0, line, endIndex)); hover.range = document.validateRange(new Range(line, 0, line, endOfLineIndex));
}
else {
const endIndex = document.lineAt(line).firstNonWhitespaceCharacterIndex;
hover.range = new Range(line, 0, line, endIndex);
}
if (cfg.heatmap.enabled) { if (cfg.heatmap.enabled) {
Annotations.applyHeatmap(hover, commit.date, now); Annotations.applyHeatmap(hover, commit.date, now);
@@ -44,6 +56,8 @@ export class HoverBlameAnnotationProvider extends BlameAnnotationProviderBase {
this.editor.setDecorations(this.decoration!, decorations); this.editor.setDecorations(this.decoration!, decorations);
} }
// console.timeEnd('Computing blame annotations...');
this.selection(shaOrLine, blame); this.selection(shaOrLine, blame);
return true; return true;
} }

View File

@@ -1,9 +1,9 @@
'use strict'; 'use strict';
import { DecorationOptions, ExtensionContext, Position, Range, TextEditor, TextEditorDecorationType } from 'vscode'; import { DecorationOptions, ExtensionContext, Position, Range, TextEditor, TextEditorDecorationType } from 'vscode';
import { endOfLineIndex } from './annotations'; import { Annotations, endOfLineIndex } from './annotations';
import { FileAnnotationType } from './annotationController'; import { FileAnnotationType } from './annotationController';
import { AnnotationProviderBase } from './annotationProvider'; import { AnnotationProviderBase } from './annotationProvider';
import { CommitFormatter, GitService, GitUri } from '../gitService'; import { GitService, GitUri } from '../gitService';
export class RecentChangesAnnotationProvider extends AnnotationProviderBase { export class RecentChangesAnnotationProvider extends AnnotationProviderBase {
@@ -27,12 +27,12 @@ export class RecentChangesAnnotationProvider extends AnnotationProviderBase {
for (const chunk of diff.chunks) { for (const chunk of diff.chunks) {
let count = chunk.currentPosition.start - 2; let count = chunk.currentPosition.start - 2;
for (const change of chunk.current) { for (const line of chunk.lines) {
if (change === undefined) continue; if (line.line === undefined) continue;
count++; count++;
if (change.state === 'unchanged') continue; if (line.state === 'unchanged') continue;
let endingIndex = 0; let endingIndex = 0;
if (cfg.hover.details || cfg.hover.changes) { if (cfg.hover.details || cfg.hover.changes) {
@@ -43,14 +43,14 @@ export class RecentChangesAnnotationProvider extends AnnotationProviderBase {
if (cfg.hover.details) { if (cfg.hover.details) {
decorators.push({ decorators.push({
hoverMessage: CommitFormatter.toHoverAnnotation(commit, dateFormat), hoverMessage: Annotations.getHoverMessage(commit, dateFormat),
range: range range: range
} as DecorationOptions); } as DecorationOptions);
} }
let message: string | undefined = undefined; let message: string | undefined = undefined;
if (cfg.hover.changes) { if (cfg.hover.changes) {
message = CommitFormatter.toHoverDiff(commit, chunk.previous[count], change); message = Annotations.getHoverDiffMessage(commit, line);
} }
decorators.push({ decorators.push({

81
src/codeLensController.ts Normal file
View File

@@ -0,0 +1,81 @@
'use strict';
import { Objects } from './system';
import { Disposable, ExtensionContext, languages, TextEditor, workspace } from 'vscode';
import { IConfig } from './configuration';
import { CommandContext, ExtensionKey, setCommandContext } from './constants';
import { GitCodeLensProvider } from './gitCodeLensProvider';
import { GitService } from './gitService';
import { Logger } from './logger';
export class CodeLensController extends Disposable {
private _codeLensProvider: GitCodeLensProvider | undefined;
private _codeLensProviderDisposable: Disposable | undefined;
private _config: IConfig;
private _disposable: Disposable | undefined;
constructor(private context: ExtensionContext, private git: GitService) {
super(() => this.dispose());
this._onConfigurationChanged();
const subscriptions: Disposable[] = [];
subscriptions.push(workspace.onDidChangeConfiguration(this._onConfigurationChanged, this));
subscriptions.push(git.onDidChangeGitCache(this._onGitCacheChanged, this));
this._disposable = Disposable.from(...subscriptions);
}
dispose() {
this._disposable && this._disposable.dispose();
this._codeLensProviderDisposable && this._codeLensProviderDisposable.dispose();
this._codeLensProviderDisposable = undefined;
this._codeLensProvider = undefined;
}
private _onConfigurationChanged() {
const cfg = workspace.getConfiguration().get<IConfig>(ExtensionKey)!;
if (!Objects.areEquivalent(cfg.codeLens, this._config && this._config.codeLens)) {
Logger.log('CodeLens config changed; resetting CodeLens provider');
if (cfg.codeLens.enabled && (cfg.codeLens.recentChange.enabled || cfg.codeLens.authors.enabled)) {
if (this._codeLensProvider) {
this._codeLensProvider.reset();
}
else {
this._codeLensProvider = new GitCodeLensProvider(this.context, this.git);
this._codeLensProviderDisposable = languages.registerCodeLensProvider(GitCodeLensProvider.selector, this._codeLensProvider);
}
}
else {
this._codeLensProviderDisposable && this._codeLensProviderDisposable.dispose();
this._codeLensProviderDisposable = undefined;
this._codeLensProvider = undefined;
}
setCommandContext(CommandContext.CanToggleCodeLens, cfg.codeLens.recentChange.enabled || cfg.codeLens.authors.enabled);
}
this._config = cfg;
}
private _onGitCacheChanged() {
Logger.log('Git cache changed; resetting CodeLens provider');
this._codeLensProvider && this._codeLensProvider.reset();
}
toggleCodeLens(editor: TextEditor) {
if (!this._config.codeLens.recentChange.enabled && !this._config.codeLens.authors.enabled) return;
Logger.log(`toggleCodeLens()`);
if (this._codeLensProviderDisposable) {
this._codeLensProviderDisposable.dispose();
this._codeLensProviderDisposable = undefined;
return;
}
this._codeLensProviderDisposable = languages.registerCodeLensProvider(GitCodeLensProvider.selector, new GitCodeLensProvider(this.context, this.git));
}
}

View File

@@ -1,8 +1,7 @@
'use strict'; 'use strict';
export * from './commands/common'; export * from './commands/common';
export * from './commands/keyboard'; export * from './commands/clearFileAnnotations';
export * from './commands/closeUnchangedFiles'; export * from './commands/closeUnchangedFiles';
export * from './commands/copyMessageToClipboard'; export * from './commands/copyMessageToClipboard';
export * from './commands/copyShaToClipboard'; export * from './commands/copyShaToClipboard';
@@ -12,6 +11,7 @@ export * from './commands/diffLineWithWorking';
export * from './commands/diffWithBranch'; export * from './commands/diffWithBranch';
export * from './commands/diffWithNext'; export * from './commands/diffWithNext';
export * from './commands/diffWithPrevious'; export * from './commands/diffWithPrevious';
export * from './commands/diffWithRevision';
export * from './commands/diffWithWorking'; export * from './commands/diffWithWorking';
export * from './commands/openChangedFiles'; export * from './commands/openChangedFiles';
export * from './commands/openBranchInRemote'; export * from './commands/openBranchInRemote';

View File

@@ -0,0 +1,24 @@
'use strict';
import { TextEditor, TextEditorEdit, Uri, window } from 'vscode';
import { AnnotationController } from '../annotations/annotationController';
import { Commands, EditorCommand } from './common';
import { Logger } from '../logger';
export class ClearFileAnnotationsCommand extends EditorCommand {
constructor(private annotationController: AnnotationController) {
super(Commands.ClearFileAnnotations);
}
async execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri): Promise<any> {
if (editor === undefined || editor.document === undefined || editor.document.isDirty) return undefined;
try {
return this.annotationController.clear(editor.viewColumn || -1);
}
catch (ex) {
Logger.error(ex, 'ClearFileAnnotationsCommand');
return window.showErrorMessage(`Unable to clear file annotations. See output channel for more details`);
}
}
}

View File

@@ -1,80 +1,87 @@
'use strict'; 'use strict';
import { commands, TextEditor, Uri, window } from 'vscode'; import { commands, TextEditor, Uri, window } from 'vscode';
import { ActiveEditorTracker } from '../activeEditorTracker'; import { ActiveEditorTracker } from '../activeEditorTracker';
import { ActiveEditorCommand, Commands, getCommandUri } from './common'; import { ActiveEditorCommand, Commands, getCommandUri } from './common';
import { TextEditorComparer, UriComparer } from '../comparers'; import { TextEditorComparer, UriComparer } from '../comparers';
import { BuiltInCommands } from '../constants'; import { BuiltInCommands } from '../constants';
import { GitService } from '../gitService'; import { GitService } from '../gitService';
import { Logger } from '../logger'; import { Logger } from '../logger';
import { Messages } from '../messages'; import { Messages } from '../messages';
export interface CloseUnchangedFilesCommandArgs { export interface CloseUnchangedFilesCommandArgs {
uris?: Uri[]; uris?: Uri[];
} }
export class CloseUnchangedFilesCommand extends ActiveEditorCommand { export class CloseUnchangedFilesCommand extends ActiveEditorCommand {
constructor(private git: GitService) { constructor(private git: GitService) {
super(Commands.CloseUnchangedFiles); super(Commands.CloseUnchangedFiles);
} }
async execute(editor: TextEditor, uri?: Uri, args: CloseUnchangedFilesCommandArgs = {}) { async execute(editor?: TextEditor, uri?: Uri, args: CloseUnchangedFilesCommandArgs = {}) {
uri = getCommandUri(uri, editor); uri = getCommandUri(uri, editor);
try { try {
if (args.uris === undefined) { if (args.uris === undefined) {
const repoPath = await this.git.getRepoPathFromUri(uri); args = { ...args };
if (!repoPath) return Messages.showNoRepositoryWarningMessage(`Unable to close unchanged files`);
const repoPath = await this.git.getRepoPathFromUri(uri);
const status = await this.git.getStatusForRepo(repoPath); if (!repoPath) return Messages.showNoRepositoryWarningMessage(`Unable to close unchanged files`);
if (status === undefined) return window.showWarningMessage(`Unable to close unchanged files`);
const status = await this.git.getStatusForRepo(repoPath);
args.uris = status.files.map(_ => _.Uri); if (status === undefined) return window.showWarningMessage(`Unable to close unchanged files`);
}
args.uris = status.files.map(_ => _.Uri);
if (args.uris.length === 0) return commands.executeCommand(BuiltInCommands.CloseAllEditors); }
const editorTracker = new ActiveEditorTracker(); if (args.uris.length === 0) return commands.executeCommand(BuiltInCommands.CloseAllEditors);
let count = 0; const editorTracker = new ActiveEditorTracker();
let previous = undefined;
let editor = window.activeTextEditor; let count = 0;
while (true) { let previous = undefined;
if (editor !== undefined) { let editor = window.activeTextEditor;
if (TextEditorComparer.equals(previous, editor, { useId: true, usePosition: true })) { while (true) {
break; if (editor !== undefined) {
} if (TextEditorComparer.equals(previous, editor, { useId: true, usePosition: true })) {
break;
if (editor.document !== undefined && }
(editor.document.isDirty || args.uris.some(_ => UriComparer.equals(_, editor!.document && editor!.document.uri)))) {
previous = editor; if (editor.document !== undefined &&
editor = await editorTracker.awaitNext(500); (editor.document.isDirty || args.uris.some(_ => UriComparer.equals(_, editor!.document && editor!.document.uri)))) {
continue; const lastPrevious = previous;
} previous = editor;
} editor = await editorTracker.awaitNext(500);
previous = editor; if (TextEditorComparer.equals(lastPrevious, editor, { useId: true, usePosition: true })) {
editor = await editorTracker.awaitClose(500); break;
}
if (previous === undefined && editor === undefined) { continue;
count++; }
// This is such a shitty hack, but I can't figure out any other reliable way to know that we've cycled through all the editors :( }
if (count >= 4) {
break; previous = editor;
} editor = await editorTracker.awaitClose(500);
}
else { if (previous === undefined && editor === undefined) {
count = 0; count++;
} // This is such a shitty hack, but I can't figure out any other reliable way to know that we've cycled through all the editors :(
} if (count >= 4) {
break;
editorTracker.dispose(); }
}
return undefined; else {
} count = 0;
catch (ex) { }
Logger.error(ex, 'CloseUnchangedFilesCommand'); }
return window.showErrorMessage(`Unable to close unchanged files. See output channel for more details`);
} editorTracker.dispose();
}
return undefined;
}
catch (ex) {
Logger.error(ex, 'CloseUnchangedFilesCommand');
return window.showErrorMessage(`Unable to close unchanged files. See output channel for more details`);
}
}
} }

View File

@@ -1,10 +1,12 @@
'use strict'; 'use strict';
import { commands, Disposable, TextDocumentShowOptions, TextEditor, TextEditorEdit, Uri, window, workspace } from 'vscode'; import { commands, Disposable, SourceControlResourceGroup, SourceControlResourceState, TextDocumentShowOptions, TextEditor, TextEditorEdit, Uri, window, workspace } from 'vscode';
import { BuiltInCommands } from '../constants'; import { ExplorerNode } from '../views/explorerNodes';
import { Logger } from '../logger'; import { Logger } from '../logger';
import { Telemetry } from '../telemetry'; import { Telemetry } from '../telemetry';
export type Commands = 'gitlens.closeUnchangedFiles' | export type Commands =
'gitlens.clearFileAnnotations' |
'gitlens.closeUnchangedFiles' |
'gitlens.copyMessageToClipboard' | 'gitlens.copyMessageToClipboard' |
'gitlens.copyShaToClipboard' | 'gitlens.copyShaToClipboard' |
'gitlens.diffDirectory' | 'gitlens.diffDirectory' |
@@ -12,6 +14,7 @@ export type Commands = 'gitlens.closeUnchangedFiles' |
'gitlens.diffWithNext' | 'gitlens.diffWithNext' |
'gitlens.diffWithPrevious' | 'gitlens.diffWithPrevious' |
'gitlens.diffLineWithPrevious' | 'gitlens.diffLineWithPrevious' |
'gitlens.diffWithRevision' |
'gitlens.diffWithWorking' | 'gitlens.diffWithWorking' |
'gitlens.diffLineWithWorking' | 'gitlens.diffLineWithWorking' |
'gitlens.openChangedFiles' | 'gitlens.openChangedFiles' |
@@ -42,6 +45,7 @@ export type Commands = 'gitlens.closeUnchangedFiles' |
'gitlens.toggleFileRecentChanges' | 'gitlens.toggleFileRecentChanges' |
'gitlens.toggleLineBlame'; 'gitlens.toggleLineBlame';
export const Commands = { export const Commands = {
ClearFileAnnotations: 'gitlens.clearFileAnnotations' as Commands,
CloseUnchangedFiles: 'gitlens.closeUnchangedFiles' as Commands, CloseUnchangedFiles: 'gitlens.closeUnchangedFiles' as Commands,
CopyMessageToClipboard: 'gitlens.copyMessageToClipboard' as Commands, CopyMessageToClipboard: 'gitlens.copyMessageToClipboard' as Commands,
CopyShaToClipboard: 'gitlens.copyShaToClipboard' as Commands, CopyShaToClipboard: 'gitlens.copyShaToClipboard' as Commands,
@@ -50,6 +54,7 @@ export const Commands = {
DiffWithNext: 'gitlens.diffWithNext' as Commands, DiffWithNext: 'gitlens.diffWithNext' as Commands,
DiffWithPrevious: 'gitlens.diffWithPrevious' as Commands, DiffWithPrevious: 'gitlens.diffWithPrevious' as Commands,
DiffLineWithPrevious: 'gitlens.diffLineWithPrevious' as Commands, DiffLineWithPrevious: 'gitlens.diffLineWithPrevious' as Commands,
DiffWithRevision: 'gitlens.diffWithRevision' as Commands,
DiffWithWorking: 'gitlens.diffWithWorking' as Commands, DiffWithWorking: 'gitlens.diffWithWorking' as Commands,
DiffLineWithWorking: 'gitlens.diffLineWithWorking' as Commands, DiffLineWithWorking: 'gitlens.diffLineWithWorking' as Commands,
OpenChangedFiles: 'gitlens.openChangedFiles' as Commands, OpenChangedFiles: 'gitlens.openChangedFiles' as Commands,
@@ -87,33 +92,68 @@ export function getCommandUri(uri?: Uri, editor?: TextEditor): Uri | undefined {
return editor.document.uri; return editor.document.uri;
} }
export type CommandContext = 'gitlens:canToggleCodeLens' | export interface CommandContextParsingOptions {
'gitlens:enabled' | editor: boolean;
'gitlens:hasRemotes' | uri: boolean;
'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) { export interface CommandBaseContext {
return commands.executeCommand(BuiltInCommands.SetContext, key, value); editor?: TextEditor;
uri?: Uri;
}
export interface CommandScmGroupsContext extends CommandBaseContext {
type: 'scm-groups';
scmResourceGroups: SourceControlResourceGroup[];
}
export interface CommandScmStatesContext extends CommandBaseContext {
type: 'scm-states';
scmResourceStates: SourceControlResourceState[];
}
export interface CommandUnknownContext extends CommandBaseContext {
type: 'unknown';
}
export interface CommandUriContext extends CommandBaseContext {
type: 'uri';
}
export interface CommandViewContext extends CommandBaseContext {
type: 'view';
node: ExplorerNode;
}
export type CommandContext = CommandScmGroupsContext | CommandScmStatesContext | CommandUnknownContext | CommandUriContext | CommandViewContext;
function isScmResourceGroup(group: any): group is SourceControlResourceGroup {
if (group === undefined) return false;
return (group as SourceControlResourceGroup).id !== undefined && (group.handle !== undefined || (group as SourceControlResourceGroup).label !== undefined || (group as SourceControlResourceGroup).resourceStates !== undefined);
}
function isScmResourceState(state: any): state is SourceControlResourceState {
if (state === undefined) return false;
return (state as SourceControlResourceState).resourceUri !== undefined;
}
function isTextEditor(editor: any): editor is TextEditor {
if (editor === undefined) return false;
return editor.id !== undefined && ((editor as TextEditor).edit !== undefined || (editor as TextEditor).document !== undefined);
} }
export abstract class Command extends Disposable { export abstract class Command extends Disposable {
protected readonly contextParsingOptions: CommandContextParsingOptions = { editor: false, uri: false };
private _disposable: Disposable; private _disposable: Disposable;
constructor(protected command: Commands) { constructor(protected command: Commands) {
super(() => this.dispose()); super(() => this.dispose());
this._disposable = commands.registerCommand(command, this._execute, this); this._disposable = commands.registerCommand(command, this._execute, this);
} }
@@ -121,46 +161,86 @@ export abstract class Command extends Disposable {
this._disposable && this._disposable.dispose(); this._disposable && this._disposable.dispose();
} }
protected _execute(...args: any[]): any { protected async preExecute(context: CommandContext, ...args: any[]): Promise<any> {
Telemetry.trackEvent(this.command);
return this.execute(...args); return this.execute(...args);
} }
abstract execute(...args: any[]): any; abstract execute(...args: any[]): any;
}
export abstract class EditorCommand extends Disposable { protected _execute(...args: any[]): any {
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); Telemetry.trackEvent(this.command);
return this.execute(editor, edit, ...args);
const [context, rest] = Command._parseContext(this.contextParsingOptions, ...args);
return this.preExecute(context, ...rest);
} }
abstract execute(editor: TextEditor, edit: TextEditorEdit, ...args: any[]): any; private static _parseContext(options: CommandContextParsingOptions, ...args: any[]): [CommandContext, any[]] {
let editor: TextEditor | undefined = undefined;
let firstArg = args[0];
if (options.editor && (firstArg === undefined || isTextEditor(firstArg))) {
editor = firstArg;
args = args.slice(1);
firstArg = args[0];
}
if (options.uri && (firstArg === undefined || firstArg instanceof Uri)) {
const [uri, ...rest] = args as [Uri, any];
return [{ type: 'uri', editor: editor, uri: uri }, rest];
}
if (firstArg instanceof ExplorerNode) {
const [node, ...rest] = args as [ExplorerNode, any];
return [{ type: 'view', node: node, uri: node.uri }, rest];
}
if (isScmResourceState(firstArg)) {
const states = [];
let count = 0;
for (const arg of args) {
if (!isScmResourceState(arg)) break;
count++;
states.push(arg);
}
return [{ type: 'scm-states', scmResourceStates: states, uri: states[0].resourceUri }, args.slice(count)];
}
if (isScmResourceGroup(firstArg)) {
const groups = [];
let count = 0;
for (const arg of args) {
if (!isScmResourceGroup(arg)) break;
count++;
groups.push(arg);
}
return [{ type: 'scm-groups', scmResourceGroups: groups }, args.slice(count)];
}
return [{ type: 'unknown', editor: editor }, args];
}
} }
export abstract class ActiveEditorCommand extends Command { export abstract class ActiveEditorCommand extends Command {
protected readonly contextParsingOptions: CommandContextParsingOptions = { editor: true, uri: true };
constructor(public readonly command: Commands) { constructor(public readonly command: Commands) {
super(command); super(command);
} }
protected async preExecute(context: CommandContext, ...args: any[]): Promise<any> {
return this.execute(context.editor, context.uri, ...args);
}
protected _execute(...args: any[]): any { protected _execute(...args: any[]): any {
return super._execute(window.activeTextEditor, ...args); return super._execute(window.activeTextEditor, ...args);
} }
abstract execute(editor: TextEditor, ...args: any[]): any; abstract execute(editor?: TextEditor, ...args: any[]): any;
} }
let lastCommand: { command: string, args: any[] } | undefined = undefined; let lastCommand: { command: string, args: any[] } | undefined = undefined;
@@ -185,6 +265,27 @@ export abstract class ActiveEditorCachedCommand extends ActiveEditorCommand {
abstract execute(editor: TextEditor, ...args: any[]): any; abstract execute(editor: TextEditor, ...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 async function openEditor(uri: Uri, options?: TextDocumentShowOptions): Promise<TextEditor | undefined> { export async function openEditor(uri: Uri, options?: TextDocumentShowOptions): Promise<TextEditor | undefined> {
try { try {
const defaults: TextDocumentShowOptions = { const defaults: TextDocumentShowOptions = {

View File

@@ -17,10 +17,12 @@ export class CopyMessageToClipboardCommand extends ActiveEditorCommand {
super(Commands.CopyMessageToClipboard); super(Commands.CopyMessageToClipboard);
} }
async execute(editor: TextEditor, uri?: Uri, args: CopyMessageToClipboardCommandArgs = {}): Promise<any> { async execute(editor?: TextEditor, uri?: Uri, args: CopyMessageToClipboardCommandArgs = {}): Promise<any> {
uri = getCommandUri(uri, editor); uri = getCommandUri(uri, editor);
try { try {
args = { ...args };
// If we don't have an editor then get the message of the last commit to the branch // If we don't have an editor then get the message of the last commit to the branch
if (uri === undefined) { if (uri === undefined) {
if (!this.git.repoPath) return undefined; if (!this.git.repoPath) return undefined;

View File

@@ -16,10 +16,12 @@ export class CopyShaToClipboardCommand extends ActiveEditorCommand {
super(Commands.CopyShaToClipboard); super(Commands.CopyShaToClipboard);
} }
async execute(editor: TextEditor, uri?: Uri, args: CopyShaToClipboardCommandArgs = {}): Promise<any> { async execute(editor?: TextEditor, uri?: Uri, args: CopyShaToClipboardCommandArgs = {}): Promise<any> {
uri = getCommandUri(uri, editor); uri = getCommandUri(uri, editor);
try { try {
args = { ...args };
// If we don't have an editor then get the sha of the last commit to the branch // If we don't have an editor then get the sha of the last commit to the branch
if (uri === undefined) { if (uri === undefined) {
if (!this.git.repoPath) return undefined; if (!this.git.repoPath) return undefined;

View File

@@ -2,7 +2,7 @@
import { Iterables } from '../system'; import { Iterables } from '../system';
import { commands, TextEditor, Uri, window } from 'vscode'; import { commands, TextEditor, Uri, window } from 'vscode';
import { ActiveEditorCommand, Commands, getCommandUri } from './common'; import { ActiveEditorCommand, Commands, getCommandUri } from './common';
import { BuiltInCommands } from '../constants'; import { BuiltInCommands, GlyphChars } from '../constants';
import { GitService } from '../gitService'; import { GitService } from '../gitService';
import { Logger } from '../logger'; import { Logger } from '../logger';
import { Messages } from '../messages'; import { Messages } from '../messages';
@@ -19,7 +19,7 @@ export class DiffDirectoryCommand extends ActiveEditorCommand {
super(Commands.DiffDirectory); super(Commands.DiffDirectory);
} }
async execute(editor: TextEditor, uri?: Uri, args: DiffDirectoryCommandCommandArgs = {}): Promise<any> { async execute(editor?: TextEditor, uri?: Uri, args: DiffDirectoryCommandCommandArgs = {}): Promise<any> {
const diffTool = await this.git.getConfig('diff.tool'); const diffTool = await this.git.getConfig('diff.tool');
if (!diffTool) { if (!diffTool) {
const result = await window.showWarningMessage(`Unable to open directory compare because there is no Git diff tool configured`, 'View Git Docs'); const result = await window.showWarningMessage(`Unable to open directory compare because there is no Git diff tool configured`, 'View Git Docs');
@@ -35,11 +35,13 @@ export class DiffDirectoryCommand extends ActiveEditorCommand {
if (!repoPath) return Messages.showNoRepositoryWarningMessage(`Unable to open directory compare`); if (!repoPath) return Messages.showNoRepositoryWarningMessage(`Unable to open directory compare`);
if (!args.shaOrBranch1) { if (!args.shaOrBranch1) {
args = { ...args };
const branches = await this.git.getBranches(repoPath); const branches = await this.git.getBranches(repoPath);
const current = Iterables.find(branches, _ => _.current); const current = Iterables.find(branches, _ => _.current);
if (current == null) return window.showWarningMessage(`Unable to open directory compare`); if (current == null) return window.showWarningMessage(`Unable to open directory compare`);
const pick = await BranchesQuickPick.show(branches, `Compare ${current.name} to \u2026`); const pick = await BranchesQuickPick.show(branches, `Compare ${current.name} to ${GlyphChars.Ellipsis}`);
if (pick === undefined) return undefined; if (pick === undefined) return undefined;
if (pick instanceof CommandQuickPickItem) return pick.execute(); if (pick instanceof CommandQuickPickItem) return pick.execute();

View File

@@ -1,7 +1,7 @@
'use strict'; 'use strict';
import { commands, TextDocumentShowOptions, TextEditor, Uri, window } from 'vscode'; import { commands, Range, TextDocumentShowOptions, TextEditor, Uri, window } from 'vscode';
import { ActiveEditorCommand, Commands, getCommandUri } from './common'; import { ActiveEditorCommand, Commands, getCommandUri } from './common';
import { BuiltInCommands } from '../constants'; import { BuiltInCommands, GlyphChars } from '../constants';
import { DiffWithPreviousCommandArgs } from './diffWithPrevious'; import { DiffWithPreviousCommandArgs } from './diffWithPrevious';
import { DiffWithWorkingCommandArgs } from './diffWithWorking'; import { DiffWithWorkingCommandArgs } from './diffWithWorking';
import { GitCommit, GitService, GitUri } from '../gitService'; import { GitCommit, GitService, GitUri } from '../gitService';
@@ -21,12 +21,16 @@ export class DiffLineWithPreviousCommand extends ActiveEditorCommand {
super(Commands.DiffLineWithPrevious); super(Commands.DiffLineWithPrevious);
} }
async execute(editor: TextEditor, uri?: Uri, args: DiffLineWithPreviousCommandArgs = {}): Promise<any> { async execute(editor?: TextEditor, uri?: Uri, args: DiffLineWithPreviousCommandArgs = {}): Promise<any> {
uri = getCommandUri(uri, editor); uri = getCommandUri(uri, editor);
if (uri === undefined) return undefined; if (uri === undefined) return undefined;
const gitUri = await GitUri.fromUri(uri, this.git); const gitUri = await GitUri.fromUri(uri, this.git);
args.line = args.line || (editor === undefined ? gitUri.offset : editor.selection.active.line);
args = { ...args };
if (args.line === undefined) {
args.line = editor === undefined ? gitUri.offset : editor.selection.active.line;
}
if (args.commit === undefined || GitService.isUncommitted(args.commit.sha)) { if (args.commit === undefined || GitService.isUncommitted(args.commit.sha)) {
if (editor !== undefined && editor.document !== undefined && editor.document.isDirty) return undefined; if (editor !== undefined && editor.document !== undefined && editor.document.isDirty) return undefined;
@@ -73,14 +77,18 @@ export class DiffLineWithPreviousCommand extends ActiveEditorCommand {
this.git.getVersionedFile(args.commit.repoPath, args.commit.uri.fsPath, args.commit.sha) this.git.getVersionedFile(args.commit.repoPath, args.commit.uri.fsPath, args.commit.sha)
]); ]);
if (args.line !== undefined && args.line !== 0) {
if (args.showOptions === undefined) {
args.showOptions = {};
}
args.showOptions.selection = new Range(args.line, 0, args.line, 0);
}
await commands.executeCommand(BuiltInCommands.Diff, await commands.executeCommand(BuiltInCommands.Diff,
Uri.file(lhs), Uri.file(lhs),
Uri.file(rhs), Uri.file(rhs),
`${path.basename(args.commit.uri.fsPath)} (${args.commit.shortSha}) \u2194 ${path.basename(gitUri.fsPath)} (${gitUri.shortSha})`, `${path.basename(args.commit.uri.fsPath)} (${args.commit.shortSha}) ${GlyphChars.ArrowLeftRight} ${path.basename(gitUri.fsPath)} (${gitUri.shortSha})`,
args.showOptions); args.showOptions);
// TODO: Figure out how to focus the left pane
return await commands.executeCommand(BuiltInCommands.RevealLine, { lineNumber: args.line, at: 'center' });
} }
catch (ex) { catch (ex) {
Logger.error(ex, 'DiffWithPreviousLineCommand', 'getVersionedFile'); Logger.error(ex, 'DiffWithPreviousLineCommand', 'getVersionedFile');

View File

@@ -18,12 +18,16 @@ export class DiffLineWithWorkingCommand extends ActiveEditorCommand {
super(Commands.DiffLineWithWorking); super(Commands.DiffLineWithWorking);
} }
async execute(editor: TextEditor, uri?: Uri, args: DiffLineWithWorkingCommandArgs = {}): Promise<any> { async execute(editor?: TextEditor, uri?: Uri, args: DiffLineWithWorkingCommandArgs = {}): Promise<any> {
uri = getCommandUri(uri, editor); uri = getCommandUri(uri, editor);
if (uri === undefined) return undefined; if (uri === undefined) return undefined;
const gitUri = await GitUri.fromUri(uri, this.git); const gitUri = await GitUri.fromUri(uri, this.git);
args.line = args.line || (editor === undefined ? gitUri.offset : editor.selection.active.line);
args = { ...args };
if (args.line === undefined) {
args.line = editor === undefined ? gitUri.offset : editor.selection.active.line;
}
if (args.commit === undefined || GitService.isUncommitted(args.commit.sha)) { if (args.commit === undefined || GitService.isUncommitted(args.commit.sha)) {
if (editor !== undefined && editor.document !== undefined && editor.document.isDirty) return undefined; if (editor !== undefined && editor.document !== undefined && editor.document.isDirty) return undefined;

View File

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

View File

@@ -2,7 +2,7 @@
import { Iterables } from '../system'; import { Iterables } from '../system';
import { commands, Range, TextDocumentShowOptions, TextEditor, Uri, window } from 'vscode'; import { commands, Range, TextDocumentShowOptions, TextEditor, Uri, window } from 'vscode';
import { ActiveEditorCommand, Commands, getCommandUri } from './common'; import { ActiveEditorCommand, Commands, getCommandUri } from './common';
import { BuiltInCommands } from '../constants'; import { BuiltInCommands, GlyphChars } from '../constants';
import { GitLogCommit, GitService, GitUri } from '../gitService'; import { GitLogCommit, GitService, GitUri } from '../gitService';
import { Logger } from '../logger'; import { Logger } from '../logger';
import { Messages } from '../messages'; import { Messages } from '../messages';
@@ -21,11 +21,14 @@ export class DiffWithNextCommand extends ActiveEditorCommand {
super(Commands.DiffWithNext); super(Commands.DiffWithNext);
} }
async execute(editor: TextEditor, uri?: Uri, args: DiffWithNextCommandArgs = {}): Promise<any> { async execute(editor?: TextEditor, uri?: Uri, args: DiffWithNextCommandArgs = {}): Promise<any> {
uri = getCommandUri(uri, editor); uri = getCommandUri(uri, editor);
if (uri === undefined) return undefined; if (uri === undefined) return undefined;
args.line = args.line || (editor === undefined ? 0 : editor.selection.active.line); args = { ...args };
if (args.line === undefined) {
args.line = editor === undefined ? 0 : editor.selection.active.line;
}
if (args.commit === undefined || !(args.commit instanceof GitLogCommit) || args.range !== undefined) { if (args.commit === undefined || !(args.commit instanceof GitLogCommit) || args.range !== undefined) {
const gitUri = await GitUri.fromUri(uri, this.git); const gitUri = await GitUri.fromUri(uri, this.git);
@@ -38,7 +41,7 @@ export class DiffWithNextCommand extends ActiveEditorCommand {
const sha = args.commit === undefined ? gitUri.sha : args.commit.sha; const sha = args.commit === undefined ? gitUri.sha : args.commit.sha;
const log = await this.git.getLogForFile(gitUri.repoPath, gitUri.fsPath, undefined, sha !== undefined ? undefined : 2, args.range!); const log = await this.git.getLogForFile(gitUri.repoPath, gitUri.fsPath, undefined, { maxCount: sha !== undefined ? undefined : 2, range: args.range! });
if (log === undefined) return Messages.showFileNotUnderSourceControlWarningMessage('Unable to open compare'); if (log === undefined) return Messages.showFileNotUnderSourceControlWarningMessage('Unable to open compare');
args.commit = (sha && log.commits.get(sha)) || Iterables.first(log.commits.values()); args.commit = (sha && log.commits.get(sha)) || Iterables.first(log.commits.values());
@@ -57,14 +60,18 @@ export class DiffWithNextCommand extends ActiveEditorCommand {
this.git.getVersionedFile(args.commit.repoPath, args.commit.uri.fsPath, args.commit.sha) this.git.getVersionedFile(args.commit.repoPath, args.commit.uri.fsPath, args.commit.sha)
]); ]);
if (args.line !== undefined && args.line !== 0) {
if (args.showOptions === undefined) {
args.showOptions = {};
}
args.showOptions.selection = new Range(args.line, 0, args.line, 0);
}
await commands.executeCommand(BuiltInCommands.Diff, await commands.executeCommand(BuiltInCommands.Diff,
Uri.file(lhs), Uri.file(lhs),
Uri.file(rhs), Uri.file(rhs),
`${path.basename(args.commit.uri.fsPath)} (${args.commit.shortSha}) \u2194 ${path.basename(args.commit.nextUri.fsPath)} (${args.commit.nextShortSha})`, `${path.basename(args.commit.uri.fsPath)} (${args.commit.shortSha}) ${GlyphChars.ArrowLeftRight} ${path.basename(args.commit.nextUri.fsPath)} (${args.commit.nextShortSha})`,
args.showOptions); args.showOptions);
// TODO: Figure out how to focus the left pane
return await commands.executeCommand(BuiltInCommands.RevealLine, { lineNumber: args.line, at: 'center' });
} }
catch (ex) { catch (ex) {
Logger.error(ex, 'DiffWithNextCommand', 'getVersionedFile'); Logger.error(ex, 'DiffWithNextCommand', 'getVersionedFile');

View File

@@ -2,7 +2,7 @@
import { Iterables } from '../system'; import { Iterables } from '../system';
import { commands, Range, TextDocumentShowOptions, TextEditor, Uri, window } from 'vscode'; import { commands, Range, TextDocumentShowOptions, TextEditor, Uri, window } from 'vscode';
import { ActiveEditorCommand, Commands, getCommandUri } from './common'; import { ActiveEditorCommand, Commands, getCommandUri } from './common';
import { BuiltInCommands } from '../constants'; import { BuiltInCommands, GlyphChars } from '../constants';
import { DiffWithWorkingCommandArgs } from './diffWithWorking'; import { DiffWithWorkingCommandArgs } from './diffWithWorking';
import { GitCommit, GitService, GitUri } from '../gitService'; import { GitCommit, GitService, GitUri } from '../gitService';
import { Logger } from '../logger'; import { Logger } from '../logger';
@@ -22,19 +22,22 @@ export class DiffWithPreviousCommand extends ActiveEditorCommand {
super(Commands.DiffWithPrevious); super(Commands.DiffWithPrevious);
} }
async execute(editor: TextEditor, uri?: Uri, args: DiffWithPreviousCommandArgs = {}): Promise<any> { async execute(editor?: TextEditor, uri?: Uri, args: DiffWithPreviousCommandArgs = {}): Promise<any> {
uri = getCommandUri(uri, editor); uri = getCommandUri(uri, editor);
if (uri === undefined) return undefined; if (uri === undefined) return undefined;
args.line = args.line || (editor === undefined ? 0 : editor.selection.active.line); args = { ...args };
if (args.line === undefined) {
args.line = editor === undefined ? 0 : editor.selection.active.line;
}
if (args.commit === undefined || (args.commit.type !== 'file') || args.range !== undefined) { if (args.commit === undefined || args.commit.type !== 'file' || args.range !== undefined) {
const gitUri = await GitUri.fromUri(uri, this.git); const gitUri = await GitUri.fromUri(uri, this.git);
try { try {
const sha = args.commit === undefined ? gitUri.sha : args.commit.sha; const sha = args.commit === undefined ? gitUri.sha : args.commit.sha;
const log = await this.git.getLogForFile(gitUri.repoPath, gitUri.fsPath, undefined, sha !== undefined ? undefined : 2, args.range!); const log = await this.git.getLogForFile(gitUri.repoPath, gitUri.fsPath, sha, { maxCount: 2, range: args.range!, skipMerges: true });
if (log === undefined) return Messages.showFileNotUnderSourceControlWarningMessage('Unable to open compare'); if (log === undefined) return Messages.showFileNotUnderSourceControlWarningMessage('Unable to open compare');
args.commit = (sha && log.commits.get(sha)) || Iterables.first(log.commits.values()); args.commit = (sha && log.commits.get(sha)) || Iterables.first(log.commits.values());
@@ -56,14 +59,18 @@ export class DiffWithPreviousCommand extends ActiveEditorCommand {
this.git.getVersionedFile(args.commit.repoPath, args.commit.previousUri.fsPath, args.commit.previousSha) this.git.getVersionedFile(args.commit.repoPath, args.commit.previousUri.fsPath, args.commit.previousSha)
]); ]);
if (args.line !== undefined && args.line !== 0) {
if (args.showOptions === undefined) {
args.showOptions = {};
}
args.showOptions.selection = new Range(args.line, 0, args.line, 0);
}
await commands.executeCommand(BuiltInCommands.Diff, await commands.executeCommand(BuiltInCommands.Diff,
Uri.file(lhs), Uri.file(lhs),
Uri.file(rhs), Uri.file(rhs),
`${path.basename(args.commit.previousUri.fsPath)} (${args.commit.previousShortSha}) \u2194 ${path.basename(args.commit.uri.fsPath)} (${args.commit.shortSha})`, `${path.basename(args.commit.previousUri.fsPath)} (${args.commit.previousShortSha}) ${GlyphChars.ArrowLeftRight} ${path.basename(args.commit.uri.fsPath)} (${args.commit.shortSha})`,
args.showOptions); args.showOptions);
// TODO: Figure out how to focus the left pane
return await commands.executeCommand(BuiltInCommands.RevealLine, { lineNumber: args.line, at: 'center' });
} }
catch (ex) { catch (ex) {
Logger.error(ex, 'DiffWithPreviousCommand', 'getVersionedFile'); Logger.error(ex, 'DiffWithPreviousCommand', 'getVersionedFile');

View File

@@ -0,0 +1,69 @@
'use strict';
import { commands, Range, TextDocumentShowOptions, TextEditor, Uri, window } from 'vscode';
import { ActiveEditorCommand, Commands, getCommandUri } from './common';
import { BuiltInCommands, GlyphChars } from '../constants';
import { GitService, GitUri } from '../gitService';
import { Logger } from '../logger';
import { Messages } from '../messages';
import { CommandQuickPickItem, FileHistoryQuickPick } from '../quickPicks';
import * as path from 'path';
export interface DiffWithRevisionCommandArgs {
line?: number;
maxCount?: number;
showOptions?: TextDocumentShowOptions;
}
export class DiffWithRevisionCommand extends ActiveEditorCommand {
constructor(private git: GitService) {
super(Commands.DiffWithRevision);
}
async execute(editor?: TextEditor, uri?: Uri, args: DiffWithRevisionCommandArgs = {}): Promise<any> {
uri = getCommandUri(uri, editor);
if (uri === undefined) return undefined;
args = { ...args };
if (args.line === undefined) {
args.line = editor === undefined ? 0 : editor.selection.active.line;
}
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 {
const log = await this.git.getLogForFile(gitUri.repoPath, gitUri.fsPath, gitUri.sha, { maxCount: args.maxCount });
if (log === undefined) return Messages.showFileNotUnderSourceControlWarningMessage('Unable to open history compare');
if (progressCancellation.token.isCancellationRequested) return undefined;
const pick = await FileHistoryQuickPick.show(this.git, log, gitUri, progressCancellation, { pickerOnly: true });
if (pick === undefined) return undefined;
if (pick instanceof CommandQuickPickItem) return pick.execute();
const compare = await this.git.getVersionedFile(gitUri.repoPath, gitUri.fsPath, pick.commit.sha);
if (args.line !== undefined && args.line !== 0) {
if (args.showOptions === undefined) {
args.showOptions = {};
}
args.showOptions.selection = new Range(args.line, 0, args.line, 0);
}
await commands.executeCommand(BuiltInCommands.Diff,
Uri.file(compare),
gitUri.fileUri(),
`${path.basename(gitUri.fsPath)} (${pick.commit.shortSha}) ${GlyphChars.ArrowLeftRight} ${path.basename(gitUri.fsPath)}`,
args.showOptions);
}
catch (ex) {
Logger.error(ex, 'DiffWithRevisionCommand', 'getVersionedFile');
return window.showErrorMessage(`Unable to open history compare. See output channel for more details`);
}
}
}

View File

@@ -1,7 +1,7 @@
'use strict'; 'use strict';
import { commands, TextDocumentShowOptions, TextEditor, Uri, window } from 'vscode'; import { commands, Range, TextDocumentShowOptions, TextEditor, Uri, window } from 'vscode';
import { ActiveEditorCommand, Commands, getCommandUri } from './common'; import { ActiveEditorCommand, Commands, getCommandUri } from './common';
import { BuiltInCommands } from '../constants'; import { BuiltInCommands, GlyphChars } from '../constants';
import { GitCommit, GitService, GitUri } from '../gitService'; import { GitCommit, GitService, GitUri } from '../gitService';
import { Logger } from '../logger'; import { Logger } from '../logger';
import { Messages } from '../messages'; import { Messages } from '../messages';
@@ -19,11 +19,14 @@ export class DiffWithWorkingCommand extends ActiveEditorCommand {
super(Commands.DiffWithWorking); super(Commands.DiffWithWorking);
} }
async execute(editor: TextEditor, uri?: Uri, args: DiffWithWorkingCommandArgs = {}): Promise<any> { async execute(editor?: TextEditor, uri?: Uri, args: DiffWithWorkingCommandArgs = {}): Promise<any> {
uri = getCommandUri(uri, editor); uri = getCommandUri(uri, editor);
if (uri === undefined) return undefined; if (uri === undefined) return undefined;
args.line = args.line || (editor === undefined ? 0 : editor.selection.active.line); args = { ...args };
if (args.line === undefined) {
args.line = editor === undefined ? 0 : editor.selection.active.line;
}
if (args.commit === undefined || GitService.isUncommitted(args.commit.sha)) { if (args.commit === undefined || GitService.isUncommitted(args.commit.sha)) {
const gitUri = await GitUri.fromUri(uri, this.git); const gitUri = await GitUri.fromUri(uri, this.git);
@@ -48,14 +51,18 @@ export class DiffWithWorkingCommand extends ActiveEditorCommand {
try { try {
const compare = await this.git.getVersionedFile(args.commit.repoPath, args.commit.uri.fsPath, args.commit.sha); const compare = await this.git.getVersionedFile(args.commit.repoPath, args.commit.uri.fsPath, args.commit.sha);
if (args.line !== undefined && args.line !== 0) {
if (args.showOptions === undefined) {
args.showOptions = {};
}
args.showOptions.selection = new Range(args.line, 0, args.line, 0);
}
await commands.executeCommand(BuiltInCommands.Diff, await commands.executeCommand(BuiltInCommands.Diff,
Uri.file(compare), Uri.file(compare),
Uri.file(path.resolve(gitUri.repoPath, workingFileName)), Uri.file(path.resolve(gitUri.repoPath, workingFileName)),
`${path.basename(args.commit.uri.fsPath)} (${args.commit.shortSha}) \u2194 ${path.basename(workingFileName)}`, `${path.basename(args.commit.uri.fsPath)} (${args.commit.shortSha}) ${GlyphChars.ArrowLeftRight} ${path.basename(workingFileName)}`,
args.showOptions); args.showOptions);
// TODO: Figure out how to focus the left pane
return await commands.executeCommand(BuiltInCommands.RevealLine, { lineNumber: args.line, at: 'center' });
} }
catch (ex) { catch (ex) {
Logger.error(ex, 'DiffWithWorkingCommand', 'getVersionedFile'); Logger.error(ex, 'DiffWithWorkingCommand', 'getVersionedFile');

View File

@@ -1,7 +1,8 @@
'use strict'; 'use strict';
import { Arrays } from '../system'; import { Arrays } from '../system';
import { commands, TextEditor, TextEditorEdit, Uri, window } from 'vscode'; import { commands, TextEditor, Uri, window } from 'vscode';
import { ActiveEditorCommand, Commands, getCommandUri } from './common'; import { ActiveEditorCommand, Commands, getCommandUri } from './common';
import { GlyphChars } from '../constants';
import { GitService, GitUri } from '../gitService'; import { GitService, GitUri } from '../gitService';
import { Logger } from '../logger'; import { Logger } from '../logger';
import { BranchesQuickPick, CommandQuickPickItem } from '../quickPicks'; import { BranchesQuickPick, CommandQuickPickItem } from '../quickPicks';
@@ -17,7 +18,7 @@ export class OpenBranchInRemoteCommand extends ActiveEditorCommand {
super(Commands.OpenBranchInRemote); super(Commands.OpenBranchInRemote);
} }
async execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri, args: OpenBranchInRemoteCommandArgs = {}) { async execute(editor?: TextEditor, uri?: Uri, args: OpenBranchInRemoteCommandArgs = {}) {
uri = getCommandUri(uri, editor); uri = getCommandUri(uri, editor);
const gitUri = uri && await GitUri.fromUri(uri, this.git); const gitUri = uri && await GitUri.fromUri(uri, this.git);
@@ -27,9 +28,11 @@ export class OpenBranchInRemoteCommand extends ActiveEditorCommand {
try { try {
if (args.branch === undefined) { if (args.branch === undefined) {
args = { ...args };
const branches = await this.git.getBranches(repoPath); const branches = await this.git.getBranches(repoPath);
const pick = await BranchesQuickPick.show(branches, `Show history for branch\u2026`); const pick = await BranchesQuickPick.show(branches, `Show history for branch${GlyphChars.Ellipsis}`);
if (pick === undefined) return undefined; if (pick === undefined) return undefined;
if (pick instanceof CommandQuickPickItem) return undefined; if (pick instanceof CommandQuickPickItem) return undefined;

View File

@@ -15,11 +15,13 @@ export class OpenChangedFilesCommand extends ActiveEditorCommand {
super(Commands.OpenChangedFiles); super(Commands.OpenChangedFiles);
} }
async execute(editor: TextEditor, uri?: Uri, args: OpenChangedFilesCommandArgs = {}) { async execute(editor?: TextEditor, uri?: Uri, args: OpenChangedFilesCommandArgs = {}) {
uri = getCommandUri(uri, editor); uri = getCommandUri(uri, editor);
try { try {
if (args.uris === undefined) { if (args.uris === undefined) {
args = { ...args };
const repoPath = await this.git.getRepoPathFromUri(uri); const repoPath = await this.git.getRepoPathFromUri(uri);
if (!repoPath) return Messages.showNoRepositoryWarningMessage(`Unable to open changed files`); if (!repoPath) return Messages.showNoRepositoryWarningMessage(`Unable to open changed files`);

View File

@@ -1,11 +1,16 @@
'use strict'; 'use strict';
import { Arrays } from '../system'; import { Arrays } from '../system';
import { commands, TextEditor, TextEditorEdit, Uri, window } from 'vscode'; import { commands, TextEditor, Uri, window } from 'vscode';
import { ActiveEditorCommand, Commands, getCommandUri } from './common'; import { ActiveEditorCommand, CommandContext, Commands, getCommandUri } from './common';
import { GitBlameCommit, GitService, GitUri } from '../gitService'; import { GitBlameCommit, GitService, GitUri } from '../gitService';
import { Logger } from '../logger'; import { Logger } from '../logger';
import { Messages } from '../messages'; import { Messages } from '../messages';
import { OpenInRemoteCommandArgs } from './openInRemote'; import { OpenInRemoteCommandArgs } from './openInRemote';
import { CommitNode } from '../views/explorerNodes';
export interface OpenCommitInRemoteCommandArgs {
sha?: string;
}
export class OpenCommitInRemoteCommand extends ActiveEditorCommand { export class OpenCommitInRemoteCommand extends ActiveEditorCommand {
@@ -13,7 +18,17 @@ export class OpenCommitInRemoteCommand extends ActiveEditorCommand {
super(Commands.OpenCommitInRemote); super(Commands.OpenCommitInRemote);
} }
async execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri) { protected async preExecute(context: CommandContext, args: OpenCommitInRemoteCommandArgs = {}): Promise<any> {
if (context.type === 'view' && context.node instanceof CommitNode) {
args = { ...args };
args.sha = context.node.commit.sha;
return this.execute(context.editor, context.node.commit.uri, args);
}
return this.execute(context.editor, context.uri, args);
}
async execute(editor?: TextEditor, uri?: Uri, args: OpenCommitInRemoteCommandArgs = {}) {
uri = getCommandUri(uri, editor); uri = getCommandUri(uri, editor);
if (uri === undefined) return undefined; if (uri === undefined) return undefined;
if (editor !== undefined && editor.document !== undefined && editor.document.isDirty) return undefined; if (editor !== undefined && editor.document !== undefined && editor.document.isDirty) return undefined;
@@ -21,26 +36,29 @@ export class OpenCommitInRemoteCommand extends ActiveEditorCommand {
const gitUri = await GitUri.fromUri(uri, this.git); const gitUri = await GitUri.fromUri(uri, this.git);
if (!gitUri.repoPath) return undefined; if (!gitUri.repoPath) return undefined;
const line = editor === undefined ? gitUri.offset : editor.selection.active.line;
try { try {
const blameline = line - gitUri.offset; if (args.sha === undefined) {
if (blameline < 0) return undefined; const line = editor === undefined ? gitUri.offset : editor.selection.active.line;
const blameline = line - gitUri.offset;
if (blameline < 0) return undefined;
const blame = await this.git.getBlameForLine(gitUri, blameline); const blame = await this.git.getBlameForLine(gitUri, blameline);
if (blame === undefined) return Messages.showFileNotUnderSourceControlWarningMessage('Unable to open commit in remote provider'); if (blame === undefined) return Messages.showFileNotUnderSourceControlWarningMessage('Unable to open commit in remote provider');
let commit = blame.commit; let commit = blame.commit;
// If the line is uncommitted, find the previous commit // If the line is uncommitted, find the previous commit
if (commit.isUncommitted) { if (commit.isUncommitted) {
commit = new GitBlameCommit(commit.repoPath, commit.previousSha!, commit.previousFileName!, commit.author, commit.date, commit.message, []); commit = new GitBlameCommit(commit.repoPath, commit.previousSha!, commit.previousFileName!, commit.author, commit.date, commit.message, []);
}
args.sha = commit.sha;
} }
const remotes = Arrays.uniqueBy(await this.git.getRemotes(gitUri.repoPath), _ => _.url, _ => !!_.provider); const remotes = Arrays.uniqueBy(await this.git.getRemotes(gitUri.repoPath), _ => _.url, _ => !!_.provider);
return commands.executeCommand(Commands.OpenInRemote, uri, { return commands.executeCommand(Commands.OpenInRemote, uri, {
resource: { resource: {
type: 'commit', type: 'commit',
sha: commit.sha sha: args.sha
}, },
remotes remotes
} as OpenInRemoteCommandArgs); } as OpenInRemoteCommandArgs);

View File

@@ -1,18 +1,22 @@
'use strict'; 'use strict';
import { Arrays } from '../system'; import { Arrays } from '../system';
import { commands, Range, TextEditor, TextEditorEdit, Uri, window } from 'vscode'; import { commands, Range, TextEditor, Uri, window } from 'vscode';
import { ActiveEditorCommand, Commands, getCommandUri } from './common'; import { ActiveEditorCommand, Commands, getCommandUri } from './common';
import { GitService, GitUri } from '../gitService'; import { GitService, GitUri } from '../gitService';
import { Logger } from '../logger'; import { Logger } from '../logger';
import { OpenInRemoteCommandArgs } from './openInRemote'; import { OpenInRemoteCommandArgs } from './openInRemote';
export interface OpenFileInRemoteCommandArgs {
range?: boolean;
}
export class OpenFileInRemoteCommand extends ActiveEditorCommand { export class OpenFileInRemoteCommand extends ActiveEditorCommand {
constructor(private git: GitService) { constructor(private git: GitService) {
super(Commands.OpenFileInRemote); super(Commands.OpenFileInRemote);
} }
async execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri) { async execute(editor?: TextEditor, uri?: Uri, args: OpenFileInRemoteCommandArgs = { range: true }) {
uri = getCommandUri(uri, editor); uri = getCommandUri(uri, editor);
if (uri === undefined) return undefined; if (uri === undefined) return undefined;
@@ -23,7 +27,9 @@ export class OpenFileInRemoteCommand extends ActiveEditorCommand {
try { try {
const remotes = Arrays.uniqueBy(await this.git.getRemotes(gitUri.repoPath), _ => _.url, _ => !!_.provider); 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 })); const range = (args.range && editor !== undefined)
? new Range(editor.selection.start.with({ line: editor.selection.start.line + 1 }), editor.selection.end.with({ line: editor.selection.end.line + 1 }))
: undefined;
return commands.executeCommand(Commands.OpenInRemote, uri, { return commands.executeCommand(Commands.OpenInRemote, uri, {
resource: { resource: {

View File

@@ -1,6 +1,8 @@
'use strict'; 'use strict';
import { Strings } from '../system';
import { TextEditor, Uri, window } from 'vscode'; import { TextEditor, Uri, window } from 'vscode';
import { ActiveEditorCommand, Commands, getCommandUri } from './common'; import { ActiveEditorCommand, Commands, getCommandUri } from './common';
import { GlyphChars } from '../constants';
import { GitLogCommit, GitRemote, RemoteResource } from '../gitService'; import { GitLogCommit, GitRemote, RemoteResource } from '../gitService';
import { Logger } from '../logger'; import { Logger } from '../logger';
import { CommandQuickPickItem, OpenRemoteCommandQuickPickItem, RemotesQuickPick } from '../quickPicks'; import { CommandQuickPickItem, OpenRemoteCommandQuickPickItem, RemotesQuickPick } from '../quickPicks';
@@ -21,9 +23,10 @@ export class OpenInRemoteCommand extends ActiveEditorCommand {
async execute(editor: TextEditor, uri?: Uri, args: OpenInRemoteCommandArgs = {}) { async execute(editor: TextEditor, uri?: Uri, args: OpenInRemoteCommandArgs = {}) {
uri = getCommandUri(uri, editor); uri = getCommandUri(uri, editor);
try { args = { ...args };
if (args.remotes === undefined || args.resource === undefined) return undefined; if (args.remotes === undefined || args.resource === undefined) return undefined;
try {
if (args.remotes.length === 1) { if (args.remotes.length === 1) {
const command = new OpenRemoteCommandQuickPickItem(args.remotes[0], args.resource); const command = new OpenRemoteCommandQuickPickItem(args.remotes[0], args.resource);
return command.execute(); return command.execute();
@@ -42,35 +45,35 @@ export class OpenInRemoteCommand extends ActiveEditorCommand {
args.remotes = [remote]; args.remotes = [remote];
} }
} }
placeHolder = `open ${args.resource.branch} branch in\u2026`; placeHolder = `open ${args.resource.branch} branch in${GlyphChars.Ellipsis}`;
break; break;
case 'commit': case 'commit':
const shortSha = args.resource.sha.substring(0, 8); const shortSha = args.resource.sha.substring(0, 8);
placeHolder = `open commit ${shortSha} in\u2026`; placeHolder = `open commit ${shortSha} in${GlyphChars.Ellipsis}`;
break; break;
case 'file': case 'file':
if (args.resource.commit !== undefined && args.resource.commit instanceof GitLogCommit) { if (args.resource.commit !== undefined && args.resource.commit instanceof GitLogCommit) {
if (args.resource.commit.status === 'D') { if (args.resource.commit.status === 'D') {
args.resource.sha = args.resource.commit.previousSha; args.resource.sha = args.resource.commit.previousSha;
placeHolder = `open ${args.resource.fileName} \u00a0\u2022\u00a0 ${args.resource.commit.previousShortSha} in\u2026`; placeHolder = `open ${args.resource.fileName} ${Strings.pad(GlyphChars.Dot, 1, 1)} ${args.resource.commit.previousShortSha} in${GlyphChars.Ellipsis}`;
} }
else { else {
args.resource.sha = args.resource.commit.sha; args.resource.sha = args.resource.commit.sha;
placeHolder = `open ${args.resource.fileName} \u00a0\u2022\u00a0 ${args.resource.commit.shortSha} in\u2026`; placeHolder = `open ${args.resource.fileName} ${Strings.pad(GlyphChars.Dot, 1, 1)} ${args.resource.commit.shortSha} in${GlyphChars.Ellipsis}`;
} }
} }
else { else {
const shortFileSha = args.resource.sha === undefined ? '' : args.resource.sha.substring(0, 8); const shortFileSha = args.resource.sha === undefined ? '' : args.resource.sha.substring(0, 8);
const shaSuffix = shortFileSha ? ` \u00a0\u2022\u00a0 ${shortFileSha}` : ''; const shaSuffix = shortFileSha ? ` ${Strings.pad(GlyphChars.Dot, 1, 1)} ${shortFileSha}` : '';
placeHolder = `open ${args.resource.fileName}${shaSuffix} in\u2026`; placeHolder = `open ${args.resource.fileName}${shaSuffix} in${GlyphChars.Ellipsis}`;
} }
break; break;
case 'working-file': case 'working-file':
placeHolder = `open ${args.resource.fileName} in\u2026`; placeHolder = `open ${args.resource.fileName} in${GlyphChars.Ellipsis}`;
break; break;
} }

View File

@@ -1,6 +1,6 @@
'use strict'; 'use strict';
import { Arrays } from '../system'; import { Arrays } from '../system';
import { commands, TextEditor, TextEditorEdit, Uri, window } from 'vscode'; import { commands, TextEditor, Uri, window } from 'vscode';
import { ActiveEditorCommand, Commands, getCommandUri } from './common'; import { ActiveEditorCommand, Commands, getCommandUri } from './common';
import { GitService, GitUri } from '../gitService'; import { GitService, GitUri } from '../gitService';
import { Logger } from '../logger'; import { Logger } from '../logger';
@@ -12,7 +12,7 @@ export class OpenRepoInRemoteCommand extends ActiveEditorCommand {
super(Commands.OpenRepoInRemote); super(Commands.OpenRepoInRemote);
} }
async execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri) { async execute(editor?: TextEditor, uri?: Uri) {
uri = getCommandUri(uri, editor); uri = getCommandUri(uri, editor);
const gitUri = uri && await GitUri.fromUri(uri, this.git); const gitUri = uri && await GitUri.fromUri(uri, this.git);

View File

@@ -24,6 +24,8 @@ export class ShowBlameHistoryCommand extends EditorCommand {
if (uri === undefined) return undefined; if (uri === undefined) return undefined;
if (args.range == null || args.position == null) { if (args.range == null || args.position == null) {
args = { ...args };
// If the command is executed manually -- treat it as a click on the root lens (i.e. show blame for the whole file) // 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.range = editor.document.validateRange(new Range(0, 0, 1000000, 1000000));
args.position = editor.document.validateRange(new Range(0, 0, 0, 1000000)).start; args.position = editor.document.validateRange(new Range(0, 0, 0, 1000000)).start;

View File

@@ -1,6 +1,8 @@
'use strict'; 'use strict';
import { Strings } from '../system';
import { commands, InputBoxOptions, TextEditor, Uri, window } from 'vscode'; import { commands, InputBoxOptions, TextEditor, Uri, window } from 'vscode';
import { ActiveEditorCachedCommand, Commands, getCommandUri } from './common'; import { ActiveEditorCachedCommand, Commands, getCommandUri } from './common';
import { GlyphChars } from '../constants';
import { GitRepoSearchBy, GitService, GitUri } from '../gitService'; import { GitRepoSearchBy, GitService, GitUri } from '../gitService';
import { Logger } from '../logger'; import { Logger } from '../logger';
import { Messages } from '../messages'; import { Messages } from '../messages';
@@ -28,7 +30,7 @@ export class ShowCommitSearchCommand extends ActiveEditorCachedCommand {
super(Commands.ShowCommitSearch); super(Commands.ShowCommitSearch);
} }
async execute(editor: TextEditor, uri?: Uri, args: ShowCommitSearchCommandArgs = {}) { async execute(editor?: TextEditor, uri?: Uri, args: ShowCommitSearchCommandArgs = {}) {
uri = getCommandUri(uri, editor); uri = getCommandUri(uri, editor);
const gitUri = uri === undefined ? undefined : await GitUri.fromUri(uri, this.git); const gitUri = uri === undefined ? undefined : await GitUri.fromUri(uri, this.git);
@@ -36,6 +38,7 @@ export class ShowCommitSearchCommand extends ActiveEditorCachedCommand {
const repoPath = gitUri === undefined ? this.git.repoPath : gitUri.repoPath; const repoPath = gitUri === undefined ? this.git.repoPath : gitUri.repoPath;
if (!repoPath) return Messages.showNoRepositoryWarningMessage(`Unable to show commit search`); if (!repoPath) return Messages.showNoRepositoryWarningMessage(`Unable to show commit search`);
args = { ...args };
if (!args.search || args.searchBy == null) { if (!args.search || args.searchBy == null) {
try { try {
if (!args.search) { if (!args.search) {
@@ -93,14 +96,17 @@ export class ShowCommitSearchCommand extends ActiveEditorCachedCommand {
originalSearch = `@${args.search}`; originalSearch = `@${args.search}`;
placeHolder = `commits with author matching '${args.search}'`; placeHolder = `commits with author matching '${args.search}'`;
break; break;
case GitRepoSearchBy.Files: case GitRepoSearchBy.Files:
originalSearch = `:${args.search}`; originalSearch = `:${args.search}`;
placeHolder = `commits with files matching '${args.search}'`; placeHolder = `commits with files matching '${args.search}'`;
break; break;
case GitRepoSearchBy.Message: case GitRepoSearchBy.Message:
originalSearch = args.search; originalSearch = args.search;
placeHolder = `commits with message matching '${args.search}'`; placeHolder = `commits with message matching '${args.search}'`;
break; break;
case GitRepoSearchBy.Sha: case GitRepoSearchBy.Sha:
originalSearch = `#${args.search}`; originalSearch = `#${args.search}`;
placeHolder = `commits with id matching '${args.search}'`; placeHolder = `commits with id matching '${args.search}'`;
@@ -109,8 +115,8 @@ export class ShowCommitSearchCommand extends ActiveEditorCachedCommand {
// Create a command to get back to here // Create a command to get back to here
const currentCommand = new CommandQuickPickItem({ const currentCommand = new CommandQuickPickItem({
label: `go back \u21A9`, label: `go back ${GlyphChars.ArrowBack}`,
description: `\u00a0 \u2014 \u00a0\u00a0 to commit search` description: `${Strings.pad(GlyphChars.Dash, 2, 3)} to commit search`
}, Commands.ShowCommitSearch, [ }, Commands.ShowCommitSearch, [
uri, uri,
{ {
@@ -130,8 +136,8 @@ export class ShowCommitSearchCommand extends ActiveEditorCachedCommand {
sha: pick.commit.sha, sha: pick.commit.sha,
commit: pick.commit, commit: pick.commit,
goBackCommand: new CommandQuickPickItem({ goBackCommand: new CommandQuickPickItem({
label: `go back \u21A9`, label: `go back ${GlyphChars.ArrowBack}`,
description: `\u00a0 \u2014 \u00a0\u00a0 to search for ${placeHolder}` description: `${Strings.pad(GlyphChars.Dash, 2, 2)} to search for ${placeHolder}`
}, Commands.ShowCommitSearch, [ }, Commands.ShowCommitSearch, [
uri, uri,
args args

View File

@@ -17,10 +17,12 @@ export class ShowFileBlameCommand extends EditorCommand {
} }
async execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri, args: ShowFileBlameCommandArgs = {}): Promise<any> { async execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri, args: ShowFileBlameCommandArgs = {}): Promise<any> {
if (editor !== undefined && editor.document !== undefined && editor.document.isDirty) return undefined; if (editor === undefined || editor.document === undefined || editor.document.isDirty) return undefined;
try { try {
if (args.type === undefined) { if (args.type === undefined) {
args = { ...args };
const cfg = workspace.getConfiguration().get<IConfig>(ExtensionKey)!; const cfg = workspace.getConfiguration().get<IConfig>(ExtensionKey)!;
args.type = cfg.blame.file.annotationType; args.type = cfg.blame.file.annotationType;
} }

View File

@@ -2,6 +2,7 @@
import { commands, Position, Range, TextEditor, TextEditorEdit, Uri, window } from 'vscode'; import { commands, Position, Range, TextEditor, TextEditorEdit, Uri, window } from 'vscode';
import { Commands, EditorCommand, getCommandUri } from './common'; import { Commands, EditorCommand, getCommandUri } from './common';
import { BuiltInCommands } from '../constants'; import { BuiltInCommands } from '../constants';
import { GitExplorer } from '../views/gitExplorer';
import { GitService, GitUri } from '../gitService'; import { GitService, GitUri } from '../gitService';
import { Messages } from '../messages'; import { Messages } from '../messages';
import { Logger } from '../logger'; import { Logger } from '../logger';
@@ -14,7 +15,7 @@ export interface ShowFileHistoryCommandArgs {
export class ShowFileHistoryCommand extends EditorCommand { export class ShowFileHistoryCommand extends EditorCommand {
constructor(private git: GitService) { constructor(private git: GitService, private explorer?: GitExplorer) {
super(Commands.ShowFileHistory); super(Commands.ShowFileHistory);
} }
@@ -23,6 +24,8 @@ export class ShowFileHistoryCommand extends EditorCommand {
if (uri === undefined) return undefined; if (uri === undefined) return undefined;
if (args.position == null) { if (args.position == null) {
args = { ...args };
// If the command is executed manually -- treat it as a click on the root lens (i.e. show blame for the whole file) // 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; args.position = editor.document.validateRange(new Range(0, 0, 0, 1000000)).start;
} }
@@ -30,6 +33,11 @@ export class ShowFileHistoryCommand extends EditorCommand {
const gitUri = await GitUri.fromUri(uri, this.git); const gitUri = await GitUri.fromUri(uri, this.git);
try { try {
if (this.explorer !== undefined) {
this.explorer.addHistory(gitUri);
return undefined;
}
const locations = await this.git.getLogLocations(gitUri, args.sha, args.line); const locations = await this.git.getLogLocations(gitUri, args.sha, args.line);
if (locations === undefined) return Messages.showFileNotUnderSourceControlWarningMessage('Unable to show file history'); if (locations === undefined) return Messages.showFileNotUnderSourceControlWarningMessage('Unable to show file history');

View File

@@ -11,7 +11,7 @@ export class ShowLastQuickPickCommand extends Command {
async execute() { async execute() {
const command = getLastCommand(); const command = getLastCommand();
if (!command) return undefined; if (command === undefined) return undefined;
try { try {
return commands.executeCommand(command.command, ...command.args); return commands.executeCommand(command.command, ...command.args);

View File

@@ -16,10 +16,12 @@ export class ShowLineBlameCommand extends EditorCommand {
} }
async execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri, args: ShowLineBlameCommandArgs = {}): Promise<any> { async execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri, args: ShowLineBlameCommandArgs = {}): Promise<any> {
if (editor !== undefined && editor.document !== undefined && editor.document.isDirty) return undefined; if (editor === undefined || editor.document === undefined || editor.document.isDirty) return undefined;
try { try {
if (args.type === undefined) { if (args.type === undefined) {
args = { ...args };
const cfg = workspace.getConfiguration().get<IConfig>(ExtensionKey)!; const cfg = workspace.getConfiguration().get<IConfig>(ExtensionKey)!;
args.type = cfg.blame.line.annotationType; args.type = cfg.blame.line.annotationType;
} }

View File

@@ -1,6 +1,8 @@
'use strict'; 'use strict';
import { Strings } from '../system';
import { commands, TextEditor, Uri, window } from 'vscode'; import { commands, TextEditor, Uri, window } from 'vscode';
import { ActiveEditorCachedCommand, Commands, getCommandUri } from './common'; import { ActiveEditorCachedCommand, Commands, getCommandUri } from './common';
import { GlyphChars } from '../constants';
import { GitLog, GitService, GitUri } from '../gitService'; import { GitLog, GitService, GitUri } from '../gitService';
import { Logger } from '../logger'; import { Logger } from '../logger';
import { Messages } from '../messages'; import { Messages } from '../messages';
@@ -22,11 +24,12 @@ export class ShowQuickBranchHistoryCommand extends ActiveEditorCachedCommand {
super(Commands.ShowQuickBranchHistory); super(Commands.ShowQuickBranchHistory);
} }
async execute(editor: TextEditor, uri?: Uri, args: ShowQuickBranchHistoryCommandArgs = {}) { async execute(editor?: TextEditor, uri?: Uri, args: ShowQuickBranchHistoryCommandArgs = {}) {
uri = getCommandUri(uri, editor); uri = getCommandUri(uri, editor);
const gitUri = uri && await GitUri.fromUri(uri, this.git); const gitUri = uri && await GitUri.fromUri(uri, this.git);
args = { ...args };
if (args.maxCount == null) { if (args.maxCount == null) {
args.maxCount = this.git.config.advanced.maxQuickHistory; args.maxCount = this.git.config.advanced.maxQuickHistory;
} }
@@ -39,7 +42,7 @@ export class ShowQuickBranchHistoryCommand extends ActiveEditorCachedCommand {
if (args.branch === undefined) { if (args.branch === undefined) {
const branches = await this.git.getBranches(repoPath); const branches = await this.git.getBranches(repoPath);
const pick = await BranchesQuickPick.show(branches, `Show history for branch\u2026`); const pick = await BranchesQuickPick.show(branches, `Show history for branch${GlyphChars.Ellipsis}`);
if (pick === undefined) return undefined; if (pick === undefined) return undefined;
if (pick instanceof CommandQuickPickItem) return pick.execute(); if (pick instanceof CommandQuickPickItem) return pick.execute();
@@ -64,8 +67,8 @@ export class ShowQuickBranchHistoryCommand extends ActiveEditorCachedCommand {
// Create a command to get back to here // Create a command to get back to here
const currentCommand = new CommandQuickPickItem({ const currentCommand = new CommandQuickPickItem({
label: `go back \u21A9`, label: `go back ${GlyphChars.ArrowBack}`,
description: `\u00a0 \u2014 \u00a0\u00a0 to \u00a0$(git-branch) ${args.branch} history` description: `${Strings.pad(GlyphChars.Dash, 2, 3)} to ${GlyphChars.Space}$(git-branch) ${args.branch} history`
}, Commands.ShowQuickBranchHistory, [ }, Commands.ShowQuickBranchHistory, [
uri, uri,
args args

View File

@@ -1,6 +1,9 @@
'use strict'; 'use strict';
import { Strings } from '../system';
import { commands, TextEditor, Uri, window } from 'vscode'; import { commands, TextEditor, Uri, window } from 'vscode';
import { ActiveEditorCachedCommand, Commands, getCommandUri } from './common'; import { ActiveEditorCachedCommand, CommandContext, Commands, getCommandUri } from './common';
import { GlyphChars } from '../constants';
import { CommitNode } from '../views/explorerNodes';
import { GitCommit, GitLog, GitLogCommit, GitService, GitUri } from '../gitService'; import { GitCommit, GitLog, GitLogCommit, GitService, GitUri } from '../gitService';
import { Logger } from '../logger'; import { Logger } from '../logger';
import { CommandQuickPickItem, CommitDetailsQuickPick, CommitWithFileStatusQuickPickItem } from '../quickPicks'; import { CommandQuickPickItem, CommitDetailsQuickPick, CommitWithFileStatusQuickPickItem } from '../quickPicks';
@@ -22,7 +25,19 @@ export class ShowQuickCommitDetailsCommand extends ActiveEditorCachedCommand {
super(Commands.ShowQuickCommitDetails); super(Commands.ShowQuickCommitDetails);
} }
async execute(editor: TextEditor, uri?: Uri, args: ShowQuickCommitDetailsCommandArgs = {}) { protected async preExecute(context: CommandContext, ...args: any[]): Promise<any> {
if (context.type === 'view') {
if (context.node instanceof CommitNode) {
args = [{ sha: context.node.uri.sha, commit: context.node.commit }];
}
else {
args = [{ sha: context.node.uri.sha }];
}
}
return this.execute(context.editor, context.uri, ...args);
}
async execute(editor?: TextEditor, uri?: Uri, args: ShowQuickCommitDetailsCommandArgs = {}) {
uri = getCommandUri(uri, editor); uri = getCommandUri(uri, editor);
if (uri === undefined) return undefined; if (uri === undefined) return undefined;
@@ -31,6 +46,7 @@ export class ShowQuickCommitDetailsCommand extends ActiveEditorCachedCommand {
let repoPath = gitUri.repoPath; let repoPath = gitUri.repoPath;
let workingFileName = path.relative(repoPath || '', gitUri.fsPath); let workingFileName = path.relative(repoPath || '', gitUri.fsPath);
args = { ...args };
if (args.sha === undefined) { if (args.sha === undefined) {
if (editor === undefined) return undefined; if (editor === undefined) return undefined;
@@ -83,8 +99,8 @@ export class ShowQuickCommitDetailsCommand extends ActiveEditorCachedCommand {
if (args.goBackCommand === undefined) { if (args.goBackCommand === undefined) {
// Create a command to get back to the branch history // Create a command to get back to the branch history
args.goBackCommand = new CommandQuickPickItem({ args.goBackCommand = new CommandQuickPickItem({
label: `go back \u21A9`, label: `go back ${GlyphChars.ArrowBack}`,
description: `\u00a0 \u2014 \u00a0\u00a0 to branch history` description: `${Strings.pad(GlyphChars.Dash, 2, 3)} to branch history`
}, Commands.ShowQuickCurrentBranchHistory, [ }, Commands.ShowQuickCurrentBranchHistory, [
new GitUri(args.commit.uri, args.commit) new GitUri(args.commit.uri, args.commit)
]); ]);
@@ -92,8 +108,8 @@ export class ShowQuickCommitDetailsCommand extends ActiveEditorCachedCommand {
// Create a command to get back to where we are right now // Create a command to get back to where we are right now
const currentCommand = new CommandQuickPickItem({ const currentCommand = new CommandQuickPickItem({
label: `go back \u21A9`, label: `go back ${GlyphChars.ArrowBack}`,
description: `\u00a0 \u2014 \u00a0\u00a0 to details of \u00a0$(git-commit) ${args.commit.shortSha}` description: `${Strings.pad(GlyphChars.Dash, 2, 3)} to details of ${GlyphChars.Space}$(git-commit) ${args.commit.shortSha}`
}, Commands.ShowQuickCommitDetails, [ }, Commands.ShowQuickCommitDetails, [
new GitUri(args.commit.uri, args.commit), new GitUri(args.commit.uri, args.commit),
args args

View File

@@ -1,6 +1,8 @@
'use strict'; 'use strict';
import { Strings } from '../system';
import { TextEditor, Uri, window } from 'vscode'; import { TextEditor, Uri, window } from 'vscode';
import { ActiveEditorCachedCommand, Commands, getCommandUri } from './common'; import { ActiveEditorCachedCommand, Commands, getCommandUri } from './common';
import { GlyphChars } from '../constants';
import { GitCommit, GitLog, GitLogCommit, GitService, GitUri } from '../gitService'; import { GitCommit, GitLog, GitLogCommit, GitService, GitUri } from '../gitService';
import { Logger } from '../logger'; import { Logger } from '../logger';
import { CommandQuickPickItem, CommitFileDetailsQuickPick } from '../quickPicks'; import { CommandQuickPickItem, CommitFileDetailsQuickPick } from '../quickPicks';
@@ -22,7 +24,7 @@ export class ShowQuickCommitFileDetailsCommand extends ActiveEditorCachedCommand
super(Commands.ShowQuickCommitFileDetails); super(Commands.ShowQuickCommitFileDetails);
} }
async execute(editor: TextEditor, uri?: Uri, args: ShowQuickCommitFileDetailsCommandArgs = {}) { async execute(editor?: TextEditor, uri?: Uri, args: ShowQuickCommitFileDetailsCommandArgs = {}) {
uri = getCommandUri(uri, editor); uri = getCommandUri(uri, editor);
if (uri === undefined) return undefined; if (uri === undefined) return undefined;
@@ -30,6 +32,7 @@ export class ShowQuickCommitFileDetailsCommand extends ActiveEditorCachedCommand
const gitUri = await GitUri.fromUri(uri, this.git); const gitUri = await GitUri.fromUri(uri, this.git);
args = { ...args };
if (args.sha === undefined) { if (args.sha === undefined) {
if (editor === undefined) return undefined; if (editor === undefined) return undefined;
@@ -85,8 +88,8 @@ export class ShowQuickCommitFileDetailsCommand extends ActiveEditorCachedCommand
if (args.goBackCommand === undefined) { if (args.goBackCommand === undefined) {
// Create a command to get back to the commit details // Create a command to get back to the commit details
args.goBackCommand = new CommandQuickPickItem({ args.goBackCommand = new CommandQuickPickItem({
label: `go back \u21A9`, label: `go back ${GlyphChars.ArrowBack}`,
description: `\u00a0 \u2014 \u00a0\u00a0 to details of \u00a0$(git-commit) ${shortSha}` description: `${Strings.pad(GlyphChars.Dash, 2, 3)} to details of ${GlyphChars.Space}$(git-commit) ${shortSha}`
}, Commands.ShowQuickCommitDetails, [ }, Commands.ShowQuickCommitDetails, [
new GitUri(args.commit.uri, args.commit), new GitUri(args.commit.uri, args.commit),
{ {
@@ -98,8 +101,8 @@ export class ShowQuickCommitFileDetailsCommand extends ActiveEditorCachedCommand
// Create a command to get back to where we are right now // Create a command to get back to where we are right now
const currentCommand = new CommandQuickPickItem({ const currentCommand = new CommandQuickPickItem({
label: `go back \u21A9`, label: `go back ${GlyphChars.ArrowBack}`,
description: `\u00a0 \u2014 \u00a0\u00a0 to details of \u00a0$(file-text) ${path.basename(args.commit.fileName)} in \u00a0$(git-commit) ${shortSha}` description: `${Strings.pad(GlyphChars.Dash, 2, 3)} to details of ${GlyphChars.Space}$(file-text) ${path.basename(args.commit.fileName)} in ${GlyphChars.Space}$(git-commit) ${shortSha}`
}, Commands.ShowQuickCommitFileDetails, [ }, Commands.ShowQuickCommitFileDetails, [
new GitUri(args.commit.uri, args.commit), new GitUri(args.commit.uri, args.commit),
args args

View File

@@ -17,7 +17,7 @@ export class ShowQuickCurrentBranchHistoryCommand extends ActiveEditorCachedComm
super(Commands.ShowQuickCurrentBranchHistory); super(Commands.ShowQuickCurrentBranchHistory);
} }
async execute(editor: TextEditor, uri?: Uri, args: ShowQuickCurrentBranchHistoryCommandArgs = {}) { async execute(editor?: TextEditor, uri?: Uri, args: ShowQuickCurrentBranchHistoryCommandArgs = {}) {
uri = getCommandUri(uri, editor); uri = getCommandUri(uri, editor);
try { try {

View File

@@ -1,76 +1,79 @@
'use strict'; 'use strict';
import { commands, Range, TextEditor, Uri, window } from 'vscode'; import { Strings } from '../system';
import { ActiveEditorCachedCommand, Commands, getCommandUri } from './common'; import { commands, Range, TextEditor, Uri, window } from 'vscode';
import { GitLog, GitService, GitUri } from '../gitService'; import { ActiveEditorCachedCommand, Commands, getCommandUri } from './common';
import { Logger } from '../logger'; import { GlyphChars } from '../constants';
import { CommandQuickPickItem, FileHistoryQuickPick } from '../quickPicks'; import { GitLog, GitService, GitUri } from '../gitService';
import { ShowQuickCommitFileDetailsCommandArgs } from './showQuickCommitFileDetails'; import { Logger } from '../logger';
import { Messages } from '../messages'; import { CommandQuickPickItem, FileHistoryQuickPick } from '../quickPicks';
import * as path from 'path'; import { ShowQuickCommitFileDetailsCommandArgs } from './showQuickCommitFileDetails';
import { Messages } from '../messages';
export interface ShowQuickFileHistoryCommandArgs { import * as path from 'path';
log?: GitLog;
maxCount?: number; export interface ShowQuickFileHistoryCommandArgs {
range?: Range; log?: GitLog;
maxCount?: number;
goBackCommand?: CommandQuickPickItem; range?: Range;
nextPageCommand?: CommandQuickPickItem;
} goBackCommand?: CommandQuickPickItem;
nextPageCommand?: CommandQuickPickItem;
export class ShowQuickFileHistoryCommand extends ActiveEditorCachedCommand { }
constructor(private git: GitService) { export class ShowQuickFileHistoryCommand extends ActiveEditorCachedCommand {
super(Commands.ShowQuickFileHistory);
} 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); async execute(editor?: TextEditor, uri?: Uri, args: ShowQuickFileHistoryCommandArgs = {}) {
uri = getCommandUri(uri, editor);
const gitUri = await GitUri.fromUri(uri, this.git); if (uri === undefined) return commands.executeCommand(Commands.ShowQuickCurrentBranchHistory);
if (args.maxCount == null) { const gitUri = await GitUri.fromUri(uri, this.git);
args.maxCount = this.git.config.advanced.maxQuickHistory;
} args = { ...args };
if (args.maxCount == null) {
const progressCancellation = FileHistoryQuickPick.showProgress(gitUri); args.maxCount = this.git.config.advanced.maxQuickHistory;
try { }
if (args.log === undefined) {
args.log = await this.git.getLogForFile(gitUri.repoPath, gitUri.fsPath, gitUri.sha, args.maxCount, args.range); const progressCancellation = FileHistoryQuickPick.showProgress(gitUri);
if (args.log === undefined) return Messages.showFileNotUnderSourceControlWarningMessage('Unable to show file history'); try {
} if (args.log === undefined) {
args.log = await this.git.getLogForFile(gitUri.repoPath, gitUri.fsPath, gitUri.sha, { maxCount: args.maxCount, range: args.range });
if (progressCancellation.token.isCancellationRequested) return undefined; if (args.log === undefined) return Messages.showFileNotUnderSourceControlWarningMessage('Unable to show file history');
}
const pick = await FileHistoryQuickPick.show(this.git, args.log, gitUri, progressCancellation, args.goBackCommand, args.nextPageCommand);
if (pick === undefined) return undefined; if (progressCancellation.token.isCancellationRequested) return undefined;
if (pick instanceof CommandQuickPickItem) return pick.execute(); const pick = await FileHistoryQuickPick.show(this.git, args.log, gitUri, progressCancellation, { goBackCommand: args.goBackCommand, nextPageCommand: args.nextPageCommand });
if (pick === undefined) return undefined;
// Create a command to get back to where we are right now
const currentCommand = new CommandQuickPickItem({ if (pick instanceof CommandQuickPickItem) return pick.execute();
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}` : ''}` // Create a command to get back to where we are right now
}, Commands.ShowQuickFileHistory, [ const currentCommand = new CommandQuickPickItem({
uri, label: `go back ${GlyphChars.ArrowBack}`,
args description: `${Strings.pad(GlyphChars.Dash, 2, 3)} to history of ${GlyphChars.Space}$(file-text) ${path.basename(pick.commit.fileName)}${gitUri.sha ? ` from ${GlyphChars.Space}$(git-commit) ${gitUri.shortSha}` : ''}`
]); }, Commands.ShowQuickFileHistory, [
uri,
return commands.executeCommand(Commands.ShowQuickCommitFileDetails, args
new GitUri(pick.commit.uri, pick.commit), ]);
{
commit: pick.commit, return commands.executeCommand(Commands.ShowQuickCommitFileDetails,
fileLog: args.log, new GitUri(pick.commit.uri, pick.commit),
sha: pick.commit.sha, {
goBackCommand: currentCommand commit: pick.commit,
} as ShowQuickCommitFileDetailsCommandArgs); fileLog: args.log,
} sha: pick.commit.sha,
catch (ex) { goBackCommand: currentCommand
Logger.error(ex, 'ShowQuickFileHistoryCommand'); } as ShowQuickCommitFileDetailsCommandArgs);
return window.showErrorMessage(`Unable to show file history. See output channel for more details`); }
} catch (ex) {
finally { Logger.error(ex, 'ShowQuickFileHistoryCommand');
progressCancellation.dispose(); return window.showErrorMessage(`Unable to show file history. See output channel for more details`);
} }
} finally {
progressCancellation.dispose();
}
}
} }

View File

@@ -16,7 +16,7 @@ export class ShowQuickRepoStatusCommand extends ActiveEditorCachedCommand {
super(Commands.ShowQuickRepoStatus); super(Commands.ShowQuickRepoStatus);
} }
async execute(editor: TextEditor, uri?: Uri, args: ShowQuickRepoStatusCommandArgs = {}) { async execute(editor?: TextEditor, uri?: Uri, args: ShowQuickRepoStatusCommandArgs = {}) {
uri = getCommandUri(uri, editor); uri = getCommandUri(uri, editor);
try { try {

View File

@@ -1,6 +1,8 @@
'use strict'; 'use strict';
import { Strings } from '../system';
import { commands, TextEditor, Uri, window } from 'vscode'; import { commands, TextEditor, Uri, window } from 'vscode';
import { ActiveEditorCachedCommand, Commands, getCommandUri } from './common'; import { ActiveEditorCachedCommand, Commands, getCommandUri } from './common';
import { GlyphChars } from '../constants';
import { GitService, GitUri } from '../gitService'; import { GitService, GitUri } from '../gitService';
import { Logger } from '../logger'; import { Logger } from '../logger';
import { Messages } from '../messages'; import { Messages } from '../messages';
@@ -17,7 +19,7 @@ export class ShowQuickStashListCommand extends ActiveEditorCachedCommand {
super(Commands.ShowQuickStashList); super(Commands.ShowQuickStashList);
} }
async execute(editor: TextEditor, uri?: Uri, args: ShowQuickStashListCommandArgs = {}) { async execute(editor?: TextEditor, uri?: Uri, args: ShowQuickStashListCommandArgs = {}) {
uri = getCommandUri(uri, editor); uri = getCommandUri(uri, editor);
try { try {
@@ -29,8 +31,8 @@ export class ShowQuickStashListCommand extends ActiveEditorCachedCommand {
// Create a command to get back to here // Create a command to get back to here
const currentCommand = new CommandQuickPickItem({ const currentCommand = new CommandQuickPickItem({
label: `go back \u21A9`, label: `go back ${GlyphChars.ArrowBack}`,
description: `\u00a0 \u2014 \u00a0\u00a0 to stashed changes` description: `${Strings.pad(GlyphChars.Dash, 2, 3)} to stashed changes`
}, Commands.ShowQuickStashList, [ }, Commands.ShowQuickStashList, [
uri, uri,
{ {

View File

@@ -1,10 +1,13 @@
'use strict'; 'use strict';
import { Strings } from '../system';
import { MessageItem, window } from 'vscode'; import { MessageItem, window } from 'vscode';
import { GitService, GitStashCommit } from '../gitService'; import { GitService, GitStashCommit } from '../gitService';
import { Command, Commands } from './common'; import { Command, CommandContext, Commands } from './common';
import { GlyphChars } from '../constants';
import { CommitQuickPickItem, StashListQuickPick } from '../quickPicks'; import { CommitQuickPickItem, StashListQuickPick } from '../quickPicks';
import { Logger } from '../logger'; import { Logger } from '../logger';
import { CommandQuickPickItem } from '../quickPicks'; import { CommandQuickPickItem } from '../quickPicks';
import { StashCommitNode } from '../views/stashCommitNode';
export interface StashApplyCommandArgs { export interface StashApplyCommandArgs {
confirm?: boolean; confirm?: boolean;
@@ -20,16 +23,30 @@ export class StashApplyCommand extends Command {
super(Commands.StashApply); super(Commands.StashApply);
} }
protected async preExecute(context: CommandContext, args: StashApplyCommandArgs = { confirm: true, deleteAfter: false }) {
if (context.type === 'view' && context.node instanceof StashCommitNode) {
args = { ...args };
const stash = context.node.commit;
args.stashItem = { stashName: stash.stashName, message: stash.message };
return this.execute(args);
}
return super.preExecute(context, args);
}
async execute(args: StashApplyCommandArgs = { confirm: true, deleteAfter: false }) { async execute(args: StashApplyCommandArgs = { confirm: true, deleteAfter: false }) {
if (!this.git.repoPath) return undefined; if (!this.git.repoPath) return undefined;
args = { ...args };
if (args.stashItem === undefined || args.stashItem.stashName === undefined) { if (args.stashItem === undefined || args.stashItem.stashName === undefined) {
const stash = await this.git.getStashList(this.git.repoPath); const stash = await this.git.getStashList(this.git.repoPath);
if (stash === undefined) return window.showInformationMessage(`There are no stashed changes`); if (stash === undefined) return window.showInformationMessage(`There are no stashed changes`);
const currentCommand = new CommandQuickPickItem({ const currentCommand = new CommandQuickPickItem({
label: `go back \u21A9`, label: `go back ${GlyphChars.ArrowBack}`,
description: `\u00a0 \u2014 \u00a0\u00a0 to apply stashed changes` description: `${Strings.pad(GlyphChars.Dash, 2, 3)} to apply stashed changes`
}, Commands.StashApply, [args]); }, Commands.StashApply, [args]);
const pick = await StashListQuickPick.show(this.git, stash, 'apply', args.goBackCommand, currentCommand); const pick = await StashListQuickPick.show(this.git, stash, 'apply', args.goBackCommand, currentCommand);
@@ -41,7 +58,7 @@ export class StashApplyCommand extends Command {
try { try {
if (args.confirm) { if (args.confirm) {
const message = args.stashItem.message.length > 80 ? `${args.stashItem.message.substring(0, 80)}\u2026` : args.stashItem.message; const message = args.stashItem.message.length > 80 ? `${args.stashItem.message.substring(0, 80)}${GlyphChars.Ellipsis}` : 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); 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(); if (result === undefined || result.title === 'No') return args.goBackCommand === undefined ? undefined : args.goBackCommand.execute();
@@ -53,7 +70,10 @@ export class StashApplyCommand extends Command {
catch (ex) { catch (ex) {
Logger.error(ex, 'StashApplyCommand'); Logger.error(ex, 'StashApplyCommand');
if (ex.message.includes('Your local changes to the following files would be overwritten by merge')) { 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.`); return window.showWarningMessage(`Unable to apply stash. Your working tree changes would be overwritten.`);
}
else if (ex.message.includes('Auto-merging') && ex.message.includes('CONFLICT')) {
return window.showInformationMessage(`Stash applied with conflicts`);
} }
else { else {
return window.showErrorMessage(`Unable to apply stash. See output channel for more details`); return window.showErrorMessage(`Unable to apply stash. See output channel for more details`);

View File

@@ -1,9 +1,11 @@
'use strict'; 'use strict';
import { MessageItem, window } from 'vscode'; import { MessageItem, window } from 'vscode';
import { Command, CommandContext, Commands } from './common';
import { GlyphChars } from '../constants';
import { GitService } from '../gitService'; import { GitService } from '../gitService';
import { Command, Commands } from './common';
import { Logger } from '../logger'; import { Logger } from '../logger';
import { CommandQuickPickItem } from '../quickPicks'; import { CommandQuickPickItem } from '../quickPicks';
import { StashCommitNode } from '../views/stashCommitNode';
export interface StashDeleteCommandArgs { export interface StashDeleteCommandArgs {
confirm?: boolean; confirm?: boolean;
@@ -18,9 +20,23 @@ export class StashDeleteCommand extends Command {
super(Commands.StashDelete); super(Commands.StashDelete);
} }
protected async preExecute(context: CommandContext, args: StashDeleteCommandArgs = { confirm: true }) {
if (context.type === 'view' && context.node instanceof StashCommitNode) {
args = { ...args };
const stash = context.node.commit;
args.stashItem = { stashName: stash.stashName, message: stash.message };
return this.execute(args);
}
return super.preExecute(context, args);
}
async execute(args: StashDeleteCommandArgs = { confirm: true }) { async execute(args: StashDeleteCommandArgs = { confirm: true }) {
if (!this.git.repoPath) return undefined; if (!this.git.repoPath) return undefined;
args = { ...args };
if (args.stashItem === undefined || args.stashItem.stashName === undefined) return undefined; if (args.stashItem === undefined || args.stashItem.stashName === undefined) return undefined;
if (args.confirm === undefined) { if (args.confirm === undefined) {
@@ -29,7 +45,7 @@ export class StashDeleteCommand extends Command {
try { try {
if (args.confirm) { if (args.confirm) {
const message = args.stashItem.message.length > 80 ? `${args.stashItem.message.substring(0, 80)}\u2026` : args.stashItem.message; const message = args.stashItem.message.length > 80 ? `${args.stashItem.message.substring(0, 80)}${GlyphChars.Ellipsis}` : args.stashItem.message;
const result = await window.showWarningMessage(`Delete stashed changes '${message}'?`, { title: 'Yes' } as MessageItem, { title: 'No', isCloseAffordance: true } as MessageItem); 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(); if (result === undefined || result.title !== 'Yes') return args.goBackCommand === undefined ? undefined : args.goBackCommand.execute();
} }

View File

@@ -18,9 +18,10 @@ export class StashSaveCommand extends Command {
super(Commands.StashSave); super(Commands.StashSave);
} }
async execute(args: StashSaveCommandArgs = { unstagedOnly : false }) { async execute(args: StashSaveCommandArgs = { unstagedOnly: false }) {
if (!this.git.repoPath) return undefined; if (!this.git.repoPath) return undefined;
args = { ...args };
if (args.unstagedOnly === undefined) { if (args.unstagedOnly === undefined) {
args.unstagedOnly = false; args.unstagedOnly = false;
} }

View File

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

View File

@@ -17,10 +17,12 @@ export class ToggleFileBlameCommand extends EditorCommand {
} }
async execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri, args: ToggleFileBlameCommandArgs = {}): Promise<any> { async execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri, args: ToggleFileBlameCommandArgs = {}): Promise<any> {
if (editor !== undefined && editor.document !== undefined && editor.document.isDirty) return undefined; if (editor === undefined || editor.document === undefined || editor.document.isDirty) return undefined;
try { try {
if (args.type === undefined) { if (args.type === undefined) {
args = { ...args };
const cfg = workspace.getConfiguration().get<IConfig>(ExtensionKey)!; const cfg = workspace.getConfiguration().get<IConfig>(ExtensionKey)!;
args.type = cfg.blame.file.annotationType; args.type = cfg.blame.file.annotationType;
} }

View File

@@ -11,7 +11,7 @@ export class ToggleFileRecentChangesCommand extends EditorCommand {
} }
async execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri): Promise<any> { async execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri): Promise<any> {
if (editor !== undefined && editor.document !== undefined && editor.document.isDirty) return undefined; if (editor === undefined || editor.document === undefined || editor.document.isDirty) return undefined;
try { try {
return this.annotationController.toggleAnnotations(editor, FileAnnotationType.RecentChanges); return this.annotationController.toggleAnnotations(editor, FileAnnotationType.RecentChanges);

View File

@@ -16,10 +16,12 @@ export class ToggleLineBlameCommand extends EditorCommand {
} }
async execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri, args: ToggleLineBlameCommandArgs = {}): Promise<any> { async execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri, args: ToggleLineBlameCommandArgs = {}): Promise<any> {
if (editor !== undefined && editor.document !== undefined && editor.document.isDirty) return undefined; if (editor === undefined || editor.document === undefined || editor.document.isDirty) return undefined;
try { try {
if (args.type === undefined) { if (args.type === undefined) {
args = { ...args };
const cfg = workspace.getConfiguration().get<IConfig>(ExtensionKey)!; const cfg = workspace.getConfiguration().get<IConfig>(ExtensionKey)!;
args.type = cfg.blame.line.annotationType; args.type = cfg.blame.line.annotationType;
} }

View File

@@ -134,10 +134,6 @@ export interface IThemeConfig {
uncommittedForegroundColor: string | null; uncommittedForegroundColor: string | null;
}; };
}; };
hover: {
separateLines: boolean;
};
}; };
line: { line: {
@@ -181,9 +177,6 @@ export const themeDefaults: IThemeConfig = {
foregroundColor: 'rgb(116, 116, 116)', foregroundColor: 'rgb(116, 116, 116)',
uncommittedForegroundColor: null uncommittedForegroundColor: null
} }
},
hover: {
separateLines: false
} }
}, },
line: { line: {
@@ -303,6 +296,24 @@ export interface IConfig {
defaultDateFormat: string | null; defaultDateFormat: string | null;
fileHistoryExplorer: {
commitFormat: string;
// commitFileFormat: string;
// dateFormat: string | null;
};
gitExplorer: {
commitFormat: string;
commitFileFormat: string;
// dateFormat: string | null;
};
stashExplorer: {
stashFormat: string;
stashFileFormat: string;
// dateFormat: string | null;
};
statusBar: { statusBar: {
enabled: boolean; enabled: boolean;
alignment: 'left' | 'right'; alignment: 'left' | 'right';

View File

@@ -1,4 +1,5 @@
'use strict'; 'use strict';
import { commands } from 'vscode';
export const ExtensionId = 'gitlens'; export const ExtensionId = 'gitlens';
export const ExtensionKey = ExtensionId; export const ExtensionKey = ExtensionId;
@@ -38,6 +39,30 @@ export const BuiltInCommands = {
ToggleRenderWhitespace: 'editor.action.toggleRenderWhitespace' as BuiltInCommands ToggleRenderWhitespace: 'editor.action.toggleRenderWhitespace' as BuiltInCommands
}; };
export type CommandContext =
'gitlens:canToggleCodeLens' |
'gitlens:enabled' |
'gitlens:hasRemotes' |
'gitlens:isBlameable' |
'gitlens:isRepository' |
'gitlens:isTracked' |
'gitlens:key' |
'gitlens:annotationStatus';
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,
AnnotationStatus: 'gitlens:annotationStatus' as CommandContext
};
export function setCommandContext(key: CommandContext | string, value: any) {
return commands.executeCommand(BuiltInCommands.SetContext, key, value);
}
export type DocumentSchemes = 'file' | 'git' | 'gitlens-git'; export type DocumentSchemes = 'file' | 'git' | 'gitlens-git';
export const DocumentSchemes = { export const DocumentSchemes = {
File: 'file' as DocumentSchemes, File: 'file' as DocumentSchemes,
@@ -45,6 +70,33 @@ export const DocumentSchemes = {
GitLensGit: 'gitlens-git' as DocumentSchemes GitLensGit: 'gitlens-git' as DocumentSchemes
}; };
export type GlyphChars = '\u21a9' |
'\u2193' |
'\u2937' |
'\u2190' |
'\u2194' |
'\u21e8' |
'\u2191' |
'\u2014' |
'\u2022' |
'\u2026' |
'\u00a0' |
'\u200b';
export const GlyphChars = {
ArrowBack: '\u21a9' as GlyphChars,
ArrowDown: '\u2193' as GlyphChars,
ArrowDropRight: '\u2937' as GlyphChars,
ArrowLeft: '\u2190' as GlyphChars,
ArrowLeftRight: '\u2194' as GlyphChars,
ArrowRightHollow: '\u21e8' as GlyphChars,
ArrowUp: '\u2191' as GlyphChars,
Dash: '\u2014' as GlyphChars,
Dot: '\u2022' as GlyphChars,
Ellipsis: '\u2026' as GlyphChars,
Space: '\u00a0' as GlyphChars,
ZeroWidthSpace: '\u200b' as GlyphChars
};
export type WorkspaceState = 'gitlensVersion'; export type WorkspaceState = 'gitlensVersion';
export const WorkspaceState = { export const WorkspaceState = {
GitLensVersion: 'gitlensVersion' as WorkspaceState GitLensVersion: 'gitlensVersion' as WorkspaceState

View File

@@ -1,6 +1,6 @@
'use strict'; 'use strict';
import { Functions, Objects } from './system'; import { Functions, Objects } from './system';
import { DecorationOptions, DecorationRenderOptions, Disposable, ExtensionContext, Range, StatusBarAlignment, StatusBarItem, TextEditor, TextEditorDecorationType, TextEditorSelectionChangeEvent, window, workspace } from 'vscode'; import { debug, DecorationOptions, DecorationRenderOptions, Disposable, ExtensionContext, Range, StatusBarAlignment, StatusBarItem, TextEditor, TextEditorDecorationType, TextEditorSelectionChangeEvent, window, workspace } from 'vscode';
import { AnnotationController, FileAnnotationType } from './annotations/annotationController'; import { AnnotationController, FileAnnotationType } from './annotations/annotationController';
import { Annotations, endOfLineIndex } from './annotations/annotations'; import { Annotations, endOfLineIndex } from './annotations/annotations';
import { Commands } from './commands'; import { Commands } from './commands';
@@ -8,6 +8,7 @@ import { TextEditorComparer } from './comparers';
import { IConfig, StatusBarCommand } from './configuration'; import { IConfig, StatusBarCommand } from './configuration';
import { DocumentSchemes, ExtensionKey } from './constants'; import { DocumentSchemes, ExtensionKey } from './constants';
import { BlameabilityChangeEvent, CommitFormatter, GitCommit, GitCommitLine, GitContextTracker, GitService, GitUri } from './gitService'; import { BlameabilityChangeEvent, CommitFormatter, GitCommit, GitCommitLine, GitContextTracker, GitService, GitUri } from './gitService';
import { Logger } from './logger';
const annotationDecoration: TextEditorDecorationType = window.createTextEditorDecorationType({ const annotationDecoration: TextEditorDecorationType = window.createTextEditorDecorationType({
after: { after: {
@@ -24,14 +25,16 @@ export const LineAnnotationType = {
export class CurrentLineController extends Disposable { export class CurrentLineController extends Disposable {
private _activeEditorLineDisposable: Disposable | undefined;
private _blameable: boolean; private _blameable: boolean;
private _blameLineAnnotationState: { enabled: boolean, annotationType: LineAnnotationType, reason: 'user' | 'debugging' } | undefined = undefined;
private _config: IConfig; private _config: IConfig;
private _currentLine: number = -1; private _currentLine: number = -1;
private _debugSessionEndDisposable: Disposable | undefined;
private _disposable: Disposable; private _disposable: Disposable;
private _editor: TextEditor | undefined; private _editor: TextEditor | undefined;
private _isAnnotating: boolean = false; private _isAnnotating: boolean = false;
private _statusBarItem: StatusBarItem | undefined; private _statusBarItem: StatusBarItem | undefined;
private _trackCurrentLineDisposable: Disposable | undefined;
private _updateBlameDebounced: (line: number, editor: TextEditor) => Promise<void>; private _updateBlameDebounced: (line: number, editor: TextEditor) => Promise<void>;
private _uri: GitUri; private _uri: GitUri;
@@ -46,7 +49,8 @@ export class CurrentLineController extends Disposable {
subscriptions.push(workspace.onDidChangeConfiguration(this._onConfigurationChanged, this)); subscriptions.push(workspace.onDidChangeConfiguration(this._onConfigurationChanged, this));
subscriptions.push(git.onDidChangeGitCache(this._onGitCacheChanged, this)); subscriptions.push(git.onDidChangeGitCache(this._onGitCacheChanged, this));
subscriptions.push(annotationController.onDidToggleAnnotations(this._onAnnotationsToggled, this)); subscriptions.push(annotationController.onDidToggleAnnotations(this._onFileAnnotationsToggled, this));
subscriptions.push(debug.onDidStartDebugSession(this._onDebugSessionStarted, this));
this._disposable = Disposable.from(...subscriptions); this._disposable = Disposable.from(...subscriptions);
} }
@@ -54,8 +58,9 @@ export class CurrentLineController extends Disposable {
dispose() { dispose() {
this._clearAnnotations(this._editor, true); this._clearAnnotations(this._editor, true);
this._activeEditorLineDisposable && this._activeEditorLineDisposable.dispose(); this._trackCurrentLineDisposable && this._trackCurrentLineDisposable.dispose();
this._statusBarItem && this._statusBarItem.dispose(); this._statusBarItem && this._statusBarItem.dispose();
this._debugSessionEndDisposable && this._debugSessionEndDisposable.dispose();
this._disposable && this._disposable.dispose(); this._disposable && this._disposable.dispose();
} }
@@ -64,8 +69,14 @@ export class CurrentLineController extends Disposable {
let changed = false; let changed = false;
if (!Objects.areEquivalent(cfg.blame.line, this._config && this._config.blame.line) || 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) || changed = true;
this._blameLineAnnotationState = undefined;
this._clearAnnotations(this._editor);
}
if (!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.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)) { !Objects.areEquivalent(cfg.theme.annotations.line.trailing, this._config && this._config.theme.annotations.line.trailing)) {
changed = true; changed = true;
@@ -94,55 +105,26 @@ export class CurrentLineController extends Disposable {
if (!changed) return; if (!changed) return;
const trackCurrentLine = cfg.statusBar.enabled || cfg.blame.line.enabled; const trackCurrentLine = cfg.statusBar.enabled || cfg.blame.line.enabled || (this._blameLineAnnotationState && this._blameLineAnnotationState.enabled);
if (trackCurrentLine && !this._activeEditorLineDisposable) { if (trackCurrentLine && !this._trackCurrentLineDisposable) {
const subscriptions: Disposable[] = []; const subscriptions: Disposable[] = [];
subscriptions.push(window.onDidChangeActiveTextEditor(this._onActiveTextEditorChanged, this)); subscriptions.push(window.onDidChangeActiveTextEditor(this._onActiveTextEditorChanged, this));
subscriptions.push(window.onDidChangeTextEditorSelection(this._onTextEditorSelectionChanged, this)); subscriptions.push(window.onDidChangeTextEditorSelection(this._onTextEditorSelectionChanged, this));
subscriptions.push(this.gitContextTracker.onDidBlameabilityChange(this._onBlameabilityChanged, this)); subscriptions.push(this.gitContextTracker.onDidChangeBlameability(this._onBlameabilityChanged, this));
this._activeEditorLineDisposable = Disposable.from(...subscriptions); this._trackCurrentLineDisposable = Disposable.from(...subscriptions);
} }
else if (!trackCurrentLine && this._activeEditorLineDisposable) { else if (!trackCurrentLine && this._trackCurrentLineDisposable) {
this._activeEditorLineDisposable.dispose(); this._trackCurrentLineDisposable.dispose();
this._activeEditorLineDisposable = undefined; this._trackCurrentLineDisposable = undefined;
} }
this._onActiveTextEditorChanged(window.activeTextEditor); this.refresh(window.activeTextEditor);
} }
private isEditorBlameable(editor: TextEditor | undefined): boolean { private _onActiveTextEditorChanged(editor?: TextEditor) {
if (editor === undefined || editor.document === undefined) return false; this.refresh(editor);
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) { private _onBlameabilityChanged(e: BlameabilityChangeEvent) {
@@ -158,12 +140,30 @@ export class CurrentLineController extends Disposable {
this._updateBlameDebounced(this._editor.selection.active.line, this._editor); this._updateBlameDebounced(this._editor.selection.active.line, this._editor);
} }
private _onAnnotationsToggled() { private _onDebugSessionStarted() {
this._onActiveTextEditorChanged(window.activeTextEditor); const state = this._blameLineAnnotationState !== undefined ? this._blameLineAnnotationState : this._config.blame.line;
if (!state.enabled) return;
this._debugSessionEndDisposable = debug.onDidTerminateDebugSession(this._onDebugSessionEnded, this);
this.toggleAnnotations(window.activeTextEditor, state.annotationType, 'debugging');
}
private _onDebugSessionEnded() {
this._debugSessionEndDisposable && this._debugSessionEndDisposable.dispose();
this._debugSessionEndDisposable = undefined;
if (this._blameLineAnnotationState === undefined || this._blameLineAnnotationState.enabled || this._blameLineAnnotationState.reason !== 'debugging') return;
this.toggleAnnotations(window.activeTextEditor, this._blameLineAnnotationState.annotationType);
}
private _onFileAnnotationsToggled() {
this.refresh(window.activeTextEditor);
} }
private _onGitCacheChanged() { private _onGitCacheChanged() {
this._onActiveTextEditorChanged(window.activeTextEditor); Logger.log('Git cache changed; resetting current line annotations');
this.refresh(window.activeTextEditor);
} }
private async _onTextEditorSelectionChanged(e: TextEditorSelectionChangeEvent): Promise<void> { private async _onTextEditorSelectionChanged(e: TextEditorSelectionChangeEvent): Promise<void> {
@@ -183,6 +183,15 @@ export class CurrentLineController extends Disposable {
this._updateBlameDebounced(line, e.textEditor); this._updateBlameDebounced(line, e.textEditor);
} }
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 _updateBlame(line: number, editor: TextEditor) { private async _updateBlame(line: number, editor: TextEditor) {
line = line - this._uri.offset; line = line - this._uri.offset;
@@ -221,6 +230,30 @@ export class CurrentLineController extends Disposable {
editor.setDecorations(annotationDecoration, []); editor.setDecorations(annotationDecoration, []);
} }
async refresh(editor?: TextEditor) {
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);
}
async show(commit: GitCommit, blameLine: GitCommitLine, editor: TextEditor, line: number) { async show(commit: GitCommit, blameLine: GitCommitLine, editor: TextEditor, line: number) {
// I have no idea why I need this protection -- but it happens // I have no idea why I need this protection -- but it happens
if (editor.document === undefined) return; if (editor.document === undefined) return;
@@ -229,25 +262,23 @@ export class CurrentLineController extends Disposable {
await this._updateAnnotations(commit, blameLine, editor, line); await this._updateAnnotations(commit, blameLine, editor, line);
} }
async showAnnotations(editor: TextEditor, type: LineAnnotationType) { async showAnnotations(editor: TextEditor | undefined, type: LineAnnotationType, reason: 'user' | 'debugging' = 'user') {
if (editor === undefined) return; if (editor === undefined) return;
const cfg = this._config.blame.line; const state = this._blameLineAnnotationState !== undefined ? this._blameLineAnnotationState : this._config.blame.line;
if (!cfg.enabled || cfg.annotationType !== type) { if (!state.enabled || state.annotationType !== type) {
cfg.enabled = true; this._blameLineAnnotationState = { enabled: true, annotationType: type, reason: reason };
cfg.annotationType = type;
await this._clearAnnotations(editor); await this._clearAnnotations(editor);
await this._updateBlame(editor.selection.active.line, editor); await this._updateBlame(editor.selection.active.line, editor);
} }
} }
async toggleAnnotations(editor: TextEditor, type: LineAnnotationType) { async toggleAnnotations(editor: TextEditor | undefined, type: LineAnnotationType, reason: 'user' | 'debugging' = 'user') {
if (editor === undefined) return; if (editor === undefined) return;
const cfg = this._config.blame.line; const state = this._blameLineAnnotationState !== undefined ? this._blameLineAnnotationState : this._config.blame.line;
cfg.enabled = !cfg.enabled; this._blameLineAnnotationState = { enabled: !state.enabled, annotationType: type, reason: reason };
cfg.annotationType = type;
await this._clearAnnotations(editor); await this._clearAnnotations(editor);
await this._updateBlame(editor.selection.active.line, editor); await this._updateBlame(editor.selection.active.line, editor);
@@ -255,7 +286,9 @@ export class CurrentLineController extends Disposable {
private async _updateAnnotations(commit: GitCommit, blameLine: GitCommitLine, editor: TextEditor, line?: number) { private async _updateAnnotations(commit: GitCommit, blameLine: GitCommitLine, editor: TextEditor, line?: number) {
const cfg = this._config.blame.line; const cfg = this._config.blame.line;
if (!cfg.enabled) return;
const state = this._blameLineAnnotationState !== undefined ? this._blameLineAnnotationState : cfg;
if (!state.enabled) return;
line = line === undefined ? blameLine.line + this._uri.offset : line; line = line === undefined ? blameLine.line + this._uri.offset : line;
@@ -269,7 +302,7 @@ export class CurrentLineController extends Disposable {
let showDetailsStartIndex = 0; let showDetailsStartIndex = 0;
let showDetailsInStartingWhitespace = false; let showDetailsInStartingWhitespace = false;
switch (cfg.annotationType) { switch (state.annotationType) {
case LineAnnotationType.Trailing: { case LineAnnotationType.Trailing: {
const cfgAnnotations = this._config.annotations.line.trailing; const cfgAnnotations = this._config.annotations.line.trailing;

View File

@@ -1,14 +1,13 @@
'use strict'; 'use strict';
// import { Objects } from './system'; // import { Objects } from './system';
import { ExtensionContext, extensions, languages, window, workspace } from 'vscode'; import { commands, ExtensionContext, extensions, languages, window, workspace } from 'vscode';
import { AnnotationController } from './annotations/annotationController'; import { AnnotationController } from './annotations/annotationController';
import { CommandContext, setCommandContext } from './commands';
import { CloseUnchangedFilesCommand, OpenChangedFilesCommand } from './commands'; import { CloseUnchangedFilesCommand, OpenChangedFilesCommand } from './commands';
import { OpenBranchInRemoteCommand, OpenCommitInRemoteCommand, OpenFileInRemoteCommand, OpenInRemoteCommand, OpenRepoInRemoteCommand } from './commands'; import { OpenBranchInRemoteCommand, OpenCommitInRemoteCommand, OpenFileInRemoteCommand, OpenInRemoteCommand, OpenRepoInRemoteCommand } from './commands';
import { CopyMessageToClipboardCommand, CopyShaToClipboardCommand } from './commands'; import { CopyMessageToClipboardCommand, CopyShaToClipboardCommand } from './commands';
import { DiffDirectoryCommand, DiffLineWithPreviousCommand, DiffLineWithWorkingCommand, DiffWithBranchCommand, DiffWithNextCommand, DiffWithPreviousCommand, DiffWithWorkingCommand} from './commands'; import { DiffDirectoryCommand, DiffLineWithPreviousCommand, DiffLineWithWorkingCommand, DiffWithBranchCommand, DiffWithNextCommand, DiffWithPreviousCommand, DiffWithRevisionCommand, DiffWithWorkingCommand } from './commands';
import { ResetSuppressedWarningsCommand } from './commands'; import { ResetSuppressedWarningsCommand } from './commands';
import { ShowFileBlameCommand, ShowLineBlameCommand, ToggleFileBlameCommand, ToggleFileRecentChangesCommand, ToggleLineBlameCommand } from './commands'; import { ClearFileAnnotationsCommand, ShowFileBlameCommand, ShowLineBlameCommand, ToggleFileBlameCommand, ToggleFileRecentChangesCommand, ToggleLineBlameCommand } from './commands';
import { ShowBlameHistoryCommand, ShowFileHistoryCommand } from './commands'; import { ShowBlameHistoryCommand, ShowFileHistoryCommand } from './commands';
import { ShowLastQuickPickCommand } from './commands'; import { ShowLastQuickPickCommand } from './commands';
import { ShowQuickBranchHistoryCommand, ShowQuickCurrentBranchHistoryCommand, ShowQuickFileHistoryCommand } from './commands'; import { ShowQuickBranchHistoryCommand, ShowQuickCurrentBranchHistoryCommand, ShowQuickFileHistoryCommand } from './commands';
@@ -16,13 +15,17 @@ import { ShowCommitSearchCommand, ShowQuickCommitDetailsCommand, ShowQuickCommit
import { ShowQuickRepoStatusCommand, ShowQuickStashListCommand } from './commands'; import { ShowQuickRepoStatusCommand, ShowQuickStashListCommand } from './commands';
import { StashApplyCommand, StashDeleteCommand, StashSaveCommand } from './commands'; import { StashApplyCommand, StashDeleteCommand, StashSaveCommand } from './commands';
import { ToggleCodeLensCommand } from './commands'; import { ToggleCodeLensCommand } from './commands';
import { Keyboard } from './commands';
import { CodeLensLocations, IConfig, LineHighlightLocations } from './configuration'; import { CodeLensLocations, IConfig, LineHighlightLocations } from './configuration';
import { ApplicationInsightsKey, ExtensionKey, QualifiedExtensionId, WorkspaceState } from './constants'; import { ApplicationInsightsKey, CommandContext, ExtensionKey, QualifiedExtensionId, setCommandContext, WorkspaceState } from './constants';
import { CodeLensController } from './codeLensController';
import { CurrentLineController, LineAnnotationType } from './currentLineController'; import { CurrentLineController, LineAnnotationType } from './currentLineController';
import { GitContentProvider } from './gitContentProvider'; import { GitContentProvider } from './gitContentProvider';
// import { GitExplorer } from './views/gitExplorer';
import { FileHistoryExplorer } from './views/fileHistoryExplorer';
import { StashExplorer } from './views/stashExplorer';
import { GitRevisionCodeLensProvider } from './gitRevisionCodeLensProvider'; import { GitRevisionCodeLensProvider } from './gitRevisionCodeLensProvider';
import { GitContextTracker, GitService } from './gitService'; import { GitContextTracker, GitService } from './gitService';
import { Keyboard } from './keyboard';
import { Logger } from './logger'; import { Logger } from './logger';
import { Messages, SuppressedKeys } from './messages'; import { Messages, SuppressedKeys } from './messages';
import { Telemetry } from './telemetry'; import { Telemetry } from './telemetry';
@@ -70,7 +73,7 @@ export async function activate(context: ExtensionContext) {
await context.globalState.update(WorkspaceState.GitLensVersion, gitlensVersion); await context.globalState.update(WorkspaceState.GitLensVersion, gitlensVersion);
const git = new GitService(context, repoPath); const git = new GitService(repoPath);
context.subscriptions.push(git); context.subscriptions.push(git);
const gitContextTracker = new GitContextTracker(git); const gitContextTracker = new GitContextTracker(git);
@@ -83,11 +86,22 @@ export async function activate(context: ExtensionContext) {
const annotationController = new AnnotationController(context, git, gitContextTracker); const annotationController = new AnnotationController(context, git, gitContextTracker);
context.subscriptions.push(annotationController); context.subscriptions.push(annotationController);
const codeLensController = new CodeLensController(context, git);
context.subscriptions.push(codeLensController);
const currentLineController = new CurrentLineController(context, git, gitContextTracker, annotationController); const currentLineController = new CurrentLineController(context, git, gitContextTracker, annotationController);
context.subscriptions.push(currentLineController); context.subscriptions.push(currentLineController);
context.subscriptions.push(new Keyboard()); context.subscriptions.push(new Keyboard());
// const explorer = new GitExplorer(context, git);
// context.subscriptions.push(window.registerTreeDataProvider('gitlens.gitExplorer', explorer));
context.subscriptions.push(window.registerTreeDataProvider('gitlens.fileHistoryExplorer', new FileHistoryExplorer(context, git)));
context.subscriptions.push(window.registerTreeDataProvider('gitlens.stashExplorer', new StashExplorer(context, git)));
context.subscriptions.push(commands.registerTextEditorCommand('gitlens.computingFileAnnotations', () => { }));
context.subscriptions.push(new CloseUnchangedFilesCommand(git)); context.subscriptions.push(new CloseUnchangedFilesCommand(git));
context.subscriptions.push(new OpenChangedFilesCommand(git)); context.subscriptions.push(new OpenChangedFilesCommand(git));
context.subscriptions.push(new CopyMessageToClipboardCommand(git)); context.subscriptions.push(new CopyMessageToClipboardCommand(git));
@@ -98,12 +112,14 @@ export async function activate(context: ExtensionContext) {
context.subscriptions.push(new DiffWithBranchCommand(git)); context.subscriptions.push(new DiffWithBranchCommand(git));
context.subscriptions.push(new DiffWithNextCommand(git)); context.subscriptions.push(new DiffWithNextCommand(git));
context.subscriptions.push(new DiffWithPreviousCommand(git)); context.subscriptions.push(new DiffWithPreviousCommand(git));
context.subscriptions.push(new DiffWithRevisionCommand(git));
context.subscriptions.push(new DiffWithWorkingCommand(git)); context.subscriptions.push(new DiffWithWorkingCommand(git));
context.subscriptions.push(new OpenBranchInRemoteCommand(git)); context.subscriptions.push(new OpenBranchInRemoteCommand(git));
context.subscriptions.push(new OpenCommitInRemoteCommand(git)); context.subscriptions.push(new OpenCommitInRemoteCommand(git));
context.subscriptions.push(new OpenFileInRemoteCommand(git)); context.subscriptions.push(new OpenFileInRemoteCommand(git));
context.subscriptions.push(new OpenInRemoteCommand()); context.subscriptions.push(new OpenInRemoteCommand());
context.subscriptions.push(new OpenRepoInRemoteCommand(git)); context.subscriptions.push(new OpenRepoInRemoteCommand(git));
context.subscriptions.push(new ClearFileAnnotationsCommand(annotationController));
context.subscriptions.push(new ShowFileBlameCommand(annotationController)); context.subscriptions.push(new ShowFileBlameCommand(annotationController));
context.subscriptions.push(new ShowLineBlameCommand(currentLineController)); context.subscriptions.push(new ShowLineBlameCommand(currentLineController));
context.subscriptions.push(new ToggleFileBlameCommand(annotationController)); context.subscriptions.push(new ToggleFileBlameCommand(annotationController));
@@ -124,7 +140,7 @@ export async function activate(context: ExtensionContext) {
context.subscriptions.push(new StashApplyCommand(git)); context.subscriptions.push(new StashApplyCommand(git));
context.subscriptions.push(new StashDeleteCommand(git)); context.subscriptions.push(new StashDeleteCommand(git));
context.subscriptions.push(new StashSaveCommand(git)); context.subscriptions.push(new StashSaveCommand(git));
context.subscriptions.push(new ToggleCodeLensCommand(git)); context.subscriptions.push(new ToggleCodeLensCommand(codeLensController));
// Constantly over my data cap so stop collecting initialized event // Constantly over my data cap so stop collecting initialized event
// Telemetry.trackEvent('initialized', Objects.flatten(cfg, 'config', true)); // Telemetry.trackEvent('initialized', Objects.flatten(cfg, 'config', true));

View File

@@ -1,14 +1,10 @@
'use strict'; 'use strict';
import { Strings } from '../../system'; import { Strings } from '../../system';
import { GitCommit } from '../models/commit'; import { GitCommit } from '../models/commit';
import { GitDiffLine } from '../models/diff'; import { Formatter, IFormatOptions } from './formatter';
import * as moment from 'moment'; import * as moment from 'moment';
const escapeMarkdownRegEx = /[`\>\#\*\_\-\+\.]/g; export interface ICommitFormatOptions extends IFormatOptions {
// const sampleMarkdown = '## message `not code` *not important* _no underline_ \n> don\'t quote me \n- don\'t list me \n+ don\'t list me \n1. don\'t list me \nnot h1 \n=== \nnot h2 \n---\n***\n---\n___';
export interface ICommitFormatOptions {
dateFormat?: string | null;
tokenOptions?: { tokenOptions?: {
ago?: Strings.ITokenOptions; ago?: Strings.ITokenOptions;
author?: Strings.ITokenOptions; author?: Strings.ITokenOptions;
@@ -18,58 +14,34 @@ export interface ICommitFormatOptions {
}; };
} }
export class CommitFormatter { export class CommitFormatter extends Formatter<GitCommit, ICommitFormatOptions> {
private _commit: GitCommit;
private _options: ICommitFormatOptions;
constructor(commit: GitCommit, options?: ICommitFormatOptions) {
this.reset(commit, options);
}
reset(commit: GitCommit, options?: ICommitFormatOptions) {
this._commit = commit;
if (options === undefined && this._options !== undefined) return;
options = options || {};
if (options.tokenOptions == null) {
options.tokenOptions = {};
}
if (options.dateFormat == null) {
options.dateFormat = 'MMMM Do, YYYY h:MMa';
}
this._options = options;
}
get ago() { get ago() {
const ago = moment(this._commit.date).fromNow(); const ago = moment(this._item.date).fromNow();
return this._padOrTruncate(ago, this._options.tokenOptions!.ago); return this._padOrTruncate(ago, this._options.tokenOptions!.ago);
} }
get author() { get author() {
const author = this._commit.author; const author = this._item.author;
return this._padOrTruncate(author, this._options.tokenOptions!.author); return this._padOrTruncate(author, this._options.tokenOptions!.author);
} }
get authorAgo() { get authorAgo() {
const authorAgo = `${this._commit.author}, ${moment(this._commit.date).fromNow()}`; const authorAgo = `${this._item.author}, ${moment(this._item.date).fromNow()}`;
return this._padOrTruncate(authorAgo, this._options.tokenOptions!.authorAgo); return this._padOrTruncate(authorAgo, this._options.tokenOptions!.authorAgo);
} }
get date() { get date() {
const date = moment(this._commit.date).format(this._options.dateFormat!); const date = moment(this._item.date).format(this._options.dateFormat!);
return this._padOrTruncate(date, this._options.tokenOptions!.date); return this._padOrTruncate(date, this._options.tokenOptions!.date);
} }
get id() { get id() {
return this._commit.shortSha; return this._item.shortSha;
} }
get message() { get message() {
const message = this._commit.isUncommitted ? 'Uncommitted change' : this._commit.message; const message = this._item.isUncommitted ? 'Uncommitted change' : this._item.message;
return this._padOrTruncate(message, this._options.tokenOptions!.message); return this._padOrTruncate(message, this._options.tokenOptions!.message);
} }
@@ -77,122 +49,10 @@ export class CommitFormatter {
return this.id; 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;
}
private static _formatter: CommitFormatter | undefined = undefined;
static fromCommit(commit: GitCommit, options?: ICommitFormatOptions): CommitFormatter {
if (CommitFormatter._formatter === undefined) {
CommitFormatter._formatter = new CommitFormatter(commit, options);
}
else {
CommitFormatter._formatter.reset(commit, options);
}
return CommitFormatter._formatter;
}
static fromTemplate(template: string, commit: GitCommit, dateFormat: string | null): string; 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, 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;
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; return super.fromTemplateCore(this, template, commit, dateFormatOrOptions);
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 | null): string | string[] {
if (dateFormat === null) {
dateFormat = 'MMMM Do, YYYY h:MMa';
}
let message = '';
if (!commit.isUncommitted) {
message = commit.message
// Escape markdown
.replace(escapeMarkdownRegEx, '\\$&')
// Escape markdown header (since the above regex won't match it)
.replace(/^===/gm, '\u200b===')
// Keep under the same block-quote
.replace(/\n/g, ' \n');
message = `\n\n> ${message}`;
}
return `\`${commit.shortSha}\` &nbsp; __${commit.author}__, ${moment(commit.date).fromNow()} &nbsp; _(${moment(commit.date).format(dateFormat)})_${message}`;
}
static toHoverDiff(commit: GitCommit, previous: GitDiffLine | undefined, current: GitDiffLine | 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: GitDiffLine | undefined, current: GitDiffLine | undefined): string {
return `\`\`\`
- ${previous === undefined ? '' : previous.line.trim()}
+ ${current === undefined ? '' : current.line.trim()}
\`\`\``;
} }
} }

View File

@@ -0,0 +1,120 @@
'use strict';
import { Strings } from '../../system';
export interface IFormatOptions {
dateFormat?: string | null;
tokenOptions?: { [id: string]: Strings.ITokenOptions | undefined };
}
type Constructor<T = {}> = new (...args: any[]) => T;
export abstract class Formatter<TItem = any, TOptions extends IFormatOptions = IFormatOptions> {
protected _item: TItem;
protected _options: TOptions;
constructor(item: TItem, options?: TOptions) {
this.reset(item, options);
}
reset(item: TItem, options?: TOptions) {
this._item = item;
if (options === undefined && this._options !== undefined) return;
if (options === undefined) {
options = {} as TOptions;
}
if (options.dateFormat == null) {
options.dateFormat = 'MMMM Do, YYYY h:MMa';
}
if (options.tokenOptions == null) {
options.tokenOptions = {};
}
this._options = options;
}
private collapsableWhitespace: number = 0;
protected _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;
const width = Strings.getWidth(s);
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 - width;
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 - width;
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;
}
private static _formatter: Formatter | undefined = undefined;
protected static fromTemplateCore<TFormatter extends Formatter<TItem, TOptions>, TItem, TOptions extends IFormatOptions>(formatter: TFormatter | Constructor<TFormatter>, template: string, item: TItem, dateFormatOrOptions?: string | null | TOptions): string {
if (formatter instanceof Formatter) return Strings.interpolate(template, formatter);
let options: TOptions | 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]: Strings.ITokenOptions | undefined });
options = {
dateFormat: dateFormatOrOptions,
tokenOptions: tokenOptions
} as TOptions;
}
else {
options = dateFormatOrOptions;
}
if (this._formatter === undefined) {
this._formatter = new formatter(item, options);
}
else {
this._formatter.reset(item, options);
}
return Strings.interpolate(template, this._formatter);
}
}

View File

@@ -0,0 +1,38 @@
'use strict';
import { Strings } from '../../system';
import { Formatter, IFormatOptions } from './formatter';
import { GitStatusFile, IGitStatusFile } from '../models/status';
import * as path from 'path';
export interface IStatusFormatOptions extends IFormatOptions {
tokenOptions?: {
file?: Strings.ITokenOptions;
filePath?: Strings.ITokenOptions;
path?: Strings.ITokenOptions;
};
}
export class StatusFileFormatter extends Formatter<IGitStatusFile, IStatusFormatOptions> {
get file() {
const file = path.basename(this._item.fileName);
return this._padOrTruncate(file, this._options.tokenOptions!.file);
}
get filePath() {
const filePath = GitStatusFile.getFormattedPath(this._item);
return this._padOrTruncate(filePath, this._options.tokenOptions!.filePath);
}
get path() {
const directory = GitStatusFile.getFormattedDirectory(this._item, false);
return this._padOrTruncate(directory, this._options.tokenOptions!.file);
}
static fromTemplate(template: string, status: IGitStatusFile, dateFormat: string | null): string;
static fromTemplate(template: string, status: IGitStatusFile, options?: IStatusFormatOptions): string;
static fromTemplate(template: string, status: IGitStatusFile, dateFormatOrOptions?: string | null | IStatusFormatOptions): string;
static fromTemplate(template: string, status: IGitStatusFile, dateFormatOrOptions?: string | null | IStatusFormatOptions): string {
return super.fromTemplateCore(this, template, status, dateFormatOrOptions);
}
}

View File

@@ -185,7 +185,7 @@ export class Git {
} }
static diff(repoPath: string, fileName: string, sha1?: string, sha2?: string, encoding?: string) { static diff(repoPath: string, fileName: string, sha1?: string, sha2?: string, encoding?: string) {
const params = [`diff`, `--diff-filter=M`, `-M`]; const params = [`diff`, `--diff-filter=M`, `-M`, `--no-ext-diff`];
if (sha1) { if (sha1) {
params.push(sha1); params.push(sha1);
} }
@@ -197,7 +197,7 @@ export class Git {
} }
static diff_nameStatus(repoPath: string, sha1?: string, sha2?: string) { static diff_nameStatus(repoPath: string, sha1?: string, sha2?: string) {
const params = [`diff`, `--name-status`, `-M`]; const params = [`diff`, `--name-status`, `-M`, `--no-ext-diff`];
if (sha1) { if (sha1) {
params.push(sha1); params.push(sha1);
} }
@@ -235,16 +235,16 @@ export class Git {
return gitCommand({ cwd: repoPath }, ...params); return gitCommand({ cwd: repoPath }, ...params);
} }
static log_file(repoPath: string, fileName: string, sha?: string, maxCount?: number, reverse: boolean = false, startLine?: number, endLine?: number) { static log_file(repoPath: string, fileName: string, sha?: string, options: { maxCount?: number, reverse?: boolean, startLine?: number, endLine?: number, skipMerges?: boolean } = { reverse: false, skipMerges: false }) {
const [file, root] = Git.splitPath(fileName, repoPath); const [file, root] = Git.splitPath(fileName, repoPath);
const params = [...defaultLogParams, `--follow`]; const params = [...defaultLogParams, `--follow`];
if (maxCount && !reverse) { if (options.maxCount && !options.reverse) {
params.push(`-n${maxCount}`); params.push(`-n${options.maxCount}`);
} }
// If we are looking for a specific sha don't exclude merge commits // If we are looking for a specific sha don't exclude merge commits
if (!sha || maxCount! > 2) { if (options.skipMerges || !sha || options.maxCount! > 2) {
params.push(`--no-merges`); params.push(`--no-merges`);
} }
else { else {
@@ -252,7 +252,7 @@ export class Git {
} }
if (sha) { if (sha) {
if (reverse) { if (options.reverse) {
params.push(`--reverse`); params.push(`--reverse`);
params.push(`--ancestry-path`); params.push(`--ancestry-path`);
params.push(`${sha}..HEAD`); params.push(`${sha}..HEAD`);
@@ -262,8 +262,8 @@ export class Git {
} }
} }
if (startLine != null && endLine != null) { if (options.startLine != null && options.endLine != null) {
params.push(`-L ${startLine},${endLine}:${file}`); params.push(`-L ${options.startLine},${options.endLine}:${file}`);
} }
params.push(`--`); params.push(`--`);

View File

@@ -1,7 +1,7 @@
'use strict'; 'use strict';
import { Disposable, Event, EventEmitter, TextDocument, TextDocumentChangeEvent, TextEditor, window, workspace } from 'vscode'; import { Disposable, Event, EventEmitter, TextDocument, TextDocumentChangeEvent, TextEditor, window, workspace } from 'vscode';
import { CommandContext, setCommandContext } from '../commands';
import { TextDocumentComparer } from '../comparers'; import { TextDocumentComparer } from '../comparers';
import { CommandContext, setCommandContext } from '../constants';
import { GitService, GitUri } from '../gitService'; import { GitService, GitUri } from '../gitService';
import { Logger } from '../logger'; import { Logger } from '../logger';
@@ -12,9 +12,9 @@ export interface BlameabilityChangeEvent {
export class GitContextTracker extends Disposable { export class GitContextTracker extends Disposable {
private _onDidBlameabilityChange = new EventEmitter<BlameabilityChangeEvent>(); private _onDidChangeBlameability = new EventEmitter<BlameabilityChangeEvent>();
get onDidBlameabilityChange(): Event<BlameabilityChangeEvent> { get onDidChangeBlameability(): Event<BlameabilityChangeEvent> {
return this._onDidBlameabilityChange.event; return this._onDidChangeBlameability.event;
} }
private _disposable: Disposable; private _disposable: Disposable;
@@ -154,7 +154,7 @@ export class GitContextTracker extends Disposable {
try { try {
setCommandContext(CommandContext.IsBlameable, blameable); setCommandContext(CommandContext.IsBlameable, blameable);
this._onDidBlameabilityChange.fire({ this._onDidChangeBlameability.fire({
blameable: blameable, blameable: blameable,
editor: this._editor editor: this._editor
}); });

View File

@@ -55,9 +55,9 @@ function findSystemGitWin32(basePath: string): Promise<IGit> {
} }
function findGitWin32(): Promise<IGit> { function findGitWin32(): Promise<IGit> {
return findSystemGitWin32(process.env['ProgramW6432']) return findSystemGitWin32(process.env['ProgramW6432']!)
.then(null, () => findSystemGitWin32(process.env['ProgramFiles(x86)'])) .then(null, () => findSystemGitWin32(process.env['ProgramFiles(x86)']!))
.then(null, () => findSystemGitWin32(process.env['ProgramFiles'])) .then(null, () => findSystemGitWin32(process.env['ProgramFiles']!))
.then(null, () => findSpecificGit('git')); .then(null, () => findSpecificGit('git'));
} }

View File

@@ -1,6 +1,7 @@
'use strict'; 'use strict';
import { Strings } from '../system';
import { Uri } from 'vscode'; import { Uri } from 'vscode';
import { DocumentSchemes } from '../constants'; import { DocumentSchemes, GlyphChars } from '../constants';
import { GitService, IGitStatusFile } from '../gitService'; import { GitService, IGitStatusFile } from '../gitService';
import * as path from 'path'; import * as path from 'path';
@@ -18,11 +19,11 @@ export class GitUri extends Uri {
if (!uri) return; if (!uri) return;
const base = this as any; const base = this as any;
base._scheme = uri.scheme; for (const key in uri) {
base._authority = uri.authority; if (uri.hasOwnProperty(key)) {
base._path = uri.path; base[key] = (uri as any)[key];
base._query = uri.query; }
base._fragment = uri.fragment; }
this.offset = 0; this.offset = 0;
if (uri.scheme === DocumentSchemes.GitLensGit) { if (uri.scheme === DocumentSchemes.GitLensGit) {
@@ -62,7 +63,7 @@ export class GitUri extends Uri {
return Uri.file(this.sha ? this.path : this.fsPath); return Uri.file(this.sha ? this.path : this.fsPath);
} }
getFormattedPath(separator: string = ' \u00a0\u2022\u00a0 '): string { getFormattedPath(separator: string = Strings.pad(GlyphChars.Dot, 2, 2)): string {
let directory = path.dirname(this.fsPath); let directory = path.dirname(this.fsPath);
if (this.repoPath) { if (this.repoPath) {
directory = path.relative(this.repoPath, directory); directory = path.relative(this.repoPath, directory);
@@ -108,7 +109,7 @@ export class GitUri extends Uri {
return (!directory || directory === '.') ? '' : directory; return (!directory || directory === '.') ? '' : directory;
} }
static getFormattedPath(fileNameOrUri: string | Uri, separator: string = ' \u00a0\u2022\u00a0 '): string { static getFormattedPath(fileNameOrUri: string | Uri, separator: string = Strings.pad(GlyphChars.Dot, 2, 2)): string {
let fileName: string; let fileName: string;
if (fileNameOrUri instanceof Uri) { if (fileNameOrUri instanceof Uri) {
if (fileNameOrUri instanceof GitUri) return fileNameOrUri.getFormattedPath(separator); if (fileNameOrUri instanceof GitUri) return fileNameOrUri.getFormattedPath(separator);
@@ -140,4 +141,4 @@ export interface IGitUriData {
originalFileName?: string; originalFileName?: string;
index?: number; index?: number;
decoration?: string; decoration?: string;
} }

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