12 Commits

Author SHA1 Message Date
Eric Amodio
c5f57172e4 Fixes issue where work tree files could no show up 2017-09-23 23:36:43 -04:00
Eric Amodio
dd0a636e24 Preps v5.2.0 2017-09-23 23:29:49 -04:00
Eric Amodio
393ec351f0 Adds gitlens.gitExplorer.includeWorkingTree setting
Adds auto-update for working trree
Fixes issues with working tree status
2017-09-23 16:29:56 -04:00
Eric Amodio
99d6da9c90 Fixes issue with whitespace collapsing in decorations 2017-09-22 01:01:43 -04:00
Eric Amodio
0eb202b8ae Closes #150 - adds auto view to custom view 2017-09-22 00:48:57 -04:00
Eric Amodio
65736a6ce7 Adds watching of creates/deletes inside .git 2017-09-21 18:43:46 -04:00
Eric Amodio
ed42eba8b8 Fixes issue with untracked files being counted properly 2017-09-21 18:43:12 -04:00
Eric Amodio
2245d82319 Preps v5.2.0-beta 2017-09-20 01:41:32 -04:00
Eric Amodio
f7df845dfe Adds working tree status to custom view (insiders)
Hides working changed files behind insiders flag
Unhides Changed Files node from behind insiders flag
Adds changed file count to Changed Files node label
Adds icon to Changed Files node
Adds upstream branch to upstream status nodes
Sorts files in the Changed Files node
2017-09-20 01:37:18 -04:00
Eric Amodio
712544fab8 Adds git diff --shortstat support 2017-09-20 01:10:47 -04:00
Eric Amodio
a114e2de87 Changes the file sort in the custom view 2017-09-20 01:09:19 -04:00
Eric Amodio
70071448d6 Closes #146 - Attempts to deal with emoji in gutter
Adds ability to automagically set the width of the gutter annotations
2017-09-20 01:01:21 -04:00
27 changed files with 452 additions and 142 deletions

View File

