From ff5780e6966c6b2e6a056e8d017a01e19e5e65ec Mon Sep 17 00:00:00 2001 From: Eric Amodio Date: Wed, 29 Nov 2017 04:13:54 -0500 Subject: [PATCH] Adds commit search results view Adds comparison results view --- CHANGELOG.md | 22 +- README.md | 52 ++- images/dark/icon-close.svg | 4 + images/dark/icon-lock.svg | 4 + images/dark/icon-locked.svg | 6 + images/light/icon-close.svg | 4 + images/light/icon-lock.svg | 4 + images/light/icon-locked.svg | 6 + package.json | 768 +++++++++++++++++++++++++----------- src/commands/showCommitSearch.ts | 14 +- src/configuration.ts | 53 ++- src/constants.ts | 15 +- src/extension.ts | 11 +- src/git/parsers/statusParser.ts | 9 +- src/gitService.ts | 5 +- src/quickPicks/commits.ts | 6 +- src/quickPicks/common.ts | 20 +- src/system/function.ts | 13 + src/views/branchHistoryNode.ts | 74 ---- src/views/branchNode.ts | 77 ++++ src/views/branchesNode.ts | 9 +- src/views/commitFileNode.ts | 25 +- src/views/commitNode.ts | 18 +- src/views/commitsNode.ts | 36 ++ src/views/commitsResultsNode.ts | 69 ++++ src/views/comparisionResultsNode.ts | 56 +++ src/views/explorerCommands.ts | 110 +++--- src/views/explorerNode.ts | 59 ++- src/views/explorerNodes.ts | 7 +- src/views/fileHistoryNode.ts | 2 +- src/views/folderNode.ts | 19 +- src/views/gitExplorer.ts | 62 ++- src/views/historyNode.ts | 2 +- src/views/remoteNode.ts | 9 +- src/views/remotesNode.ts | 5 +- src/views/resultsExplorer.ts | 181 +++++++++ src/views/stashFileNode.ts | 5 +- src/views/stashNode.ts | 11 +- src/views/stashesNode.ts | 5 +- src/views/statusFileCommitsNode.ts | 18 +- src/views/statusFileNode.ts | 95 +++++ src/views/statusFilesNode.ts | 9 +- src/views/statusFilesResultsNode.ts | 82 ++++ src/views/statusUpstreamNode.ts | 5 +- 44 files changed, 1564 insertions(+), 502 deletions(-) create mode 100644 images/dark/icon-close.svg create mode 100644 images/dark/icon-lock.svg create mode 100644 images/dark/icon-locked.svg create mode 100644 images/light/icon-close.svg create mode 100644 images/light/icon-lock.svg create mode 100644 images/light/icon-locked.svg delete mode 100644 src/views/branchHistoryNode.ts create mode 100644 src/views/branchNode.ts create mode 100644 src/views/commitsNode.ts create mode 100644 src/views/commitsResultsNode.ts create mode 100644 src/views/comparisionResultsNode.ts create mode 100644 src/views/resultsExplorer.ts create mode 100644 src/views/statusFileNode.ts create mode 100644 src/views/statusFilesResultsNode.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index ced8394..841e388 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,27 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p - Automatically updates to track the repository of the active editor - Only visible if there is more than 1 repository within the workspace - Adds [Gravatar](https://en.gravatar.com/) support to the `GitLens` view -- Adds `gitlens.gitExplorer.gravatars` setting to specify whether or not to show gravatar images instead of commit (or status) icons in the `GitLens` view + - Adds `gitlens.gitExplorer.gravatars` setting to specify whether or not to show gravatar images instead of commit (or status) icons in the `GitLens` view +- Adds `Select for Compare` command (`gitlens.explorers.selectForCompare`) to branch and revision (commit) nodes in the `GitLens` view to mark the base revision of a comparision +- Adds `Compare with Selected` command (`gitlens.explorers.compareWithSelected`) to branch and revision (commit) nodes in the `GitLens` view once another branch or revision (commit) node within the same repository has been selected to compare the current selection with the previously selected revision (branch or commit) +- Adds `Show in Results` option to commit search quick pick menu to show the results in the `GitLens Results` view +- Adds an all-new, on-demand `GitLens Results` view to the Explorer activity + + - Provides semi-persistent results for commit search operations, via `Show Commit Search` command (`gitlens.showCommitSearch`) + - Expand each revision (commit) to quickly see the set of files changed, complete with status indicators for adds, changes, renames, and deletes + - Provides a context menu on each revision (commit) with `Open Commit in Remote`, `Open All Changes`, `Open All Changes with Working Tree`, `Open Files`, `Open Revisions`, `Copy Commit ID to Clipboard`, `Copy Commit Message to Clipboard`, `Show Commit Details`, `Compare with Selected`, `Select for Compare`, `Rebase Commit (via Terminal)`, `Reset Commit (via Terminal)`, and `Refresh` commands + - Provides a context menu on each changed file with `Open Changes`, `Open Changes with Working Tree`, `Open File`, `Open Revision`, `Open File in Remote`, `Open Revision in Remote`, `Apply Changes`, and `Show Commit File Details` commands + + - Provides semi-persistent results for revision comparison operations, via `Select for Compare` command (`gitlens.explorers.selectForCompare`) and `Compare with Selected` command (`gitlens.explorers.compareWithSelected`) + - `Commits` node — provides a list of the commits between the compared revisions (branches or commits) + - Expand each revision (commit) to quickly see the set of files changed, complete with status indicators for adds, changes, renames, and deletes + - Provides a context menu on each revision (commit) with `Open Commit in Remote`, `Open All Changes`, `Open All Changes with Working Tree`, `Open Files`, `Open Revisions`, `Copy Commit ID to Clipboard`, `Copy Commit Message to Clipboard`, `Show Commit Details`, `Compare with Selected`, `Select for Compare`, `Rebase Commit (via Terminal)`, `Reset Commit (via Terminal)`, and `Refresh` commands + - Provides a context menu on each changed file with `Open Changes`, `Open Changes with Working Tree`, `Open File`, `Open Revision`, `Open File in Remote`, `Open Revision in Remote`, `Apply Changes`, and `Show Commit File Details` commands + - `Changed Files` node — provides a list of all the files changed between the compared revisions (branches or commits) + - Expands to a file-based view of all changed files + - Provides a context menu on each changed file with `Open Changes`, `Open Changes with Working Tree`, `Open File`, `Open Revision`, `Open File in Remote`, `Open Revision in Remote`, `Apply Changes`, and `Show Commit File Details` commands + + - Provides toolbar commands to `Search Commits`, `Keep Results`, `Refresh`, `Show Files in Automatic View` or `Show Files in List View` or `Show Files in Tree View`, and `Close` ### Fixed - Fixes [#228](https://github.com/eamodio/vscode-gitlens/issues/228) - Gutter blame spills over heatmap diff --git a/README.md b/README.md index 33a99fc..87bdd47 100644 --- a/README.md +++ b/README.md @@ -129,14 +129,14 @@ While GitLens is highly customizable and provides many [configuration settings]( ### Navigate and Explore -- Adds a [customizable](#gitlens-custom-view-settings) `GitLens` view to the Explorer activity +- Adds a [customizable](#gitlens-view-settings) `GitLens` view to the Explorer activity - `Repository View` - provides a full repository explorer ![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 - - 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 the name of the current branch, [optionally](#gitlens-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: - `None` - up-to-date with the upstream - `Green` - ahead of the upstream @@ -146,11 +146,11 @@ While GitLens is highly customizable and provides many [configuration settings]( - 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 + - Expands to a file-based view of all changed files in the working tree ([optionally](#gitlens-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 - `Branches` node — provides a list of the local branches - - Indicates which branch is the current branch and [optionally](#gitlens-custom-view-settings) shows the remote tracking branch + - Indicates which branch is the current branch and [optionally](#gitlens-view-settings) shows the remote tracking branch - Expand each branch to easily see its revision (commit) history - Provides indicator dots on each branch icon which denote the following: - `None` - no upstream or up-to-date with the upstream @@ -158,7 +158,7 @@ While GitLens is highly customizable and provides many [configuration settings]( - `Red` - behind the upstream - `Yellow` - both ahead of and behind the upstream - Expand each revision (commit) to quickly see the set of files changed, complete with status indicators for adds, changes, renames, and deletes - - Provides a context menu on each revision (commit) with `Open Commit in Remote`, `Open All Changes`, `Open All Changes with Working Tree`, `Open Files`, `Open Revisions`, `Copy Commit ID to Clipboard`, `Copy Commit Message to Clipboard`, `Show Commit Details`, `Rebase Commit (via Terminal)`, `Reset Commit (via Terminal)`, and `Refresh` commands + - Provides a context menu on each revision (commit) with `Open Commit in Remote`, `Open All Changes`, `Open All Changes with Working Tree`, `Open Files`, `Open Revisions`, `Copy Commit ID to Clipboard`, `Copy Commit Message to Clipboard`, `Show Commit Details`, `Compare with Selected`, `Select for Compare`, `Rebase Commit (via Terminal)`, `Reset Commit (via Terminal)`, and `Refresh` commands - Provides a context menu on each changed file with `Open Changes`, `Open Changes with Working Tree`, `Open File`, `Open Revision`, `Open File in Remote`, `Open Revision in Remote`, `Apply Changes`, and `Show Commit File Details` commands - Provides a context menu on each branch with `Open Branch in Remote`, `Checkout Branch (via Terminal)`, `Create Branch (via Terminal)...`, `Delete Branch (via Terminal)`, `Rebase Branch to Remote (via Terminal)`, `Squash Branch into Commit (via Terminal)`, and `Refresh` commands - Provides a context menu with `Open Branches in Remote`, and `Refresh` commands @@ -168,7 +168,7 @@ While GitLens is highly customizable and provides many [configuration settings]( - Expand each remote to see its list of branches - Expand each branch to easily see its revision (commit) history - Expand each revision (commit) to quickly see the set of files changed, complete with status indicators for adds, changes, renames, and deletes - - Provides a context menu on each revision (commit) with `Open Commit in Remote`, `Open All Changes`, `Open All Changes with Working Tree`, `Open Files`, `Open Revisions`, `Copy Commit ID to Clipboard`, `Copy Commit Message to Clipboard`,`Show Commit Details`, and `Refresh` commands + - Provides a context menu on each revision (commit) with `Open Commit in Remote`, `Open All Changes`, `Open All Changes with Working Tree`, `Open Files`, `Open Revisions`, `Copy Commit ID to Clipboard`, `Copy Commit Message to Clipboard`,`Show Commit Details`, `Compare with Selected`, `Select for Compare`, and `Refresh` commands - Provides a context menu on each changed file with `Open Changes`, `Open Changes with Working Tree`, `Open File`, `Open Revision`, `Open File in Remote`, `Open Revision in Remote`, `Apply Changes`, `Show File History`, and `Show Commit File Details` commands - Provides a context menu on each remote branch with `Open Branch in Remote`, `Checkout Branch (via Terminal)`, `Create Branch (via Terminal)...`, `Delete Branch (via Terminal)`, `Squash Branch into Commit (via Terminal)`, and `Refresh` commands - Provides a context menu on each remote with `Open Branches in Remote`, `Open Repository in Remote`, `Remove Remote (via Terminal)`, and `Refresh` commands @@ -176,7 +176,7 @@ While GitLens is highly customizable and provides many [configuration settings]( - `Stashes` node — provides a list of stashed changes - Expand each stash to quickly see the set of files stashed, complete with status indicators for adds, changes, renames, and deletes - - Provides a context menu on each stash with `Apply Stashed Changes` (confirmation required), `Delete Stashed Changes` (confirmation required), `Open All Changes`, `Open All Changes with Working Tree`, `Open Files`, `Open Revisions`, `Copy Commit Message to Clipboard`, and `Refresh` commands + - Provides a context menu on each stash with `Apply Stashed Changes` (confirmation required), `Delete Stashed Changes` (confirmation required), `Open All Changes`, `Open All Changes with Working Tree`, `Open Files`, `Open Revisions`, `Copy Commit Message to Clipboard`, `Compare with Selected`, `Select for Compare`, and `Refresh` commands - Provides a context menu on each stashed file with `Apply Changes`, `Open Changes`, `Open Changes with Working Tree`, `Open File`, `Open Revision`, `Open File in Remote`, and `Show File History` commands - Provides a context menu with `Stash Changes`, and `Refresh` commands @@ -198,6 +198,25 @@ While GitLens is highly customizable and provides many [configuration settings]( - Use `#` to search for a commit with id of `` -- See [Git docs](https://git-scm.com/docs/git-log) - Use `~` to search for commits with differences whose patch text contains added/removed lines that match `` -- See [Git docs](https://git-scm.com/docs/git-log#git-log--Gltregexgt) - Use `=` to search for commits with differences that change the number of occurrences of the specified string (i.e. addition/deletion) in a file -- See [Git docs](https://git-scm.com/docs/git-log#git-log--Sltstringgt) + - Provides a `Show in Results` option to show the search results in the `GitLens Results` view + +- Adds an on-demand, [customizable](#gitlens-results-view-settings) `GitLens Results` view to the Explorer activity + + - Provides semi-persistent results for commit search operations, via `Show Commit Search` command (`gitlens.showCommitSearch`) + - Expand each revision (commit) to quickly see the set of files changed, complete with status indicators for adds, changes, renames, and deletes + - Provides a context menu on each revision (commit) with `Open Commit in Remote`, `Open All Changes`, `Open All Changes with Working Tree`, `Open Files`, `Open Revisions`, `Copy Commit ID to Clipboard`, `Copy Commit Message to Clipboard`, `Show Commit Details`, `Compare with Selected`, `Select for Compare`, `Rebase Commit (via Terminal)`, `Reset Commit (via Terminal)`, and `Refresh` commands + - Provides a context menu on each changed file with `Open Changes`, `Open Changes with Working Tree`, `Open File`, `Open Revision`, `Open File in Remote`, `Open Revision in Remote`, `Apply Changes`, and `Show Commit File Details` commands + + - Provides semi-persistent results for revision comparison operations, via `Select for Compare` command (`gitlens.explorers.selectForCompare`) and `Compare with Selected` command (`gitlens.explorers.compareWithSelected`) + - `Commits` node — provides a list of the commits between the compared revisions (branches or commits) + - Expand each revision (commit) to quickly see the set of files changed, complete with status indicators for adds, changes, renames, and deletes + - Provides a context menu on each revision (commit) with `Open Commit in Remote`, `Open All Changes`, `Open All Changes with Working Tree`, `Open Files`, `Open Revisions`, `Copy Commit ID to Clipboard`, `Copy Commit Message to Clipboard`, `Show Commit Details`, `Compare with Selected`, `Select for Compare`, `Rebase Commit (via Terminal)`, `Reset Commit (via Terminal)`, and `Refresh` commands + - Provides a context menu on each changed file with `Open Changes`, `Open Changes with Working Tree`, `Open File`, `Open Revision`, `Open File in Remote`, `Open Revision in Remote`, `Apply Changes`, and `Show Commit File Details` commands + - `Changed Files` node — provides a list of all the files changed between the compared revisions (branches or commits) + - Expands to a file-based view of all changed files + - Provides a context menu on each changed file with `Open Changes`, `Open Changes with Working Tree`, `Open File`, `Open Revision`, `Open File in Remote`, `Open Revision in Remote`, `Apply Changes`, and `Show Commit File Details` commands + + - Provides toolbar commands to `Search Commits`, `Keep Results`, `Refresh`, `Show Files in Automatic View` or `Show Files in List View` or `Show Files in Tree View`, and `Close` - Adds commands to open files, commits, branches, and the repository in the supported remote services, **BitBucket, GitHub, GitLab, and Visual Studio Team Services** or a [**user-defined** remote services](#custom-remotes-settings) — only available if a Git upstream service is configured in the repository - Also supports [remote services with custom domains](#custom-remotes-settings), such as **BitBucket, Bitbucket Server (previously called Stash), GitHub, GitHub Enterprise, GitLab** @@ -420,15 +439,30 @@ GitLens is highly customizable and provides many configuration settings to allow |`gitlens.gitExplorer.files.layout`|Specifies how the `GitLens` view will display files
`auto` - automatically switches between displaying files as a `tree` or `list` based on the `gitlens.gitExplorer.files.threshold` setting and the number of files at each nesting level
`list` - displays files as a list
`tree` - displays files as a tree |`gitlens.gitExplorer.files.compact`|Specifies whether or not to compact (flatten) unnecessary file nesting in the `GitLens` view
Only applies when displaying files as a `tree` or `auto` |`gitlens.gitExplorer.files.threshold`|Specifies when to switch between displaying files as a `tree` or `list` based on the number of files in a nesting level in the `GitLens` view
Only applies when displaying files as `auto` -|`gitlens.gitExplorer.includeWorkingTree`|Specifies whether or not to include working tree files inside the `Repository Status` node of the `GitLens` view -|`gitlens.gitExplorer.showTrackingBranch`|Specifies whether or not to show the tracking branch when displaying local branches in the `GitLens` view" |`gitlens.gitExplorer.commitFormat`|Specifies the format of committed changes in the `GitLens` view
Available tokens
${id} - commit id
${author} - commit author
${message} - commit message
${ago} - relative commit date (e.g. 1 day ago)
${date} - formatted commit date (format specified by `gitlens.statusBar.dateFormat`)
${authorAgo} - commit author, relative commit date
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` view
Available tokens
${directory} - directory name
${file} - file name
${filePath} - formatted file name and path
${path} - full file path |`gitlens.gitExplorer.gravatars`|Specifies whether or not to show gravatar images instead of commit (or status) icons in the `GitLens` view +|`gitlens.gitExplorer.includeWorkingTree`|Specifies whether or not to include working tree files inside the `Repository Status` node of the `GitLens` view +|`gitlens.gitExplorer.showTrackingBranch`|Specifies whether or not to show the tracking branch when displaying local branches in the `GitLens` view" |`gitlens.gitExplorer.stashFormat`|Specifies the format of stashed changes in the `GitLens` view
Available tokens
${id} - commit id
${author} - commit author
${message} - commit message
${ago} - relative commit date (e.g. 1 day ago)
${date} - formatted commit date (format specified by `gitlens.statusBar.dateFormat`)
${authorAgo} - commit author, relative commit date
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` view
Available tokens
${directory} - directory name
${file} - file name
${filePath} - formatted file name and path
${path} - full file path |`gitlens.gitExplorer.statusFileFormat`|Specifies the format of the status of a working or committed file in the `GitLens` view
Available tokens
${directory} - directory name
${file} - file name
${filePath} - formatted file name and path
${path} - full file path
${working} - optional indicator if the file is uncommitted +### GitLens Results View Settings + +|Name | Description +|-----|------------ +|`gitlens.resultsExplorer.files.layout`|Specifies how the `GitLens Results` view will display files
`auto` - automatically switches between displaying files as a `tree` or `list` based on the `gitlens.resultsExplorer.files.threshold` setting and the number of files at each nesting level
`list` - displays files as a list
`tree` - displays files as a tree +|`gitlens.resultsExplorer.files.compact`|Specifies whether or not to compact (flatten) unnecessary file nesting in the `GitLens Results` view
Only applies when displaying files as a `tree` or `auto` +|`gitlens.resultsExplorer.files.threshold`|Specifies when to switch between displaying files as a `tree` or `list` based on the number of files in a nesting level in the `GitLens Results` view
Only applies when displaying files as `auto` +|`gitlens.resultsExplorer.commitFormat`|Specifies the format of committed changes in the `GitLens Results` view
Available tokens
${id} - commit id
${author} - commit author
${message} - commit message
${ago} - relative commit date (e.g. 1 day ago)
${date} - formatted commit date (format specified by `gitlens.statusBar.dateFormat`)
${authorAgo} - commit author, relative commit date
See https://github.com/eamodio/vscode-gitlens/wiki/Advanced-Formatting for advanced formatting +|`gitlens.resultsExplorer.commitFileFormat`|Specifies the format of a committed file in the `GitLens Results` view
Available tokens
${directory} - directory name
${file} - file name
${filePath} - formatted file name and path
${path} - full file path +|`gitlens.resultsExplorer.gravatars`|Specifies whether or not to show gravatar images instead of commit (or status) icons in the `GitLens Results` view +|`gitlens.resultsExplorer.showTrackingBranch`|Specifies whether or not to show the tracking branch when displaying local branches in the `GitLens Results` view" +|`gitlens.resultsExplorer.stashFormat`|Specifies the format of stashed changes in the `GitLens Results` view
Available tokens
${id} - commit id
${author} - commit author
${message} - commit message
${ago} - relative commit date (e.g. 1 day ago)
${date} - formatted commit date (format specified by `gitlens.statusBar.dateFormat`)
${authorAgo} - commit author, relative commit date
See https://github.com/eamodio/vscode-gitlens/wiki/Advanced-Formatting for advanced formatting +|`gitlens.resultsExplorer.stashFileFormat`|Specifies the format of a stashed file in the `GitLens Results` view
Available tokens
${directory} - directory name
${file} - file name
${filePath} - formatted file name and path
${path} - full file path +|`gitlens.resultsExplorer.statusFileFormat`|Specifies the format of the status of a working or committed file in the `GitLens Results` view
Available tokens
${directory} - directory name
${file} - file name
${filePath} - formatted file name and path
${path} - full file path
${working} - optional indicator if the file is uncommitted + ### Custom Remotes Settings |Name | Description diff --git a/images/dark/icon-close.svg b/images/dark/icon-close.svg new file mode 100644 index 0000000..df1da6a --- /dev/null +++ b/images/dark/icon-close.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/images/dark/icon-lock.svg b/images/dark/icon-lock.svg new file mode 100644 index 0000000..650b098 --- /dev/null +++ b/images/dark/icon-lock.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/images/dark/icon-locked.svg b/images/dark/icon-locked.svg new file mode 100644 index 0000000..79ffb21 --- /dev/null +++ b/images/dark/icon-locked.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/images/light/icon-close.svg b/images/light/icon-close.svg new file mode 100644 index 0000000..9952816 --- /dev/null +++ b/images/light/icon-close.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/images/light/icon-lock.svg b/images/light/icon-lock.svg new file mode 100644 index 0000000..5871a2f --- /dev/null +++ b/images/light/icon-lock.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/images/light/icon-locked.svg b/images/light/icon-locked.svg new file mode 100644 index 0000000..f709ef7 --- /dev/null +++ b/images/light/icon-locked.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/package.json b/package.json index b745fea..fd9cc4f 100644 --- a/package.json +++ b/package.json @@ -662,6 +662,71 @@ "description": "Specifies user-defined remote (code-hosting) services or custom domains for built-in remote services", "scope": "resource" }, + "gitlens.resultsExplorer.commitFormat": { + "type": "string", + "default": "${message} \u00a0\u2022\u00a0 ${authorAgo} \u00a0 (${id})", + "description": "Specifies the format of committed changes in the `GitLens Results` view\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", + "scope": "window" + }, + "gitlens.resultsExplorer.commitFileFormat": { + "type": "string", + "default": "${filePath}", + "description": "Specifies the format of a committed file in the `GitLens Results` view\nAvailable tokens\n ${directory} - directory name\n ${file} - file name\n ${filePath} - formatted file name and path\n ${path} - full file path", + "scope": "window" + }, + "gitlens.resultsExplorer.files.layout": { + "type": "string", + "default": "auto", + "enum": [ + "auto", + "list", + "tree" + ], + "description": "Specifies how the `GitLens Results` view will display files\n `auto` - automatically switches between displaying files as a `tree` or `list` based on the `gitlens.gitExplorer.files.threshold` setting and the number of files at each nesting level\n `list` - displays files as a list\n `tree` - displays files as a tree", + "scope": "window" + }, + "gitlens.resultsExplorer.files.compact": { + "type": "boolean", + "default": true, + "description": "Specifies whether or not to compact (flatten) unnecessary file nesting in the `GitLens Results` view\nOnly applies when displaying files as a `tree` or `auto`", + "scope": "window" + }, + "gitlens.resultsExplorer.files.threshold": { + "type": "number", + "default": 5, + "description": "Specifies when to switch between displaying files as a `tree` or `list` based on the number of files in a nesting level in the `GitLens Results` view\nOnly applies when displaying files as `auto`", + "scope": "window" + }, + "gitlens.resultsExplorer.gravatars": { + "type": "boolean", + "default": true, + "description": "Specifies whether or not to show gravatar images instead of commit (or status) icons in the `GitLens Results` view", + "scope": "window" + }, + "gitlens.resultsExplorer.showTrackingBranch": { + "type": "boolean", + "default": true, + "description": "Specifies whether or not to show the tracking branch when displaying local branches in the `GitLens Results` view", + "scope": "window" + }, + "gitlens.resultsExplorer.stashFormat": { + "type": "string", + "default": "${message}", + "description": "Specifies the format of stashed changes in the `GitLens Results` view\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", + "scope": "window" + }, + "gitlens.resultsExplorer.stashFileFormat": { + "type": "string", + "default": "${filePath}", + "description": "Specifies the format of a stashed file in the `GitLens Results` view\nAvailable tokens\n ${directory} - directory name\n ${file} - file name\n ${filePath} - formatted file name and path\n ${path} - full file path", + "scope": "window" + }, + "gitlens.resultsExplorer.statusFileFormat": { + "type": "string", + "default": "${working}${filePath}", + "description": "Specifies the format of the status of a working or committed file in the `GitLens Results` view\nAvailable tokens\n ${directory} - directory name\n ${file} - file name\n ${filePath} - formatted file name and path\n ${path} - full file path\n ${working} - optional indicator if the file is uncommitted", + "scope": "window" + }, "gitlens.statusBar.enabled": { "type": "boolean", "default": true, @@ -1272,151 +1337,222 @@ "category": "GitLens" }, { - "command": "gitlens.gitExplorer.setAutoRefreshToOn", - "title": "Enable Automatic Refresh", + "command": "gitlens.explorers.openChanges", + "title": "Open Changes", "category": "GitLens" }, { - "command": "gitlens.gitExplorer.setAutoRefreshToOff", - "title": "Disable Automatic Refresh", + "command": "gitlens.explorers.openChangesWithWorking", + "title": "Open Changes with Working Tree", "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.explorers.openFile", + "title": "Open File", + "category": "GitLens" }, { - "command": "gitlens.gitExplorer.refreshNode", - "title": "Refresh", + "command": "gitlens.explorers.openFileRevision", + "title": "Open Revision", "category": "GitLens" }, { - "command": "gitlens.gitExplorer.setFilesLayoutToAuto", - "title": "Show Files in Automatic View", + "command": "gitlens.explorers.openFileRevisionInRemote", + "title": "Open Revision in Remote", "category": "GitLens" }, { - "command": "gitlens.gitExplorer.setFilesLayoutToList", - "title": "Show Files in List View", + "command": "gitlens.explorers.openChangedFiles", + "title": "Open Files", "category": "GitLens" }, { - "command": "gitlens.gitExplorer.setFilesLayoutToTree", - "title": "Show Files in Tree View", + "command": "gitlens.explorers.openChangedFileChanges", + "title": "Open All Changes", "category": "GitLens" }, { - "command": "gitlens.gitExplorer.switchToHistoryView", - "title": "Switch to History View", - "category": "GitLens", - "icon": { - "dark": "images/dark/icon-history.svg", - "light": "images/light/icon-history.svg" - } + "command": "gitlens.explorers.openChangedFileChangesWithWorking", + "title": "Open All Changes with Working Tree", + "category": "GitLens" }, { - "command": "gitlens.gitExplorer.switchToRepositoryView", - "title": "Switch to Repository View", - "category": "GitLens", - "icon": { - "dark": "images/dark/icon-repo.svg", - "light": "images/light/icon-repo.svg" - } + "command": "gitlens.explorers.openChangedFileRevisions", + "title": "Open Revisions", + "category": "GitLens" }, { - "command": "gitlens.gitExplorer.openChanges", - "title": "Open Changes", + "command": "gitlens.explorers.applyChanges", + "title": "Apply Changes", "category": "GitLens" }, { - "command": "gitlens.gitExplorer.openChangesWithWorking", - "title": "Open Changes with Working Tree", + "command": "gitlens.explorers.compareWithSelected", + "title": "Compare with Selected", "category": "GitLens" }, { - "command": "gitlens.gitExplorer.openFile", - "title": "Open File", + "command": "gitlens.explorers.selectForCompare", + "title": "Select for Compare", "category": "GitLens" }, { - "command": "gitlens.gitExplorer.openFileRevision", - "title": "Open Revision", + "command": "gitlens.explorers.terminalCheckoutBranch", + "title": "Checkout Branch (via Terminal)", "category": "GitLens" }, { - "command": "gitlens.gitExplorer.openFileRevisionInRemote", - "title": "Open Revision in Remote", + "command": "gitlens.explorers.terminalCreateBranch", + "title": "Create Branch (via Terminal)...", "category": "GitLens" }, { - "command": "gitlens.gitExplorer.openChangedFiles", - "title": "Open Files", + "command": "gitlens.explorers.terminalDeleteBranch", + "title": "Delete Branch (via Terminal)", "category": "GitLens" }, { - "command": "gitlens.gitExplorer.openChangedFileChanges", - "title": "Open All Changes", + "command": "gitlens.explorers.terminalRebaseBranchToRemote", + "title": "Rebase Branch to Remote (via Terminal)", "category": "GitLens" }, { - "command": "gitlens.gitExplorer.openChangedFileChangesWithWorking", - "title": "Open All Changes with Working Tree", + "command": "gitlens.explorers.terminalSquashBranchIntoCommit", + "title": "Squash Branch into Commit (via Terminal)", "category": "GitLens" }, { - "command": "gitlens.gitExplorer.openChangedFileRevisions", - "title": "Open Revisions", + "command": "gitlens.explorers.terminalRebaseCommit", + "title": "Rebase Commit (via Terminal)", "category": "GitLens" }, { - "command": "gitlens.gitExplorer.applyChanges", - "title": "Apply Changes", + "command": "gitlens.explorers.terminalResetCommit", + "title": "Reset Commit (via Terminal)", "category": "GitLens" }, { - "command": "gitlens.gitExplorer.terminalCheckoutBranch", - "title": "Checkout Branch (via Terminal)", + "command": "gitlens.explorers.terminalRemoveRemote", + "title": "Remove Remote (via Terminal)", "category": "GitLens" }, { - "command": "gitlens.gitExplorer.terminalCreateBranch", - "title": "Create Branch (via Terminal)...", + "command": "gitlens.gitExplorer.refresh", + "title": "Refresh", + "category": "GitLens", + "icon": { + "dark": "images/dark/icon-refresh.svg", + "light": "images/light/icon-refresh.svg" + } + }, + { + "command": "gitlens.gitExplorer.refreshNode", + "title": "Refresh", "category": "GitLens" }, { - "command": "gitlens.gitExplorer.terminalDeleteBranch", - "title": "Delete Branch (via Terminal)", + "command": "gitlens.gitExplorer.setFilesLayoutToAuto", + "title": "Show Files in Automatic View", "category": "GitLens" }, { - "command": "gitlens.gitExplorer.terminalRebaseBranchToRemote", - "title": "Rebase Branch to Remote (via Terminal)", + "command": "gitlens.gitExplorer.setFilesLayoutToList", + "title": "Show Files in List View", "category": "GitLens" }, { - "command": "gitlens.gitExplorer.terminalSquashBranchIntoCommit", - "title": "Squash Branch into Commit (via Terminal)", + "command": "gitlens.gitExplorer.setFilesLayoutToTree", + "title": "Show Files in Tree View", "category": "GitLens" }, { - "command": "gitlens.gitExplorer.terminalRebaseCommit", - "title": "Rebase Commit (via Terminal)", + "command": "gitlens.gitExplorer.setAutoRefreshToOn", + "title": "Enable Automatic Refresh", "category": "GitLens" }, { - "command": "gitlens.gitExplorer.terminalResetCommit", - "title": "Reset Commit (via Terminal)", + "command": "gitlens.gitExplorer.setAutoRefreshToOff", + "title": "Disable Automatic Refresh", "category": "GitLens" }, { - "command": "gitlens.gitExplorer.terminalRemoveRemote", - "title": "Remove Remote (via Terminal)", + "command": "gitlens.gitExplorer.switchToHistoryView", + "title": "Switch to History View", + "category": "GitLens", + "icon": { + "dark": "images/dark/icon-history.svg", + "light": "images/light/icon-history.svg" + } + }, + { + "command": "gitlens.gitExplorer.switchToRepositoryView", + "title": "Switch to Repository View", + "category": "GitLens", + "icon": { + "dark": "images/dark/icon-repo.svg", + "light": "images/light/icon-repo.svg" + } + }, + { + "command": "gitlens.resultsExplorer.clearResultsNode", + "title": "Clear Results", "category": "GitLens" + }, + { + "command": "gitlens.resultsExplorer.close", + "title": "Close", + "category": "GitLens", + "icon": { + "dark": "images/dark/icon-close.svg", + "light": "images/light/icon-close.svg" + } + }, + { + "command": "gitlens.resultsExplorer.refresh", + "title": "Refresh", + "category": "GitLens", + "icon": { + "dark": "images/dark/icon-refresh.svg", + "light": "images/light/icon-refresh.svg" + } + }, + { + "command": "gitlens.resultsExplorer.refreshNode", + "title": "Refresh", + "category": "GitLens" + }, + { + "command": "gitlens.resultsExplorer.setFilesLayoutToAuto", + "title": "Show Files in Automatic View", + "category": "GitLens" + }, + { + "command": "gitlens.resultsExplorer.setFilesLayoutToList", + "title": "Show Files in List View", + "category": "GitLens" + }, + { + "command": "gitlens.resultsExplorer.setFilesLayoutToTree", + "title": "Show Files in Tree View", + "category": "GitLens" + }, + { + "command": "gitlens.resultsExplorer.setKeepResultsToOn", + "title": "Keep Results", + "category": "GitLens", + "icon": { + "dark": "images/dark/icon-lock.svg", + "light": "images/light/icon-lock.svg" + } + }, + { + "command": "gitlens.resultsExplorer.setKeepResultsToOff", + "title": "Keep Results", + "category": "GitLens", + "icon": { + "dark": "images/dark/icon-locked.svg", + "light": "images/light/icon-locked.svg" + } } ], "menus": { @@ -1574,111 +1710,155 @@ "when": "gitlens:enabled" }, { - "command": "gitlens.gitExplorer.setAutoRefreshToOn", + "command": "gitlens.explorers.openChanges", "when": "false" }, { - "command": "gitlens.gitExplorer.setAutoRefreshToOff", + "command": "gitlens.explorers.openChangesWithWorking", "when": "false" }, { - "command": "gitlens.gitExplorer.setFilesLayoutToAuto", + "command": "gitlens.explorers.openFile", "when": "false" }, { - "command": "gitlens.gitExplorer.setFilesLayoutToList", + "command": "gitlens.explorers.openFileRevision", "when": "false" }, { - "command": "gitlens.gitExplorer.setFilesLayoutToTree", + "command": "gitlens.explorers.openFileRevisionInRemote", "when": "false" }, { - "command": "gitlens.gitExplorer.refresh", + "command": "gitlens.explorers.openChangedFiles", "when": "false" }, { - "command": "gitlens.gitExplorer.refreshNode", + "command": "gitlens.explorers.openChangedFileChanges", "when": "false" }, { - "command": "gitlens.gitExplorer.switchToHistoryView", - "when": "gitlens:gitExplorer:view == repository" + "command": "gitlens.explorers.openChangedFileChangesWithWorking", + "when": "false" }, { - "command": "gitlens.gitExplorer.switchToRepositoryView", - "when": "gitlens:gitExplorer:view == history" + "command": "gitlens.explorers.openChangedFileRevisions", + "when": "false" + }, + { + "command": "gitlens.explorers.applyChanges", + "when": "false" + }, + { + "command": "gitlens.explorers.compareWithSelected", + "when": "false" }, { - "command": "gitlens.gitExplorer.openChanges", + "command": "gitlens.explorers.selectForCompare", "when": "false" }, { - "command": "gitlens.gitExplorer.openChangesWithWorking", + "command": "gitlens.explorers.terminalCheckoutBranch", "when": "false" }, { - "command": "gitlens.gitExplorer.openFile", + "command": "gitlens.explorers.terminalCreateBranch", "when": "false" }, { - "command": "gitlens.gitExplorer.openFileRevision", + "command": "gitlens.explorers.terminalDeleteBranch", "when": "false" }, { - "command": "gitlens.gitExplorer.openFileRevisionInRemote", + "command": "gitlens.explorers.terminalRebaseBranchToRemote", + "when": "false" + }, + { + "command": "gitlens.explorers.terminalSquashBranchIntoCommit", + "when": "false" + }, + { + "command": "gitlens.explorers.terminalRebaseCommit", + "when": "false" + }, + { + "command": "gitlens.explorers.terminalResetCommit", + "when": "false" + }, + { + "command": "gitlens.explorers.terminalRemoveRemote", + "when": "false" + }, + { + "command": "gitlens.gitExplorer.refresh", + "when": "false" + }, + { + "command": "gitlens.gitExplorer.refreshNode", + "when": "false" + }, + { + "command": "gitlens.gitExplorer.setFilesLayoutToAuto", "when": "false" }, { - "command": "gitlens.gitExplorer.openChangedFiles", + "command": "gitlens.gitExplorer.setFilesLayoutToList", "when": "false" }, { - "command": "gitlens.gitExplorer.openChangedFileChanges", + "command": "gitlens.gitExplorer.setFilesLayoutToTree", "when": "false" }, { - "command": "gitlens.gitExplorer.openChangedFileChangesWithWorking", + "command": "gitlens.gitExplorer.setAutoRefreshToOn", "when": "false" }, { - "command": "gitlens.gitExplorer.openChangedFileRevisions", + "command": "gitlens.gitExplorer.setAutoRefreshToOff", "when": "false" }, { - "command": "gitlens.gitExplorer.applyChanges", + "command": "gitlens.gitExplorer.switchToHistoryView", + "when": "gitlens:gitExplorer:view == repository" + }, + { + "command": "gitlens.gitExplorer.switchToRepositoryView", + "when": "gitlens:gitExplorer:view == history" + }, + { + "command": "gitlens.resultsExplorer.clearResultsNode", "when": "false" }, { - "command": "gitlens.gitExplorer.terminalCheckoutBranch", + "command": "gitlens.resultsExplorer.close", "when": "false" }, { - "command": "gitlens.gitExplorer.terminalCreateBranch", + "command": "gitlens.resultsExplorer.refresh", "when": "false" }, { - "command": "gitlens.gitExplorer.terminalDeleteBranch", + "command": "gitlens.resultsExplorer.refreshNode", "when": "false" }, { - "command": "gitlens.gitExplorer.terminalRebaseBranchToRemote", + "command": "gitlens.resultsExplorer.setFilesLayoutToAuto", "when": "false" }, { - "command": "gitlens.gitExplorer.terminalSquashBranchIntoCommit", + "command": "gitlens.resultsExplorer.setFilesLayoutToList", "when": "false" }, { - "command": "gitlens.gitExplorer.terminalRebaseCommit", + "command": "gitlens.resultsExplorer.setFilesLayoutToTree", "when": "false" }, { - "command": "gitlens.gitExplorer.terminalResetCommit", + "command": "gitlens.resultsExplorer.setKeepResultsToOn", "when": "false" }, { - "command": "gitlens.gitExplorer.terminalRemoveRemote", + "command": "gitlens.resultsExplorer.setKeepResultsToOff", "when": "false" } ], @@ -1940,403 +2120,538 @@ "command": "gitlens.gitExplorer.setAutoRefreshToOff", "when": "view == gitlens.gitExplorer && config.gitlens.gitExplorer.autoRefresh && gitlens:gitExplorer:autoRefresh", "group": "2_gitlens" + }, + { + "command": "gitlens.showCommitSearch", + "when": "view == gitlens.resultsExplorer", + "group": "navigation@1" + }, + { + "command": "gitlens.resultsExplorer.setKeepResultsToOn", + "when": "view == gitlens.resultsExplorer && !gitlens:resultsExplorer:keepResults", + "group": "navigation@2" + }, + { + "command": "gitlens.resultsExplorer.setKeepResultsToOff", + "when": "view == gitlens.resultsExplorer && gitlens:resultsExplorer:keepResults", + "group": "navigation@2" + }, + { + "command": "gitlens.resultsExplorer.refresh", + "when": "view == gitlens.resultsExplorer", + "group": "navigation@3" + }, + { + "command": "gitlens.resultsExplorer.close", + "when": "view == gitlens.resultsExplorer", + "group": "navigation@9" + }, + { + "command": "gitlens.resultsExplorer.setFilesLayoutToAuto", + "when": "view == gitlens.resultsExplorer", + "group": "1_gitlens" + }, + { + "command": "gitlens.resultsExplorer.setFilesLayoutToList", + "when": "view == gitlens.resultsExplorer", + "group": "1_gitlens" + }, + { + "command": "gitlens.resultsExplorer.setFilesLayoutToTree", + "when": "view == gitlens.resultsExplorer", + "group": "1_gitlens" } ], "view/item/context": [ { "command": "gitlens.openBranchesInRemote", - "when": "view == gitlens.gitExplorer && viewItem == gitlens:branches:remotes", + "when": "viewItem == gitlens:branches:remotes", "group": "1_gitlens@1" }, { - "command": "gitlens.gitExplorer.terminalCheckoutBranch", - "when": "view == gitlens.gitExplorer && viewItem == gitlens:branch-history", + "command": "gitlens.explorers.compareWithSelected", + "when": "viewItem == gitlens:branch && gitlens:explorers:canCompare", + "group": "7_gitlens@1" + }, + { + "command": "gitlens.explorers.selectForCompare", + "when": "viewItem == gitlens:branch", + "group": "7_gitlens@2" + }, + { + "command": "gitlens.explorers.terminalCheckoutBranch", + "when": "viewItem == gitlens:branch", "group": "8_gitlens@1" }, { - "command": "gitlens.gitExplorer.terminalCreateBranch", - "when": "view == gitlens.gitExplorer && viewItem == gitlens:branch-history", + "command": "gitlens.explorers.terminalCreateBranch", + "when": "viewItem == gitlens:branch", "group": "8_gitlens@2" }, { - "command": "gitlens.gitExplorer.terminalDeleteBranch", - "when": "view == gitlens.gitExplorer && viewItem == gitlens:branch-history", + "command": "gitlens.explorers.terminalDeleteBranch", + "when": "viewItem == gitlens:branch", "group": "8_gitlens@3" }, { - "command": "gitlens.gitExplorer.terminalSquashBranchIntoCommit", - "when": "view == gitlens.gitExplorer && viewItem == gitlens:branch-history", + "command": "gitlens.explorers.terminalSquashBranchIntoCommit", + "when": "viewItem == gitlens:branch", "group": "8_gitlens@4" }, { "command": "gitlens.openBranchInRemote", - "when": "view == gitlens.gitExplorer && viewItem == gitlens:branch-history:tracking", + "when": "viewItem == gitlens:branch:tracking", "group": "1_gitlens@1" }, { - "command": "gitlens.gitExplorer.terminalCheckoutBranch", - "when": "view == gitlens.gitExplorer && viewItem == gitlens:branch-history:tracking", + "command": "gitlens.explorers.compareWithSelected", + "when": "viewItem == gitlens:branch:tracking && gitlens:explorers:canCompare", + "group": "7_gitlens@1" + }, + { + "command": "gitlens.explorers.selectForCompare", + "when": "viewItem == gitlens:branch:tracking", + "group": "7_gitlens@2" + }, + { + "command": "gitlens.explorers.terminalCheckoutBranch", + "when": "viewItem == gitlens:branch:tracking", "group": "8_gitlens@1" }, { - "command": "gitlens.gitExplorer.terminalCreateBranch", - "when": "view == gitlens.gitExplorer && viewItem == gitlens:branch-history:tracking", + "command": "gitlens.explorers.terminalCreateBranch", + "when": "viewItem == gitlens:branch:tracking", "group": "8_gitlens@2" }, { - "command": "gitlens.gitExplorer.terminalDeleteBranch", - "when": "view == gitlens.gitExplorer && viewItem == gitlens:branch-history:tracking", + "command": "gitlens.explorers.terminalDeleteBranch", + "when": "viewItem == gitlens:branch:tracking", "group": "8_gitlens@3" }, { - "command": "gitlens.gitExplorer.terminalSquashBranchIntoCommit", - "when": "view == gitlens.gitExplorer && viewItem == gitlens:branch-history:tracking", + "command": "gitlens.explorers.terminalSquashBranchIntoCommit", + "when": "viewItem == gitlens:branch:tracking", "group": "8_gitlens@4" }, { - "command": "gitlens.gitExplorer.terminalCreateBranch", - "when": "view == gitlens.gitExplorer && viewItem == gitlens:current-branch-history", + "command": "gitlens.explorers.compareWithSelected", + "when": "viewItem == gitlens:current-branch && gitlens:explorers:canCompare", + "group": "7_gitlens@1" + }, + { + "command": "gitlens.explorers.selectForCompare", + "when": "viewItem == gitlens:current-branch", + "group": "7_gitlens@2" + }, + { + "command": "gitlens.explorers.terminalCreateBranch", + "when": "viewItem == gitlens:current-branch", "group": "8_gitlens@1" }, { "command": "gitlens.openBranchInRemote", - "when": "view == gitlens.gitExplorer && viewItem == gitlens:current-branch-history:tracking", + "when": "viewItem == gitlens:current-branch:tracking", "group": "1_gitlens@1" }, { - "command": "gitlens.gitExplorer.terminalCreateBranch", - "when": "view == gitlens.gitExplorer && viewItem == gitlens:current-branch-history:tracking", + "command": "gitlens.explorers.compareWithSelected", + "when": "viewItem == gitlens:current-branch:tracking && gitlens:explorers:canCompare", + "group": "7_gitlens@1" + }, + { + "command": "gitlens.explorers.selectForCompare", + "when": "viewItem == gitlens:current-branch:tracking", + "group": "7_gitlens@2" + }, + { + "command": "gitlens.explorers.terminalCreateBranch", + "when": "viewItem == gitlens:current-branch:tracking", "group": "8_gitlens@1" }, { - "command": "gitlens.gitExplorer.terminalRebaseBranchToRemote", - "when": "view == gitlens.gitExplorer && viewItem == gitlens:current-branch-history:tracking", + "command": "gitlens.explorers.terminalRebaseBranchToRemote", + "when": "viewItem == gitlens:current-branch:tracking", "group": "8_gitlens@2" }, { "command": "gitlens.openBranchInRemote", - "when": "view == gitlens.gitExplorer && viewItem == gitlens:remote-branch-history", + "when": "viewItem == gitlens:remote-branch", "group": "1_gitlens@1" }, { - "command": "gitlens.gitExplorer.terminalCheckoutBranch", - "when": "view == gitlens.gitExplorer && viewItem == gitlens:remote-branch-history", + "command": "gitlens.explorers.compareWithSelected", + "when": "viewItem == gitlens:remote-branch && gitlens:explorers:canCompare", + "group": "7_gitlens@1" + }, + { + "command": "gitlens.explorers.selectForCompare", + "when": "viewItem == gitlens:remote-branch", + "group": "7_gitlens@2" + }, + { + "command": "gitlens.explorers.terminalCheckoutBranch", + "when": "viewItem == gitlens:remote-branch", "group": "8_gitlens@1" }, { - "command": "gitlens.gitExplorer.terminalCreateBranch", - "when": "view == gitlens.gitExplorer && viewItem == gitlens:remote-branch-history", + "command": "gitlens.explorers.terminalCreateBranch", + "when": "viewItem == gitlens:remote-branch", "group": "8_gitlens@2" }, { - "command": "gitlens.gitExplorer.terminalDeleteBranch", - "when": "view == gitlens.gitExplorer && viewItem == gitlens:remote-branch-history", + "command": "gitlens.explorers.terminalDeleteBranch", + "when": "viewItem == gitlens:remote-branch", "group": "8_gitlens@3" }, { - "command": "gitlens.gitExplorer.terminalSquashBranchIntoCommit", - "when": "view == gitlens.gitExplorer && viewItem == gitlens:remote-branch-history", + "command": "gitlens.explorers.terminalSquashBranchIntoCommit", + "when": "viewItem == gitlens:remote-branch", "group": "8_gitlens@4" }, { "command": "gitlens.openCommitInRemote", - "when": "gitlens:hasRemotes && view == gitlens.gitExplorer && viewItem == gitlens:commit", + "when": "viewItem == gitlens:commit && gitlens:hasRemotes", "group": "1_gitlens@1" }, { - "command": "gitlens.gitExplorer.openChangedFileChanges", - "when": "view == gitlens.gitExplorer && viewItem == gitlens:commit", + "command": "gitlens.explorers.openChangedFileChanges", + "when": "viewItem == gitlens:commit", "group": "2_gitlens@1" }, { - "command": "gitlens.gitExplorer.openChangedFileChangesWithWorking", - "when": "view == gitlens.gitExplorer && viewItem == gitlens:commit", + "command": "gitlens.explorers.openChangedFileChangesWithWorking", + "when": "viewItem == gitlens:commit", "group": "2_gitlens@1" }, { - "command": "gitlens.gitExplorer.openChangedFiles", - "when": "view == gitlens.gitExplorer && viewItem == gitlens:commit", + "command": "gitlens.explorers.openChangedFiles", + "when": "viewItem == gitlens:commit", "group": "3_gitlens@1" }, { - "command": "gitlens.gitExplorer.openChangedFileRevisions", - "when": "view == gitlens.gitExplorer && viewItem == gitlens:commit", + "command": "gitlens.explorers.openChangedFileRevisions", + "when": "viewItem == gitlens:commit", "group": "3_gitlens@2" }, { "command": "gitlens.copyShaToClipboard", - "when": "view == gitlens.gitExplorer && viewItem == gitlens:commit", + "when": "viewItem == gitlens:commit", "group": "4_gitlens@1" }, { "command": "gitlens.copyMessageToClipboard", - "when": "view == gitlens.gitExplorer && viewItem == gitlens:commit", + "when": "viewItem == gitlens:commit", "group": "4_gitlens@2" }, { "command": "gitlens.showQuickCommitDetails", - "when": "view == gitlens.gitExplorer && viewItem == gitlens:commit", + "when": "viewItem == gitlens:commit", "group": "5_gitlens@1" }, { + "command": "gitlens.explorers.compareWithSelected", + "when": "viewItem == gitlens:commit && gitlens:explorers:canCompare", + "group": "7_gitlens@1" + }, + { + "command": "gitlens.explorers.selectForCompare", + "when": "viewItem == gitlens:commit", + "group": "7_gitlens@2" + }, + { "command": "gitlens.openCommitInRemote", - "when": "gitlens:hasRemotes && view == gitlens.gitExplorer && viewItem == gitlens:commit:current", + "when": "viewItem == gitlens:commit:current && gitlens:hasRemotes", "group": "1_gitlens@1" }, { - "command": "gitlens.gitExplorer.openChangedFileChanges", - "when": "view == gitlens.gitExplorer && viewItem == gitlens:commit:current", + "command": "gitlens.explorers.openChangedFileChanges", + "when": "viewItem == gitlens:commit:current", "group": "2_gitlens@1" }, { - "command": "gitlens.gitExplorer.openChangedFileChangesWithWorking", - "when": "view == gitlens.gitExplorer && viewItem == gitlens:commit:current", + "command": "gitlens.explorers.openChangedFileChangesWithWorking", + "when": "viewItem == gitlens:commit:current", "group": "2_gitlens@1" }, { - "command": "gitlens.gitExplorer.openChangedFiles", - "when": "view == gitlens.gitExplorer && viewItem == gitlens:commit:current", + "command": "gitlens.explorers.openChangedFiles", + "when": "viewItem == gitlens:commit:current", "group": "3_gitlens@1" }, { - "command": "gitlens.gitExplorer.openChangedFileRevisions", - "when": "view == gitlens.gitExplorer && viewItem == gitlens:commit:current", + "command": "gitlens.explorers.openChangedFileRevisions", + "when": "viewItem == gitlens:commit:current", "group": "3_gitlens@2" }, { "command": "gitlens.copyShaToClipboard", - "when": "view == gitlens.gitExplorer && viewItem == gitlens:commit:current", + "when": "viewItem == gitlens:commit:current", "group": "4_gitlens@1" }, { "command": "gitlens.copyMessageToClipboard", - "when": "view == gitlens.gitExplorer && viewItem == gitlens:commit:current", + "when": "viewItem == gitlens:commit:current", "group": "4_gitlens@2" }, { "command": "gitlens.showQuickCommitDetails", - "when": "view == gitlens.gitExplorer && viewItem == gitlens:commit:current", + "when": "viewItem == gitlens:commit:current", "group": "5_gitlens@1" }, { - "command": "gitlens.gitExplorer.terminalRebaseCommit", - "when": "view == gitlens.gitExplorer && viewItem == gitlens:commit:current", + "command": "gitlens.explorers.compareWithSelected", + "when": "viewItem == gitlens:commit:current && gitlens:explorers:canCompare", + "group": "7_gitlens@1" + }, + { + "command": "gitlens.explorers.selectForCompare", + "when": "viewItem == gitlens:commit:current", + "group": "7_gitlens@2" + }, + { + "command": "gitlens.explorers.terminalRebaseCommit", + "when": "viewItem == gitlens:commit:current", "group": "8_gitlens@1" }, { - "command": "gitlens.gitExplorer.terminalResetCommit", - "when": "view == gitlens.gitExplorer && viewItem == gitlens:commit:current", + "command": "gitlens.explorers.terminalResetCommit", + "when": "viewItem == gitlens:commit:current", "group": "8_gitlens@2" }, { - "command": "gitlens.gitExplorer.openChanges", - "when": "view == gitlens.gitExplorer && viewItem == gitlens:commit-file", + "command": "gitlens.explorers.openChanges", + "when": "viewItem == gitlens:commit-file", "group": "1_gitlens@1" }, { - "command": "gitlens.gitExplorer.openChangesWithWorking", - "when": "view == gitlens.gitExplorer && viewItem == gitlens:commit-file", + "command": "gitlens.explorers.openChangesWithWorking", + "when": "viewItem == gitlens:commit-file", "group": "1_gitlens@2" }, { - "command": "gitlens.gitExplorer.openFile", - "when": "view == gitlens.gitExplorer && viewItem == gitlens:commit-file", + "command": "gitlens.explorers.openFile", + "when": "viewItem == gitlens:commit-file", "group": "2_gitlens@1" }, { - "command": "gitlens.gitExplorer.openFileRevision", - "when": "view == gitlens.gitExplorer && viewItem == gitlens:commit-file", + "command": "gitlens.explorers.openFileRevision", + "when": "viewItem == gitlens:commit-file", "group": "2_gitlens@2" }, { "command": "gitlens.openFileInRemote", - "when": "gitlens:hasRemotes && view == gitlens.gitExplorer && viewItem == gitlens:commit-file", + "when": "viewItem == gitlens:commit-file && gitlens:hasRemotes", "group": "3_gitlens@1" }, { - "command": "gitlens.gitExplorer.openFileRevisionInRemote", - "when": "gitlens:hasRemotes && view == gitlens.gitExplorer && viewItem == gitlens:commit-file", + "command": "gitlens.explorers.openFileRevisionInRemote", + "when": "viewItem == gitlens:commit-file && gitlens:hasRemotes", "group": "3_gitlens@2" }, { - "command": "gitlens.gitExplorer.applyChanges", - "when": "view == gitlens.gitExplorer && viewItem == gitlens:commit-file", + "command": "gitlens.explorers.applyChanges", + "when": "viewItem == gitlens:commit-file", "group": "4_gitlens@1" }, { "command": "gitlens.showQuickFileHistory", - "when": "view == gitlens.gitExplorer && viewItem == gitlens:commit-file && gitlens:gitExplorer:view == repository", + "when": "viewItem == gitlens:commit-file && gitlens:gitExplorer:view == repository", "group": "5_gitlens@1" }, { "command": "gitlens.showQuickCommitFileDetails", - "when": "view == gitlens.gitExplorer && viewItem == gitlens:commit-file", + "when": "viewItem == gitlens:commit-file", "group": "5_gitlens@2" }, { - "command": "gitlens.gitExplorer.openFile", - "when": "view == gitlens.gitExplorer && viewItem == gitlens:file-history", + "command": "gitlens.explorers.openFile", + "when": "viewItem == gitlens:file-history", "group": "1_gitlens@1" }, { "command": "gitlens.openFileInRemote", - "when": "gitlens:hasRemotes && view == gitlens.gitExplorer && viewItem == gitlens:file-history", + "when": "viewItem == gitlens:file-history && gitlens:hasRemotes", "group": "1_gitlens@2" }, { "command": "gitlens.openBranchesInRemote", - "when": "view == gitlens.gitExplorer && viewItem == gitlens:remote", + "when": "viewItem == gitlens:remote", "group": "1_gitlens@1" }, { "command": "gitlens.openRepoInRemote", - "when": "view == gitlens.gitExplorer && viewItem == gitlens:remote", + "when": "viewItem == gitlens:remote", "group": "1_gitlens@2" }, { - "command": "gitlens.gitExplorer.terminalRemoveRemote", - "when": "view == gitlens.gitExplorer && viewItem == gitlens:remote", + "command": "gitlens.explorers.terminalRemoveRemote", + "when": "viewItem == gitlens:remote", "group": "8_gitlens@1" }, { "command": "gitlens.openRepoInRemote", - "when": "gitlens:hasRemotes && view == gitlens.gitExplorer && viewItem == gitlens:repository", + "when": "viewItem == gitlens:repository && gitlens:hasRemotes", + "group": "1_gitlens@1" + }, + { + "command": "gitlens.resultsExplorer.clearResultsNode", + "when": "viewItem == gitlens:comparison-results", + "group": "1_gitlens@1" + }, + { + "command": "gitlens.resultsExplorer.clearResultsNode", + "when": "viewItem == gitlens:search-results", "group": "1_gitlens@1" }, { "command": "gitlens.stashSave", - "when": "view == gitlens.gitExplorer && viewItem == gitlens:stashes", + "when": "viewItem == gitlens:stashes", "group": "1_gitlens@1" }, { "command": "gitlens.stashApply", - "when": "view == gitlens.gitExplorer && viewItem == gitlens:stash", + "when": "viewItem == gitlens:stash", "group": "1_gitlens@1" }, { "command": "gitlens.stashDelete", - "when": "view == gitlens.gitExplorer && viewItem == gitlens:stash", + "when": "viewItem == gitlens:stash", "group": "1_gitlens@2" }, { - "command": "gitlens.gitExplorer.openChangedFileChanges", - "when": "view == gitlens.gitExplorer && viewItem == gitlens:stash", + "command": "gitlens.explorers.openChangedFileChanges", + "when": "viewItem == gitlens:stash", "group": "2_gitlens@1" }, { - "command": "gitlens.gitExplorer.openChangedFileChangesWithWorking", - "when": "view == gitlens.gitExplorer && viewItem == gitlens:stash", + "command": "gitlens.explorers.openChangedFileChangesWithWorking", + "when": "viewItem == gitlens:stash", "group": "2_gitlens@1" }, { - "command": "gitlens.gitExplorer.openChangedFiles", - "when": "view == gitlens.gitExplorer && viewItem == gitlens:stash", + "command": "gitlens.explorers.openChangedFiles", + "when": "viewItem == gitlens:stash", "group": "3_gitlens@1" }, { - "command": "gitlens.gitExplorer.openChangedFileRevisions", - "when": "view == gitlens.gitExplorer && viewItem == gitlens:stash", + "command": "gitlens.explorers.openChangedFileRevisions", + "when": "viewItem == gitlens:stash", "group": "3_gitlens@2" }, { "command": "gitlens.copyMessageToClipboard", - "when": "view == gitlens.gitExplorer && viewItem == gitlens:stash", + "when": "viewItem == gitlens:stash", "group": "4_gitlens@1" }, { - "command": "gitlens.gitExplorer.applyChanges", - "when": "view == gitlens.gitExplorer && viewItem == gitlens:stash-file", + "command": "gitlens.explorers.compareWithSelected", + "when": "viewItem == gitlens:stash && gitlens:explorers:canCompare", + "group": "7_gitlens@1" + }, + { + "command": "gitlens.explorers.selectForCompare", + "when": "viewItem == gitlens:stash", + "group": "7_gitlens@2" + }, + { + "command": "gitlens.explorers.applyChanges", + "when": "viewItem == gitlens:stash-file", "group": "1_gitlens@1" }, { - "command": "gitlens.gitExplorer.openChanges", - "when": "view == gitlens.gitExplorer && viewItem == gitlens:stash-file", + "command": "gitlens.explorers.openChanges", + "when": "viewItem == gitlens:stash-file", "group": "2_gitlens@1" }, { - "command": "gitlens.gitExplorer.openChangesWithWorking", - "when": "view == gitlens.gitExplorer && viewItem == gitlens:stash-file", + "command": "gitlens.explorers.openChangesWithWorking", + "when": "viewItem == gitlens:stash-file", "group": "2_gitlens@2" }, { - "command": "gitlens.gitExplorer.openFile", - "when": "view == gitlens.gitExplorer && viewItem == gitlens:stash-file", + "command": "gitlens.explorers.openFile", + "when": "viewItem == gitlens:stash-file", "group": "3_gitlens@1" }, { - "command": "gitlens.gitExplorer.openFileRevision", - "when": "view == gitlens.gitExplorer && viewItem == gitlens:stash-file", + "command": "gitlens.explorers.openFileRevision", + "when": "viewItem == gitlens:stash-file", "group": "3_gitlens@2" }, { "command": "gitlens.openFileInRemote", - "when": "gitlens:hasRemotes && view == gitlens.gitExplorer && viewItem == gitlens:stash-file", + "when": "viewItem == gitlens:stash-file && gitlens:hasRemotes", "group": "4_gitlens@1" }, { "command": "gitlens.showQuickFileHistory", - "when": "view == gitlens.gitExplorer && viewItem == gitlens:stash-file", + "when": "viewItem == gitlens:stash-file", "group": "5_gitlens@1" }, { "command": "gitlens.openRepoInRemote", - "when": "gitlens:hasRemotes && view == gitlens.gitExplorer && viewItem == gitlens:status", + "when": "viewItem == gitlens:status && gitlens:hasRemotes", "group": "1_gitlens@1" }, { - "command": "gitlens.gitExplorer.openChanges", - "when": "view == gitlens.gitExplorer && viewItem == gitlens:status-file", + "command": "gitlens.explorers.openChanges", + "when": "viewItem == gitlens:status-file", "group": "1_gitlens@1" }, { - "command": "gitlens.gitExplorer.openChangesWithWorking", - "when": "view == gitlens.gitExplorer && viewItem == gitlens:status-file", + "command": "gitlens.explorers.openChangesWithWorking", + "when": "viewItem == gitlens:status-file", "group": "1_gitlens@2" }, { - "command": "gitlens.gitExplorer.openFile", - "when": "view == gitlens.gitExplorer && viewItem == gitlens:status-file", + "command": "gitlens.explorers.openFile", + "when": "viewItem == gitlens:status-file", "group": "2_gitlens@1" }, { - "command": "gitlens.gitExplorer.openFileRevision", - "when": "view == gitlens.gitExplorer && viewItem == gitlens:status-file", + "command": "gitlens.explorers.openFileRevision", + "when": "viewItem == gitlens:status-file", "group": "2_gitlens@2" }, { "command": "gitlens.openFileInRemote", - "when": "gitlens:hasRemotes && view == gitlens.gitExplorer && viewItem == gitlens:status-file", + "when": "viewItem == gitlens:status-file && gitlens:hasRemotes", "group": "3_gitlens@1" }, { "command": "gitlens.showQuickFileHistory", - "when": "view == gitlens.gitExplorer && viewItem == gitlens:status-file && gitlens:gitExplorer:view == repository", + "when": "viewItem == gitlens:status-file && gitlens:gitExplorer:view == repository", "group": "5_gitlens@1" }, { "command": "gitlens.showQuickCommitFileDetails", - "when": "view == gitlens.gitExplorer && viewItem == gitlens:status-file", + "when": "viewItem == gitlens:status-file", "group": "5_gitlens@2" }, { - "command": "gitlens.gitExplorer.openFile", - "when": "view == gitlens.gitExplorer && viewItem == gitlens:status-file-commits", + "command": "gitlens.explorers.openFile", + "when": "viewItem == gitlens:status-file-commits", "group": "1_gitlens@1" }, { "command": "gitlens.openFileInRemote", - "when": "gitlens:hasRemotes && view == gitlens.gitExplorer && viewItem == gitlens:status-file-commits", + "when": "viewItem == gitlens:status-file-commits && gitlens:hasRemotes", "group": "1_gitlens@2" }, { - "command": "gitlens.gitExplorer.terminalRebaseBranchToRemote", - "when": "view == gitlens.gitExplorer && viewItem == gitlens:status-upstream", + "command": "gitlens.explorers.terminalRebaseBranchToRemote", + "when": "viewItem == gitlens:status-upstream", "group": "8_gitlens@1" }, { "command": "gitlens.gitExplorer.refreshNode", "when": "view == gitlens.gitExplorer && viewItem != gitlens:commit-file && viewItem != gitlens:stash-file && viewItem != gitlens:status-file", "group": "9_gitlens@1" + }, + { + "command": "gitlens.resultsExplorer.refreshNode", + "when": "view == gitlens.resultsExplorer && viewItem != gitlens:commit-file && viewItem != gitlens:stash-file && viewItem != gitlens:status-file", + "group": "9_gitlens@1" } ] }, @@ -2516,6 +2831,11 @@ "id": "gitlens.gitExplorer", "name": "GitLens", "when": "gitlens:enabled && gitlens:gitExplorer && gitlens:hasRepository && config.gitlens.gitExplorer.enabled" + }, + { + "id": "gitlens.resultsExplorer", + "name": "GitLens Results", + "when": "gitlens:enabled && gitlens:resultsExplorer" } ] } diff --git a/src/commands/showCommitSearch.ts b/src/commands/showCommitSearch.ts index 21d3f13..b95ce86 100644 --- a/src/commands/showCommitSearch.ts +++ b/src/commands/showCommitSearch.ts @@ -6,7 +6,7 @@ import { GlyphChars } from '../constants'; import { GitRepoSearchBy, GitService, GitUri } from '../gitService'; import { Logger } from '../logger'; import { Messages } from '../messages'; -import { CommandQuickPickItem, CommitsQuickPick } from '../quickPicks'; +import { CommandQuickPickItem, CommitsQuickPick, ShowCommitsInResultsQuickPickItem } from '../quickPicks'; import { ShowQuickCommitDetailsCommandArgs } from './showQuickCommitDetails'; const searchByRegex = /^([@~=:#])/; @@ -117,7 +117,8 @@ export class ShowCommitSearchCommand extends ActiveEditorCachedCommand { const progressCancellation = CommitsQuickPick.showProgress(placeHolder!); try { - const log = await this.git.getLogForRepoSearch(repoPath, args.search, args.searchBy); + const queryFn = ((search: string, searchBy: GitRepoSearchBy) => (maxCount: number | undefined) => this.git.getLogForRepoSearch(repoPath, search, searchBy, maxCount))(args.search, args.searchBy); + const log = await queryFn(undefined); if (progressCancellation.token.isCancellationRequested) return undefined; @@ -133,7 +134,14 @@ export class ShowCommitSearchCommand extends ActiveEditorCachedCommand { } as ShowCommitSearchCommandArgs ]); - const pick = await CommitsQuickPick.show(this.git, log, placeHolder!, progressCancellation, currentCommand); + const showInResultsExplorer = log !== undefined + ? new ShowCommitsInResultsQuickPickItem(placeHolder!, log, queryFn, { + label: 'Show in Results', + description: `${Strings.pad(GlyphChars.Dash, 2, 2)} displays results in the GitLens Results view` + }) + : undefined; + + const pick = await CommitsQuickPick.show(this.git, log, placeHolder!, progressCancellation, currentCommand, showInResultsExplorer); if (pick === undefined) return undefined; if (pick instanceof CommandQuickPickItem) return pick.execute(); diff --git a/src/configuration.ts b/src/configuration.ts index 4775182..f78448a 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -38,7 +38,7 @@ export enum CustomRemoteType { GitLab = 'GitLab' } -export enum GitExplorerFilesLayout { +export enum ExplorerFilesLayout { Auto = 'auto', List = 'list', Tree = 'tree' @@ -137,26 +137,32 @@ export interface ICodeLensLanguageLocation { customSymbols?: string[]; } -export interface IGitExplorerConfig { - enabled: boolean; - autoRefresh: boolean; - view: GitExplorerView; +export interface IExplorerConfig { files: { - layout: GitExplorerFilesLayout; + layout: ExplorerFilesLayout; compact: boolean; threshold: number; }; - includeWorkingTree: boolean; - showTrackingBranch: boolean; commitFormat: string; commitFileFormat: string; + // dateFormat: string | null; gravatars: boolean; + showTrackingBranch: boolean; stashFormat: string; stashFileFormat: string; statusFileFormat: string; - // dateFormat: string | null; } +export interface IGitExplorerConfig extends IExplorerConfig { + enabled: boolean; + autoRefresh: boolean; + includeWorkingTree: boolean; + showTrackingBranch: boolean; + view: GitExplorerView; +} + +export interface IResultsExplorerConfig extends IExplorerConfig { } + export interface IRemotesConfig { type: CustomRemoteType; domain: string; @@ -265,6 +271,8 @@ export interface IConfig { remotes: IRemotesConfig[]; + resultsExplorer: IResultsExplorerConfig; + statusBar: { enabled: boolean; alignment: 'left' | 'right'; @@ -376,26 +384,41 @@ const emptyConfig: IConfig = { }, defaultDateFormat: null, gitExplorer: { - enabled: false, autoRefresh: false, - view: GitExplorerView.Auto, + enabled: false, files: { - layout: GitExplorerFilesLayout.Auto, + layout: ExplorerFilesLayout.Auto, compact: false, threshold: 0 }, + commitFormat: '', + commitFileFormat: '', + // dateFormat: string | null; + gravatars: false, includeWorkingTree: false, showTrackingBranch: false, + stashFormat: '', + stashFileFormat: '', + statusFileFormat: '', + view: GitExplorerView.Auto + }, + keymap: 'standard' as KeyMap, + remotes: [], + resultsExplorer: { + files: { + layout: ExplorerFilesLayout.Auto, + compact: false, + threshold: 0 + }, commitFormat: '', commitFileFormat: '', + // dateFormat: string | null; gravatars: false, + showTrackingBranch: false, stashFormat: '', stashFileFormat: '', statusFileFormat: '' - // dateFormat: string | null; }, - keymap: 'standard' as KeyMap, - remotes: [], statusBar: { enabled: false, alignment: 'left', diff --git a/src/constants.ts b/src/constants.ts index 197d3a9..502fe7c 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -26,20 +26,22 @@ export enum BuiltInCommands { } export enum CommandContext { + ActiveHasRemote = 'gitlens:activeHasRemote', + ActiveIsBlameable = 'gitlens:activeIsBlameable', + ActiveFileIsTracked = 'gitlens:activeIsTracked', AnnotationStatus = 'gitlens:annotationStatus', CanToggleCodeLens = 'gitlens:canToggleCodeLens', Enabled = 'gitlens:enabled', + ExplorersCanCompare = 'gitlens:explorers:canCompare', GitExplorer = 'gitlens:gitExplorer', GitExplorerAutoRefresh = 'gitlens:gitExplorer:autoRefresh', - GitExplorerFilesLayout = 'gitlens:gitExplorer:files:layout', GitExplorerView = 'gitlens:gitExplorer:view', HasRemotes = 'gitlens:hasRemotes', HasRepository = 'gitlens:hasRepository', - ActiveHasRemote = 'gitlens:activeHasRemote', - ActiveIsBlameable = 'gitlens:activeIsBlameable', - ActiveFileIsTracked = 'gitlens:activeIsTracked', Key = 'gitlens:key', - KeyMap = 'gitlens:keymap' + KeyMap = 'gitlens:keymap', + ResultsExplorer = 'gitlens:resultsExplorer', + ResultsExplorerKeepResults = 'gitlens:resultsExplorer:keepResults' } export function setCommandContext(key: CommandContext | string, value: any) { @@ -90,5 +92,6 @@ export enum GlobalState { export enum WorkspaceState { GitExplorerAutoRefresh = 'gitlens:gitExplorer:autoRefresh', - GitExplorerView = 'gitlens:gitExplorer:view' + GitExplorerView = 'gitlens:gitExplorer:view', + ResultsExplorerKeepResults = 'gitlens:resultsExplorer:keepResults' } \ No newline at end of file diff --git a/src/extension.ts b/src/extension.ts index 28f957f..8576e6b 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -7,6 +7,7 @@ import { ApplicationInsightsKey, CommandContext, ExtensionKey, GlobalState, Qual import { CodeLensController } from './codeLensController'; import { configureCommands } from './commands'; import { CurrentLineController } from './currentLineController'; +import { ExplorerCommands } from './views/explorerCommands'; import { GitContentProvider } from './gitContentProvider'; import { GitExplorer } from './views/gitExplorer'; import { GitRevisionCodeLensProvider } from './gitRevisionCodeLensProvider'; @@ -14,6 +15,7 @@ import { GitContextTracker, GitService } from './gitService'; import { Keyboard } from './keyboard'; import { Logger } from './logger'; import { Messages, SuppressedMessages } from './messages'; +import { ResultsExplorer } from './views/resultsExplorer'; import { Telemetry } from './telemetry'; // this method is called when your extension is activated @@ -74,7 +76,12 @@ export async function activate(context: ExtensionContext) { context.subscriptions.push(workspace.registerTextDocumentContentProvider(GitContentProvider.scheme, new GitContentProvider(context, git))); context.subscriptions.push(languages.registerCodeLensProvider(GitRevisionCodeLensProvider.selector, new GitRevisionCodeLensProvider(context, git))); - context.subscriptions.push(window.registerTreeDataProvider('gitlens.gitExplorer', new GitExplorer(context, git, gitContextTracker))); + + const explorerCommands = new ExplorerCommands(context, git); + context.subscriptions.push(explorerCommands); + + context.subscriptions.push(window.registerTreeDataProvider('gitlens.gitExplorer', new GitExplorer(context, explorerCommands, git, gitContextTracker))); + context.subscriptions.push(window.registerTreeDataProvider('gitlens.resultsExplorer', new ResultsExplorer(context, explorerCommands, git))); context.subscriptions.push(new Keyboard()); @@ -83,6 +90,8 @@ export async function activate(context: ExtensionContext) { // Constantly over my data cap so stop collecting initialized event // Telemetry.trackEvent('initialized', Objects.flatten(cfg, 'config', true)); + // setCommandContext(CommandContext.ResultsExplorer, false); + // Slightly delay enabling the explorer to not stop the rest of GitLens from being usable setTimeout(() => setCommandContext(CommandContext.GitExplorer, true), 1000); } diff --git a/src/git/parsers/statusParser.ts b/src/git/parsers/statusParser.ts index 9cf1e64..b7667ca 100644 --- a/src/git/parsers/statusParser.ts +++ b/src/git/parsers/statusParser.ts @@ -115,9 +115,12 @@ export class GitStatusParser { indexStatus = undefined; } - let workTreeStatus = rawStatus[1] !== '.' ? rawStatus[1].trim() : undefined; - if (workTreeStatus === '' || workTreeStatus === null) { - workTreeStatus = undefined; + let workTreeStatus = undefined; + if (rawStatus.length > 1) { + workTreeStatus = rawStatus[1] !== '.' ? rawStatus[1].trim() : undefined; + if (workTreeStatus === '' || workTreeStatus === null) { + workTreeStatus = undefined; + } } return new GitStatusFile( diff --git a/src/gitService.ts b/src/gitService.ts index fb0bf15..86d7500 100644 --- a/src/gitService.ts +++ b/src/gitService.ts @@ -1403,7 +1403,10 @@ export class GitService extends Disposable { static shortenSha(sha: string | undefined) { if (sha === undefined) return undefined; if (sha === GitService.deletedSha) return '(deleted)'; - return Git.shortenSha(sha); + + return Git.isSha(sha) + ? Git.shortenSha(sha) + : sha; } static validateGitVersion(major: number, minor: number): boolean { diff --git a/src/quickPicks/commits.ts b/src/quickPicks/commits.ts index 88df2a3..be0a3a5 100644 --- a/src/quickPicks/commits.ts +++ b/src/quickPicks/commits.ts @@ -16,9 +16,13 @@ export class CommitsQuickPick { }); } - static async show(git: GitService, log: GitLog | undefined, placeHolder: string, progressCancellation: CancellationTokenSource, goBackCommand?: CommandQuickPickItem): Promise { + static async show(git: GitService, log: GitLog | undefined, placeHolder: string, progressCancellation: CancellationTokenSource, goBackCommand?: CommandQuickPickItem, showInResultsExplorerCommand?: CommandQuickPickItem): Promise { const items = ((log && [...Iterables.map(log.commits.values(), c => new CommitQuickPickItem(c))]) || [new MessageQuickPickItem('No results found')]) as (CommitQuickPickItem | CommandQuickPickItem)[]; + if (showInResultsExplorerCommand !== undefined) { + items.splice(0, 0, showInResultsExplorerCommand); + } + if (goBackCommand !== undefined) { items.splice(0, 0, goBackCommand); } diff --git a/src/quickPicks/common.ts b/src/quickPicks/common.ts index 3c77b68..4202558 100644 --- a/src/quickPicks/common.ts +++ b/src/quickPicks/common.ts @@ -4,8 +4,9 @@ import { CancellationTokenSource, commands, Disposable, QuickPickItem, QuickPick import { Commands, openEditor } from '../commands'; import { ExtensionKey, IAdvancedConfig } from '../configuration'; import { GlyphChars } from '../constants'; -import { GitLogCommit, GitStashCommit } from '../gitService'; +import { GitLog, GitLogCommit, GitStashCommit } from '../gitService'; import { Keyboard, KeyboardScope, KeyMapping, Keys } from '../keyboard'; +import { ResultsExplorer } from '../views/resultsExplorer'; // import { Logger } from '../logger'; export function getQuickPickIgnoreFocusOut() { @@ -188,4 +189,21 @@ export class CommitQuickPickItem implements QuickPickItem { this.detail = `${GlyphChars.Space} ${commit.author}, ${commit.fromNow()}${commit.isFile ? '' : ` ${Strings.pad(GlyphChars.Dot, 1, 1)} ${commit.getDiffStatus()}`}`; } } +} + +export class ShowCommitsInResultsQuickPickItem extends CommandQuickPickItem { + + constructor( + public readonly search: string, + public readonly results: GitLog, + public readonly queryFn: (maxCount: number | undefined) => Promise, + item: QuickPickItem + ) { + super(item, undefined, undefined); + } + + async execute(options: TextDocumentShowOptions = { preserveFocus: false, preview: false }): Promise<{} | undefined> { + ResultsExplorer.instance.showCommitSearchResults(this.search, this.results, this.queryFn); + return undefined; + } } \ No newline at end of file diff --git a/src/system/function.ts b/src/system/function.ts index a2e313d..2316bd4 100644 --- a/src/system/function.ts +++ b/src/system/function.ts @@ -33,6 +33,19 @@ export namespace Functions { return propOfCore(o, key); } + export function seeded(fn: (...args: any[]) => Promise, seed: T): (...args: any[]) => Promise { + let cached: T | undefined = seed; + return (...args: any[]) => { + if (cached !== undefined) { + const promise = Promise.resolve(cached); + cached = undefined; + + return promise; + } + return fn(...args); + }; + } + export async function wait(ms: number) { await new Promise(resolve => setTimeout(resolve, ms)); } diff --git a/src/views/branchHistoryNode.ts b/src/views/branchHistoryNode.ts deleted file mode 100644 index 4cb1554..0000000 --- a/src/views/branchHistoryNode.ts +++ /dev/null @@ -1,74 +0,0 @@ -'use strict'; -import { Iterables } from '../system'; -import { TreeItem, TreeItemCollapsibleState } from 'vscode'; -import { CommitNode } from './commitNode'; -import { GlyphChars } from '../constants'; -import { ExplorerNode, MessageNode, ResourceType, ShowAllNode } from './explorerNode'; -import { GitExplorer } from './gitExplorer'; -import { GitBranch, GitUri } from '../gitService'; - -export class BranchHistoryNode extends ExplorerNode { - - maxCount: number | undefined = undefined; - - constructor( - public readonly branch: GitBranch, - uri: GitUri, - private readonly explorer: GitExplorer - ) { - super(uri); - } - - async getChildren(): Promise { - const log = await this.explorer.git.getLogForRepo(this.uri.repoPath!, this.branch.name, this.maxCount); - if (log === undefined) return [new MessageNode('No commits yet')]; - - const children: (CommitNode | ShowAllNode)[] = [...Iterables.map(log.commits.values(), c => new CommitNode(c, this.explorer, this.branch))]; - if (log.truncated) { - children.push(new ShowAllNode('Show All Commits', this, this.explorer.context)); - } - return children; - } - - async getTreeItem(): Promise { - let name = this.branch.getName(); - if (!this.branch.remote && this.branch.tracking !== undefined && this.explorer.config.showTrackingBranch) { - name += ` ${GlyphChars.Space}${GlyphChars.ArrowLeftRight}${GlyphChars.Space} ${this.branch.tracking}`; - } - const item = new TreeItem(`${this.branch!.current ? `${GlyphChars.Check} ${GlyphChars.Space}` : ''}${name}`, TreeItemCollapsibleState.Collapsed); - - if (this.branch.remote) { - item.contextValue = ResourceType.RemoteBranchHistory; - } - else if (this.branch.current) { - item.contextValue = !!this.branch.tracking - ? ResourceType.CurrentBranchHistoryWithTracking - : ResourceType.CurrentBranchHistory; - } - else { - item.contextValue = !!this.branch.tracking - ? ResourceType.BranchHistoryWithTracking - : ResourceType.BranchHistory; - } - - let iconSuffix = ''; - if (this.branch.tracking) { - if (this.branch.state.ahead && this.branch.state.behind) { - iconSuffix = '-yellow'; - } - else if (this.branch.state.ahead) { - iconSuffix = '-green'; - } - else if (this.branch.state.behind) { - iconSuffix = '-red'; - } - } - - item.iconPath = { - dark: this.explorer.context.asAbsolutePath(`images/dark/icon-branch${iconSuffix}.svg`), - light: this.explorer.context.asAbsolutePath(`images/light/icon-branch${iconSuffix}.svg`) - }; - - return item; - } - } diff --git a/src/views/branchNode.ts b/src/views/branchNode.ts new file mode 100644 index 0000000..d68a6cc --- /dev/null +++ b/src/views/branchNode.ts @@ -0,0 +1,77 @@ +'use strict'; +import { Iterables } from '../system'; +import { TreeItem, TreeItemCollapsibleState } from 'vscode'; +import { CommitNode } from './commitNode'; +import { GlyphChars } from '../constants'; +import { Explorer, ExplorerNode, ExplorerRefNode, MessageNode, ResourceType, ShowAllNode } from './explorerNode'; +import { GitBranch, GitUri } from '../gitService'; + +export class BranchNode extends ExplorerRefNode { + + readonly supportsPaging: boolean = true; + + constructor( + public readonly branch: GitBranch, + uri: GitUri, + private readonly explorer: Explorer + ) { + super(uri); + } + + get ref(): string { + return this.branch.name; + } + + async getChildren(): Promise { + const log = await this.explorer.git.getLogForRepo(this.uri.repoPath!, this.branch.name, this.maxCount); + if (log === undefined) return [new MessageNode('No commits yet')]; + + const children: (CommitNode | ShowAllNode)[] = [...Iterables.map(log.commits.values(), c => new CommitNode(c, this.explorer, this.branch))]; + if (log.truncated) { + children.push(new ShowAllNode('Show All Commits', this, this.explorer)); + } + return children; + } + + async getTreeItem(): Promise { + let name = this.branch.getName(); + if (!this.branch.remote && this.branch.tracking !== undefined && this.explorer.config.showTrackingBranch) { + name += ` ${GlyphChars.Space}${GlyphChars.ArrowLeftRight}${GlyphChars.Space} ${this.branch.tracking}`; + } + const item = new TreeItem(`${this.branch!.current ? `${GlyphChars.Check} ${GlyphChars.Space}` : ''}${name}`, TreeItemCollapsibleState.Collapsed); + + if (this.branch.remote) { + item.contextValue = ResourceType.RemoteBranch; + } + else if (this.branch.current) { + item.contextValue = !!this.branch.tracking + ? ResourceType.CurrentBranchWithTracking + : ResourceType.CurrentBranch; + } + else { + item.contextValue = !!this.branch.tracking + ? ResourceType.BranchWithTracking + : ResourceType.Branch; + } + + let iconSuffix = ''; + if (this.branch.tracking) { + if (this.branch.state.ahead && this.branch.state.behind) { + iconSuffix = '-yellow'; + } + else if (this.branch.state.ahead) { + iconSuffix = '-green'; + } + else if (this.branch.state.behind) { + iconSuffix = '-red'; + } + } + + item.iconPath = { + dark: this.explorer.context.asAbsolutePath(`images/dark/icon-branch${iconSuffix}.svg`), + light: this.explorer.context.asAbsolutePath(`images/light/icon-branch${iconSuffix}.svg`) + }; + + return item; + } +} diff --git a/src/views/branchesNode.ts b/src/views/branchesNode.ts index 783a7cc..8b90c5c 100644 --- a/src/views/branchesNode.ts +++ b/src/views/branchesNode.ts @@ -1,9 +1,8 @@ 'use strict'; import { Iterables } from '../system'; import { TreeItem, TreeItemCollapsibleState } from 'vscode'; -import { BranchHistoryNode } from './branchHistoryNode'; -import { ExplorerNode, ResourceType } from './explorerNode'; -import { GitExplorer } from './gitExplorer'; +import { BranchNode } from './branchNode'; +import { Explorer, ExplorerNode, ResourceType } from './explorerNode'; import { GitUri, Repository } from '../gitService'; export class BranchesNode extends ExplorerNode { @@ -11,7 +10,7 @@ export class BranchesNode extends ExplorerNode { constructor( uri: GitUri, private readonly repo: Repository, - private readonly explorer: GitExplorer, + private readonly explorer: Explorer, private readonly active: boolean = false ) { super(uri); @@ -22,7 +21,7 @@ export class BranchesNode extends ExplorerNode { if (branches === undefined) return []; branches.sort((a, b) => (a.current ? -1 : 1) - (b.current ? -1 : 1) || a.name.localeCompare(b.name)); - return [...Iterables.filterMap(branches, b => b.remote ? undefined : new BranchHistoryNode(b, this.uri, this.explorer))]; + return [...Iterables.filterMap(branches, b => b.remote ? undefined : new BranchNode(b, this.uri, this.explorer))]; } async getTreeItem(): Promise { diff --git a/src/views/commitFileNode.ts b/src/views/commitFileNode.ts index 05e1637..6c84e75 100644 --- a/src/views/commitFileNode.ts +++ b/src/views/commitFileNode.ts @@ -1,8 +1,7 @@ 'use strict'; import { Command, TreeItem, TreeItemCollapsibleState } from 'vscode'; import { Commands, DiffWithPreviousCommandArgs } from '../commands'; -import { ExplorerNode, ResourceType } from './explorerNode'; -import { GitExplorer } from './gitExplorer'; +import { Explorer, ExplorerNode, ResourceType } from './explorerNode'; import { CommitFormatter, getGitStatusIcon, GitBranch, GitLogCommit, GitUri, ICommitFormatOptions, IGitStatusFile, IStatusFormatOptions, StatusFileFormatter } from '../gitService'; import * as path from 'path'; @@ -25,7 +24,7 @@ export class CommitFileNode extends ExplorerNode { constructor( public readonly status: IGitStatusFile, public commit: GitLogCommit, - protected readonly explorer: GitExplorer, + protected readonly explorer: Explorer, private displayAs: CommitFileNodeDisplayAs = CommitFileNodeDisplayAs.Commit, public readonly branch?: GitBranch ) { @@ -92,13 +91,21 @@ export class CommitFileNode extends ExplorerNode { get label() { if (this._label === undefined) { this._label = (this.displayAs & CommitFileNodeDisplayAs.CommitLabel) - ? CommitFormatter.fromTemplate(this.getCommitTemplate(), this.commit, { - truncateMessageAtNewLine: true, - dataFormat: this.explorer.git.config.defaultDateFormat - } as ICommitFormatOptions) - : StatusFileFormatter.fromTemplate(this.getCommitFileTemplate(), + ? CommitFormatter.fromTemplate( + this.getCommitTemplate(), + this.commit, + { + truncateMessageAtNewLine: true, + dataFormat: this.explorer.git.config.defaultDateFormat + } as ICommitFormatOptions + ) + : StatusFileFormatter.fromTemplate( + this.getCommitFileTemplate(), this.status, - { relativePath: this.relativePath } as IStatusFormatOptions); + { + relativePath: this.relativePath + } as IStatusFormatOptions + ); } return this._label; } diff --git a/src/views/commitNode.ts b/src/views/commitNode.ts index 1aaa50f..4bac7c2 100644 --- a/src/views/commitNode.ts +++ b/src/views/commitNode.ts @@ -3,24 +3,24 @@ import { Arrays, Iterables } from '../system'; import { Command, TreeItem, TreeItemCollapsibleState } from 'vscode'; import { Commands, DiffWithPreviousCommandArgs } from '../commands'; import { CommitFileNode, CommitFileNodeDisplayAs } from './commitFileNode'; -import { GitExplorerFilesLayout } from '../configuration'; +import { ExplorerFilesLayout } from '../configuration'; import { FolderNode, IFileExplorerNode } from './folderNode'; -import { ExplorerNode, ResourceType } from './explorerNode'; -import { GitExplorer } from './gitExplorer'; +import { Explorer, ExplorerNode, ExplorerRefNode, ResourceType } from './explorerNode'; import { CommitFormatter, GitBranch, GitLogCommit, GitService, ICommitFormatOptions } from '../gitService'; import * as path from 'path'; -export class CommitNode extends ExplorerNode { - - readonly repoPath: string; +export class CommitNode extends ExplorerRefNode { constructor( public readonly commit: GitLogCommit, - private readonly explorer: GitExplorer, + private readonly explorer: Explorer, public readonly branch?: GitBranch ) { super(commit.toGitUri()); - this.repoPath = commit.repoPath; + } + + get ref(): string { + return this.commit.sha; } async getChildren(): Promise { @@ -36,7 +36,7 @@ export class CommitNode extends ExplorerNode { ...Iterables.map(commit.fileStatuses, s => new CommitFileNode(s, commit.toFileCommit(s), this.explorer, CommitFileNodeDisplayAs.File, this.branch)) ]; - if (this.explorer.config.files.layout !== GitExplorerFilesLayout.List) { + if (this.explorer.config.files.layout !== ExplorerFilesLayout.List) { const hierarchy = Arrays.makeHierarchical(children, n => n.uri.getRelativePath().split('/'), (...paths: string[]) => GitService.normalizePath(path.join(...paths)), this.explorer.config.files.compact); diff --git a/src/views/commitsNode.ts b/src/views/commitsNode.ts new file mode 100644 index 0000000..faffce4 --- /dev/null +++ b/src/views/commitsNode.ts @@ -0,0 +1,36 @@ +'use strict'; +import { Iterables } from '../system'; +import { TreeItem, TreeItemCollapsibleState } from 'vscode'; +import { CommitNode } from './commitNode'; +import { Explorer, ExplorerNode, ResourceType, ShowAllNode } from './explorerNode'; +import { GitLog, GitUri } from '../gitService'; + +export class CommitsNode extends ExplorerNode { + + readonly supportsPaging: boolean = true; + + constructor( + readonly repoPath: string, + private readonly logFn: (maxCount: number | undefined) => Promise, + private readonly explorer: Explorer + ) { + super(GitUri.fromRepoPath(repoPath)); + } + + async getChildren(): Promise { + const log = await this.logFn(this.maxCount); + if (log === undefined) return []; + + const children: (CommitNode | ShowAllNode)[] = [...Iterables.map(log.commits.values(), c => new CommitNode(c, this.explorer))]; + if (log.truncated) { + children.push(new ShowAllNode('Show All Commits', this, this.explorer)); + } + return children; + } + + async getTreeItem(): Promise { + const item = new TreeItem('Commits', TreeItemCollapsibleState.Collapsed); + item.contextValue = ResourceType.Commits; + return item; + } +} diff --git a/src/views/commitsResultsNode.ts b/src/views/commitsResultsNode.ts new file mode 100644 index 0000000..cbc308a --- /dev/null +++ b/src/views/commitsResultsNode.ts @@ -0,0 +1,69 @@ +'use strict'; +import { Iterables } from '../system'; +import { TreeItem, TreeItemCollapsibleState } from 'vscode'; +import { CommitNode } from './commitNode'; +import { Explorer, ExplorerNode, ResourceType, ShowAllNode } from './explorerNode'; +import { GitLog, GitUri } from '../gitService'; + +export class CommitsResultsNode extends ExplorerNode { + + readonly supportsPaging: boolean = true; + + private _cache: { label: string, log: GitLog | undefined } | undefined; + + constructor( + readonly repoPath: string, + private readonly labelFn: (log: GitLog | undefined) => string, + private readonly logFn: (maxCount: number | undefined) => Promise, + private readonly explorer: Explorer, + private readonly contextValue: ResourceType = ResourceType.Results + ) { + super(GitUri.fromRepoPath(repoPath)); + } + + async getChildren(): Promise { + const log = await this.getLog(); + if (log === undefined) return []; + + const children: (CommitNode | ShowAllNode)[] = [...Iterables.map(log.commits.values(), c => new CommitNode(c, this.explorer))]; + if (log.truncated) { + children.push(new ShowAllNode('Show All Results', this, this.explorer)); + } + return children; + } + + async getTreeItem(): Promise { + const log = await this.getLog(); + + const item = new TreeItem(await this.getLabel(), log && log.count > 0 ? TreeItemCollapsibleState.Expanded : TreeItemCollapsibleState.None); + item.contextValue = this.contextValue; + return item; + } + + refresh() { + this._cache = undefined; + } + + private async ensureCache() { + if (this._cache === undefined) { + const log = await this.logFn(this.maxCount); + + this._cache = { + label: this.labelFn(log), + log: log + }; + } + + return this._cache; + } + + private async getLabel() { + const cache = await this.ensureCache(); + return cache.label; + } + + private async getLog() { + const cache = await this.ensureCache(); + return cache.log; + } +} \ No newline at end of file diff --git a/src/views/comparisionResultsNode.ts b/src/views/comparisionResultsNode.ts new file mode 100644 index 0000000..21e86de --- /dev/null +++ b/src/views/comparisionResultsNode.ts @@ -0,0 +1,56 @@ +'use strict'; +import { Strings } from '../system'; +import { TreeItem, TreeItemCollapsibleState } from 'vscode'; +import { CommitsResultsNode } from './commitsResultsNode'; +import { GlyphChars } from '../constants'; +import { Explorer, ExplorerNode, ResourceType } from './explorerNode'; +import { GitLog, GitService, GitStatusFile, GitUri } from '../gitService'; +import { StatusFilesResultsNode } from './statusFilesResultsNode'; + +export class ComparisionResultsNode extends ExplorerNode { + + constructor( + repoPath: string, + private readonly ref1: string, + private readonly ref2: string, + private readonly explorer: Explorer + ) { + super(GitUri.fromRepoPath(repoPath)); + } + + async getChildren(): Promise { + this.resetChildren(); + + const commitsQueryFn = (maxCount: number | undefined) => this.explorer.git.getLogForRepo(this.uri.repoPath!, `${this.ref1}...${this.ref2}`, maxCount); + const commitsLabelFn = (log: GitLog | undefined) => { + const count = log !== undefined ? log.count : 0; + const truncated = log !== undefined ? log.truncated : false; + + if (count === 1) return `1 commit`; + return `${count === 0 ? 'No' : `${count}${truncated ? '+' : ''}`} commits`; + }; + + const filesQueryFn = () => this.explorer.git.getDiffStatus(this.uri.repoPath!, this.ref1, this.ref2); + const filesLabelFn = (diff: GitStatusFile[] | undefined) => { + const count = diff !== undefined ? diff.length : 0; + + if (count === 1) return `1 file changed`; + return `${count === 0 ? 'No' : count} files changed`; + }; + + this.children = [ + new CommitsResultsNode(this.uri.repoPath!, commitsLabelFn, commitsQueryFn, this.explorer), + new StatusFilesResultsNode(this.uri.repoPath!, this.ref1, this.ref2, filesLabelFn, filesQueryFn, this.explorer) + ]; + + return this.children; + } + + async getTreeItem(): Promise { + const repo = await this.explorer.git.getRepository(this.uri.repoPath!); + + const item = new TreeItem(`Comparing ${GitService.shortenSha(this.ref1)} to ${GitService.shortenSha(this.ref2)} ${Strings.pad(GlyphChars.Dash, 1, 1)} ${(repo && repo.formattedName) || this.uri.repoPath}`, TreeItemCollapsibleState.Expanded); + item.contextValue = ResourceType.ComparisonResults; + return item; + } +} \ No newline at end of file diff --git a/src/views/explorerCommands.ts b/src/views/explorerCommands.ts index 2eafe92..f35d96c 100644 --- a/src/views/explorerCommands.ts +++ b/src/views/explorerCommands.ts @@ -1,11 +1,15 @@ import { Arrays } from '../system'; -import { commands, ConfigurationTarget, Disposable, InputBoxOptions, Terminal, TextDocumentShowOptions, Uri, window } from 'vscode'; -import { ExtensionTerminalName } from '../constants'; -import { BranchHistoryNode, ExplorerNode, GitExplorer, GitExplorerView } from '../views/gitExplorer'; -import { configuration, GitExplorerFilesLayout } from '../configuration'; -import { CommitFileNode, CommitNode, RemoteNode, StashFileNode, StashNode, StatusFileCommitsNode, StatusUpstreamNode } from './explorerNodes'; +import { commands, Disposable, ExtensionContext, InputBoxOptions, Terminal, TextDocumentShowOptions, Uri, window } from 'vscode'; +import { CommandContext, ExtensionTerminalName, setCommandContext } from '../constants'; +import { BranchNode, ExplorerNode } from '../views/gitExplorer'; +import { CommitFileNode, CommitNode, ExplorerRefNode, RemoteNode, StashFileNode, StashNode, StatusFileCommitsNode, StatusUpstreamNode } from './explorerNodes'; import { Commands, DiffWithCommandArgs, DiffWithCommandArgsRevision, DiffWithPreviousCommandArgs, DiffWithWorkingCommandArgs, openEditor, OpenFileInRemoteCommandArgs, OpenFileRevisionCommandArgs } from '../commands'; import { GitService, GitUri } from '../gitService'; +import { ResultsExplorer } from './resultsExplorer'; + +export interface RefreshNodeCommandArgs { + maxCount?: number; +} export class ExplorerCommands extends Disposable { @@ -13,48 +17,62 @@ export class ExplorerCommands extends Disposable { private _terminal: Terminal | undefined; constructor( - private explorer: GitExplorer + public readonly context: ExtensionContext, + public readonly git: GitService ) { super(() => this.dispose()); - commands.registerCommand('gitlens.gitExplorer.setAutoRefreshToOn', () => this.explorer.setAutoRefresh(configuration.get(configuration.name('gitExplorer')('autoRefresh').value), true), this); - commands.registerCommand('gitlens.gitExplorer.setAutoRefreshToOff', () => this.explorer.setAutoRefresh(configuration.get(configuration.name('gitExplorer')('autoRefresh').value), false), this); - commands.registerCommand('gitlens.gitExplorer.setFilesLayoutToAuto', () => this.setFilesLayout(GitExplorerFilesLayout.Auto), this); - commands.registerCommand('gitlens.gitExplorer.setFilesLayoutToList', () => this.setFilesLayout(GitExplorerFilesLayout.List), this); - commands.registerCommand('gitlens.gitExplorer.setFilesLayoutToTree', () => this.setFilesLayout(GitExplorerFilesLayout.Tree), this); - commands.registerCommand('gitlens.gitExplorer.switchToHistoryView', () => this.explorer.switchTo(GitExplorerView.History), this); - commands.registerCommand('gitlens.gitExplorer.switchToRepositoryView', () => this.explorer.switchTo(GitExplorerView.Repository), this); - commands.registerCommand('gitlens.gitExplorer.refresh', this.explorer.refresh, this.explorer); - commands.registerCommand('gitlens.gitExplorer.refreshNode', this.explorer.refreshNode, this.explorer); - commands.registerCommand('gitlens.gitExplorer.openChanges', this.openChanges, this); - commands.registerCommand('gitlens.gitExplorer.openChangesWithWorking', this.openChangesWithWorking, this); - commands.registerCommand('gitlens.gitExplorer.openFile', this.openFile, this); - commands.registerCommand('gitlens.gitExplorer.openFileRevision', this.openFileRevision, this); - commands.registerCommand('gitlens.gitExplorer.openFileRevisionInRemote', this.openFileRevisionInRemote, this); - commands.registerCommand('gitlens.gitExplorer.openChangedFiles', this.openChangedFiles, this); - commands.registerCommand('gitlens.gitExplorer.openChangedFileChanges', this.openChangedFileChanges, this); - commands.registerCommand('gitlens.gitExplorer.openChangedFileChangesWithWorking', this.openChangedFileChangesWithWorking, this); - commands.registerCommand('gitlens.gitExplorer.openChangedFileRevisions', this.openChangedFileRevisions, this); - commands.registerCommand('gitlens.gitExplorer.applyChanges', this.applyChanges, this); - commands.registerCommand('gitlens.gitExplorer.terminalCheckoutBranch', this.terminalCheckoutBranch, this); - commands.registerCommand('gitlens.gitExplorer.terminalCreateBranch', this.terminalCreateBranch, this); - commands.registerCommand('gitlens.gitExplorer.terminalDeleteBranch', this.terminalDeleteBranch, this); - commands.registerCommand('gitlens.gitExplorer.terminalRebaseBranchToRemote', this.terminalRebaseBranchToRemote, this); - commands.registerCommand('gitlens.gitExplorer.terminalSquashBranchIntoCommit', this.terminalSquashBranchIntoCommit, this); - commands.registerCommand('gitlens.gitExplorer.terminalRebaseCommit', this.terminalRebaseCommit, this); - commands.registerCommand('gitlens.gitExplorer.terminalResetCommit', this.terminalResetCommit, this); - commands.registerCommand('gitlens.gitExplorer.terminalRemoveRemote', this.terminalRemoveRemote, this); - } - - dispose() { + commands.registerCommand('gitlens.explorers.openChanges', this.openChanges, this); + commands.registerCommand('gitlens.explorers.openChangesWithWorking', this.openChangesWithWorking, this); + commands.registerCommand('gitlens.explorers.openFile', this.openFile, this); + commands.registerCommand('gitlens.explorers.openFileRevision', this.openFileRevision, this); + commands.registerCommand('gitlens.explorers.openFileRevisionInRemote', this.openFileRevisionInRemote, this); + commands.registerCommand('gitlens.explorers.openChangedFiles', this.openChangedFiles, this); + commands.registerCommand('gitlens.explorers.openChangedFileChanges', this.openChangedFileChanges, this); + commands.registerCommand('gitlens.explorers.openChangedFileChangesWithWorking', this.openChangedFileChangesWithWorking, this); + commands.registerCommand('gitlens.explorers.openChangedFileRevisions', this.openChangedFileRevisions, this); + commands.registerCommand('gitlens.explorers.applyChanges', this.applyChanges, this); + commands.registerCommand('gitlens.explorers.compareWithSelected', this.compareWithSelected, this); + commands.registerCommand('gitlens.explorers.selectForCompare', this.selectForCompare, this); + commands.registerCommand('gitlens.explorers.terminalCheckoutBranch', this.terminalCheckoutBranch, this); + commands.registerCommand('gitlens.explorers.terminalCreateBranch', this.terminalCreateBranch, this); + commands.registerCommand('gitlens.explorers.terminalDeleteBranch', this.terminalDeleteBranch, this); + commands.registerCommand('gitlens.explorers.terminalRebaseBranchToRemote', this.terminalRebaseBranchToRemote, this); + commands.registerCommand('gitlens.explorers.terminalSquashBranchIntoCommit', this.terminalSquashBranchIntoCommit, this); + commands.registerCommand('gitlens.explorers.terminalRebaseCommit', this.terminalRebaseCommit, this); + commands.registerCommand('gitlens.explorers.terminalResetCommit', this.terminalResetCommit, this); + commands.registerCommand('gitlens.explorers.terminalRemoveRemote', this.terminalRemoveRemote, this); + } + + dispose() { this._disposable && this._disposable.dispose(); } - private async applyChanges(node: CommitFileNode | StashFileNode) { - await this.explorer.git.checkoutFile(node.uri); + private async applyChanges(node: CommitFileNode | StashFileNode) { + await this.git.checkoutFile(node.uri); return this.openFile(node); } + private async compareWithSelected(node: ExplorerNode) { + if (this._selection === undefined || !(node instanceof ExplorerRefNode)) return; + if (this._selection.repoPath !== node.repoPath) return; + + ResultsExplorer.instance.showCommitComparisonResults(this._selection.repoPath, this._selection.ref, node.ref); + } + + private _selection: { ref: string, repoPath: string | undefined } | undefined; + + private async selectForCompare(node: ExplorerNode) { + if (!(node instanceof ExplorerRefNode)) return; + + this._selection = { + ref: node.ref, + repoPath: node.repoPath + }; + + setCommandContext(CommandContext.ExplorersCanCompare, true); + } + private openChanges(node: CommitNode | StashNode) { const command = node.getCommand(); if (command === undefined || command.arguments === undefined) return; @@ -141,19 +159,15 @@ export class ExplorerCommands extends Disposable { return commands.executeCommand(Commands.OpenFileInRemote, node.commit.toGitUri(node.commit.status === 'D'), { range: false } as OpenFileInRemoteCommandArgs); } - private async setFilesLayout(layout: GitExplorerFilesLayout) { - return configuration.update(configuration.name('gitExplorer')('files')('layout').value, layout, ConfigurationTarget.Global); - } - async terminalCheckoutBranch(node: ExplorerNode) { - if (!(node instanceof BranchHistoryNode)) return; + if (!(node instanceof BranchNode)) return; const command = `checkout ${node.branch.name}`; this.sendTerminalCommand(command, node.branch.repoPath); } async terminalCreateBranch(node: ExplorerNode) { - if (!(node instanceof BranchHistoryNode)) return; + if (!(node instanceof BranchNode)) return; const name = await window.showInputBox({ prompt: `Please provide a branch name (Press 'Enter' to confirm or 'Escape' to cancel)`, @@ -167,7 +181,7 @@ export class ExplorerCommands extends Disposable { } terminalDeleteBranch(node: ExplorerNode) { - if (!(node instanceof BranchHistoryNode)) return; + if (!(node instanceof BranchNode)) return; const command = node.branch.remote ? `push ${node.branch.remote} :${node.branch.name}` @@ -176,7 +190,7 @@ export class ExplorerCommands extends Disposable { } terminalRebaseBranchToRemote(node: ExplorerNode) { - if (node instanceof BranchHistoryNode) { + if (node instanceof BranchNode) { if (!node.branch.current || !node.branch.tracking) return; const command = `rebase -i ${node.branch.tracking}`; @@ -189,7 +203,7 @@ export class ExplorerCommands extends Disposable { } terminalSquashBranchIntoCommit(node: ExplorerNode) { - if (!(node instanceof BranchHistoryNode)) return; + if (!(node instanceof BranchNode)) return; const command = `merge --squash ${node.branch.name}`; this.sendTerminalCommand(command, node.branch.repoPath); @@ -227,7 +241,7 @@ export class ExplorerCommands extends Disposable { } }, this); - this.explorer.context.subscriptions.push(this._disposable); + this.context.subscriptions.push(this._disposable); } return this._terminal; diff --git a/src/views/explorerNode.ts b/src/views/explorerNode.ts index 856fb57..e07ce36 100644 --- a/src/views/explorerNode.ts +++ b/src/views/explorerNode.ts @@ -1,20 +1,35 @@ 'use strict'; -import { Command, Disposable, ExtensionContext, TreeItem, TreeItemCollapsibleState } from 'vscode'; +import { Command, Disposable, TreeItem, TreeItemCollapsibleState } from 'vscode'; import { GlyphChars } from '../constants'; +import { RefreshNodeCommandArgs } from './explorerCommands'; import { GitUri } from '../gitService'; -import { RefreshNodeCommandArgs } from './gitExplorer'; +import { GitExplorer } from './gitExplorer'; +import { ResultsExplorer } from './resultsExplorer'; + +export enum RefreshReason { + ActiveEditorChanged = 'active-editor-changed', + AutoRefreshChanged = 'auto-refresh-changed', + Command = 'command', + ConfigurationChanged = 'configuration', + NodeCommand = 'node-command', + RepoChanged = 'repo-changed', + ViewChanged = 'view-changed', + VisibleEditorsChanged = 'visible-editors-changed' +} export enum ResourceType { + Branch = 'gitlens:branch', + BranchWithTracking = 'gitlens:branch:tracking', Branches = 'gitlens:branches', BranchesWithRemotes = 'gitlens:branches:remotes', - BranchHistory = 'gitlens:branch-history', - BranchHistoryWithTracking = 'gitlens:branch-history:tracking', - CurrentBranchHistory = 'gitlens:current-branch-history', - CurrentBranchHistoryWithTracking = 'gitlens:current-branch-history:tracking', - RemoteBranchHistory = 'gitlens:remote-branch-history', + CurrentBranch = 'gitlens:current-branch', + CurrentBranchWithTracking = 'gitlens:current-branch:tracking', + RemoteBranch = 'gitlens:remote-branch', Commit = 'gitlens:commit', CommitOnCurrentBranch = 'gitlens:commit:current', CommitFile = 'gitlens:commit-file', + Commits = 'gitlens:commits', + ComparisonResults = 'gitlens:comparison-results', FileHistory = 'gitlens:file-history', Folder = 'gitlens:folder', History = 'gitlens:history', @@ -24,6 +39,8 @@ export enum ResourceType { Remotes = 'gitlens:remotes', Repositories = 'gitlens:repositories', Repository = 'gitlens:repository', + Results = 'gitlens:results', + SearchResults = 'gitlens:search-results', Stash = 'gitlens:stash', StashFile = 'gitlens:stash-file', Stashes = 'gitlens:stashes', @@ -34,10 +51,15 @@ export enum ResourceType { StatusUpstream = 'gitlens:status-upstream' } +export type Explorer = GitExplorer | ResultsExplorer; + // let id = 0; export abstract class ExplorerNode extends Disposable { + readonly supportsPaging: boolean = false; + maxCount: number | undefined; + protected children: ExplorerNode[] | undefined; protected disposable: Disposable | undefined; // protected readonly id: number; @@ -65,7 +87,9 @@ export abstract class ExplorerNode extends Disposable { return undefined; } - resetChildren() { + refresh(): void { } + + resetChildren(): void { if (this.children !== undefined) { this.children.forEach(c => c.dispose()); this.children = undefined; @@ -73,6 +97,13 @@ export abstract class ExplorerNode extends Disposable { } } +export abstract class ExplorerRefNode extends ExplorerNode { + abstract get ref(): string; + get repoPath(): string { + return this.uri.repoPath!; + } +} + export class MessageNode extends ExplorerNode { constructor( @@ -99,7 +130,7 @@ export class PagerNode extends ExplorerNode { constructor( private readonly message: string, private readonly node: ExplorerNode, - protected readonly context: ExtensionContext + protected readonly explorer: Explorer ) { super(new GitUri()); } @@ -113,8 +144,8 @@ export class PagerNode extends ExplorerNode { item.contextValue = ResourceType.Pager; item.command = this.getCommand(); item.iconPath = { - dark: this.context.asAbsolutePath('images/dark/icon-unfold.svg'), - light: this.context.asAbsolutePath('images/light/icon-unfold.svg') + dark: this.explorer.context.asAbsolutePath('images/dark/icon-unfold.svg'), + light: this.explorer.context.asAbsolutePath('images/light/icon-unfold.svg') }; return item; } @@ -122,7 +153,7 @@ export class PagerNode extends ExplorerNode { getCommand(): Command | undefined { return { title: 'Refresh', - command: 'gitlens.gitExplorer.refreshNode', + command: this.explorer.getQualifiedCommand('refreshNode'), arguments: [this.node, this.args] } as Command; } @@ -135,8 +166,8 @@ export class ShowAllNode extends PagerNode { constructor( message: string, node: ExplorerNode, - context: ExtensionContext + explorer: Explorer ) { - super(`${message} ${GlyphChars.Space}${GlyphChars.Dash}${GlyphChars.Space} this may take a while`, node, context); + super(`${message} ${GlyphChars.Space}${GlyphChars.Dash}${GlyphChars.Space} this may take a while`, node, explorer); } } \ No newline at end of file diff --git a/src/views/explorerNodes.ts b/src/views/explorerNodes.ts index 3448e20..ae8485c 100644 --- a/src/views/explorerNodes.ts +++ b/src/views/explorerNodes.ts @@ -3,9 +3,12 @@ export * from './explorerNode'; export * from './activeRepositoryNode'; export * from './branchesNode'; -export * from './branchHistoryNode'; +export * from './branchNode'; export * from './commitFileNode'; export * from './commitNode'; +export * from './commitsNode'; +export * from './commitsResultsNode'; +export * from './comparisionResultsNode'; export * from './fileHistoryNode'; export * from './historyNode'; export * from './remoteNode'; @@ -16,6 +19,8 @@ export * from './stashesNode'; export * from './stashFileNode'; export * from './stashNode'; export * from './statusFileCommitsNode'; +export * from './statusFileNode'; export * from './statusFilesNode'; +export * from './statusFilesResultsNode'; export * from './statusNode'; export * from './statusUpstreamNode'; \ No newline at end of file diff --git a/src/views/fileHistoryNode.ts b/src/views/fileHistoryNode.ts index 9c271bf..6386353 100644 --- a/src/views/fileHistoryNode.ts +++ b/src/views/fileHistoryNode.ts @@ -3,8 +3,8 @@ import { Iterables } from '../system'; import { Disposable, TreeItem, TreeItemCollapsibleState } from 'vscode'; import { CommitFileNode, CommitFileNodeDisplayAs } from './commitFileNode'; import { ExplorerNode, MessageNode, ResourceType } from './explorerNode'; -import { BlameabilityChangeEvent, BlameabilityChangeReason, GitCommitType, GitLogCommit, GitService, GitUri, Repository, RepositoryChange, RepositoryChangeEvent } from '../gitService'; import { GitExplorer } from './gitExplorer'; +import { BlameabilityChangeEvent, BlameabilityChangeReason, GitCommitType, GitLogCommit, GitService, GitUri, Repository, RepositoryChange, RepositoryChangeEvent } from '../gitService'; import { Logger } from '../logger'; export class FileHistoryNode extends ExplorerNode { diff --git a/src/views/folderNode.ts b/src/views/folderNode.ts index c79143a..93f6e3f 100644 --- a/src/views/folderNode.ts +++ b/src/views/folderNode.ts @@ -1,9 +1,8 @@ 'use strict'; import { Arrays, Objects } from '../system'; import { TreeItem, TreeItemCollapsibleState } from 'vscode'; -import { GitExplorerFilesLayout, IGitExplorerConfig } from '../configuration'; -import { ExplorerNode, ResourceType } from './explorerNode'; -import { GitExplorer } from './gitExplorer'; +import { ExplorerFilesLayout, IExplorerConfig } from '../configuration'; +import { Explorer, ExplorerNode, ResourceType } from './explorerNode'; import { GitUri } from '../gitService'; export interface IFileExplorerNode extends ExplorerNode { @@ -23,7 +22,7 @@ export class FolderNode extends ExplorerNode { public readonly folderName: string, public readonly relativePath: string | undefined, public readonly root: Arrays.IHierarchicalItem, - private readonly explorer: GitExplorer + private readonly explorer: Explorer ) { super(GitUri.fromRepoPath(repoPath)); } @@ -34,7 +33,7 @@ export class FolderNode extends ExplorerNode { let children: (FolderNode | IFileExplorerNode)[]; const nesting = FolderNode.getFileNesting(this.explorer.config, this.root.descendants, this.relativePath === undefined); - if (nesting !== GitExplorerFilesLayout.List) { + if (nesting !== ExplorerFilesLayout.List) { children = []; for (const folder of Objects.values(this.root.children)) { if (folder.value === undefined) { @@ -71,14 +70,14 @@ export class FolderNode extends ExplorerNode { return this.folderName; } - static getFileNesting(config: IGitExplorerConfig, children: T[], isRoot: boolean): GitExplorerFilesLayout { - const nesting = config.files.layout || GitExplorerFilesLayout.Auto; - if (nesting === GitExplorerFilesLayout.Auto) { + static getFileNesting(config: IExplorerConfig, children: T[], isRoot: boolean): ExplorerFilesLayout { + const nesting = config.files.layout || ExplorerFilesLayout.Auto; + if (nesting === ExplorerFilesLayout.Auto) { if (isRoot || config.files.compact) { const nestingThreshold = config.files.threshold || 5; - if (children.length <= nestingThreshold) return GitExplorerFilesLayout.List; + if (children.length <= nestingThreshold) return ExplorerFilesLayout.List; } - return GitExplorerFilesLayout.Tree; + return ExplorerFilesLayout.Tree; } return nesting; } diff --git a/src/views/gitExplorer.ts b/src/views/gitExplorer.ts index dcc739f..90a849e 100644 --- a/src/views/gitExplorer.ts +++ b/src/views/gitExplorer.ts @@ -1,26 +1,16 @@ 'use strict'; import { Functions } from '../system'; -import { ConfigurationChangeEvent, Disposable, Event, EventEmitter, ExtensionContext, TextDocumentShowOptions, TextEditor, TreeDataProvider, TreeItem, Uri, window } from 'vscode'; +import { commands, ConfigurationChangeEvent, ConfigurationTarget, Disposable, Event, EventEmitter, ExtensionContext, TextDocumentShowOptions, TextEditor, TreeDataProvider, TreeItem, Uri, window } from 'vscode'; import { UriComparer } from '../comparers'; -import { configuration, IGitExplorerConfig } from '../configuration'; +import { configuration, ExplorerFilesLayout, IGitExplorerConfig } from '../configuration'; import { CommandContext, GlyphChars, setCommandContext, WorkspaceState } from '../constants'; -import { ExplorerCommands } from './explorerCommands'; -import { BranchHistoryNode, ExplorerNode, HistoryNode, MessageNode, RepositoriesNode, RepositoryNode } from './explorerNodes'; +import { ExplorerCommands, RefreshNodeCommandArgs } from './explorerCommands'; +import { ExplorerNode, HistoryNode, MessageNode, RefreshReason, RepositoriesNode, RepositoryNode } from './explorerNodes'; import { GitChangeEvent, GitChangeReason, GitContextTracker, GitService, GitUri } from '../gitService'; import { Logger } from '../logger'; export * from './explorerNodes'; -enum RefreshReason { - ActiveEditorChanged = 'active-editor-changed', - AutoRefreshChanged = 'auto-refresh-changed', - Command = 'command', - NodeCommand = 'node-command', - RepoChanged = 'repo-changed', - ViewChanged = 'view-changed', - VisibleEditorsChanged = 'visible-editors-changed' -} - export enum GitExplorerView { Auto = 'auto', History = 'history', @@ -32,10 +22,6 @@ export interface OpenFileRevisionCommandArgs { showOptions?: TextDocumentShowOptions; } -export interface RefreshNodeCommandArgs { - maxCount?: number; -} - export class GitExplorer implements TreeDataProvider { private _config: IGitExplorerConfig; @@ -54,11 +40,22 @@ export class GitExplorer implements TreeDataProvider { constructor( public readonly context: ExtensionContext, + readonly explorerCommands: ExplorerCommands, public readonly git: GitService, public readonly gitContextTracker: GitContextTracker ) { + commands.registerCommand('gitlens.gitExplorer.refresh', this.refresh, this); + commands.registerCommand('gitlens.gitExplorer.refreshNode', this.refreshNode, this); + commands.registerCommand('gitlens.gitExplorer.setFilesLayoutToAuto', () => this.setFilesLayout(ExplorerFilesLayout.Auto), this); + commands.registerCommand('gitlens.gitExplorer.setFilesLayoutToList', () => this.setFilesLayout(ExplorerFilesLayout.List), this); + commands.registerCommand('gitlens.gitExplorer.setFilesLayoutToTree', () => this.setFilesLayout(ExplorerFilesLayout.Tree), this); + + commands.registerCommand('gitlens.gitExplorer.setAutoRefreshToOn', () => this.setAutoRefresh(configuration.get(configuration.name('gitExplorer')('autoRefresh').value), true), this); + commands.registerCommand('gitlens.gitExplorer.setAutoRefreshToOff', () => this.setAutoRefresh(configuration.get(configuration.name('gitExplorer')('autoRefresh').value), false), this); + commands.registerCommand('gitlens.gitExplorer.switchToHistoryView', () => this.switchTo(GitExplorerView.History), this); + commands.registerCommand('gitlens.gitExplorer.switchToRepositoryView', () => this.switchTo(GitExplorerView.Repository), this); + context.subscriptions.push( - new ExplorerCommands(this), window.onDidChangeActiveTextEditor(Functions.debounce(this.onActiveEditorChanged, 500), this), window.onDidChangeVisibleTextEditors(Functions.debounce(this.onVisibleEditorsChanged, 500), this), configuration.onDidChange(this.onConfigurationChanged, this) @@ -87,10 +84,6 @@ export class GitExplorer implements TreeDataProvider { this.setAutoRefresh(cfg.autoRefresh); } - if (initializing || configuration.changed(e, section('files')('layout').value)) { - setCommandContext(CommandContext.GitExplorerFilesLayout, cfg.files.layout); - } - let view = cfg.view; if (view === GitExplorerView.Auto) { view = this.context.workspaceState.get(WorkspaceState.GitExplorerView, GitExplorerView.Repository); @@ -200,10 +193,15 @@ export class GitExplorer implements TreeDataProvider { return new HistoryNode(uri, repo, this); } - async refresh(reason: RefreshReason | undefined, root?: ExplorerNode) { + getQualifiedCommand(command: string) { + return `gitlens.gitExplorer.${command}`; + } + + async refresh(reason?: RefreshReason, root?: ExplorerNode) { if (reason === undefined) { reason = RefreshReason.Command; } + Logger.log(`GitExplorer[view=${this._view}].refresh`, `reason='${reason}'`); if (this._root === undefined || (root === undefined && this._view === GitExplorerView.History)) { @@ -217,18 +215,12 @@ export class GitExplorer implements TreeDataProvider { refreshNode(node: ExplorerNode, args?: RefreshNodeCommandArgs) { Logger.log(`GitExplorer[view=${this._view}].refreshNode`); - // Since the root node won't actually refresh, force it - if (node === this._root) { - this._onDidChangeTreeData.fire(); - - return; - } - - if (args !== undefined && node instanceof BranchHistoryNode) { + if (args !== undefined && node.supportsPaging) { node.maxCount = args.maxCount; } - this._onDidChangeTreeData.fire(node); + // Since the root node won't actually refresh, force everything + this._onDidChangeTreeData.fire(node === this._root ? undefined : node); } async reset(view: GitExplorerView, force: boolean = false) { @@ -252,6 +244,10 @@ export class GitExplorer implements TreeDataProvider { this._root = undefined; } + private async setFilesLayout(layout: ExplorerFilesLayout) { + return configuration.update(configuration.name('gitExplorer')('files')('layout').value, layout, ConfigurationTarget.Global); + } + private setRoot(root: ExplorerNode | undefined): boolean { if (this._root === root) return false; diff --git a/src/views/historyNode.ts b/src/views/historyNode.ts index 8242e27..2ef24de 100644 --- a/src/views/historyNode.ts +++ b/src/views/historyNode.ts @@ -2,8 +2,8 @@ import { TreeItem, TreeItemCollapsibleState } from 'vscode'; import { ExplorerNode, ResourceType } from './explorerNode'; import { FileHistoryNode } from './fileHistoryNode'; -import { GitUri, Repository } from '../gitService'; import { GitExplorer } from './gitExplorer'; +import { GitUri, Repository } from '../gitService'; export class HistoryNode extends ExplorerNode { diff --git a/src/views/remoteNode.ts b/src/views/remoteNode.ts index 0be14a3..28e7e7a 100644 --- a/src/views/remoteNode.ts +++ b/src/views/remoteNode.ts @@ -1,10 +1,9 @@ 'use strict'; import { Iterables } from '../system'; import { TreeItem, TreeItemCollapsibleState } from 'vscode'; -import { BranchHistoryNode } from './branchHistoryNode'; +import { BranchNode } from './branchNode'; import { GlyphChars } from '../constants'; -import { ExplorerNode, ResourceType } from './explorerNode'; -import { GitExplorer } from './gitExplorer'; +import { Explorer, ExplorerNode, ResourceType } from './explorerNode'; import { GitRemote, GitRemoteType, GitUri, Repository } from '../gitService'; export class RemoteNode extends ExplorerNode { @@ -13,7 +12,7 @@ export class RemoteNode extends ExplorerNode { public readonly remote: GitRemote, uri: GitUri, private readonly repo: Repository, - private readonly explorer: GitExplorer + private readonly explorer: Explorer ) { super(uri); } @@ -23,7 +22,7 @@ export class RemoteNode extends ExplorerNode { if (branches === undefined) return []; branches.sort((a, b) => a.name.localeCompare(b.name)); - return [...Iterables.filterMap(branches, b => !b.remote || !b.name.startsWith(this.remote.name) ? undefined : new BranchHistoryNode(b, this.uri, this.explorer))]; + return [...Iterables.filterMap(branches, b => !b.remote || !b.name.startsWith(this.remote.name) ? undefined : new BranchNode(b, this.uri, this.explorer))]; } getTreeItem(): TreeItem { diff --git a/src/views/remotesNode.ts b/src/views/remotesNode.ts index de084d6..7e8b45c 100644 --- a/src/views/remotesNode.ts +++ b/src/views/remotesNode.ts @@ -1,8 +1,7 @@ 'use strict'; import { Iterables } from '../system'; import { TreeItem, TreeItemCollapsibleState } from 'vscode'; -import { ExplorerNode, MessageNode, ResourceType } from './explorerNode'; -import { GitExplorer } from './gitExplorer'; +import { Explorer, ExplorerNode, MessageNode, ResourceType } from './explorerNode'; import { GitUri, Repository } from '../gitService'; import { RemoteNode } from './remoteNode'; @@ -11,7 +10,7 @@ export class RemotesNode extends ExplorerNode { constructor( uri: GitUri, private readonly repo: Repository, - private readonly explorer: GitExplorer + private readonly explorer: Explorer ) { super(uri); } diff --git a/src/views/resultsExplorer.ts b/src/views/resultsExplorer.ts new file mode 100644 index 0000000..b64ffa7 --- /dev/null +++ b/src/views/resultsExplorer.ts @@ -0,0 +1,181 @@ +'use strict'; +import { Functions } from '../system'; +import { commands, ConfigurationChangeEvent, ConfigurationTarget, Event, EventEmitter, ExtensionContext, TreeDataProvider, TreeItem } from 'vscode'; +import { configuration, ExplorerFilesLayout, IExplorerConfig } from '../configuration'; +import { CommandContext, setCommandContext, WorkspaceState } from '../constants'; +import { ExplorerCommands, RefreshNodeCommandArgs } from './explorerCommands'; +import { CommitsResultsNode, ComparisionResultsNode, ExplorerNode, MessageNode, RefreshReason, ResourceType } from './explorerNodes'; +import { GitLog, GitService } from '../gitService'; +import { Logger } from '../logger'; + +export * from './explorerNodes'; + +let _instance: ResultsExplorer; + +export class ResultsExplorer implements TreeDataProvider { + + static get instance(): ResultsExplorer { + return _instance; + } + + private _config: IExplorerConfig; + private _roots: ExplorerNode[] = []; + + private _onDidChangeTreeData = new EventEmitter(); + public get onDidChangeTreeData(): Event { + return this._onDidChangeTreeData.event; + } + + constructor( + public readonly context: ExtensionContext, + readonly explorerCommands: ExplorerCommands, + public readonly git: GitService + ) { + _instance = this; + + commands.registerCommand('gitlens.resultsExplorer.refresh', this.refreshNodes, this); + commands.registerCommand('gitlens.resultsExplorer.refreshNode', this.refreshNode, this); + commands.registerCommand('gitlens.resultsExplorer.setFilesLayoutToAuto', () => this.setFilesLayout(ExplorerFilesLayout.Auto), this); + commands.registerCommand('gitlens.resultsExplorer.setFilesLayoutToList', () => this.setFilesLayout(ExplorerFilesLayout.List), this); + commands.registerCommand('gitlens.resultsExplorer.setFilesLayoutToTree', () => this.setFilesLayout(ExplorerFilesLayout.Tree), this); + + commands.registerCommand('gitlens.resultsExplorer.clearResultsNode', this.clearResultsNode, this); + commands.registerCommand('gitlens.resultsExplorer.close', this.close, this); + commands.registerCommand('gitlens.resultsExplorer.setKeepResultsToOn', () => this.setKeepResults(true), this); + commands.registerCommand('gitlens.resultsExplorer.setKeepResultsToOff', () => this.setKeepResults(false), this); + + setCommandContext(CommandContext.ResultsExplorerKeepResults, this.keepResults); + + context.subscriptions.push( + configuration.onDidChange(this.onConfigurationChanged, this) + ); + this.onConfigurationChanged(configuration.initializingChangeEvent); + } + + private async onConfigurationChanged(e: ConfigurationChangeEvent) { + const initializing = configuration.initializing(e); + + const section = configuration.name('resultsExplorer'); + if (!initializing && !configuration.changed(e, section.value)) return; + + const cfg = configuration.get(section.value); + + if (!initializing && this._roots.length !== 0) { + this.refresh(RefreshReason.ConfigurationChanged); + } + + this._config = cfg; + } + + get config(): IExplorerConfig { + return this._config; + } + + get keepResults(): boolean { + return this.context.workspaceState.get(WorkspaceState.ResultsExplorerKeepResults, false); + } + + close() { + this.clearResults(); + setCommandContext(CommandContext.ResultsExplorer, false); + } + + async getChildren(node?: ExplorerNode): Promise { + if (this._roots.length === 0) return [new MessageNode('No results')]; + + if (node === undefined) return this._roots; + return node.getChildren(); + } + + async getTreeItem(node: ExplorerNode): Promise { + return node.getTreeItem(); + } + + getQualifiedCommand(command: string) { + return `gitlens.resultsExplorer.${command}`; + } + + async refresh(reason?: RefreshReason) { + if (reason === undefined) { + reason = RefreshReason.Command; + } + + Logger.log(`ResultsExplorer.refresh`, `reason='${reason}'`); + + this._onDidChangeTreeData.fire(); + } + + refreshNode(node: ExplorerNode, args?: RefreshNodeCommandArgs) { + Logger.log(`ResultsExplorer.refreshNode`); + + if (args !== undefined && node.supportsPaging) { + node.maxCount = args.maxCount; + } + node.refresh(); + + // Since a root node won't actually refresh, force everything + this._onDidChangeTreeData.fire(this._roots.includes(node) ? undefined : node); + } + + refreshNodes() { + Logger.log(`ResultsExplorer.refreshNodes`); + + this._roots.forEach(n => n.refresh()); + + this._onDidChangeTreeData.fire(); + } + + async showCommitComparisonResults(repoPath: string, ref1: string, ref2: string) { + this.addResults(new ComparisionResultsNode(repoPath, ref1, ref2, this)); + setCommandContext(CommandContext.ResultsExplorer, true); + } + + showCommitSearchResults(search: string, results: GitLog, queryFn: (maxCount: number | undefined) => Promise) { + const labelFn = (log: GitLog | undefined) => { + const count = log !== undefined ? log.count : 0; + const truncated = log !== undefined ? log.truncated : false; + + if (count === 1) return `1 result for ${search}`; + return `${count === 0 ? 'No' : `${count}${truncated ? '+' : ''}`} results for ${search}`; + }; + + this.addResults(new CommitsResultsNode(results.repoPath, labelFn, Functions.seeded(queryFn, results), this, ResourceType.SearchResults)); + setCommandContext(CommandContext.ResultsExplorer, true); + } + + private addResults(results: ExplorerNode): boolean { + if (this._roots.includes(results)) return false; + + if (this._roots.length > 0 && !this.keepResults) { + this.clearResults(); + } + + this._roots.splice(0, 0, results); + this.refreshNode(results); + return true; + } + + private clearResults() { + if (this._roots.length === 0) return; + + this._roots.forEach(r => r.dispose()); + this._roots = []; + } + + private clearResultsNode(node: ExplorerNode) { + const index = this._roots.findIndex(n => n === node); + if (index === -1) return; + + this._roots.splice(index, 1); + this.refresh(); + } + + private async setFilesLayout(layout: ExplorerFilesLayout) { + return configuration.update(configuration.name('resultsExplorer')('files')('layout').value, layout, ConfigurationTarget.Global); + } + + private setKeepResults(enabled: boolean) { + this.context.workspaceState.update(WorkspaceState.ResultsExplorerKeepResults, enabled); + setCommandContext(CommandContext.ResultsExplorerKeepResults, enabled); + } +} \ No newline at end of file diff --git a/src/views/stashFileNode.ts b/src/views/stashFileNode.ts index 1ff8472..c8dae32 100644 --- a/src/views/stashFileNode.ts +++ b/src/views/stashFileNode.ts @@ -1,7 +1,6 @@ 'use strict'; import { CommitFileNode, CommitFileNodeDisplayAs } from './commitFileNode'; -import { ResourceType } from './explorerNode'; -import { GitExplorer } from './gitExplorer'; +import { Explorer, ResourceType } from './explorerNode'; import { GitLogCommit, IGitStatusFile } from '../gitService'; export class StashFileNode extends CommitFileNode { @@ -9,7 +8,7 @@ export class StashFileNode extends CommitFileNode { constructor( status: IGitStatusFile, commit: GitLogCommit, - explorer: GitExplorer + explorer: Explorer ) { super(status, commit, explorer, CommitFileNodeDisplayAs.File); } diff --git a/src/views/stashNode.ts b/src/views/stashNode.ts index 6508c36..e1ff750 100644 --- a/src/views/stashNode.ts +++ b/src/views/stashNode.ts @@ -1,20 +1,23 @@ 'use strict'; import { Iterables } from '../system'; import { TreeItem, TreeItemCollapsibleState } from 'vscode'; -import { ExplorerNode, ResourceType } from './explorerNode'; -import { GitExplorer } from './gitExplorer'; +import { Explorer, ExplorerNode, ExplorerRefNode, ResourceType } from './explorerNode'; import { CommitFormatter, GitStashCommit, ICommitFormatOptions } from '../gitService'; import { StashFileNode } from './stashFileNode'; -export class StashNode extends ExplorerNode { +export class StashNode extends ExplorerRefNode { constructor( public readonly commit: GitStashCommit, - private readonly explorer: GitExplorer + private readonly explorer: Explorer ) { super(commit.toGitUri()); } + get ref(): string { + return this.commit.sha; + } + async getChildren(): Promise { const statuses = (this.commit as GitStashCommit).fileStatuses; diff --git a/src/views/stashesNode.ts b/src/views/stashesNode.ts index 960bd02..841a75c 100644 --- a/src/views/stashesNode.ts +++ b/src/views/stashesNode.ts @@ -1,8 +1,7 @@ 'use strict'; import { Iterables } from '../system'; import { TreeItem, TreeItemCollapsibleState } from 'vscode'; -import { ExplorerNode, MessageNode, ResourceType } from './explorerNode'; -import { GitExplorer } from './gitExplorer'; +import { Explorer, ExplorerNode, MessageNode, ResourceType } from './explorerNode'; import { GitUri, Repository } from '../gitService'; import { StashNode } from './stashNode'; @@ -11,7 +10,7 @@ export class StashesNode extends ExplorerNode { constructor( uri: GitUri, private readonly repo: Repository, - private readonly explorer: GitExplorer + private readonly explorer: Explorer ) { super(uri); } diff --git a/src/views/statusFileCommitsNode.ts b/src/views/statusFileCommitsNode.ts index 4264bec..5ff880e 100644 --- a/src/views/statusFileCommitsNode.ts +++ b/src/views/statusFileCommitsNode.ts @@ -2,8 +2,7 @@ import { Command, TreeItem, TreeItemCollapsibleState } from 'vscode'; import { Commands, DiffWithPreviousCommandArgs } from '../commands'; import { CommitFileNode, CommitFileNodeDisplayAs } from './commitFileNode'; -import { ExplorerNode, ResourceType } from './explorerNode'; -import { GitExplorer } from './gitExplorer'; +import { Explorer, ExplorerNode, ResourceType } from './explorerNode'; import { getGitStatusIcon, GitBranch, GitLogCommit, GitUri, IGitStatusFile, IGitStatusFileWithCommit, IStatusFormatOptions, StatusFileFormatter } from '../gitService'; import * as path from 'path'; @@ -13,7 +12,7 @@ export class StatusFileCommitsNode extends ExplorerNode { public readonly repoPath: string, public readonly status: IGitStatusFile, public readonly commits: GitLogCommit[], - private readonly explorer: GitExplorer, + private readonly explorer: Explorer, public readonly branch?: GitBranch ) { super(GitUri.fromFileStatus(status, repoPath, 'HEAD')); @@ -56,9 +55,16 @@ export class StatusFileCommitsNode extends ExplorerNode { private _label: string | undefined; get label() { if (this._label === undefined) { - this._label = StatusFileFormatter.fromTemplate(this.explorer.config.statusFileFormat, - { ...this.status, commit: this.commit } as IGitStatusFileWithCommit, - { relativePath: this.relativePath } as IStatusFormatOptions); + this._label = StatusFileFormatter.fromTemplate( + this.explorer.config.statusFileFormat, + { + ...this.status, + commit: this.commit + } as IGitStatusFileWithCommit, + { + relativePath: this.relativePath + } as IStatusFormatOptions + ); } return this._label; } diff --git a/src/views/statusFileNode.ts b/src/views/statusFileNode.ts new file mode 100644 index 0000000..8a3e731 --- /dev/null +++ b/src/views/statusFileNode.ts @@ -0,0 +1,95 @@ +'use strict'; +import { Command, TreeItem, TreeItemCollapsibleState } from 'vscode'; +import { Commands, DiffWithCommandArgs } from '../commands'; +import { Explorer, ExplorerNode, ResourceType } from './explorerNode'; +import { getGitStatusIcon, GitStatusFile, GitUri, IStatusFormatOptions, StatusFileFormatter } from '../gitService'; +import * as path from 'path'; + +export class StatusFileNode extends ExplorerNode { + + constructor( + readonly repoPath: string, + private readonly status: GitStatusFile, + private readonly ref1: string, + private readonly ref2: string, + private readonly explorer: Explorer + ) { + super(GitUri.fromFileStatus(status, repoPath)); + } + + getChildren(): ExplorerNode[] { + return []; + } + + getTreeItem(): TreeItem { + const item = new TreeItem(this.label, TreeItemCollapsibleState.None); + item.contextValue = ResourceType.StatusFile; + + const icon = getGitStatusIcon(this.status.status); + item.iconPath = { + dark: this.explorer.context.asAbsolutePath(path.join('images', 'dark', icon)), + light: this.explorer.context.asAbsolutePath(path.join('images', 'light', icon)) + }; + + item.command = this.getCommand(); + return item; + } + + private _folderName: string | undefined; + get folderName() { + if (this._folderName === undefined) { + this._folderName = path.dirname(this.uri.getRelativePath()); + } + return this._folderName; + } + + private _label: string | undefined; + get label() { + if (this._label === undefined) { + this._label = StatusFileFormatter.fromTemplate(this.explorer.config.statusFileFormat, this.status, { + relativePath: this.relativePath + } as IStatusFormatOptions); + } + return this._label; + } + + private _relativePath: string | undefined; + get relativePath(): string | undefined { + return this._relativePath; + } + set relativePath(value: string | undefined) { + this._relativePath = value; + this._label = undefined; + } + + get priority(): boolean { + return false; + } + + getCommand(): Command | undefined { + return { + title: 'Open Changes', + command: Commands.DiffWith, + arguments: [ + this.uri, + { + lhs: { + sha: this.ref1, + uri: this.uri + }, + rhs: { + sha: this.ref2, + uri: this.uri + }, + repoPath: this.uri.repoPath!, + + line: 0, + showOptions: { + preserveFocus: true, + preview: true + } + } as DiffWithCommandArgs + ] + }; + } +} diff --git a/src/views/statusFilesNode.ts b/src/views/statusFilesNode.ts index 66443bc..af1cbe7 100644 --- a/src/views/statusFilesNode.ts +++ b/src/views/statusFilesNode.ts @@ -1,7 +1,7 @@ 'use strict'; import { Arrays, Iterables, Objects } from '../system'; import { TreeItem, TreeItemCollapsibleState } from 'vscode'; -import { GitExplorerFilesLayout } from '../configuration'; +import { ExplorerFilesLayout } from '../configuration'; import { ExplorerNode, ResourceType, ShowAllNode } from './explorerNode'; import { FolderNode, IFileExplorerNode } from './folderNode'; import { GitExplorer } from './gitExplorer'; @@ -12,8 +12,7 @@ import * as path from 'path'; export class StatusFilesNode extends ExplorerNode { readonly repoPath: string; - - maxCount: number | undefined = undefined; + readonly supportsPaging: boolean = true; constructor( public readonly status: GitStatus, @@ -91,7 +90,7 @@ export class StatusFilesNode extends ExplorerNode { ...Iterables.map(Objects.values(groups), statuses => new StatusFileCommitsNode(repoPath, statuses[statuses.length - 1], statuses.map(s => s.commit), this.explorer, this.branch)) ]; - if (this.explorer.config.files.layout !== GitExplorerFilesLayout.List) { + if (this.explorer.config.files.layout !== ExplorerFilesLayout.List) { const hierarchy = Arrays.makeHierarchical(children, n => n.uri.getRelativePath().split('/'), (...paths: string[]) => GitService.normalizePath(path.join(...paths)), this.explorer.config.files.compact); @@ -103,7 +102,7 @@ export class StatusFilesNode extends ExplorerNode { } if (log !== undefined && log.truncated) { - (children as (IFileExplorerNode | ShowAllNode)[]).push(new ShowAllNode('Show All Changes', this, this.explorer.context)); + (children as (IFileExplorerNode | ShowAllNode)[]).push(new ShowAllNode('Show All Changes', this, this.explorer)); } return children; } diff --git a/src/views/statusFilesResultsNode.ts b/src/views/statusFilesResultsNode.ts new file mode 100644 index 0000000..9468514 --- /dev/null +++ b/src/views/statusFilesResultsNode.ts @@ -0,0 +1,82 @@ +'use strict'; +import { Arrays, Iterables } from '../system'; +import { TreeItem, TreeItemCollapsibleState } from 'vscode'; +import { ExplorerFilesLayout } from '../configuration'; +import { Explorer, ExplorerNode, ResourceType } from './explorerNode'; +import { FolderNode, IFileExplorerNode } from './folderNode'; +import { GitService, GitStatusFile, GitUri } from '../gitService'; +import { StatusFileNode } from './statusFileNode'; +import * as path from 'path'; + +export class StatusFilesResultsNode extends ExplorerNode { + + readonly supportsPaging: boolean = true; + + private _cache: { label: string, diff: GitStatusFile[] | undefined } | undefined; + + constructor( + readonly repoPath: string, + private readonly ref1: string, + private readonly ref2: string, + private readonly labelFn: (diff: GitStatusFile[] | undefined) => string, + private readonly diffFn: () => Promise, + private readonly explorer: Explorer + ) { + super(GitUri.fromRepoPath(repoPath)); + } + + async getChildren(): Promise { + const diff = await this.getDiff(); + if (diff === undefined) return []; + + let children: IFileExplorerNode[] = [...Iterables.map(diff, s => new StatusFileNode(this.repoPath, s, this.ref1, this.ref2, this.explorer))]; + + if (this.explorer.config.files.layout !== ExplorerFilesLayout.List) { + const hierarchy = Arrays.makeHierarchical(children, n => n.uri.getRelativePath().split('/'), + (...paths: string[]) => GitService.normalizePath(path.join(...paths)), this.explorer.config.files.compact); + + const root = new FolderNode(this.repoPath, '', undefined, hierarchy, this.explorer); + children = await root.getChildren() as IFileExplorerNode[]; + } + else { + children.sort((a, b) => (a.priority ? -1 : 1) - (b.priority ? -1 : 1) || a.label!.localeCompare(b.label!)); + } + + return children; + } + + async getTreeItem(): Promise { + const diff = await this.getDiff(); + + const item = new TreeItem(await this.getLabel(), diff && diff.length > 0 ? TreeItemCollapsibleState.Expanded : TreeItemCollapsibleState.None); + item.contextValue = ResourceType.Results; + return item; + } + + refresh() { + this._cache = undefined; + } + + private async ensureCache() { + if (this._cache === undefined) { + const diff = await this.diffFn(); + + this._cache = { + label: this.labelFn(diff), + diff: diff + }; + } + + return this._cache; + } + + private async getLabel() { + const cache = await this.ensureCache(); + return cache.label; + } + + private async getDiff() { + const cache = await this.ensureCache(); + return cache.diff; + } +} \ No newline at end of file diff --git a/src/views/statusUpstreamNode.ts b/src/views/statusUpstreamNode.ts index e838b6a..64ea357 100644 --- a/src/views/statusUpstreamNode.ts +++ b/src/views/statusUpstreamNode.ts @@ -2,8 +2,7 @@ import { Iterables } from '../system'; import { TreeItem, TreeItemCollapsibleState } from 'vscode'; import { CommitNode } from './commitNode'; -import { ExplorerNode, ResourceType } from './explorerNode'; -import { GitExplorer } from './gitExplorer'; +import { Explorer, ExplorerNode, ResourceType } from './explorerNode'; import { GitStatus, GitUri } from '../gitService'; export class StatusUpstreamNode extends ExplorerNode { @@ -11,7 +10,7 @@ export class StatusUpstreamNode extends ExplorerNode { constructor( public readonly status: GitStatus, public readonly direction: 'ahead' | 'behind', - private readonly explorer: GitExplorer + private readonly explorer: Explorer ) { super(GitUri.fromRepoPath(status.repoPath)); }