From 3f3b8d28d6ad783efc687a16be9e5f0d9eaf8497 Mon Sep 17 00:00:00 2001 From: Eric Amodio Date: Thu, 8 Nov 2018 00:09:31 -0500 Subject: [PATCH] Adds Search Commits view Renames showCommitInResults to showCommitInView Renames showFileHistoryInResults to showFileHistoryInView --- images/dark/icon-clear.svg | 4 + images/light/icon-clear.svg | 4 + package.json | 377 ++++++++++++++++++++---------- src/commands/common.ts | 21 +- src/commands/diffBranchWithBranch.ts | 2 +- src/commands/showCommitSearch.ts | 34 +-- src/commands/showQuickCommitDetails.ts | 16 +- src/commands/showQuickFileHistory.ts | 44 ++-- src/commands/showView.ts | 5 +- src/constants.ts | 6 +- src/container.ts | 23 ++ src/quickpicks/branchHistoryQuickPick.ts | 24 +- src/quickpicks/commitQuickPick.ts | 6 +- src/quickpicks/commonQuickPicks.ts | 47 ++-- src/quickpicks/fileHistoryQuickPick.ts | 6 +- src/system/function.ts | 28 +-- src/ui/config.ts | 7 + src/views/fileHistoryView.ts | 17 +- src/views/lineHistoryView.ts | 5 - src/views/nodes.ts | 2 +- src/views/nodes/common.ts | 2 +- src/views/nodes/fileHistoryTrackerNode.ts | 23 +- src/views/nodes/resultsCommitNode.ts | 28 --- src/views/nodes/resultsNode.ts | 165 +++++++------ src/views/nodes/searchNode.ts | 140 +++++++++++ src/views/nodes/viewNode.ts | 17 +- src/views/repositoriesView.ts | 5 - src/views/resultsView.ts | 116 ++------- src/views/searchView.ts | 226 ++++++++++++++++++ src/views/viewBase.ts | 3 +- src/views/viewCommands.ts | 27 ++- 31 files changed, 941 insertions(+), 489 deletions(-) create mode 100644 images/dark/icon-clear.svg create mode 100644 images/light/icon-clear.svg delete mode 100644 src/views/nodes/resultsCommitNode.ts create mode 100644 src/views/nodes/searchNode.ts create mode 100644 src/views/searchView.ts diff --git a/images/dark/icon-clear.svg b/images/dark/icon-clear.svg new file mode 100644 index 0000000..0317c44 --- /dev/null +++ b/images/dark/icon-clear.svg @@ -0,0 +1,4 @@ + + + + diff --git a/images/light/icon-clear.svg b/images/light/icon-clear.svg new file mode 100644 index 0000000..21e1875 --- /dev/null +++ b/images/light/icon-clear.svg @@ -0,0 +1,4 @@ + + + + diff --git a/package.json b/package.json index 279d651..22f46cf 100644 --- a/package.json +++ b/package.json @@ -1256,7 +1256,7 @@ "gitlens.views.repositories.includeWorkingTree": { "type": "boolean", "default": true, - "description": "Specifies whether to include working tree files inside the `Repository Status` node of the `Repositories` view", + "description": "Specifies whether to include working tree files inside the `Repository` node of the `Repositories` view", "scope": "window" }, "gitlens.views.repositories.location": { @@ -1296,7 +1296,7 @@ "tree" ], "enumDescriptions": [ - "Automatically switches between displaying files as a `tree` or `list` based on the `#gitlens.views.repositories.files.threshold#` value and the number of files at each nesting level", + "Automatically switches between displaying files as a `tree` or `list` based on the `#gitlens.views.results.files.threshold#` value and the number of files at each nesting level", "Displays files as a list", "Displays files as a tree" ], @@ -1325,6 +1325,56 @@ "description": "Specifies where to show the `Results` view", "scope": "window" }, + "gitlens.views.search.enabled": { + "type": "boolean", + "default": true, + "description": "Specifies whether to show the `Search Commits` view", + "scope": "window" + }, + "gitlens.views.search.files.compact": { + "type": "boolean", + "default": true, + "description": "Specifies whether to compact (flatten) unnecessary file nesting in the `Search Commits` view\nOnly applies when `#gitlens.views.search.files.layout#` is set to `tree` or `auto`", + "scope": "window" + }, + "gitlens.views.search.files.layout": { + "type": "string", + "default": "auto", + "enum": [ + "auto", + "list", + "tree" + ], + "enumDescriptions": [ + "Automatically switches between displaying files as a `tree` or `list` based on the `#gitlens.views.search.files.threshold#` value and the number of files at each nesting level", + "Displays files as a list", + "Displays files as a tree" + ], + "description": "Specifies how the `Results` view will display files", + "scope": "window" + }, + "gitlens.views.search.files.threshold": { + "type": "number", + "default": 5, + "description": "Specifies when to switch between displaying files as a `tree` or `list` based on the number of files in a nesting level in the `Search Commits` view\nOnly applies when `#gitlens.views.search.files.layout#` is set to `auto`", + "scope": "window" + }, + "gitlens.views.search.location": { + "type": "string", + "default": "gitlens", + "enum": [ + "gitlens", + "explorer", + "scm" + ], + "enumDescriptions": [ + "Adds to the GitLens view", + "Adds to the Explorer view", + "Adds to the Source Control view" + ], + "description": "Specifies where to show the `Search Commits` view", + "scope": "window" + }, "gitlens.views.stashFileFormat": { "type": "string", "default": "${filePath}", @@ -1548,6 +1598,11 @@ "category": "GitLens" }, { + "command": "gitlens.showSearchView", + "title": "Show Search Commits View", + "category": "GitLens" + }, + { "command": "gitlens.diffDirectory", "title": "Directory Compare Working Tree with...", "category": "GitLens" @@ -1703,13 +1758,13 @@ "category": "GitLens" }, { - "command": "gitlens.showCommitInResults", - "title": "Show Commit in Results", + "command": "gitlens.showCommitInView", + "title": "Show Commit in View", "category": "GitLens" }, { - "command": "gitlens.showFileHistoryInResults", - "title": "Show File History in Results", + "command": "gitlens.showFileHistoryInView", + "title": "Show File History in View", "category": "GitLens" }, { @@ -2181,11 +2236,6 @@ } }, { - "command": "gitlens.views.repositories.refreshNode", - "title": "Refresh", - "category": "GitLens" - }, - { "command": "gitlens.views.repositories.setFilesLayoutToAuto", "title": "Automatic Layout", "category": "GitLens" @@ -2220,11 +2270,6 @@ } }, { - "command": "gitlens.views.fileHistory.refreshNode", - "title": "Refresh", - "category": "GitLens" - }, - { "command": "gitlens.views.fileHistory.changeBase", "title": "Change Base...", "category": "GitLens", @@ -2271,11 +2316,6 @@ } }, { - "command": "gitlens.views.lineHistory.refreshNode", - "title": "Refresh", - "category": "GitLens" - }, - { "command": "gitlens.views.lineHistory.changeBase", "title": "Change Base...", "category": "GitLens", @@ -2322,15 +2362,6 @@ } }, { - "command": "gitlens.views.results.dismissNode", - "title": "Dismiss", - "category": "GitLens", - "icon": { - "dark": "images/dark/icon-close-small.svg", - "light": "images/light/icon-close-small.svg" - } - }, - { "command": "gitlens.views.results.refresh", "title": "Refresh", "category": "GitLens", @@ -2340,11 +2371,6 @@ } }, { - "command": "gitlens.views.results.refreshNode", - "title": "Refresh", - "category": "GitLens" - }, - { "command": "gitlens.views.results.setFilesLayoutToAuto", "title": "Automatic Layout", "category": "GitLens" @@ -2385,6 +2411,71 @@ "dark": "images/dark/icon-swap.svg", "light": "images/light/icon-swap.svg" } + }, + { + "command": "gitlens.views.search.clear", + "title": "Clear Results", + "category": "GitLens", + "icon": { + "dark": "images/dark/icon-clear.svg", + "light": "images/light/icon-clear.svg" + } + }, + { + "command": "gitlens.views.search.refresh", + "title": "Refresh", + "category": "GitLens", + "icon": { + "dark": "images/dark/icon-refresh.svg", + "light": "images/light/icon-refresh.svg" + } + }, + { + "command": "gitlens.views.search.setFilesLayoutToAuto", + "title": "Automatic Layout", + "category": "GitLens" + }, + { + "command": "gitlens.views.search.setFilesLayoutToList", + "title": "List Layout", + "category": "GitLens" + }, + { + "command": "gitlens.views.search.setFilesLayoutToTree", + "title": "Tree Layout", + "category": "GitLens" + }, + { + "command": "gitlens.views.search.setKeepResultsToOn", + "title": "Keep Results", + "category": "GitLens", + "icon": { + "dark": "images/dark/icon-lock.svg", + "light": "images/light/icon-lock.svg" + } + }, + { + "command": "gitlens.views.search.setKeepResultsToOff", + "title": "Keep Results", + "category": "GitLens", + "icon": { + "dark": "images/dark/icon-locked.svg", + "light": "images/light/icon-locked.svg" + } + }, + { + "command": "gitlens.views.dismissNode", + "title": "Dismiss", + "category": "GitLens", + "icon": { + "dark": "images/dark/icon-close-small.svg", + "light": "images/light/icon-close-small.svg" + } + }, + { + "command": "gitlens.views.refreshNode", + "title": "Refresh", + "category": "GitLens" } ], "menus": { @@ -2506,11 +2597,11 @@ "when": "gitlens:enabled" }, { - "command": "gitlens.showCommitInResults", + "command": "gitlens.showCommitInView", "when": "gitlens:activeFileStatus =~ /blameable/" }, { - "command": "gitlens.showFileHistoryInResults", + "command": "gitlens.showFileHistoryInView", "when": "gitlens:activeFileStatus =~ /tracked/" }, { @@ -2794,10 +2885,6 @@ "when": "false" }, { - "command": "gitlens.views.repositories.refreshNode", - "when": "false" - }, - { "command": "gitlens.views.repositories.setFilesLayoutToAuto", "when": "false" }, @@ -2822,10 +2909,6 @@ "when": "false" }, { - "command": "gitlens.views.fileHistory.refreshNode", - "when": "false" - }, - { "command": "gitlens.views.fileHistory.changeBase", "when": "false" }, @@ -2850,10 +2933,6 @@ "when": "false" }, { - "command": "gitlens.views.lineHistory.refreshNode", - "when": "false" - }, - { "command": "gitlens.views.lineHistory.changeBase", "when": "false" }, @@ -2878,18 +2957,10 @@ "when": "false" }, { - "command": "gitlens.views.results.dismissNode", - "when": "false" - }, - { "command": "gitlens.views.results.refresh", "when": "false" }, { - "command": "gitlens.views.results.refreshNode", - "when": "false" - }, - { "command": "gitlens.views.results.setFilesLayoutToAuto", "when": "false" }, @@ -2912,6 +2983,42 @@ { "command": "gitlens.views.results.swapComparision", "when": "false" + }, + { + "command": "gitlens.views.search.clear", + "when": "false" + }, + { + "command": "gitlens.views.search.refresh", + "when": "false" + }, + { + "command": "gitlens.views.search.setFilesLayoutToAuto", + "when": "false" + }, + { + "command": "gitlens.views.search.setFilesLayoutToList", + "when": "false" + }, + { + "command": "gitlens.views.search.setFilesLayoutToTree", + "when": "false" + }, + { + "command": "gitlens.views.search.setKeepResultsToOn", + "when": "false" + }, + { + "command": "gitlens.views.search.setKeepResultsToOff", + "when": "false" + }, + { + "command": "gitlens.views.dismissNode", + "when": "false" + }, + { + "command": "gitlens.views.refreshNode", + "when": "false" } ], "editor/context": [ @@ -3146,153 +3253,178 @@ ], "view/title": [ { - "command": "gitlens.showCommitSearch", - "when": "gitlens:enabled && view =~ /^gitlens.views.repositories:/", - "group": "navigation@1" - }, - { "command": "gitlens.pushRepositories", - "when": "gitlens:enabled && view =~ /^gitlens.views.repositories:/", + "when": "gitlens:enabled && view =~ /^gitlens\\.views\\.repositories:/", "group": "navigation@6" }, { "command": "gitlens.pullRepositories", - "when": "gitlens:enabled && view =~ /^gitlens.views.repositories:/", + "when": "gitlens:enabled && view =~ /^gitlens\\.views\\.repositories:/", "group": "navigation@7" }, { "command": "gitlens.fetchRepositories", - "when": "gitlens:enabled && view =~ /^gitlens.views.repositories:/", + "when": "gitlens:enabled && view =~ /^gitlens\\.views\\.repositories:/", "group": "navigation@8" }, { "command": "gitlens.views.repositories.refresh", - "when": "view =~ /^gitlens.views.repositories:/", - "group": "navigation@9" + "when": "view =~ /^gitlens\\.views\\.repositories:/", + "group": "navigation@98" }, { "command": "gitlens.views.repositories.setFilesLayoutToAuto", - "when": "view =~ /^gitlens.views.repositories:/", + "when": "view =~ /^gitlens\\.views\\.repositories:/", "group": "1_gitlens" }, { "command": "gitlens.views.repositories.setFilesLayoutToList", - "when": "view =~ /^gitlens.views.repositories:/", + "when": "view =~ /^gitlens\\.views\\.repositories:/", "group": "1_gitlens" }, { "command": "gitlens.views.repositories.setFilesLayoutToTree", - "when": "view =~ /^gitlens.views.repositories:/", + "when": "view =~ /^gitlens\\.views\\.repositories:/", "group": "1_gitlens" }, { "command": "gitlens.views.repositories.setAutoRefreshToOn", - "when": "view =~ /^gitlens.views.repositories:/ && config.gitlens.views.repositories.autoRefresh && !gitlens:views:repositories:autoRefresh", + "when": "view =~ /^gitlens\\.views\\.repositories:/ && config.gitlens.views.repositories.autoRefresh && !gitlens:views:repositories:autoRefresh", "group": "2_gitlens" }, { "command": "gitlens.views.repositories.setAutoRefreshToOff", - "when": "view =~ /^gitlens.views.repositories:/ && config.gitlens.views.repositories.autoRefresh && gitlens:views:repositories:autoRefresh", + "when": "view =~ /^gitlens\\.views\\.repositories:/ && config.gitlens.views.repositories.autoRefresh && gitlens:views:repositories:autoRefresh", "group": "2_gitlens" }, { "command": "gitlens.views.fileHistory.setEditorFollowingOn", - "when": "view =~ /^gitlens.views.fileHistory:/ && !gitlens:views:fileHistory:editorFollowing", + "when": "view =~ /^gitlens\\.views\\.fileHistory:/ && !gitlens:views:fileHistory:editorFollowing", "group": "navigation@1" }, { "command": "gitlens.views.fileHistory.setEditorFollowingOff", - "when": "view =~ /^gitlens.views.fileHistory:/ && gitlens:views:fileHistory:editorFollowing", + "when": "view =~ /^gitlens\\.views\\.fileHistory:/ && gitlens:views:fileHistory:editorFollowing", "group": "navigation@1" }, { "command": "gitlens.views.fileHistory.changeBase", - "when": "view =~ /^gitlens.views.fileHistory:/", + "when": "view =~ /^gitlens\\.views\\.fileHistory:/", "group": "navigation@2" }, { "command": "gitlens.views.fileHistory.refresh", - "when": "view =~ /^gitlens.views.fileHistory:/", + "when": "view =~ /^gitlens\\.views\\.fileHistory:/", "group": "navigation@99" }, { "command": "gitlens.views.fileHistory.setRenameFollowingOn", - "when": "view =~ /^gitlens.views.fileHistory:/ && !config.gitlens.advanced.fileHistoryFollowsRenames", + "when": "view =~ /^gitlens\\.views\\.fileHistory:/ && !config.gitlens.advanced.fileHistoryFollowsRenames", "group": "1_gitlens" }, { "command": "gitlens.views.fileHistory.setRenameFollowingOff", - "when": "view =~ /^gitlens.views.fileHistory:/ && config.gitlens.advanced.fileHistoryFollowsRenames", + "when": "view =~ /^gitlens\\.views\\.fileHistory:/ && config.gitlens.advanced.fileHistoryFollowsRenames", "group": "1_gitlens" }, { "command": "gitlens.views.lineHistory.setEditorFollowingOn", - "when": "view =~ /^gitlens.views.lineHistory:/ && !gitlens:views:lineHistory:editorFollowing", + "when": "view =~ /^gitlens\\.views\\.lineHistory:/ && !gitlens:views:lineHistory:editorFollowing", "group": "navigation@1" }, { "command": "gitlens.views.lineHistory.setEditorFollowingOff", - "when": "view =~ /^gitlens.views.lineHistory:/ && gitlens:views:lineHistory:editorFollowing", + "when": "view =~ /^gitlens\\.views\\.lineHistory:/ && gitlens:views:lineHistory:editorFollowing", "group": "navigation@1" }, { "command": "gitlens.views.lineHistory.changeBase", - "when": "view =~ /^gitlens.views.lineHistory:/", + "when": "view =~ /^gitlens\\.views\\.lineHistory:/", "group": "navigation@2" }, { "command": "gitlens.views.lineHistory.refresh", - "when": "view =~ /^gitlens.views.lineHistory:/", + "when": "view =~ /^gitlens\\.views\\.lineHistory:/", "group": "navigation@99" }, { "command": "gitlens.views.lineHistory.setRenameFollowingOn", - "when": "view =~ /^gitlens.views.lineHistory:/ && !config.gitlens.advanced.fileHistoryFollowsRenames", + "when": "view =~ /^gitlens\\.views\\.lineHistory:/ && !config.gitlens.advanced.fileHistoryFollowsRenames", "group": "1_gitlens" }, { "command": "gitlens.views.lineHistory.setRenameFollowingOff", - "when": "view =~ /^gitlens.views.lineHistory:/ && config.gitlens.advanced.fileHistoryFollowsRenames", + "when": "view =~ /^gitlens\\.views\\.lineHistory:/ && config.gitlens.advanced.fileHistoryFollowsRenames", "group": "1_gitlens" }, { - "command": "gitlens.showCommitSearch", - "when": "gitlens:enabled && view =~ /^gitlens.views.results:/", - "group": "navigation@1" - }, - { "command": "gitlens.views.results.setKeepResultsToOn", - "when": "view =~ /^gitlens.views.results:/ && !gitlens:views:results:keepResults", + "when": "view =~ /^gitlens\\.views\\.results:/ && !gitlens:views:results:keepResults", "group": "navigation@2" }, { "command": "gitlens.views.results.setKeepResultsToOff", - "when": "view =~ /^gitlens.views.results:/ && gitlens:views:results:keepResults", + "when": "view =~ /^gitlens\\.views\\.results:/ && gitlens:views:results:keepResults", "group": "navigation@2" }, { "command": "gitlens.views.results.refresh", - "when": "view =~ /^gitlens.views.results:/", + "when": "view =~ /^gitlens\\.views\\.results:/", "group": "navigation@3" }, { "command": "gitlens.views.results.close", - "when": "view =~ /^gitlens.views.results:/", + "when": "view =~ /^gitlens\\.views\\.results:/", "group": "navigation@9" }, { "command": "gitlens.views.results.setFilesLayoutToAuto", - "when": "view =~ /^gitlens.views.results:/", + "when": "view =~ /^gitlens\\.views\\.results:/", "group": "1_gitlens" }, { "command": "gitlens.views.results.setFilesLayoutToList", - "when": "view =~ /^gitlens.views.results:/", + "when": "view =~ /^gitlens\\.views\\.results:/", "group": "1_gitlens" }, { "command": "gitlens.views.results.setFilesLayoutToTree", - "when": "view =~ /^gitlens.views.results:/", + "when": "view =~ /^gitlens\\.views\\.results:/", + "group": "1_gitlens" + }, + { + "command": "gitlens.views.search.clear", + "when": "view =~ /^gitlens\\.views\\.search:/", + "group": "navigation@2" + }, + { + "command": "gitlens.views.search.setKeepResultsToOn", + "when": "view =~ /^gitlens\\.views\\.search:/ && !gitlens:views:search:keepResults", + "group": "navigation@3" + }, + { + "command": "gitlens.views.search.setKeepResultsToOff", + "when": "view =~ /^gitlens\\.views\\.search:/ && gitlens:views:search:keepResults", + "group": "navigation@3" + }, + { + "command": "gitlens.views.search.refresh", + "when": "view =~ /^gitlens\\.views\\.search:/", + "group": "navigation@99" + }, + { + "command": "gitlens.views.search.setFilesLayoutToAuto", + "when": "view =~ /^gitlens\\.views\\.search:/", + "group": "1_gitlens" + }, + { + "command": "gitlens.views.search.setFilesLayoutToList", + "when": "view =~ /^gitlens\\.views\\.search:/", + "group": "1_gitlens" + }, + { + "command": "gitlens.views.search.setFilesLayoutToTree", + "when": "view =~ /^gitlens\\.views\\.search:/", "group": "1_gitlens" } ], @@ -3460,7 +3592,7 @@ "group": "5_gitlens_1@1" }, { - "command": "gitlens.showCommitInResults", + "command": "gitlens.showCommitInView", "when": "viewItem =~ /gitlens:commit\\b/", "group": "5_gitlens_1@2" }, @@ -3591,7 +3723,7 @@ "group": "5_gitlens_2@2" }, { - "command": "gitlens.showCommitInResults", + "command": "gitlens.showCommitInView", "when": "viewItem =~ /gitlens:file\\b(?!(:stash|:status))/", "group": "5_gitlens_2@3" }, @@ -3601,7 +3733,7 @@ "group": "8_gitlens@1" }, { - "command": "gitlens.showFileHistoryInResults", + "command": "gitlens.showFileHistoryInView", "when": "viewItem =~ /gitlens:file\\b/", "group": "8_gitlens@2" }, @@ -3641,6 +3773,11 @@ "group": "7_gitlens_more@1" }, { + "command": "gitlens.showCommitSearch", + "when": "viewItem == gitlens:repository", + "group": "inline@1" + }, + { "command": "gitlens.views.push", "when": "viewItem == gitlens:repository && gitlens:hasRemotes", "group": "inline@97", @@ -3733,13 +3870,13 @@ "group": "inline@1" }, { - "command": "gitlens.views.results.dismissNode", - "when": "viewItem =~ /gitlens:results\\b(?!:(commits|files))/", + "command": "gitlens.views.dismissNode", + "when": "viewItem =~ /gitlens:(results|search)\\b(?!:(commits|files))/", "group": "inline@2" }, { - "command": "gitlens.views.results.dismissNode", - "when": "viewItem =~ /gitlens:results\\b(?!:(commits|files))/", + "command": "gitlens.views.dismissNode", + "when": "viewItem =~ /gitlens:(results|search)\\b(?!:(commits|files))/", "group": "1_gitlens@1" }, { @@ -3793,23 +3930,8 @@ "group": "8_gitlens" }, { - "command": "gitlens.views.repositories.refreshNode", - "when": "view =~ /^gitlens.views.repositories:/ && viewItem =~ /gitlens:(?!file\\b)/", - "group": "9_gitlens@1" - }, - { - "command": "gitlens.views.results.refreshNode", - "when": "view =~ /^gitlens.views.results:/ && viewItem =~ /gitlens:(?!file\\b)/", - "group": "9_gitlens@1" - }, - { - "command": "gitlens.views.fileHistory.refreshNode", - "when": "view =~ /^gitlens.views.fileHistory:/ && viewItem =~ /gitlens:(?!file\\b)/", - "group": "9_gitlens@1" - }, - { - "command": "gitlens.views.lineHistory.refreshNode", - "when": "view =~ /^gitlens.views.lineHistory:/ && viewItem =~ /gitlens:(?!file\\b)/", + "command": "gitlens.views.refreshNode", + "when": "view =~ /^gitlens\\.views\\./ && viewItem =~ /gitlens:(?!file\\b)/", "group": "9_gitlens@1" } ] @@ -4031,6 +4153,11 @@ "id": "gitlens.views.results:gitlens", "name": "Results", "when": "gitlens:enabled && gitlens:views:results && config.gitlens.views.results.location == gitlens" + }, + { + "id": "gitlens.views.search:gitlens", + "name": "Search Commits", + "when": "config.gitlens.views.search.enabled && config.gitlens.views.search.location == gitlens" } ], "explorer": [ @@ -4053,6 +4180,11 @@ "id": "gitlens.views.results:explorer", "name": "GitLens: Results", "when": "gitlens:enabled && gitlens:views:results && config.gitlens.views.results.location == explorer" + }, + { + "id": "gitlens.views.search:explorer", + "name": "GitLens: Search Commits", + "when": "config.gitlens.views.search.enabled && config.gitlens.views.search.location == explorer" } ], "scm": [ @@ -4075,6 +4207,11 @@ "id": "gitlens.views.results:scm", "name": "GitLens: Results", "when": "gitlens:enabled && gitlens:views:results && config.gitlens.views.results.location == scm" + }, + { + "id": "gitlens.views.search:scm", + "name": "GitLens: Search Commits", + "when": "config.gitlens.views.search.enabled && config.gitlens.views.search.location == scm" } ] } diff --git a/src/commands/common.ts b/src/commands/common.ts index cf367e1..d3cea3e 100644 --- a/src/commands/common.ts +++ b/src/commands/common.ts @@ -56,10 +56,10 @@ export enum Commands { PullRepositories = 'gitlens.pullRepositories', PushRepositories = 'gitlens.pushRepositories', ResetSuppressedWarnings = 'gitlens.resetSuppressedWarnings', - ShowCommitInResults = 'gitlens.showCommitInResults', + ShowCommitInView = 'gitlens.showCommitInView', ShowCommitSearch = 'gitlens.showCommitSearch', ShowFileHistoryView = 'gitlens.showFileHistoryView', - ShowFileHistoryInResults = 'gitlens.showFileHistoryInResults', + ShowFileHistoryInView = 'gitlens.showFileHistoryInView', ShowLineHistoryView = 'gitlens.showLineHistoryView', ShowLastQuickPick = 'gitlens.showLastQuickPick', ShowQuickBranchHistory = 'gitlens.showQuickBranchHistory', @@ -72,6 +72,7 @@ export enum Commands { ShowQuickStashList = 'gitlens.showQuickStashList', ShowRepositoriesView = 'gitlens.showRepositoriesView', ShowResultsView = 'gitlens.showResultsView', + ShowSearchView = 'gitlens.showSearchView', ShowSettingsPage = 'gitlens.showSettingsPage', ShowWelcomePage = 'gitlens.showWelcomePage', StashApply = 'gitlens.stashApply', @@ -197,9 +198,9 @@ export interface CommandUriContext extends CommandBaseContext { type: 'uri'; } -export interface CommandViewContext extends CommandBaseContext { - type: 'view'; -} +// export interface CommandViewContext extends CommandBaseContext { +// type: 'view'; +// } export interface CommandViewItemContext extends CommandBaseContext { type: 'viewItem'; @@ -287,7 +288,7 @@ export type CommandContext = | CommandScmStatesContext | CommandUnknownContext | CommandUriContext - | CommandViewContext + // | CommandViewContext | CommandViewItemContext; function isScmResourceGroup(group: any): group is SourceControlResourceGroup { @@ -373,15 +374,11 @@ export abstract class Command implements Disposable { firstArg = args[0]; } - let maybeView = false; if (options.uri && (firstArg == null || firstArg instanceof Uri)) { const [uri, ...rest] = args as [Uri, any]; if (uri !== undefined) { return [{ command: command, type: 'uri', editor: editor, uri: uri }, rest]; } - else { - maybeView = args.length === 0; - } } if (firstArg instanceof ViewNode) { @@ -418,10 +415,6 @@ export abstract class Command implements Disposable { return [{ command: command, type: 'scm-groups', scmResourceGroups: groups }, args.slice(count)]; } - if (maybeView) { - return [{ command: command, type: 'view', editor: editor }, args]; - } - return [{ command: command, type: 'unknown', editor: editor }, args]; } } diff --git a/src/commands/diffBranchWithBranch.ts b/src/commands/diffBranchWithBranch.ts index 60669bc..9557cad 100644 --- a/src/commands/diffBranchWithBranch.ts +++ b/src/commands/diffBranchWithBranch.ts @@ -75,7 +75,7 @@ export class DiffBranchWithBranchCommand extends ActiveEditorCommand { if (args.ref1 === undefined) return undefined; } - await Container.resultsView.addComparison(repoPath, args.ref1, args.ref2); + await Container.resultsView.compare(repoPath, args.ref1, args.ref2); return undefined; } diff --git a/src/commands/showCommitSearch.ts b/src/commands/showCommitSearch.ts index 8537b15..110a07c 100644 --- a/src/commands/showCommitSearch.ts +++ b/src/commands/showCommitSearch.ts @@ -5,7 +5,7 @@ import { Container } from '../container'; import { GitRepoSearchBy, GitService, GitUri } from '../git/gitService'; import { Logger } from '../logger'; import { Messages } from '../messages'; -import { CommandQuickPickItem, CommitsQuickPick, ShowCommitsSearchInResultsQuickPickItem } from '../quickpicks'; +import { CommandQuickPickItem, CommitsQuickPick, ShowCommitSearchResultsInViewQuickPickItem } from '../quickpicks'; import { Iterables, Strings } from '../system'; import { ActiveEditorCachedCommand, @@ -39,7 +39,7 @@ export interface ShowCommitSearchCommandArgs { search?: string; searchBy?: GitRepoSearchBy; maxCount?: number; - showInResults?: boolean; + showInView?: boolean; goBackCommand?: CommandQuickPickItem; } @@ -51,14 +51,17 @@ export class ShowCommitSearchCommand extends ActiveEditorCachedCommand { } protected async preExecute(context: CommandContext, args: ShowCommitSearchCommandArgs = {}) { - if (context.type === 'view' || context.type === 'viewItem') { + if (context.type === 'viewItem') { args = { ...args }; - args.showInResults = true; + args.showInView = true; if (isCommandViewContextWithRepo(context)) { return this.execute(context.editor, context.node.uri, args); } } + else { + // TODO: Add a user setting (default to view?) + } return this.execute(context.editor, context.uri, args); } @@ -86,10 +89,14 @@ export class ShowCommitSearchCommand extends ActiveEditorCachedCommand { selection = [1, 1]; } + if (args.showInView) { + await Container.searchView.show(); + } + args.search = await window.showInputBox({ value: args.search, prompt: `Please enter a search string`, - placeHolder: `search by message, author (@), files (:), commit id (#), changes (=), changed lines (~)`, + placeHolder: `Search commits by message, author (@), files (:), commit id (#), changes (=), changed lines (~)`, valueSelection: selection } as InputBoxOptions); if (args.search === undefined) { @@ -142,14 +149,11 @@ export class ShowCommitSearchCommand extends ActiveEditorCachedCommand { break; } - if (args.showInResults) { - void Container.resultsView.addSearchResults( - repoPath, - Container.git.getLogForSearch(repoPath, args.search!, args.searchBy!, { - maxCount: args.maxCount - }), - { label: searchLabel! } - ); + if (args.showInView) { + void Container.searchView.search(repoPath, args.search, args.searchBy, { + maxCount: args.maxCount, + label: { label: searchLabel! } + }); return; } @@ -189,7 +193,9 @@ export class ShowCommitSearchCommand extends ActiveEditorCachedCommand { ) : undefined, showInResultsCommand: - log !== undefined ? new ShowCommitsSearchInResultsQuickPickItem(log, searchLabel!) : undefined + log !== undefined + ? new ShowCommitSearchResultsInViewQuickPickItem(log, { label: searchLabel! }) + : undefined }); if (pick === undefined) return undefined; diff --git a/src/commands/showQuickCommitDetails.ts b/src/commands/showQuickCommitDetails.ts index f686660..58e0a91 100644 --- a/src/commands/showQuickCommitDetails.ts +++ b/src/commands/showQuickCommitDetails.ts @@ -3,7 +3,7 @@ import * as paths from 'path'; import { commands, TextEditor, Uri } from 'vscode'; import { GlyphChars } from '../constants'; import { Container } from '../container'; -import { GitCommit, GitLog, GitLogCommit, GitUri } from '../git/gitService'; +import { GitCommit, GitLog, GitLogCommit, GitRepoSearchBy, GitUri } from '../git/gitService'; import { Logger } from '../logger'; import { Messages } from '../messages'; import { CommandQuickPickItem, CommitQuickPick, CommitWithFileStatusQuickPickItem } from '../quickpicks'; @@ -22,7 +22,7 @@ export interface ShowQuickCommitDetailsCommandArgs { sha?: string; commit?: GitCommit | GitLogCommit; repoLog?: GitLog; - showInResults?: boolean; + showInView?: boolean; goBackCommand?: CommandQuickPickItem; } @@ -40,13 +40,13 @@ export class ShowQuickCommitDetailsCommand extends ActiveEditorCachedCommand { } constructor() { - super([Commands.ShowCommitInResults, Commands.ShowQuickCommitDetails, Commands.ShowQuickRevisionDetails]); + super([Commands.ShowCommitInView, Commands.ShowQuickCommitDetails, Commands.ShowQuickRevisionDetails]); } protected async preExecute(context: CommandContext, args: ShowQuickCommitDetailsCommandArgs = {}): Promise { - if (context.command === Commands.ShowCommitInResults) { + if (context.command === Commands.ShowCommitInView) { args = { ...args }; - args.showInResults = true; + args.showInView = true; } if (context.command === Commands.ShowQuickRevisionDetails && context.editor !== undefined) { @@ -135,8 +135,10 @@ export class ShowQuickCommitDetailsCommand extends ActiveEditorCachedCommand { args.commit.workingFileName = workingFileName; } - if (args.showInResults) { - void (await Container.resultsView.addCommit(args.commit as GitLogCommit)); + if (args.showInView) { + void (await Container.searchView.search(repoPath!, args.commit.sha, GitRepoSearchBy.Sha, { + label: { label: `commits with an id matching '${args.commit.shortSha}'` } + })); return undefined; } diff --git a/src/commands/showQuickFileHistory.ts b/src/commands/showQuickFileHistory.ts index 1452e61..0914239 100644 --- a/src/commands/showQuickFileHistory.ts +++ b/src/commands/showQuickFileHistory.ts @@ -10,7 +10,7 @@ import { ChooseFromBranchesAndTagsQuickPickItem, CommandQuickPickItem, FileHistoryQuickPick, - ShowCommitsInResultsQuickPickItem + ShowFileHistoryInViewQuickPickItem } from '../quickpicks'; import { Iterables, Strings } from '../system'; import { ActiveEditorCachedCommand, command, CommandContext, Commands, getCommandUri } from './common'; @@ -21,7 +21,7 @@ export interface ShowQuickFileHistoryCommandArgs { log?: GitLog; maxCount?: number; range?: Range; - showInResults?: boolean; + showInView?: boolean; goBackCommand?: CommandQuickPickItem; nextPageCommand?: CommandQuickPickItem; @@ -30,13 +30,13 @@ export interface ShowQuickFileHistoryCommandArgs { @command() export class ShowQuickFileHistoryCommand extends ActiveEditorCachedCommand { constructor() { - super([Commands.ShowFileHistoryInResults, Commands.ShowQuickFileHistory]); + super([Commands.ShowFileHistoryInView, Commands.ShowQuickFileHistory]); } protected async preExecute(context: CommandContext, args: ShowQuickFileHistoryCommandArgs = {}): Promise { - if (context.command === Commands.ShowFileHistoryInResults) { + if (context.command === Commands.ShowFileHistoryInView) { args = { ...args }; - args.showInResults = true; + args.showInView = true; } return this.execute(context.editor, context.uri, args); @@ -48,16 +48,19 @@ export class ShowQuickFileHistoryCommand extends ActiveEditorCachedCommand { const gitUri = await GitUri.fromUri(uri); + if (args.showInView) { + await Container.fileHistoryView.showHistoryForUri(gitUri); + + return undefined; + } + args = { ...args }; const placeHolder = `${gitUri.getFormattedPath({ suffix: args.branchOrTag ? ` (${args.branchOrTag.name})` : undefined })}${gitUri.sha ? ` ${Strings.pad(GlyphChars.Dot, 1, 1)} ${gitUri.shortSha}` : ''}`; - let progressCancellation; - if (!args.showInResults) { - progressCancellation = FileHistoryQuickPick.showProgress(placeHolder); - } + const progressCancellation = FileHistoryQuickPick.showProgress(placeHolder); try { if (args.log === undefined) { @@ -78,15 +81,6 @@ export class ShowQuickFileHistoryCommand extends ActiveEditorCachedCommand { return undefined; } - if (args.showInResults) { - void (await Container.resultsView.addSearchResults(gitUri.repoPath!, args.log, { - label: placeHolder, - resultsType: { singular: 'commit', plural: 'commits' } - })); - - return undefined; - } - let previousPageCommand: CommandQuickPickItem | undefined = undefined; if (args.log.truncated) { @@ -129,8 +123,8 @@ export class ShowQuickFileHistoryCommand extends ActiveEditorCachedCommand { args.branchOrTag instanceof GitTag ? '$(tag)' : '$(git-branch)' } ${args.branchOrTag.name}` : gitUri.sha - ? ` from ${GlyphChars.Space}$(git-commit) ${gitUri.shortSha}` - : '' + ? ` from ${GlyphChars.Space}$(git-commit) ${gitUri.shortSha}` + : '' }` }, Commands.ShowQuickFileHistory, @@ -154,12 +148,12 @@ export class ShowQuickFileHistoryCommand extends ActiveEditorCachedCommand { [uri, { ...args, log: undefined, maxCount: 0 }] ) : undefined, - showInResultsCommand: + showInViewCommand: args.log !== undefined - ? new ShowCommitsInResultsQuickPickItem(args.log, { - label: placeHolder, - resultsType: { singular: 'commit', plural: 'commits' } - }) + ? new ShowFileHistoryInViewQuickPickItem( + gitUri, + (args.branchOrTag && args.branchOrTag.ref) || gitUri.sha + ) : undefined }); if (pick === undefined) return undefined; diff --git a/src/commands/showView.ts b/src/commands/showView.ts index 93ced3a..6923f2f 100644 --- a/src/commands/showView.ts +++ b/src/commands/showView.ts @@ -9,7 +9,8 @@ export class ShowViewCommand extends Command { Commands.ShowRepositoriesView, Commands.ShowFileHistoryView, Commands.ShowLineHistoryView, - Commands.ShowResultsView + Commands.ShowResultsView, + Commands.ShowSearchView ]); } @@ -27,6 +28,8 @@ export class ShowViewCommand extends Command { return Container.lineHistoryView.show(); case Commands.ShowResultsView: return Container.resultsView.show(); + case Commands.ShowSearchView: + return Container.searchView.show(); } return undefined; diff --git a/src/constants.ts b/src/constants.ts index 040e49e..0cf66d3 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -38,7 +38,8 @@ export enum CommandContext { ViewsLineHistoryEditorFollowing = 'gitlens:views:lineHistory:editorFollowing', ViewsRepositoriesAutoRefresh = 'gitlens:views:repositories:autoRefresh', ViewsResults = 'gitlens:views:results', - ViewsResultsKeepResults = 'gitlens:views:results:keepResults' + ViewsResultsKeepResults = 'gitlens:views:results:keepResults', + ViewsSearchKeepResults = 'gitlens:views:search:keepResults' } export function setCommandContext(key: CommandContext | string, value: any) { @@ -126,5 +127,6 @@ export const ImageMimetypes: { [key: string]: string } = { export enum WorkspaceState { ViewsRepositoriesAutoRefresh = 'gitlens:views:repositories:autoRefresh', - ViewsResultsKeepResults = 'gitlens:views:results:keepResults' + ViewsResultsKeepResults = 'gitlens:views:results:keepResults', + ViewsSearchKeepResults = 'gitlens:views:search:keepResults' } diff --git a/src/container.ts b/src/container.ts index d34fd95..2c2e0ae 100644 --- a/src/container.ts +++ b/src/container.ts @@ -15,6 +15,7 @@ import { FileHistoryView } from './views/fileHistoryView'; import { LineHistoryView } from './views/lineHistoryView'; import { RepositoriesView } from './views/repositoriesView'; import { ResultsView } from './views/resultsView'; +import { SearchView } from './views/searchView'; import { ViewCommands } from './views/viewCommands'; import { SettingsEditor } from './webviews/settingsEditor'; import { WelcomeEditor } from './webviews/welcomeEditor'; @@ -79,6 +80,19 @@ export class Container { }); } + if (config.views.search.enabled) { + context.subscriptions.push((this._searchView = new SearchView())); + } + else { + let disposable: Disposable; + disposable = configuration.onDidChange(e => { + if (configuration.changed(e, configuration.name('views')('search')('enabled').value)) { + disposable.dispose(); + context.subscriptions.push((this._searchView = new SearchView())); + } + }); + } + context.subscriptions.push(new GitFileSystemProvider()); } @@ -162,6 +176,15 @@ export class Container { return this._resultsView; } + private static _searchView: SearchView | undefined; + static get searchView() { + if (this._searchView === undefined) { + this._context.subscriptions.push((this._searchView = new SearchView())); + } + + return this._searchView; + } + private static _settingsEditor: SettingsEditor; static get settingsEditor() { return this._settingsEditor; diff --git a/src/quickpicks/branchHistoryQuickPick.ts b/src/quickpicks/branchHistoryQuickPick.ts index b826aea..232b9b4 100644 --- a/src/quickpicks/branchHistoryQuickPick.ts +++ b/src/quickpicks/branchHistoryQuickPick.ts @@ -1,6 +1,6 @@ 'use strict'; import { CancellationTokenSource, QuickPickOptions, window } from 'vscode'; -import { Commands, ShowCommitSearchCommandArgs, ShowQuickBranchHistoryCommandArgs } from '../commands'; +import { Commands, ShowQuickBranchHistoryCommandArgs } from '../commands'; import { GlyphChars } from '../constants'; import { Container } from '../container'; import { GitLog, GitUri, RemoteResource } from '../git/gitService'; @@ -73,28 +73,6 @@ export class BranchHistoryQuickPick { ); } - items.splice( - 0, - 0, - new CommandQuickPickItem( - { - label: `$(search) Show Commit Search`, - description: `${Strings.pad( - GlyphChars.Dash, - 2, - 3 - )} search for commits by message, author, files, or commit id` - }, - Commands.ShowCommitSearch, - [ - GitUri.fromRepoPath(log.repoPath), - { - goBackCommand: currentCommand - } as ShowCommitSearchCommandArgs - ] - ) - ); - let previousPageCommand: CommandQuickPickItem | undefined = undefined; if (log.truncated || log.sha) { diff --git a/src/quickpicks/commitQuickPick.ts b/src/quickpicks/commitQuickPick.ts index 5a2d738..a2bd6da 100644 --- a/src/quickpicks/commitQuickPick.ts +++ b/src/quickpicks/commitQuickPick.ts @@ -31,7 +31,7 @@ import { OpenFileCommandQuickPickItem, OpenFilesCommandQuickPickItem, QuickPickItem, - ShowCommitInResultsQuickPickItem + ShowCommitInViewQuickPickItem } from './commonQuickPicks'; import { OpenRemotesCommandQuickPickItem } from './remotesQuickPick'; @@ -163,10 +163,10 @@ export class CommitQuickPick { ) ); - items.splice(index++, 0, new ShowCommitInResultsQuickPickItem(commit)); + items.splice(index++, 0, new ShowCommitInViewQuickPickItem(commit)); } else { - items.splice(index++, 0, new ShowCommitInResultsQuickPickItem(commit)); + items.splice(index++, 0, new ShowCommitInViewQuickPickItem(commit)); const remotes = await Container.git.getRemotes(commit.repoPath); if (remotes.length) { diff --git a/src/quickpicks/commonQuickPicks.ts b/src/quickpicks/commonQuickPicks.ts index 99293fa..44ecd3b 100644 --- a/src/quickpicks/commonQuickPicks.ts +++ b/src/quickpicks/commonQuickPicks.ts @@ -13,9 +13,9 @@ import { Commands, openEditor } from '../commands'; import { configuration } from '../configuration'; import { GlyphChars } from '../constants'; import { Container } from '../container'; -import { GitLog, GitLogCommit, GitStashCommit } from '../git/gitService'; +import { GitLog, GitLogCommit, GitRepoSearchBy, GitStashCommit, GitUri } from '../git/gitService'; import { KeyMapping, Keys } from '../keyboard'; -import { Strings } from '../system'; +import { Functions, Strings } from '../system'; import { BranchesAndTagsQuickPick, BranchQuickPickItem, TagQuickPickItem } from './branchesAndTagsQuickPick'; export function getQuickPickIgnoreFocusOut() { @@ -214,50 +214,65 @@ export class OpenFilesCommandQuickPickItem extends CommandQuickPickItem { } } -export class ShowCommitInResultsQuickPickItem extends CommandQuickPickItem { +export class ShowCommitInViewQuickPickItem extends CommandQuickPickItem { constructor( public readonly commit: GitLogCommit, item: QuickPickItem = { - label: 'Show in Results', - description: `${Strings.pad(GlyphChars.Dash, 2, 2)} displays commit in the GitLens Results view` + label: 'Show in View', + description: `${Strings.pad(GlyphChars.Dash, 2, 2)} displays the commit in the GitLens Search Commits view` } ) { super(item, undefined, undefined); } async execute(): Promise<{} | undefined> { - await Container.resultsView.addCommit(this.commit); + await Container.searchView.search(this.commit.repoPath, this.commit.sha, GitRepoSearchBy.Sha, { + label: { label: `commits with an id matching '${this.commit.shortSha}'` } + }); return undefined; } } -export class ShowCommitsInResultsQuickPickItem extends CommandQuickPickItem { +export class ShowCommitSearchResultsInViewQuickPickItem extends CommandQuickPickItem { constructor( public readonly results: GitLog, public readonly resultsLabel: string | { label: string; resultsType?: { singular: string; plural: string } }, item: QuickPickItem = { - label: 'Show in Results', - description: `${Strings.pad(GlyphChars.Dash, 2, 2)} displays commits in the GitLens Results view` + label: 'Show in View', + description: `${Strings.pad( + GlyphChars.Dash, + 2, + 2 + )} displays the search results in the GitLens Search Commits view` } ) { super(item, undefined, undefined); } async execute(): Promise<{} | undefined> { - await Container.resultsView.addSearchResults(this.results.repoPath, this.results, this.resultsLabel); + await Container.searchView.showSearchResults(this.results.repoPath, this.results, { label: this.resultsLabel }); return undefined; } } -export class ShowCommitsSearchInResultsQuickPickItem extends ShowCommitsInResultsQuickPickItem { +export class ShowFileHistoryInViewQuickPickItem extends CommandQuickPickItem { constructor( - public readonly results: GitLog, - public readonly search: string, + public readonly uri: GitUri, + public readonly baseRef: string | undefined, item: QuickPickItem = { - label: 'Show in Results', - description: `${Strings.pad(GlyphChars.Dash, 2, 2)} displays results in the GitLens Results view` + label: 'Show in View', + description: `${Strings.pad( + GlyphChars.Dash, + 2, + 2 + )} displays the file history in the GitLens File History view` } ) { - super(results, { label: search }, item); + super(item, undefined, undefined); + } + + async execute(): Promise<{} | undefined> { + await Container.fileHistoryView.showHistoryForUri(this.uri, this.baseRef); + return undefined; } } diff --git a/src/quickpicks/fileHistoryQuickPick.ts b/src/quickpicks/fileHistoryQuickPick.ts index 75c503f..3f90fbd 100644 --- a/src/quickpicks/fileHistoryQuickPick.ts +++ b/src/quickpicks/fileHistoryQuickPick.ts @@ -37,7 +37,7 @@ export class FileHistoryQuickPick { pickerOnly?: boolean; progressCancellation?: CancellationTokenSource; showAllCommand?: CommandQuickPickItem; - showInResultsCommand?: CommandQuickPickItem; + showInViewCommand?: CommandQuickPickItem; } = {} ): Promise { options = { pickerOnly: false, ...options }; @@ -55,9 +55,9 @@ export class FileHistoryQuickPick { new ChooseFromBranchesAndTagsQuickPickItem(log.repoPath, placeHolder, options.currentCommand) ); - if (options.showInResultsCommand !== undefined) { + if (options.showInViewCommand !== undefined) { index++; - items.splice(0, 0, options.showInResultsCommand); + items.splice(0, 0, options.showInViewCommand); } if (log.truncated || log.sha) { diff --git a/src/system/function.ts b/src/system/function.ts index 61eaae8..6f70fc3 100644 --- a/src/system/function.ts +++ b/src/system/function.ts @@ -16,6 +16,19 @@ interface IPropOfValue { } export namespace Functions { + export function cachedOnce(fn: (...args: any[]) => Promise, seed: T): (...args: any[]) => Promise { + let cached: T | undefined = seed; + return (...args: any[]) => { + if (cached !== undefined) { + const promise = Promise.resolve(cached); + cached = undefined; + + return promise; + } + return fn(...args); + }; + } + export function cancellable(promise: Promise, token: CancellationToken): Promise { return new Promise((resolve, reject) => { token.onCancellationRequested(() => resolve(undefined)); @@ -65,7 +78,7 @@ export namespace Functions { return tracked; } - export function isPromise(o: any) { + export function isPromise(o: any): o is Promise { return (typeof o === 'object' || typeof o === 'function') && typeof o.then === 'function'; } @@ -84,19 +97,6 @@ export namespace Functions { return propOfCore(o, key); } - export function seeded(fn: (...args: any[]) => Promise, seed: T): (...args: any[]) => Promise { - let cached: T | undefined = seed; - return (...args: any[]) => { - if (cached !== undefined) { - const promise = Promise.resolve(cached); - cached = undefined; - - return promise; - } - return fn(...args); - }; - } - export function interval(fn: (...args: any[]) => void, ms: number): Disposable { let timer: NodeJS.Timer | undefined; const disposable = { diff --git a/src/ui/config.ts b/src/ui/config.ts index 7a426dd..f71d915 100644 --- a/src/ui/config.ts +++ b/src/ui/config.ts @@ -156,6 +156,7 @@ export interface ViewsConfig { lineHistory: LineHistoryViewConfig; repositories: RepositoriesViewConfig; results: ResultsViewConfig; + search: SearchViewConfig; stashFileFormat: string; stashFormat: string; statusFileFormat: string; @@ -257,6 +258,12 @@ export interface RemotesUrlsConfig { fileRange: string; } +export interface SearchViewConfig { + enabled: boolean; + files: ViewsFilesConfig; + location: 'explorer' | 'gitlens' | 'scm'; +} + export interface Config { blame: { avatars: boolean; diff --git a/src/views/fileHistoryView.ts b/src/views/fileHistoryView.ts index 10a31f2..9757c50 100644 --- a/src/views/fileHistoryView.ts +++ b/src/views/fileHistoryView.ts @@ -3,9 +3,9 @@ import { commands, ConfigurationChangeEvent } from 'vscode'; import { configuration, FileHistoryViewConfig, ViewsConfig } from '../configuration'; import { CommandContext, setCommandContext } from '../constants'; import { Container } from '../container'; -import { FileHistoryTrackerNode, ViewNode } from './nodes'; +import { GitUri } from '../git/gitUri'; +import { FileHistoryTrackerNode } from './nodes'; import { RefreshReason, ViewBase } from './viewBase'; -import { RefreshNodeCommandArgs } from './viewCommands'; export class FileHistoryView extends ViewBase { constructor() { @@ -23,11 +23,6 @@ export class FileHistoryView extends ViewBase { protected registerCommands() { void Container.viewCommands; commands.registerCommand(this.getQualifiedCommand('refresh'), () => this.refresh(), this); - commands.registerCommand( - this.getQualifiedCommand('refreshNode'), - (node: ViewNode, args?: RefreshNodeCommandArgs) => this.refreshNode(node, args), - this - ); commands.registerCommand(this.getQualifiedCommand('changeBase'), () => this.changeBase(), this); commands.registerCommand( this.getQualifiedCommand('setEditorFollowingOn'), @@ -78,6 +73,14 @@ export class FileHistoryView extends ViewBase { return { ...Container.config.views, ...Container.config.views.fileHistory }; } + async showHistoryForUri(uri: GitUri, baseRef?: string) { + const root = this.ensureRoot(); + + this.setEditorFollowing(false); + await root.showHistoryForUri(uri, baseRef); + return this.show(); + } + private changeBase() { if (this._root !== undefined) { void this._root.changeBase(); diff --git a/src/views/lineHistoryView.ts b/src/views/lineHistoryView.ts index bbf20df..4a66694 100644 --- a/src/views/lineHistoryView.ts +++ b/src/views/lineHistoryView.ts @@ -23,11 +23,6 @@ export class LineHistoryView extends ViewBase { protected registerCommands() { void Container.viewCommands; commands.registerCommand(this.getQualifiedCommand('refresh'), () => this.refresh(), this); - commands.registerCommand( - this.getQualifiedCommand('refreshNode'), - (node: ViewNode, args?: RefreshNodeCommandArgs) => this.refreshNode(node, args), - this - ); commands.registerCommand(this.getQualifiedCommand('changeBase'), () => this.changeBase(), this); commands.registerCommand( this.getQualifiedCommand('setEditorFollowingOn'), diff --git a/src/views/nodes.ts b/src/views/nodes.ts index 4de4d64..facbb7d 100644 --- a/src/views/nodes.ts +++ b/src/views/nodes.ts @@ -13,10 +13,10 @@ export * from './nodes/remoteNode'; export * from './nodes/remotesNode'; export * from './nodes/repositoriesNode'; export * from './nodes/repositoryNode'; -export * from './nodes/resultsCommitNode'; export * from './nodes/resultsCommitsNode'; export * from './nodes/resultsComparisonNode'; export * from './nodes/resultsNode'; +export * from './nodes/searchNode'; export * from './nodes/stashesNode'; export * from './nodes/stashFileNode'; export * from './nodes/stashNode'; diff --git a/src/views/nodes/common.ts b/src/views/nodes/common.ts index 11c4f41..9fde55d 100644 --- a/src/views/nodes/common.ts +++ b/src/views/nodes/common.ts @@ -162,7 +162,7 @@ export abstract class PagerNode extends ViewNode { getCommand(): Command | undefined { return { title: 'Refresh', - command: this.view.getQualifiedCommand('refreshNode'), + command: 'gitlens.views.refreshNode', arguments: [this._parent, this._args] } as Command; } diff --git a/src/views/nodes/fileHistoryTrackerNode.ts b/src/views/nodes/fileHistoryTrackerNode.ts index 8fc2bd7..8b05f0a 100644 --- a/src/views/nodes/fileHistoryTrackerNode.ts +++ b/src/views/nodes/fileHistoryTrackerNode.ts @@ -14,6 +14,7 @@ import { ResourceType, SubscribeableViewNode, unknownGitUri, ViewNode } from './ export class FileHistoryTrackerNode extends SubscribeableViewNode { private _baseRef: string | undefined; + private _fileUri: GitUri | undefined; private _child: FileHistoryNode | undefined; constructor(view: FileHistoryView) { @@ -36,7 +37,7 @@ export class FileHistoryTrackerNode extends SubscribeableViewNode { if (this._child === undefined) { - if (this.uri === unknownGitUri) { + if (this._fileUri === undefined && this.uri === unknownGitUri) { return [ new MessageNode( this.view, @@ -46,7 +47,7 @@ export class FileHistoryTrackerNode extends SubscribeableViewNode { private _children: (ViewNode | MessageNode)[] = []; constructor(view: ResultsView) { @@ -17,83 +14,85 @@ export class ResultsNode extends ViewNode { async getChildren(): Promise { if (this._children.length === 0) { - const command = { - title: 'Search Commits', - command: 'gitlens.showCommitSearch' - }; - - return [ - new CommandMessageNode( - this.view, - this, - { - ...command, - arguments: [this, { searchBy: GitRepoSearchBy.Message } as ShowCommitSearchCommandArgs] - }, - `Start a commit search by`, - 'Click to search' - ), - new CommandMessageNode( - this.view, - this, - { - ...command, - arguments: [this, { searchBy: GitRepoSearchBy.Message } as ShowCommitSearchCommandArgs] - }, - `${GlyphChars.Space.repeat(4)} message ${Strings.pad(GlyphChars.Dash, 1, 1)} use `, - 'Click to search by message' - ), - new CommandMessageNode( - this.view, - this, - { - ...command, - arguments: [this, { searchBy: GitRepoSearchBy.Author } as ShowCommitSearchCommandArgs] - }, - `${GlyphChars.Space.repeat(4)} author ${Strings.pad(GlyphChars.Dash, 1, 1)} use @`, - 'Click to search by author' - ), - new CommandMessageNode( - this.view, - this, - { - ...command, - arguments: [this, { searchBy: GitRepoSearchBy.Sha } as ShowCommitSearchCommandArgs] - }, - `${GlyphChars.Space.repeat(4)} commit id ${Strings.pad(GlyphChars.Dash, 1, 1)} use #`, - 'Click to search by commit id' - ), - new CommandMessageNode( - this.view, - this, - { - ...command, - arguments: [this, { searchBy: GitRepoSearchBy.Files } as ShowCommitSearchCommandArgs] - }, - `${GlyphChars.Space.repeat(4)} files ${Strings.pad(GlyphChars.Dash, 1, 1)} use :`, - 'Click to search by files' - ), - new CommandMessageNode( - this.view, - this, - { - ...command, - arguments: [this, { searchBy: GitRepoSearchBy.Changes } as ShowCommitSearchCommandArgs] - }, - `${GlyphChars.Space.repeat(4)} changes ${Strings.pad(GlyphChars.Dash, 1, 1)} use =`, - 'Click to search by changes' - ), - new CommandMessageNode( - this.view, - this, - { - ...command, - arguments: [this, { searchBy: GitRepoSearchBy.ChangedLines } as ShowCommitSearchCommandArgs] - }, - `${GlyphChars.Space.repeat(4)} changed lines ${Strings.pad(GlyphChars.Dash, 1, 1)} use ~`, - 'Click to search by changed lines' - ) - ]; + this._children = []; + + // const command = { + // title: 'Search Commits', + // command: 'gitlens.showCommitSearch' + // }; + + // return [ + // new CommandMessageNode( + // this.view, + // this, + // { + // ...command, + // arguments: [this, { searchBy: GitRepoSearchBy.Message } as ShowCommitSearchCommandArgs] + // }, + // `Start a commit search by`, + // 'Click to search' + // ), + // new CommandMessageNode( + // this.view, + // this, + // { + // ...command, + // arguments: [this, { searchBy: GitRepoSearchBy.Message } as ShowCommitSearchCommandArgs] + // }, + // `${GlyphChars.Space.repeat(4)} message ${Strings.pad(GlyphChars.Dash, 1, 1)} use `, + // 'Click to search by message' + // ), + // new CommandMessageNode( + // this.view, + // this, + // { + // ...command, + // arguments: [this, { searchBy: GitRepoSearchBy.Author } as ShowCommitSearchCommandArgs] + // }, + // `${GlyphChars.Space.repeat(4)} author ${Strings.pad(GlyphChars.Dash, 1, 1)} use @`, + // 'Click to search by author' + // ), + // new CommandMessageNode( + // this.view, + // this, + // { + // ...command, + // arguments: [this, { searchBy: GitRepoSearchBy.Sha } as ShowCommitSearchCommandArgs] + // }, + // `${GlyphChars.Space.repeat(4)} commit id ${Strings.pad(GlyphChars.Dash, 1, 1)} use #`, + // 'Click to search by commit id' + // ), + // new CommandMessageNode( + // this.view, + // this, + // { + // ...command, + // arguments: [this, { searchBy: GitRepoSearchBy.Files } as ShowCommitSearchCommandArgs] + // }, + // `${GlyphChars.Space.repeat(4)} files ${Strings.pad(GlyphChars.Dash, 1, 1)} use :`, + // 'Click to search by files' + // ), + // new CommandMessageNode( + // this.view, + // this, + // { + // ...command, + // arguments: [this, { searchBy: GitRepoSearchBy.Changes } as ShowCommitSearchCommandArgs] + // }, + // `${GlyphChars.Space.repeat(4)} changes ${Strings.pad(GlyphChars.Dash, 1, 1)} use =`, + // 'Click to search by changes' + // ), + // new CommandMessageNode( + // this.view, + // this, + // { + // ...command, + // arguments: [this, { searchBy: GitRepoSearchBy.ChangedLines } as ShowCommitSearchCommandArgs] + // }, + // `${GlyphChars.Space.repeat(4)} changed lines ${Strings.pad(GlyphChars.Dash, 1, 1)} use ~`, + // 'Click to search by changed lines' + // ) + // ]; } return this._children; diff --git a/src/views/nodes/searchNode.ts b/src/views/nodes/searchNode.ts new file mode 100644 index 0000000..3215243 --- /dev/null +++ b/src/views/nodes/searchNode.ts @@ -0,0 +1,140 @@ +'use strict'; +import { TreeItem, TreeItemCollapsibleState } from 'vscode'; +import { ShowCommitSearchCommandArgs } from '../../commands'; +import { GlyphChars } from '../../constants'; +import { GitRepoSearchBy } from '../../git/gitService'; +import { debug, Functions, gate, log } from '../../system'; +import { View } from '../viewBase'; +import { CommandMessageNode, MessageNode } from './common'; +import { ResourceType, unknownGitUri, ViewNode } from './viewNode'; + +export class SearchNode extends ViewNode { + private _children: (ViewNode | MessageNode)[] = []; + + constructor(view: View) { + super(unknownGitUri, view); + } + + async getChildren(): Promise { + if (this._children.length === 0) { + const command = { + title: ' ', + command: 'gitlens.showCommitSearch' + }; + + return [ + new CommandMessageNode( + this.view, + this, + { + ...command, + arguments: [this, { searchBy: GitRepoSearchBy.Message } as ShowCommitSearchCommandArgs] + }, + `Search commits by message (use <message-pattern>)`, + 'Click to search commits by message' + ), + new CommandMessageNode( + this.view, + this, + { + ...command, + arguments: [this, { searchBy: GitRepoSearchBy.Author } as ShowCommitSearchCommandArgs] + }, + `${GlyphChars.Space.repeat(4)} or, by author (use @<author-pattern>)`, + 'Click to search commits by author' + ), + new CommandMessageNode( + this.view, + this, + { + ...command, + arguments: [this, { searchBy: GitRepoSearchBy.Sha } as ShowCommitSearchCommandArgs] + }, + `${GlyphChars.Space.repeat(4)} or, by commit id (use #<sha>)`, + 'Click to search commits by commit id' + ), + new CommandMessageNode( + this.view, + this, + { + ...command, + arguments: [this, { searchBy: GitRepoSearchBy.Files } as ShowCommitSearchCommandArgs] + }, + `${GlyphChars.Space.repeat(4)} or, by files (use :<file-pattern>)`, + 'Click to search commits by files' + ), + new CommandMessageNode( + this.view, + this, + { + ...command, + arguments: [this, { searchBy: GitRepoSearchBy.Changes } as ShowCommitSearchCommandArgs] + }, + `${GlyphChars.Space.repeat(4)} or, by changes (use =<pattern>)`, + 'Click to search commits by changes' + ), + new CommandMessageNode( + this.view, + this, + { + ...command, + arguments: [this, { searchBy: GitRepoSearchBy.ChangedLines } as ShowCommitSearchCommandArgs] + }, + `${GlyphChars.Space.repeat(4)} or, by changed lines (use ~<pattern>)`, + 'Click to search commits by changed lines' + ) + ]; + } + + return this._children; + } + + getTreeItem(): TreeItem { + const item = new TreeItem(`Search`, TreeItemCollapsibleState.Expanded); + item.contextValue = ResourceType.Results; + return item; + } + + addOrReplace(results: ViewNode, replace: boolean) { + if (this._children.includes(results)) return; + + if (this._children.length !== 0 && replace) { + this._children.length = 0; + this._children.push(results); + } + else { + this._children.splice(0, 0, results); + } + + this.view.triggerNodeChange(); + } + + @log() + clear() { + if (this._children.length === 0) return; + + this._children.length = 0; + this.view.triggerNodeChange(); + } + + @log({ + args: { 0: (n: ViewNode) => n.toString() } + }) + dismiss(node: ViewNode) { + if (this._children.length === 0) return; + + const index = this._children.findIndex(n => n === node); + if (index === -1) return; + + this._children.splice(index, 1); + this.view.triggerNodeChange(); + } + + @gate() + @debug() + async refresh() { + if (this._children.length === 0) return; + + await Promise.all(this._children.map(c => c.refresh()).filter(Functions.isPromise) as Promise[]); + } +} diff --git a/src/views/nodes/viewNode.ts b/src/views/nodes/viewNode.ts index e565ddf..5c05328 100644 --- a/src/views/nodes/viewNode.ts +++ b/src/views/nodes/viewNode.ts @@ -36,6 +36,7 @@ export enum ResourceType { ResultsFile = 'gitlens:file:results', ResultsFiles = 'gitlens:results:files', SearchResults = 'gitlens:results:search', + Search = 'gitlens:search', Stash = 'gitlens:stash', StashFile = 'gitlens:file:stash', Stashes = 'gitlens:stashes', @@ -92,6 +93,12 @@ export abstract class ViewNode { @gate() @debug() refresh(reason?: RefreshReason): void | boolean | Promise | Promise {} + + @gate() + @debug() + triggerChange(): Promise { + return this.view.refreshNode(this); + } } export abstract class ViewRefNode extends ViewNode { @@ -162,12 +169,6 @@ export abstract class SubscribeableViewNode extends V } } - @gate() - @debug() - async triggerChange() { - return this.view.refreshNode(this); - } - protected abstract async subscribe(): Promise; @debug() @@ -227,3 +228,7 @@ export abstract class SubscribeableViewNode extends V await this._subscription; } } + +export function canDismissNode(view: View): view is View & { dismissNode(node: ViewNode): void } { + return typeof (view as any).dismissNode === 'function'; +} diff --git a/src/views/repositoriesView.ts b/src/views/repositoriesView.ts index 74ecc22..e5f96da 100644 --- a/src/views/repositoriesView.ts +++ b/src/views/repositoriesView.ts @@ -31,11 +31,6 @@ export class RepositoriesView extends ViewBase { commands.registerCommand(this.getQualifiedCommand('refresh'), () => this.refresh(), this); commands.registerCommand( - this.getQualifiedCommand('refreshNode'), - (node: ViewNode, args?: RefreshNodeCommandArgs) => this.refreshNode(node, args), - this - ); - commands.registerCommand( this.getQualifiedCommand('setFilesLayoutToAuto'), () => this.setFilesLayout(ViewFilesLayout.Auto), this diff --git a/src/views/resultsView.ts b/src/views/resultsView.ts index 381d307..3ec872c 100644 --- a/src/views/resultsView.ts +++ b/src/views/resultsView.ts @@ -1,21 +1,10 @@ 'use strict'; import { commands, ConfigurationChangeEvent } from 'vscode'; import { configuration, ResultsViewConfig, ViewFilesLayout, ViewsConfig } from '../configuration'; -import { CommandContext, GlyphChars, setCommandContext, WorkspaceState } from '../constants'; +import { CommandContext, setCommandContext, WorkspaceState } from '../constants'; import { Container } from '../container'; -import { GitLog, GitLogCommit } from '../git/gitService'; -import { Functions, Strings } from '../system'; -import { - NamedRef, - ResourceType, - ResultsCommitNode, - ResultsCommitsNode, - ResultsComparisonNode, - ResultsNode, - ViewNode -} from './nodes'; +import { NamedRef, ResultsComparisonNode, ResultsNode, ViewNode } from './nodes'; import { RefreshReason, ViewBase } from './viewBase'; -import { RefreshNodeCommandArgs } from './viewCommands'; export class ResultsView extends ViewBase { constructor() { @@ -34,13 +23,10 @@ export class ResultsView extends ViewBase { protected registerCommands() { void Container.viewCommands; + // commands.registerCommand(this.getQualifiedCommand('clear'), () => this.clear(), this); + commands.registerCommand(this.getQualifiedCommand('close'), () => this.close(), this); commands.registerCommand(this.getQualifiedCommand('refresh'), () => this.refresh(), this); commands.registerCommand( - this.getQualifiedCommand('refreshNode'), - (node: ViewNode, args?: RefreshNodeCommandArgs) => this.refreshNode(node, args), - this - ); - commands.registerCommand( this.getQualifiedCommand('setFilesLayoutToAuto'), () => this.setFilesLayout(ViewFilesLayout.Auto), this @@ -55,13 +41,6 @@ export class ResultsView extends ViewBase { () => this.setFilesLayout(ViewFilesLayout.Tree), this ); - - commands.registerCommand( - this.getQualifiedCommand('dismissNode'), - (node: ViewNode) => this.dismissNode(node), - this - ); - commands.registerCommand(this.getQualifiedCommand('close'), () => this.close(), this); commands.registerCommand(this.getQualifiedCommand('setKeepResultsToOn'), () => this.setKeepResults(true), this); commands.registerCommand( this.getQualifiedCommand('setKeepResultsToOff'), @@ -102,6 +81,12 @@ export class ResultsView extends ViewBase { return Container.context.workspaceState.get(WorkspaceState.ViewsResultsKeepResults, false); } + clear() { + if (this._root === undefined) return; + + this._root.clear(); + } + close() { if (this._root === undefined) return; @@ -111,11 +96,13 @@ export class ResultsView extends ViewBase { setCommandContext(CommandContext.ViewsResults, false); } - addCommit(commit: GitLogCommit) { - return this.addResults(new ResultsCommitNode(this, commit)); + dismissNode(node: ViewNode) { + if (this._root === undefined) return; + + this._root.dismiss(node); } - addComparison(repoPath: string, ref1: string | NamedRef, ref2: string | NamedRef) { + compare(repoPath: string, ref1: string | NamedRef, ref2: string | NamedRef) { return this.addResults( new ResultsComparisonNode( this, @@ -126,72 +113,9 @@ export class ResultsView extends ViewBase { ); } - addSearchResults( - repoPath: string, - resultsOrPromise: GitLog | Promise, - resultsLabel: - | string - | { - label: string; - resultsType?: { singular: string; plural: string }; - } - ) { - const getCommitsQuery = async (maxCount: number | undefined) => { - const results = await resultsOrPromise; - - let log; - if (results !== undefined) { - log = await Functions.seeded( - results.query === undefined - ? (maxCount: number | undefined) => Promise.resolve(results) - : results.query, - results.maxCount === maxCount ? results : undefined - )(maxCount); - } - - let label; - if (typeof resultsLabel === 'string') { - label = resultsLabel; - } - else { - const count = log !== undefined ? log.count : 0; - const truncated = log !== undefined ? log.truncated : false; - - const resultsType = - resultsLabel.resultsType === undefined - ? { singular: 'result', plural: 'results' } - : resultsLabel.resultsType; - - let repository = ''; - if ((await Container.git.getRepositoryCount()) > 1) { - const repo = await Container.git.getRepository(repoPath); - repository = ` ${Strings.pad(GlyphChars.Dash, 1, 1)} ${(repo && repo.formattedName) || repoPath}`; - } - - label = `${Strings.pluralize(resultsType.singular, count, { - number: truncated ? `${count}+` : undefined, - plural: resultsType.plural, - zero: 'No' - })} for ${resultsLabel.label}${repository}`; - } - - return { - label: label, - log: log - }; - }; - - return this.addResults( - new ResultsCommitsNode(this, this._root!, repoPath, getCommitsQuery, ResourceType.SearchResults) - ); - } - private async addResults(results: ViewNode) { - if (this._root === undefined) { - this._root = this.getRoot(); - } - - this._root.addOrReplace(results, !this.keepResults); + const root = this.ensureRoot(); + root.addOrReplace(results, !this.keepResults); this._enabled = true; await setCommandContext(CommandContext.ViewsResults, true); @@ -199,12 +123,6 @@ export class ResultsView extends ViewBase { setTimeout(() => this._tree!.reveal(results, { select: true }), 250); } - private dismissNode(node: ViewNode) { - if (this._root === undefined) return; - - this._root.dismiss(node); - } - private setFilesLayout(layout: ViewFilesLayout) { return configuration.updateEffective(configuration.name('views')('results')('files')('layout').value, layout); } diff --git a/src/views/searchView.ts b/src/views/searchView.ts new file mode 100644 index 0000000..3072170 --- /dev/null +++ b/src/views/searchView.ts @@ -0,0 +1,226 @@ +'use strict'; +import { commands, ConfigurationChangeEvent } from 'vscode'; +import { configuration, SearchViewConfig, ViewFilesLayout, ViewsConfig } from '../configuration'; +import { CommandContext, GlyphChars, setCommandContext, WorkspaceState } from '../constants'; +import { Container } from '../container'; +import { GitLog, GitRepoSearchBy } from '../git/gitService'; +import { Functions, Strings } from '../system'; +import { ResourceType, ResultsCommitsNode, SearchNode, ViewNode } from './nodes'; +import { RefreshReason, ViewBase } from './viewBase'; + +interface SearchQueryResult { + label: string; + log: GitLog | undefined; +} + +export class SearchView extends ViewBase { + constructor() { + super('gitlens.views.search'); + + setCommandContext(CommandContext.ViewsSearchKeepResults, this.keepResults); + } + + getRoot() { + return new SearchNode(this); + } + + protected get location(): string { + return this.config.location; + } + + protected registerCommands() { + void Container.viewCommands; + commands.registerCommand(this.getQualifiedCommand('clear'), () => this.clear(), this); + commands.registerCommand(this.getQualifiedCommand('refresh'), () => this.refresh(), this); + commands.registerCommand( + this.getQualifiedCommand('setFilesLayoutToAuto'), + () => this.setFilesLayout(ViewFilesLayout.Auto), + this + ); + commands.registerCommand( + this.getQualifiedCommand('setFilesLayoutToList'), + () => this.setFilesLayout(ViewFilesLayout.List), + this + ); + commands.registerCommand( + this.getQualifiedCommand('setFilesLayoutToTree'), + () => this.setFilesLayout(ViewFilesLayout.Tree), + this + ); + commands.registerCommand(this.getQualifiedCommand('setKeepResultsToOn'), () => this.setKeepResults(true), this); + commands.registerCommand( + this.getQualifiedCommand('setKeepResultsToOff'), + () => this.setKeepResults(false), + this + ); + } + + protected onConfigurationChanged(e: ConfigurationChangeEvent) { + if ( + !configuration.changed(e, configuration.name('views')('search').value) && + !configuration.changed(e, configuration.name('views').value) && + !configuration.changed(e, configuration.name('defaultGravatarsStyle').value) + ) { + return; + } + + if (configuration.changed(e, configuration.name('views')('search')('location').value)) { + this.initialize(this.config.location /*, { showCollapseAll: true } */); + } + + if (!configuration.initializing(e) && this._root !== undefined) { + void this.refresh(RefreshReason.ConfigurationChanged); + } + } + + get config(): ViewsConfig & SearchViewConfig { + return { ...Container.config.views, ...Container.config.views.search }; + } + + get keepResults(): boolean { + return Container.context.workspaceState.get(WorkspaceState.ViewsSearchKeepResults, false); + } + + clear() { + if (this._root === undefined) return; + + this._root.clear(); + } + + dismissNode(node: ViewNode) { + if (this._root === undefined) return; + + this._root.dismiss(node); + } + + async search( + repoPath: string, + search: string, + searchBy: GitRepoSearchBy, + options: { + maxCount?: number; + label: + | string + | { + label: string; + resultsType?: { singular: string; plural: string }; + }; + } + ) { + await this.show(); + + const searchQueryFn = this.getSearchQueryFn( + repoPath, + Container.git.getLogForSearch(repoPath, search, searchBy, { + maxCount: options.maxCount + }), + options + ); + + return this.addResults( + new ResultsCommitsNode(this, this._root!, repoPath, searchQueryFn, ResourceType.SearchResults) + ); + } + + async showSearchResults( + repoPath: string, + results: GitLog, + options: { + label: + | string + | { + label: string; + resultsType?: { singular: string; plural: string }; + }; + } + ) { + const label = await this.getSearchLabel(repoPath, options.label, results); + const searchQueryFn = Functions.cachedOnce(this.getSearchQueryFn(repoPath, results, options), { + label: label, + log: results + }); + + return this.addResults( + new ResultsCommitsNode(this, this._root!, repoPath, searchQueryFn, ResourceType.SearchResults) + ); + } + + private async addResults(results: ViewNode) { + const root = this.ensureRoot(); + root.addOrReplace(results, !this.keepResults); + + setTimeout(() => this._tree!.reveal(results, { select: true }), 250); + } + + private async getSearchLabel( + repoPath: string, + label: + | string + | { + label: string; + resultsType?: { singular: string; plural: string }; + }, + log: GitLog | undefined + ) { + if (typeof label === 'string') return label; + + const count = log !== undefined ? log.count : 0; + const truncated = log !== undefined ? log.truncated : false; + + const resultsType = + label.resultsType === undefined ? { singular: 'result', plural: 'results' } : label.resultsType; + + let repository = ''; + if ((await Container.git.getRepositoryCount()) > 1) { + const repo = await Container.git.getRepository(repoPath); + repository = ` ${Strings.pad(GlyphChars.Dash, 1, 1)} ${(repo && repo.formattedName) || repoPath}`; + } + + return `${Strings.pluralize(resultsType.singular, count, { + number: truncated ? `${count}+` : undefined, + plural: resultsType.plural, + zero: 'No' + })} for ${label.label}${repository}`; + } + + private getSearchQueryFn( + repoPath: string, + results: Promise | GitLog | undefined, + options: { + label: + | string + | { + label: string; + resultsType?: { singular: string; plural: string }; + }; + } + ): (maxCount: number | undefined) => Promise { + return async (maxCount: number | undefined) => { + if (Functions.isPromise(results)) { + results = await results; + } + + let log; + if (results !== undefined) { + log = await (results.query === undefined + ? (maxCount: number | undefined) => Promise.resolve(results) + : results.query)(maxCount); + } + + const label = await this.getSearchLabel(repoPath, options.label, log); + return { + label: label, + log: log + }; + }; + } + + private setFilesLayout(layout: ViewFilesLayout) { + return configuration.updateEffective(configuration.name('views')('search')('files')('layout').value, layout); + } + + private setKeepResults(enabled: boolean) { + Container.context.workspaceState.update(WorkspaceState.ViewsSearchKeepResults, enabled); + setCommandContext(CommandContext.ViewsSearchKeepResults, enabled); + } +} diff --git a/src/views/viewBase.ts b/src/views/viewBase.ts index d117f47..1342523 100644 --- a/src/views/viewBase.ts +++ b/src/views/viewBase.ts @@ -23,6 +23,7 @@ import { ViewNode } from './nodes'; import { isPageable } from './nodes/viewNode'; import { RepositoriesView } from './repositoriesView'; import { ResultsView } from './resultsView'; +import { SearchView } from './searchView'; import { RefreshNodeCommandArgs } from './viewCommands'; export enum RefreshReason { @@ -30,7 +31,7 @@ export enum RefreshReason { VisibilityChanged = 'VisibilityChanged' } -export type View = RepositoriesView | FileHistoryView | LineHistoryView | ResultsView; +export type View = RepositoriesView | FileHistoryView | LineHistoryView | ResultsView | SearchView; export interface TreeViewNodeStateChangeEvent extends TreeViewExpansionEvent { state: TreeItemCollapsibleState; diff --git a/src/views/viewCommands.ts b/src/views/viewCommands.ts index c03e709..96407ea 100644 --- a/src/views/viewCommands.ts +++ b/src/views/viewCommands.ts @@ -19,6 +19,7 @@ import { GitService, GitUri } from '../git/gitService'; import { Arrays } from '../system'; import { BranchNode, + canDismissNode, CommitFileNode, CommitNode, RemoteNode, @@ -49,6 +50,17 @@ export class ViewCommands implements Disposable { private _terminalCwd: string | undefined; constructor() { + commands.registerCommand( + 'gitlens.views.refreshNode', + (node: ViewNode, args?: RefreshNodeCommandArgs) => node.view.refreshNode(node, args), + this + ); + commands.registerCommand( + 'gitlens.views.dismissNode', + (node: ViewNode) => canDismissNode(node.view) && node.view.dismissNode(node), + this + ); + commands.registerCommand('gitlens.views.fetch', this.fetch, this); commands.registerCommand('gitlens.views.pull', this.pull, this); commands.registerCommand('gitlens.views.push', this.push, this); @@ -149,19 +161,19 @@ export class ViewCommands implements Disposable { private compareWithHead(node: ViewNode) { if (!(node instanceof ViewRefNode)) return; - return Container.resultsView.addComparison(node.repoPath, node.ref, 'HEAD'); + return Container.resultsView.compare(node.repoPath, node.ref, 'HEAD'); } private compareWithRemote(node: BranchNode) { if (!node.branch.tracking) return; - return Container.resultsView.addComparison(node.repoPath, node.branch.tracking, node.ref); + return Container.resultsView.compare(node.repoPath, node.branch.tracking, node.ref); } private compareWithWorking(node: ViewNode) { if (!(node instanceof ViewRefNode)) return; - return Container.resultsView.addComparison(node.repoPath, node.ref, ''); + return Container.resultsView.compare(node.repoPath, node.ref, ''); } private async compareAncestryWithWorking(node: BranchNode) { @@ -171,7 +183,7 @@ export class ViewCommands implements Disposable { const commonAncestor = await Container.git.getMergeBase(node.repoPath, branch.ref, node.ref); if (commonAncestor === undefined) return; - return Container.resultsView.addComparison( + return Container.resultsView.compare( node.repoPath, { ref: commonAncestor, label: `ancestry with ${node.ref} (${GitService.shortenSha(commonAncestor)})` }, '' @@ -201,7 +213,7 @@ export class ViewCommands implements Disposable { return; } - return Container.resultsView.addComparison(this._selection.repoPath, this._selection.ref, node.ref); + return Container.resultsView.compare(this._selection.repoPath, this._selection.ref, node.ref); } private _selection: ICompareSelected | undefined; @@ -310,9 +322,8 @@ export class ViewCommands implements Disposable { options: TextDocumentShowOptions = { preserveFocus: false, preview: false } ) { const repoPath = node.commit.repoPath; - const uris = Arrays.filterMap( - node.commit.files, - f => (f.status !== 'D' ? GitUri.fromFile(f, repoPath) : undefined) + const uris = Arrays.filterMap(node.commit.files, f => + f.status !== 'D' ? GitUri.fromFile(f, repoPath) : undefined ); for (const uri of uris) {