From 400aac071dc7771d7a13ba3944e61d0360ef377f Mon Sep 17 00:00:00 2001 From: Eric Amodio Date: Thu, 15 Aug 2019 19:35:32 -0400 Subject: [PATCH] Adds confirmation toggle to git commands --- images/dark/icon-check.svg | 3 + images/dark/icon-no-check.svg | 3 + images/light/icon-check.svg | 3 + images/light/icon-no-check.svg | 3 + src/commands/git/checkout.ts | 63 +++++++++--------- src/commands/git/cherry-pick.ts | 2 +- src/commands/git/fetch.ts | 25 +++----- src/commands/git/merge.ts | 2 +- src/commands/git/pull.ts | 90 +++++++++++++------------- src/commands/git/push.ts | 25 +++----- src/commands/git/rebase.ts | 2 +- src/commands/git/stash.ts | 63 +++++++++--------- src/commands/gitCommands.ts | 139 ++++++++++++++++++++++++++++++++++------ src/commands/quickCommand.ts | 30 +++++++-- 14 files changed, 287 insertions(+), 166 deletions(-) create mode 100644 images/dark/icon-check.svg create mode 100644 images/dark/icon-no-check.svg create mode 100644 images/light/icon-check.svg create mode 100644 images/light/icon-no-check.svg diff --git a/images/dark/icon-check.svg b/images/dark/icon-check.svg new file mode 100644 index 0000000..e4094c5 --- /dev/null +++ b/images/dark/icon-check.svg @@ -0,0 +1,3 @@ + + + diff --git a/images/dark/icon-no-check.svg b/images/dark/icon-no-check.svg new file mode 100644 index 0000000..659bc34 --- /dev/null +++ b/images/dark/icon-no-check.svg @@ -0,0 +1,3 @@ + + + diff --git a/images/light/icon-check.svg b/images/light/icon-check.svg new file mode 100644 index 0000000..ef3c42c --- /dev/null +++ b/images/light/icon-check.svg @@ -0,0 +1,3 @@ + + + diff --git a/images/light/icon-no-check.svg b/images/light/icon-no-check.svg new file mode 100644 index 0000000..f023f0c --- /dev/null +++ b/images/light/icon-no-check.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/commands/git/checkout.ts b/src/commands/git/checkout.ts index 00dbd7d..08137a0 100644 --- a/src/commands/git/checkout.ts +++ b/src/commands/git/checkout.ts @@ -1,6 +1,6 @@ 'use strict'; /* eslint-disable no-loop-func */ -import { ProgressLocation, QuickInputButtons, window } from 'vscode'; +import { ProgressLocation, QuickInputButton, window } from 'vscode'; import { Container } from '../../container'; import { GitBranch, GitReference, GitTag, Repository } from '../../git/gitService'; import { GlyphChars } from '../../constants'; @@ -9,22 +9,24 @@ import { ReferencesQuickPickItem, RefQuickPickItem, RepositoryQuickPickItem } fr import { Strings } from '../../system'; import { Logger } from '../../logger'; +type Mutable = Omit & { -readonly [P in K]: T[P] }; + interface State { repos: Repository[]; branchOrTagOrRef: GitBranch | GitTag | GitReference; createBranch?: string; } -export interface CommandArgs { +export interface CheckoutGitCommandArgs { readonly command: 'checkout'; state?: Partial; - skipConfirmation?: boolean; + confirm?: boolean; } export class CheckoutGitCommand extends QuickCommandBase { - constructor(args?: CommandArgs) { - super('checkout', 'Checkout'); + constructor(args?: CheckoutGitCommandArgs) { + super('checkout', 'checkout', 'Checkout'); if (args === undefined || args.state === undefined) return; @@ -37,16 +39,9 @@ export class CheckoutGitCommand extends QuickCommandBase { counter++; } - if ( - args.skipConfirmation === undefined && - Container.config.gitCommands.skipConfirmations.includes(this.label) - ) { - args.skipConfirmation = true; - } - this._initialState = { counter: counter, - skipConfirmation: counter > 1 && args.skipConfirmation, + confirm: args.confirm, ...args.state }; } @@ -109,13 +104,22 @@ export class CheckoutGitCommand extends QuickCommandBase { } if (state.branchOrTagOrRef === undefined || state.counter < 2) { - const includeTags = showTags || state.repos.length === 1; + showTags = state.repos.length === 1; + + const toggleTagsButton: Mutable = { + iconPath: { + dark: Container.context.asAbsolutePath('images/dark/icon-tag.svg') as any, + light: Container.context.asAbsolutePath('images/light/icon-tag.svg') as any + }, + tooltip: showTags ? 'Hide Tags' : 'Show Tags' + }; const items = await getBranchesAndOrTags( state.repos, - includeTags, + showTags, state.repos.length === 1 ? undefined : { filterBranches: b => !b.remote } ); + const step = this.createPickStep({ title: `${this.title}${Strings.pad(GlyphChars.Dot, 2, 2)}${ state.repos.length === 1 @@ -123,39 +127,30 @@ export class CheckoutGitCommand extends QuickCommandBase { : `${state.repos.length} repositories` }`, placeholder: `Choose a branch${ - includeTags ? ' or tag' : '' + showTags ? ' or tag' : '' } to checkout to${GlyphChars.Space.repeat(3)}(select or enter a reference)`, matchOnDescription: true, items: items, selectedItems: state.branchOrTagOrRef ? items.filter(ref => ref.label === state.branchOrTagOrRef!.ref) : undefined, - buttons: includeTags - ? [QuickInputButtons.Back] - : [ - QuickInputButtons.Back, - { - iconPath: { - dark: Container.context.asAbsolutePath('images/dark/icon-tag.svg') as any, - light: Container.context.asAbsolutePath('images/light/icon-tag.svg') as any - }, - tooltip: 'Show Tags' - } - ], + additionalButtons: [toggleTagsButton], onDidClickButton: async (quickpick, button) => { quickpick.busy = true; quickpick.enabled = false; - if (!showTags) { - showTags = true; - } + showTags = !showTags; + toggleTagsButton.tooltip = showTags ? 'Hide Tags' : 'Show Tags'; quickpick.placeholder = `Choose a branch${ showTags ? ' or tag' : '' } to checkout to${GlyphChars.Space.repeat(3)}(select or enter a reference)`; - quickpick.buttons = [QuickInputButtons.Back]; - quickpick.items = await getBranchesAndOrTags(state.repos!, showTags); + quickpick.items = await getBranchesAndOrTags( + state.repos!, + showTags, + state.repos!.length === 1 ? undefined : { filterBranches: b => !b.remote } + ); quickpick.busy = false; quickpick.enabled = true; @@ -228,7 +223,7 @@ export class CheckoutGitCommand extends QuickCommandBase { } } - if (!state.skipConfirmation) { + if (this.confirm(state.confirm)) { const step = this.createConfirmStep( `Confirm ${this.title}${Strings.pad(GlyphChars.Dot, 2, 2)}${ state.repos.length === 1 diff --git a/src/commands/git/cherry-pick.ts b/src/commands/git/cherry-pick.ts index 60da22c..7a3fb84 100644 --- a/src/commands/git/cherry-pick.ts +++ b/src/commands/git/cherry-pick.ts @@ -25,7 +25,7 @@ interface State { export class CherryPickGitCommand extends QuickCommandBase { constructor() { - super('cherry-pick', 'Cherry Pick', { description: 'via Terminal' }); + super('cherry-pick', 'cherry-pick', 'Cherry Pick', false, { description: 'via Terminal' }); } execute(state: State) { diff --git a/src/commands/git/fetch.ts b/src/commands/git/fetch.ts index 365c00d..d1a5c10 100644 --- a/src/commands/git/fetch.ts +++ b/src/commands/git/fetch.ts @@ -12,16 +12,16 @@ interface State { flags: string[]; } -export interface CommandArgs { +export interface FetchGitCommandArgs { readonly command: 'fetch'; state?: Partial; - skipConfirmation?: boolean; + confirm?: boolean; } export class FetchGitCommand extends QuickCommandBase { - constructor(args?: CommandArgs) { - super('fetch', 'Fetch'); + constructor(args?: FetchGitCommandArgs) { + super('fetch', 'fetch', 'Fetch'); if (args === undefined || args.state === undefined) return; @@ -30,16 +30,9 @@ export class FetchGitCommand extends QuickCommandBase { counter++; } - if ( - args.skipConfirmation === undefined && - Container.config.gitCommands.skipConfirmations.includes(this.label) - ) { - args.skipConfirmation = true; - } - this._initialState = { counter: counter, - skipConfirmation: counter > 0 && args.skipConfirmation, + confirm: args.confirm, ...args.state }; } @@ -94,10 +87,7 @@ export class FetchGitCommand extends QuickCommandBase { } } - if (state.skipConfirmation) { - state.flags = []; - } - else { + if (this.confirm(state.confirm)) { const step = this.createConfirmStep>( `Confirm ${this.title}${Strings.pad(GlyphChars.Dot, 2, 2)}${ state.repos.length === 1 @@ -159,6 +149,9 @@ export class FetchGitCommand extends QuickCommandBase { state.flags = selection[0].item; } + else { + state.flags = []; + } this.execute(state as State); break; diff --git a/src/commands/git/merge.ts b/src/commands/git/merge.ts index d5984cd..f7f5151 100644 --- a/src/commands/git/merge.ts +++ b/src/commands/git/merge.ts @@ -24,7 +24,7 @@ interface State { export class MergeGitCommand extends QuickCommandBase { constructor() { - super('merge', 'Merge', { description: 'via Terminal' }); + super('merge', 'merge', 'Merge', false, { description: 'via Terminal' }); } execute(state: State) { diff --git a/src/commands/git/pull.ts b/src/commands/git/pull.ts index 34ecaa2..a1345a7 100644 --- a/src/commands/git/pull.ts +++ b/src/commands/git/pull.ts @@ -12,16 +12,16 @@ interface State { flags: string[]; } -export interface CommandArgs { +export interface PullGitCommandArgs { readonly command: 'pull'; state?: Partial; - skipConfirmation?: boolean; + confirm?: boolean; } export class PullGitCommand extends QuickCommandBase { - constructor(args?: CommandArgs) { - super('pull', 'Pull'); + constructor(args?: PullGitCommandArgs) { + super('pull', 'pull', 'Pull'); if (args === undefined || args.state === undefined) return; @@ -30,16 +30,9 @@ export class PullGitCommand extends QuickCommandBase { counter++; } - if ( - args.skipConfirmation === undefined && - Container.config.gitCommands.skipConfirmations.includes(this.label) - ) { - args.skipConfirmation = true; - } - this._initialState = { counter: counter, - skipConfirmation: counter > 0 && args.skipConfirmation, + confirm: args.confirm, ...args.state }; } @@ -91,44 +84,51 @@ export class PullGitCommand extends QuickCommandBase { } } - const step = this.createConfirmStep>( - `Confirm ${this.title}${Strings.pad(GlyphChars.Dot, 2, 2)}${ - state.repos.length === 1 ? state.repos[0].formattedName : `${state.repos.length} repositories` - }`, - [ - { - label: this.title, - description: '', - detail: `Will pull ${ - state.repos.length === 1 - ? state.repos[0].formattedName - : `${state.repos.length} repositories` - }`, - item: [] - }, - { - label: `${this.title} with Rebase`, - description: '--rebase', - detail: `Will pull with rebase ${ - state.repos.length === 1 - ? state.repos[0].formattedName - : `${state.repos.length} repositories` - }`, - item: ['--rebase'] + if (this.confirm(state.confirm)) { + const step = this.createConfirmStep>( + `Confirm ${this.title}${Strings.pad(GlyphChars.Dot, 2, 2)}${ + state.repos.length === 1 + ? state.repos[0].formattedName + : `${state.repos.length} repositories` + }`, + [ + { + label: this.title, + description: '', + detail: `Will pull ${ + state.repos.length === 1 + ? state.repos[0].formattedName + : `${state.repos.length} repositories` + }`, + item: [] + }, + { + label: `${this.title} with Rebase`, + description: '--rebase', + detail: `Will pull with rebase ${ + state.repos.length === 1 + ? state.repos[0].formattedName + : `${state.repos.length} repositories` + }`, + item: ['--rebase'] + } + ] + ); + const selection = yield step; + + if (!this.canMoveNext(step, state, selection)) { + if (oneRepo) { + break; } - ] - ); - const selection = yield step; - if (!this.canMoveNext(step, state, selection)) { - if (oneRepo) { - break; + continue; } - continue; + state.flags = selection[0].item; + } + else { + state.flags = []; } - - state.flags = selection[0].item; this.execute(state as State); break; diff --git a/src/commands/git/push.ts b/src/commands/git/push.ts index f090ae9..43c8168 100644 --- a/src/commands/git/push.ts +++ b/src/commands/git/push.ts @@ -12,16 +12,16 @@ interface State { flags: string[]; } -export interface CommandArgs { +export interface PushGitCommandArgs { readonly command: 'push'; state?: Partial; - skipConfirmation?: boolean; + confirm?: boolean; } export class PushGitCommand extends QuickCommandBase { - constructor(args?: CommandArgs) { - super('push', 'Push'); + constructor(args?: PushGitCommandArgs) { + super('push', 'push', 'Push'); if (args === undefined || args.state === undefined) return; @@ -30,16 +30,9 @@ export class PushGitCommand extends QuickCommandBase { counter++; } - if ( - args.skipConfirmation === undefined && - Container.config.gitCommands.skipConfirmations.includes(this.label) - ) { - args.skipConfirmation = true; - } - this._initialState = { counter: counter, - skipConfirmation: counter > 0 && args.skipConfirmation, + confirm: args.confirm, ...args.state }; } @@ -91,10 +84,7 @@ export class PushGitCommand extends QuickCommandBase { } } - if (state.skipConfirmation) { - state.flags = []; - } - else { + if (this.confirm(state.confirm)) { const step = this.createConfirmStep( `Confirm ${this.title}${Strings.pad(GlyphChars.Dot, 2, 2)}${ state.repos.length === 1 @@ -136,6 +126,9 @@ export class PushGitCommand extends QuickCommandBase { state.flags = selection[0].item; } + else { + state.flags = []; + } this.execute(state as State); break; diff --git a/src/commands/git/rebase.ts b/src/commands/git/rebase.ts index 4a23639..032e880 100644 --- a/src/commands/git/rebase.ts +++ b/src/commands/git/rebase.ts @@ -24,7 +24,7 @@ interface State { export class RebaseGitCommand extends QuickCommandBase { constructor() { - super('rebase', 'Rebase', { description: 'via Terminal' }); + super('rebase', 'rebase', 'Rebase', false, { description: 'via Terminal' }); } execute(state: State) { diff --git a/src/commands/git/stash.ts b/src/commands/git/stash.ts index 4e6433a..f5bda5c 100644 --- a/src/commands/git/stash.ts +++ b/src/commands/git/stash.ts @@ -1,5 +1,5 @@ 'use strict'; -import { QuickPickItem, Uri, window } from 'vscode'; +import { QuickInputButtons, QuickPickItem, Uri, window } from 'vscode'; import { Container } from '../../container'; import { GitStashCommit, GitUri, Repository } from '../../git/gitService'; import { BreakQuickCommand, QuickCommandBase, QuickInputStep, QuickPickStep, StepState } from '../quickCommand'; @@ -45,18 +45,20 @@ interface PushState { } type State = ApplyState | DropState | PopState | PushState; -type StashStepState = Partial & { counter: number; repo: Repository; skipConfirmation?: boolean }; +type StashStepState = StepState & { repo: Repository }; -export interface CommandArgs { +export interface StashGitCommandArgs { readonly command: 'stash'; state?: Partial; - skipConfirmation?: boolean; + confirm?: boolean; } export class StashGitCommand extends QuickCommandBase { - constructor(args?: CommandArgs) { - super('stash', 'Stash'); + private _subcommand: string | undefined; + + constructor(args?: StashGitCommandArgs) { + super('stash', 'stash', 'Stash'); if (args === undefined || args.state === undefined) return; @@ -86,20 +88,21 @@ export class StashGitCommand extends QuickCommandBase { break; } - if ( - args.skipConfirmation === undefined && - Container.config.gitCommands.skipConfirmations.includes(`${this.label}-${args.state.subcommand}`) - ) { - args.skipConfirmation = true; - } - this._initialState = { counter: counter, - skipConfirmation: counter > 0 && args.skipConfirmation, + confirm: args.confirm, ...args.state }; } + get canSkipConfirm(): boolean { + return this._subcommand === 'drop' ? false : super.canSkipConfirm; + } + + get confirmationKey() { + return this._subcommand === undefined ? undefined : `${super.confirmationKey}-${this._subcommand}`; + } + protected async *steps(): AsyncIterableIterator { const state: StepState = this._initialState === undefined ? { counter: 0 } : this._initialState; let oneRepo = false; @@ -107,6 +110,8 @@ export class StashGitCommand extends QuickCommandBase { while (true) { try { if (state.subcommand === undefined || state.counter < 1) { + this._subcommand = undefined; + const step = this.createPickStep>({ title: this.title, placeholder: `Choose a ${this.label} command`, @@ -131,7 +136,8 @@ export class StashGitCommand extends QuickCommandBase { picked: state.subcommand === 'push', item: 'push' } - ] + ], + buttons: [QuickInputButtons.Back] }); const selection = yield step; @@ -142,6 +148,8 @@ export class StashGitCommand extends QuickCommandBase { state.subcommand = selection[0].item; } + this._subcommand = state.subcommand; + if (state.repo === undefined || state.counter < 2) { const repos = [...(await Container.git.getOrderedRepositories())]; @@ -289,10 +297,7 @@ export class StashGitCommand extends QuickCommandBase { state.stash = selection[0].item; } - if (state.skipConfirmation) { - state.flags = []; - } - else { + if (this.confirm(state.confirm)) { const message = state.stash.message.length > 80 ? `${state.stash.message.substring(0, 80)}${GlyphChars.Ellipsis}` @@ -344,6 +349,9 @@ export class StashGitCommand extends QuickCommandBase { state.subcommand = selection[0].command; state.flags = selection[0].item; } + else { + state.flags = []; + } void Container.git.stashApply(state.repo.path, state.stash!.stashName, state.subcommand === 'pop'); @@ -380,7 +388,7 @@ export class StashGitCommand extends QuickCommandBase { } ) ) - ] + ] }); const selection = yield step; @@ -391,10 +399,7 @@ export class StashGitCommand extends QuickCommandBase { state.stash = selection[0].item; } - if (state.skipConfirmation) { - state.flags = []; - } - else { + // if (this.confirm(state.confirm)) { const message = state.stash.message.length > 80 ? `${state.stash.message.substring(0, 80)}${GlyphChars.Ellipsis}` @@ -418,7 +423,7 @@ export class StashGitCommand extends QuickCommandBase { if (!this.canMoveNext(step, state, selection)) { break; } - } + // } void Container.git.stashDelete(state.repo.path, state.stash.stashName); @@ -448,10 +453,7 @@ export class StashGitCommand extends QuickCommandBase { state.message = value; } - if (state.skipConfirmation) { - state.flags = []; - } - else { + if (this.confirm(state.confirm)) { const step = this.createConfirmStep>( `Confirm ${this.title} ${state.subcommand}${Strings.pad(GlyphChars.Dot, 2, 2)}${ state.repo.formattedName @@ -499,6 +501,9 @@ export class StashGitCommand extends QuickCommandBase { state.flags = selection[0].item; } + else { + state.flags = []; + } void Container.git.stashSave(state.repo.path, state.message, state.uris, { includeUntracked: state.flags.includes('--include-untracked'), diff --git a/src/commands/gitCommands.ts b/src/commands/gitCommands.ts index e797c4e..5d93d4d 100644 --- a/src/commands/gitCommands.ts +++ b/src/commands/gitCommands.ts @@ -1,26 +1,28 @@ 'use strict'; -import { Disposable, InputBox, QuickInputButtons, QuickPick, QuickPickItem, window } from 'vscode'; +import { Disposable, InputBox, QuickInputButton, QuickInputButtons, QuickPick, QuickPickItem, window } from 'vscode'; import { command, Command, Commands } from './common'; import { log } from '../system'; import { isQuickInputStep, isQuickPickStep, QuickCommandBase, QuickInputStep, QuickPickStep } from './quickCommand'; import { Directive, DirectiveQuickPickItem } from '../quickpicks'; -import { CommandArgs as CheckoutCommandArgs, CheckoutGitCommand } from './git/checkout'; +import { CheckoutGitCommand, CheckoutGitCommandArgs } from './git/checkout'; import { CherryPickGitCommand } from './git/cherry-pick'; -import { CommandArgs as FetchCommandArgs, FetchGitCommand } from './git/fetch'; +import { FetchGitCommand, FetchGitCommandArgs } from './git/fetch'; import { MergeGitCommand } from './git/merge'; -import { CommandArgs as PullCommandArgs, PullGitCommand } from './git/pull'; -import { CommandArgs as PushCommandArgs, PushGitCommand } from './git/push'; +import { PullGitCommand, PullGitCommandArgs } from './git/pull'; +import { PushGitCommand, PushGitCommandArgs } from './git/push'; import { RebaseGitCommand } from './git/rebase'; -import { CommandArgs as StashCommandArgs, StashGitCommand } from './git/stash'; +import { StashGitCommand, StashGitCommandArgs } from './git/stash'; +import { Container } from '../container'; +import { configuration } from '../configuration'; const sanitizeLabel = /\$\(.+?\)|\W/g; export type GitCommandsCommandArgs = - | CheckoutCommandArgs - | FetchCommandArgs - | PullCommandArgs - | PushCommandArgs - | StashCommandArgs; + | CheckoutGitCommandArgs + | FetchGitCommandArgs + | PullGitCommandArgs + | PushGitCommandArgs + | StashGitCommandArgs; class PickCommandStep implements QuickPickStep { readonly buttons = []; @@ -65,6 +67,32 @@ class PickCommandStep implements QuickPickStep { @command() export class GitCommandsCommand extends Command { + private readonly GitQuickInputButtons = class { + static readonly WillConfirm: QuickInputButton = { + iconPath: { + dark: Container.context.asAbsolutePath('images/dark/icon-check.svg') as any, + light: Container.context.asAbsolutePath('images/light/icon-check.svg') as any + }, + tooltip: 'Will ask for confirmation at the end (click to toggle)' + }; + + static readonly WillConfirmForced: QuickInputButton = { + iconPath: { + dark: Container.context.asAbsolutePath('images/dark/icon-check.svg') as any, + light: Container.context.asAbsolutePath('images/light/icon-check.svg') as any + }, + tooltip: 'Will ask for confirmation at the end (cannot be changed)' + }; + + static readonly WillSkipConfirm: QuickInputButton = { + iconPath: { + dark: Container.context.asAbsolutePath('images/dark/icon-no-check.svg') as any, + light: Container.context.asAbsolutePath('images/light/icon-no-check.svg') as any + }, + tooltip: 'Will NOT ask for confirmation at the end (click to toggle)' + }; + }; + constructor() { super(Commands.GitCommands); } @@ -123,11 +151,21 @@ export class GitCommandsCommand extends Command { return; } - const step = commandsStep.command && commandsStep.command.value; - if (step === undefined || !isQuickInputStep(step) || step.onDidClickButton === undefined) + if (e === this.GitQuickInputButtons.WillConfirmForced) return; + if ( + e === this.GitQuickInputButtons.WillConfirm || + e === this.GitQuickInputButtons.WillSkipConfirm + ) { + await this.toggleConfirmation(input, commandsStep.command); + return; + } - step.onDidClickButton(input, e); + const step = commandsStep.command && commandsStep.command.value; + if (step !== undefined && isQuickInputStep(step) && step.onDidClickButton !== undefined) { + step.onDidClickButton(input, e); + input.buttons = this.getButtons(step, commandsStep.command); + } }), input.onDidChangeValue(async e => { if (step.validate === undefined) return; @@ -140,7 +178,7 @@ export class GitCommandsCommand extends Command { }) ); - input.buttons = step.buttons || [QuickInputButtons.Back]; + input.buttons = this.getButtons(step, commandsStep.command); input.title = step.title; input.placeholder = step.placeholder; if (step.value !== undefined) { @@ -184,10 +222,21 @@ export class GitCommandsCommand extends Command { return; } - const step = commandsStep.command && commandsStep.command.value; - if (step === undefined || !isQuickPickStep(step) || step.onDidClickButton === undefined) return; + if (e === this.GitQuickInputButtons.WillConfirmForced) return; + if ( + e === this.GitQuickInputButtons.WillConfirm || + e === this.GitQuickInputButtons.WillSkipConfirm + ) { + await this.toggleConfirmation(quickpick, commandsStep.command); - step.onDidClickButton(quickpick, e); + return; + } + + const step = commandsStep.command && commandsStep.command.value; + if (step !== undefined && isQuickPickStep(step) && step.onDidClickButton !== undefined) { + step.onDidClickButton(quickpick, e); + quickpick.buttons = this.getButtons(step, commandsStep.command); + } }), quickpick.onDidChangeValue(async e => { if (!overrideItems) { @@ -308,7 +357,6 @@ export class GitCommandsCommand extends Command { }) ); - quickpick.buttons = step.buttons || [QuickInputButtons.Back]; quickpick.title = step.title; quickpick.placeholder = step.placeholder; quickpick.matchOnDescription = Boolean(step.matchOnDescription); @@ -330,6 +378,9 @@ export class GitCommandsCommand extends Command { commandsStep.command = undefined; } + // Needs to be after we reset the command + quickpick.buttons = this.getButtons(step, commandsStep.command); + if (step.value !== undefined) { quickpick.value = step.value; } @@ -343,6 +394,31 @@ export class GitCommandsCommand extends Command { } } + private getButtons(step: QuickInputStep | QuickPickStep, command?: QuickCommandBase) { + if (command === undefined) return []; + + if (step.buttons !== undefined) return step.buttons; + + const buttons = [QuickInputButtons.Back]; + + if (step.additionalButtons !== undefined) { + buttons.push(...step.additionalButtons); + } + + if (command.canSkipConfirm) { + if (command.confirmationKey === undefined) return buttons; + + buttons.push( + command.confirm() ? this.GitQuickInputButtons.WillConfirm : this.GitQuickInputButtons.WillSkipConfirm + ); + } + else { + buttons.push(this.GitQuickInputButtons.WillConfirmForced); + } + + return buttons; + } + private async nextStep( quickInput: QuickPick | InputBox, command: QuickCommandBase, @@ -357,4 +433,29 @@ export class GitCommandsCommand extends Command { quickInput.value = ''; return next.value; } + + private async toggleConfirmation( + input: InputBox | QuickPick, + command: QuickCommandBase | undefined + ) { + if (command === undefined || command.confirmationKey === undefined) return; + + const section = configuration.name('gitCommands')('skipConfirmations').value; + const skipConfirmations = configuration.get(section) || []; + + const index = skipConfirmations.indexOf(command.confirmationKey); + if (index !== -1) { + skipConfirmations.splice(index, 1); + } + else { + skipConfirmations.push(command.confirmationKey); + } + + void (await configuration.updateEffective( + configuration.name('gitCommands')('skipConfirmations').value, + skipConfirmations + )); + + input.buttons = this.getButtons(command.value!, command); + } } diff --git a/src/commands/quickCommand.ts b/src/commands/quickCommand.ts index 0e13f08..ddd1f8e 100644 --- a/src/commands/quickCommand.ts +++ b/src/commands/quickCommand.ts @@ -1,7 +1,8 @@ 'use strict'; -import { InputBox, QuickInputButton, QuickPick, QuickPickItem } from 'vscode'; +import { InputBox, QuickInputButton, QuickInputButtons, QuickPick, QuickPickItem } from 'vscode'; import { Promises } from '../system'; import { Directive, DirectiveQuickPickItem } from '../quickpicks'; +import { Container } from '../container'; export * from './quickCommand.helpers'; @@ -12,6 +13,7 @@ export class BreakQuickCommand extends Error { } export interface QuickInputStep { + additionalButtons?: QuickInputButton[]; buttons?: QuickInputButton[]; placeholder?: string; title?: string; @@ -26,6 +28,7 @@ export function isQuickInputStep(item: QuickPickStep | QuickInputStep): item is } export interface QuickPickStep { + additionalButtons?: QuickInputButton[]; buttons?: QuickInputButton[]; selectedItems?: QuickPickItem[]; items: (DirectiveQuickPickItem | T)[] | DirectiveQuickPickItem[]; @@ -46,7 +49,7 @@ export function isQuickPickStep(item: QuickPickStep | QuickInputStep): item is Q return (item as QuickPickStep).items !== undefined; } -export type StepState = Partial & { counter: number; skipConfirmation?: boolean }; +export type StepState = Partial & { counter: number; confirm?: boolean }; export abstract class QuickCommandBase implements QuickPickItem { static is(item: QuickPickItem): item is QuickCommandBase { @@ -56,12 +59,16 @@ export abstract class QuickCommandBase implements QuickPickItem { readonly description?: string; readonly detail?: string; + protected _initialState?: StepState; + private _current: QuickPickStep | QuickInputStep | undefined; private _stepsIterator: AsyncIterableIterator | undefined; constructor( + public readonly key: string, public readonly label: string, public readonly title: string, + private readonly _canSkipConfirm: boolean = true, options: { description?: string; detail?: string; @@ -71,6 +78,14 @@ export abstract class QuickCommandBase implements QuickPickItem { this.detail = options.detail; } + get canSkipConfirm(): boolean { + return this._canSkipConfirm; + } + + get confirmationKey(): string | undefined { + return this.key; + } + private _picked: boolean = false; get picked() { return this._picked; @@ -79,7 +94,13 @@ export abstract class QuickCommandBase implements QuickPickItem { this._picked = value; } - protected _initialState?: StepState; + confirm(override?: boolean) { + if (!this.canSkipConfirm || this.confirmationKey === undefined) return true; + + return override !== undefined + ? override + : !Container.config.gitCommands.skipConfirmations.includes(this.confirmationKey); + } protected abstract steps(): AsyncIterableIterator; @@ -122,7 +143,8 @@ export abstract class QuickCommandBase implements QuickPickItem { placeholder: placeholder || `Confirm ${this.title}`, title: title, items: [...confirmations, cancel || DirectiveQuickPickItem.create(Directive.Cancel)], - selectedItems: [confirmations[0]] + selectedItems: [confirmations[0]], + buttons: [QuickInputButtons.Back] }); }