Преглед на файлове

Adds commit search results view

Adds comparison results view
main
Eric Amodio преди 7 години
родител
ревизия
ff5780e696
променени са 44 файла, в които са добавени 1564 реда и са изтрити 502 реда
  1. +21
    -1
      CHANGELOG.md
  2. +43
    -9
      README.md
  3. +4
    -0
      images/dark/icon-close.svg
  4. +4
    -0
      images/dark/icon-lock.svg
  5. +6
    -0
      images/dark/icon-locked.svg
  6. +4
    -0
      images/light/icon-close.svg
  7. +4
    -0
      images/light/icon-lock.svg
  8. +6
    -0
      images/light/icon-locked.svg
  9. +544
    -224
      package.json
  10. +11
    -3
      src/commands/showCommitSearch.ts
  11. +38
    -15
      src/configuration.ts
  12. +9
    -6
      src/constants.ts
  13. +10
    -1
      src/extension.ts
  14. +6
    -3
      src/git/parsers/statusParser.ts
  15. +4
    -1
      src/gitService.ts
  16. +5
    -1
      src/quickPicks/commits.ts
  17. +19
    -1
      src/quickPicks/common.ts
  18. +13
    -0
      src/system/function.ts
  19. +0
    -74
      src/views/branchHistoryNode.ts
  20. +77
    -0
      src/views/branchNode.ts
  21. +4
    -5
      src/views/branchesNode.ts
  22. +16
    -9
      src/views/commitFileNode.ts
  23. +9
    -9
      src/views/commitNode.ts
  24. +36
    -0
      src/views/commitsNode.ts
  25. +69
    -0
      src/views/commitsResultsNode.ts
  26. +56
    -0
      src/views/comparisionResultsNode.ts
  27. +62
    -48
      src/views/explorerCommands.ts
  28. +45
    -14
      src/views/explorerNode.ts
  29. +6
    -1
      src/views/explorerNodes.ts
  30. +1
    -1
      src/views/fileHistoryNode.ts
  31. +9
    -10
      src/views/folderNode.ts
  32. +29
    -33
      src/views/gitExplorer.ts
  33. +1
    -1
      src/views/historyNode.ts
  34. +4
    -5
      src/views/remoteNode.ts
  35. +2
    -3
      src/views/remotesNode.ts
  36. +181
    -0
      src/views/resultsExplorer.ts
  37. +2
    -3
      src/views/stashFileNode.ts
  38. +7
    -4
      src/views/stashNode.ts
  39. +2
    -3
      src/views/stashesNode.ts
  40. +12
    -6
      src/views/statusFileCommitsNode.ts
  41. +95
    -0
      src/views/statusFileNode.ts
  42. +4
    -5
      src/views/statusFilesNode.ts
  43. +82
    -0
      src/views/statusFilesResultsNode.ts
  44. +2
    -3
      src/views/statusUpstreamNode.ts

+ 21
- 1
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

