From 487fb5197c0403bf1fd0858dbba8af82f6d8af81 Mon Sep 17 00:00:00 2001 From: Eric Amodio Date: Sun, 12 Mar 2017 01:15:44 -0500 Subject: [PATCH] Adds paging support to repo/file quick picks Adds keyboard support to page in repo/file quick picks Adds progress indicator for repo/file quick picks Completely reworks keyboard scopes --- src/commands.ts | 2 +- src/commands/keyboard.ts | 146 +++++++++++++++++++++-------------- src/commands/showQuickFileHistory.ts | 14 ++-- src/commands/showQuickRepoHistory.ts | 19 +++-- src/extension.ts | 2 +- src/quickPicks/commitDetails.ts | 28 +++---- src/quickPicks/commitFileDetails.ts | 28 +++---- src/quickPicks/fileHistory.ts | 80 +++++++++++++------ src/quickPicks/quickPicks.ts | 33 ++++---- src/quickPicks/repoHistory.ts | 70 ++++++++++++----- src/quickPicks/repoStatus.ts | 6 +- 11 files changed, 253 insertions(+), 175 deletions(-) diff --git a/src/commands.ts b/src/commands.ts index 8ce8753..af1520d 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -2,7 +2,7 @@ import { commands } from 'vscode'; import { BuiltInCommands } from './constants'; -export { Keyboard, Keys } from './commands/keyboard'; +export { Keyboard, KeyboardScope, KeyMapping, KeyNoopCommand, Keys } from './commands/keyboard'; export { ActiveEditorCommand, Command, Commands, EditorCommand, openEditor } from './commands/commands'; export { CloseUnchangedFilesCommand } from './commands/closeUnchangedFiles'; diff --git a/src/commands/keyboard.ts b/src/commands/keyboard.ts index 6f92a93..6d3bb31 100644 --- a/src/commands/keyboard.ts +++ b/src/commands/keyboard.ts @@ -1,9 +1,12 @@ 'use strict'; -import { commands, Disposable, ExtensionContext, QuickPickItem } from 'vscode'; +import { commands, Disposable, QuickPickItem } from 'vscode'; import { CommandContext, setCommandContext } from '../commands'; import { CommandQuickPickItem, OpenFileCommandQuickPickItem } from '../quickPicks/quickPicks'; import { Logger } from '../logger'; +const keyNoopCommand = Object.create(null) as QuickPickItem; +export { keyNoopCommand as KeyNoopCommand }; + export declare type Keys = 'left' | 'right' | ',' | '.'; export const keys: Keys[] = [ 'left', @@ -12,16 +15,72 @@ export const keys: Keys[] = [ '.' ]; -let scopeCount = 0; -let counters = { - left: 0, - right: 0, - ',': 0, - '.': 0 -}; +export declare type KeyMapping = { [id: string]: (QuickPickItem | (() => Promise)) }; +let mappings: KeyMapping[] = []; let _instance: Keyboard; +export class KeyboardScope extends Disposable { + + constructor(private mapping: KeyMapping) { + super(() => this.dispose()); + + for (const key in mapping) { + mapping[key] = mapping[key] || keyNoopCommand; + } + } + + async dispose() { + const index = mappings.indexOf(this.mapping); + Logger.log('KeyboardScope.dispose', mappings.length, index); + if (index === (mappings.length - 1)) { + mappings.pop(); + await this.updateKeyCommandsContext(mappings[mappings.length - 1]); + } + else { + mappings.splice(index, 1); + } + } + + async begin() { + mappings.push(this.mapping); + await this.updateKeyCommandsContext(this.mapping); + return this; + } + + async clearKeyCommand(key: Keys) { + const mapping = mappings[mappings.length - 1]; + if (mapping !== this.mapping || !mapping[key]) return; + + Logger.log('KeyboardScope.clearKeyCommand', mappings.length, key); + mapping[key] = undefined; + await setCommandContext(`${CommandContext.Key}:${key}`, false); + } + + async setKeyCommand(key: Keys, command: QuickPickItem | (() => Promise)) { + const mapping = mappings[mappings.length - 1]; + if (mapping !== this.mapping) return; + + Logger.log('KeyboardScope.setKeyCommand', mappings.length, key, !!mapping[key]); + + if (!mapping[key]) { + mapping[key] = command; + await setCommandContext(`${CommandContext.Key}:${key}`, true); + } + else { + mapping[key] = command; + } + } + + private async updateKeyCommandsContext(mapping: KeyMapping) { + const promises = []; + for (const key of keys) { + promises.push(setCommandContext(`${CommandContext.Key}:${key}`, !!(mapping && mapping[key]))); + } + await Promise.all(promises); + } +} + export class Keyboard extends Disposable { static get instance(): Keyboard { @@ -30,7 +89,7 @@ export class Keyboard extends Disposable { private _disposable: Disposable; - constructor(private context: ExtensionContext) { + constructor() { super(() => this.dispose()); const subscriptions: Disposable[] = []; @@ -48,62 +107,35 @@ export class Keyboard extends Disposable { this._disposable && this._disposable.dispose(); } + async beginScope(mapping?: KeyMapping): Promise { + Logger.log('Keyboard.beginScope', mappings.length); + return await new KeyboardScope(mapping ? Object.assign(Object.create(null), mapping) : Object.create(null)).begin(); + } + async execute(key: Keys): Promise<{}> { - let command = this.context.globalState.get(`gitlens:key:${key}`) as CommandQuickPickItem | (() => Promise); - if (typeof command === 'function') { - command = await command(); - } - if (!command || !(command instanceof CommandQuickPickItem)) return undefined; + if (!mappings.length) return undefined; - Logger.log('Keyboard.execute', key); + try { + const mapping = mappings[mappings.length - 1]; - if (command instanceof OpenFileCommandQuickPickItem) { - // Have to open this pinned right now, because vscode doesn't have a way to open a unpinned, but unfocused editor - return await command.execute(true); - } + let command = mapping[key] as CommandQuickPickItem | (() => Promise); + if (typeof command === 'function') { + command = await command(); + } + if (!command || !(command instanceof CommandQuickPickItem)) return undefined; - return await command.execute(); - } + Logger.log('Keyboard.execute', key); - async enterScope(...keyCommands: [Keys, QuickPickItem | (() => Promise)][]) { - Logger.log('Keyboard.enterScope', scopeCount); - scopeCount++; - // await setCommandContext(CommandContext.Key, ++scopeCount); - if (keyCommands && Array.isArray(keyCommands) && keyCommands.length) { - for (const [key, command] of keyCommands) { - await setCommandContext(`${CommandContext.Key}:${key}`, ++counters[key]); - await this.setKeyCommand(key as Keys, command); + if (command instanceof OpenFileCommandQuickPickItem) { + // Have to open this pinned right now, because vscode doesn't have a way to open a unpinned, but unfocused editor + return await command.execute(true); } - } - } - async exitScope(clear: boolean = true) { - Logger.log('Keyboard.exitScope', scopeCount); - if (scopeCount) { - scopeCount--; - // await setCommandContext(CommandContext.Key, --scopeCount); + return await command.execute(); } - if (clear && !scopeCount) { - for (const key of keys) { - if (counters[key]) { - await setCommandContext(`${CommandContext.Key}:${key}`, --counters[key]); - } - await this.clearKeyCommand(key); - } + catch (ex) { + Logger.error('Keyboard.execute', ex); + return undefined; } } - - async clearKeyCommand(key: Keys) { - Logger.log('Keyboard.clearKeyCommand', key); - if (counters[key]) { - await setCommandContext(`${CommandContext.Key}:${key}`, --counters[key]); - } - await this.context.globalState.update(`gitlens:key:${key}`, undefined); - } - - async setKeyCommand(key: Keys, command: QuickPickItem | (() => Promise)) { - Logger.log('Keyboard.setKeyCommand', key); - await setCommandContext(`${CommandContext.Key}:${key}`, ++counters[key]); - await this.context.globalState.update(`gitlens:key:${key}`, command); - } } \ No newline at end of file diff --git a/src/commands/showQuickFileHistory.ts b/src/commands/showQuickFileHistory.ts index f6dde4a..4f2b8d8 100644 --- a/src/commands/showQuickFileHistory.ts +++ b/src/commands/showQuickFileHistory.ts @@ -1,9 +1,9 @@ 'use strict'; import { commands, TextEditor, Uri, window } from 'vscode'; -import { ActiveEditorCommand, Commands } from './commands'; +import { ActiveEditorCommand, Commands } from '../commands'; import { GitProvider, GitUri, IGitLog } from '../gitProvider'; import { Logger } from '../logger'; -import { CommandQuickPickItem, FileHistoryQuickPick, showQuickPickProgress } from '../quickPicks'; +import { CommandQuickPickItem, FileHistoryQuickPick } from '../quickPicks'; import * as path from 'path'; export class ShowQuickFileHistoryCommand extends ActiveEditorCommand { @@ -12,14 +12,12 @@ export class ShowQuickFileHistoryCommand extends ActiveEditorCommand { super(Commands.ShowQuickFileHistory); } - async execute(editor: TextEditor, uri?: Uri, maxCount?: number, goBackCommand?: CommandQuickPickItem, log?: IGitLog) { + async execute(editor: TextEditor, uri?: Uri, maxCount?: number, goBackCommand?: CommandQuickPickItem, log?: IGitLog, nextPageCommand?: CommandQuickPickItem) { if (!(uri instanceof Uri)) { uri = editor && editor.document && editor.document.uri; } - if (!uri) { - return commands.executeCommand(Commands.ShowQuickRepoHistory); - } + if (!uri) return commands.executeCommand(Commands.ShowQuickRepoHistory); const gitUri = await GitUri.fromUri(uri, this.git); @@ -27,7 +25,7 @@ export class ShowQuickFileHistoryCommand extends ActiveEditorCommand { maxCount = this.git.config.advanced.maxQuickHistory; } - const progressCancellation = showQuickPickProgress(`Loading file history \u2014 ${maxCount ? ` limited to ${maxCount} commits` : ` this may take a while`}\u2026`); + const progressCancellation = FileHistoryQuickPick.showProgress(maxCount); try { if (!log) { log = await this.git.getLogForFile(gitUri.fsPath, gitUri.sha, gitUri.repoPath, undefined, maxCount); @@ -36,7 +34,7 @@ export class ShowQuickFileHistoryCommand extends ActiveEditorCommand { if (progressCancellation.token.isCancellationRequested) return undefined; - const pick = await FileHistoryQuickPick.show(log, uri, gitUri.sha, progressCancellation, goBackCommand); + const pick = await FileHistoryQuickPick.show(log, gitUri, progressCancellation, goBackCommand, nextPageCommand); if (!pick) return undefined; if (pick instanceof CommandQuickPickItem) { diff --git a/src/commands/showQuickRepoHistory.ts b/src/commands/showQuickRepoHistory.ts index 8d6997d..e04a412 100644 --- a/src/commands/showQuickRepoHistory.ts +++ b/src/commands/showQuickRepoHistory.ts @@ -1,9 +1,9 @@ 'use strict'; import { commands, TextEditor, Uri, window } from 'vscode'; -import { ActiveEditorCommand, Commands } from './commands'; +import { ActiveEditorCommand, Commands } from '../commands'; import { GitProvider, GitUri, IGitLog } from '../gitProvider'; import { Logger } from '../logger'; -import { CommandQuickPickItem, RepoHistoryQuickPick, showQuickPickProgress } from '../quickPicks'; +import { CommandQuickPickItem, RepoHistoryQuickPick } from '../quickPicks'; export class ShowQuickRepoHistoryCommand extends ActiveEditorCommand { @@ -11,30 +11,33 @@ export class ShowQuickRepoHistoryCommand extends ActiveEditorCommand { super(Commands.ShowQuickRepoHistory); } - async execute(editor: TextEditor, uri?: Uri, sha?: string, maxCount?: number | undefined, goBackCommand?: CommandQuickPickItem, log?: IGitLog) { + async execute(editor: TextEditor, uri?: Uri, maxCount?: number, goBackCommand?: CommandQuickPickItem, log?: IGitLog, nextPageCommand?: CommandQuickPickItem) { if (!(uri instanceof Uri)) { uri = editor && editor.document && editor.document.uri; } + const gitUri = await GitUri.fromUri(uri, this.git); + Logger.log(`ShowQuickRepoHistoryCommand.execute`, gitUri.shortSha); + if (maxCount == null) { maxCount = this.git.config.advanced.maxQuickHistory; } - const progressCancellation = showQuickPickProgress(`Loading repository history \u2014 ${maxCount ? ` limited to ${maxCount} commits` : ` this may take a while`}\u2026`); + const progressCancellation = RepoHistoryQuickPick.showProgress(maxCount); try { if (!log) { - const repoPath = await this.git.getRepoPathFromUri(uri, this.repoPath); + const repoPath = gitUri.repoPath || await this.git.getRepoPathFromUri(uri, this.repoPath); if (!repoPath) return window.showWarningMessage(`Unable to show repository history`); if (progressCancellation.token.isCancellationRequested) return undefined; - log = await this.git.getLogForRepo(repoPath, sha, maxCount); + log = await this.git.getLogForRepo(repoPath, gitUri.sha, maxCount); if (!log) return window.showWarningMessage(`Unable to show repository history`); } if (progressCancellation.token.isCancellationRequested) return undefined; - const pick = await RepoHistoryQuickPick.show(log, uri, sha, progressCancellation, goBackCommand); + const pick = await RepoHistoryQuickPick.show(log, gitUri, progressCancellation, goBackCommand, nextPageCommand); if (!pick) return undefined; if (pick instanceof CommandQuickPickItem) { @@ -45,7 +48,7 @@ export class ShowQuickRepoHistoryCommand extends ActiveEditorCommand { new CommandQuickPickItem({ label: `go back \u21A9`, description: `\u00a0 \u2014 \u00a0\u00a0 to repository history` - }, Commands.ShowQuickRepoHistory, [uri, sha, maxCount, goBackCommand, log]), + }, Commands.ShowQuickRepoHistory, [uri, maxCount, goBackCommand, log]), log); } catch (ex) { diff --git a/src/extension.ts b/src/extension.ts index 48c4301..5ec9a66 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -86,7 +86,7 @@ export async function activate(context: ExtensionContext) { const activeLineController = new BlameActiveLineController(context, git, blameabilityTracker, annotationController); context.subscriptions.push(activeLineController); - context.subscriptions.push(new Keyboard(context)); + context.subscriptions.push(new Keyboard()); context.subscriptions.push(new CloseUnchangedFilesCommand(git, repoPath)); context.subscriptions.push(new OpenChangedFilesCommand(git, repoPath)); diff --git a/src/quickPicks/commitDetails.ts b/src/quickPicks/commitDetails.ts index 570ddb4..a59750a 100644 --- a/src/quickPicks/commitDetails.ts +++ b/src/quickPicks/commitDetails.ts @@ -1,9 +1,9 @@ 'use strict'; import { QuickPickItem, QuickPickOptions, Uri, window } from 'vscode'; -import { Commands, Keyboard } from '../commands'; +import { Commands, Keyboard, KeyNoopCommand } from '../commands'; import { GitLogCommit, GitProvider, IGitLog } from '../gitProvider'; import { CommitWithFileStatusQuickPickItem } from './gitQuickPicks'; -import { CommandQuickPickItem, getQuickPickIgnoreFocusOut, KeyCommandQuickPickItem, KeyNoopCommandQuickPickItem, OpenFilesCommandQuickPickItem } from './quickPicks'; +import { CommandQuickPickItem, getQuickPickIgnoreFocusOut, KeyCommandQuickPickItem, OpenFilesCommandQuickPickItem } from './quickPicks'; import * as moment from 'moment'; import * as path from 'path'; @@ -72,30 +72,26 @@ export class CommitDetailsQuickPick { items.splice(0, 0, goBackCommand); } - const previousCommand = commit.previousSha - ? new KeyCommandQuickPickItem(Commands.ShowQuickCommitDetails, [commit.previousUri, commit.previousSha, undefined, goBackCommand, repoLog]) - : new KeyNoopCommandQuickPickItem(); + const previousCommand = commit.previousSha && new KeyCommandQuickPickItem(Commands.ShowQuickCommitDetails, [commit.previousUri, commit.previousSha, undefined, goBackCommand, repoLog]); let nextCommand: CommandQuickPickItem | (() => Promise); if (repoLog) { - nextCommand = commit.nextSha - ? new KeyCommandQuickPickItem(Commands.ShowQuickCommitDetails, [commit.nextUri, commit.nextSha, undefined, goBackCommand, repoLog]) - : new KeyNoopCommandQuickPickItem(); + nextCommand = commit.nextSha && new KeyCommandQuickPickItem(Commands.ShowQuickCommitDetails, [commit.nextUri, commit.nextSha, undefined, goBackCommand, repoLog]); } else { nextCommand = async () => { const log = await git.getLogForRepo(commit.repoPath, undefined, git.config.advanced.maxQuickHistory); const c = log && log.commits.get(commit.sha); - if (!c) return new KeyNoopCommandQuickPickItem(); + if (!c) return KeyNoopCommand; return new KeyCommandQuickPickItem(Commands.ShowQuickCommitDetails, [c.nextUri, c.nextSha, undefined, goBackCommand, log]); }; } - await Keyboard.instance.enterScope( - ['left', goBackCommand], - [',', previousCommand], - ['.', nextCommand] - ); + const scope = await Keyboard.instance.beginScope({ + left: goBackCommand, + ',': previousCommand, + '.': nextCommand + }); const pick = await window.showQuickPick(items, { matchOnDescription: true, @@ -103,11 +99,11 @@ export class CommitDetailsQuickPick { placeHolder: `${commit.shortSha} \u2022 ${commit.author}, ${moment(commit.date).fromNow()} \u2022 ${commit.message}`, ignoreFocusOut: getQuickPickIgnoreFocusOut(), onDidSelectItem: (item: QuickPickItem) => { - Keyboard.instance.setKeyCommand('right', item); + scope.setKeyCommand('right', item); } } as QuickPickOptions); - await Keyboard.instance.exitScope(); + await scope.dispose(); return pick; } diff --git a/src/quickPicks/commitFileDetails.ts b/src/quickPicks/commitFileDetails.ts index a4138cb..5d0e97d 100644 --- a/src/quickPicks/commitFileDetails.ts +++ b/src/quickPicks/commitFileDetails.ts @@ -1,9 +1,9 @@ 'use strict'; import { Iterables } from '../system'; import { QuickPickItem, QuickPickOptions, Uri, window } from 'vscode'; -import { Commands, Keyboard } from '../commands'; +import { Commands, Keyboard, KeyNoopCommand } from '../commands'; import { GitCommit, GitLogCommit, GitProvider, GitUri, IGitLog } from '../gitProvider'; -import { CommandQuickPickItem, getQuickPickIgnoreFocusOut, KeyCommandQuickPickItem, KeyNoopCommandQuickPickItem, OpenFileCommandQuickPickItem } from './quickPicks'; +import { CommandQuickPickItem, getQuickPickIgnoreFocusOut, KeyCommandQuickPickItem, OpenFileCommandQuickPickItem } from './quickPicks'; import * as moment from 'moment'; import * as path from 'path'; @@ -93,41 +93,37 @@ export class CommitFileDetailsQuickPick { items.splice(0, 0, goBackCommand); } - const previousCommand = commit.previousSha - ? new KeyCommandQuickPickItem(Commands.ShowQuickCommitFileDetails, [commit.previousUri, commit.previousSha, undefined, goBackCommand, options, fileLog]) - : new KeyNoopCommandQuickPickItem(); + const previousCommand = commit.previousSha && new KeyCommandQuickPickItem(Commands.ShowQuickCommitFileDetails, [commit.previousUri, commit.previousSha, undefined, goBackCommand, options, fileLog]); let nextCommand: CommandQuickPickItem | (() => Promise); if (fileLog) { - nextCommand = commit.nextSha - ? new KeyCommandQuickPickItem(Commands.ShowQuickCommitFileDetails, [commit.nextUri, commit.nextSha, undefined, goBackCommand, options, fileLog]) - : new KeyNoopCommandQuickPickItem(); + nextCommand = commit.nextSha && new KeyCommandQuickPickItem(Commands.ShowQuickCommitFileDetails, [commit.nextUri, commit.nextSha, undefined, goBackCommand, options, fileLog]); } else { 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); - if (!c) return new KeyNoopCommandQuickPickItem(); + if (!c) return KeyNoopCommand; return new KeyCommandQuickPickItem(Commands.ShowQuickCommitFileDetails, [c.nextUri, c.nextSha, undefined, goBackCommand, options, log]); }; } - await Keyboard.instance.enterScope( - ['left', goBackCommand], - [',', previousCommand], - ['.', nextCommand] - ); + const scope = await Keyboard.instance.beginScope({ + left: goBackCommand, + ',': previousCommand, + '.': nextCommand + }); const pick = await window.showQuickPick(items, { matchOnDescription: true, placeHolder: `${commit.getFormattedPath()} \u2022 ${isUncommitted ? 'Uncommitted \u21E8 ' : '' }${commit.shortSha} \u2022 ${commit.author}, ${moment(commit.date).fromNow()} \u2022 ${commit.message}`, ignoreFocusOut: getQuickPickIgnoreFocusOut(), onDidSelectItem: (item: QuickPickItem) => { - Keyboard.instance.setKeyCommand('right', item); + scope.setKeyCommand('right', item); } } as QuickPickOptions); - await Keyboard.instance.exitScope(); + await scope.dispose(); return pick; } diff --git a/src/quickPicks/fileHistory.ts b/src/quickPicks/fileHistory.ts index 07ebef0..d238c78 100644 --- a/src/quickPicks/fileHistory.ts +++ b/src/quickPicks/fileHistory.ts @@ -1,42 +1,68 @@ 'use strict'; import { Iterables } from '../system'; -import { CancellationTokenSource, QuickPickItem, QuickPickOptions, Uri, window } from 'vscode'; -import { Commands, Keyboard } from '../commands'; +import { CancellationTokenSource, QuickPickOptions, Uri, window } from 'vscode'; +import { Commands, Keyboard, KeyNoopCommand } from '../commands'; import { GitUri, IGitLog } from '../gitProvider'; import { CommitQuickPickItem } from './gitQuickPicks'; -import { CommandQuickPickItem, getQuickPickIgnoreFocusOut } from './quickPicks'; +import { CommandQuickPickItem, getQuickPickIgnoreFocusOut, showQuickPickProgress } from './quickPicks'; import * as path from 'path'; export class FileHistoryQuickPick { - static async show(log: IGitLog, uri: Uri, sha: string, progressCancellation: CancellationTokenSource, goBackCommand?: CommandQuickPickItem): Promise { + static showProgress(maxCount?: number) { + return showQuickPickProgress(`Loading file history \u2014 ${maxCount ? ` limited to ${maxCount} commits` : ` this may take a while`}\u2026`, + { + left: KeyNoopCommand, + ',': KeyNoopCommand, + '.': KeyNoopCommand + }); + } + + static async show(log: IGitLog, uri: GitUri, progressCancellation: CancellationTokenSource, goBackCommand?: CommandQuickPickItem, nextPageCommand?: CommandQuickPickItem): Promise { const items = Array.from(Iterables.map(log.commits.values(), c => new CommitQuickPickItem(c))) as (CommitQuickPickItem | CommandQuickPickItem)[]; - if (log.truncated) { + let previousPageCommand: CommandQuickPickItem; + + let index = 0; + if (log.truncated || uri.sha) { + index++; items.splice(0, 0, new CommandQuickPickItem({ label: `$(sync) Show All Commits`, - description: `\u00a0 \u2014 \u00a0\u00a0 Currently only showing the first ${log.maxCount} commits`, - detail: `This may take a while` - }, Commands.ShowQuickFileHistory, [uri, 0, goBackCommand])); - - const last = Iterables.last(log.commits.values()); - items.push(new CommandQuickPickItem({ - label: `$(ellipsis) Show More Commits`, - description: `\u00a0 \u2014 \u00a0\u00a0 Shows the next ${log.maxCount} commits` - }, Commands.ShowQuickFileHistory, [new GitUri(uri, last), log.maxCount, goBackCommand])); + description: `\u00a0 \u2014 \u00a0\u00a0 this may take a while` + }, Commands.ShowQuickFileHistory, [Uri.file(uri.fsPath), 0, goBackCommand])); + + if (nextPageCommand) { + index++; + items.splice(0, 0, nextPageCommand); + } + + if (log.truncated) { + const npc = new CommandQuickPickItem({ + label: `$(arrow-right) Show Next Commits`, + description: `\u00a0 \u2014 \u00a0\u00a0 shows ${log.maxCount} newer commits` + }, Commands.ShowQuickFileHistory, [uri, log.maxCount, goBackCommand, undefined, nextPageCommand]); + + const last = Iterables.last(log.commits.values()); + + previousPageCommand = new CommandQuickPickItem({ + label: `$(arrow-left) Show Previous Commits`, + description: `\u00a0 \u2014 \u00a0\u00a0 shows ${log.maxCount} older commits` + }, Commands.ShowQuickFileHistory, [new GitUri(uri, last), log.maxCount, goBackCommand, undefined, npc]); + + index++; + items.splice(0, 0, previousPageCommand); + } } // Only show the full repo option if we are the root if (!goBackCommand) { - items.splice(0, 0, new CommandQuickPickItem({ + items.splice(index, 0, new CommandQuickPickItem({ label: `$(repo) Show Repository History`, - description: null, - detail: 'Shows the commit history of the repository' + description: `\u00a0 \u2014 \u00a0\u00a0 shows the repository commit history` }, Commands.ShowQuickRepoHistory, [ undefined, undefined, - undefined, new CommandQuickPickItem({ label: `go back \u21A9`, description: `\u00a0 \u2014 \u00a0\u00a0 to history of \u00a0$(file-text) ${path.basename(uri.fsPath)}` @@ -50,7 +76,11 @@ export class FileHistoryQuickPick { if (progressCancellation.token.isCancellationRequested) return undefined; - await Keyboard.instance.enterScope(['left', goBackCommand]); + const scope = await Keyboard.instance.beginScope({ + left: goBackCommand, + ',': previousPageCommand, + '.': nextPageCommand + }); const commit = Iterables.first(log.commits.values()); @@ -59,14 +89,14 @@ export class FileHistoryQuickPick { const pick = await window.showQuickPick(items, { matchOnDescription: true, matchOnDetail: true, - placeHolder: `${commit.getFormattedPath()}${sha ? ` \u00a0\u2022\u00a0 ${sha.substring(0, 8)}` : ''}`, - ignoreFocusOut: getQuickPickIgnoreFocusOut(), - onDidSelectItem: (item: QuickPickItem) => { - Keyboard.instance.setKeyCommand('right', item); - } + placeHolder: `${commit.getFormattedPath()}${uri.sha ? ` \u00a0\u2022\u00a0 ${uri.sha.substring(0, 8)}` : ''}`, + ignoreFocusOut: getQuickPickIgnoreFocusOut() + // onDidSelectItem: (item: QuickPickItem) => { + // scope.setKeyCommand('right', item); + // } } as QuickPickOptions); - await Keyboard.instance.exitScope(); + await scope.dispose(); return pick; } diff --git a/src/quickPicks/quickPicks.ts b/src/quickPicks/quickPicks.ts index e686c82..32b7c94 100644 --- a/src/quickPicks/quickPicks.ts +++ b/src/quickPicks/quickPicks.ts @@ -1,26 +1,32 @@ 'use strict'; import { CancellationTokenSource, commands, QuickPickItem, QuickPickOptions, TextEditor, Uri, window, workspace } from 'vscode'; -import { Commands, openEditor } from '../commands'; +import { Commands, Keyboard, KeyboardScope, KeyMapping, openEditor } from '../commands'; import { IAdvancedConfig } from '../configuration'; -import { Logger } from '../logger'; +// import { Logger } from '../logger'; export function getQuickPickIgnoreFocusOut() { return !workspace.getConfiguration('gitlens').get('advanced').quickPick.closeOnFocusOut; } -export function showQuickPickProgress(message: string): CancellationTokenSource { +export function showQuickPickProgress(message: string, mapping?: KeyMapping): CancellationTokenSource { const cancellation = new CancellationTokenSource(); - _showQuickPickProgress(message, cancellation); + _showQuickPickProgress(message, cancellation, mapping); return cancellation; } -async function _showQuickPickProgress(message: string, cancellation: CancellationTokenSource) { - Logger.log(`showQuickPickProgress`, `show`, message); +async function _showQuickPickProgress(message: string, cancellation: CancellationTokenSource, mapping?: KeyMapping) { + // Logger.log(`showQuickPickProgress`, `show`, message); + + 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); + + // Logger.log(`showQuickPickProgress`, `cancel`, message); + + scope && scope.dispose(); cancellation.cancel(); } @@ -28,7 +34,7 @@ function _getInfiniteCancellablePromise(cancellation: CancellationTokenSource) { return new Promise((resolve, reject) => { const disposable = cancellation.token.onCancellationRequested(() => { disposable.dispose(); - reject(); + resolve([]); }); }); } @@ -55,17 +61,6 @@ export class KeyCommandQuickPickItem extends CommandQuickPickItem { } } -export class KeyNoopCommandQuickPickItem extends CommandQuickPickItem { - - constructor() { - super({ label: undefined, description: undefined }, undefined, undefined); - } - - execute(): Thenable<{}> { - return Promise.resolve(undefined); - } -} - export class OpenFileCommandQuickPickItem extends CommandQuickPickItem { constructor(public uri: Uri, item: QuickPickItem) { diff --git a/src/quickPicks/repoHistory.ts b/src/quickPicks/repoHistory.ts index 241ba55..7c90ccf 100644 --- a/src/quickPicks/repoHistory.ts +++ b/src/quickPicks/repoHistory.ts @@ -1,28 +1,52 @@ 'use strict'; import { Iterables } from '../system'; -import { CancellationTokenSource, QuickPickItem, QuickPickOptions, Uri, window } from 'vscode'; -import { Commands, Keyboard } from '../commands'; -import { IGitLog } from '../gitProvider'; +import { CancellationTokenSource, QuickPickOptions, Uri, window } from 'vscode'; +import { Commands, Keyboard, KeyNoopCommand } from '../commands'; +import { GitUri, IGitLog } from '../gitProvider'; import { CommitQuickPickItem } from './gitQuickPicks'; -import { CommandQuickPickItem, getQuickPickIgnoreFocusOut } from './quickPicks'; +import { CommandQuickPickItem, getQuickPickIgnoreFocusOut, showQuickPickProgress } from './quickPicks'; export class RepoHistoryQuickPick { - static async show(log: IGitLog, uri: Uri, sha: string, progressCancellation: CancellationTokenSource, goBackCommand?: CommandQuickPickItem): Promise { + static showProgress(maxCount?: number) { + return showQuickPickProgress(`Loading repository history \u2014 ${maxCount ? ` limited to ${maxCount} commits` : ` this may take a while`}\u2026`, + { + left: KeyNoopCommand, + ',': KeyNoopCommand, + '.': KeyNoopCommand + }); + } + + static async show(log: IGitLog, uri: GitUri, progressCancellation: CancellationTokenSource, goBackCommand?: CommandQuickPickItem, nextPageCommand?: CommandQuickPickItem): Promise { const items = Array.from(Iterables.map(log.commits.values(), c => new CommitQuickPickItem(c, ` \u2014 ${c.fileNames}`))) as (CommitQuickPickItem | CommandQuickPickItem)[]; - if (log.truncated) { + let previousPageCommand: CommandQuickPickItem; + + if (log.truncated || uri.sha) { items.splice(0, 0, new CommandQuickPickItem({ label: `$(sync) Show All Commits`, - description: `\u00a0 \u2014 \u00a0\u00a0 Currently only showing the first ${log.maxCount} commits`, - detail: `This may take a while` - }, Commands.ShowQuickRepoHistory, [uri, undefined, 0, goBackCommand])); - - const last = Iterables.last(log.commits.values()); - items.push(new CommandQuickPickItem({ - label: `$(ellipsis) Show More Commits`, - description: `\u00a0 \u2014 \u00a0\u00a0 Shows the next ${log.maxCount} commits` - }, Commands.ShowQuickRepoHistory, [uri, last.sha, log.maxCount, goBackCommand])); + description: `\u00a0 \u2014 \u00a0\u00a0 this may take a while` + }, Commands.ShowQuickRepoHistory, [Uri.file(uri.fsPath), 0, goBackCommand])); + + if (nextPageCommand) { + items.splice(0, 0, nextPageCommand); + } + + if (log.truncated) { + const npc = new CommandQuickPickItem({ + label: `$(arrow-right) Show Next Commits`, + description: `\u00a0 \u2014 \u00a0\u00a0 shows ${log.maxCount} newer commits` + }, Commands.ShowQuickRepoHistory, [uri, log.maxCount, goBackCommand, undefined, nextPageCommand]); + + const last = Iterables.last(log.commits.values()); + + previousPageCommand = new CommandQuickPickItem({ + label: `$(arrow-left) Show Previous Commits`, + description: `\u00a0 \u2014 \u00a0\u00a0 shows ${log.maxCount} older commits` + }, Commands.ShowQuickRepoHistory, [new GitUri(uri, last), log.maxCount, goBackCommand, undefined, npc]); + + items.splice(0, 0, previousPageCommand); + } } if (goBackCommand) { @@ -31,7 +55,11 @@ export class RepoHistoryQuickPick { if (progressCancellation.token.isCancellationRequested) return undefined; - await Keyboard.instance.enterScope(['left', goBackCommand]); + const scope = await Keyboard.instance.beginScope({ + left: goBackCommand, + ',': previousPageCommand, + '.': nextPageCommand + }); progressCancellation.cancel(); @@ -39,13 +67,13 @@ export class RepoHistoryQuickPick { matchOnDescription: true, matchOnDetail: true, placeHolder: 'Search by commit message, filename, or sha', - ignoreFocusOut: getQuickPickIgnoreFocusOut(), - onDidSelectItem: (item: QuickPickItem) => { - Keyboard.instance.setKeyCommand('right', item); - } + ignoreFocusOut: getQuickPickIgnoreFocusOut() + // onDidSelectItem: (item: QuickPickItem) => { + // scope.setKeyCommand('right', item); + // } } as QuickPickOptions); - await Keyboard.instance.exitScope(); + await scope.dispose(); return pick; } diff --git a/src/quickPicks/repoStatus.ts b/src/quickPicks/repoStatus.ts index fc9f538..a7d8820 100644 --- a/src/quickPicks/repoStatus.ts +++ b/src/quickPicks/repoStatus.ts @@ -90,18 +90,18 @@ export class RepoStatusQuickPick { items.splice(0, 0, goBackCommand); } - await Keyboard.instance.enterScope(['left', goBackCommand]); + const scope = await Keyboard.instance.beginScope({ left: goBackCommand }); const pick = await window.showQuickPick(items, { matchOnDescription: true, placeHolder: statuses.length ? 'Repository has changes' : 'Repository has no changes', ignoreFocusOut: getQuickPickIgnoreFocusOut(), onDidSelectItem: (item: QuickPickItem) => { - Keyboard.instance.setKeyCommand('right', item); + scope.setKeyCommand('right', item); } } as QuickPickOptions); - await Keyboard.instance.exitScope(); + await scope.dispose(); return pick; }