diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index b5bede72cdd..a63189d3f73 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { lstat, Stats } from 'fs'; import * as os from 'os'; import * as path from 'path'; import { commands, Disposable, LineChange, MessageOptions, OutputChannel, Position, ProgressLocation, QuickPickItem, Range, SourceControlResourceState, TextDocumentShowOptions, TextEditor, Uri, ViewColumn, window, workspace, WorkspaceEdit, WorkspaceFolder, TimelineItem, env, Selection, TextDocumentContentProvider } from 'vscode'; @@ -357,167 +356,14 @@ export class CommandCenter { } @command('git.openResource') - async openResource(resource: Resource, preserveFocus: boolean): Promise { + async openResource(resource: Resource): Promise { const repository = this.model.getRepository(resource.resourceUri); if (!repository) { return; } - const config = workspace.getConfiguration('git', Uri.file(repository.root)); - const openDiffOnClick = config.get('openDiffOnClick'); - - if (openDiffOnClick) { - await this._openResource(resource, undefined, preserveFocus, false); - } else { - await this.openFile(resource); - } - } - - private async _openResource(resource: Resource, preview?: boolean, preserveFocus?: boolean, preserveSelection?: boolean): Promise { - let stat: Stats | undefined; - - try { - stat = await new Promise((c, e) => lstat(resource.resourceUri.fsPath, (err, stat) => err ? e(err) : c(stat))); - } catch (err) { - // noop - } - - let left: Uri | undefined; - let right: Uri | undefined; - - if (stat && stat.isDirectory()) { - const repository = this.model.getRepositoryForSubmodule(resource.resourceUri); - - if (repository) { - right = toGitUri(resource.resourceUri, resource.resourceGroupType === ResourceGroupType.Index ? 'index' : 'wt', { submoduleOf: repository.root }); - } - } else { - if (resource.type === Status.DELETED_BY_US || resource.type === Status.DELETED_BY_THEM) { - left = toGitUri(resource.resourceUri, '~1'); - } else { - left = this.getLeftResource(resource); - } - - right = this.getRightResource(resource); - } - - const title = this.getTitle(resource); - - if (!right) { - // TODO - console.error('oh no'); - return; - } - - const opts: TextDocumentShowOptions = { - preserveFocus, - preview, - viewColumn: ViewColumn.Active - }; - - const activeTextEditor = window.activeTextEditor; - - // Check if active text editor has same path as other editor. we cannot compare via - // URI.toString() here because the schemas can be different. Instead we just go by path. - if (preserveSelection && activeTextEditor && activeTextEditor.document.uri.path === right.path) { - opts.selection = activeTextEditor.selection; - } - - if (!left) { - await commands.executeCommand('vscode.open', right, { ...opts, override: resource.type === Status.BOTH_MODIFIED ? false : undefined }, title); - } else { - await commands.executeCommand('vscode.diff', left, right, title, opts); - } - } - - private getLeftResource(resource: Resource): Uri | undefined { - switch (resource.type) { - case Status.INDEX_MODIFIED: - case Status.INDEX_RENAMED: - case Status.INDEX_ADDED: - return toGitUri(resource.original, 'HEAD'); - - case Status.MODIFIED: - case Status.UNTRACKED: - return toGitUri(resource.resourceUri, '~'); - - case Status.DELETED_BY_THEM: - return toGitUri(resource.resourceUri, ''); - } - return undefined; - } - - private getRightResource(resource: Resource): Uri | undefined { - switch (resource.type) { - case Status.INDEX_MODIFIED: - case Status.INDEX_ADDED: - case Status.INDEX_COPIED: - case Status.INDEX_RENAMED: - return toGitUri(resource.resourceUri, ''); - - case Status.INDEX_DELETED: - case Status.DELETED: - return toGitUri(resource.resourceUri, 'HEAD'); - - case Status.DELETED_BY_US: - return toGitUri(resource.resourceUri, '~3'); - - case Status.DELETED_BY_THEM: - return toGitUri(resource.resourceUri, '~2'); - - case Status.MODIFIED: - case Status.UNTRACKED: - case Status.IGNORED: - case Status.INTENT_TO_ADD: - const repository = this.model.getRepository(resource.resourceUri); - - if (!repository) { - return; - } - - const uriString = resource.resourceUri.toString(); - const [indexStatus] = repository.indexGroup.resourceStates.filter(r => r.resourceUri.toString() === uriString); - - if (indexStatus && indexStatus.renameResourceUri) { - return indexStatus.renameResourceUri; - } - - return resource.resourceUri; - - case Status.BOTH_ADDED: - case Status.BOTH_MODIFIED: - return resource.resourceUri; - } - return undefined; - } - - private getTitle(resource: Resource): string { - const basename = path.basename(resource.resourceUri.fsPath); - - switch (resource.type) { - case Status.INDEX_MODIFIED: - case Status.INDEX_RENAMED: - case Status.INDEX_ADDED: - return localize('git.title.index', '{0} (Index)', basename); - - case Status.MODIFIED: - case Status.BOTH_ADDED: - case Status.BOTH_MODIFIED: - return localize('git.title.workingTree', '{0} (Working Tree)', basename); - - case Status.DELETED_BY_US: - return localize('git.title.theirs', '{0} (Theirs)', basename); - - case Status.DELETED_BY_THEM: - return localize('git.title.ours', '{0} (Ours)', basename); - - case Status.UNTRACKED: - return localize('git.title.untracked', '{0} (Untracked)', basename); - - default: - return ''; - } + await resource.open(); } async cloneRepository(url?: string, parentPath?: string, options: { recursive?: boolean } = {}): Promise { @@ -871,7 +717,7 @@ export class CommandCenter { return; } - const HEAD = this.getLeftResource(resource); + const HEAD = resource.leftUri; const basename = path.basename(resource.resourceUri.fsPath); const title = `${basename} (HEAD)`; @@ -889,10 +735,6 @@ export class CommandCenter { @command('git.openChange') async openChange(arg?: Resource | Uri, ...resourceStates: SourceControlResourceState[]): Promise { - const preserveFocus = arg instanceof Resource; - const preview = !(arg instanceof Resource); - - const preserveSelection = arg instanceof Uri || !arg; let resources: Resource[] | undefined = undefined; if (arg instanceof Uri) { @@ -919,7 +761,7 @@ export class CommandCenter { } for (const resource of resources) { - await this._openResource(resource, preview, preserveFocus, preserveSelection); + await resource.openChange(); } } diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index d09f118295c..f7948563a01 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -75,13 +75,21 @@ export class Resource implements SourceControlResourceState { return this._resourceUri; } - @memoize + get leftUri(): Uri | undefined { + return this.resources[0]; + } + + get rightUri(): Uri { + return this.resources[1]; + } + get command(): Command { - return { - command: 'git.openResource', - title: localize('open', "Open"), - arguments: [this] - }; + return this._commandResolver.resolveDefaultCommand(this); + } + + @memoize + private get resources(): [Uri | undefined, Uri] { + return this._commandResolver.getResources(this); } get resourceGroupType(): ResourceGroupType { return this._resourceGroupType; } @@ -262,12 +270,28 @@ export class Resource implements SourceControlResourceState { } constructor( + private _commandResolver: ResourceCommandResolver, private _resourceGroupType: ResourceGroupType, private _resourceUri: Uri, private _type: Status, private _useIcons: boolean, - private _renameResourceUri?: Uri + private _renameResourceUri?: Uri, ) { } + + async open(): Promise { + const command = this.command; + await commands.executeCommand(command.command, ...(command.arguments || [])); + } + + async openFile(): Promise { + const command = this._commandResolver.resolveFileCommand(this); + await commands.executeCommand(command.command, ...(command.arguments || [])); + } + + async openChange(): Promise { + const command = this._commandResolver.resolveChangeCommand(this); + await commands.executeCommand(command.command, ...(command.arguments || [])); + } } export const enum Operation { @@ -553,6 +577,142 @@ class DotGitWatcher implements IFileWatcher { } } +class ResourceCommandResolver { + + constructor(private repository: Repository) { } + + resolveDefaultCommand(resource: Resource): Command { + const config = workspace.getConfiguration('git', Uri.file(this.repository.root)); + const openDiffOnClick = config.get('openDiffOnClick', true); + return openDiffOnClick ? this.resolveChangeCommand(resource) : this.resolveFileCommand(resource); + } + + resolveFileCommand(resource: Resource): Command { + return { + command: 'vscode.open', + title: localize('open', "Open"), + arguments: [resource.resourceUri] + }; + } + + resolveChangeCommand(resource: Resource): Command { + const title = this.getTitle(resource); + + if (!resource.leftUri) { + return { + command: 'vscode.open', + title: localize('open', "Open"), + arguments: [resource.rightUri, { override: resource.type === Status.BOTH_MODIFIED ? false : undefined }, title] + }; + } else { + return { + command: 'vscode.diff', + title: localize('open', "Open"), + arguments: [resource.leftUri, resource.rightUri, title] + }; + } + } + + getResources(resource: Resource): [Uri | undefined, Uri] { + for (const submodule of this.repository.submodules) { + if (path.join(this.repository.root, submodule.path) === resource.resourceUri.fsPath) { + return [undefined, toGitUri(resource.resourceUri, resource.resourceGroupType === ResourceGroupType.Index ? 'index' : 'wt', { submoduleOf: this.repository.root })]; + } + } + + return [this.getLeftResource(resource), this.getRightResource(resource)]; + } + + private getLeftResource(resource: Resource): Uri | undefined { + switch (resource.type) { + case Status.INDEX_MODIFIED: + case Status.INDEX_RENAMED: + case Status.INDEX_ADDED: + return toGitUri(resource.original, 'HEAD'); + + case Status.MODIFIED: + case Status.UNTRACKED: + return toGitUri(resource.resourceUri, '~'); + + case Status.DELETED_BY_US: + case Status.DELETED_BY_THEM: + return toGitUri(resource.resourceUri, '~1'); + } + return undefined; + } + + private getRightResource(resource: Resource): Uri { + switch (resource.type) { + case Status.INDEX_MODIFIED: + case Status.INDEX_ADDED: + case Status.INDEX_COPIED: + case Status.INDEX_RENAMED: + return toGitUri(resource.resourceUri, ''); + + case Status.INDEX_DELETED: + case Status.DELETED: + return toGitUri(resource.resourceUri, 'HEAD'); + + case Status.DELETED_BY_US: + return toGitUri(resource.resourceUri, '~3'); + + case Status.DELETED_BY_THEM: + return toGitUri(resource.resourceUri, '~2'); + + case Status.MODIFIED: + case Status.UNTRACKED: + case Status.IGNORED: + case Status.INTENT_TO_ADD: + const uriString = resource.resourceUri.toString(); + const [indexStatus] = this.repository.indexGroup.resourceStates.filter(r => r.resourceUri.toString() === uriString); + + if (indexStatus && indexStatus.renameResourceUri) { + return indexStatus.renameResourceUri; + } + + return resource.resourceUri; + + case Status.BOTH_ADDED: + case Status.BOTH_MODIFIED: + return resource.resourceUri; + } + + throw new Error('Should never happen'); + } + + private getTitle(resource: Resource): string { + const basename = path.basename(resource.resourceUri.fsPath); + + switch (resource.type) { + case Status.INDEX_MODIFIED: + case Status.INDEX_RENAMED: + case Status.INDEX_ADDED: + return localize('git.title.index', '{0} (Index)', basename); + + case Status.MODIFIED: + case Status.BOTH_ADDED: + case Status.BOTH_MODIFIED: + return localize('git.title.workingTree', '{0} (Working Tree)', basename); + + case Status.INDEX_DELETED: + case Status.DELETED: + return localize('git.title.deleted', '{0} (Deleted)', basename); + + case Status.DELETED_BY_US: + return localize('git.title.theirs', '{0} (Theirs)', basename); + + case Status.DELETED_BY_THEM: + return localize('git.title.ours', '{0} (Ours)', basename); + + case Status.UNTRACKED: + return localize('git.title.untracked', '{0} (Untracked)', basename); + + default: + return ''; + } + } +} + export class Repository implements Disposable { private _onDidChangeRepository = new EventEmitter(); @@ -683,6 +843,7 @@ export class Repository implements Disposable { private isRepositoryHuge = false; private didWarnAboutLimit = false; + private resourceCommandResolver = new ResourceCommandResolver(this); private disposables: Disposable[] = []; constructor( @@ -753,6 +914,7 @@ export class Repository implements Disposable { e.affectsConfiguration('git.branchSortOrder', root) || e.affectsConfiguration('git.untrackedChanges', root) || e.affectsConfiguration('git.ignoreSubmodules', root) + || e.affectsConfiguration('git.openDiffOnClick', root) )(this.updateModelState, this, this.disposables); const updateInputBoxVisibility = () => { @@ -1637,36 +1799,36 @@ export class Repository implements Disposable { switch (raw.x + raw.y) { case '??': switch (untrackedChanges) { - case 'mixed': return workingTree.push(new Resource(ResourceGroupType.WorkingTree, uri, Status.UNTRACKED, useIcons)); - case 'separate': return untracked.push(new Resource(ResourceGroupType.Untracked, uri, Status.UNTRACKED, useIcons)); + case 'mixed': return workingTree.push(new Resource(this.resourceCommandResolver, ResourceGroupType.WorkingTree, uri, Status.UNTRACKED, useIcons)); + case 'separate': return untracked.push(new Resource(this.resourceCommandResolver, ResourceGroupType.Untracked, uri, Status.UNTRACKED, useIcons)); default: return undefined; } case '!!': switch (untrackedChanges) { - case 'mixed': return workingTree.push(new Resource(ResourceGroupType.WorkingTree, uri, Status.IGNORED, useIcons)); - case 'separate': return untracked.push(new Resource(ResourceGroupType.Untracked, uri, Status.IGNORED, useIcons)); + case 'mixed': return workingTree.push(new Resource(this.resourceCommandResolver, ResourceGroupType.WorkingTree, uri, Status.IGNORED, useIcons)); + case 'separate': return untracked.push(new Resource(this.resourceCommandResolver, ResourceGroupType.Untracked, uri, Status.IGNORED, useIcons)); default: return undefined; } - case 'DD': return merge.push(new Resource(ResourceGroupType.Merge, uri, Status.BOTH_DELETED, useIcons)); - case 'AU': return merge.push(new Resource(ResourceGroupType.Merge, uri, Status.ADDED_BY_US, useIcons)); - case 'UD': return merge.push(new Resource(ResourceGroupType.Merge, uri, Status.DELETED_BY_THEM, useIcons)); - case 'UA': return merge.push(new Resource(ResourceGroupType.Merge, uri, Status.ADDED_BY_THEM, useIcons)); - case 'DU': return merge.push(new Resource(ResourceGroupType.Merge, uri, Status.DELETED_BY_US, useIcons)); - case 'AA': return merge.push(new Resource(ResourceGroupType.Merge, uri, Status.BOTH_ADDED, useIcons)); - case 'UU': return merge.push(new Resource(ResourceGroupType.Merge, uri, Status.BOTH_MODIFIED, useIcons)); + case 'DD': return merge.push(new Resource(this.resourceCommandResolver, ResourceGroupType.Merge, uri, Status.BOTH_DELETED, useIcons)); + case 'AU': return merge.push(new Resource(this.resourceCommandResolver, ResourceGroupType.Merge, uri, Status.ADDED_BY_US, useIcons)); + case 'UD': return merge.push(new Resource(this.resourceCommandResolver, ResourceGroupType.Merge, uri, Status.DELETED_BY_THEM, useIcons)); + case 'UA': return merge.push(new Resource(this.resourceCommandResolver, ResourceGroupType.Merge, uri, Status.ADDED_BY_THEM, useIcons)); + case 'DU': return merge.push(new Resource(this.resourceCommandResolver, ResourceGroupType.Merge, uri, Status.DELETED_BY_US, useIcons)); + case 'AA': return merge.push(new Resource(this.resourceCommandResolver, ResourceGroupType.Merge, uri, Status.BOTH_ADDED, useIcons)); + case 'UU': return merge.push(new Resource(this.resourceCommandResolver, ResourceGroupType.Merge, uri, Status.BOTH_MODIFIED, useIcons)); } switch (raw.x) { - case 'M': index.push(new Resource(ResourceGroupType.Index, uri, Status.INDEX_MODIFIED, useIcons)); break; - case 'A': index.push(new Resource(ResourceGroupType.Index, uri, Status.INDEX_ADDED, useIcons)); break; - case 'D': index.push(new Resource(ResourceGroupType.Index, uri, Status.INDEX_DELETED, useIcons)); break; - case 'R': index.push(new Resource(ResourceGroupType.Index, uri, Status.INDEX_RENAMED, useIcons, renameUri)); break; - case 'C': index.push(new Resource(ResourceGroupType.Index, uri, Status.INDEX_COPIED, useIcons, renameUri)); break; + case 'M': index.push(new Resource(this.resourceCommandResolver, ResourceGroupType.Index, uri, Status.INDEX_MODIFIED, useIcons)); break; + case 'A': index.push(new Resource(this.resourceCommandResolver, ResourceGroupType.Index, uri, Status.INDEX_ADDED, useIcons)); break; + case 'D': index.push(new Resource(this.resourceCommandResolver, ResourceGroupType.Index, uri, Status.INDEX_DELETED, useIcons)); break; + case 'R': index.push(new Resource(this.resourceCommandResolver, ResourceGroupType.Index, uri, Status.INDEX_RENAMED, useIcons, renameUri)); break; + case 'C': index.push(new Resource(this.resourceCommandResolver, ResourceGroupType.Index, uri, Status.INDEX_COPIED, useIcons, renameUri)); break; } switch (raw.y) { - case 'M': workingTree.push(new Resource(ResourceGroupType.WorkingTree, uri, Status.MODIFIED, useIcons, renameUri)); break; - case 'D': workingTree.push(new Resource(ResourceGroupType.WorkingTree, uri, Status.DELETED, useIcons, renameUri)); break; - case 'A': workingTree.push(new Resource(ResourceGroupType.WorkingTree, uri, Status.INTENT_TO_ADD, useIcons, renameUri)); break; + case 'M': workingTree.push(new Resource(this.resourceCommandResolver, ResourceGroupType.WorkingTree, uri, Status.MODIFIED, useIcons, renameUri)); break; + case 'D': workingTree.push(new Resource(this.resourceCommandResolver, ResourceGroupType.WorkingTree, uri, Status.DELETED, useIcons, renameUri)); break; + case 'A': workingTree.push(new Resource(this.resourceCommandResolver, ResourceGroupType.WorkingTree, uri, Status.INTENT_TO_ADD, useIcons, renameUri)); break; } return undefined; diff --git a/src/vs/workbench/api/browser/mainThreadSCM.ts b/src/vs/workbench/api/browser/mainThreadSCM.ts index 32dbe8049de..8f1e25ebb39 100644 --- a/src/vs/workbench/api/browser/mainThreadSCM.ts +++ b/src/vs/workbench/api/browser/mainThreadSCM.ts @@ -68,7 +68,8 @@ class MainThreadSCMResource implements ISCMResource { readonly sourceUri: URI, readonly resourceGroup: ISCMResourceGroup, readonly decorations: ISCMResourceDecorations, - readonly contextValue: string | undefined + readonly contextValue: string | undefined, + readonly command: Command | undefined ) { } open(preserveFocus: boolean): Promise { @@ -201,7 +202,7 @@ class MainThreadSCMProvider implements ISCMProvider { for (const [start, deleteCount, rawResources] of groupSlices) { const resources = rawResources.map(rawResource => { - const [handle, sourceUri, icons, tooltip, strikeThrough, faded, contextValue] = rawResource; + const [handle, sourceUri, icons, tooltip, strikeThrough, faded, contextValue, command] = rawResource; const icon = icons[0]; const iconDark = icons[1] || icon; const decorations = { @@ -220,7 +221,8 @@ class MainThreadSCMProvider implements ISCMProvider { URI.revive(sourceUri), group, decorations, - contextValue || undefined + contextValue || undefined, + command ); }); diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 723a3b13bb9..0a8679fd6de 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -865,7 +865,8 @@ export type SCMRawResource = [ string /*tooltip*/, boolean /*strike through*/, boolean /*faded*/, - string /*context value*/ + string /*context value*/, + ICommandDto | undefined /*command*/ ]; export type SCMRawResourceSplice = [ diff --git a/src/vs/workbench/api/common/extHostSCM.ts b/src/vs/workbench/api/common/extHostSCM.ts index 83739e744dd..2d39371d18f 100644 --- a/src/vs/workbench/api/common/extHostSCM.ts +++ b/src/vs/workbench/api/common/extHostSCM.ts @@ -90,6 +90,49 @@ function compareResourceStatesDecorations(a: vscode.SourceControlResourceDecorat return result; } +function compareCommands(a: vscode.Command, b: vscode.Command): number { + if (a.command !== b.command) { + return a.command < b.command ? -1 : 1; + } + + if (a.title !== b.title) { + return a.title < b.title ? -1 : 1; + } + + if (a.tooltip !== b.tooltip) { + if (a.tooltip !== undefined && b.tooltip !== undefined) { + return a.tooltip < b.tooltip ? -1 : 1; + } else if (a.tooltip !== undefined) { + return 1; + } else if (b.tooltip !== undefined) { + return -1; + } + } + + if (a.arguments === b.arguments) { + return 0; + } else if (!a.arguments) { + return -1; + } else if (!b.arguments) { + return 1; + } else if (a.arguments.length !== b.arguments.length) { + return a.arguments.length - b.arguments.length; + } + + for (let i = 0; i < a.arguments.length; i++) { + const aArg = a.arguments[i]; + const bArg = b.arguments[i]; + + if (aArg === bArg) { + continue; + } + + return aArg < bArg ? -1 : 1; + } + + return 0; +} + function compareResourceStates(a: vscode.SourceControlResourceState, b: vscode.SourceControlResourceState): number { let result = comparePaths(a.resourceUri.fsPath, b.resourceUri.fsPath, true); @@ -97,6 +140,18 @@ function compareResourceStates(a: vscode.SourceControlResourceState, b: vscode.S return result; } + if (a.command && b.command) { + result = compareCommands(a.command, b.command); + } else if (a.command) { + return 1; + } else if (b.command) { + return -1; + } + + if (result !== 0) { + return result; + } + if (a.decorations && b.decorations) { result = compareResourceStatesDecorations(a.decorations, b.decorations); } else if (a.decorations) { @@ -223,8 +278,9 @@ class ExtHostSourceControlResourceGroup implements vscode.SourceControlResourceG private _resourceHandlePool: number = 0; private _resourceStates: vscode.SourceControlResourceState[] = []; - private _resourceStatesMap: Map = new Map(); - private _resourceStatesCommandsMap: Map = new Map(); + private _resourceStatesMap = new Map(); + private _resourceStatesCommandsMap = new Map(); + private _resourceStatesDisposablesMap = new Map(); private readonly _onDidUpdateResourceStates = new Emitter(); readonly onDidUpdateResourceStates = this._onDidUpdateResourceStates.event; @@ -302,9 +358,16 @@ class ExtHostSourceControlResourceGroup implements vscode.SourceControlResourceG const lightIconUri = r.decorations && getIconResource(r.decorations.light) || iconUri; const darkIconUri = r.decorations && getIconResource(r.decorations.dark) || iconUri; const icons: UriComponents[] = []; + let command: ICommandDto | undefined; if (r.command) { - this._resourceStatesCommandsMap.set(handle, r.command); + if (r.command.command === 'vscode.open' || r.command.command === 'vscode.diff') { + const disposables = new DisposableStore(); + command = this._commands.converter.toInternal(r.command, disposables); + this._resourceStatesDisposablesMap.set(handle, disposables); + } else { + this._resourceStatesCommandsMap.set(handle, r.command); + } } if (lightIconUri) { @@ -320,7 +383,7 @@ class ExtHostSourceControlResourceGroup implements vscode.SourceControlResourceG const faded = r.decorations && !!r.decorations.faded; const contextValue = r.contextValue || ''; - const rawResource = [handle, sourceUri, icons, tooltip, strikeThrough, faded, contextValue] as SCMRawResource; + const rawResource = [handle, sourceUri, icons, tooltip, strikeThrough, faded, contextValue, command] as SCMRawResource; return { rawResource, handle }; }); @@ -340,6 +403,8 @@ class ExtHostSourceControlResourceGroup implements vscode.SourceControlResourceG for (const handle of handlesToDelete) { this._resourceStatesMap.delete(handle); this._resourceStatesCommandsMap.delete(handle); + this._resourceStatesDisposablesMap.get(handle)?.dispose(); + this._resourceStatesDisposablesMap.delete(handle); } } diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index 574657783c8..3adbad1a5e4 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -78,6 +78,7 @@ import { ColorScheme } from 'vs/platform/theme/common/theme'; import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity'; import { LabelFuzzyScore } from 'vs/base/browser/ui/tree/abstractTree'; import { Selection } from 'vs/editor/common/core/selection'; +import { API_OPEN_DIFF_EDITOR_COMMAND_ID, API_OPEN_EDITOR_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands'; type TreeElement = ISCMRepository | ISCMInput | ISCMResourceGroup | IResourceNode | ISCMResource; @@ -1936,13 +1937,17 @@ export class SCMViewPane extends ViewPane { } // ISCMResource - await e.element.open(!!e.editorOptions.preserveFocus); + if (e.element.command?.id === API_OPEN_EDITOR_COMMAND_ID || e.element.command?.id === API_OPEN_DIFF_EDITOR_COMMAND_ID) { + await this.commandService.executeCommand(e.element.command.id, ...(e.element.command.arguments || []), e); + } else { + await e.element.open(!!e.editorOptions.preserveFocus); - if (e.editorOptions.pinned) { - const activeEditorPane = this.editorService.activeEditorPane; + if (e.editorOptions.pinned) { + const activeEditorPane = this.editorService.activeEditorPane; - if (activeEditorPane) { - activeEditorPane.group.pinEditor(activeEditorPane.input); + if (activeEditorPane) { + activeEditorPane.group.pinEditor(activeEditorPane.input); + } } } diff --git a/src/vs/workbench/contrib/scm/common/scm.ts b/src/vs/workbench/contrib/scm/common/scm.ts index 7ce0990afb9..407c508439f 100644 --- a/src/vs/workbench/contrib/scm/common/scm.ts +++ b/src/vs/workbench/contrib/scm/common/scm.ts @@ -34,7 +34,8 @@ export interface ISCMResource { readonly resourceGroup: ISCMResourceGroup; readonly sourceUri: URI; readonly decorations: ISCMResourceDecorations; - readonly contextValue?: string; + readonly contextValue: string | undefined; + readonly command: Command | undefined; open(preserveFocus: boolean): Promise; }