@@ -6,17 +6,25 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
## [Unreleased] ## [Unreleased]
## [5.1.1-beta] ## [5.2.0] - 2017-09-23
### Added ### Added
- Adds new `Changed Files` node to the `Repository Status` node of the `GitLens` custom view's `Repository View` (enabled via `"gitlens.insiders": true`) -- closes [#139](https://github.com/eamodio/vscode-gitlens/issues/139) - Adds new `Changed Files` node to the `Repository Status` node of the `GitLens` custom view's `Repository View` -- closes [#139](https://github.com/eamodio/vscode-gitlens/issues/139)
- Provides a file-based view of all the changed files in the working tree and/or files in commits that haven't yet been pushed upstream - Provides a at-a-glance view of all "working" changes
- Expands to a file-based view of all changed files in the working tree (enabled via `"gitlens.insiders": true`) and/or all files in all commits ahead of the upstream
- Adds optional (on by default) working tree status information to the `Repository Status` node in the `GitLens` custom view
- Adds `auto` value to `gitlens.gitExplorer.view` setting - closes [#150](https://github.com/eamodio/vscode-gitlens/issues/150)
- Adds `gitlens.gitExplorer.enabled` setting to specify whether or not to show the `GitLens` custom view - closes [#144](https://github.com/eamodio/vscode-gitlens/issues/144) - Adds `gitlens.gitExplorer.enabled` setting to specify whether or not to show the `GitLens` custom view - closes [#144](https://github.com/eamodio/vscode-gitlens/issues/144)
- Adds `gitlens.gitExplorer.includeWorkingTree` setting to specify whether or not to include working tree files inside the `Repository Status` node of the `GitLens` custom view
- Adds `gitlens.gitExplorer.statusFileFormat` setting to the format of the status of a working or committed file in the `GitLens` custom view
### Changed ### Changed
- Chnages the default of the `gitlens.gitExplorer.commitFormat` setting to add parentheses around the commit id - Changes the sorting (now alphabetical) of files shown in the `GitLens` custom view
- Changes the default of the `gitlens.gitExplorer.view` setting to `auto`
- Changes the default of the `gitlens.gitExplorer.commitFormat` setting to add parentheses around the commit id
- Removes many menu items from `editor/title` & `editor/title/context` by default -- can be re-enabled via the `gitlens.advanced.menus` setting - Removes many menu items from `editor/title` & `editor/title/context` by default -- can be re-enabled via the `gitlens.advanced.menus` setting
### Fixed ### Fixed
- Fixes [#146](https://github.com/eamodio/vscode-gitlens/issues/146) - Blame gutter annotation issue when commit contains emoji
- Fixes an issue when running `Open File in Remote` with a multi-line selection wasn't properly opening the selection in GitLab -- thanks to [PR #145](https://github.com/eamodio/vscode-gitlens/pull/145) by Amanda Cameron ([@AmandaCameron](https://github.com/AmandaCameron))! - Fixes an issue when running `Open File in Remote` with a multi-line selection wasn't properly opening the selection in GitLab -- thanks to [PR #145](https://github.com/eamodio/vscode-gitlens/pull/145) by Amanda Cameron ([@AmandaCameron](https://github.com/AmandaCameron))!
- Fixes an issue where the `gitlens.advanced.menus` setting wasn't controlling all the menu items properly - Fixes an issue where the `gitlens.advanced.menus` setting wasn't controlling all the menu items properly

View File

@@ -126,13 +126,17 @@ GitLens provides an unobtrusive blame annotation at the end of the current line,
![GitLens Repository view](https://raw.githubusercontent.com/eamodio/vscode-gitlens/master/images/screenshot-git-custom-view-repository.png) ![GitLens Repository view](https://raw.githubusercontent.com/eamodio/vscode-gitlens/master/images/screenshot-git-custom-view-repository.png)
- `Repository Status` node — provides the status of the repository - `Repository Status` node — provides the status of the repository
- Provides the name of the current branch, its upstream tracking branch (if available), and its upstream status (if available) - Provides the name of the current branch, [optionally](#gitlens-custom-view-settings) its working tree status, and its upstream tracking branch and status (if available)
- Provides indicator dots on the repository icon which denote the following: - Provides indicator dots on the repository icon which denote the following:
- `None` - up-to-date with the upstream - `None` - up-to-date with the upstream
- `Green` - ahead of the upstream - `Green` - ahead of the upstream
- `Red` - behind the upstream - `Red` - behind the upstream
- `Yellow` - both ahead of and behind the upstream - `Yellow` - both ahead of and behind the upstream
- Provides additional nodes, if the current branch is not synchronized with the upstream, to quickly see and explore the specific commits ahead and/or behind the upstream - Provides additional upstream status nodes, if the current branch is tracking a remote branch and
- is behind the upstream — quickly see and explore the specific commits behind the upstream (i.e. commits that haven't been pulled)
- is ahead of the upstream — quickly see and explore the specific commits ahead of the upstream (i.e. commits that haven't been pushed)
- `Changed Files` node — provides a at-a-glance view of all "working" changes
- Expands to a file-based view of all changed files in the working tree ([optionally](#gitlens-custom-view-settings)) and/or all files in all commits ahead of the upstream
- Provides a context menu with `Open Repository in Remote`, and `Refresh` commands - Provides a context menu with `Open Repository in Remote`, and `Refresh` commands
- `Branches` node — provides a list of the local branches - `Branches` node — provides a list of the local branches
@@ -342,7 +346,7 @@ GitLens is highly customizable and provides many configuration settings to allow
|`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)
|`gitlens.codeLens.authors.command`|Specifies the command to be executed when the `authors` code lens is clicked<br />`gitlens.toggleFileBlame` - toggles file blame annotations<br />`gitlens.showBlameHistory` - opens the blame history explorer<br />`gitlens.showFileHistory` - opens the file history explorer<br />`gitlens.diffWithPrevious` - compares the current committed file with the previous commit<br />`gitlens.showQuickCommitDetails` - shows a commit details quick pick<br />`gitlens.showQuickCommitFileDetails` - shows a commit file details quick pick<br />`gitlens.showQuickFileHistory` - shows a file history quick pick<br />`gitlens.showQuickRepoHistory` - shows a branch history quick pick |`gitlens.codeLens.authors.command`|Specifies the command to be executed when the `authors` code lens is clicked<br />`gitlens.toggleFileBlame` - toggles file blame annotations<br />`gitlens.showBlameHistory` - opens the blame history explorer<br />`gitlens.showFileHistory` - opens the file history explorer<br />`gitlens.diffWithPrevious` - compares the current committed file with the previous commit<br />`gitlens.showQuickCommitDetails` - shows a commit details quick pick<br />`gitlens.showQuickCommitFileDetails` - shows a commit file details quick pick<br />`gitlens.showQuickFileHistory` - shows a file history quick pick<br />`gitlens.showQuickRepoHistory` - shows a branch history quick pick
|`gitlens.codeLens.locations`|Specifies where Git code lens will be shown in the document<br />`document` - adds code lens at the top of the document<br />`containers` - adds code lens at the start of container-like symbols (modules, classes, interfaces, etc)<br />`blocks` - adds code lens at the start of block-like symbols (functions, methods, properties, etc) lines<br />`custom` - adds code lens at the start of symbols contained in `gitlens.codeLens.locationCustomSymbols` |`gitlens.codeLens.locations`|Specifies where Git code lens will be shown in the document<br />`document` - adds code lens at the top of the document<br />`containers` - adds code lens at the start of container-like symbols (modules, classes, interfaces, etc)<br />`blocks` - adds code lens at the start of block-like symbols (functions, methods, properties, etc) lines<br />`custom` - adds code lens at the start of symbols contained in `gitlens.codeLens.locationCustomSymbols`
|`gitlens.codeLens.customLocationSymbols`|Specifies the set of document symbols where Git code lens will be shown in the document |`gitlens.codeLens.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
@@ -351,12 +355,14 @@ GitLens is highly customizable and provides many configuration settings to allow
|Name | Description |Name | Description
|-----|------------ |-----|------------
|`gitlens.gitExplorer.enabled`|Specifies whether or not to show the `GitLens` custom view" |`gitlens.gitExplorer.enabled`|Specifies whether or not to show the `GitLens` custom view"
|`gitlens.gitExplorer.view`|Specifies the starting view (mode) of the `GitLens` custom view<br />`history` - shows the commit history of the active file<br />`repository` - shows a repository explorer" |`gitlens.gitExplorer.view`|Specifies the starting view (mode) of the `GitLens` custom view<br /> `auto` - shows the last selected view, defaults to `repository`<br />`history` - shows the commit history of the active file<br />`repository` - shows a repository explorer"
|`gitlens.gitExplorer.includeWorkingTree`|Specifies whether or not to include working tree files inside the `Repository Status` node of the `GitLens` custom view
|`gitlens.gitExplorer.showTrackingBranch`|Specifies whether or not to show the tracking branch when displaying local branches in the `GitLens` custom view" |`gitlens.gitExplorer.showTrackingBranch`|Specifies whether or not to show the tracking branch when displaying local branches in the `GitLens` custom view"
|`gitlens.gitExplorer.commitFormat`|Specifies the format of committed changes in the `GitLens` custom view<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.gitExplorer.commitFormat`|Specifies the format of committed changes in the `GitLens` custom view<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.gitExplorer.commitFileFormat`|Specifies the format of a committed file in the `GitLens` custom view<br />Available tokens<br /> ${file} - file name<br /> ${filePath} - file name and path<br /> ${path} - file path |`gitlens.gitExplorer.commitFileFormat`|Specifies the format of a committed file in the `GitLens` custom view<br />Available tokens<br /> ${file} - file name<br /> ${filePath} - file name and path<br /> ${path} - file path
|`gitlens.gitExplorer.stashFormat`|Specifies the format of stashed changes in the `GitLens` custom view<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.gitExplorer.stashFormat`|Specifies the format of stashed changes in the `GitLens` custom view<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.gitExplorer.stashFileFormat`|Specifies the format of a stashed file in the `GitLens` custom view<br />Available tokens<br /> ${file} - file name<br /> ${filePath} - file name and path<br /> ${path} - file path |`gitlens.gitExplorer.stashFileFormat`|Specifies the format of a stashed file in the `GitLens` custom view<br />Available tokens<br /> ${file} - file name<br /> ${filePath} - file name and path<br /> ${path} - file path
|`gitlens.gitExplorer.statusFileFormat`|Specifies the format of the status of a working or committed file in the `GitLens` custom view<br />Available tokens<br /> ${file} - file name<br /> ${filePath} - file name and path<br /> ${path} - file path<br />${working} - optional indicator if the file is uncommitted
### Custom Remotes Settings ### Custom Remotes Settings

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<svg width="16" height="22" xmlns="http://www.w3.org/2000/svg">
<path fill="#C5C5C5" d="m7.5,10l2,0l0,1l-2,0l0,2l-1,0l0,-2l-2,0l0,-1l2,0l0,-2l1,0l0,2l0,0zm-3,6l5,0l0,-1l-5,0l0,1l0,0zm4.5,-11l3.5,3.5l0,9.5c0,0.55 -0.45,1 -1,1l-9,0c-0.55,0 -1,-0.45 -1,-1l0,-12c0,-0.55 0.45,-1 1,-1l6.5,0l0,0zm2.5,4l-3,-3l-6,0l0,12l9,0l0,-9l0,0zm-1.5,-6l-5.5,0l0,1l5,0l4,4l0,8l1,0l0,-8.5l-4.5,-4.5l0,0z" />
</svg>

After

Width:  |  Height:  |  Size: 437 B

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<svg width="16" height="22" xmlns="http://www.w3.org/2000/svg">
<path fill="#424242" d="m7.5,10l2,0l0,1l-2,0l0,2l-1,0l0,-2l-2,0l0,-1l2,0l0,-2l1,0l0,2l0,0zm-3,6l5,0l0,-1l-5,0l0,1l0,0zm4.5,-11l3.5,3.5l0,9.5c0,0.55 -0.45,1 -1,1l-9,0c-0.55,0 -1,-0.45 -1,-1l0,-12c0,-0.55 0.45,-1 1,-1l6.5,0l0,0zm2.5,4l-3,-3l-6,0l0,12l9,0l0,-9l0,0zm-1.5,-6l-5.5,0l0,1l5,0l4,4l0,8l1,0l0,-8.5l-4.5,-4.5l0,0z" />
</svg>

After

Width:  |  Height:  |  Size: 437 B

29
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{ {
"name": "gitlens", "name": "gitlens",
"version": "5.1.1-beta", "version": "5.2.0",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {
@@ -47,11 +47,6 @@
"json-stable-stringify": "1.0.1" "json-stable-stringify": "1.0.1"
} }
}, },
"ansi-regex": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
"integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg="
},
"ansi-styles": { "ansi-styles": {
"version": "2.2.1", "version": "2.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
@@ -1336,11 +1331,6 @@
"integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
"dev": true "dev": true
}, },
"is-fullwidth-code-point": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
"integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8="
},
"is-glob": { "is-glob": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz",
@@ -2334,15 +2324,6 @@
"integrity": "sha1-l+mNj6TRBdYqJpHR3AfoINuN/E8=", "integrity": "sha1-l+mNj6TRBdYqJpHR3AfoINuN/E8=",
"dev": true "dev": true
}, },
"string-width": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
"integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
"requires": {
"is-fullwidth-code-point": "2.0.0",
"strip-ansi": "4.0.0"
}
},
"string_decoder": { "string_decoder": {
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
@@ -2358,14 +2339,6 @@
"integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=", "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=",
"dev": true "dev": true
}, },
"strip-ansi": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
"integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
"requires": {
"ansi-regex": "3.0.0"
}
},
"strip-bom": { "strip-bom": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz",

View File

@@ -1,6 +1,6 @@
{ {
"name": "gitlens", "name": "gitlens",
"version": "5.1.1-beta", "version": "5.2.0",
"author": { "author": {
"name": "Eric Amodio", "name": "Eric Amodio",
"email": "eamodio@gmail.com" "email": "eamodio@gmail.com"
@@ -303,7 +303,7 @@
"minItems": 1, "minItems": 1,
"maxItems": 4, "maxItems": 4,
"uniqueItems": true, "uniqueItems": true,
"description": "Specifies where Git code lens will be shown in the document\n `document` - adds code lens at the top of the document\n `containers` - adds code lens at the start of container-like symbols (modules, classes, interfaces, etc)\n `blocks` - adds code lens at the start of block-like symbols (functions, methods, properties, etc) lines\n `custom` - adds code lens at the start of symbols contained in `gitlens.codeLens.locationCustomSymbols`" "description": "Specifies where Git code lens will be shown in the document\n `document` - adds code lens at the top of the document\n `containers` - adds code lens at the start of container-like symbols (modules, classes, interfaces, etc)\n `blocks` - adds code lens at the start of block-like symbols (functions, methods, properties, etc) lines\n `custom` - adds code lens at the start of symbols contained in `gitlens.codeLens.locationCustomSymbols`"
}, },
"gitlens.codeLens.customLocationSymbols": { "gitlens.codeLens.customLocationSymbols": {
"type": "array", "type": "array",
@@ -388,7 +388,7 @@
"minItems": 1, "minItems": 1,
"maxItems": 4, "maxItems": 4,
"uniqueItems": true, "uniqueItems": true,
"description": "Specifies where Git code lens will be shown in the document for the specified language\n `document` - adds code lens at the top of the document\n `containers` - adds code lens at the start of container-like symbols (modules, classes, interfaces, etc)\n `blocks` - adds code lens at the start of block-like symbols (functions, methods, properties, etc) lines\n `custom` - adds code lens at the start of symbols contained in `customSymbols`" "description": "Specifies where Git code lens will be shown in the document for the specified language\n `document` - adds code lens at the top of the document\n `containers` - adds code lens at the start of container-like symbols (modules, classes, interfaces, etc)\n `blocks` - adds code lens at the start of block-like symbols (functions, methods, properties, etc) lines\n `custom` - adds code lens at the start of symbols contained in `customSymbols`"
}, },
"customSymbols": { "customSymbols": {
"type": "array", "type": "array",
@@ -428,6 +428,11 @@
"default": true, "default": true,
"description": "Specifies whether or not to show the `GitLens` custom view" "description": "Specifies whether or not to show the `GitLens` custom view"
}, },
"gitlens.gitExplorer.includeWorkingTree": {
"type": "boolean",
"default": true,
"description": "Specifies whether or not to include working tree files inside the `Repository Status` node of the `GitLens` custom view"
},
"gitlens.gitExplorer.showTrackingBranch": { "gitlens.gitExplorer.showTrackingBranch": {
"type": "boolean", "type": "boolean",
"default": true, "default": true,
@@ -443,14 +448,20 @@
"default": "${filePath}", "default": "${filePath}",
"description": "Specifies the format of a stashed file in the `GitLens` custom view\nAvailable tokens\n ${file} - file name\n ${filePath} - file name and path\n ${path} - file path" "description": "Specifies the format of a stashed file in the `GitLens` custom view\nAvailable tokens\n ${file} - file name\n ${filePath} - file name and path\n ${path} - file path"
}, },
"gitlens.gitExplorer.statusFileFormat": {
"type": "string",
"default": "${working}${filePath}",
"description": "Specifies the format of the status of a working or committed file in the `GitLens` custom view\nAvailable tokens\n ${file} - file name\n ${filePath} - file name and path\n ${path} - file path\n ${working} - optional indicator if the file is uncommitted"
},
"gitlens.gitExplorer.view": { "gitlens.gitExplorer.view": {
"type": "string", "type": "string",
"default": "repository", "default": "auto",
"enum": [ "enum": [
"auto",
"history", "history",
"repository" "repository"
], ],
"description": "Specifies the starting view (mode) of the `GitLens` custom view\n `history` - shows the commit history of the active file\n `repository` - shows a repository explorer" "description": "Specifies the starting view (mode) of the `GitLens` custom view\n `auto` - shows the last selected view, defaults to `repository`\n `history` - shows the commit history of the active file\n `repository` - shows a repository explorer"
}, },
"gitlens.remotes": { "gitlens.remotes": {
"type": "array", "type": "array",
@@ -1921,7 +1932,6 @@
"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",
"string-width": "2.1.1",
"tmp": "0.0.33" "tmp": "0.0.33"
}, },
"devDependencies": { "devDependencies": {

View File

@@ -1,4 +1,4 @@
import { Dates, Strings } from '../system'; import { Dates, Objects, Strings } from '../system';
import { DecorationInstanceRenderOptions, DecorationOptions, MarkdownString, ThemableDecorationRenderOptions } from 'vscode'; import { DecorationInstanceRenderOptions, DecorationOptions, MarkdownString, ThemableDecorationRenderOptions } from 'vscode';
import { DiffWithCommand, OpenCommitInRemoteCommand, ShowQuickCommitDetailsCommand } from '../commands'; import { DiffWithCommand, OpenCommitInRemoteCommand, ShowQuickCommitDetailsCommand } from '../commands';
import { IThemeConfig, themeDefaults } from '../configuration'; import { IThemeConfig, themeDefaults } from '../configuration';
@@ -109,14 +109,14 @@ export class Annotations {
} }
static gutter(commit: GitCommit, format: string, dateFormatOrFormatOptions: string | null | ICommitFormatOptions, renderOptions: IRenderOptions): DecorationOptions { static gutter(commit: GitCommit, format: string, dateFormatOrFormatOptions: string | null | ICommitFormatOptions, renderOptions: IRenderOptions): DecorationOptions {
const content = Strings.pad(CommitFormatter.fromTemplate(format, commit, dateFormatOrFormatOptions), 1, 1); const message = CommitFormatter.fromTemplate(format, commit, dateFormatOrFormatOptions);
return { return {
renderOptions: { renderOptions: {
before: { before: {
...renderOptions.before, ...renderOptions.before,
...{ ...{
contentText: content contentText: Strings.pad(message.replace(/ /g, GlyphChars.Space), 1, 1)
} }
}, },
dark: { dark: {
@@ -133,9 +133,23 @@ export class Annotations {
} as DecorationOptions; } as DecorationOptions;
} }
static gutterRenderOptions(cfgTheme: IThemeConfig, heatmap: IHeatmapConfig): IRenderOptions { static gutterRenderOptions(cfgTheme: IThemeConfig, heatmap: IHeatmapConfig, options: ICommitFormatOptions): IRenderOptions {
const cfgFileTheme = cfgTheme.annotations.file.gutter; const cfgFileTheme = cfgTheme.annotations.file.gutter;
// Try to get the width of the string, if there is a cap
let width = 4; // Start with a padding
for (const token of Objects.values<Strings.ITokenOptions | undefined>(options.tokenOptions)) {
if (token === undefined) continue;
// If any token is uncapped, kick out and set no max
if (token.truncateTo == null) {
width = 0;
break;
}
width += token.truncateTo;
}
let borderStyle = undefined; let borderStyle = undefined;
let borderWidth = undefined; let borderWidth = undefined;
if (heatmap.enabled) { if (heatmap.enabled) {
@@ -152,7 +166,8 @@ export class Annotations {
borderStyle: borderStyle, borderStyle: borderStyle,
borderWidth: borderWidth, borderWidth: borderWidth,
height: '100%', height: '100%',
margin: '0 26px -1px 0' margin: '0 26px -1px 0',
width: (width > 4) ? `${width}ch` : undefined
}, },
dark: { dark: {
backgroundColor: cfgFileTheme.dark.backgroundColor || undefined, backgroundColor: cfgFileTheme.dark.backgroundColor || undefined,
@@ -195,10 +210,11 @@ export class Annotations {
truncateMessageAtNewLine: true, truncateMessageAtNewLine: true,
dateFormat: dateFormat dateFormat: dateFormat
} as ICommitFormatOptions); } as ICommitFormatOptions);
return { return {
renderOptions: { renderOptions: {
after: { after: {
contentText: Strings.pad(message, 1, 1) contentText: Strings.pad(message.replace(/ /g, GlyphChars.Space), 1, 1)
}, },
dark: { dark: {
after: { after: {

View File

@@ -34,7 +34,7 @@ export class GutterBlameAnnotationProvider extends BlameAnnotationProviderBase {
const now = Date.now(); const now = Date.now();
const offset = this.uri.offset; const offset = this.uri.offset;
const renderOptions = Annotations.gutterRenderOptions(this._config.theme, cfg.heatmap); const renderOptions = Annotations.gutterRenderOptions(this._config.theme, cfg.heatmap, options);
const separateLines = this._config.theme.annotations.file.gutter.separateLines; const separateLines = this._config.theme.annotations.file.gutter.separateLines;
const decorations: DecorationOptions[] = []; const decorations: DecorationOptions[] = [];
@@ -58,7 +58,7 @@ export class GutterBlameAnnotationProvider extends BlameAnnotationProviderBase {
...gutter.renderOptions, ...gutter.renderOptions,
before: { before: {
...gutter.renderOptions!.before, ...gutter.renderOptions!.before,
contentText: GlyphChars.Space.repeat(Strings.getWidth(gutter.renderOptions!.before!.contentText!)) contentText: GlyphChars.Space.repeat(Strings.width(gutter.renderOptions!.before!.contentText!))
} }
}; };

View File

@@ -319,11 +319,13 @@ export interface IConfig {
gitExplorer: { gitExplorer: {
enabled: boolean; enabled: boolean;
view: GitExplorerView; view: GitExplorerView;
includeWorkingTree: boolean;
showTrackingBranch: boolean; showTrackingBranch: boolean;
commitFormat: string; commitFormat: string;
commitFileFormat: string; commitFileFormat: string;
stashFormat: string; stashFormat: string;
stashFileFormat: string; stashFileFormat: string;
statusFileFormat: string;
// dateFormat: string | null; // dateFormat: string | null;
}; };

View File

@@ -81,10 +81,12 @@ export type GlyphChars = '\u21a9' |
'\u21e8' | '\u21e8' |
'\u2191' | '\u2191' |
'\u2197' | '\u2197' |
'\u2217' |
'\u2713' | '\u2713' |
'\u2014' | '\u2014' |
'\u2022' | '\u2022' |
'\u2026' | '\u2026' |
'\u270E' |
'\u00a0' | '\u00a0' |
'\u200b'; '\u200b';
export const GlyphChars = { export const GlyphChars = {
@@ -97,15 +99,22 @@ export const GlyphChars = {
ArrowRightHollow: '\u21e8' as GlyphChars, ArrowRightHollow: '\u21e8' as GlyphChars,
ArrowUp: '\u2191' as GlyphChars, ArrowUp: '\u2191' as GlyphChars,
ArrowUpRight: '\u2197' as GlyphChars, ArrowUpRight: '\u2197' as GlyphChars,
Asterisk: '\u2217' as GlyphChars,
Check: '\u2713' as GlyphChars, Check: '\u2713' as GlyphChars,
Dash: '\u2014' as GlyphChars, Dash: '\u2014' as GlyphChars,
Dot: '\u2022' as GlyphChars, Dot: '\u2022' as GlyphChars,
Ellipsis: '\u2026' as GlyphChars, Ellipsis: '\u2026' as GlyphChars,
Pensil: '\u270E' as GlyphChars,
Space: '\u00a0' as GlyphChars, Space: '\u00a0' as GlyphChars,
ZeroWidthSpace: '\u200b' as GlyphChars ZeroWidthSpace: '\u200b' as GlyphChars
}; };
export type WorkspaceState = 'gitlensVersion'; export type GlobalState = 'gitlensVersion';
export const WorkspaceState = { export const GlobalState = {
GitLensVersion: 'gitlensVersion' as WorkspaceState GitLensVersion: 'gitlensVersion' as GlobalState
};
export type WorkspaceState = 'gitlens:gitExplorer:view';
export const WorkspaceState = {
GitExplorerView: 'gitlens:gitExplorer:view' as WorkspaceState
}; };

View File

@@ -16,7 +16,7 @@ import { ShowQuickRepoStatusCommand, ShowQuickStashListCommand } from './command
import { StashApplyCommand, StashDeleteCommand, StashSaveCommand } from './commands'; import { StashApplyCommand, StashDeleteCommand, StashSaveCommand } from './commands';
import { ToggleCodeLensCommand } from './commands'; import { ToggleCodeLensCommand } from './commands';
import { CodeLensLocations, IConfig, LineHighlightLocations } from './configuration'; import { CodeLensLocations, IConfig, LineHighlightLocations } from './configuration';
import { ApplicationInsightsKey, CommandContext, ExtensionKey, QualifiedExtensionId, setCommandContext, WorkspaceState } from './constants'; import { ApplicationInsightsKey, CommandContext, ExtensionKey, GlobalState, QualifiedExtensionId, setCommandContext } from './constants';
import { CodeLensController } from './codeLensController'; import { CodeLensController } from './codeLensController';
import { CurrentLineController, LineAnnotationType } from './currentLineController'; import { CurrentLineController, LineAnnotationType } from './currentLineController';
import { RemoteProviderFactory } from './git/remotes/factory'; import { RemoteProviderFactory } from './git/remotes/factory';
@@ -71,7 +71,7 @@ export async function activate(context: ExtensionContext) {
notifyOnUnsupportedGitVersion(context, gitVersion); notifyOnUnsupportedGitVersion(context, gitVersion);
notifyOnNewGitLensVersion(context, gitlensVersion); notifyOnNewGitLensVersion(context, gitlensVersion);
await context.globalState.update(WorkspaceState.GitLensVersion, gitlensVersion); await context.globalState.update(GlobalState.GitLensVersion, gitlensVersion);
const git = new GitService(repoPath); const git = new GitService(repoPath);
context.subscriptions.push(git); context.subscriptions.push(git);
@@ -148,7 +148,7 @@ export async function activate(context: ExtensionContext) {
export function deactivate() { } export function deactivate() { }
async function migrateSettings(context: ExtensionContext) { async function migrateSettings(context: ExtensionContext) {
const previousVersion = context.globalState.get<string>(WorkspaceState.GitLensVersion); const previousVersion = context.globalState.get<string>(GlobalState.GitLensVersion);
if (previousVersion === undefined) return; if (previousVersion === undefined) return;
const [major] = previousVersion.split('.'); const [major] = previousVersion.split('.');
@@ -274,7 +274,7 @@ async function migrateSettings(context: ExtensionContext) {
async function notifyOnNewGitLensVersion(context: ExtensionContext, version: string) { async function notifyOnNewGitLensVersion(context: ExtensionContext, version: string) {
if (context.globalState.get(SuppressedKeys.UpdateNotice, false)) return; if (context.globalState.get(SuppressedKeys.UpdateNotice, false)) return;
const previousVersion = context.globalState.get<string>(WorkspaceState.GitLensVersion); const previousVersion = context.globalState.get<string>(GlobalState.GitLensVersion);
if (previousVersion === undefined) { if (previousVersion === undefined) {
Logger.log(`GitLens first-time install`); Logger.log(`GitLens first-time install`);

View File

@@ -51,7 +51,7 @@ export abstract class Formatter<TItem = any, TOptions extends IFormatOptions = I
let max = options.truncateTo; let max = options.truncateTo;
const width = Strings.getWidth(s); const width = Strings.width(s);
if (max === undefined) { if (max === undefined) {
if (this.collapsableWhitespace === 0) return s; if (this.collapsableWhitespace === 0) return s;

View File

@@ -1,7 +1,8 @@
'use strict'; 'use strict';
import { Strings } from '../../system'; import { Strings } from '../../system';
import { GlyphChars } from '../../constants';
import { Formatter, IFormatOptions } from './formatter'; import { Formatter, IFormatOptions } from './formatter';
import { GitStatusFile, IGitStatusFile } from '../models/status'; import { GitStatusFile, IGitStatusFile, IGitStatusFileWithCommit } from '../models/status';
import * as path from 'path'; import * as path from 'path';
export interface IStatusFormatOptions extends IFormatOptions { export interface IStatusFormatOptions extends IFormatOptions {
@@ -29,6 +30,11 @@ export class StatusFileFormatter extends Formatter<IGitStatusFile, IStatusFormat
return this._padOrTruncate(directory, this._options.tokenOptions!.file); return this._padOrTruncate(directory, this._options.tokenOptions!.file);
} }
get working() {
const commit = (this._item as IGitStatusFileWithCommit).commit;
return (commit !== undefined && commit.isUncommitted) ? `${GlyphChars.Pensil} ${GlyphChars.Space}` : '';
}
static fromTemplate(template: string, status: IGitStatusFile, dateFormat: string | null): string; 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, 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;

View File

@@ -259,6 +259,14 @@ export class Git {
return gitCommand({ cwd: repoPath }, ...params); return gitCommand({ cwd: repoPath }, ...params);
} }
static diff_shortstat(repoPath: string, sha?: string) {
const params = [`diff`, `--shortstat`, `--no-ext-diff`];
if (sha) {
params.push(sha);
}
return gitCommand({ cwd: repoPath }, ...params);
}
static difftool_dirDiff(repoPath: string, sha1: string, sha2?: string) { static difftool_dirDiff(repoPath: string, sha1: string, sha2?: string) {
const params = [`difftool`, `--dir-diff`, sha1]; const params = [`difftool`, `--dir-diff`, sha1];
if (sha2) { if (sha2) {
@@ -332,6 +340,14 @@ export class Git {
return gitCommand({ cwd: repoPath }, ...params, ...search); return gitCommand({ cwd: repoPath }, ...params, ...search);
} }
static log_shortstat(repoPath: string, sha?: string) {
const params = [`log`, `--shortstat`, `--oneline`];
if (sha) {
params.push(sha);
}
return gitCommand({ cwd: repoPath }, ...params);
}
static async ls_files(repoPath: string, fileName: string): Promise<string> { static async ls_files(repoPath: string, fileName: string): Promise<string> {
try { try {
return await gitCommand({ cwd: repoPath, overrideErrorHandling: true }, 'ls-files', fileName); return await gitCommand({ cwd: repoPath, overrideErrorHandling: true }, 'ls-files', fileName);

View File

@@ -34,3 +34,9 @@ export interface GitDiff {
diff?: string; diff?: string;
} }
export interface GitDiffShortStat {
files: number;
insertions: number;
deletions: number;
}

View File

@@ -3,6 +3,7 @@ import { Strings } from '../../system';
import { Uri } from 'vscode'; import { Uri } from 'vscode';
import { GlyphChars } from '../../constants'; import { GlyphChars } from '../../constants';
import { GitUri } from '../gitUri'; import { GitUri } from '../gitUri';
import { GitLogCommit } from './logCommit';
import * as path from 'path'; import * as path from 'path';
export interface GitStatus { export interface GitStatus {
@@ -27,6 +28,10 @@ export interface IGitStatusFile {
originalFileName?: string; originalFileName?: string;
} }
export interface IGitStatusFileWithCommit extends IGitStatusFile {
commit: GitLogCommit;
}
export class GitStatusFile implements IGitStatusFile { export class GitStatusFile implements IGitStatusFile {
originalFileName?: string; originalFileName?: string;

View File

@@ -1,8 +1,9 @@
'use strict'; 'use strict';
import { Iterables, Strings } from '../../system'; import { Iterables, Strings } from '../../system';
import { GitDiff, GitDiffChunk, GitDiffChunkLine, GitDiffLine } from './../git'; import { GitDiff, GitDiffChunk, GitDiffChunkLine, GitDiffLine, GitDiffShortStat } from './../git';
const unifiedDiffRegex = /^@@ -([\d]+),([\d]+) [+]([\d]+),([\d]+) @@([\s\S]*?)(?=^@@)/gm; const unifiedDiffRegex = /^@@ -([\d]+),([\d]+) [+]([\d]+),([\d]+) @@([\s\S]*?)(?=^@@)/gm;
const shortStatDiffRegex = /^\s*(\d+)\sfiles? changed(?:,\s+(\d+)\s+insertions?\(\+\))?(?:,\s+(\d+)\s+deletions?\(-\))?/;
export class GitDiffParser { export class GitDiffParser {
@@ -116,4 +117,20 @@ export class GitDiffParser {
return chunkLines; return chunkLines;
} }
static parseShortStat(data: string): GitDiffShortStat | undefined {
if (!data) return undefined;
const match = shortStatDiffRegex.exec(data);
if (match == null) return undefined;
const files = match[1];
const insertions = match[2];
const deletions = match[3];
return {
files: files == null ? 0 : parseInt(files, 10),
insertions: insertions == null ? 0 : parseInt(insertions, 10),
deletions: deletions == null ? 0 : parseInt(deletions, 10)
} as GitDiffShortStat;
}
} }

View File

@@ -4,7 +4,7 @@ import { Disposable, Event, EventEmitter, FileSystemWatcher, Location, Position,
import { IConfig } from './configuration'; import { IConfig } from './configuration';
import { DocumentSchemes, ExtensionKey, GlyphChars } from './constants'; import { DocumentSchemes, ExtensionKey, GlyphChars } from './constants';
import { RemoteProviderFactory } from './git/remotes/factory'; import { RemoteProviderFactory } from './git/remotes/factory';
import { Git, GitAuthor, GitBlame, GitBlameCommit, GitBlameLine, GitBlameLines, GitBlameParser, GitBranch, GitBranchParser, GitCommit, GitDiff, GitDiffChunkLine, GitDiffParser, GitLog, GitLogCommit, GitLogParser, GitRemote, GitRemoteParser, GitStash, GitStashParser, GitStatus, GitStatusFile, GitStatusParser, IGit, setDefaultEncoding } from './git/git'; import { Git, GitAuthor, GitBlame, GitBlameCommit, GitBlameLine, GitBlameLines, GitBlameParser, GitBranch, GitBranchParser, GitCommit, GitDiff, GitDiffChunkLine, GitDiffParser, GitDiffShortStat, GitLog, GitLogCommit, GitLogParser, GitRemote, GitRemoteParser, GitStash, GitStashParser, GitStatus, GitStatusFile, GitStatusParser, IGit, setDefaultEncoding } from './git/git';
import { GitUri, IGitCommitInfo, IGitUriData } from './git/gitUri'; import { GitUri, IGitCommitInfo, IGitUriData } from './git/gitUri';
import { Logger } from './logger'; import { Logger } from './logger';
import * as fs from 'fs'; import * as fs from 'fs';
@@ -86,6 +86,11 @@ export class GitService extends Disposable {
return this._onDidChangeGitCache.event; return this._onDidChangeGitCache.event;
} }
private _onDidChangeFileSystem = new EventEmitter<Uri>();
get onDidChangeFileSystem(): Event<Uri> {
return this._onDidChangeFileSystem.event;
}
private _onDidChangeRepo = new EventEmitter<RepoChangedReasons[]>(); private _onDidChangeRepo = new EventEmitter<RepoChangedReasons[]>();
get onDidChangeRepo(): Event<RepoChangedReasons[]> { get onDidChangeRepo(): Event<RepoChangedReasons[]> {
return this._onDidChangeRepo.event; return this._onDidChangeRepo.event;
@@ -121,14 +126,16 @@ export class GitService extends Disposable {
} }
dispose() { dispose() {
this.stopWatchingFileSystem();
this._repoWatcher && this._repoWatcher.dispose();
this._repoWatcher = undefined;
this._disposable && this._disposable.dispose(); this._disposable && this._disposable.dispose();
this._cacheDisposable && this._cacheDisposable.dispose(); this._cacheDisposable && this._cacheDisposable.dispose();
this._cacheDisposable = undefined; this._cacheDisposable = undefined;
this._repoWatcher && this._repoWatcher.dispose();
this._repoWatcher = undefined;
this._gitCache.clear(); this._gitCache.clear();
this._remotesCache.clear(); this._remotesCache.clear();
this._uriCache.clear(); this._uriCache.clear();
@@ -156,6 +163,8 @@ export class GitService extends Disposable {
disposables.push(workspace.onDidChangeTextDocument(this._onTextDocumentChanged, this)); disposables.push(workspace.onDidChangeTextDocument(this._onTextDocumentChanged, this));
disposables.push(workspace.onDidSaveTextDocument(d => this._removeCachedEntry(d, RemoveCacheReason.DocumentSaved))); disposables.push(workspace.onDidSaveTextDocument(d => this._removeCachedEntry(d, RemoveCacheReason.DocumentSaved)));
disposables.push(this._repoWatcher.onDidChange(this._onRepoChanged, this)); disposables.push(this._repoWatcher.onDidChange(this._onRepoChanged, this));
disposables.push(this._repoWatcher.onDidCreate(this._onRepoChanged, this));
disposables.push(this._repoWatcher.onDidDelete(this._onRepoChanged, this));
this._cacheDisposable = Disposable.from(...disposables); this._cacheDisposable = Disposable.from(...disposables);
} }
@@ -599,6 +608,11 @@ export class GitService extends Disposable {
return Git.normalizePath(typeof fileNameOrUri === 'string' ? fileNameOrUri : fileNameOrUri.fsPath).toLowerCase(); return Git.normalizePath(typeof fileNameOrUri === 'string' ? fileNameOrUri : fileNameOrUri.fsPath).toLowerCase();
} }
async getChangedFilesCount(repoPath: string, sha?: string): Promise<GitDiffShortStat | undefined> {
const data = await Git.diff_shortstat(repoPath, sha);
return GitDiffParser.parseShortStat(data);
}
async getConfig(key: string, repoPath?: string): Promise<string> { async getConfig(key: string, repoPath?: string): Promise<string> {
Logger.log(`getConfig('${key}', '${repoPath}')`); Logger.log(`getConfig('${key}', '${repoPath}')`);
@@ -1028,6 +1042,33 @@ export class GitService extends Disposable {
return Git.difftool_dirDiff(repoPath, sha1, sha2); return Git.difftool_dirDiff(repoPath, sha1, sha2);
} }
private _fsWatcherDisposable: Disposable | undefined;
startWatchingFileSystem() {
if (this._fsWatcherDisposable !== undefined) return;
const debouncedFn = Functions.debounce((uri: Uri) => this._onDidChangeFileSystem.fire(uri), 2500);
const fn = (uri: Uri) => {
// Ignore .git changes
if (/\.git/.test(uri.fsPath)) return;
debouncedFn(uri);
};
const watcher = workspace.createFileSystemWatcher(`**`);
this._fsWatcherDisposable = Disposable.from(
watcher,
watcher.onDidChange(fn),
watcher.onDidCreate(fn),
watcher.onDidDelete(fn)
);
}
stopWatchingFileSystem() {
this._fsWatcherDisposable && this._fsWatcherDisposable.dispose();
this._fsWatcherDisposable = undefined;
}
stashApply(repoPath: string, stashName: string, deleteAfter: boolean = false) { stashApply(repoPath: string, stashName: string, deleteAfter: boolean = false) {
Logger.log(`stashApply('${repoPath}', ${stashName}, ${deleteAfter})`); Logger.log(`stashApply('${repoPath}', ${stashName}, ${deleteAfter})`);

View File

@@ -1,16 +1,11 @@
'use strict'; 'use strict';
const _escapeRegExp = require('lodash.escaperegexp'); const _escapeRegExp = require('lodash.escaperegexp');
const stringWidth = require('string-width');
export namespace Strings { export namespace Strings {
export function escapeRegExp(s: string): string { export function escapeRegExp(s: string): string {
return _escapeRegExp(s); return _escapeRegExp(s);
} }
export function getWidth(s: string): number {
return stringWidth(s);
}
const TokenRegex = /\$\{([^|]*?)(?:\|(\d+)(\-|\?)?)?\}/g; const TokenRegex = /\$\{([^|]*?)(?:\|(\d+)(\-|\?)?)?\}/g;
const TokenSanitizeRegex = /\$\{(\w*?)(?:\W|\d)*?\}/g; const TokenSanitizeRegex = /\$\{(\w*?)(?:\W|\d)*?\}/g;
@@ -68,19 +63,19 @@ export namespace Strings {
} }
export function padLeft(s: string, padTo: number, padding: string = '\u00a0') { export function padLeft(s: string, padTo: number, padding: string = '\u00a0') {
const diff = padTo - getWidth(s); const diff = padTo - width(s);
return (diff <= 0) ? s : '\u00a0'.repeat(diff) + s; return (diff <= 0) ? s : '\u00a0'.repeat(diff) + s;
} }
export function padLeftOrTruncate(s: string, max: number, padding?: string) { export function padLeftOrTruncate(s: string, max: number, padding?: string) {
const len = getWidth(s); const len = width(s);
if (len < max) return padLeft(s, max, padding); if (len < max) return padLeft(s, max, padding);
if (len > max) return truncate(s, max); if (len > max) return truncate(s, max);
return s; return s;
} }
export function padRight(s: string, padTo: number, padding: string = '\u00a0') { export function padRight(s: string, padTo: number, padding: string = '\u00a0') {
const diff = padTo - getWidth(s); const diff = padTo - width(s);
return (diff <= 0) ? s : s + '\u00a0'.repeat(diff); return (diff <= 0) ? s : s + '\u00a0'.repeat(diff);
} }
@@ -88,14 +83,14 @@ export namespace Strings {
const left = max < 0; const left = max < 0;
max = Math.abs(max); max = Math.abs(max);
const len = getWidth(s); const len = width(s);
if (len < max) return left ? padLeft(s, max, padding) : padRight(s, max, padding); if (len < max) return left ? padLeft(s, max, padding) : padRight(s, max, padding);
if (len > max) return truncate(s, max); if (len > max) return truncate(s, max);
return s; return s;
} }
export function padRightOrTruncate(s: string, max: number, padding?: string) { export function padRightOrTruncate(s: string, max: number, padding?: string) {
const len = getWidth(s); const len = width(s);
if (len < max) return padRight(s, max, padding); if (len < max) return padRight(s, max, padding);
if (len > max) return truncate(s, max); if (len > max) return truncate(s, max);
return s; return s;
@@ -112,15 +107,15 @@ export namespace Strings {
export function truncate(s: string, truncateTo: number, ellipsis: string = '\u2026') { export function truncate(s: string, truncateTo: number, ellipsis: string = '\u2026') {
if (!s) return s; if (!s) return s;
const len = getWidth(s); const len = width(s);
if (len <= truncateTo) return s; if (len <= truncateTo) return s;
if (len === s.length) return `${s.substring(0, truncateTo - 1)}${ellipsis}`; if (len === s.length) return `${s.substring(0, truncateTo - 1)}${ellipsis}`;
// Skip ahead to start as far as we can by assuming all the double-width characters won't be truncated // Skip ahead to start as far as we can by assuming all the double-width characters won't be truncated
let chars = Math.floor(truncateTo / (len / s.length)); let chars = Math.floor(truncateTo / (len / s.length));
let count = getWidth(s.substring(0, chars)); let count = width(s.substring(0, chars));
while (count < truncateTo) { while (count < truncateTo) {
count += getWidth(s[chars++]); count += width(s[chars++]);
} }
if (count >= truncateTo) { if (count >= truncateTo) {
@@ -129,4 +124,107 @@ export namespace Strings {
return `${s.substring(0, chars)}${ellipsis}`; return `${s.substring(0, chars)}${ellipsis}`;
} }
const ansiRegex = /[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[a-zA-Z\\d]*)*)?\\u0007)|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PRZcf-ntqry=><~]))/g;
export function width(s: string): number {
if (!s || s.length === 0) return 0;
s = s.replace(ansiRegex, '');
let count = 0;
let emoji = 0;
let joiners = 0;
const graphemes = [...s];
for (let i = 0; i < graphemes.length; i++) {
const code = graphemes[i].codePointAt(0)!;
// Ignore control characters
if (code <= 0x1F || (code >= 0x7F && code <= 0x9F)) continue;
// Ignore combining characters
if (code >= 0x300 && code <= 0x36F) continue;
// https://stackoverflow.com/questions/30757193/find-out-if-character-in-string-is-emoji
if (
(code >= 0x1F600 && code <= 0x1F64F) || // Emoticons
(code >= 0x1F300 && code <= 0x1F5FF) || // Misc Symbols and Pictographs
(code >= 0x1F680 && code <= 0x1F6FF) || // Transport and Map
(code >= 0x2600 && code <= 0x26FF) || // Misc symbols
(code >= 0x2700 && code <= 0x27BF) || // Dingbats
(code >= 0xFE00 && code <= 0xFE0F) || // Variation Selectors
(code >= 0x1F900 && code <= 0x1F9FF) || // Supplemental Symbols and Pictographs
(code >= 65024 && code <= 65039) || // Variation selector
(code >= 8400 && code <= 8447) // Combining Diacritical Marks for Symbols
) {
if (code >= 0x1F3FB && code <= 0x1F3FF) continue; // emoji modifier fitzpatrick type
emoji++;
count += 2;
continue;
}
// Ignore zero-width joiners '\u200d'
if (code === 8205) {
joiners++;
count -= 2;
continue;
}
// Surrogates
if (code > 0xFFFF) {
i++;
}
count += isFullwidthCodePoint(code) ? 2 : 1;
}
const offset = emoji - joiners;
if (offset > 1) {
count += offset - 1;
}
return count;
}
function isFullwidthCodePoint(cp: number) {
// code points are derived from:
// http://www.unix.org/Public/UNIDATA/EastAsianWidth.txt
if (
cp >= 0x1100 && (
cp <= 0x115f || // Hangul Jamo
cp === 0x2329 || // LEFT-POINTING ANGLE BRACKET
cp === 0x232a || // RIGHT-POINTING ANGLE BRACKET
// CJK Radicals Supplement .. Enclosed CJK Letters and Months
(0x2e80 <= cp && cp <= 0x3247 && cp !== 0x303f) ||
// Enclosed CJK Letters and Months .. CJK Unified Ideographs Extension A
(0x3250 <= cp && cp <= 0x4dbf) ||
// CJK Unified Ideographs .. Yi Radicals
(0x4e00 <= cp && cp <= 0xa4c6) ||
// Hangul Jamo Extended-A
(0xa960 <= cp && cp <= 0xa97c) ||
// Hangul Syllables
(0xac00 <= cp && cp <= 0xd7a3) ||
// CJK Compatibility Ideographs
(0xf900 <= cp && cp <= 0xfaff) ||
// Vertical Forms
(0xfe10 <= cp && cp <= 0xfe19) ||
// CJK Compatibility Forms .. Small Form Variants
(0xfe30 <= cp && cp <= 0xfe6b) ||
// Halfwidth and Fullwidth Forms
(0xff01 <= cp && cp <= 0xff60) ||
(0xffe0 <= cp && cp <= 0xffe6) ||
// Kana Supplement
(0x1b000 <= cp && cp <= 0x1b001) ||
// Enclosed Ideographic Supplement
(0x1f200 <= cp && cp <= 0x1f251) ||
// CJK Unified Ideographs Extension B .. Tertiary Ideographic Plane
(0x20000 <= cp && cp <= 0x3fffd)
)
) {
return true;
}
return false;
}
} }

View File

@@ -42,14 +42,7 @@ export class CommitFileNode extends ExplorerNode {
} }
} }
const label = (this.displayAs & CommitFileNodeDisplayAs.CommitLabel) const item = new TreeItem(this.label, TreeItemCollapsibleState.None);
? CommitFormatter.fromTemplate(this.getCommitTemplate(), this.commit, {
truncateMessageAtNewLine: true,
dataFormat: this.git.config.defaultDateFormat
} as ICommitFormatOptions)
: StatusFileFormatter.fromTemplate(this.getCommitFileTemplate(), this.status);
const item = new TreeItem(label, TreeItemCollapsibleState.None);
item.contextValue = this.resourceType; item.contextValue = this.resourceType;
const icon = (this.displayAs & CommitFileNodeDisplayAs.CommitIcon) const icon = (this.displayAs & CommitFileNodeDisplayAs.CommitIcon)
@@ -63,9 +56,25 @@ export class CommitFileNode extends ExplorerNode {
item.command = this.getCommand(); item.command = this.getCommand();
// Only cache the label for a single refresh
this._label = undefined;
return item; return item;
} }
private _label: string | undefined;
get label() {
if (this._label === undefined) {
this._label = (this.displayAs & CommitFileNodeDisplayAs.CommitLabel)
? CommitFormatter.fromTemplate(this.getCommitTemplate(), this.commit, {
truncateMessageAtNewLine: true,
dataFormat: this.git.config.defaultDateFormat
} as ICommitFormatOptions)
: StatusFileFormatter.fromTemplate(this.getCommitFileTemplate(), this.status);
}
return this._label;
}
protected getCommitTemplate() { protected getCommitTemplate() {
return this.git.config.gitExplorer.commitFormat; return this.git.config.gitExplorer.commitFormat;
} }

View File

@@ -26,7 +26,9 @@ export class CommitNode extends ExplorerNode {
const commit = Iterables.first(log.commits.values()); const commit = Iterables.first(log.commits.values());
if (commit === undefined) return []; if (commit === undefined) return [];
return [...Iterables.map(commit.fileStatuses, s => new CommitFileNode(s, commit, this.context, this.git, CommitFileNodeDisplayAs.File, this.branch))]; const children = [...Iterables.map(commit.fileStatuses, s => new CommitFileNode(s, commit, this.context, this.git, CommitFileNodeDisplayAs.File, this.branch))];
children.sort((a, b) => a.label!.localeCompare(b.label!));
return children;
} }
getTreeItem(): TreeItem { getTreeItem(): TreeItem {

View File

@@ -4,16 +4,18 @@ import { commands, Event, EventEmitter, ExtensionContext, TextDocumentShowOption
import { Commands, DiffWithCommandArgs, DiffWithCommandArgsRevision, DiffWithPreviousCommandArgs, DiffWithWorkingCommandArgs, openEditor, OpenFileInRemoteCommandArgs } from '../commands'; import { Commands, DiffWithCommandArgs, DiffWithCommandArgsRevision, DiffWithPreviousCommandArgs, DiffWithWorkingCommandArgs, openEditor, OpenFileInRemoteCommandArgs } from '../commands';
import { UriComparer } from '../comparers'; import { UriComparer } from '../comparers';
import { ExtensionKey, IConfig } from '../configuration'; import { ExtensionKey, IConfig } from '../configuration';
import { CommandContext, setCommandContext } from '../constants'; import { CommandContext, setCommandContext, WorkspaceState } from '../constants';
import { BranchHistoryNode, CommitFileNode, CommitNode, ExplorerNode, HistoryNode, MessageNode, RepositoryNode, StashNode } from './explorerNodes'; import { BranchHistoryNode, CommitFileNode, CommitNode, ExplorerNode, HistoryNode, MessageNode, RepositoryNode, StashNode } from './explorerNodes';
import { GitService, GitUri, RepoChangedReasons } from '../gitService'; import { GitService, GitUri, RepoChangedReasons } from '../gitService';
export * from './explorerNodes'; export * from './explorerNodes';
export type GitExplorerView = export type GitExplorerView =
'auto' |
'history' | 'history' |
'repository'; 'repository';
export const GitExplorerView = { export const GitExplorerView = {
Auto: 'auto' as GitExplorerView,
History: 'history' as GitExplorerView, History: 'history' as GitExplorerView,
Repository: 'repository' as GitExplorerView Repository: 'repository' as GitExplorerView
}; };
@@ -31,7 +33,7 @@ export class GitExplorer implements TreeDataProvider<ExplorerNode> {
private _config: IConfig; private _config: IConfig;
private _root?: ExplorerNode; private _root?: ExplorerNode;
private _view: GitExplorerView = GitExplorerView.Repository; private _view: GitExplorerView | undefined;
private _onDidChangeTreeData = new EventEmitter<ExplorerNode>(); private _onDidChangeTreeData = new EventEmitter<ExplorerNode>();
public get onDidChangeTreeData(): Event<ExplorerNode> { public get onDidChangeTreeData(): Event<ExplorerNode> {
@@ -62,10 +64,6 @@ export class GitExplorer implements TreeDataProvider<ExplorerNode> {
context.subscriptions.push(workspace.onDidChangeConfiguration(this.onConfigurationChanged, this)); context.subscriptions.push(workspace.onDidChangeConfiguration(this.onConfigurationChanged, this));
this.onConfigurationChanged(); this.onConfigurationChanged();
this._view = this._config.gitExplorer.view;
setCommandContext(CommandContext.GitExplorerView, this._view);
this._root = this.getRootNode();
} }
async getTreeItem(node: ExplorerNode): Promise<TreeItem> { async getTreeItem(node: ExplorerNode): Promise<TreeItem> {
@@ -83,14 +81,14 @@ export class GitExplorer implements TreeDataProvider<ExplorerNode> {
} }
private getRootNode(editor?: TextEditor): ExplorerNode | undefined { private getRootNode(editor?: TextEditor): ExplorerNode | undefined {
const uri = new GitUri(Uri.file(this.git.repoPath), { repoPath: this.git.repoPath, fileName: this.git.repoPath });
switch (this._view) { switch (this._view) {
case GitExplorerView.History: return this.getHistoryNode(editor || window.activeTextEditor); case GitExplorerView.History:
case GitExplorerView.Repository: return new RepositoryNode(uri, this.context, this.git); return this.getHistoryNode(editor || window.activeTextEditor);
}
return undefined; default:
const uri = new GitUri(Uri.file(this.git.repoPath), { repoPath: this.git.repoPath, fileName: this.git.repoPath });
return new RepositoryNode(uri, this.context, this.git);
}
} }
private getHistoryNode(editor: TextEditor | undefined): ExplorerNode | undefined { private getHistoryNode(editor: TextEditor | undefined): ExplorerNode | undefined {
@@ -116,15 +114,21 @@ export class GitExplorer implements TreeDataProvider<ExplorerNode> {
private onConfigurationChanged() { private onConfigurationChanged() {
const cfg = workspace.getConfiguration().get<IConfig>(ExtensionKey)!; const cfg = workspace.getConfiguration().get<IConfig>(ExtensionKey)!;
if (!Objects.areEquivalent(cfg.gitExplorer, this._config && this._config.gitExplorer)) { const changed = !Objects.areEquivalent(cfg.gitExplorer, this._config && this._config.gitExplorer);
setTimeout(() => {
this._root = this.getRootNode(window.activeTextEditor);
this.refresh();
}, 1);
}
this._config = cfg; this._config = cfg;
}
if (changed) {
let view = cfg.gitExplorer.view;
if (view === GitExplorerView.Auto) {
view = this.context.workspaceState.get<GitExplorerView>(WorkspaceState.GitExplorerView, GitExplorerView.Repository);
}
this.setView(view);
this._root = this.getRootNode(window.activeTextEditor);
this.refresh();
}
}
private onRepoChanged(reasons: RepoChangedReasons[]) { private onRepoChanged(reasons: RepoChangedReasons[]) {
if (this._view !== GitExplorerView.Repository) return; if (this._view !== GitExplorerView.Repository) return;
@@ -148,12 +152,26 @@ export class GitExplorer implements TreeDataProvider<ExplorerNode> {
this.refresh(node); this.refresh(node);
} }
switchTo(view: GitExplorerView) { setView(view: GitExplorerView) {
if (this._view === view) return; if (this._view === view) return;
if (this._config.gitExplorer.view === GitExplorerView.Auto) {
this.context.workspaceState.update(WorkspaceState.GitExplorerView, view);
}
this._view = view; this._view = view;
setCommandContext(CommandContext.GitExplorerView, this._view); setCommandContext(CommandContext.GitExplorerView, this._view);
if (view !== GitExplorerView.Repository) {
this.git.stopWatchingFileSystem();
}
}
switchTo(view: GitExplorerView) {
if (this._view === view) return;
this.setView(view);
this._root = this.getRootNode(window.activeTextEditor); this._root = this.getRootNode(window.activeTextEditor);
this.refresh(); this.refresh();
} }

View File

@@ -31,7 +31,9 @@ export class StashNode extends ExplorerNode {
} }
} }
return statuses.map(s => new StashFileNode(s, this.commit, this.context, this.git)); const children = statuses.map(s => new StashFileNode(s, this.commit, this.context, this.git));
children.sort((a, b) => a.label!.localeCompare(b.label!));
return children;
} }
getTreeItem(): TreeItem { getTreeItem(): TreeItem {

View File

@@ -3,7 +3,7 @@ import { Command, ExtensionContext, TreeItem, TreeItemCollapsibleState, Uri } fr
import { Commands, DiffWithPreviousCommandArgs } from '../commands'; import { Commands, DiffWithPreviousCommandArgs } from '../commands';
import { CommitFileNode, CommitFileNodeDisplayAs } from './commitFileNode'; import { CommitFileNode, CommitFileNodeDisplayAs } from './commitFileNode';
import { ExplorerNode, ResourceType } from './explorerNode'; import { ExplorerNode, ResourceType } from './explorerNode';
import { getGitStatusIcon, GitBranch, GitLogCommit, GitService, GitUri, IGitStatusFile, StatusFileFormatter } from '../gitService'; import { getGitStatusIcon, GitBranch, GitLogCommit, GitService, GitUri, IGitStatusFile, IGitStatusFileWithCommit, StatusFileFormatter } from '../gitService';
import * as path from 'path'; import * as path from 'path';
export class StatusFileCommitsNode extends ExplorerNode { export class StatusFileCommitsNode extends ExplorerNode {
@@ -26,7 +26,7 @@ export class StatusFileCommitsNode extends ExplorerNode {
} }
async getTreeItem(): Promise<TreeItem> { async getTreeItem(): Promise<TreeItem> {
const item = new TreeItem(StatusFileFormatter.fromTemplate(this.git.config.gitExplorer.commitFileFormat, this.status), TreeItemCollapsibleState.Collapsed); const item = new TreeItem(this.label, TreeItemCollapsibleState.Collapsed);
item.contextValue = this.resourceType; item.contextValue = this.resourceType;
const icon = getGitStatusIcon(this.status.status); const icon = getGitStatusIcon(this.status.status);
@@ -41,9 +41,20 @@ export class StatusFileCommitsNode extends ExplorerNode {
item.command = this.getCommand(); item.command = this.getCommand();
} }
// Only cache the label for a single refresh
this._label = undefined;
return item; return item;
} }
private _label: string | undefined;
get label() {
if (this._label === undefined) {
this._label = StatusFileFormatter.fromTemplate(this.git.config.gitExplorer.statusFileFormat, { ...this.status, commit: this.commit } as IGitStatusFileWithCommit);
}
return this._label;
}
get commit() { get commit() {
return this.commits[0]; return this.commits[0];
} }

View File

@@ -2,13 +2,9 @@
import { Arrays, Iterables, Objects } from '../system'; import { Arrays, Iterables, Objects } from '../system';
import { ExtensionContext, TreeItem, TreeItemCollapsibleState, Uri } from 'vscode'; import { ExtensionContext, TreeItem, TreeItemCollapsibleState, Uri } from 'vscode';
import { ExplorerNode, ResourceType, ShowAllNode } from './explorerNode'; import { ExplorerNode, ResourceType, ShowAllNode } from './explorerNode';
import { GitBranch, GitLog, GitLogCommit, GitService, GitStatus, GitUri, IGitStatusFile } from '../gitService'; import { GitBranch, GitLog, GitLogCommit, GitService, GitStatus, GitUri, IGitStatusFileWithCommit } from '../gitService';
import { StatusFileCommitsNode } from './statusFileCommitsNode'; import { StatusFileCommitsNode } from './statusFileCommitsNode';
interface IGitStatusFileWithCommit extends IGitStatusFile {
commit: GitLogCommit;
}
export class StatusFilesNode extends ExplorerNode { export class StatusFilesNode extends ExplorerNode {
readonly resourceType: ResourceType = 'gitlens:status-files'; readonly resourceType: ResourceType = 'gitlens:status-files';
@@ -26,23 +22,21 @@ export class StatusFilesNode extends ExplorerNode {
} }
async getChildren(): Promise<ExplorerNode[]> { async getChildren(): Promise<ExplorerNode[]> {
let statuses: IGitStatusFileWithCommit[]; let statuses: IGitStatusFileWithCommit[] = [];
let log: GitLog | undefined; let log: GitLog | undefined;
if (this.range !== undefined) { if (this.range !== undefined) {
log = await this.git.getLogForRepo(this.status.repoPath, this.range, this.maxCount); log = await this.git.getLogForRepo(this.status.repoPath, this.range, this.maxCount);
if (log === undefined) return []; if (log !== undefined) {
statuses = Array.from(Iterables.flatMap(log.commits.values(), c => {
statuses = Array.from(Iterables.flatMap(log.commits.values(), c => { return c.fileStatuses.map(s => {
return c.fileStatuses.map(s => { return { ...s, commit: c } as IGitStatusFileWithCommit;
return { ...s, commit: c } as IGitStatusFileWithCommit; });
}); }));
})); }
}
else {
statuses = [];
} }
if (this.status.files.length !== 0) { if (this.status.files.length !== 0 && this.includeWorkingTree) {
statuses.splice(0, 0, ...this.status.files.map(s => { statuses.splice(0, 0, ...this.status.files.map(s => {
return { ...s, commit: new GitLogCommit('file', this.status.repoPath, GitService.uncommittedSha, s.fileName, 'You', new Date(), '', s.status, [s], s.originalFileName, 'HEAD', s.fileName) } as IGitStatusFileWithCommit; return { ...s, commit: new GitLogCommit('file', this.status.repoPath, GitService.uncommittedSha, s.fileName, 'You', new Date(), '', s.status, [s], s.originalFileName, 'HEAD', s.fileName) } as IGitStatusFileWithCommit;
})); }));
@@ -56,6 +50,8 @@ export class StatusFilesNode extends ExplorerNode {
statuses => new StatusFileCommitsNode(this.uri.repoPath!, statuses[statuses.length - 1], statuses.map(s => s.commit), this.context, this.git, this.branch)) statuses => new StatusFileCommitsNode(this.uri.repoPath!, statuses[statuses.length - 1], statuses.map(s => s.commit), this.context, this.git, this.branch))
]; ];
children.sort((a: StatusFileCommitsNode, b: StatusFileCommitsNode) => (a.commit.isUncommitted ? -1 : 1) - (b.commit.isUncommitted ? -1 : 1) || a.label!.localeCompare(b.label!));
if (log !== undefined && log.truncated) { if (log !== undefined && log.truncated) {
children.push(new ShowAllNode('Show All Changes', this, this.context)); children.push(new ShowAllNode('Show All Changes', this, this.context));
} }
@@ -63,8 +59,28 @@ export class StatusFilesNode extends ExplorerNode {
} }
async getTreeItem(): Promise<TreeItem> { async getTreeItem(): Promise<TreeItem> {
const item = new TreeItem(`Changed Files`, TreeItemCollapsibleState.Collapsed); let files = (this.status.files !== undefined && this.includeWorkingTree) ? this.status.files.length : 0;
if (this.status.upstream !== undefined) {
const stats = await this.git.getChangedFilesCount(this.status.repoPath, `${this.status.upstream}...`);
if (stats !== undefined) {
files += stats.files;
}
}
const label = `${files} file${files > 1 ? 's' : ''} changed`; // ${this.status.upstream === undefined ? '' : ` (ahead of ${this.status.upstream})`}`;
const item = new TreeItem(label, TreeItemCollapsibleState.Collapsed);
item.contextValue = this.resourceType; item.contextValue = this.resourceType;
item.iconPath = {
dark: this.context.asAbsolutePath(`images/dark/icon-diff.svg`),
light: this.context.asAbsolutePath(`images/light/icon-diff.svg`)
};
return item; return item;
} }
private get includeWorkingTree(): boolean {
return this.git.config.gitExplorer.includeWorkingTree;
}
} }

View File

@@ -1,9 +1,11 @@
import { ExtensionContext, TreeItem, TreeItemCollapsibleState } from 'vscode'; import { commands, Disposable, ExtensionContext, TreeItem, TreeItemCollapsibleState, Uri } from 'vscode';
import { ExplorerNode, ResourceType } from './explorerNode'; import { ExplorerNode, ResourceType } from './explorerNode';
import { GitService, GitUri } from '../gitService'; import { GitService, GitStatus, GitUri } from '../gitService';
import { StatusFilesNode } from './statusFilesNode'; import { StatusFilesNode } from './statusFilesNode';
import { StatusUpstreamNode } from './statusUpstreamNode'; import { StatusUpstreamNode } from './statusUpstreamNode';
let _eventDisposable: Disposable | undefined;
export class StatusNode extends ExplorerNode { export class StatusNode extends ExplorerNode {
readonly resourceType: ResourceType = 'gitlens:status'; readonly resourceType: ResourceType = 'gitlens:status';
@@ -30,29 +32,45 @@ export class StatusNode extends ExplorerNode {
children.push(new StatusUpstreamNode(status, 'ahead', this.context, this.git)); children.push(new StatusUpstreamNode(status, 'ahead', this.context, this.git));
} }
if (status.files.length !== 0 || status.state.ahead && this.git.config.insiders) { if (status.state.ahead || (status.files.length !== 0 && this.includeWorkingTree)) {
const range = status.state.ahead const range = status.upstream
? `${status.upstream}..${status.branch}` ? `${status.upstream}..${status.branch}`
: undefined; : undefined;
children.splice(0, 0, new StatusFilesNode(status, range, this.context, this.git)); children.push(new StatusFilesNode(status, range, this.context, this.git));
} }
return children; return children;
} }
async getTreeItem(): Promise<TreeItem> { private _status: GitStatus | undefined;
async getTreeItem(): Promise < TreeItem > {
const status = await this.git.getStatusForRepo(this.uri.repoPath!); const status = await this.git.getStatusForRepo(this.uri.repoPath!);
if (status === undefined) return new TreeItem('No repo status'); if (status === undefined) return new TreeItem('No repo status');
if (_eventDisposable !== undefined) {
_eventDisposable.dispose();
_eventDisposable = undefined;
}
if (this.includeWorkingTree) {
this._status = status;
_eventDisposable = this.git.onDidChangeFileSystem(this.onFileSystemChanged, this);
this.git.startWatchingFileSystem();
}
let hasChildren = false; let hasChildren = false;
const hasWorkingChanges = status.files.length !== 0 && this.includeWorkingTree;
let label = ''; let label = '';
let iconSuffix = ''; let iconSuffix = '';
if (status.upstream) { if (status.upstream) {
if (!status.state.ahead && !status.state.behind) { if (!status.state.ahead && !status.state.behind) {
label = `${status.branch} is up-to-date with ${status.upstream}`; label = `${status.branch}${hasWorkingChanges ? ' has uncommitted changes and' : ''} is up-to-date with ${status.upstream}`;
} }
else { else {
label = `${status.branch} is not up-to-date with ${status.upstream}`; label = `${status.branch}${hasWorkingChanges ? ' has uncommitted changes and' : ''} is not up-to-date with ${status.upstream}`;
hasChildren = true; hasChildren = true;
if (status.state.ahead && status.state.behind) { if (status.state.ahead && status.state.behind) {
iconSuffix = '-yellow'; iconSuffix = '-yellow';
@@ -66,14 +84,10 @@ export class StatusNode extends ExplorerNode {
} }
} }
else { else {
label = `${status.branch} is up-to-date`; label = `${status.branch} ${hasWorkingChanges ? 'has uncommitted changes' : this.includeWorkingTree ? 'has no changes' : 'has nothing to commit'}`;
} }
if (this.git.config.insiders) { const item = new TreeItem(label, (hasChildren || hasWorkingChanges) ? TreeItemCollapsibleState.Expanded : TreeItemCollapsibleState.None);
hasChildren = hasChildren || status.files.length !== 0;
}
const item = new TreeItem(label, hasChildren ? TreeItemCollapsibleState.Expanded : TreeItemCollapsibleState.None);
item.contextValue = this.resourceType; item.contextValue = this.resourceType;
item.iconPath = { item.iconPath = {
@@ -83,4 +97,21 @@ export class StatusNode extends ExplorerNode {
return item; return item;
} }
private get includeWorkingTree(): boolean {
return this.git.config.gitExplorer.includeWorkingTree;
}
private async onFileSystemChanged(uri: Uri) {
const status = await this.git.getStatusForRepo(this.uri.repoPath!);
// If we haven't changed from having some working changes to none or vice versa then just refresh the node
// This is because of https://github.com/Microsoft/vscode/issues/34789
if (this._status !== undefined && status !== undefined &&
((this._status.files.length === status.files.length) || (this._status.files.length > 0 && status.files.length > 0))) {
commands.executeCommand('gitlens.gitExplorer.refreshNode', this);
}
commands.executeCommand('gitlens.gitExplorer.refresh');
}
} }

View File

@@ -43,8 +43,8 @@ export class StatusUpstreamNode extends ExplorerNode {
async getTreeItem(): Promise<TreeItem> { async getTreeItem(): Promise<TreeItem> {
const label = this.direction === 'ahead' const label = this.direction === 'ahead'
? `${this.status.state.ahead} commit${this.status.state.ahead > 1 ? 's' : ''} ahead (local changes)` // of ${this.status.upstream}` ? `${this.status.state.ahead} commit${this.status.state.ahead > 1 ? 's' : ''} (ahead of ${this.status.upstream})`
: `${this.status.state.behind} commit${this.status.state.behind > 1 ? 's' : ''} behind (remote changes)`; // ${this.status.upstream}`; : `${this.status.state.behind} commit${this.status.state.behind > 1 ? 's' : ''} (behind ${this.status.upstream})`;
const item = new TreeItem(label, TreeItemCollapsibleState.Collapsed); const item = new TreeItem(label, TreeItemCollapsibleState.Collapsed);
item.contextValue = this.resourceType; item.contextValue = this.resourceType;