From eaea44872cdb26296a421561632da148d2c9cb42 Mon Sep 17 00:00:00 2001 From: Eric Amodio Date: Thu, 9 Mar 2017 02:18:20 -0500 Subject: [PATCH] Refactors commit quick pick commands Splits showQuickCommitDetails into showQuickCommitDetails and showQuickCommitFileDetails Adds closeUnchangedFiles command Adds openChangedFiles command Adds diffDirectory command Adds contextual description to the `go back` commands Fixes #44 by adding a warning message about Git version requirements Fixes intermittent errors when adding active line annotations Fixes intermittent errors when opening multiple files via quick picks Updates dependencies Preps v2.11.0 --- CHANGELOG.md | 25 +++- README.md | 6 +- package.json | 61 +++++++-- src/activeEditorTracker.ts | 59 ++++++++ src/blameActiveLineController.ts | 11 +- src/commands.ts | 6 +- src/commands/closeUnchangedFiles.ts | 70 ++++++++++ src/commands/commands.ts | 21 ++- src/commands/diffDirectory.ts | 35 +++++ src/commands/openChangedFiles.ts | 41 ++++++ src/commands/showQuickCommitDetails.ts | 69 +++------- src/commands/showQuickCommitFileDetails.ts | 89 +++++++++++++ src/commands/showQuickFileHistory.ts | 29 ++-- src/commands/showQuickRepoHistory.ts | 42 ++---- src/commands/showQuickRepoStatus.ts | 16 +-- src/comparers.ts | 10 +- src/configuration.ts | 6 +- src/extension.ts | 18 ++- src/git/enrichers/logParserEnricher.ts | 6 +- src/git/git.ts | 81 ++++++----- src/git/gitEnrichment.ts | 5 +- src/gitCodeLensProvider.ts | 15 ++- src/gitProvider.ts | 34 +++-- src/quickPicks.ts | 3 +- src/quickPicks/commitDetails.ts | 148 ++++----------------- src/quickPicks/commitFileDetails.ts | 111 ++++++++++++++++ src/quickPicks/fileHistory.ts | 22 +-- src/quickPicks/quickPicks.ts | 19 +-- src/quickPicks/repoHistory.ts | 4 +- src/quickPicks/repoStatus.ts | 57 +++++--- 30 files changed, 765 insertions(+), 354 deletions(-) create mode 100644 src/activeEditorTracker.ts create mode 100644 src/commands/closeUnchangedFiles.ts create mode 100644 src/commands/diffDirectory.ts create mode 100644 src/commands/openChangedFiles.ts create mode 100644 src/commands/showQuickCommitFileDetails.ts create mode 100644 src/quickPicks/commitFileDetails.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index bd2f011..c18d760 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,28 @@ ## Release Notes +### 2.11.0 +- Adds `gitlens.showQuickCommitFileDetails` command to show a quick pick list of details for a file commit +- Adds `gitlens.showQuickCommitFileDetails` command to CodeLens +- Adds `gitlens.showQuickCommitFileDetails` command to the status bar +- Adds `gitlens.closeUnchangedFiles` command to close any editors that don't have uncommitted changes +- Adds `gitlens.openChangedFiles` command to open all files that have uncommitted changes +- Adds `gitlens.diffDirectory` command to open the configured git difftool to compare directory versions +- Adds `Directory Compare with Previous Commit` command on the `gitlens.showQuickCommitDetails` quick pick +- Adds `Directory Compare with Working Tree` command on the `gitlens.showQuickCommitDetails` quick pick +- Adds a `Changed Files` grouping on the `gitlens.showQuickCommitDetails` quick pick +- Adds a `Close Unchanged Files` command on the `gitlens.showQuickRepoStatus` quick pick +- Adds a contextual description to the `go back` command in quick pick lists +- Changes layout of the `gitlens.showQuickRepoStatus` quick pick for better clarity +- Changes behavior of `gitlens.showQuickCommitDetails` to show commit a quick pick list of details for a commit +- Changes default of `gitlens.codeLens.recentChange.command` to be `gitlens.showQuickCommitFileDetails` (though there is no visible behavior change) +- Renames `Open Files` to `Open Changed Files` on the `gitlens.showQuickCommitDetails` quick pick +- Renames `Open Working Files` to `Open Changed Working Files` on the `gitlens.showQuickCommitDetails` quick pick +- Renames `Show Changed Files` to `Show Commit Details` on the `gitlens.showQuickCommitFileDetails` quick pick +- Renames `Open Files` to `Open Changed Files` on the `gitlens.showQuickRepoStatus` quick pick +- Fixes [#44](https://github.com/eamodio/vscode-gitlens/issues/43) by adding a warning message about Git version requirements +- Fixes intermittent errors when adding active line annotations +- Fixes intermittent errors when opening multiple files via quick picks + ### 2.10.1 - Fixes [#43](https://github.com/eamodio/vscode-gitlens/issues/43) - File-level CodeLens isn't using the blame of the whole file as it should - Fixes issue with single quotes (') in annotations @@ -108,7 +131,7 @@ - Adds `gitlens.showQuickRepoHistory` and `gitlens.showQuickCommitDetails` commands to the status bar - Changes the default command of `gitlens.codeLens.recentChange.command` to `gitlens.showQuickCommitDetails` - Changes the default command of `gitlens.statusBar.command` to `gitlens.showQuickCommitDetails` -- Changes Changes behavior of `gitlens.showQuickCommitDetails` to show commit commands rather than file set (use `Show Changed Files` command to get to the file set) +- Changes behavior of `gitlens.showQuickCommitDetails` to show commit commands rather than file set (use `Show Changed Files` command to get to the file set) - Changes `gitlens.diffWithPrevious` command to behave as `gitlens.diffWithWorking` if the file has uncommitted changes - Renames `gitlens.diffWithPrevious` command from `Diff Commit with Previous` to `Compare with Previous Commit` - Renames `gitlens.diffLineWithPrevious` command from `Diff Commit (line) with Previous` to `Compare Line with Previous Commit` diff --git a/README.md b/README.md index 510fe64..2b9be0a 100644 --- a/README.md +++ b/README.md @@ -46,12 +46,12 @@ Provides Git CodeLens information (most recent commit, # of authors), on-demand |`gitlens.codeLens.locationCustomSymbols`|Specifies the set of document symbols to render active document CodeLens on. Must be a member of `SymbolKind` |`gitlens.codeLens.languageLocations`|Specifies where CodeLens will be rendered in the active document for the specified languages |`gitlens.codeLens.recentChange.enabled`|Specifies whether the recent change CodeLens is shown -|`gitlens.codeLens.recentChange.command`|"Specifies the command executed when the recent change CodeLens is clicked. `gitlens.toggleBlame` - toggles blame annotations. `gitlens.showBlameHistory` - opens the blame history explorer. `gitlens.showFileHistory` - opens the file history explorer. `gitlens.diffWithPrevious` - compares the current checked-in file with the previous commit. `gitlens.showQuickCommitDetails` - shows a commit details quick pick. `gitlens.showQuickFileHistory` - shows a file history quick pick. `gitlens.showQuickFileHistory` - shows a repository history quick pick +|`gitlens.codeLens.recentChange.command`|"Specifies the command executed when the recent change CodeLens is clicked. `gitlens.toggleBlame` - toggles blame annotations. `gitlens.showBlameHistory` - opens the blame history explorer. `gitlens.showFileHistory` - opens the file history explorer. `gitlens.diffWithPrevious` - compares the current checked-in file with the previous commit. `gitlens.showQuickCommitDetails` - shows a commit details quick pick. `gitlens.showQuickCommitFileDetails` - shows a commit file details quick pick. `gitlens.showQuickFileHistory` - shows a file history quick pick. `gitlens.showQuickFileHistory` - shows a repository history quick pick |`gitlens.codeLens.authors.enabled`|Specifies whether the authors CodeLens is shown -|`gitlens.codeLens.authors.command`|Specifies the command executed when the authors CodeLens is clicked. `gitlens.toggleBlame` - toggles blame annotations. `gitlens.showBlameHistory` - opens the blame history explorer. `gitlens.showFileHistory` - opens the file history explorer. `gitlens.diffWithPrevious` - compares the current checked-in file with the previous commit. `gitlens.showQuickCommitDetails` - shows a commit details quick pick. `gitlens.showQuickFileHistory` - shows a file history quick pick. `gitlens.showQuickFileHistory` - shows a repository history quick pick +|`gitlens.codeLens.authors.command`|Specifies the command executed when the authors CodeLens is clicked. `gitlens.toggleBlame` - toggles blame annotations. `gitlens.showBlameHistory` - opens the blame history explorer. `gitlens.showFileHistory` - opens the file history explorer. `gitlens.diffWithPrevious` - compares the current checked-in file with the previous commit. `gitlens.showQuickCommitDetails` - shows a commit details quick pick. `gitlens.showQuickCommitFileDetails` - shows a commit file details quick pick. `gitlens.showQuickFileHistory` - shows a file history quick pick. `gitlens.showQuickFileHistory` - shows a repository history quick pick |`gitlens.menus.diff.enabled`|Specifies whether diff commands will be added to the context menus |`gitlens.statusBar.enabled`|Specifies whether blame information is shown in the status bar -|`gitlens.statusBar.command`|"Specifies the command executed when the blame status bar item is clicked. `gitlens.toggleBlame` - toggles blame annotations. `gitlens.showBlameHistory` - opens the blame history explorer. `gitlens.showFileHistory` - opens the file history explorer. `gitlens.diffWithPrevious` - compares the current checked-in file with the previous commit. `gitlens.showQuickCommitDetails` - shows a commit details quick pick. `gitlens.showQuickFileHistory` - shows a file history quick pick. `gitlens.showQuickFileHistory` - shows a repository history quick pick +|`gitlens.statusBar.command`|"Specifies the command executed when the blame status bar item is clicked. `gitlens.toggleBlame` - toggles blame annotations. `gitlens.showBlameHistory` - opens the blame history explorer. `gitlens.showFileHistory` - opens the file history explorer. `gitlens.diffWithPrevious` - compares the current checked-in file with the previous commit. `gitlens.showQuickCommitDetails` - shows a commit details quick pick. `gitlens.showQuickCommitFileDetails` - shows a commit file details quick pick. `gitlens.showQuickFileHistory` - shows a file history quick pick. `gitlens.showQuickFileHistory` - shows a repository history quick pick |`gitlens.statusBar.date`|Specifies whether and how the commit date will be shown in the blame status bar. `off` - no date. `relative` - relative date (e.g. 1 day ago). `absolute` - date format specified by `gitlens.statusBar.dateFormat` |`gitlens.statusBar.dateFormat`|Specifies the date format of how absolute dates will be shown in the blame status bar. See https://momentjs.com/docs/#/displaying/format/ for valid formats diff --git a/package.json b/package.json index 1c5aa0c..a98ee88 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gitlens", - "version": "2.10.1", + "version": "2.11.0", "author": { "name": "Eric Amodio", "email": "eamodio@gmail.com" @@ -214,17 +214,18 @@ }, "gitlens.codeLens.recentChange.command": { "type": "string", - "default": "gitlens.showQuickCommitDetails", + "default": "gitlens.showQuickCommitFileDetails", "enum": [ "gitlens.toggleBlame", "gitlens.showBlameHistory", "gitlens.showFileHistory", "gitlens.diffWithPrevious", "gitlens.showQuickCommitDetails", + "gitlens.showQuickCommitFileDetails", "gitlens.showQuickFileHistory", "gitlens.showQuickRepoHistory" ], - "description": "Specifies the command executed when the recent change CodeLens is clicked. `gitlens.toggleBlame` - toggles blame annotations. `gitlens.showBlameHistory` - opens the blame history explorer. `gitlens.showFileHistory` - opens the file history explorer. `gitlens.diffWithPrevious` - compares the current checked-in file with the previous commit. `gitlens.showQuickCommitDetails` - shows a commit details quick pick. `gitlens.showQuickFileHistory` - shows a file history quick pick. `gitlens.showQuickFileHistory` - shows a repository history quick pick" + "description": "Specifies the command executed when the recent change CodeLens is clicked. `gitlens.toggleBlame` - toggles blame annotations. `gitlens.showBlameHistory` - opens the blame history explorer. `gitlens.showFileHistory` - opens the file history explorer. `gitlens.diffWithPrevious` - compares the current checked-in file with the previous commit. `gitlens.showQuickCommitDetails` - shows a commit details quick pick. `gitlens.showQuickCommitFileDetails` - shows a commit file details quick pick. `gitlens.showQuickFileHistory` - shows a file history quick pick. `gitlens.showQuickFileHistory` - shows a repository history quick pick" }, "gitlens.codeLens.authors.enabled": { "type": "boolean", @@ -240,10 +241,11 @@ "gitlens.showFileHistory", "gitlens.diffWithPrevious", "gitlens.showQuickCommitDetails", + "gitlens.showQuickCommitFileDetails", "gitlens.showQuickFileHistory", "gitlens.showQuickRepoHistory" ], - "description": "Specifies the command executed when the authors CodeLens is clicked. `gitlens.toggleBlame` - toggles blame annotations. `gitlens.showBlameHistory` - opens the blame history explorer. `gitlens.showFileHistory` - opens the file history explorer. `gitlens.diffWithPrevious` - compares the current checked-in file with the previous commit. `gitlens.showQuickCommitDetails` - shows a commit details quick pick. `gitlens.showQuickFileHistory` - shows a file history quick pick. `gitlens.showQuickFileHistory` - shows a repository history quick pick" + "description": "Specifies the command executed when the authors CodeLens is clicked. `gitlens.toggleBlame` - toggles blame annotations. `gitlens.showBlameHistory` - opens the blame history explorer. `gitlens.showFileHistory` - opens the file history explorer. `gitlens.diffWithPrevious` - compares the current checked-in file with the previous commit. `gitlens.showQuickCommitDetails` - shows a commit details quick pick. `gitlens.showQuickCommitFileDetails` - shows a commit file details quick pick. `gitlens.showQuickFileHistory` - shows a file history quick pick. `gitlens.showQuickFileHistory` - shows a repository history quick pick" }, "gitlens.menus.diff.enabled": { "type": "boolean", @@ -265,10 +267,11 @@ "gitlens.diffWithPrevious", "gitlens.toggleCodeLens", "gitlens.showQuickCommitDetails", + "gitlens.showQuickCommitFileDetails", "gitlens.showQuickFileHistory", "gitlens.showQuickRepoHistory" ], - "description": "Specifies the command executed when the blame status bar item is clicked. `gitlens.toggleBlame` - toggles blame annotations. `gitlens.showBlameHistory` - opens the blame history explorer. `gitlens.showFileHistory` - opens the file history explorer. `gitlens.diffWithPrevious` - compares the current checked-in file with the previous commit. `gitlens.showQuickCommitDetails` - shows a commit details quick pick. `gitlens.showQuickFileHistory` - shows a file history quick pick. `gitlens.showQuickFileHistory` - shows a repository history quick pick" + "description": "Specifies the command executed when the blame status bar item is clicked. `gitlens.toggleBlame` - toggles blame annotations. `gitlens.showBlameHistory` - opens the blame history explorer. `gitlens.showFileHistory` - opens the file history explorer. `gitlens.diffWithPrevious` - compares the current checked-in file with the previous commit. `gitlens.showQuickCommitDetails` - shows a commit details quick pick. `gitlens.showQuickCommitFileDetails` - shows a commit file details quick pick. `gitlens.showQuickFileHistory` - shows a file history quick pick. `gitlens.showQuickFileHistory` - shows a repository history quick pick" }, "gitlens.statusBar.date": { "type": "string", @@ -348,6 +351,11 @@ "title": "Right KeyPress", "category": "GitLens:KeyPress" }, + { + "command": "gitlens.diffDirectory", + "title": "Directory Compare", + "category": "GitLens" + }, { "command": "gitlens.diffWithPrevious", "title": "Compare with Previous Commit", @@ -402,6 +410,11 @@ "title": "Show Commit Details", "category": "GitLens" }, + { + "command": "gitlens.showQuickCommitFileDetails", + "title": "Show Line Commit Details", + "category": "GitLens" + }, { "command": "gitlens.showQuickFileHistory", "title": "Show File History", @@ -426,6 +439,16 @@ "command": "gitlens.copyMessageToClipboard", "title": "Copy Commit Message to Clipboard", "category": "GitLens" + }, + { + "command": "gitlens.closeUnchangedFiles", + "title": "Close Unchanged Files", + "category": "GitLens" + }, + { + "command": "gitlens.openChangedFiles", + "title": "Open Changed Files", + "category": "GitLens" } ], "menus": { @@ -438,6 +461,10 @@ "command": "gitlens.key.right", "when": "false" }, + { + "command": "gitlens.diffDirectory", + "when": "gitlens:enabled" + }, { "command": "gitlens.diffWithPrevious", "when": "gitlens:enabled" @@ -478,6 +505,10 @@ "command": "gitlens.showQuickCommitDetails", "when": "gitlens:enabled && gitlens:isBlameable" }, + { + "command": "gitlens.showQuickCommitFileDetails", + "when": "gitlens:enabled && gitlens:isBlameable" + }, { "command": "gitlens.showQuickFileHistory", "when": "gitlens:enabled" @@ -497,6 +528,14 @@ { "command": "gitlens.copyMessageToClipboard", "when": "gitlens:enabled && gitlens:isBlameable" + }, + { + "command": "gitlens.closeUnchangedFiles", + "when": "gitlens:enabled" + }, + { + "command": "gitlens.openChangedFiles", + "when": "gitlens:enabled" } ], "explorer/context": [ @@ -572,7 +611,7 @@ "group": "1_gitlens@2" }, { - "command": "gitlens.showQuickCommitDetails", + "command": "gitlens.showQuickCommitFileDetails", "when": "editorTextFocus && gitlens:enabled && gitlens:isBlameable", "group": "1_gitlens@3" }, @@ -651,7 +690,7 @@ "when": "gitlens:enabled" }, { - "command": "gitlens.showQuickCommitDetails", + "command": "gitlens.showQuickCommitFileDetails", "key": "alt+c", "mac": "alt+c", "when": "editorTextFocus && gitlens:enabled" @@ -701,17 +740,17 @@ "lodash.isequal": "^4.5.0", "lodash.once": "^4.1.1", "moment": "^2.17.1", - "spawn-rx": "^2.0.8", + "spawn-rx": "^2.0.10", "tmp": "^0.0.31" }, "devDependencies": { "@types/copy-paste": "^1.1.30", "@types/mocha": "^2.2.39", - "@types/node": "^7.0.5", + "@types/node": "^7.0.7", "@types/tmp": "^0.0.32", "mocha": "^3.2.0", - "tslint": "^4.4.2", + "tslint": "^4.5.1", "typescript": "^2.2.1", - "vscode": "^1.0.3" + "vscode": "^1.0.5" } } \ No newline at end of file diff --git a/src/activeEditorTracker.ts b/src/activeEditorTracker.ts new file mode 100644 index 0000000..13fcb25 --- /dev/null +++ b/src/activeEditorTracker.ts @@ -0,0 +1,59 @@ + +'use strict'; +import { commands, Disposable, TextEditor, window } from 'vscode'; + +export class ActiveEditorTracker extends Disposable { + + private _disposable: Disposable; + private _resolver: (value?: TextEditor | PromiseLike) => void; + + constructor() { + super(() => this.dispose()); + + this._disposable = window.onDidChangeActiveTextEditor(e => this._resolver && this._resolver(e)); + } + + dispose() { + this._disposable && this._disposable.dispose(); + } + + async awaitClose(timeout: number = 500): Promise { + this.close(); + return this.wait(timeout); + } + + async awaitNext(timeout: number = 500): Promise { + this.next(); + return this.wait(timeout); + } + + async close(): Promise<{}> { + return commands.executeCommand('workbench.action.closeActiveEditor'); + } + + async next(): Promise<{}> { + return commands.executeCommand('workbench.action.nextEditor'); + } + + async wait(timeout: number = 500): Promise { + const editor = await new Promise((resolve, reject) => { + let timer: any; + + this._resolver = (editor: TextEditor) => { + if (timer) { + clearTimeout(timer as any); + timer = 0; + resolve(editor); + } + }; + + timer = setTimeout(() => { + resolve(window.activeTextEditor); + timer = 0; + }, timeout) as any; + }); + this._resolver = undefined; + return editor; + } +} + diff --git a/src/blameActiveLineController.ts b/src/blameActiveLineController.ts index e673147..7af6e36 100644 --- a/src/blameActiveLineController.ts +++ b/src/blameActiveLineController.ts @@ -224,6 +224,9 @@ export class BlameActiveLineController extends Disposable { } async show(commit: GitCommit, blameLine: IGitCommitLine, editor: TextEditor) { + // I have no idea why I need this protection -- but it happens + if (!editor.document) return; + if (this._config.statusBar.enabled) { switch (this._config.statusBar.date) { case 'off': @@ -260,9 +263,12 @@ export class BlameActiveLineController extends Disposable { case StatusBarCommand.ToggleCodeLens: this._statusBarItem.tooltip = 'Toggle Git CodeLens'; break; - case StatusBarCommand.ShowQuickFileHistory: + case StatusBarCommand.ShowQuickCommitDetails: this._statusBarItem.tooltip = 'Show Commit Details'; break; + case StatusBarCommand.ShowQuickCommitFileDetails: + this._statusBarItem.tooltip = 'Show Line Commit Details'; + break; case StatusBarCommand.ShowQuickFileHistory: this._statusBarItem.tooltip = 'Show File History'; break; @@ -296,6 +302,9 @@ export class BlameActiveLineController extends Disposable { logCommit = log && log.commits.get(commit.sha); } + // I have no idea why I need this protection -- but it happens + if (!editor.document) return; + let hoverMessage: string | string[]; if (activeLine !== 'inline') { // If the messages match (or we couldn't find the log), then this is a possible duplicate annotation diff --git a/src/commands.ts b/src/commands.ts index fcfef05..090322b 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -1,17 +1,21 @@ 'use strict'; export { Keyboard } from './commands/keyboard'; -export { ActiveEditorCommand, Command, Commands, EditorCommand } from './commands/commands'; +export { ActiveEditorCommand, Command, Commands, EditorCommand, openEditor } from './commands/commands'; +export { CloseUnchangedFilesCommand } from './commands/closeUnchangedFiles'; export { CopyMessageToClipboardCommand } from './commands/copyMessageToClipboard'; export { CopyShaToClipboardCommand } from './commands/copyShaToClipboard'; +export { DiffDirectoryCommand } from './commands/diffDirectory'; export { DiffLineWithPreviousCommand } from './commands/diffLineWithPrevious'; export { DiffLineWithWorkingCommand } from './commands/diffLineWithWorking'; export { DiffWithPreviousCommand } from './commands/diffWithPrevious'; export { DiffWithWorkingCommand } from './commands/diffWithWorking'; +export { OpenChangedFilesCommand } from './commands/openChangedFiles'; export { ShowBlameCommand } from './commands/showBlame'; export { ShowBlameHistoryCommand } from './commands/showBlameHistory'; export { ShowFileHistoryCommand } from './commands/showFileHistory'; export { ShowQuickCommitDetailsCommand } from './commands/showQuickCommitDetails'; +export { ShowQuickCommitFileDetailsCommand } from './commands/showQuickCommitFileDetails'; export { ShowQuickFileHistoryCommand } from './commands/showQuickFileHistory'; export { ShowQuickRepoHistoryCommand } from './commands/showQuickRepoHistory'; export { ShowQuickRepoStatusCommand } from './commands/showQuickRepoStatus'; diff --git a/src/commands/closeUnchangedFiles.ts b/src/commands/closeUnchangedFiles.ts new file mode 100644 index 0000000..b8eb1a6 --- /dev/null +++ b/src/commands/closeUnchangedFiles.ts @@ -0,0 +1,70 @@ +'use strict'; +import { TextEditor, Uri, window } from 'vscode'; +import { ActiveEditorTracker } from '../activeEditorTracker'; +import { ActiveEditorCommand, Commands } from './commands'; +import { TextEditorComparer, UriComparer } from '../comparers'; +import { GitProvider } from '../gitProvider'; +import { Logger } from '../logger'; +import * as path from 'path'; + +export class CloseUnchangedFilesCommand extends ActiveEditorCommand { + + constructor(private git: GitProvider, public repoPath: string) { + super(Commands.CloseUnchangedFiles); + } + + async execute(editor: TextEditor, uri?: Uri, uris?: Uri[]) { + if (!(uri instanceof Uri)) { + uri = editor && editor.document && editor.document.uri; + } + + try { + if (!uris) { + const repoPath = await this.git.getRepoPathFromUri(uri, this.repoPath); + if (!repoPath) return window.showWarningMessage(`Unable to close unchanged files`); + + const statuses = await this.git.getStatusesForRepo(repoPath); + if (!statuses) return window.showWarningMessage(`Unable to close unchanged files`); + + uris = statuses.map(_ => Uri.file(path.resolve(repoPath, _.fileName))); + } + + const editorTracker = new ActiveEditorTracker(); + + let active = window.activeTextEditor; + let editor = active; + do { + if (editor) { + if ((editor.document && editor.document.isDirty) || + uris.some(_ => UriComparer.equals(_, editor.document && editor.document.uri))) { + // If we didn't start with a valid editor, set one once we find it + if (!active) { + active = editor; + } + editor = await editorTracker.awaitNext(500); + } + else { + if (active === editor) { + active = undefined; + } + editor = await editorTracker.awaitClose(500); + } + } + else { + if (active === editor) { + active = undefined; + } + editor = await editorTracker.awaitClose(500); + } + } while ((!active && !editor) || !TextEditorComparer.equals(active, editor, { useId: true, usePosition: true })); + + editorTracker.dispose(); + + return undefined; + } + catch (ex) { + Logger.error('[GitLens.CloseUnchangedFilesCommand]', ex); + return window.showErrorMessage(`Unable to close unchanged files. See output channel for more details`); + } + } +} \ No newline at end of file diff --git a/src/commands/commands.ts b/src/commands/commands.ts index 74fc603..722bc21 100644 --- a/src/commands/commands.ts +++ b/src/commands/commands.ts @@ -1,18 +1,23 @@ 'use strict'; -import { commands, Disposable, TextEditor, TextEditorEdit, window } from 'vscode'; +import { commands, Disposable, TextEditor, TextEditorEdit, Uri, window, workspace } from 'vscode'; +import { BuiltInCommands } from '../constants'; -export type Commands = 'gitlens.copyMessageToClipboard' | 'gitlens.copyShaToClipboard' | 'gitlens.diffWithPrevious' | 'gitlens.diffLineWithPrevious' | 'gitlens.diffWithWorking' | 'gitlens.diffLineWithWorking' | 'gitlens.showBlame' | 'gitlens.showBlameHistory' | 'gitlens.showFileHistory' | 'gitlens.showQuickCommitDetails' | 'gitlens.showQuickFileHistory' | 'gitlens.showQuickRepoHistory' | 'gitlens.showQuickRepoStatus' | 'gitlens.toggleBlame' | 'gitlens.toggleCodeLens'; +export type Commands = 'gitlens.closeUnchangedFiles' | 'gitlens.copyMessageToClipboard' | 'gitlens.copyShaToClipboard' | 'gitlens.diffDirectory' | 'gitlens.diffWithPrevious' | 'gitlens.diffLineWithPrevious' | 'gitlens.diffWithWorking' | 'gitlens.diffLineWithWorking' | 'gitlens.openChangedFiles' | 'gitlens.showBlame' | 'gitlens.showBlameHistory' | 'gitlens.showFileHistory' | 'gitlens.showQuickCommitDetails' | 'gitlens.showQuickCommitFileDetails' | 'gitlens.showQuickFileHistory' | 'gitlens.showQuickRepoHistory' | 'gitlens.showQuickRepoStatus' | 'gitlens.toggleBlame' | 'gitlens.toggleCodeLens'; export const Commands = { + CloseUnchangedFiles: 'gitlens.closeUnchangedFiles' as Commands, CopyMessageToClipboard: 'gitlens.copyMessageToClipboard' as Commands, CopyShaToClipboard: 'gitlens.copyShaToClipboard' as Commands, + DiffDirectory: 'gitlens.diffDirectory' as Commands, DiffWithPrevious: 'gitlens.diffWithPrevious' as Commands, DiffLineWithPrevious: 'gitlens.diffLineWithPrevious' as Commands, DiffWithWorking: 'gitlens.diffWithWorking' as Commands, DiffLineWithWorking: 'gitlens.diffLineWithWorking' as Commands, + OpenChangedFiles: 'gitlens.openChangedFiles' as Commands, ShowBlame: 'gitlens.showBlame' as Commands, ShowBlameHistory: 'gitlens.showBlameHistory' as Commands, ShowFileHistory: 'gitlens.showFileHistory' as Commands, ShowQuickCommitDetails: 'gitlens.showQuickCommitDetails' as Commands, + ShowQuickCommitFileDetails: 'gitlens.showQuickCommitFileDetails' as Commands, ShowQuickFileHistory: 'gitlens.showQuickFileHistory' as Commands, ShowQuickRepoHistory: 'gitlens.showQuickRepoHistory' as Commands, ShowQuickRepoStatus: 'gitlens.showQuickRepoStatus' as Commands, @@ -68,4 +73,16 @@ export abstract class ActiveEditorCommand extends Disposable { } abstract execute(editor: TextEditor, ...args: any[]): any; +} + +export async function openEditor(uri: Uri, pinned: boolean = false) { + try { + if (!pinned) return await commands.executeCommand(BuiltInCommands.Open, uri); + + const document = await workspace.openTextDocument(uri); + return window.showTextDocument(document, (window.activeTextEditor && window.activeTextEditor.viewColumn) || 1, true); + } + catch (ex) { + return undefined; + } } \ No newline at end of file diff --git a/src/commands/diffDirectory.ts b/src/commands/diffDirectory.ts new file mode 100644 index 0000000..0efc48b --- /dev/null +++ b/src/commands/diffDirectory.ts @@ -0,0 +1,35 @@ +'use strict'; +import { TextEditor, Uri, window } from 'vscode'; +import { ActiveEditorCommand, Commands } from './commands'; +import { GitProvider } from '../gitProvider'; +import { Logger } from '../logger'; + +export class DiffDirectoryCommand extends ActiveEditorCommand { + + constructor(private git: GitProvider, public repoPath: string) { + super(Commands.DiffDirectory); + } + + async execute(editor: TextEditor, uri?: Uri, shaOrBranch1?: string, shaOrBranch2?: string): Promise { + if (!(uri instanceof Uri)) { + uri = editor && editor.document && editor.document.uri; + } + + try { + const repoPath = await this.git.getRepoPathFromUri(uri, this.repoPath); + if (!repoPath) return window.showWarningMessage(`Unable to open directory diff`); + + if (!shaOrBranch1) { + //window.showQuickPick() + return undefined; + } + + this.git.openDirectoryDiff(repoPath, shaOrBranch1, shaOrBranch2); + return undefined; + } + catch (ex) { + Logger.error('GitLens.DiffDirectoryCommand', ex); + return window.showErrorMessage(`Unable to open directory diff. See output channel for more details`); + } + } +} \ No newline at end of file diff --git a/src/commands/openChangedFiles.ts b/src/commands/openChangedFiles.ts new file mode 100644 index 0000000..bbf2524 --- /dev/null +++ b/src/commands/openChangedFiles.ts @@ -0,0 +1,41 @@ +'use strict'; +import { TextEditor, Uri, window } from 'vscode'; +import { ActiveEditorCommand, Commands, openEditor } from './commands'; +import { GitProvider } from '../gitProvider'; +import { Logger } from '../logger'; +import * as path from 'path'; + +export class OpenChangedFilesCommand extends ActiveEditorCommand { + + constructor(private git: GitProvider, public repoPath: string) { + super(Commands.OpenChangedFiles); + } + + async execute(editor: TextEditor, uri?: Uri, uris?: Uri[]) { + if (!(uri instanceof Uri)) { + uri = editor && editor.document && editor.document.uri; + } + + try { + if (!uris) { + const repoPath = await this.git.getRepoPathFromUri(uri, this.repoPath); + if (!repoPath) return window.showWarningMessage(`Unable to open changed files`); + + const statuses = await this.git.getStatusesForRepo(repoPath); + if (!statuses) return window.showWarningMessage(`Unable to open changed files`); + + uris = statuses.filter(_ => _.status !== 'D').map(_ => Uri.file(path.resolve(repoPath, _.fileName))); + } + + for (const uri of uris) { + await openEditor(uri, true); + } + + return undefined; + } + catch (ex) { + Logger.error('[GitLens.OpenChangedFilesCommand]', ex); + return window.showErrorMessage(`Unable to open changed files. See output channel for more details`); + } + } +} \ No newline at end of file diff --git a/src/commands/showQuickCommitDetails.ts b/src/commands/showQuickCommitDetails.ts index 99c10b4..97c64dd 100644 --- a/src/commands/showQuickCommitDetails.ts +++ b/src/commands/showQuickCommitDetails.ts @@ -4,7 +4,7 @@ import { commands, TextEditor, Uri, window } from 'vscode'; import { ActiveEditorCommand, Commands } from './commands'; import { GitCommit, GitLogCommit, GitProvider, GitUri } from '../gitProvider'; import { Logger } from '../logger'; -import { CommandQuickPickItem, CommitFileDetailsQuickPick, CommitDetailsQuickPick, CommitWithFileStatusQuickPickItem } from '../quickPicks'; +import { CommandQuickPickItem, CommitDetailsQuickPick, CommitWithFileStatusQuickPickItem } from '../quickPicks'; export class ShowQuickCommitDetailsCommand extends ActiveEditorCommand { @@ -12,7 +12,7 @@ export class ShowQuickCommitDetailsCommand extends ActiveEditorCommand { super(Commands.ShowQuickCommitDetails); } - async execute(editor: TextEditor, uri?: Uri, sha?: string, commit?: GitCommit, goBackCommand?: CommandQuickPickItem, options: { showFileHistory?: boolean } = { showFileHistory: true }) { + async execute(editor: TextEditor, uri?: Uri, sha?: string, commit?: GitCommit | GitLogCommit, goBackCommand?: CommandQuickPickItem) { if (!(uri instanceof Uri)) { if (!editor || !editor.document) return undefined; uri = editor.document.uri; @@ -35,72 +35,43 @@ export class ShowQuickCommitDetailsCommand extends ActiveEditorCommand { sha = blame.commit.isUncommitted ? blame.commit.previousSha : blame.commit.sha; repoPath = blame.commit.repoPath; - return commands.executeCommand(Commands.ShowQuickFileHistory, uri, undefined, blame.commit); + commit = blame.commit; } catch (ex) { - Logger.error('[GitLens.ShowQuickCommitDetails]', `getBlameForLine(${blameline})`, ex); + Logger.error('[GitLens.ShowQuickCommitDetailsCommand]', `getBlameForLine(${blameline})`, ex); return window.showErrorMessage(`Unable to show commit details. See output channel for more details`); } } try { - let pick: CommitWithFileStatusQuickPickItem | CommandQuickPickItem; - let alreadyPickedCommit = !!commit; - let workingFileName: string; - if (!alreadyPickedCommit) { - let log = await this.git.getLogForRepo(repoPath, sha, 0); + if (!commit || !(commit instanceof GitLogCommit) || commit.type !== 'repo') { + let log = await this.git.getLogForRepo(repoPath, sha, 2); if (!log) return window.showWarningMessage(`Unable to show commit details`); commit = Iterables.first(log.commits.values()); - - pick = await CommitDetailsQuickPick.show(commit as GitLogCommit, uri, goBackCommand); - if (!pick) return undefined; - - if (!(pick instanceof CommitWithFileStatusQuickPickItem)) { - return pick.execute(); - } - - // Attempt to the most recent commit -- so that we can find the real working filename if there was a rename - const workingCommit = await this.git.findMostRecentCommitForFile(pick.uri.fsPath, pick.sha); - // TODO: Leave this at undefined until findMostRecentCommitForFile actually works - workingFileName = !workingCommit ? pick.fileName : undefined; - - log = await this.git.getLogForFile(pick.gitUri.fsPath, pick.sha, undefined, undefined, 2); - if (!log) return window.showWarningMessage(`Unable to show commit details`); - - commit = Iterables.find(log.commits.values(), c => c.sha === commit.sha); - uri = pick.gitUri || uri; - } - else { - // Attempt to the most recent commit -- so that we can find the real working filename if there was a rename - const workingCommit = await this.git.findMostRecentCommitForFile(commit.uri.fsPath, commit.sha); - // TODO: Leave this at undefined until findMostRecentCommitForFile actually works - workingFileName = !workingCommit ? commit.fileName : undefined; } - pick = await CommitFileDetailsQuickPick.show(this.git, commit, workingFileName, uri, - // Create a command to get back to where we are right now - new CommandQuickPickItem({ + if (!goBackCommand) { + // Create a command to get back to the commit details + goBackCommand = new CommandQuickPickItem({ label: `go back \u21A9`, - description: null - }, Commands.ShowQuickCommitDetails, [new GitUri(commit.uri, commit), sha, commit, goBackCommand, options]), - // If we have already picked a commit, just jump back to the previous (since we skipped a quickpick menu) - // Otherwise setup a normal back command - alreadyPickedCommit - ? goBackCommand - : new CommandQuickPickItem({ - label: `go back \u21A9`, - description: null - }, Commands.ShowQuickCommitDetails, [new GitUri(commit.uri, commit), sha, undefined, goBackCommand, options]), - { showFileHistory: options.showFileHistory }); + description: `\u00a0 \u2014 \u00a0\u00a0 to repository history` + }, Commands.ShowQuickRepoHistory, [new GitUri(commit.uri, commit)]); + } + const pick = await CommitDetailsQuickPick.show(commit as GitLogCommit, uri, goBackCommand); if (!pick) return undefined; - if (pick instanceof CommandQuickPickItem) { + if (!(pick instanceof CommitWithFileStatusQuickPickItem)) { return pick.execute(); } - return undefined; + return commands.executeCommand(Commands.ShowQuickCommitFileDetails, pick.gitUri, pick.sha, undefined, + // Create a command to get back to where we are right now + new CommandQuickPickItem({ + label: `go back \u21A9`, + description: `\u00a0 \u2014 \u00a0\u00a0 to details of \u00a0$(git-commit) ${pick.sha}` + }, Commands.ShowQuickCommitDetails, [new GitUri(commit.uri, commit), sha, commit, goBackCommand])); } catch (ex) { Logger.error('[GitLens.ShowQuickCommitDetailsCommand]', ex); diff --git a/src/commands/showQuickCommitFileDetails.ts b/src/commands/showQuickCommitFileDetails.ts new file mode 100644 index 0000000..282d96f --- /dev/null +++ b/src/commands/showQuickCommitFileDetails.ts @@ -0,0 +1,89 @@ +'use strict'; +import { Iterables } from '../system'; +import { TextEditor, Uri, window } from 'vscode'; +import { ActiveEditorCommand, Commands } from './commands'; +import { GitCommit, GitLogCommit, GitProvider, GitUri } from '../gitProvider'; +import { Logger } from '../logger'; +import { CommandQuickPickItem, CommitFileDetailsQuickPick } from '../quickPicks'; +import * as path from 'path'; + +export class ShowQuickCommitFileDetailsCommand extends ActiveEditorCommand { + + constructor(private git: GitProvider) { + super(Commands.ShowQuickCommitFileDetails); + } + + async execute(editor: TextEditor, uri?: Uri, sha?: string, commit?: GitCommit | GitLogCommit, goBackCommand?: CommandQuickPickItem, options: { showFileHistory?: boolean } = { showFileHistory: true }) { + if (!(uri instanceof Uri)) { + if (!editor || !editor.document) return undefined; + uri = editor.document.uri; + } + + const gitUri = await GitUri.fromUri(uri, this.git); + + let repoPath = gitUri.repoPath; + + if (!sha) { + if (!editor) return undefined; + + const blameline = editor.selection.active.line - gitUri.offset; + if (blameline < 0) return undefined; + + try { + const blame = await this.git.getBlameForLine(gitUri.fsPath, blameline, gitUri.sha, gitUri.repoPath); + if (!blame) return window.showWarningMessage(`Unable to show commit file details. File is probably not under source control`); + + sha = blame.commit.isUncommitted ? blame.commit.previousSha : blame.commit.sha; + repoPath = blame.commit.repoPath; + + commit = blame.commit; + } + catch (ex) { + Logger.error('[GitLens.ShowQuickCommitFileDetailsCommand]', `getBlameForLine(${blameline})`, ex); + return window.showErrorMessage(`Unable to show commit file details. See output channel for more details`); + } + } + + try { + if (!commit || ((commit instanceof GitLogCommit) && commit.type !== 'file')) { + let log = await this.git.getLogForFile(uri.fsPath, sha, undefined, undefined, 2); + if (!log) return window.showWarningMessage(`Unable to show commit file details`); + + commit = Iterables.find(log.commits.values(), c => c.sha === sha); + } + + // Attempt to the most recent commit -- so that we can find the real working filename if there was a rename + const workingCommit = await this.git.findMostRecentCommitForFile(commit.uri.fsPath, commit.sha); + // TODO: Leave this at undefined until findMostRecentCommitForFile actually works + const workingFileName = !workingCommit ? commit.fileName : undefined; + + if (!goBackCommand) { + // Create a command to get back to the commit details + goBackCommand = new CommandQuickPickItem({ + label: `go back \u21A9`, + description: `\u00a0 \u2014 \u00a0\u00a0 to details of \u00a0$(git-commit) ${sha}` + }, Commands.ShowQuickCommitDetails, [new GitUri(commit.uri, commit), sha, commit]); + } + + const pick = await CommitFileDetailsQuickPick.show(this.git, commit, workingFileName, uri, goBackCommand, + // Create a command to get back to where we are right now + new CommandQuickPickItem({ + label: `go back \u21A9`, + description: `\u00a0 \u2014 \u00a0\u00a0 to details of \u00a0$(file-text) ${path.basename(commit.fileName)} in \u00a0$(git-commit) ${sha}` + }, Commands.ShowQuickCommitFileDetails, [new GitUri(commit.uri, commit), sha, commit, goBackCommand, options]), + { showFileHistory: options.showFileHistory }); + + if (!pick) return undefined; + + if (pick instanceof CommandQuickPickItem) { + return pick.execute(); + } + + return undefined; + } + catch (ex) { + Logger.error('[GitLens.ShowQuickCommitFileDetailsCommand]', ex); + return window.showErrorMessage(`Unable to show commit file details. See output channel for more details`); + } + } +} \ No newline at end of file diff --git a/src/commands/showQuickFileHistory.ts b/src/commands/showQuickFileHistory.ts index 351f83a..fca4cfc 100644 --- a/src/commands/showQuickFileHistory.ts +++ b/src/commands/showQuickFileHistory.ts @@ -1,9 +1,10 @@ 'use strict'; import { commands, TextEditor, Uri, window } from 'vscode'; import { ActiveEditorCommand, Commands } from './commands'; -import { GitCommit, GitProvider, GitUri } from '../gitProvider'; +import { GitProvider, GitUri } from '../gitProvider'; import { Logger } from '../logger'; import { CommandQuickPickItem, FileHistoryQuickPick } from '../quickPicks'; +import * as path from 'path'; export class ShowQuickFileHistoryCommand extends ActiveEditorCommand { @@ -11,7 +12,7 @@ export class ShowQuickFileHistoryCommand extends ActiveEditorCommand { super(Commands.ShowQuickFileHistory); } - async execute(editor: TextEditor, uri?: Uri, maxCount?: number, commit?: GitCommit, goBackCommand?: CommandQuickPickItem) { + async execute(editor: TextEditor, uri?: Uri, maxCount?: number, goBackCommand?: CommandQuickPickItem) { if (!(uri instanceof Uri)) { uri = editor && editor.document && editor.document.uri; } @@ -27,27 +28,21 @@ export class ShowQuickFileHistoryCommand extends ActiveEditorCommand { } try { - if (!commit) { - const log = await this.git.getLogForFile(gitUri.fsPath, gitUri.sha, gitUri.repoPath, undefined, maxCount); - if (!log) return window.showWarningMessage(`Unable to show file history. File is probably not under source control`); + const log = await this.git.getLogForFile(gitUri.fsPath, gitUri.sha, gitUri.repoPath, undefined, maxCount); + if (!log) return window.showWarningMessage(`Unable to show file history. File is probably not under source control`); - let pick = await FileHistoryQuickPick.show(log, uri, maxCount, this.git.config.advanced.maxQuickHistory, goBackCommand); - if (!pick) return undefined; + let pick = await FileHistoryQuickPick.show(log, uri, gitUri.sha, maxCount, this.git.config.advanced.maxQuickHistory, goBackCommand); + if (!pick) return undefined; - if (pick instanceof CommandQuickPickItem) { - return pick.execute(); - } - - commit = pick.commit; + if (pick instanceof CommandQuickPickItem) { + return pick.execute(); } - return commands.executeCommand(Commands.ShowQuickCommitDetails, - new GitUri(commit.uri, commit), - commit.sha, commit, + return commands.executeCommand(Commands.ShowQuickCommitFileDetails, new GitUri(pick.commit.uri, pick.commit), pick.commit.sha, pick.commit, new CommandQuickPickItem({ label: `go back \u21A9`, - description: null - }, Commands.ShowQuickFileHistory, [uri, maxCount, undefined, goBackCommand]), + description: `\u00a0 \u2014 \u00a0\u00a0 to history of \u00a0$(file-text) ${path.basename(pick.commit.fileName)}` + }, Commands.ShowQuickFileHistory, [uri, maxCount, goBackCommand]), { showFileHistory: false }); } catch (ex) { diff --git a/src/commands/showQuickRepoHistory.ts b/src/commands/showQuickRepoHistory.ts index 5314b38..9652006 100644 --- a/src/commands/showQuickRepoHistory.ts +++ b/src/commands/showQuickRepoHistory.ts @@ -1,7 +1,7 @@ 'use strict'; import { commands, TextEditor, Uri, window } from 'vscode'; import { ActiveEditorCommand, Commands } from './commands'; -import { GitCommit, GitProvider, GitUri } from '../gitProvider'; +import { GitProvider, GitUri } from '../gitProvider'; import { Logger } from '../logger'; import { CommandQuickPickItem, RepoHistoryQuickPick } from '../quickPicks'; @@ -11,7 +11,7 @@ export class ShowQuickRepoHistoryCommand extends ActiveEditorCommand { super(Commands.ShowQuickRepoHistory); } - async execute(editor: TextEditor, uri?: Uri, maxCount?: number, commit?: GitCommit, goBackCommand?: CommandQuickPickItem) { + async execute(editor: TextEditor, uri?: Uri, maxCount?: number, goBackCommand?: CommandQuickPickItem) { if (!(uri instanceof Uri)) { uri = editor && editor.document && editor.document.uri; } @@ -21,42 +21,24 @@ export class ShowQuickRepoHistoryCommand extends ActiveEditorCommand { } try { - let repoPath: string; - if (uri instanceof Uri) { - const gitUri = await GitUri.fromUri(uri, this.git); - repoPath = gitUri.repoPath; - - if (!repoPath) { - repoPath = await this.git.getRepoPathFromFile(gitUri.fsPath); - } - } - - if (!repoPath) { - repoPath = this.repoPath; - } + const repoPath = await this.git.getRepoPathFromUri(uri, this.repoPath); if (!repoPath) return window.showWarningMessage(`Unable to show repository history`); - if (!commit) { - const log = await this.git.getLogForRepo(repoPath, undefined, maxCount); - if (!log) return window.showWarningMessage(`Unable to show repository history`); + const log = await this.git.getLogForRepo(repoPath, undefined, maxCount); + if (!log) return window.showWarningMessage(`Unable to show repository history`); - const pick = await RepoHistoryQuickPick.show(log, uri, maxCount, this.git.config.advanced.maxQuickHistory, goBackCommand); - if (!pick) return undefined; + const pick = await RepoHistoryQuickPick.show(log, uri, maxCount, this.git.config.advanced.maxQuickHistory, goBackCommand); + if (!pick) return undefined; - if (pick instanceof CommandQuickPickItem) { - return pick.execute(); - } - - commit = pick.commit; + if (pick instanceof CommandQuickPickItem) { + return pick.execute(); } - return commands.executeCommand(Commands.ShowQuickCommitDetails, - new GitUri(commit.uri, commit), - commit.sha, undefined, + return commands.executeCommand(Commands.ShowQuickCommitDetails, new GitUri(pick.commit.uri, pick.commit), pick.commit.sha, pick.commit, new CommandQuickPickItem({ label: `go back \u21A9`, - description: null - }, Commands.ShowQuickRepoHistory, [uri, maxCount, undefined, goBackCommand])); + description: `\u00a0 \u2014 \u00a0\u00a0 to repository history` + }, Commands.ShowQuickRepoHistory, [uri, maxCount, goBackCommand])); } catch (ex) { Logger.error('[GitLens.ShowQuickRepoHistoryCommand]', ex); diff --git a/src/commands/showQuickRepoStatus.ts b/src/commands/showQuickRepoStatus.ts index a96f23a..33f46b2 100644 --- a/src/commands/showQuickRepoStatus.ts +++ b/src/commands/showQuickRepoStatus.ts @@ -1,7 +1,7 @@ 'use strict'; import { TextEditor, Uri, window } from 'vscode'; import { ActiveEditorCommand, Commands } from './commands'; -import { GitProvider, GitUri } from '../gitProvider'; +import { GitProvider } from '../gitProvider'; import { Logger } from '../logger'; import { CommandQuickPickItem, RepoStatusQuickPick } from '../quickPicks'; @@ -17,19 +17,7 @@ export class ShowQuickRepoStatusCommand extends ActiveEditorCommand { } try { - let repoPath: string; - if (uri instanceof Uri) { - const gitUri = await GitUri.fromUri(uri, this.git); - repoPath = gitUri.repoPath; - - if (!repoPath) { - repoPath = await this.git.getRepoPathFromFile(gitUri.fsPath); - } - } - - if (!repoPath) { - repoPath = this.repoPath; - } + const repoPath = await this.git.getRepoPathFromUri(uri, this.repoPath); if (!repoPath) return window.showWarningMessage(`Unable to show repository status`); const statuses = await this.git.getStatusesForRepo(repoPath); diff --git a/src/comparers.ts b/src/comparers.ts index f1b67a0..7030b00 100644 --- a/src/comparers.ts +++ b/src/comparers.ts @@ -27,10 +27,18 @@ class TextDocumentComparer extends Comparer { class TextEditorComparer extends Comparer { - equals(lhs: TextEditor, rhs: TextEditor) { + equals(lhs: TextEditor, rhs: TextEditor, options: { useId: boolean, usePosition: boolean } = { useId: false, usePosition: false }) { if (!lhs && !rhs) return true; if ((lhs && !rhs) || (!lhs && rhs)) return false; + if (options.usePosition && (lhs.viewColumn !== rhs.viewColumn)) return false; + + if (options.useId && (!lhs.document || !rhs.document)) { + if ((lhs as any)._id !== (rhs as any)._id) return false; + + return true; + } + return textDocumentComparer.equals(lhs.document, rhs.document); } } diff --git a/src/configuration.ts b/src/configuration.ts index e5dfb56..e2ec616 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -28,13 +28,14 @@ export interface IBlameConfig { }; } -export type CodeLensCommand = 'gitlens.toggleBlame' | 'gitlens.showBlameHistory' | 'gitlens.showFileHistory' | 'gitlens.diffWithPrevious' | 'gitlens.showQuickCommitDetails' | 'gitlens.showQuickFileHistory' | 'gitlens.showQuickRepoHistory'; +export type CodeLensCommand = 'gitlens.toggleBlame' | 'gitlens.showBlameHistory' | 'gitlens.showFileHistory' | 'gitlens.diffWithPrevious' | 'gitlens.showQuickCommitDetails' | 'gitlens.showQuickCommitFileDetails' | 'gitlens.showQuickFileHistory' | 'gitlens.showQuickRepoHistory'; export const CodeLensCommand = { BlameAnnotate: Commands.ToggleBlame as CodeLensCommand, ShowBlameHistory: Commands.ShowBlameHistory as CodeLensCommand, ShowFileHistory: Commands.ShowFileHistory as CodeLensCommand, DiffWithPrevious: Commands.DiffWithPrevious as CodeLensCommand, ShowQuickCommitDetails: Commands.ShowQuickCommitDetails as CodeLensCommand, + ShowQuickCommitFileDetails: Commands.ShowQuickCommitFileDetails as CodeLensCommand, ShowQuickFileHistory: Commands.ShowQuickFileHistory as CodeLensCommand, ShowQuickRepoHistory: Commands.ShowQuickRepoHistory as CodeLensCommand }; @@ -75,7 +76,7 @@ export interface ICodeLensesConfig { authors: ICodeLensConfig; } -export type StatusBarCommand = 'gitlens.toggleBlame' | 'gitlens.showBlameHistory' | 'gitlens.showFileHistory' | 'gitlens.toggleCodeLens' | 'gitlens.diffWithPrevious' | 'gitlens.showQuickCommitDetails' | 'gitlens.showQuickFileHistory' | 'gitlens.showQuickRepoHistory'; +export type StatusBarCommand = 'gitlens.toggleBlame' | 'gitlens.showBlameHistory' | 'gitlens.showFileHistory' | 'gitlens.toggleCodeLens' | 'gitlens.diffWithPrevious' | 'gitlens.showQuickCommitDetails' | 'gitlens.showQuickCommitFileDetails' | 'gitlens.showQuickFileHistory' | 'gitlens.showQuickRepoHistory'; export const StatusBarCommand = { BlameAnnotate: Commands.ToggleBlame as StatusBarCommand, ShowBlameHistory: Commands.ShowBlameHistory as StatusBarCommand, @@ -83,6 +84,7 @@ export const StatusBarCommand = { DiffWithPrevious: Commands.DiffWithPrevious as StatusBarCommand, ToggleCodeLens: Commands.ToggleCodeLens as StatusBarCommand, ShowQuickCommitDetails: Commands.ShowQuickCommitDetails as StatusBarCommand, + ShowQuickCommitFileDetails: Commands.ShowQuickCommitFileDetails as StatusBarCommand, ShowQuickFileHistory: Commands.ShowQuickFileHistory as StatusBarCommand, ShowQuickRepoHistory: Commands.ShowQuickRepoHistory as StatusBarCommand }; diff --git a/src/extension.ts b/src/extension.ts index 062901b..d610060 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -4,11 +4,12 @@ import { BlameabilityTracker } from './blameabilityTracker'; import { BlameActiveLineController } from './blameActiveLineController'; import { BlameAnnotationController } from './blameAnnotationController'; import { configureCssCharacters } from './blameAnnotationFormatter'; +import { CloseUnchangedFilesCommand, OpenChangedFilesCommand } from './commands'; import { CopyMessageToClipboardCommand, CopyShaToClipboardCommand } from './commands'; -import { DiffLineWithPreviousCommand, DiffLineWithWorkingCommand, DiffWithPreviousCommand, DiffWithWorkingCommand} from './commands'; +import { DiffDirectoryCommand, DiffLineWithPreviousCommand, DiffLineWithWorkingCommand, DiffWithPreviousCommand, DiffWithWorkingCommand} from './commands'; import { ShowBlameCommand, ToggleBlameCommand } from './commands'; import { ShowBlameHistoryCommand, ShowFileHistoryCommand } from './commands'; -import { ShowQuickCommitDetailsCommand, ShowQuickFileHistoryCommand, ShowQuickRepoHistoryCommand, ShowQuickRepoStatusCommand} from './commands'; +import { ShowQuickCommitDetailsCommand, ShowQuickCommitFileDetailsCommand, ShowQuickFileHistoryCommand, ShowQuickRepoHistoryCommand, ShowQuickRepoStatusCommand} from './commands'; import { ToggleCodeLensCommand } from './commands'; import { Keyboard } from './commands'; import { IAdvancedConfig, IBlameConfig } from './configuration'; @@ -44,12 +45,19 @@ export async function activate(context: ExtensionContext) { catch (ex) { Logger.error(ex); if (ex.message.includes('Unable to find git')) { - await window.showErrorMessage(`GitLens: Unable to find Git. Please make sure Git is installed. Also ensure that Git is either in the PATH, or that 'gitlens.advanced.git' is pointed to its installed location.`); + await window.showErrorMessage(`GitLens was unable to find Git. Please make sure Git is installed. Also ensure that Git is either in the PATH, or that 'gitlens.advanced.git' is pointed to its installed location.`); } commands.executeCommand(BuiltInCommands.SetContext, 'gitlens:enabled', false); return; } + const version = Git.gitInfo().version; + const [major, minor] = version.split('.'); + // If git is less than v2.2.0 + if (parseInt(major, 10) < 2 || parseInt(minor, 10) < 2) { + await window.showErrorMessage(`GitLens requires a newer version of Git (>= 2.2.0) than is currently installed (${version}). Please install a more recent version of Git.`); + } + let gitEnabled = workspace.getConfiguration('git').get('enabled'); commands.executeCommand(BuiltInCommands.SetContext, 'gitlens:enabled', gitEnabled); context.subscriptions.push(workspace.onDidChangeConfiguration(() => { @@ -79,8 +87,11 @@ export async function activate(context: ExtensionContext) { context.subscriptions.push(new Keyboard(context)); + context.subscriptions.push(new CloseUnchangedFilesCommand(git, repoPath)); + context.subscriptions.push(new OpenChangedFilesCommand(git, repoPath)); context.subscriptions.push(new CopyMessageToClipboardCommand(git, repoPath)); context.subscriptions.push(new CopyShaToClipboardCommand(git, repoPath)); + context.subscriptions.push(new DiffDirectoryCommand(git, repoPath)); context.subscriptions.push(new DiffWithWorkingCommand(git)); context.subscriptions.push(new DiffLineWithWorkingCommand(git)); context.subscriptions.push(new DiffWithPreviousCommand(git)); @@ -90,6 +101,7 @@ export async function activate(context: ExtensionContext) { context.subscriptions.push(new ShowBlameHistoryCommand(git)); context.subscriptions.push(new ShowFileHistoryCommand(git)); context.subscriptions.push(new ShowQuickCommitDetailsCommand(git)); + context.subscriptions.push(new ShowQuickCommitFileDetailsCommand(git)); context.subscriptions.push(new ShowQuickFileHistoryCommand(git)); context.subscriptions.push(new ShowQuickRepoHistoryCommand(git, repoPath)); context.subscriptions.push(new ShowQuickRepoStatusCommand(git, repoPath)); diff --git a/src/git/enrichers/logParserEnricher.ts b/src/git/enrichers/logParserEnricher.ts index 9fb72c3..9aecc2b 100644 --- a/src/git/enrichers/logParserEnricher.ts +++ b/src/git/enrichers/logParserEnricher.ts @@ -1,5 +1,5 @@ 'use strict'; -import Git, { GitFileStatus, GitLogCommit, IGitAuthor, IGitEnricher, IGitLog } from './../git'; +import Git, { GitFileStatus, GitLogCommit, GitLogType, IGitAuthor, IGitEnricher, IGitLog } from './../git'; import * as moment from 'moment'; import * as path from 'path'; @@ -149,7 +149,7 @@ export class GitLogParserEnricher implements IGitEnricher { return entries; } - enrich(data: string, type: 'file' | 'repo', fileNameOrRepoPath: string, isRepoPath: boolean = false): IGitLog { + enrich(data: string, type: GitLogType, fileNameOrRepoPath: string, isRepoPath: boolean = false): IGitLog { const entries = this._parseEntries(data, isRepoPath); if (!entries) return undefined; @@ -189,7 +189,7 @@ export class GitLogParserEnricher implements IGitEnricher { authors.set(entry.author, author); } - commit = new GitLogCommit(repoPath, entry.sha, relativeFileName, entry.author, moment(entry.authorDate).toDate(), entry.summary, entry.status, entry.fileStatuses, undefined, entry.originalFileName); + commit = new GitLogCommit(type, repoPath, entry.sha, relativeFileName, entry.author, moment(entry.authorDate).toDate(), entry.summary, entry.status, entry.fileStatuses, undefined, entry.originalFileName); if (relativeFileName !== entry.fileName) { commit.originalFileName = entry.fileName; diff --git a/src/git/git.ts b/src/git/git.ts index b3e4722..f423c38 100644 --- a/src/git/git.ts +++ b/src/git/git.ts @@ -91,8 +91,17 @@ export default class Git { return gitCommand(root, ...params, `--`, file); } + static diffDir(repoPath: string, sha1: string, sha2?: string) { + const params = [`difftool`, `--dir-diff`, sha1]; + if (sha2) { + params.push(sha2); + } + + return gitCommand(repoPath, ...params); + } + static diffStatus(repoPath: string, sha1?: string, sha2?: string) { - const params = [`diff`, `--name- status`, `-M`]; + const params = [`diff`, `--name-status`, `-M`]; if (sha1) { params.push(sha1); } @@ -103,6 +112,43 @@ export default class Git { return gitCommand(repoPath, ...params); } + static async getVersionedFile(fileName: string, repoPath: string, sha: string) { + const data = await Git.getVersionedFileText(fileName, repoPath, sha); + + const ext = path.extname(fileName); + return new Promise((resolve, reject) => { + tmp.file({ prefix: `${path.basename(fileName, ext)}-${sha}__`, postfix: ext }, + (err, destination, fd, cleanupCallback) => { + if (err) { + reject(err); + return; + } + + Logger.log(`getVersionedFile(${fileName}, ${repoPath}, ${sha}); destination=${destination}`); + fs.appendFile(destination, data, err => { + if (err) { + reject(err); + return; + } + + resolve(destination); + }); + }); + }); + } + + static getVersionedFileText(fileName: string, repoPath: string, sha: string) { + const [file, root] = Git.splitPath(Git.normalizePath(fileName), repoPath); + sha = sha.replace('^', ''); + + if (Git.isUncommitted(sha)) return Promise.reject(new Error(`sha=${sha} is uncommitted`)); + return gitCommand(root, 'show', `${sha}:./${file}`); + } + + static gitInfo(): IGit { + return git; + } + static log(fileName: string, sha?: string, repoPath?: string, maxCount?: number, reverse: boolean = false) { const [file, root]: [string, string] = Git.splitPath(Git.normalizePath(fileName), repoPath); @@ -150,39 +196,6 @@ export default class Git { return gitCommand(repoPath, ...params); } - static async getVersionedFile(fileName: string, repoPath: string, sha: string) { - const data = await Git.getVersionedFileText(fileName, repoPath, sha); - - const ext = path.extname(fileName); - return new Promise((resolve, reject) => { - tmp.file({ prefix: `${path.basename(fileName, ext)}-${sha}__`, postfix: ext }, - (err, destination, fd, cleanupCallback) => { - if (err) { - reject(err); - return; - } - - Logger.log(`getVersionedFile(${fileName}, ${repoPath}, ${sha}); destination=${destination}`); - fs.appendFile(destination, data, err => { - if (err) { - reject(err); - return; - } - - resolve(destination); - }); - }); - }); - } - - static getVersionedFileText(fileName: string, repoPath: string, sha: string) { - const [file, root] = Git.splitPath(Git.normalizePath(fileName), repoPath); - sha = sha.replace('^', ''); - - if (Git.isUncommitted(sha)) return Promise.reject(new Error(`sha=${sha} is uncommitted`)); - return gitCommand(root, 'show', `${sha}:./${file}`); - } - static statusFile(fileName: string, repoPath: string): Promise { const [file, root]: [string, string] = Git.splitPath(Git.normalizePath(fileName), repoPath); diff --git a/src/git/gitEnrichment.ts b/src/git/gitEnrichment.ts index b0d8333..b7c2ee2 100644 --- a/src/git/gitEnrichment.ts +++ b/src/git/gitEnrichment.ts @@ -103,12 +103,15 @@ export class GitCommit implements IGitCommit { } } +export type GitLogType = 'file' | 'repo'; + export class GitLogCommit extends GitCommit { fileStatuses: { status: GitFileStatus, fileName: string }[]; status: GitFileStatus; constructor( + public type: GitLogType, repoPath: string, sha: string, fileName: string, @@ -145,7 +148,7 @@ export interface IGitCommitLine { export interface IGitLog { repoPath: string; authors: Map; - commits: Map; + commits: Map; } export declare type GitFileStatus = '?' | 'A' | 'C' | 'D' | 'M' | 'R' | 'U'; diff --git a/src/gitCodeLensProvider.ts b/src/gitCodeLensProvider.ts index 647d66b..78c760b 100644 --- a/src/gitCodeLensProvider.ts +++ b/src/gitCodeLensProvider.ts @@ -271,6 +271,7 @@ export default class GitCodeLensProvider implements CodeLensProvider { case CodeLensCommand.ShowFileHistory: return this._applyShowFileHistoryCommand(title, lens, blame, recentCommit); case CodeLensCommand.DiffWithPrevious: return this._applyDiffWithPreviousCommand(title, lens, blame, recentCommit); case CodeLensCommand.ShowQuickCommitDetails: return this._applyShowQuickCommitDetailsCommand(title, lens, blame, recentCommit); + case CodeLensCommand.ShowQuickCommitFileDetails: return this._applyShowQuickCommitFileDetailsCommand(title, lens, blame, recentCommit); case CodeLensCommand.ShowQuickFileHistory: return this._applyShowQuickFileHistoryCommand(title, lens, blame, recentCommit); case CodeLensCommand.ShowQuickRepoHistory: return this._applyShowQuickRepoHistoryCommand(title, lens, blame, recentCommit); default: return lens; @@ -291,6 +292,7 @@ export default class GitCodeLensProvider implements CodeLensProvider { case CodeLensCommand.ShowFileHistory: return this._applyShowFileHistoryCommand(title, lens, blame); case CodeLensCommand.DiffWithPrevious: return this._applyDiffWithPreviousCommand(title, lens, blame); case CodeLensCommand.ShowQuickCommitDetails: return this._applyShowQuickCommitDetailsCommand(title, lens, blame); + case CodeLensCommand.ShowQuickCommitFileDetails: return this._applyShowQuickCommitFileDetailsCommand(title, lens, blame); case CodeLensCommand.ShowQuickFileHistory: return this._applyShowQuickFileHistoryCommand(title, lens, blame); case CodeLensCommand.ShowQuickRepoHistory: return this._applyShowQuickRepoHistoryCommand(title, lens, blame); default: return lens; @@ -363,8 +365,17 @@ export default class GitCodeLensProvider implements CodeLensProvider { _applyShowQuickCommitDetailsCommand(title: string, lens: T, blame: IGitBlameLines, commit?: GitCommit): T { lens.command = { title: title, - command: CodeLensCommand.ShowQuickFileHistory, - arguments: [Uri.file(lens.uri.fsPath), undefined, commit] + command: CodeLensCommand.ShowQuickCommitDetails, + arguments: [Uri.file(lens.uri.fsPath), commit.sha, commit] + }; + return lens; + } + + _applyShowQuickCommitFileDetailsCommand(title: string, lens: T, blame: IGitBlameLines, commit?: GitCommit): T { + lens.command = { + title: title, + command: CodeLensCommand.ShowQuickCommitFileDetails, + arguments: [Uri.file(lens.uri.fsPath), commit.sha, commit] }; return lens; } diff --git a/src/gitProvider.ts b/src/gitProvider.ts index d77c097..ad8f9e7 100644 --- a/src/gitProvider.ts +++ b/src/gitProvider.ts @@ -293,13 +293,22 @@ export class GitProvider extends Disposable { return Git.repoPath(cwd); } + async getRepoPathFromUri(uri?: Uri, fallbackRepoPath?: string): Promise { + if (!(uri instanceof Uri)) return fallbackRepoPath; + + const gitUri = await GitUri.fromUri(uri, this); + if (gitUri.repoPath) return gitUri.repoPath; + + return (await this.getRepoPathFromFile(gitUri.fsPath)) || fallbackRepoPath; + } + async getRepoPathFromFile(fileName: string): Promise { const log = await this.getLogForFile(fileName, undefined, undefined, undefined, 1); return log && log.repoPath; } getBlameForFile(fileName: string, sha?: string, repoPath?: string): Promise { - Logger.log(`getBlameForFile('${fileName}', ${sha}, ${repoPath})`); + Logger.log(`getBlameForFile('${fileName}', ${sha}, '${repoPath}')`); fileName = Git.normalizePath(fileName); const useCaching = this.UseGitCaching && !sha; @@ -362,7 +371,7 @@ export class GitProvider extends Disposable { } async getBlameForLine(fileName: string, line: number, sha?: string, repoPath?: string): Promise { - Logger.log(`getBlameForLine('${fileName}', ${line}, ${sha}, ${repoPath})`); + Logger.log(`getBlameForLine('${fileName}', ${line}, ${sha}, '${repoPath}')`); if (this.UseGitCaching && !sha) { const blame = await this.getBlameForFile(fileName); @@ -400,7 +409,7 @@ export class GitProvider extends Disposable { } async getBlameForRange(fileName: string, range: Range, sha?: string, repoPath?: string): Promise { - Logger.log(`getBlameForRange('${fileName}', [${range.start.line}, ${range.end.line}], ${sha}, ${repoPath})`); + Logger.log(`getBlameForRange('${fileName}', [${range.start.line}, ${range.end.line}], ${sha}, '${repoPath}')`); const blame = await this.getBlameForFile(fileName, sha, repoPath); if (!blame) return undefined; @@ -408,7 +417,7 @@ export class GitProvider extends Disposable { } getBlameForRangeSync(blame: IGitBlame, fileName: string, range: Range, sha?: string, repoPath?: string): IGitBlameLines | undefined { - Logger.log(`getBlameForRangeSync('${fileName}', [${range.start.line}, ${range.end.line}], ${sha}, ${repoPath})`); + Logger.log(`getBlameForRangeSync('${fileName}', [${range.start.line}, ${range.end.line}], ${sha}, '${repoPath}')`); if (!blame.lines.length) return Object.assign({ allLines: blame.lines }, blame); @@ -455,7 +464,7 @@ export class GitProvider extends Disposable { } async getBlameLocations(fileName: string, range: Range, sha?: string, repoPath?: string, selectedSha?: string, line?: number): Promise { - Logger.log(`getBlameLocations('${fileName}', [${range.start.line}, ${range.end.line}], ${sha}, ${repoPath})`); + Logger.log(`getBlameLocations('${fileName}', [${range.start.line}, ${range.end.line}], ${sha}, '${repoPath}')`); const blame = await this.getBlameForRange(fileName, range, sha, repoPath); if (!blame) return undefined; @@ -494,7 +503,7 @@ export class GitProvider extends Disposable { } getLogForFile(fileName: string, sha?: string, repoPath?: string, range?: Range, maxCount?: number, reverse: boolean = false): Promise { - Logger.log(`getLogForFile('${fileName}', ${sha}, ${repoPath}, ${range && `[${range.start.line}, ${range.end.line}]`}, ${maxCount})`); + Logger.log(`getLogForFile('${fileName}', ${sha}, '${repoPath}', ${range && `[${range.start.line}, ${range.end.line}]`}, ${maxCount})`); fileName = Git.normalizePath(fileName); const useCaching = this.UseGitCaching && !sha && !range && !maxCount; @@ -555,7 +564,7 @@ export class GitProvider extends Disposable { } async getLogLocations(fileName: string, sha?: string, repoPath?: string, selectedSha?: string, line?: number): Promise { - Logger.log(`getLogLocations('${fileName}', ${sha}, ${repoPath}, ${selectedSha}, ${line})`); + Logger.log(`getLogLocations('${fileName}', ${sha}, '${repoPath}', ${selectedSha}, ${line})`); const log = await this.getLogForFile(fileName, sha, repoPath); if (!log) return undefined; @@ -590,13 +599,13 @@ export class GitProvider extends Disposable { } async isFileUncommitted(fileName: string, repoPath: string): Promise { - Logger.log(`isFileUncommitted('${fileName}', ${repoPath})`); + Logger.log(`isFileUncommitted('${fileName}', '${repoPath}')`); const status = await this.getStatusForFile(fileName, repoPath); return !!status; } async getVersionedFile(fileName: string, repoPath: string, sha: string) { - Logger.log(`getVersionedFile('${fileName}', ${repoPath}, ${sha})`); + Logger.log(`getVersionedFile('${fileName}', '${repoPath}', ${sha})`); const file = await Git.getVersionedFile(fileName, repoPath, sha); if (this.UseUriCaching) { @@ -608,7 +617,7 @@ export class GitProvider extends Disposable { } getVersionedFileText(fileName: string, repoPath: string, sha: string) { - Logger.log(`getVersionedFileText('${fileName}', ${repoPath}, ${sha})`); + Logger.log(`getVersionedFileText('${fileName}', '${repoPath}', ${sha})`); return Git.getVersionedFileText(fileName, repoPath, sha); } @@ -618,6 +627,11 @@ export class GitProvider extends Disposable { this.hasGitUriForFile(editor)); } + openDirectoryDiff(repoPath: string, sha1: string, sha2?: string) { + Logger.log(`openDirectoryDiff('${repoPath}', ${sha1}, ${sha2})`); + return Git.diffDir(repoPath, sha1, sha2); + } + toggleCodeLens(editor: TextEditor) { if (this.config.codeLens.visibility !== CodeLensVisibility.OnDemand || (!this.config.codeLens.recentChange.enabled && !this.config.codeLens.authors.enabled)) return; diff --git a/src/quickPicks.ts b/src/quickPicks.ts index f07d8f9..f42e11f 100644 --- a/src/quickPicks.ts +++ b/src/quickPicks.ts @@ -1,7 +1,8 @@ 'use strict'; export { CommandQuickPickItem, OpenFileCommandQuickPickItem, OpenFilesCommandQuickPickItem } from './quickPicks/quickPicks'; export { CommitQuickPickItem, CommitWithFileStatusQuickPickItem } from './quickPicks/gitQuickPicks'; -export { OpenCommitFileCommandQuickPickItem, OpenCommitWorkingTreeFileCommandQuickPickItem, OpenCommitFilesCommandQuickPickItem, OpenCommitWorkingTreeFilesCommandQuickPickItem, CommitDetailsQuickPick, CommitFileDetailsQuickPick } from './quickPicks/commitDetails'; +export { OpenCommitFilesCommandQuickPickItem, OpenCommitWorkingTreeFilesCommandQuickPickItem, CommitDetailsQuickPick } from './quickPicks/commitDetails'; +export { OpenCommitFileCommandQuickPickItem, OpenCommitWorkingTreeFileCommandQuickPickItem, CommitFileDetailsQuickPick } from './quickPicks/commitFileDetails'; export { FileHistoryQuickPick } from './quickPicks/fileHistory'; export { RepoHistoryQuickPick } from './quickPicks/repoHistory'; export { OpenStatusFileCommandQuickPickItem, OpenStatusFilesCommandQuickPickItem, RepoStatusQuickPick } from './quickPicks/repoStatus'; diff --git a/src/quickPicks/commitDetails.ts b/src/quickPicks/commitDetails.ts index 86a92dc..bdb4c54 100644 --- a/src/quickPicks/commitDetails.ts +++ b/src/quickPicks/commitDetails.ts @@ -1,45 +1,20 @@ 'use strict'; -import { Iterables } from '../system'; import { QuickPickItem, QuickPickOptions, Uri, window } from 'vscode'; import { Commands, Keyboard } from '../commands'; -import { GitCommit, GitLogCommit, GitProvider, GitUri } from '../gitProvider'; +import { GitLogCommit, GitProvider } from '../gitProvider'; import { CommitWithFileStatusQuickPickItem } from './gitQuickPicks'; -import { CommandQuickPickItem, getQuickPickIgnoreFocusOut, OpenFileCommandQuickPickItem, OpenFilesCommandQuickPickItem } from './quickPicks'; +import { CommandQuickPickItem, getQuickPickIgnoreFocusOut, OpenFilesCommandQuickPickItem } from './quickPicks'; import * as moment from 'moment'; import * as path from 'path'; -export { CommandQuickPickItem, CommitWithFileStatusQuickPickItem }; - -export class OpenCommitFileCommandQuickPickItem extends OpenFileCommandQuickPickItem { - - constructor(commit: GitCommit, item?: QuickPickItem) { - const uri = GitProvider.toGitContentUri(commit); - super(uri, item || { - label: `$(file-symlink-file) Open File`, - description: `\u00a0 \u2014 \u00a0\u00a0 as of \u00a0 $(git-commit) \u00a0 ${commit.sha} \u00a0\u2022\u00a0 ${commit.getFormattedPath()}` - }); - } -} - -export class OpenCommitWorkingTreeFileCommandQuickPickItem extends OpenFileCommandQuickPickItem { - - constructor(commit: GitCommit, item?: QuickPickItem) { - const uri = Uri.file(path.resolve(commit.repoPath, commit.fileName)); - super(uri, item || { - label: `$(file-symlink-file) Open Working File`, - description: `\u00a0 \u2014 \u00a0\u00a0 ${commit.getFormattedPath()}` - }); - } -} - export class OpenCommitFilesCommandQuickPickItem extends OpenFilesCommandQuickPickItem { constructor(commit: GitLogCommit, item?: QuickPickItem) { const repoPath = commit.repoPath; const uris = commit.fileStatuses.map(_ => GitProvider.toGitContentUri(commit.sha, _.fileName, repoPath, commit.originalFileName)); super(uris, item || { - label: `$(file-symlink-file) Open Files`, - description: `\u00a0 \u2014 \u00a0\u00a0 as of \u00a0 $(git-commit) \u00a0 ${commit.sha}` + label: `$(file-symlink-file) Open Changed Files`, + description: `\u00a0 \u2014 \u00a0\u00a0 in \u00a0$(git-commit) ${commit.sha}` //detail: `Opens all of the changed files in $(git-commit) ${commit.sha}` }); } @@ -51,7 +26,7 @@ export class OpenCommitWorkingTreeFilesCommandQuickPickItem extends OpenFilesCom const repoPath = commit.repoPath; const uris = commit.fileStatuses.map(_ => Uri.file(path.resolve(repoPath, _.fileName))); super(uris, item || { - label: `$(file-symlink-file) Open Working Files`, + label: `$(file-symlink-file) Open Changed Working Files`, description: undefined //detail: `Opens all of the changed file in the working tree` }); @@ -63,18 +38,35 @@ export class CommitDetailsQuickPick { static async show(commit: GitLogCommit, uri: Uri, goBackCommand?: CommandQuickPickItem): Promise { const items: (CommitWithFileStatusQuickPickItem | CommandQuickPickItem)[] = commit.fileStatuses.map(fs => new CommitWithFileStatusQuickPickItem(commit, fs.fileName, fs.status)); - items.splice(0, 0, new CommandQuickPickItem({ + let index = 0; + + items.splice(index++, 0, new CommandQuickPickItem({ label: `$(clippy) Copy Commit Sha to Clipboard`, - description: `\u00a0 \u2014 \u00a0\u00a0 $(git-commit) ${commit.sha}` + description: `\u00a0 \u2014 \u00a0\u00a0 ${commit.sha}` }, Commands.CopyShaToClipboard, [uri, commit.sha])); - items.splice(1, 0, new CommandQuickPickItem({ + items.splice(index++, 0, new CommandQuickPickItem({ label: `$(clippy) Copy Commit Message to Clipboard`, - description: `\u00a0 \u2014 \u00a0\u00a0 $(git-commit) ${commit.message}` + description: `\u00a0 \u2014 \u00a0\u00a0 ${commit.message}` }, Commands.CopyMessageToClipboard, [uri, commit.sha, commit.message])); - items.splice(2, 0, new OpenCommitWorkingTreeFilesCommandQuickPickItem(commit)); - items.splice(3, 0, new OpenCommitFilesCommandQuickPickItem(commit)); + items.splice(index++, 0, new CommandQuickPickItem({ + label: `$(git-compare) Directory Compare with Previous Commit`, + description: `\u00a0 \u2014 \u00a0\u00a0 $(git-commit) ${commit.previousSha || `${commit.sha}^`} \u00a0 $(git-compare) \u00a0 $(git-commit) ${commit.sha}` + }, Commands.DiffDirectory, [commit.uri, commit.previousSha || `${commit.sha}^`, commit.sha])); + + items.splice(index++, 0, new CommandQuickPickItem({ + label: `$(git-compare) Directory Compare with Working Tree`, + description: `\u00a0 \u2014 \u00a0\u00a0 $(git-commit) ${commit.sha} \u00a0 $(git-compare) \u00a0 $(file-directory) Working Tree` + }, Commands.DiffDirectory, [uri, commit.sha])); + + items.splice(index++, 0, new CommandQuickPickItem({ + label: `Changed Files`, + description: null + }, Commands.ShowQuickCommitDetails, [uri, commit.sha, commit, goBackCommand])); + + items.push(new OpenCommitFilesCommandQuickPickItem(commit)); + items.push(new OpenCommitWorkingTreeFilesCommandQuickPickItem(commit)); if (goBackCommand) { items.splice(0, 0, goBackCommand); @@ -94,90 +86,6 @@ export class CommitDetailsQuickPick { await Keyboard.instance.exitScope(); - return pick; - } -} - -export class CommitFileDetailsQuickPick { - - static async show(git: GitProvider, commit: GitCommit, workingFileName: string, uri: Uri, currentCommand?: CommandQuickPickItem, goBackCommand?: CommandQuickPickItem, options: { showFileHistory?: boolean } = {}): Promise { - const items: CommandQuickPickItem[] = []; - - const workingName = (workingFileName && path.basename(workingFileName)) || path.basename(commit.fileName); - - const isUncommitted = commit.isUncommitted; - if (isUncommitted) { - // Since we can't trust the previous sha on an uncommitted commit, find the last commit for this file - const log = await git.getLogForFile(commit.uri.fsPath, undefined, undefined, undefined, 2); - if (!log) return undefined; - - commit = Iterables.first(log.commits.values()); - } - - if (commit.previousSha) { - items.push(new CommandQuickPickItem({ - label: `$(git-compare) Compare with Previous Commit`, - description: `\u00a0 \u2014 \u00a0\u00a0 $(git-commit) ${commit.previousSha} \u00a0 $(git-compare) \u00a0 $(git-commit) ${commit.sha}` - }, Commands.DiffWithPrevious, [commit.uri, commit])); - } - - items.push(new CommandQuickPickItem({ - label: `$(git-compare) Compare with Working Tree`, - description: `\u00a0 \u2014 \u00a0\u00a0 $(git-commit) ${commit.sha} \u00a0 $(git-compare) \u00a0 $(file-text) ${workingName}` - }, Commands.DiffWithWorking, [uri, commit])); - - items.push(new CommandQuickPickItem({ - label: `$(diff) Show Changed Files`, - description: undefined, //`\u00a0 \u2014 \u00a0\u00a0 $(git-commit) ${commit.sha}`, - detail: `Shows all of the changed files in commit $(git-commit) ${commit.sha}` - }, Commands.ShowQuickCommitDetails, [new GitUri(commit.uri, commit), commit.sha, undefined, currentCommand])); - - items.push(new CommandQuickPickItem({ - label: `$(clippy) Copy Commit Sha to Clipboard`, - description: `\u00a0 \u2014 \u00a0\u00a0 $(git-commit) ${commit.sha}` - }, Commands.CopyShaToClipboard, [uri, commit.sha])); - - items.push(new CommandQuickPickItem({ - label: `$(clippy) Copy Commit Message to Clipboard`, - description: `\u00a0 \u2014 \u00a0\u00a0 $(git-commit) ${commit.message}` - }, Commands.CopyMessageToClipboard, [uri, commit.sha, commit.message])); - - items.push(new OpenCommitWorkingTreeFileCommandQuickPickItem(commit)); - items.push(new OpenCommitFileCommandQuickPickItem(commit)); - - if (options.showFileHistory) { - if (workingFileName) { - items.push(new CommandQuickPickItem({ - label: `$(history) Show File History`, - description: undefined, //`\u00a0 \u2014 \u00a0\u00a0 ${path.basename(commit.fileName)}`, - detail: `Shows the commit history of the file, starting at the most recent commit` - }, Commands.ShowQuickFileHistory, [commit.uri, undefined, undefined, currentCommand])); - } - - items.push(new CommandQuickPickItem({ - label: `$(history) Show Previous File History`, - description: undefined, //`\u00a0 \u2014 \u00a0\u00a0 ${path.basename(commit.fileName)}`, - detail: `Shows the previous commit history of the file, starting at $(git-commit) ${commit.sha}` - }, Commands.ShowQuickFileHistory, [new GitUri(commit.uri, commit), undefined, undefined, currentCommand])); - } - - if (goBackCommand) { - items.splice(0, 0, goBackCommand); - } - - await Keyboard.instance.enterScope(['left', goBackCommand]); - - const pick = await window.showQuickPick(items, { - matchOnDescription: true, - placeHolder: `${commit.getFormattedPath()} \u2022 ${isUncommitted ? 'Uncommitted \u21E8 ' : '' }${commit.sha} \u2022 ${commit.author}, ${moment(commit.date).fromNow()} \u2022 ${commit.message}`, - ignoreFocusOut: getQuickPickIgnoreFocusOut(), - onDidSelectItem: (item: QuickPickItem) => { - Keyboard.instance.setKeyCommand('right', item); - } - } as QuickPickOptions); - - await Keyboard.instance.exitScope(); - return pick; } } \ No newline at end of file diff --git a/src/quickPicks/commitFileDetails.ts b/src/quickPicks/commitFileDetails.ts new file mode 100644 index 0000000..9e44924 --- /dev/null +++ b/src/quickPicks/commitFileDetails.ts @@ -0,0 +1,111 @@ +'use strict'; +import { Iterables } from '../system'; +import { QuickPickItem, QuickPickOptions, Uri, window } from 'vscode'; +import { Commands, Keyboard } from '../commands'; +import { GitCommit, GitLogCommit, GitProvider, GitUri } from '../gitProvider'; +import { CommandQuickPickItem, getQuickPickIgnoreFocusOut, OpenFileCommandQuickPickItem } from './quickPicks'; +import * as moment from 'moment'; +import * as path from 'path'; + +export class OpenCommitFileCommandQuickPickItem extends OpenFileCommandQuickPickItem { + + constructor(commit: GitCommit, item?: QuickPickItem) { + const uri = GitProvider.toGitContentUri(commit); + super(uri, item || { + label: `$(file-symlink-file) Open File`, + description: `\u00a0 \u2014 \u00a0\u00a0 ${path.basename(commit.fileName)} in \u00a0$(git-commit) ${commit.sha}` + }); + } +} + +export class OpenCommitWorkingTreeFileCommandQuickPickItem extends OpenFileCommandQuickPickItem { + + constructor(commit: GitCommit, item?: QuickPickItem) { + const uri = Uri.file(path.resolve(commit.repoPath, commit.fileName)); + super(uri, item || { + label: `$(file-symlink-file) Open Working File`, + description: `\u00a0 \u2014 \u00a0\u00a0 ${path.basename(commit.fileName)}` + }); + } +} + +export class CommitFileDetailsQuickPick { + + static async show(git: GitProvider, commit: GitCommit | GitLogCommit, workingFileName: string, uri: Uri, goBackCommand?: CommandQuickPickItem, currentCommand?: CommandQuickPickItem, options: { showFileHistory?: boolean } = {}): Promise { + const items: CommandQuickPickItem[] = []; + + const workingName = (workingFileName && path.basename(workingFileName)) || path.basename(commit.fileName); + + const isUncommitted = commit.isUncommitted; + if (isUncommitted) { + // Since we can't trust the previous sha on an uncommitted commit, find the last commit for this file + const log = await git.getLogForFile(commit.uri.fsPath, undefined, undefined, undefined, 2); + if (!log) return undefined; + + commit = Iterables.first(log.commits.values()); + } + + if (!options.showFileHistory) { + items.push(new CommandQuickPickItem({ + label: `$(git-commit) Show Commit Details`, + description: `\u00a0 \u2014 \u00a0\u00a0 $(git-commit) ${commit.sha}` + }, Commands.ShowQuickCommitDetails, [new GitUri(commit.uri, commit), commit.sha, commit, currentCommand])); + } + + if (commit.previousSha) { + items.push(new CommandQuickPickItem({ + label: `$(git-compare) Compare with Previous Commit`, + description: `\u00a0 \u2014 \u00a0\u00a0 $(git-commit) ${commit.previousSha} \u00a0 $(git-compare) \u00a0 $(git-commit) ${commit.sha}` + }, Commands.DiffWithPrevious, [commit.uri, commit])); + } + + items.push(new CommandQuickPickItem({ + label: `$(git-compare) Compare with Working Tree`, + description: `\u00a0 \u2014 \u00a0\u00a0 $(git-commit) ${commit.sha} \u00a0 $(git-compare) \u00a0 $(file-text) ${workingName}` + }, Commands.DiffWithWorking, [uri, commit])); + + items.push(new CommandQuickPickItem({ + label: `$(clippy) Copy Commit Sha to Clipboard`, + description: `\u00a0 \u2014 \u00a0\u00a0 ${commit.sha}` + }, Commands.CopyShaToClipboard, [uri, commit.sha])); + + items.push(new CommandQuickPickItem({ + label: `$(clippy) Copy Commit Message to Clipboard`, + description: `\u00a0 \u2014 \u00a0\u00a0 ${commit.message}` + }, Commands.CopyMessageToClipboard, [uri, commit.sha, commit.message])); + + items.push(new OpenCommitFileCommandQuickPickItem(commit)); + items.push(new OpenCommitWorkingTreeFileCommandQuickPickItem(commit)); + + if (workingFileName && options.showFileHistory) { + items.push(new CommandQuickPickItem({ + label: `$(history) Show File History`, + description: `\u00a0 \u2014 \u00a0\u00a0 of ${path.basename(commit.fileName)}` + }, Commands.ShowQuickFileHistory, [commit.uri, undefined, currentCommand])); + } + + items.push(new CommandQuickPickItem({ + label: `$(history) Show ${workingFileName && options.showFileHistory ? 'Previous ' : ''}File History`, + description: `\u00a0 \u2014 \u00a0\u00a0 of ${path.basename(commit.fileName)} \u00a0\u2022\u00a0 starting from \u00a0$(git-commit) ${commit.sha}` + }, Commands.ShowQuickFileHistory, [new GitUri(commit.uri, commit), undefined, currentCommand])); + + if (goBackCommand) { + items.splice(0, 0, goBackCommand); + } + + await Keyboard.instance.enterScope(['left', goBackCommand]); + + const pick = await window.showQuickPick(items, { + matchOnDescription: true, + placeHolder: `${commit.getFormattedPath()} \u2022 ${isUncommitted ? 'Uncommitted \u21E8 ' : '' }${commit.sha} \u2022 ${commit.author}, ${moment(commit.date).fromNow()} \u2022 ${commit.message}`, + ignoreFocusOut: getQuickPickIgnoreFocusOut(), + onDidSelectItem: (item: QuickPickItem) => { + Keyboard.instance.setKeyCommand('right', item); + } + } as QuickPickOptions); + + await Keyboard.instance.exitScope(); + + return pick; + } +} \ No newline at end of file diff --git a/src/quickPicks/fileHistory.ts b/src/quickPicks/fileHistory.ts index 079a5ea..996cd17 100644 --- a/src/quickPicks/fileHistory.ts +++ b/src/quickPicks/fileHistory.ts @@ -5,12 +5,11 @@ import { Commands, Keyboard } from '../commands'; import { IGitLog } from '../gitProvider'; import { CommitQuickPickItem } from './gitQuickPicks'; import { CommandQuickPickItem, getQuickPickIgnoreFocusOut } from './quickPicks'; - -export { CommandQuickPickItem, CommitQuickPickItem }; +import * as path from 'path'; export class FileHistoryQuickPick { - static async show(log: IGitLog, uri: Uri, maxCount: number, defaultMaxCount: number, goBackCommand?: CommandQuickPickItem): Promise { + static async show(log: IGitLog, uri: Uri, sha: string, maxCount: number, defaultMaxCount: number, goBackCommand?: CommandQuickPickItem): Promise { const items = Array.from(Iterables.map(log.commits.values(), c => new CommitQuickPickItem(c))) as (CommitQuickPickItem | CommandQuickPickItem)[]; if (maxCount !== 0 && items.length >= defaultMaxCount) { @@ -18,7 +17,7 @@ export class FileHistoryQuickPick { label: `$(sync) Show All Commits`, description: `\u00a0 \u2014 \u00a0\u00a0 Currently only showing the first ${defaultMaxCount} commits`, detail: `This may take a while` - }, Commands.ShowQuickFileHistory, [uri, 0, undefined, goBackCommand])); + }, Commands.ShowQuickFileHistory, [uri, 0, goBackCommand])); } // Only show the full repo option if we are the root @@ -27,10 +26,15 @@ export class FileHistoryQuickPick { label: `$(repo) Show Repository History`, description: null, detail: 'Shows the commit history of the repository' - }, Commands.ShowQuickRepoHistory, [undefined, undefined, undefined, new CommandQuickPickItem({ - label: `go back \u21A9`, - description: null - }, Commands.ShowQuickFileHistory, [uri, maxCount])])); + }, Commands.ShowQuickRepoHistory, + [ + undefined, + undefined, + new CommandQuickPickItem({ + label: `go back \u21A9`, + description: `\u00a0 \u2014 \u00a0\u00a0 to history of \u00a0$(file-text) ${path.basename(uri.fsPath)}` + }, Commands.ShowQuickFileHistory, [uri, maxCount]) + ])); } if (goBackCommand) { @@ -44,7 +48,7 @@ export class FileHistoryQuickPick { const pick = await window.showQuickPick(items, { matchOnDescription: true, matchOnDetail: true, - placeHolder: commit.getFormattedPath(), + placeHolder: `${commit.getFormattedPath()}${sha ? ` \u00a0\u2022\u00a0 ${sha}` : ''}`, ignoreFocusOut: getQuickPickIgnoreFocusOut(), onDidSelectItem: (item: QuickPickItem) => { Keyboard.instance.setKeyCommand('right', item); diff --git a/src/quickPicks/quickPicks.ts b/src/quickPicks/quickPicks.ts index 59e809e..6fc291e 100644 --- a/src/quickPicks/quickPicks.ts +++ b/src/quickPicks/quickPicks.ts @@ -1,25 +1,12 @@ 'use strict'; -import { commands, QuickPickItem, TextEditor, Uri, window, workspace } from 'vscode'; -import { Commands } from '../commands'; +import { commands, QuickPickItem, TextEditor, Uri, workspace } from 'vscode'; +import { Commands, openEditor } from '../commands'; import { IAdvancedConfig } from '../configuration'; -import { BuiltInCommands } from '../constants'; export function getQuickPickIgnoreFocusOut() { return !workspace.getConfiguration('gitlens').get('advanced').quickPick.closeOnFocusOut; } -export async function openEditor(uri: Uri, pinned: boolean = false) { - try { - if (!pinned) return await commands.executeCommand(BuiltInCommands.Open, uri); - - const document = await workspace.openTextDocument(uri); - return window.showTextDocument(document, (window.activeTextEditor && window.activeTextEditor.viewColumn) || 1, true); - } - catch (ex) { - return undefined; - } -} - export class CommandQuickPickItem implements QuickPickItem { label: string; @@ -58,7 +45,7 @@ export class OpenFilesCommandQuickPickItem extends CommandQuickPickItem { async execute(): Promise<{}> { for (const uri of this.uris) { - openEditor(uri, true); + await openEditor(uri, true); } return undefined; } diff --git a/src/quickPicks/repoHistory.ts b/src/quickPicks/repoHistory.ts index adc84c9..62d2a48 100644 --- a/src/quickPicks/repoHistory.ts +++ b/src/quickPicks/repoHistory.ts @@ -6,8 +6,6 @@ import { IGitLog } from '../gitProvider'; import { CommitQuickPickItem } from './gitQuickPicks'; import { CommandQuickPickItem, getQuickPickIgnoreFocusOut } from './quickPicks'; -export { CommandQuickPickItem, CommitQuickPickItem }; - export class RepoHistoryQuickPick { static async show(log: IGitLog, uri: Uri, maxCount: number, defaultMaxCount: number, goBackCommand?: CommandQuickPickItem): Promise { @@ -18,7 +16,7 @@ export class RepoHistoryQuickPick { label: `$(sync) Show All Commits`, description: `\u00a0 \u2014 \u00a0\u00a0 Currently only showing the first ${defaultMaxCount} commits`, detail: `This may take a while` - }, Commands.ShowQuickRepoHistory, [uri, 0, undefined, goBackCommand])); + }, Commands.ShowQuickRepoHistory, [uri, 0, goBackCommand])); } if (goBackCommand) { diff --git a/src/quickPicks/repoStatus.ts b/src/quickPicks/repoStatus.ts index 0926cc4..fc9f538 100644 --- a/src/quickPicks/repoStatus.ts +++ b/src/quickPicks/repoStatus.ts @@ -1,13 +1,11 @@ 'use strict'; import { Iterables } from '../system'; import { QuickPickItem, QuickPickOptions, Uri, window } from 'vscode'; -import { Keyboard } from '../commands'; +import { Commands, Keyboard } from '../commands'; import { getGitStatusIcon, GitFileStatusItem } from '../gitProvider'; -import { CommandQuickPickItem, getQuickPickIgnoreFocusOut, OpenFileCommandQuickPickItem, OpenFilesCommandQuickPickItem } from './quickPicks'; +import { CommandQuickPickItem, getQuickPickIgnoreFocusOut, OpenFileCommandQuickPickItem } from './quickPicks'; import * as path from 'path'; -export { CommandQuickPickItem }; - export class OpenStatusFileCommandQuickPickItem extends OpenFileCommandQuickPickItem { constructor(status: GitFileStatusItem, item?: QuickPickItem) { @@ -26,17 +24,17 @@ export class OpenStatusFileCommandQuickPickItem extends OpenFileCommandQuickPick } } -export class OpenStatusFilesCommandQuickPickItem extends OpenFilesCommandQuickPickItem { +export class OpenStatusFilesCommandQuickPickItem extends CommandQuickPickItem { constructor(statuses: GitFileStatusItem[], item?: QuickPickItem) { const repoPath = statuses.length && statuses[0].repoPath; const uris = statuses.map(_ => Uri.file(path.resolve(repoPath, _.fileName))); - super(uris, item || { - label: `$(file-symlink-file) Open Files`, - description: undefined, - detail: `Opens all of the changed files in the repository` - }); + super(item || { + label: `$(file-symlink-file) Open Changed Files`, + description: undefined + //detail: `Opens all of the changed files in the repository` + }, Commands.OpenChangedFiles, [undefined, uris]); } } @@ -49,23 +47,42 @@ export class RepoStatusQuickPick { const items = Array.from(Iterables.map(statuses, s => new OpenStatusFileCommandQuickPickItem(s))) as (OpenStatusFileCommandQuickPickItem | OpenStatusFilesCommandQuickPickItem | CommandQuickPickItem)[]; if (statuses.some(_ => _.staged)) { - const index = statuses.findIndex(_ => !_.staged); - if (index > -1) { - items.splice(index, 0, new OpenStatusFilesCommandQuickPickItem(statuses.filter(_ => _.status !== 'D' && !_.staged), { - label: `$(file-symlink-file) Open Unstaged Files`, - description: undefined, - detail: `Opens all of the unstaged files in the repository` + let index = 0; + const unstagedIndex = statuses.findIndex(_ => !_.staged); + if (unstagedIndex > -1) { + items.splice(unstagedIndex, 0, new CommandQuickPickItem({ + label: `Unstaged Files`, + description: undefined + }, Commands.ShowQuickRepoStatus, [goBackCommand])); + + items.splice(index++, 0, new OpenStatusFilesCommandQuickPickItem(statuses.filter(_ => _.status !== 'D' && _.staged), { + label: `$(file-symlink-file) Open Staged Files`, + description: undefined })); - items.splice(0, 0, new OpenStatusFilesCommandQuickPickItem(statuses.filter(_ => _.status !== 'D' && _.staged), { - label: `$(file-symlink-file) Open Staged Files`, - description: undefined, - detail: `Opens all of the staged files in the repository` + items.splice(index++, 0, new OpenStatusFilesCommandQuickPickItem(statuses.filter(_ => _.status !== 'D' && !_.staged), { + label: `$(file-symlink-file) Open Unstaged Files`, + description: undefined })); } + + items.splice(index++, 0, new CommandQuickPickItem({ + label: `Staged Files`, + description: undefined + }, Commands.ShowQuickRepoStatus, [goBackCommand])); + } + else if (statuses.some(_ => !_.staged)) { + items.splice(0, 0, new CommandQuickPickItem({ + label: `Unstaged Files`, + description: undefined + }, Commands.ShowQuickRepoStatus, [goBackCommand])); } if (statuses.length) { + items.splice(0, 0, new CommandQuickPickItem({ + label: '$(x) Close Unchanged Files', + description: null + }, Commands.CloseUnchangedFiles)); items.splice(0, 0, new OpenStatusFilesCommandQuickPickItem(statuses.filter(_ => _.status !== 'D'))); }