From 3254bf3c49f40ed0ec83036e1d98407917e322cb Mon Sep 17 00:00:00 2001 From: Eric Amodio Date: Sat, 26 Aug 2023 01:12:31 -0400 Subject: [PATCH] Allows the Graph in a view & a tab simultaneously --- CHANGELOG.md | 6 + package.json | 103 ++++---- src/constants.ts | 1 + src/container.ts | 20 +- src/plus/webviews/graph/graphWebview.ts | 258 ++++++++++++--------- src/plus/webviews/graph/registration.ts | 18 +- src/system/command.ts | 19 +- src/system/webview.ts | 31 ++- src/webviews/apps/plus/graph/GraphWrapper.tsx | 18 +- .../apps/settings/partials/commit-graph.html | 12 + src/webviews/commitDetails/commitDetailsWebview.ts | 11 +- src/webviews/webviewCommandRegistrar.ts | 70 ++++++ src/webviews/webviewController.ts | 11 + src/webviews/webviewsController.ts | 17 +- 14 files changed, 399 insertions(+), 196 deletions(-) create mode 100644 src/webviews/webviewCommandRegistrar.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e02660..35a9e2c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,8 +8,14 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p ### Added +- Adds checkboxes to files in the _Search & Compare_ view to allow for tracking review progress — closes [#836](https://github.com/gitkraken/vscode-gitlens/issues/836) +- Allows the _Commit Graph_ to be open in the panel and in the editor area simultaneously - Adds an _Open Changes_ button to commits in the file history quick pick menu — closes [#2641](https://github.com/gitkraken/vscode-gitlens/issues/2641) thanks to [PR #2800](https://github.com/gitkraken/vscode-gitlens/pull/2800) by Omar Ghazi ([@omarfesal](https://github.com/omarfesal)) +### Changed + +- Changes the `gitlens.graph.layout` setting to be a default preference rather than a mode change + ### Fixed - Fixes [#2885](https://github.com/gitkraken/vscode-gitlens/issues/2885) - Folder History not show changed files of commit diff --git a/package.json b/package.json index 731ad80..d342891 100644 --- a/package.json +++ b/package.json @@ -2402,10 +2402,10 @@ "panel" ], "enumDescriptions": [ - "Shows the Commit Graph in an editor tab", - "Shows the Commit Graph in the bottom panel" + "Prefer showing the Commit Graph in the editor area", + "Prefer showing the Commit Graph in the bottom panel" ], - "markdownDescription": "Specifies the layout of the _Commit Graph_", + "markdownDescription": "Specifies the preferred layout of the _Commit Graph_", "scope": "window", "order": 99 }, @@ -4711,7 +4711,7 @@ }, { "command": "gitlens.showGraphPage", - "title": "Show Commit Graph", + "title": "Show Commit Graph in Editor Area", "category": "GitLens", "icon": "$(gitlens-graph)" }, @@ -4750,6 +4750,12 @@ "icon": "$(gitlens-graph)" }, { + "command": "gitlens.showInCommitGraphView", + "title": "Open in Commit Graph", + "category": "GitLens", + "icon": "$(gitlens-graph)" + }, + { "command": "gitlens.showLineHistoryView", "title": "Show Line History View", "category": "GitLens" @@ -6556,6 +6562,18 @@ "category": "GitLens" }, { + "command": "gitlens.views.graph.openInTab", + "title": "Open in Editor Area", + "category": "GitLens", + "icon": "$(link-external)" + }, + { + "command": "gitlens.views.graph.refresh", + "title": "Refresh", + "category": "GitLens", + "icon": "$(refresh)" + }, + { "command": "gitlens.views.graphDetails.refresh", "title": "Refresh", "category": "GitLens", @@ -7211,13 +7229,15 @@ }, { "command": "gitlens.graph.switchToEditorLayout", - "title": "Switch Commit Graph to Editor Layout", - "category": "GitLens" + "title": "Prefer Commit Graph in Editor Area", + "category": "GitLens", + "enablement": "config.gitlens.graph.layout != editor" }, { "command": "gitlens.graph.switchToPanelLayout", - "title": "Switch Commit Graph to Panel Layout", - "category": "GitLens" + "title": "Prefer Commit Graph in Panel", + "category": "GitLens", + "enablement": "config.gitlens.graph.layout != panel" }, { "command": "gitlens.graph.push", @@ -8065,23 +8085,23 @@ }, { "command": "gitlens.showGraph", - "when": "false" + "when": "gitlens:enabled" }, { "command": "gitlens.showGraphPage", - "when": "gitlens:enabled && config.gitlens.graph.layout == editor" + "when": "gitlens:enabled" }, { "command": "gitlens.showGraphView", - "when": "gitlens:enabled && config.gitlens.graph.layout == panel" + "when": "gitlens:enabled" }, { "command": "gitlens.toggleGraph", - "when": "gitlens:enabled && config.gitlens.graph.layout == panel" + "when": "gitlens:enabled" }, { "command": "gitlens.toggleMaximizedGraph", - "when": "gitlens:enabled && config.gitlens.graph.layout == panel" + "when": "gitlens:enabled" }, { "command": "gitlens.showHomeView", @@ -8096,6 +8116,10 @@ "when": "false" }, { + "command": "gitlens.showInCommitGraphView", + "when": "false" + }, + { "command": "gitlens.showLineHistoryView", "when": "gitlens:enabled && !gitlens:hasVirtualFolders" }, @@ -9304,6 +9328,14 @@ "when": "false" }, { + "command": "gitlens.views.graph.openInTab", + "when": "false" + }, + { + "command": "gitlens.views.graph.refresh", + "when": "false" + }, + { "command": "gitlens.views.graphDetails.refresh", "when": "false" }, @@ -10337,26 +10369,6 @@ "group": "navigation@-99" }, { - "command": "gitlens.graph.push", - "when": "gitlens:webview:graph:active && gitlens:hasRemotes && !gitlens:readonly && !gitlens:untrusted && !gitlens:hasVirtualFolders", - "group": "navigation@-103" - }, - { - "command": "gitlens.graph.pull", - "when": "gitlens:webview:graph:active && gitlens:hasRemotes && !gitlens:readonly && !gitlens:untrusted && !gitlens:hasVirtualFolders", - "group": "navigation@-102" - }, - { - "command": "gitlens.graph.fetch", - "when": "gitlens:webview:graph:active && gitlens:hasRemotes && !gitlens:readonly && !gitlens:untrusted && !gitlens:hasVirtualFolders", - "group": "navigation@-101" - }, - { - "command": "gitlens.graph.switchToAnotherBranch", - "when": "gitlens:webview:graph:active && !gitlens:readonly && !gitlens:untrusted && !gitlens:hasVirtualFolders", - "group": "navigation@-100" - }, - { "command": "gitlens.graph.refresh", "when": "gitlens:webview:graph:active", "group": "navigation@-99" @@ -10875,6 +10887,16 @@ "group": "5_gitlens@0" }, { + "command": "gitlens.views.graph.openInTab", + "when": "view =~ /^gitlens\\.views\\.graph\\b/", + "group": "navigation@-100" + }, + { + "command": "gitlens.views.graph.refresh", + "when": "view =~ /^gitlens\\.views\\.graph\\b/", + "group": "navigation@-99" + }, + { "command": "gitlens.views.graphDetails.refresh", "when": "view =~ /^gitlens\\.views\\.graphDetails/", "group": "navigation@99" @@ -11255,11 +11277,6 @@ "group": "5_gitlens@3" }, { - "command": "gitlens.graph.refresh", - "when": "view =~ /^gitlens\\.views\\.graph\\b/", - "group": "navigation@-99" - }, - { "submenu": "gitlens/graph/configuration", "when": "view =~ /^gitlens\\.views\\.graph\\b/", "group": "navigation@-98" @@ -13684,13 +13701,11 @@ "gitlens/graph/configuration": [ { "command": "gitlens.graph.switchToEditorLayout", - "group": "1_gitlens@1", - "when": "config.gitlens.graph.layout != editor" + "group": "1_gitlens@1" }, { "command": "gitlens.graph.switchToPanelLayout", - "group": "1_gitlens@1", - "when": "config.gitlens.graph.layout != panel" + "group": "1_gitlens@1" }, { "command": "gitlens.showSettingsPage#commit-graph", @@ -14559,7 +14574,7 @@ "type": "webview", "id": "gitlens.views.graph", "name": "Graph", - "when": "!gitlens:disabled && gitlens:plus:enabled && config.gitlens.graph.layout == panel", + "when": "!gitlens:disabled && gitlens:plus:enabled", "contextualTitle": "GL", "icon": "$(gitlens-graph)", "initialSize": 4, @@ -14569,7 +14584,7 @@ "type": "webview", "id": "gitlens.views.graphDetails", "name": "Graph Details", - "when": "!gitlens:disabled && gitlens:plus:enabled && config.gitlens.graph.layout == panel", + "when": "!gitlens:disabled && gitlens:plus:enabled", "contextualTitle": "GL", "icon": "$(gitlens-commit-view)", "initialSize": 1, diff --git a/src/constants.ts b/src/constants.ts index a5622cf..4e8c160 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -243,6 +243,7 @@ export const enum Commands { ShowHomeView = 'gitlens.showHomeView', ShowAccountView = 'gitlens.showAccountView', ShowInCommitGraph = 'gitlens.showInCommitGraph', + ShowInCommitGraphView = 'gitlens.showInCommitGraphView', ShowInDetailsView = 'gitlens.showInDetailsView', ShowLastQuickPick = 'gitlens.showLastQuickPick', ShowLineCommitInView = 'gitlens.showLineCommitInView', diff --git a/src/container.ts b/src/container.ts index 3a8932f..16e39f2 100644 --- a/src/container.ts +++ b/src/container.ts @@ -226,9 +226,7 @@ export class Container { this._disposables.push((this._graphPanel = registerGraphWebviewPanel(this._webviews))); this._disposables.push(registerGraphWebviewCommands(this, this._graphPanel)); - if (configuration.get('graph.layout') === 'panel') { - this._disposables.push((this._graphView = registerGraphWebviewView(this._webviews))); - } + this._disposables.push((this._graphView = registerGraphWebviewView(this._webviews))); this._disposables.push(new GraphStatusBarController(this)); const settingsWebviewPanel = registerSettingsWebviewPanel(this._webviews); @@ -324,16 +322,6 @@ export class Container { if (configuration.changed(e, 'mode')) { this.ensureModeApplied(); } - - if (configuration.changed(e, 'graph.layout')) { - if (configuration.get('graph.layout') === 'panel') { - this._graphPanel?.close(); - this._graphView = registerGraphWebviewView(this._webviews); - } else { - this._graphView?.dispose(); - this._graphView = undefined; - } - } } private _accountAuthentication: AccountAuthenticationProvider; @@ -482,12 +470,8 @@ export class Container { } private readonly _graphPanel: WebviewPanelProxy; - private _graphView: WebviewViewProxy | undefined; + private readonly _graphView: WebviewViewProxy; get graphView() { - if (this._graphView == null) { - this._disposables.push((this._graphView = registerGraphWebviewView(this._webviews))); - } - return this._graphView; } diff --git a/src/plus/webviews/graph/graphWebview.ts b/src/plus/webviews/graph/graphWebview.ts index d3c851b..1d3dfab 100644 --- a/src/plus/webviews/graph/graphWebview.ts +++ b/src/plus/webviews/graph/graphWebview.ts @@ -347,122 +347,161 @@ export class GraphWebviewProvider implements WebviewProvider { registerCommands(): Disposable[] { return [ - registerCommand(Commands.RefreshGraph, () => this.host.refresh(true)), - - registerCommand('gitlens.graph.push', this.push, this), - registerCommand('gitlens.graph.pull', this.pull, this), - registerCommand('gitlens.graph.fetch', this.fetch, this), - registerCommand('gitlens.graph.publishBranch', this.publishBranch, this), - registerCommand('gitlens.graph.switchToAnotherBranch', this.switchToAnother, this), - - registerCommand('gitlens.graph.createBranch', this.createBranch, this), - registerCommand('gitlens.graph.deleteBranch', this.deleteBranch, this), - registerCommand('gitlens.graph.copyRemoteBranchUrl', item => this.openBranchOnRemote(item, true), this), - registerCommand('gitlens.graph.openBranchOnRemote', this.openBranchOnRemote, this), - registerCommand('gitlens.graph.mergeBranchInto', this.mergeBranchInto, this), - registerCommand('gitlens.graph.rebaseOntoBranch', this.rebase, this), - registerCommand('gitlens.graph.rebaseOntoUpstream', this.rebaseToRemote, this), - registerCommand('gitlens.graph.renameBranch', this.renameBranch, this), - - registerCommand('gitlens.graph.switchToBranch', this.switchTo, this), - - registerCommand('gitlens.graph.hideLocalBranch', this.hideRef, this), - registerCommand('gitlens.graph.hideRemoteBranch', this.hideRef, this), - registerCommand('gitlens.graph.hideRemote', item => this.hideRef(item, { remote: true }), this), - registerCommand('gitlens.graph.hideRefGroup', item => this.hideRef(item, { group: true }), this), - registerCommand('gitlens.graph.hideTag', this.hideRef, this), - - registerCommand('gitlens.graph.cherryPick', this.cherryPick, this), - registerCommand('gitlens.graph.copyRemoteCommitUrl', item => this.openCommitOnRemote(item, true), this), - registerCommand('gitlens.graph.showInDetailsView', this.openInDetailsView, this), - registerCommand('gitlens.graph.openCommitOnRemote', this.openCommitOnRemote, this), - registerCommand('gitlens.graph.openSCM', this.openSCM, this), - registerCommand('gitlens.graph.rebaseOntoCommit', this.rebase, this), - registerCommand('gitlens.graph.resetCommit', this.resetCommit, this), - registerCommand('gitlens.graph.resetToCommit', this.resetToCommit, this), - registerCommand('gitlens.graph.resetToTip', this.resetToTip, this), - registerCommand('gitlens.graph.revert', this.revertCommit, this), - registerCommand('gitlens.graph.switchToCommit', this.switchTo, this), - registerCommand('gitlens.graph.undoCommit', this.undoCommit, this), - - registerCommand('gitlens.graph.saveStash', this.saveStash, this), - registerCommand('gitlens.graph.applyStash', this.applyStash, this), - registerCommand('gitlens.graph.stash.delete', this.deleteStash, this), - registerCommand('gitlens.graph.stash.rename', this.renameStash, this), - - registerCommand('gitlens.graph.createTag', this.createTag, this), - registerCommand('gitlens.graph.deleteTag', this.deleteTag, this), - registerCommand('gitlens.graph.switchToTag', this.switchTo, this), - - registerCommand('gitlens.graph.createWorktree', this.createWorktree, this), - - registerCommand('gitlens.graph.createPullRequest', this.createPullRequest, this), - registerCommand('gitlens.graph.openPullRequestOnRemote', this.openPullRequestOnRemote, this), - - registerCommand('gitlens.graph.compareWithUpstream', this.compareWithUpstream, this), - registerCommand('gitlens.graph.compareWithHead', this.compareHeadWith, this), - registerCommand('gitlens.graph.compareWithWorking', this.compareWorkingWith, this), - registerCommand('gitlens.graph.compareAncestryWithWorking', this.compareAncestryWithWorking, this), - - registerCommand('gitlens.graph.copy', this.copy, this), - registerCommand('gitlens.graph.copyMessage', this.copyMessage, this), - registerCommand('gitlens.graph.copySha', this.copySha, this), - - registerCommand('gitlens.graph.addAuthor', this.addAuthor, this), - - registerCommand('gitlens.graph.columnAuthorOn', () => this.toggleColumn('author', true)), - registerCommand('gitlens.graph.columnAuthorOff', () => this.toggleColumn('author', false)), - registerCommand('gitlens.graph.columnDateTimeOn', () => this.toggleColumn('datetime', true)), - registerCommand('gitlens.graph.columnDateTimeOff', () => this.toggleColumn('datetime', false)), - registerCommand('gitlens.graph.columnShaOn', () => this.toggleColumn('sha', true)), - registerCommand('gitlens.graph.columnShaOff', () => this.toggleColumn('sha', false)), - registerCommand('gitlens.graph.columnChangesOn', () => this.toggleColumn('changes', true)), - registerCommand('gitlens.graph.columnChangesOff', () => this.toggleColumn('changes', false)), - registerCommand('gitlens.graph.columnGraphOn', () => this.toggleColumn('graph', true)), - registerCommand('gitlens.graph.columnGraphOff', () => this.toggleColumn('graph', false)), - registerCommand('gitlens.graph.columnMessageOn', () => this.toggleColumn('message', true)), - registerCommand('gitlens.graph.columnMessageOff', () => this.toggleColumn('message', false)), - registerCommand('gitlens.graph.columnRefOn', () => this.toggleColumn('ref', true)), - registerCommand('gitlens.graph.columnRefOff', () => this.toggleColumn('ref', false)), - registerCommand('gitlens.graph.columnGraphCompact', () => this.setColumnMode('graph', 'compact')), - registerCommand('gitlens.graph.columnGraphDefault', () => this.setColumnMode('graph', undefined)), - registerCommand('gitlens.graph.scrollMarkerLocalBranchOn', () => + registerCommand(`${this.host.id}.refresh`, () => this.host.refresh(true)), + + ...(this.host.isView() + ? [ + registerCommand( + `${this.host.id}.openInTab`, + () => void executeCommand(Commands.ShowGraphPage, this.repository), + ), + ] + : []), + + this.host.registerWebviewCommand('gitlens.graph.push', this.push), + this.host.registerWebviewCommand('gitlens.graph.pull', this.pull), + this.host.registerWebviewCommand('gitlens.graph.fetch', this.fetch), + this.host.registerWebviewCommand('gitlens.graph.publishBranch', this.publishBranch), + this.host.registerWebviewCommand('gitlens.graph.switchToAnotherBranch', this.switchToAnother), + + this.host.registerWebviewCommand('gitlens.graph.createBranch', this.createBranch), + this.host.registerWebviewCommand('gitlens.graph.deleteBranch', this.deleteBranch), + this.host.registerWebviewCommand('gitlens.graph.copyRemoteBranchUrl', item => + this.openBranchOnRemote(item, true), + ), + this.host.registerWebviewCommand('gitlens.graph.openBranchOnRemote', this.openBranchOnRemote), + this.host.registerWebviewCommand('gitlens.graph.mergeBranchInto', this.mergeBranchInto), + this.host.registerWebviewCommand('gitlens.graph.rebaseOntoBranch', this.rebase), + this.host.registerWebviewCommand('gitlens.graph.rebaseOntoUpstream', this.rebaseToRemote), + this.host.registerWebviewCommand('gitlens.graph.renameBranch', this.renameBranch), + + this.host.registerWebviewCommand('gitlens.graph.switchToBranch', this.switchTo), + + this.host.registerWebviewCommand('gitlens.graph.hideLocalBranch', this.hideRef), + this.host.registerWebviewCommand('gitlens.graph.hideRemoteBranch', this.hideRef), + this.host.registerWebviewCommand('gitlens.graph.hideRemote', item => + this.hideRef(item, { remote: true }), + ), + this.host.registerWebviewCommand('gitlens.graph.hideRefGroup', item => + this.hideRef(item, { group: true }), + ), + this.host.registerWebviewCommand('gitlens.graph.hideTag', this.hideRef), + + this.host.registerWebviewCommand('gitlens.graph.cherryPick', this.cherryPick), + this.host.registerWebviewCommand('gitlens.graph.copyRemoteCommitUrl', item => + this.openCommitOnRemote(item, true), + ), + this.host.registerWebviewCommand('gitlens.graph.showInDetailsView', this.openInDetailsView), + this.host.registerWebviewCommand('gitlens.graph.openCommitOnRemote', this.openCommitOnRemote), + this.host.registerWebviewCommand('gitlens.graph.openSCM', this.openSCM), + this.host.registerWebviewCommand('gitlens.graph.rebaseOntoCommit', this.rebase), + this.host.registerWebviewCommand('gitlens.graph.resetCommit', this.resetCommit), + this.host.registerWebviewCommand('gitlens.graph.resetToCommit', this.resetToCommit), + this.host.registerWebviewCommand('gitlens.graph.resetToTip', this.resetToTip), + this.host.registerWebviewCommand('gitlens.graph.revert', this.revertCommit), + this.host.registerWebviewCommand('gitlens.graph.switchToCommit', this.switchTo), + this.host.registerWebviewCommand('gitlens.graph.undoCommit', this.undoCommit), + + this.host.registerWebviewCommand('gitlens.graph.saveStash', this.saveStash), + this.host.registerWebviewCommand('gitlens.graph.applyStash', this.applyStash), + this.host.registerWebviewCommand('gitlens.graph.stash.delete', this.deleteStash), + this.host.registerWebviewCommand('gitlens.graph.stash.rename', this.renameStash), + + this.host.registerWebviewCommand('gitlens.graph.createTag', this.createTag), + this.host.registerWebviewCommand('gitlens.graph.deleteTag', this.deleteTag), + this.host.registerWebviewCommand('gitlens.graph.switchToTag', this.switchTo), + + this.host.registerWebviewCommand('gitlens.graph.createWorktree', this.createWorktree), + + this.host.registerWebviewCommand('gitlens.graph.createPullRequest', this.createPullRequest), + this.host.registerWebviewCommand('gitlens.graph.openPullRequestOnRemote', this.openPullRequestOnRemote), + + this.host.registerWebviewCommand('gitlens.graph.compareWithUpstream', this.compareWithUpstream), + this.host.registerWebviewCommand('gitlens.graph.compareWithHead', this.compareHeadWith), + this.host.registerWebviewCommand('gitlens.graph.compareWithWorking', this.compareWorkingWith), + this.host.registerWebviewCommand( + 'gitlens.graph.compareAncestryWithWorking', + this.compareAncestryWithWorking, + ), + + this.host.registerWebviewCommand('gitlens.graph.copy', this.copy), + this.host.registerWebviewCommand('gitlens.graph.copyMessage', this.copyMessage), + this.host.registerWebviewCommand('gitlens.graph.copySha', this.copySha), + + this.host.registerWebviewCommand('gitlens.graph.addAuthor', this.addAuthor), + + this.host.registerWebviewCommand('gitlens.graph.columnAuthorOn', () => this.toggleColumn('author', true)), + this.host.registerWebviewCommand('gitlens.graph.columnAuthorOff', () => this.toggleColumn('author', false)), + this.host.registerWebviewCommand('gitlens.graph.columnDateTimeOn', () => + this.toggleColumn('datetime', true), + ), + this.host.registerWebviewCommand('gitlens.graph.columnDateTimeOff', () => + this.toggleColumn('datetime', false), + ), + this.host.registerWebviewCommand('gitlens.graph.columnShaOn', () => this.toggleColumn('sha', true)), + this.host.registerWebviewCommand('gitlens.graph.columnShaOff', () => this.toggleColumn('sha', false)), + this.host.registerWebviewCommand('gitlens.graph.columnChangesOn', () => this.toggleColumn('changes', true)), + this.host.registerWebviewCommand('gitlens.graph.columnChangesOff', () => + this.toggleColumn('changes', false), + ), + this.host.registerWebviewCommand('gitlens.graph.columnGraphOn', () => this.toggleColumn('graph', true)), + this.host.registerWebviewCommand('gitlens.graph.columnGraphOff', () => this.toggleColumn('graph', false)), + this.host.registerWebviewCommand('gitlens.graph.columnMessageOn', () => this.toggleColumn('message', true)), + this.host.registerWebviewCommand('gitlens.graph.columnMessageOff', () => + this.toggleColumn('message', false), + ), + this.host.registerWebviewCommand('gitlens.graph.columnRefOn', () => this.toggleColumn('ref', true)), + this.host.registerWebviewCommand('gitlens.graph.columnRefOff', () => this.toggleColumn('ref', false)), + this.host.registerWebviewCommand('gitlens.graph.columnGraphCompact', () => + this.setColumnMode('graph', 'compact'), + ), + this.host.registerWebviewCommand('gitlens.graph.columnGraphDefault', () => + this.setColumnMode('graph', undefined), + ), + this.host.registerWebviewCommand('gitlens.graph.scrollMarkerLocalBranchOn', () => this.toggleScrollMarker('localBranches', true), ), - registerCommand('gitlens.graph.scrollMarkerLocalBranchOff', () => + this.host.registerWebviewCommand('gitlens.graph.scrollMarkerLocalBranchOff', () => this.toggleScrollMarker('localBranches', false), ), - registerCommand('gitlens.graph.scrollMarkerRemoteBranchOn', () => + this.host.registerWebviewCommand('gitlens.graph.scrollMarkerRemoteBranchOn', () => this.toggleScrollMarker('remoteBranches', true), ), - registerCommand('gitlens.graph.scrollMarkerRemoteBranchOff', () => + this.host.registerWebviewCommand('gitlens.graph.scrollMarkerRemoteBranchOff', () => this.toggleScrollMarker('remoteBranches', false), ), - registerCommand('gitlens.graph.scrollMarkerStashOn', () => this.toggleScrollMarker('stashes', true)), - registerCommand('gitlens.graph.scrollMarkerStashOff', () => this.toggleScrollMarker('stashes', false)), - registerCommand('gitlens.graph.scrollMarkerTagOn', () => this.toggleScrollMarker('tags', true)), - registerCommand('gitlens.graph.scrollMarkerTagOff', () => this.toggleScrollMarker('tags', false)), - - registerCommand('gitlens.graph.copyDeepLinkToBranch', this.copyDeepLinkToBranch, this), - registerCommand('gitlens.graph.copyDeepLinkToCommit', this.copyDeepLinkToCommit, this), - registerCommand('gitlens.graph.copyDeepLinkToRepo', this.copyDeepLinkToRepo, this), - registerCommand('gitlens.graph.copyDeepLinkToTag', this.copyDeepLinkToTag, this), - - registerCommand('gitlens.graph.openChangedFiles', this.openFiles, this), - registerCommand('gitlens.graph.openOnlyChangedFiles', this.openOnlyChangedFiles, this), - registerCommand('gitlens.graph.openChangedFileDiffs', this.openAllChanges, this), - registerCommand('gitlens.graph.openChangedFileDiffsWithWorking', this.openAllChangesWithWorking, this), - registerCommand('gitlens.graph.openChangedFileRevisions', this.openRevisions, this), - - registerCommand( - 'gitlens.graph.resetColumnsDefault', - () => this.updateColumns(defaultGraphColumnsSettings), - this, + this.host.registerWebviewCommand('gitlens.graph.scrollMarkerStashOn', () => + this.toggleScrollMarker('stashes', true), ), - registerCommand( - 'gitlens.graph.resetColumnsCompact', - () => this.updateColumns(compactGraphColumnsSettings), - this, + this.host.registerWebviewCommand('gitlens.graph.scrollMarkerStashOff', () => + this.toggleScrollMarker('stashes', false), + ), + this.host.registerWebviewCommand('gitlens.graph.scrollMarkerTagOn', () => + this.toggleScrollMarker('tags', true), + ), + this.host.registerWebviewCommand('gitlens.graph.scrollMarkerTagOff', () => + this.toggleScrollMarker('tags', false), + ), + + this.host.registerWebviewCommand('gitlens.graph.copyDeepLinkToBranch', this.copyDeepLinkToBranch), + this.host.registerWebviewCommand('gitlens.graph.copyDeepLinkToCommit', this.copyDeepLinkToCommit), + this.host.registerWebviewCommand('gitlens.graph.copyDeepLinkToRepo', this.copyDeepLinkToRepo), + this.host.registerWebviewCommand('gitlens.graph.copyDeepLinkToTag', this.copyDeepLinkToTag), + + this.host.registerWebviewCommand('gitlens.graph.openChangedFiles', this.openFiles), + this.host.registerWebviewCommand('gitlens.graph.openOnlyChangedFiles', this.openOnlyChangedFiles), + this.host.registerWebviewCommand('gitlens.graph.openChangedFileDiffs', this.openAllChanges), + this.host.registerWebviewCommand( + 'gitlens.graph.openChangedFileDiffsWithWorking', + this.openAllChangesWithWorking, + ), + this.host.registerWebviewCommand('gitlens.graph.openChangedFileRevisions', this.openRevisions), + + this.host.registerWebviewCommand('gitlens.graph.resetColumnsDefault', () => + this.updateColumns(defaultGraphColumnsSettings), + ), + this.host.registerWebviewCommand('gitlens.graph.resetColumnsCompact', () => + this.updateColumns(compactGraphColumnsSettings), ), ]; } @@ -792,10 +831,7 @@ export class GraphWebviewProvider implements WebviewProvider { }, ); - const details = - configuration.get('graph.layout') === 'panel' - ? this.container.graphDetailsView - : this.container.commitDetailsView; + const details = this.host.isView() ? this.container.graphDetailsView : this.container.commitDetailsView; if (!details.ready) { void details.show({ preserveFocus: e.preserveFocus }, { commit: commit, diff --git a/src/plus/webviews/graph/registration.ts b/src/plus/webviews/graph/registration.ts index d1780fd..ebc4b9b 100644 --- a/src/plus/webviews/graph/registration.ts +++ b/src/plus/webviews/graph/registration.ts @@ -34,7 +34,6 @@ export function registerGraphWebviewPanel(controller: WebviewsController) { const { GraphWebviewProvider } = await import(/* webpackChunkName: "graph" */ './graphWebview'); return new GraphWebviewProvider(container, host); }, - () => configuration.get('graph.layout') === 'editor', ); } @@ -55,7 +54,6 @@ export function registerGraphWebviewView(controller: WebviewsController) { const { GraphWebviewProvider } = await import(/* webpackChunkName: "graph" */ './graphWebview'); return new GraphWebviewProvider(container, host); }, - () => configuration.get('graph.layout') === 'panel', ); } @@ -113,5 +111,21 @@ export function registerGraphWebviewCommands(container: Container, webview: Webv } }, ), + registerCommand( + Commands.ShowInCommitGraphView, + ( + args: + | ShowInCommitGraphCommandArgs + | Repository + | BranchNode + | CommitNode + | CommitFileNode + | StashNode + | TagNode, + ) => { + const preserveFocus = 'preserveFocus' in args ? args.preserveFocus ?? false : false; + void container.graphView.show({ preserveFocus: preserveFocus }, args); + }, + ), ); } diff --git a/src/system/command.ts b/src/system/command.ts index 70bd629..f78669a 100644 --- a/src/system/command.ts +++ b/src/system/command.ts @@ -5,6 +5,9 @@ import type { Command } from '../commands/base'; import type { CoreCommands, CoreGitCommands, TreeViewCommands } from '../constants'; import { Commands } from '../constants'; import { Container } from '../container'; +import { isWebviewContext } from './webview'; + +export type CommandCallback = Parameters[1]; type CommandConstructor = new (container: Container) => Command; const registrableCommands: CommandConstructor[] = []; @@ -15,7 +18,7 @@ export function command(): ClassDecorator { }; } -export function registerCommand(command: string, callback: (...args: any[]) => any, thisArg?: any): Disposable { +export function registerCommand(command: string, callback: CommandCallback, thisArg?: any): Disposable { return commands.registerCommand( command, function (this: any, ...args) { @@ -26,6 +29,20 @@ export function registerCommand(command: string, callback: (...args: any[]) => a ); } +export function registerWebviewCommand(command: string, callback: CommandCallback, thisArg?: any): Disposable { + return commands.registerCommand( + command, + function (this: any, ...args) { + Container.instance.telemetry.sendEvent('command', { + command: command, + webview: isWebviewContext(args[0]) ? args[0].webview : '', + }); + callback.call(this, ...args); + }, + thisArg, + ); +} + export function registerCommands(container: Container): Disposable[] { return registrableCommands.map(c => new c(container)); } diff --git a/src/system/webview.ts b/src/system/webview.ts index 65e70c7..4848ce9 100644 --- a/src/system/webview.ts +++ b/src/system/webview.ts @@ -1,31 +1,46 @@ import type { WebviewIds, WebviewViewIds } from '../constants'; -export interface WebviewItemContext { - webview?: WebviewIds | WebviewViewIds; +export function createWebviewCommandLink( + command: `${WebviewIds | WebviewViewIds}.${string}`, + webviewId: WebviewIds | WebviewViewIds, +): string { + return `command:${command}?${encodeURIComponent(JSON.stringify({ webview: webviewId } satisfies WebviewContext))}`; +} + +export interface WebviewContext { + webview: WebviewIds | WebviewViewIds; +} + +export function isWebviewContext(item: object | null | undefined): item is WebviewContext { + if (item == null) return false; + + return 'webview' in item && item.webview != null; +} + +export interface WebviewItemContext extends Partial { webviewItem: string; webviewItemValue: TValue; } export function isWebviewItemContext( item: object | null | undefined, -): item is WebviewItemContext { +): item is WebviewItemContext & WebviewContext { if (item == null) return false; - return 'webview' in item && 'webviewItem' in item; + return 'webview' in item && item.webview != null && 'webviewItem' in item; } -export interface WebviewItemGroupContext { - webview?: WebviewIds | WebviewViewIds; +export interface WebviewItemGroupContext extends Partial { webviewItemGroup: string; webviewItemGroupValue: TValue; } export function isWebviewItemGroupContext( item: object | null | undefined, -): item is WebviewItemGroupContext { +): item is WebviewItemGroupContext & WebviewContext { if (item == null) return false; - return 'webview' in item && 'webviewItemGroup' in item; + return 'webview' in item && item.webview != null && 'webviewItemGroup' in item; } export function serializeWebviewItemContext(context: T): string { diff --git a/src/webviews/apps/plus/graph/GraphWrapper.tsx b/src/webviews/apps/plus/graph/GraphWrapper.tsx index fe6a2ec..f49c6e5 100644 --- a/src/webviews/apps/plus/graph/GraphWrapper.tsx +++ b/src/webviews/apps/plus/graph/GraphWrapper.tsx @@ -55,6 +55,7 @@ import { } from '../../../../plus/webviews/graph/protocol'; import type { Subscription } from '../../../../subscription'; import { pluralize } from '../../../../system/string'; +import { createWebviewCommandLink } from '../../../../system/webview'; import type { IpcNotificationType } from '../../../protocol'; import { MenuDivider, MenuItem, MenuLabel, MenuList } from '../../shared/components/menu/react'; import { PopMenu } from '../../shared/components/overlays/pop-menu/react'; @@ -978,9 +979,10 @@ export function GraphWrapper({ const lastFetchedDate = lastFetched && new Date(lastFetched); const fetchedText = lastFetchedDate && lastFetchedDate.getTime() !== 0 ? fromNow(lastFetchedDate) : undefined; + let action: 'fetch' | 'pull' | 'push' = 'fetch'; + let icon = 'sync'; let label = 'Fetch'; - let action = 'command:gitlens.graph.fetch'; let isBehind = false; let isAhead = false; @@ -993,12 +995,12 @@ export function GraphWrapper({ const branchPrefix = `Branch ${branchName} is`; remote = `${branchState.upstream}${branchState.provider?.name ? ` on ${branchState.provider?.name}` : ''}`; if (isBehind) { - action = 'command:gitlens.graph.pull'; + action = 'pull'; icon = 'arrow-down'; label = 'Pull'; tooltip = `Pull from ${remote}\n${branchPrefix} ${pluralize('commit', branchState.behind)} behind of`; } else if (isAhead) { - action = 'command:gitlens.graph.push'; + action = 'push'; icon = 'arrow-up'; label = 'Push'; tooltip = `Push to ${remote}\n${branchPrefix} ${pluralize('commit', branchState.ahead)} ahead of`; @@ -1017,7 +1019,7 @@ export function GraphWrapper({
{(isBehind || isAhead) && ( @@ -1041,7 +1043,11 @@ export function GraphWrapper({ )} )} - + Fetch {fetchedText && ({fetchedText})} @@ -1099,7 +1105,7 @@ export function GraphWrapper({
+ +
+ +
+
+
+ +
+
(Commands.ShowInCommitGraph, { - ref: getReferenceFromRevision(this._context.commit), - }); + void executeCommand( + this.options.mode === 'graph' + ? Commands.ShowInCommitGraphView + : Commands.ShowInCommitGraph, + { + ref: getReferenceFromRevision(this._context.commit), + }, + ); break; case 'more': this.showCommitActions(); diff --git a/src/webviews/webviewCommandRegistrar.ts b/src/webviews/webviewCommandRegistrar.ts new file mode 100644 index 0000000..2b43c6d --- /dev/null +++ b/src/webviews/webviewCommandRegistrar.ts @@ -0,0 +1,70 @@ +import type { Disposable } from 'vscode'; +import type { CommandCallback } from '../system/command'; +import { registerWebviewCommand } from '../system/command'; +import type { WebviewContext } from '../system/webview'; +import { isWebviewContext } from '../system/webview'; +import type { WebviewProvider } from './webviewController'; + +export type WebviewCommandCallback> = (arg?: T | undefined) => any; +export class WebviewCommandRegistrar implements Disposable { + private readonly _commandRegistrations = new Map< + string, + { handlers: Map; subscription: Disposable } + >(); + + dispose() { + this._commandRegistrations.forEach(({ subscription }) => void subscription.dispose()); + } + + registerCommand>( + provider: T, + id: string, + command: string, + callback: CommandCallback, + ) { + let registration = this._commandRegistrations.get(command); + if (registration == null) { + const handlers = new Map(); + registration = { + subscription: registerWebviewCommand( + command, + (...args: any[]) => { + const item = args[0]; + if (!isWebviewContext(item)) { + debugger; + return; + } + + const handler = handlers.get(item.webview); + if (handler == null) { + throw new Error( + `Unable to find Command '${command}' registration for Webview '${item.webview}'`, + ); + } + + handler.callback.call(handler.thisArg, item); + }, + this, + ), + handlers: handlers, + }; + this._commandRegistrations.set(command, registration); + } + + if (registration.handlers.has(id)) { + throw new Error(`Command '${command}' has already been registered for Webview '${id}'`); + } + + registration.handlers.set(id, { callback: callback, thisArg: provider }); + + return { + dispose: () => { + registration!.handlers.delete(id); + if (registration!.handlers.size === 0) { + this._commandRegistrations.delete(command); + registration!.subscription.dispose(); + } + }, + }; + } +} diff --git a/src/webviews/webviewController.ts b/src/webviews/webviewController.ts index 0b9abac..26dad59 100644 --- a/src/webviews/webviewController.ts +++ b/src/webviews/webviewController.ts @@ -8,8 +8,10 @@ import { setContext } from '../system/context'; import { debug, logName } from '../system/decorators/log'; import { serialize } from '../system/decorators/serialize'; import { isPromise } from '../system/promise'; +import type { WebviewContext } from '../system/webview'; import type { IpcMessage, IpcMessageParams, IpcNotificationType, WebviewFocusChangedParams } from './protocol'; import { ExecuteCommandType, onIpc, WebviewFocusChangedCommandType, WebviewReadyCommandType } from './protocol'; +import type { WebviewCommandCallback, WebviewCommandRegistrar } from './webviewCommandRegistrar'; import type { WebviewPanelDescriptor, WebviewViewDescriptor } from './webviewsController'; const maxSmallIntegerV8 = 2 ** 30; // Max number that can be stored in V8's smis (small integers) @@ -75,6 +77,7 @@ export class WebviewController< { static async create( container: Container, + commandRegistrar: WebviewCommandRegistrar, descriptor: WebviewPanelDescriptor, parent: WebviewPanel, resolveProvider: ( @@ -84,6 +87,7 @@ export class WebviewController< ): Promise>; static async create( container: Container, + commandRegistrar: WebviewCommandRegistrar, descriptor: WebviewViewDescriptor, parent: WebviewView, resolveProvider: ( @@ -93,6 +97,7 @@ export class WebviewController< ): Promise>; static async create( container: Container, + commandRegistrar: WebviewCommandRegistrar, descriptor: WebviewPanelDescriptor | WebviewViewDescriptor, parent: WebviewPanel | WebviewView, resolveProvider: ( @@ -102,6 +107,7 @@ export class WebviewController< ): Promise> { const controller = new WebviewController( container, + commandRegistrar, descriptor, parent, resolveProvider, @@ -128,6 +134,7 @@ export class WebviewController< private constructor( private readonly container: Container, + private readonly _commandRegistrar: WebviewCommandRegistrar, private readonly descriptor: Descriptor, public readonly parent: GetParentType, resolveProvider: ( @@ -179,6 +186,10 @@ export class WebviewController< this.disposable?.dispose(); } + registerWebviewCommand>(command: string, callback: WebviewCommandCallback) { + return this._commandRegistrar.registerCommand(this.provider, this.id, command, callback); + } + private _initializing: Promise | undefined; private async initialize() { if (this._initializing == null) return; diff --git a/src/webviews/webviewsController.ts b/src/webviews/webviewsController.ts index ba2f52a..0a36be6 100644 --- a/src/webviews/webviewsController.ts +++ b/src/webviews/webviewsController.ts @@ -15,6 +15,7 @@ import { debug } from '../system/decorators/log'; import { Logger } from '../system/logger'; import { getLogScope } from '../system/logger.scope'; import type { TrackedUsageFeatures } from '../telemetry/usageTracker'; +import { WebviewCommandRegistrar } from './webviewCommandRegistrar'; import type { WebviewProvider } from './webviewController'; import { WebviewController } from './webviewController'; @@ -74,10 +75,13 @@ export interface WebviewViewProxy extends Disposable { export class WebviewsController implements Disposable { private readonly disposables: Disposable[] = []; + private readonly _commandRegistrar: WebviewCommandRegistrar; private readonly _panels = new Map>(); private readonly _views = new Map>(); - constructor(private readonly container: Container) {} + constructor(private readonly container: Container) { + this.disposables.push((this._commandRegistrar = new WebviewCommandRegistrar())); + } dispose() { this.disposables.forEach(d => void d.dispose()); @@ -135,6 +139,7 @@ export class WebviewsController implements Disposable { const controller = await WebviewController.create( this.container, + this._commandRegistrar, descriptor, webviewView, resolveProvider, @@ -219,7 +224,7 @@ export class WebviewsController implements Disposable { this._panels.set(descriptor.id, registration); const disposables: Disposable[] = []; - const { container } = this; + const { container, _commandRegistrar: commandRegistrar } = this; let serializedPanel: WebviewPanel | undefined; @@ -273,7 +278,13 @@ export class WebviewsController implements Disposable { panel.iconPath = Uri.file(container.context.asAbsolutePath(descriptor.iconPath)); - controller = await WebviewController.create(container, descriptor, panel, resolveProvider); + controller = await WebviewController.create( + container, + commandRegistrar, + descriptor, + panel, + resolveProvider, + ); registration.controller = controller; disposables.push(