+ 43
- 9
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 `#<sha>` to search for a commit with id of `<sha>` -- See [Git docs](https://git-scm.com/docs/git-log)
- Use `~<regex>` to search for commits with differences whose patch text contains added/removed lines that match `<regex>` -- See [Git docs](https://git-scm.com/docs/git-log#git-log--Gltregexgt)
- Use `=<regex>` 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<br /> `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<br /> `list` - displays files as a list<br /> `tree` - displays files as a tree
|`gitlens.gitExplorer.files.compact`|Specifies whether or not to compact (flatten) unnecessary file nesting in the `GitLens` view<br />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<br />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<br />Available tokens<br /> ${id} - commit id<br /> ${author} - commit author<br /> ${message} - commit message<br /> ${ago} - relative commit date (e.g. 1 day ago)<br /> ${date} - formatted commit date (format specified by `gitlens.statusBar.dateFormat`)<br /> ${authorAgo} - commit author, relative commit date<br />See https://github.com/eamodio/vscode-gitlens/wiki/Advanced-Formatting for advanced formatting
|`gitlens.gitExplorer.commitFileFormat`|Specifies the format of a committed file in the `GitLens` view<br />Available tokens<br /> ${directory} - directory name<br /> ${file} - file name<br /> ${filePath} - formatted file name and path<br /> ${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<br />Available tokens<br /> ${id} - commit id<br /> ${author} - commit author<br /> ${message} - commit message<br /> ${ago} - relative commit date (e.g. 1 day ago)<br /> ${date} - formatted commit date (format specified by `gitlens.statusBar.dateFormat`)<br /> ${authorAgo} - commit author, relative commit date<br />See https://github.com/eamodio/vscode-gitlens/wiki/Advanced-Formatting for advanced formatting
|`gitlens.gitExplorer.stashFileFormat`|Specifies the format of a stashed file in the `GitLens` view<br />Available tokens<br /> ${directory} - directory name<br /> ${file} - file name<br /> ${filePath} - formatted file name and path<br /> ${path} - full file path
|`gitlens.gitExplorer.statusFileFormat`|Specifies the format of the status of a working or committed file in the `GitLens` view<br />Available tokens<br /> ${directory} - directory name<br /> ${file} - file name<br /> ${filePath} - formatted file name and path<br /> ${path} - full file path<br />${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<br /> `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<br /> `list` - displays files as a list<br /> `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<br />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<br />Only applies when displaying files as `auto`
|`gitlens.resultsExplorer.commitFormat`|Specifies the format of committed changes in the `GitLens Results` view<br />Available tokens<br /> ${id} - commit id<br /> ${author} - commit author<br /> ${message} - commit message<br /> ${ago} - relative commit date (e.g. 1 day ago)<br /> ${date} - formatted commit date (format specified by `gitlens.statusBar.dateFormat`)<br /> ${authorAgo} - commit author, relative commit date<br />See https://github.com/eamodio/vscode-gitlens/wiki/Advanced-Formatting for advanced formatting
|`gitlens.resultsExplorer.commitFileFormat`|Specifies the format of a committed file in the `GitLens Results` view<br />Available tokens<br /> ${directory} - directory name<br /> ${file} - file name<br /> ${filePath} - formatted file name and path<br /> ${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<br />Available tokens<br /> ${id} - commit id<br /> ${author} - commit author<br /> ${message} - commit message<br /> ${ago} - relative commit date (e.g. 1 day ago)<br /> ${date} - formatted commit date (format specified by `gitlens.statusBar.dateFormat`)<br /> ${authorAgo} - commit author, relative commit date<br />See https://github.com/eamodio/vscode-gitlens/wiki/Advanced-Formatting for advanced formatting
|`gitlens.resultsExplorer.stashFileFormat`|Specifies the format of a stashed file in the `GitLens Results` view<br />Available tokens<br /> ${directory} - directory name<br /> ${file} - file name<br /> ${filePath} - formatted file name and path<br /> ${path} - full file path
|`gitlens.resultsExplorer.statusFileFormat`|Specifies the format of the status of a working or committed file in the `GitLens Results` view<br />Available tokens<br /> ${directory} - directory name<br /> ${file} - file name<br /> ${filePath} - formatted file name and path<br /> ${path} - full file path<br />${working} - optional indicator if the file is uncommitted
### Custom Remotes Settings
|Name | Description

+ 4
- 0
images/dark/icon-close.svg Целия файл

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<svg width="16" height="22" version="1.1" xmlns="http://www.w3.org/2000/svg">
<path fill="#C5C5C5" d="m9.67454,11l4.24292,4.24292l-1.67454,1.67454l-4.24292,-4.24292l-4.24292,4.24292l-1.67454,-1.67454l4.24292,-4.24292l-4.24292,-4.24292l1.67454,-1.67454l4.24292,4.24292l4.24292,-4.24292l1.67454,1.67454l-4.24292,4.24292z" />
</svg>

+ 4
- 0
images/dark/icon-lock.svg Целия файл

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<svg width="16" height="22" version="1.1" xmlns="http://www.w3.org/2000/svg">
<path fill="#C5C5C5" d="m11.8752,9.10094l-5.94244,0c0,-0.01402 0,-0.01402 0,-0.02803l0,-1.77993c0,-0.61667 0.50455,-1.12121 1.12121,-1.12121l2.06023,0c0.61667,0 1.12121,0.50455 1.12121,1.12121s1.12122,0.42046 1.12122,0.11212c0,-1.34546 -1.00909,-2.35455 -2.24243,-2.35455l-2.06023,0c-1.23334,0 -2.24243,1.00909 -2.24243,2.24243l0,1.79394c0,0.01402 0,0.01402 0,0.02803l-0.68674,0c-0.56061,0 -1.02311,0.4625 -1.02311,1.02311l0,5.78827c0,0.56061 0.4625,1.02311 1.02311,1.02311l7.7504,0c0.56061,0 1.02311,-0.4625 1.02311,-1.02311l0,-5.78827c0,-0.57462 -0.4625,-1.03712 -1.02311,-1.03712zm-0.09811,6.72729l-7.55419,0l0,-5.60607l7.55419,0l0,5.60607z" />
</svg>

+ 6
- 0
images/dark/icon-locked.svg Целия файл

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<svg width="16" height="22" version="1.1" xmlns="http://www.w3.org/2000/svg">
<rect fill="#C5C5C5" x="10.25" y="7.4375" width="1.125" height="2" />
<path fill="#C5C5C5" d="m11.8752,9.10094l-5.94244,0c0,-0.01402 0,-0.01402 0,-0.02803l0,-1.77993c0,-0.61667 0.50455,-1.12121 1.12121,-1.12121l2.06023,0c0.61667,0 1.12121,0.50455 1.12121,1.12121s1.12122,0.42046 1.12122,0.11212c0,-1.34546 -1.00909,-2.35455 -2.24243,-2.35455l-2.06023,0c-1.23334,0 -2.24243,1.00909 -2.24243,2.24243l0,1.79394c0,0.01402 0,0.01402 0,0.02803l-0.68674,0c-0.56061,0 -1.02311,0.4625 -1.02311,1.02311l0,5.78827c0,0.56061 0.4625,1.02311 1.02311,1.02311l7.7504,0c0.56061,0 1.02311,-0.4625 1.02311,-1.02311l0,-5.78827c0,-0.57462 -0.4625,-1.03712 -1.02311,-1.03712zm-0.09811,6.72729l-7.55419,0l0,-5.60607l7.55419,0l0,5.60607z" />
<ellipse fill="#C5C5C5" rx="1" ry="1" cx="8" cy="13" />
</svg>

+ 4
- 0
images/light/icon-close.svg Целия файл

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<svg width="16" height="22" version="1.1" xmlns="http://www.w3.org/2000/svg">
<path fill="#424242" d="m9.67454,11l4.24292,4.24292l-1.67454,1.67454l-4.24292,-4.24292l-4.24292,4.24292l-1.67454,-1.67454l4.24292,-4.24292l-4.24292,-4.24292l1.67454,-1.67454l4.24292,4.24292l4.24292,-4.24292l1.67454,1.67454l-4.24292,4.24292z" />
</svg>

+ 4
- 0
images/light/icon-lock.svg Целия файл

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<svg width="16" height="22" version="1.1" xmlns="http://www.w3.org/2000/svg">
<path fill="#424242" d="m11.8752,9.10094l-5.94244,0c0,-0.01402 0,-0.01402 0,-0.02803l0,-1.77993c0,-0.61667 0.50455,-1.12121 1.12121,-1.12121l2.06023,0c0.61667,0 1.12121,0.50455 1.12121,1.12121s1.12122,0.42046 1.12122,0.11212c0,-1.34546 -1.00909,-2.35455 -2.24243,-2.35455l-2.06023,0c-1.23334,0 -2.24243,1.00909 -2.24243,2.24243l0,1.79394c0,0.01402 0,0.01402 0,0.02803l-0.68674,0c-0.56061,0 -1.02311,0.4625 -1.02311,1.02311l0,5.78827c0,0.56061 0.4625,1.02311 1.02311,1.02311l7.7504,0c0.56061,0 1.02311,-0.4625 1.02311,-1.02311l0,-5.78827c0,-0.57462 -0.4625,-1.03712 -1.02311,-1.03712zm-0.09811,6.72729l-7.55419,0l0,-5.60607l7.55419,0l0,5.60607z" />
</svg>

+ 6
- 0
images/light/icon-locked.svg Целия файл

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<svg width="16" height="22" version="1.1" xmlns="http://www.w3.org/2000/svg">
<rect fill="#424242" x="10.25" y="7.4375" width="1.125" height="2" />
<path fill="#424242" d="m11.8752,9.10094l-5.94244,0c0,-0.01402 0,-0.01402 0,-0.02803l0,-1.77993c0,-0.61667 0.50455,-1.12121 1.12121,-1.12121l2.06023,0c0.61667,0 1.12121,0.50455 1.12121,1.12121s1.12122,0.42046 1.12122,0.11212c0,-1.34546 -1.00909,-2.35455 -2.24243,-2.35455l-2.06023,0c-1.23334,0 -2.24243,1.00909 -2.24243,2.24243l0,1.79394c0,0.01402 0,0.01402 0,0.02803l-0.68674,0c-0.56061,0 -1.02311,0.4625 -1.02311,1.02311l0,5.78827c0,0.56061 0.4625,1.02311 1.02311,1.02311l7.7504,0c0.56061,0 1.02311,-0.4625 1.02311,-1.02311l0,-5.78827c0,-0.57462 -0.4625,-1.03712 -1.02311,-1.03712zm-0.09811,6.72729l-7.55419,0l0,-5.60607l7.55419,0l0,5.60607z" />
<ellipse fill="#424242" rx="1" ry="1" cx="8" cy="13" />
</svg>

+ 544
- 224
package.json
Файловите разлики са ограничени, защото са твърде много
Целия файл


+ 11
- 3
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();

+ 38
- 15
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',

+ 9
- 6
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'
}

+ 10
- 1
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);
}

+ 6
- 3
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(

+ 4
- 1
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 {

+ 5
- 1
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<CommitQuickPickItem | CommandQuickPickItem | undefined> {
static async show(git: GitService, log: GitLog | undefined, placeHolder: string, progressCancellation: CancellationTokenSource, goBackCommand?: CommandQuickPickItem, showInResultsExplorerCommand?: CommandQuickPickItem): Promise<CommitQuickPickItem | CommandQuickPickItem | undefined> {
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);
}

+ 19
- 1
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<GitLog | undefined>,
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;
}
}

+ 13
- 0
src/system/function.ts Целия файл

@ -33,6 +33,19 @@ export namespace Functions {
return propOfCore(o, key);
}
export function seeded<T>(fn: (...args: any[]) => Promise<T>, seed: T): (...args: any[]) => Promise<T> {
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));
}

+ 0
- 74
src/views/branchHistoryNode.ts Целия файл

@ -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<ExplorerNode[]> {
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<TreeItem> {
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;
}
}

+ 77
- 0
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<ExplorerNode[]> {
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<TreeItem> {
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;
}
}

+ 4
- 5
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<TreeItem> {

+ 16
- 9
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;
}

+ 9
- 9
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<ExplorerNode[]> {
@ -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);

+ 36
- 0
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<GitLog | undefined>,
private readonly explorer: Explorer
) {
super(GitUri.fromRepoPath(repoPath));
}
async getChildren(): Promise<ExplorerNode[]> {
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<TreeItem> {
const item = new TreeItem('Commits', TreeItemCollapsibleState.Collapsed);
item.contextValue = ResourceType.Commits;
return item;
}
}

+ 69
- 0
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<GitLog | undefined>,
private readonly explorer: Explorer,
private readonly contextValue: ResourceType = ResourceType.Results
) {
super(GitUri.fromRepoPath(repoPath));
}
async getChildren(): Promise<ExplorerNode[]> {
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<TreeItem> {
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;
}
}

+ 56
- 0
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<ExplorerNode[]> {
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<TreeItem> {
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;
}
}

+ 62
- 48
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<boolean>(configuration.name('gitExplorer')('autoRefresh').value), true), this);
commands.registerCommand('gitlens.gitExplorer.setAutoRefreshToOff', () => this.explorer.setAutoRefresh(configuration.get<boolean>(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;

+ 45
- 14
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);
}
}

+ 6
- 1
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';

+ 1
- 1
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 {

+ 9
- 10
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<IFileExplorerNode>,
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<T extends IFileExplorerNode>(config: IGitExplorerConfig, children: T[], isRoot: boolean): GitExplorerFilesLayout {
const nesting = config.files.layout || GitExplorerFilesLayout.Auto;
if (nesting === GitExplorerFilesLayout.Auto) {
static getFileNesting<T extends IFileExplorerNode>(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;
}

+ 29
- 33
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<ExplorerNode> {
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<boolean>(configuration.name('gitExplorer')('autoRefresh').value), true), this);
commands.registerCommand('gitlens.gitExplorer.setAutoRefreshToOff', () => this.setAutoRefresh(configuration.get<boolean>(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<GitExplorerView>(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;

+ 1
- 1
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 {

+ 4
- 5
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 {

+ 2
- 3
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);
}

+ 181
- 0
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<ExplorerNode> {
static get instance(): ResultsExplorer {
return _instance;
}
private _config: IExplorerConfig;
private _roots: ExplorerNode[] = [];
private _onDidChangeTreeData = new EventEmitter<ExplorerNode>();
public get onDidChangeTreeData(): Event<ExplorerNode> {
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<IExplorerConfig>(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<boolean>(WorkspaceState.ResultsExplorerKeepResults, false);
}
close() {
this.clearResults();
setCommandContext(CommandContext.ResultsExplorer, false);
}
async getChildren(node?: ExplorerNode): Promise<ExplorerNode[]> {
if (this._roots.length === 0) return [new MessageNode('No results')];
if (node === undefined) return this._roots;
return node.getChildren();
}
async getTreeItem(node: ExplorerNode): Promise<TreeItem> {
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<GitLog | undefined>) {
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);
}
}

+ 2
- 3
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);
}

+ 7
- 4
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<ExplorerNode[]> {
const statuses = (this.commit as GitStashCommit).fileStatuses;

+ 2
- 3
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);
}

+ 12
- 6
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;
}

+ 95
- 0
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
]
};
}
}

+ 4
- 5
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;
}

+ 82
- 0
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<GitStatusFile[] | undefined>,
private readonly explorer: Explorer
) {
super(GitUri.fromRepoPath(repoPath));
}
async getChildren(): Promise<ExplorerNode[]> {
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<TreeItem> {
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;
}
}

+ 2
- 3
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));
}

Зареждане…
Отказ
Запис