From 3656d4e8a44a447219dd2e9ae608f4abc448d304 Mon Sep 17 00:00:00 2001 From: Eric Amodio Date: Mon, 13 Mar 2017 00:15:14 -0400 Subject: [PATCH] Adds keyboard support to page in repo/file quick picks --- src/git/enrichers/logParserEnricher.ts | 5 +++- src/git/git.ts | 13 +++++++--- src/gitProvider.ts | 8 +++--- src/quickPicks/commitDetails.ts | 44 +++++++++++++++++++++++++++++---- src/quickPicks/commitFileDetails.ts | 45 ++++++++++++++++++++++++++++++---- src/quickPicks/quickPicks.ts | 26 ++++++++++++-------- 6 files changed, 113 insertions(+), 28 deletions(-) diff --git a/src/git/enrichers/logParserEnricher.ts b/src/git/enrichers/logParserEnricher.ts index 37c5f44..7d7847e 100644 --- a/src/git/enrichers/logParserEnricher.ts +++ b/src/git/enrichers/logParserEnricher.ts @@ -152,7 +152,7 @@ export class GitLogParserEnricher implements IGitEnricher { return entries; } - enrich(data: string, type: GitLogType, fileNameOrRepoPath: string, maxCount: number | undefined, isRepoPath: boolean = false): IGitLog { + enrich(data: string, type: GitLogType, fileNameOrRepoPath: string, maxCount: number | undefined, isRepoPath: boolean, reverse: boolean): IGitLog { const entries = this._parseEntries(data, isRepoPath); if (!entries) return undefined; @@ -168,6 +168,9 @@ export class GitLogParserEnricher implements IGitEnricher { } for (let i = 0, len = entries.length; i < len; i++) { + // Since log --reverse doesn't properly honor a max count -- enforce it here + if (reverse && i >= maxCount) break; + const entry = entries[i]; if (i === 0 || isRepoPath) { diff --git a/src/git/git.ts b/src/git/git.ts index 260ba5b..94e028d 100644 --- a/src/git/git.ts +++ b/src/git/git.ts @@ -188,13 +188,20 @@ export class Git { return gitCommand(root, ...params); } - static logRepo(repoPath: string, sha?: string, maxCount?: number) { + static logRepo(repoPath: string, sha?: string, maxCount?: number, reverse: boolean = false) { const params = [...DefaultLogParams]; - if (maxCount) { + if (maxCount && !reverse) { params.push(`-n${maxCount}`); } if (sha) { - params.push(sha); + if (reverse) { + params.push(`--reverse`); + params.push(`--ancestry-path`); + params.push(`${sha}..HEAD`); + } + else { + params.push(sha); + } } return gitCommand(repoPath, ...params); } diff --git a/src/gitProvider.ts b/src/gitProvider.ts index 25d8f09..5daeaa0 100644 --- a/src/gitProvider.ts +++ b/src/gitProvider.ts @@ -489,7 +489,7 @@ export class GitProvider extends Disposable { return locations; } - async getLogForRepo(repoPath: string, sha?: string, maxCount?: number): Promise { + async getLogForRepo(repoPath: string, sha?: string, maxCount?: number, reverse: boolean = false): Promise { Logger.log(`getLogForRepo('${repoPath}', ${maxCount})`); if (maxCount == null) { @@ -497,8 +497,8 @@ export class GitProvider extends Disposable { } try { - const data = await Git.logRepo(repoPath, sha, maxCount); - return new GitLogParserEnricher().enrich(data, 'repo', repoPath, maxCount, true); + const data = await Git.logRepo(repoPath, sha, maxCount, reverse); + return new GitLogParserEnricher().enrich(data, 'repo', repoPath, maxCount, true, reverse); } catch (ex) { return undefined; @@ -532,7 +532,7 @@ export class GitProvider extends Disposable { return (range ? Git.logRange(fileName, range.start.line + 1, range.end.line + 1, sha, repoPath, maxCount) : Git.log(fileName, sha, repoPath, maxCount, reverse)) - .then(data => new GitLogParserEnricher().enrich(data, 'file', repoPath || fileName, maxCount, !!repoPath)) + .then(data => new GitLogParserEnricher().enrich(data, 'file', repoPath || fileName, maxCount, !!repoPath, reverse)) .catch(ex => { // Trap and cache expected log errors if (useCaching) { diff --git a/src/quickPicks/commitDetails.ts b/src/quickPicks/commitDetails.ts index a59750a..852403e 100644 --- a/src/quickPicks/commitDetails.ts +++ b/src/quickPicks/commitDetails.ts @@ -1,4 +1,5 @@ 'use strict'; +import { Iterables } from '../system'; import { QuickPickItem, QuickPickOptions, Uri, window } from 'vscode'; import { Commands, Keyboard, KeyNoopCommand } from '../commands'; import { GitLogCommit, GitProvider, IGitLog } from '../gitProvider'; @@ -72,16 +73,49 @@ export class CommitDetailsQuickPick { items.splice(0, 0, goBackCommand); } - const previousCommand = commit.previousSha && new KeyCommandQuickPickItem(Commands.ShowQuickCommitDetails, [commit.previousUri, commit.previousSha, undefined, goBackCommand, repoLog]); - + let previousCommand: CommandQuickPickItem | (() => Promise); let nextCommand: CommandQuickPickItem | (() => Promise); - if (repoLog) { + // If we have the full history, we are good + if (repoLog && !repoLog.truncated) { + previousCommand = commit.previousSha && new KeyCommandQuickPickItem(Commands.ShowQuickCommitDetails, [commit.previousUri, commit.previousSha, undefined, goBackCommand, repoLog]); nextCommand = commit.nextSha && new KeyCommandQuickPickItem(Commands.ShowQuickCommitDetails, [commit.nextUri, commit.nextSha, undefined, goBackCommand, repoLog]); } else { + previousCommand = async () => { + let log = repoLog; + let c = log && log.commits.get(commit.sha); + + // If we can't find the commit or the previous commit isn't available (since it isn't trustworthy) + if (!c || !c.previousSha) { + log = await git.getLogForRepo(commit.repoPath, commit.sha, git.config.advanced.maxQuickHistory); + c = log && log.commits.get(commit.sha); + + if (c) { + // Copy over next info, since it is trustworthy at this point + c.nextSha = commit.nextSha; + } + } + if (!c) return KeyNoopCommand; + return new KeyCommandQuickPickItem(Commands.ShowQuickCommitDetails, [c.previousUri, c.previousSha, undefined, goBackCommand, log]); + }; + nextCommand = async () => { - const log = await git.getLogForRepo(commit.repoPath, undefined, git.config.advanced.maxQuickHistory); - const c = log && log.commits.get(commit.sha); + let log = repoLog; + let c = log && log.commits.get(commit.sha); + + // If we can't find the commit or the next commit isn't available (since it isn't trustworthy) + if (!c || !c.nextSha) { + log = undefined; + c = undefined; + + // Try to find the next commit + const nextLog = await git.getLogForRepo(commit.repoPath, commit.sha, 1, true); + const next = nextLog && Iterables.first(nextLog.commits.values()); + if (next && next.sha !== commit.sha) { + c = commit; + c.nextSha = next.sha; + } + } if (!c) return KeyNoopCommand; return new KeyCommandQuickPickItem(Commands.ShowQuickCommitDetails, [c.nextUri, c.nextSha, undefined, goBackCommand, log]); }; diff --git a/src/quickPicks/commitFileDetails.ts b/src/quickPicks/commitFileDetails.ts index 5d0e97d..0219cda 100644 --- a/src/quickPicks/commitFileDetails.ts +++ b/src/quickPicks/commitFileDetails.ts @@ -93,16 +93,51 @@ export class CommitFileDetailsQuickPick { items.splice(0, 0, goBackCommand); } - const previousCommand = commit.previousSha && new KeyCommandQuickPickItem(Commands.ShowQuickCommitFileDetails, [commit.previousUri, commit.previousSha, undefined, goBackCommand, options, fileLog]); - + let previousCommand: CommandQuickPickItem | (() => Promise); let nextCommand: CommandQuickPickItem | (() => Promise); - if (fileLog) { + // If we have the full history, we are good + if (fileLog && !fileLog.truncated) { + previousCommand = commit.previousSha && new KeyCommandQuickPickItem(Commands.ShowQuickCommitFileDetails, [commit.previousUri, commit.previousSha, undefined, goBackCommand, options, fileLog]); nextCommand = commit.nextSha && new KeyCommandQuickPickItem(Commands.ShowQuickCommitFileDetails, [commit.nextUri, commit.nextSha, undefined, goBackCommand, options, fileLog]); } else { + previousCommand = async () => { + let log = fileLog; + let c = log && log.commits.get(commit.sha); + + // If we can't find the commit or the previous commit isn't available (since it isn't trustworthy) + if (!c || !c.previousSha) { + log = await git.getLogForFile(uri.fsPath, commit.sha, commit.repoPath, undefined, git.config.advanced.maxQuickHistory); + c = log && log.commits.get(commit.sha); + + if (c) { + // Copy over next info, since it is trustworthy at this point + c.nextSha = commit.nextSha; + c.nextFileName = commit.nextFileName; + } + } + if (!c) return KeyNoopCommand; + return new KeyCommandQuickPickItem(Commands.ShowQuickCommitFileDetails, [c.previousUri, c.previousSha, undefined, goBackCommand, options, log]); + }; + nextCommand = async () => { - const log = await git.getLogForFile(uri.fsPath, undefined, commit.repoPath, undefined, git.config.advanced.maxQuickHistory); - const c = log && log.commits.get(commit.sha); + let log = fileLog; + let c = log && log.commits.get(commit.sha); + + // If we can't find the commit or the next commit isn't available (since it isn't trustworthy) + if (!c || !c.nextSha) { + log = undefined; + c = undefined; + + // Try to find the next commit + const nextLog = await git.getLogForFile(uri.fsPath, commit.sha, commit.repoPath, undefined, 1, true); + const next = nextLog && Iterables.first(nextLog.commits.values()); + if (next && next.sha !== commit.sha) { + c = commit; + c.nextSha = next.sha; + c.nextFileName = next.originalFileName || next.fileName; + } + } if (!c) return KeyNoopCommand; return new KeyCommandQuickPickItem(Commands.ShowQuickCommitFileDetails, [c.nextUri, c.nextSha, undefined, goBackCommand, options, log]); }; diff --git a/src/quickPicks/quickPicks.ts b/src/quickPicks/quickPicks.ts index 32b7c94..797da9f 100644 --- a/src/quickPicks/quickPicks.ts +++ b/src/quickPicks/quickPicks.ts @@ -15,19 +15,25 @@ export function showQuickPickProgress(message: string, mapping?: KeyMapping): Ca } async function _showQuickPickProgress(message: string, cancellation: CancellationTokenSource, mapping?: KeyMapping) { - // Logger.log(`showQuickPickProgress`, `show`, message); + // Logger.log(`showQuickPickProgress`, `show`, message); - const scope: KeyboardScope = mapping && await Keyboard.instance.beginScope(mapping); + const scope: KeyboardScope = mapping && await Keyboard.instance.beginScope(mapping); - await window.showQuickPick(_getInfiniteCancellablePromise(cancellation), { - placeHolder: message, - ignoreFocusOut: getQuickPickIgnoreFocusOut() - } as QuickPickOptions, cancellation.token); - - // Logger.log(`showQuickPickProgress`, `cancel`, message); + try { + await window.showQuickPick(_getInfiniteCancellablePromise(cancellation), { + placeHolder: message, + ignoreFocusOut: getQuickPickIgnoreFocusOut() + } as QuickPickOptions, cancellation.token); + } + catch (ex) { + // Not sure why this throws + } + finally { + // Logger.log(`showQuickPickProgress`, `cancel`, message); - scope && scope.dispose(); - cancellation.cancel(); + cancellation.cancel(); + scope && scope.dispose(); + } } function _getInfiniteCancellablePromise(cancellation: CancellationTokenSource) {