diff --git a/extensions/git/package.json b/extensions/git/package.json index 800563a808294..3efa9e8a1d3ca 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -15,6 +15,7 @@ "scmActionButton", "scmSelectedProvider", "scmValidation", + "tabs", "timeline" ], "categories": [ @@ -2113,6 +2114,12 @@ "default": true, "description": "%config.confirmNoVerifyCommit%" }, + "git.closeDiffOnOperation": { + "type": "boolean", + "scope": "resource", + "default": false, + "description": "%config.closeDiffOnOperation%" + }, "git.openDiffOnClick": { "type": "boolean", "scope": "resource", diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json index 9fd574a721dc1..c3a9840b3561e 100644 --- a/extensions/git/package.nls.json +++ b/extensions/git/package.nls.json @@ -175,6 +175,7 @@ "config.confirmForcePush": "Controls whether to ask for confirmation before force-pushing.", "config.allowNoVerifyCommit": "Controls whether commits without running pre-commit and commit-msg hooks are allowed.", "config.confirmNoVerifyCommit": "Controls whether to ask for confirmation before committing without verification.", + "config.closeDiffOnOperation": "Controls whether the diff editor should be automatically closed when changes are committed, discarded, staged, or unstaged.", "config.openDiffOnClick": "Controls whether the diff editor should be opened when clicking a change. Otherwise the regular editor will be opened.", "config.supportCancellation": "Controls whether a notification comes up when running the Sync action, which allows the user to cancel the operation.", "config.branchSortOrder": "Controls the sort order for branches.", diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index d409a337ffabf..97b7847c56e93 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -5,7 +5,7 @@ import * as fs from 'fs'; import * as path from 'path'; -import { CancellationToken, Command, Disposable, Event, EventEmitter, Memento, OutputChannel, ProgressLocation, ProgressOptions, scm, SourceControl, SourceControlInputBox, SourceControlInputBoxValidation, SourceControlInputBoxValidationType, SourceControlResourceDecorations, SourceControlResourceGroup, SourceControlResourceState, ThemeColor, Uri, window, workspace, WorkspaceEdit, FileDecoration, commands } from 'vscode'; +import { CancellationToken, Command, Disposable, Event, EventEmitter, Memento, OutputChannel, ProgressLocation, ProgressOptions, scm, SourceControl, SourceControlInputBox, SourceControlInputBoxValidation, SourceControlInputBoxValidationType, SourceControlResourceDecorations, SourceControlResourceGroup, SourceControlResourceState, ThemeColor, Uri, window, workspace, WorkspaceEdit, FileDecoration, commands, Tab } from 'vscode'; import TelemetryReporter from '@vscode/extension-telemetry'; import * as nls from 'vscode-nls'; import { Branch, Change, ForcePushMode, GitErrorCodes, LogOptions, Ref, RefType, Remote, Status, CommitOptions, BranchQuery, FetchOptions } from './api/git'; @@ -14,7 +14,7 @@ import { debounce, memoize, throttle } from './decorators'; import { Commit, GitError, Repository as BaseRepository, Stash, Submodule, LogFileOptions } from './git'; import { StatusBarCommands } from './statusbar'; import { toGitUri } from './uri'; -import { anyEvent, combinedDisposable, debounceEvent, dispose, EmptyDisposable, eventToPromise, filterEvent, find, IDisposable, isDescendant, logTimestamp, onceEvent, relativePath } from './util'; +import { anyEvent, combinedDisposable, debounceEvent, dispose, EmptyDisposable, eventToPromise, filterEvent, find, IDisposable, isDescendant, logTimestamp, onceEvent, pathEquals, relativePath } from './util'; import { IFileWatcher, watch } from './watch'; import { Log, LogLevel } from './log'; import { IPushErrorHandlerRegistry } from './pushError'; @@ -1159,7 +1159,10 @@ export class Repository implements Disposable { } async add(resources: Uri[], opts?: { update?: boolean }): Promise { - await this.run(Operation.Add, () => this.repository.add(resources.map(r => r.fsPath), opts)); + await this.run(Operation.Add, async () => { + await this.repository.add(resources.map(r => r.fsPath), opts); + this.closeDiffEditors([], [...resources.map(r => r.fsPath)]); + }); } async rm(resources: Uri[]): Promise { @@ -1168,15 +1171,27 @@ export class Repository implements Disposable { async stage(resource: Uri, contents: string): Promise { const path = relativePath(this.repository.root, resource.fsPath).replace(/\\/g, '/'); - await this.run(Operation.Stage, () => this.repository.stage(path, contents)); + await this.run(Operation.Stage, async () => { + await this.repository.stage(path, contents); + this.closeDiffEditors([], [...resource.fsPath]); + }); this._onDidChangeOriginalResource.fire(resource); } async revert(resources: Uri[]): Promise { - await this.run(Operation.RevertFiles, () => this.repository.revert('HEAD', resources.map(r => r.fsPath))); + await this.run(Operation.RevertFiles, async () => { + await this.repository.revert('HEAD', resources.map(r => r.fsPath)); + this.closeDiffEditors([...resources.length !== 0 ? + resources.map(r => r.fsPath) : + this.indexGroup.resourceStates.map(r => r.resourceUri.fsPath)], []); + }); } async commit(message: string | undefined, opts: CommitOptions = Object.create(null)): Promise { + const indexResources = [...this.indexGroup.resourceStates.map(r => r.resourceUri.fsPath)]; + const workingGroupResources = opts.all && opts.all !== 'tracked' ? + [...this.workingTreeGroup.resourceStates.map(r => r.resourceUri.fsPath)] : []; + if (this.rebaseCommit) { await this.run(Operation.RebaseContinue, async () => { if (opts.all) { @@ -1185,6 +1200,7 @@ export class Repository implements Disposable { } await this.repository.rebaseContinue(); + this.closeDiffEditors(indexResources, workingGroupResources); }); } else { await this.run(Operation.Commit, async () => { @@ -1201,6 +1217,7 @@ export class Repository implements Disposable { } await this.repository.commit(message, opts); + this.closeDiffEditors(indexResources, workingGroupResources); }); } } @@ -1244,9 +1261,34 @@ export class Repository implements Disposable { await this.repository.clean(toClean); await this.repository.checkout('', toCheckout); await this.repository.updateSubmodules(submodulesToUpdate); + + this.closeDiffEditors([], [...toClean, ...toCheckout]); }); } + private closeDiffEditors(indexResources: string[], workingTreeResources: string[]): void { + const config = workspace.getConfiguration('git', Uri.file(this.root)); + if (!config.get('closeDiffOnOperation', false)) { return; } + + const diffEditorTabsToClose: Tab[] = []; + + // Index + diffEditorTabsToClose.push(...window.tabs + .filter(t => + t.resource && t.resource.scheme === 'git' && t.viewId === 'diff' && + indexResources.some(r => pathEquals(r, t.resource!.fsPath)))); + + // Working Tree + diffEditorTabsToClose.push(...window.tabs + .filter(t => + t.resource && t.resource.scheme === 'file' && t.viewId === 'diff' && + workingTreeResources.some(r => pathEquals(r, t.resource!.fsPath)) && + t.additionalResourcesAndViewIds.find(r => r.resource!.scheme === 'git'))); + + // Close editors + diffEditorTabsToClose.forEach(t => t.close()); + } + async branch(name: string, _checkout: boolean, _ref?: string): Promise { await this.run(Operation.Branch, () => this.repository.branch(name, _checkout, _ref)); } diff --git a/extensions/git/tsconfig.json b/extensions/git/tsconfig.json index 99129079e48ca..13997275056b0 100644 --- a/extensions/git/tsconfig.json +++ b/extensions/git/tsconfig.json @@ -14,6 +14,7 @@ "../../src/vscode-dts/vscode.proposed.scmActionButton.d.ts", "../../src/vscode-dts/vscode.proposed.scmSelectedProvider.d.ts", "../../src/vscode-dts/vscode.proposed.scmValidation.d.ts", + "../../src/vscode-dts/vscode.proposed.tabs.d.ts", "../../src/vscode-dts/vscode.proposed.timeline.d.ts", "../types/lib.textEncoder.d.ts" ]