diff --git a/extensions/extension-editing/src/quickInputExploration.ts b/extensions/extension-editing/src/quickInputExploration.ts new file mode 100644 index 0000000000000..1153d81353884 --- /dev/null +++ b/extensions/extension-editing/src/quickInputExploration.ts @@ -0,0 +1,1325 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { QuickInputToolbarItem3, ExtensionContext, commands, QuickPickItem, window, QuickInputSession9, QuickInput3, Disposable, CancellationToken, QuickInput7, QuickInput6, QuickInput11, QuickInputToolbarItem11, QuickInputCommand13, QuickInput13 } from 'vscode'; + +export function activate(context: ExtensionContext) { + context.subscriptions.push(commands.registerCommand('foobar', async () => { + const inputs1 = await collectInputs1(); + const inputs2 = await collectInputs2(); + const inputs3 = await collectInputs3(); + const inputs4 = await collectInputs4(); + const inputs5 = await collectInputs5(); + const inputs6 = await collectInputs6(); + const inputs7 = await collectInputs7(); + const inputs8 = await collectInputs8(); + const inputs9 = await collectInputs9(); + const inputs10 = await collectInputs10(); + const inputs11 = await collectInputs11(); + const inputs12 = await collectInputs12(); + const inputs13 = await collectInputs13(); + })); +} + +const resourceGroups: QuickPickItem[] = ['vscode-data-function', 'vscode-website-microservices', 'vscode-website-monitor', 'vscode-website-preview', 'vscode-website-prod'] + .map(label => ({ label })); + + + + +// #region Take 1 -------------------------------------------------------------------------------- + +async function collectInputs1() { + return window.multiStepInput(async (input, token) => { + const resourceGroup = await input.showQuickPick(resourceGroups, { placeHolder: 'Pick a resource group' }); + if (!resourceGroup || token.isCancellationRequested) { + return undefined; + } + const name = await input.showInputBox({ + prompt: 'Choose a unique name', + validateInput: value => validateNameIsUnique(value) + }); + if (!name || token.isCancellationRequested) { + return undefined; + } + const runtimes = await getAvailableRuntimes(resourceGroup, token); + const runtime = await input.showQuickPick(runtimes, { placeHolder: 'Pick a runtime' }); + return { resourceGroup, name, runtime }; + }); +} + +// #endregion + +// #region Take 2 -------------------------------------------------------------------------------- + +async function collectInputs2() { + const input = window.createQuickInput2(); + try { + const resourceGroup = await input.showQuickPick(resourceGroups, { placeHolder: 'Pick a resource group' }); + if (!resourceGroup || input.token.isCancellationRequested) { + return undefined; + } + const name = await input.showInputBox({ + prompt: 'Choose a unique name', + validateInput: value => validateNameIsUnique(value) + }); + if (!name || input.token.isCancellationRequested) { + return undefined; + } + const runtimes = await getAvailableRuntimes(resourceGroup, input.token); + const runtime = await input.showQuickPick(runtimes, { placeHolder: 'Pick a runtime' }); + return { resourceGroup, name, runtime }; + } finally { + input.dispose(); + } +} + +// #endregion + +// #region Take 3 -------------------------------------------------------------------------------- + +async function collectInputs3() { + const input = window.createQuickInput3(); + + const resourceGroup = await showQuickPick3({ + input, + placeHolder: 'Pick a resource group', + items: resourceGroups + }); + if (!resourceGroup) { + return undefined; + } + + const name = await showInputBox3({ + input, + prompt: 'Choose a unique name', + validate: validateNameIsUnique + }); + if (name === undefined) { + return undefined; + } + + const runtimes = await getAvailableRuntimes(resourceGroup, null /* token */); + const runtime = await showQuickPick3({ + input, + placeHolder: 'Pick a runtime', + items: runtimes + }); + if (!runtime) { + return undefined; + } + + input.hide(); + return { resourceGroup, name, runtime }; +} + +interface QuickPickParameters3 { + input: QuickInput3; + items: QuickPickItem[]; + placeHolder: string; +} + +async function showQuickPick3({ input, items, placeHolder }: QuickPickParameters3) { + const disposables: Disposable[] = []; + try { + return await new Promise(resolve => { + const { inputBox, message, list } = input; + inputBox.visible = true; + inputBox.placeholder = placeHolder; + inputBox.text = ''; + message.visible = false; + list.visible = true; + list.items = items; + disposables.push( + list.onDidSelectItem(item => resolve(item)), + input.onHide(() => resolve(undefined)) + ); + input.show(); + }); + } finally { + disposables.forEach(d => d.dispose()); + } +} + +interface InputBoxParameters3 { + input: QuickInput3; + prompt: string; + validate: (value: string) => Promise; +} + +async function showInputBox3({ input, prompt, validate }: InputBoxParameters3) { + const disposables: Disposable[] = []; + try { + return await new Promise(resolve => { + const { inputBox, message, list } = input; + inputBox.visible = true; + inputBox.placeholder = undefined; + inputBox.text = ''; + message.visible = true; + message.text = prompt; + message.severity = 0; + list.visible = true; + list.items = undefined; + let validating = validate(''); + disposables.push( + inputBox.onDidAccept(async text => { + if (!(await validate(text))) { + resolve(text); + } + }), + inputBox.onDidTextChange(async text => { + const current = validate(text); + validating = current; + const validationMessage = await current; + if (current === validating) { + if (validationMessage) { + message.text = validationMessage; + message.severity = 2; + } else { + message.text = prompt; + message.severity = 0; + } + } + }), + input.onHide(() => resolve(undefined)) + ); + input.show(); + }); + } finally { + disposables.forEach(d => d.dispose()); + } +} + +// #endregion + +// #region Take 4 -------------------------------------------------------------------------------- + +async function collectInputs4() { + return window.multiStepInput4('resourceGroup', { + resourceGroup: { + kind: 'singlePick', + items: resourceGroups, + placeHolder: 'Pick a resource group', + nextStep: 'name' + }, + name: { + kind: 'textInput', + prompt: 'Choose a unique name', + validateInput: value => validateNameIsUnique(value), + nextStep: 'runtime' + }, + runtime: { + kind: 'singlePick', + items: await getAvailableRuntimes(null /* inputs.resourceGroup */, null /* token */), + placeHolder: 'Pick a runtime' + }, + }); +} + +// #endregion + +// #region Take 5 -------------------------------------------------------------------------------- + +async function collectInputs5() { + return window.multiStepInput5('resourceGroup', { + resourceGroup: async input => ({ + value: input.showQuickPick(resourceGroups, { placeHolder: 'Pick a resource group' }), + next: 'name' + }), + name: async input => ({ + value: input.showInputBox({ + prompt: 'Choose a unique name', + validateInput: value => validateNameIsUnique(value) + }), + next: 'runtime' + }), + runtime: async (input, values) => { + const runtimes = await getAvailableRuntimes(values.resourceGroup, input.token); + return { value: input.showQuickPick(runtimes, { placeHolder: 'Pick a runtime' }) }; + } + }); +} + +// #endregion + +// #region Take 6 -------------------------------------------------------------------------------- + +async function collectInputs6() { + const result: Partial = {}; + + const resourceGroup = await window.showQuickPick(resourceGroups, { + placeHolder: 'Pick a resource group', + next: chooseName + }); + + async function chooseName(input: QuickInput6, resourceGroup: QuickPickItem) { + result.resourceGroup = resourceGroup; + const name = await input.showInputBox({ + prompt: 'Choose a unique name', + validateInput: value => validateNameIsUnique(value), + next: chooseRuntime + }); + } + + async function chooseRuntime(input: QuickInput6, name: string) { + result.name = name; + const runtimes = await getAvailableRuntimes(result.resourceGroup, input.token); + const runtime = await input.showQuickPick(runtimes, { placeHolder: 'Pick a runtime' }); + result.runtime = runtime; + } + + return result; +} + +// #endregion + +// #region Take 7 -------------------------------------------------------------------------------- + +async function collectInputs7() { + let step = 1; + const result: Partial = {}; + await window.multiStepInput7({ + next: async input => { + await collectInput7(input, result, ++step); + }, + previous: async input => { + await collectInput7(input, result, --step); + }, + cancel: async input => { + input.close(); + }, + }); + return result; +} + +async function collectInput7(input: QuickInput7, result: Partial, step: number) { + switch (step) { + case 1: + result.resourceGroup = await input.showQuickPick(resourceGroups, { placeHolder: 'Pick a resource group' }); + break; + case 2: + result.name = await input.showInputBox({ + prompt: 'Choose a unique name', + validateInput: value => validateNameIsUnique(value) + }); + break; + case 3: + const runtimes = await getAvailableRuntimes(result.resourceGroup, input.token); + result.runtime = await input.showQuickPick(runtimes, { placeHolder: 'Pick a runtime' }); + break; + case 4: + input.close(); + break; + } +} + +// #endregion + +// #region Take 8 -------------------------------------------------------------------------------- + +async function collectInputs8() { + const session = window.createQuickInputSession(); + try { + const resourceGroup = await window.showQuickPick(resourceGroups, { placeHolder: 'Pick a resource group', session }); + if (!resourceGroup || session.token.isCancellationRequested) { + return undefined; + } + const name = await window.showInputBox({ + prompt: 'Choose a unique name', + validateInput: value => validateNameIsUnique(value), + session + }); + if (!name || session.token.isCancellationRequested) { + return undefined; + } + const runtimes = await getAvailableRuntimes(resourceGroup, session.token); + const runtime = await window.showQuickPick(runtimes, { placeHolder: 'Pick a runtime', session }); + return { resourceGroup, name, runtime }; + } finally { + session.dispose(); + } +} + +// #endregion + +// #region Take 9 -------------------------------------------------------------------------------- + +async function collectInputs9() { + // How to surface 'Back', 'Create Resource Group' as result? + + const result: Partial = {}; + + window.multiStepInput9(async session => { + result.resourceGroup = await window.showQuickPick(resourceGroups, { + placeHolder: 'Pick a resource group', + session + }); + return chooseName; + }); + + async function chooseName(session: QuickInputSession9) { + result.name = await window.showInputBox({ + prompt: 'Choose a unique name', + validateInput: value => validateNameIsUnique(value), + session + }); + return chooseRuntime; + } + + async function chooseRuntime(session: QuickInputSession9) { + const runtimes = await getAvailableRuntimes(result.resourceGroup, session.token); + const runtime = await window.showQuickPick(runtimes, { placeHolder: 'Pick a runtime', session }); + result.runtime = runtime; + } + + return result; +} + +// #endregion + +// #region Take 10 -------------------------------------------------------------------------------- + +interface Result10 { + resourceGroup: QuickPickItem | string; + name: string; + runtime: QuickPickItem; +} + +const createResourceGroupItem10 = { iconPath: 'createResourceGroup.svg' }; + +async function collectInputs10() { + const result: Partial = {}; + + const multiStep = createMultiStepInput10([pickResourceGroup]); + await stepThrough10(multiStep); + + async function pickResourceGroup() { + return showQuickPick10({ + multiStep, + placeHolder: 'Pick a resource group', + items: resourceGroups, + toolbarItems: [createResourceGroupItem10], + triggerToolbarItem: item => inputResourceGroupName, + pick: item => { + result.resourceGroup = item; + return inputName; + } + }); + } + + async function inputResourceGroupName() { + return showInputBox10({ + multiStep, + prompt: 'Choose a unique name for the resource group', + validate: validateNameIsUnique, + accept: text => { + result.resourceGroup = text; + return inputName; + }, + shouldResume: suspend + }); + } + + async function inputName() { + return showInputBox10({ + multiStep, + prompt: 'Choose a unique name for the application service', + validate: validateNameIsUnique, + accept: text => { + result.name = text; + return pickRuntime; + }, + shouldResume: suspend + }); + } + + async function pickRuntime() { + const runtimes = await getAvailableRuntimes(result.resourceGroup, null /* token */); + return showQuickPick10({ + multiStep, + placeHolder: 'Pick a runtime', + items: runtimes, + pick: item => { + result.runtime = item; + }, + shouldResume: suspend + }); + } + + function suspend() { + // Could show a notification with the option to resume. + return new Promise((resolve, reject) => { + + }); + } + + return result; +} + +interface MultiStepInput10 { + input: QuickInput3; + steps: InputStep10[]; +} + +function createMultiStepInput10(steps: InputStep10[]): MultiStepInput10 { + return { + input: window.createQuickInput3(), + steps + }; +} + +type InputStep10 = (() => Thenable) | void; + +async function stepThrough10(multiStep: MultiStepInput10) { + let step = multiStep.steps.pop(); + while (step) { + multiStep.steps.push(step); + multiStep.input.enabled = false; + multiStep.input.busy = true; + try { + step = await step(); + } catch (e) { + if (e === 'back') { + multiStep.steps.pop(); + step = multiStep.steps.pop(); + } if (e === 'resume') { + multiStep.steps.pop(); + } else { + throw e; + } + } + } + multiStep.input.hide(); +} + +interface QuickPickParameters10 { + multiStep: MultiStepInput10; + items: QuickPickItem[]; + placeHolder: string; + toolbarItems?: QuickInputToolbarItem3[]; // TODO + triggerToolbarItem?: (item: QuickInputToolbarItem3) => InputStep10; + pick: (item: QuickPickItem) => InputStep10; + shouldResume?: () => Thenable; +} + +const backItem10: QuickInputToolbarItem3 = { iconPath: 'back.svg' }; + +async function showQuickPick10({ multiStep, items, placeHolder, pick, shouldResume }: QuickPickParameters10) { + const disposables: Disposable[] = []; + try { + return await new Promise((resolve, reject) => { + const { input } = multiStep; + const { inputBox, toolbar, message, list } = input; + inputBox.visible = true; + inputBox.placeholder = placeHolder; + inputBox.text = ''; + message.visible = false; + list.visible = true; + list.items = items; + toolbar.visible = multiStep.steps.length > 1; + toolbar.toolbarItems = [backItem10]; + toolbar.onDidTriggerToolbarItem(item => { + if (item === backItem10) { + reject('back'); + } + }); + disposables.push( + list.onDidSelectItem(item => resolve(pick(item))), + input.onHide(() => { + if (shouldResume) { + resolve(shouldResume().then(resume => { + if (resume) { + // tslint:disable-next-line:no-string-throw + throw 'resume'; + } + })); + } else { + resolve(); + } + }) + ); + input.enabled = true; + input.busy = false; + input.show(); + }); + } finally { + disposables.forEach(d => d.dispose()); + } +} + +interface InputBoxParameters10 { + multiStep: MultiStepInput10; + prompt: string; + validate: (value: string) => Promise; + toolbarItems?: QuickInputToolbarItem3[]; // TODO + triggerToolbarItem?: (item: QuickInputToolbarItem3) => InputStep10; + accept: (text: string) => InputStep10; + shouldResume?: () => Thenable; +} + +async function showInputBox10({ multiStep, prompt, validate, accept, shouldResume }: InputBoxParameters10) { + const disposables: Disposable[] = []; + try { + return await new Promise((resolve, reject) => { + const { input } = multiStep; + const { inputBox, toolbar, message, list } = input; + inputBox.visible = true; + inputBox.placeholder = undefined; + inputBox.text = ''; + message.visible = true; + message.text = prompt; + message.severity = 0; + list.visible = true; + list.items = undefined; + toolbar.visible = multiStep.steps.length > 1; + toolbar.toolbarItems = [backItem10]; + toolbar.onDidTriggerToolbarItem(item => { + if (item === backItem10) { + reject('back'); + } + }); + let validating = validate(''); + disposables.push( + inputBox.onDidAccept(async text => { + if (!(await validate(text))) { + resolve(accept(text)); + } + }), + inputBox.onDidTextChange(async text => { + const current = validate(text); + validating = current; + const validationMessage = await current; + if (current === validating) { + if (validationMessage) { + message.text = validationMessage; + message.severity = 2; + } else { + message.text = prompt; + message.severity = 0; + } + } + }), + input.onHide(() => { + if (shouldResume) { + resolve(shouldResume().then(resume => { + if (resume) { + // tslint:disable-next-line:no-string-throw + throw 'resume'; + } + })); + } else { + resolve(); + } + }) + ); + input.enabled = true; + input.busy = false; + input.show(); + }); + } finally { + disposables.forEach(d => d.dispose()); + } +} + +// #endregion + +// #region Take 11 -------------------------------------------------------------------------------- + +interface Result11 { + resourceGroup: QuickPickItem | string; + name: string; + runtime: QuickPickItem; +} + +async function collectInputs11() { + return (await MultiStepInput11.run(pickResourceGroup11, {} as Partial)) as Result11; +} + +class MyToolbarItem11 implements QuickInputToolbarItem11 { + constructor(public iconPath: string) { } +} + +async function pickResourceGroup11(input: MultiStepInput11, state: Partial) { + const createResourceGroupItem11 = new MyToolbarItem11('createResourceGroup.svg'); + const pick = await input.showQuickPick11({ + placeHolder: 'Pick a resource group', + items: resourceGroups, + toolbarItems: [createResourceGroupItem11] + }); + if (pick instanceof InputFlowAction11) { + return pick; + } + if (pick instanceof MyToolbarItem11) { + return inputResourceGroupName11; + } + state.resourceGroup = pick; + return inputName11; +} + +async function inputResourceGroupName11(input: MultiStepInput11, state: Partial) { + const name = await input.showInputBox11({ + prompt: 'Choose a unique name for the resource group', + validate: validateNameIsUnique + }); + if (name === InputFlowAction11.cancel && await suspend11()) { + return InputFlowAction11.resume; + } + if (name instanceof InputFlowAction11) { + return name; + } + state.resourceGroup = name; + return inputName11; +} + +async function inputName11(input: MultiStepInput11, state: Partial) { + const name = await input.showInputBox11({ + prompt: 'Choose a unique name for the application service', + validate: validateNameIsUnique + }); + if (name === InputFlowAction11.cancel && await suspend11()) { + return InputFlowAction11.resume; + } + if (name instanceof InputFlowAction11) { + return name; + } + state.name = name; + return pickRuntime11; +} + +async function pickRuntime11(input: MultiStepInput11, state: Partial) { + const runtimes = await getAvailableRuntimes(state.resourceGroup, null /* token */); + const runtime = await input.showQuickPick11({ + placeHolder: 'Pick a runtime', + items: runtimes + }); + if (runtime === InputFlowAction11.cancel && await suspend11()) { + return InputFlowAction11.resume; + } + if (runtime instanceof InputFlowAction11) { + return runtime; + } + state.runtime = runtime; +} + +function suspend11() { + // Could show a notification with the option to resume. + return new Promise((resolve, reject) => { + + }); +} + +class InputFlowAction11 { + private constructor() { } + static back = new InputFlowAction11(); + static cancel = new InputFlowAction11(); + static resume = new InputFlowAction11(); +} + +type InputStep11 = (input: MultiStepInput11, state: T) => Thenable | InputFlowAction11 | void>; + +interface QuickPickParameters11 { + items: QuickPickItem[]; + placeHolder: string; + toolbarItems?: QuickInputToolbarItem11[]; // TODO +} + +interface InputBoxParameters11 { + prompt: string; + validate: (value: string) => Promise; + toolbarItems?: QuickInputToolbarItem11[]; // TODO +} + +const backItem11: QuickInputToolbarItem11 = { iconPath: 'back.svg' }; + +class MultiStepInput11 { + + static async run(start: InputStep11, state: T) { + const input = new MultiStepInput11(); + return input.stepThrough11(start, state); + } + + private current?: QuickInput11; + private steps: InputStep11[] = []; + + private async stepThrough11(start: InputStep11, state: T) { + let step: InputStep11 | void = start; + while (step) { + this.steps.push(step); + if (this.current) { + this.current.enabled = false; + this.current.busy = true; + } + let next = await step(this, state); + if (next === 'back') { + this.steps.pop(); + step = this.steps.pop(); + } if (next === 'resume') { + step = this.steps.pop(); + } else { + step = next; + } + } + if (this.current) { + this.current.dispose(); + } + return state; + } + + async showQuickPick11

({ items, placeHolder }: P) { + const disposables: Disposable[] = []; + try { + return await new Promise((resolve, reject) => { + const input = window.createQuickPick11(); + const { inputBox, toolbar, list } = input; + inputBox.placeholder = placeHolder; + list.items = items; + toolbar.toolbarItems = this.steps.length > 1 ? [backItem11] : []; + disposables.push( + input, + toolbar.onDidTriggerToolbarItem(item => { + if (item === backItem11) { + resolve(InputFlowAction11.back); + } + }), + list.onDidSelectItem(item => resolve(item)), + input.onHide(() => resolve(InputFlowAction11.cancel)) + ); + if (this.current) { + this.current.replace(input); + } else { + input.show(); + } + this.current = input; + }); + } finally { + disposables.forEach(d => d.dispose()); + } + } + + async showInputBox11

({ prompt, validate }: P) { + const disposables: Disposable[] = []; + try { + return await new Promise((resolve, reject) => { + const input = window.createInputBox11(); + const { inputBox, toolbar, message } = input; + message.text = prompt; + message.severity = 0; + toolbar.toolbarItems = this.steps.length > 1 ? [backItem11] : []; + let validating = validate(''); + disposables.push( + input, + toolbar.onDidTriggerToolbarItem(item => { + if (item === backItem11) { + resolve(InputFlowAction11.back); + } + }), + inputBox.onDidAccept(async text => { + if (!(await validate(text))) { + resolve(text); + } + }), + inputBox.onDidTextChange(async text => { + const current = validate(text); + validating = current; + const validationMessage = await current; + if (current === validating) { + if (validationMessage) { + message.text = validationMessage; + message.severity = 2; + } else { + message.text = prompt; + message.severity = 0; + } + } + }), + input.onHide(() => resolve(InputFlowAction11.cancel)) + ); + if (this.current) { + this.current.replace(input); + } else { + input.show(); + } + this.current = input; + }); + } finally { + disposables.forEach(d => d.dispose()); + } + } +} + +// #endregion + +// #region Take 12 -------------------------------------------------------------------------------- + +interface Result12 { + resourceGroup: QuickPickItem | string; + name: string; + runtime: QuickPickItem; +} + +async function collectInputs12() { + const result = {} as Partial; + await MultiStepInput12.run(input => pickResourceGroup12(input, {})); + return result; +} + +class MyToolbarItem12 implements QuickInputToolbarItem11 { + constructor(public iconPath: string) { } +} + +async function pickResourceGroup12(input: MultiStepInput12, state: Partial) { + const createResourceGroupItem12 = new MyToolbarItem12('createResourceGroup.svg'); + const pick = await input.showQuickPick12({ + placeHolder: 'Pick a resource group', + items: resourceGroups, + toolbarItems: [createResourceGroupItem12], + shouldResume: shouldResume12 + }); + if (pick instanceof MyToolbarItem12) { + return (input: MultiStepInput12) => inputResourceGroupName12(input, state); + } + state.resourceGroup = pick; + return (input: MultiStepInput12) => inputName12(input, state); +} + +async function inputResourceGroupName12(input: MultiStepInput12, state: Partial) { + state.resourceGroup = await input.showInputBox12({ + prompt: 'Choose a unique name for the resource group', + validate: validateNameIsUnique, + shouldResume: shouldResume12 + }); + return (input: MultiStepInput12) => inputName12(input, state); +} + +async function inputName12(input: MultiStepInput12, state: Partial) { + state.name = await input.showInputBox12({ + prompt: 'Choose a unique name for the application service', + validate: validateNameIsUnique, + shouldResume: shouldResume12 + }); + return (input: MultiStepInput12) => pickRuntime12(input, state); +} + +async function pickRuntime12(input: MultiStepInput12, state: Partial) { + const runtimes = await getAvailableRuntimes(state.resourceGroup, null /* token */); + state.runtime = await input.showQuickPick12({ + placeHolder: 'Pick a runtime', + items: runtimes, + shouldResume: shouldResume12 + }); +} + +function shouldResume12() { + // Could show a notification with the option to resume. + return new Promise((resolve, reject) => { + + }); +} + +class InputFlowAction12 { + private constructor() { } + static back = new InputFlowAction12(); + static cancel = new InputFlowAction12(); + static resume = new InputFlowAction12(); +} + +type InputStep12 = (input: MultiStepInput12) => Thenable; + +interface QuickPickParameters12 { + items: QuickPickItem[]; + placeHolder: string; + toolbarItems?: QuickInputToolbarItem11[]; + shouldResume: () => Thenable; +} + +interface InputBoxParameters12 { + prompt: string; + validate: (value: string) => Promise; + toolbarItems?: QuickInputToolbarItem11[]; + shouldResume: () => Thenable; +} + +const backItem12: QuickInputToolbarItem11 = { iconPath: 'back.svg' }; + +class MultiStepInput12 { + + static async run(start: InputStep12) { + const input = new MultiStepInput12(); + return input.stepThrough12(start); + } + + private current?: QuickInput11; + private steps: InputStep12[] = []; + + private async stepThrough12(start: InputStep12) { + let step: InputStep12 | void = start; + while (step) { + this.steps.push(step); + if (this.current) { + this.current.enabled = false; + this.current.busy = true; + } + try { + step = await step(this); + } catch (err) { + if (err === InputFlowAction12.back) { + this.steps.pop(); + step = this.steps.pop(); + } else if (err === InputFlowAction12.resume) { + step = this.steps.pop(); + } else if (err === InputFlowAction12.cancel) { + step = undefined; + } else { + throw err; + } + } + } + if (this.current) { + this.current.dispose(); + } + } + + async showQuickPick12

({ items, placeHolder, toolbarItems, shouldResume }: P) { + const disposables: Disposable[] = []; + try { + return await new Promise((resolve, reject) => { + const input = window.createQuickPick11(); + const { inputBox, toolbar, list } = input; + inputBox.placeholder = placeHolder; + list.items = items; + toolbar.toolbarItems = [ + ...(this.steps.length > 1 ? [backItem12] : []), + ...(toolbarItems || []) + ]; + disposables.push( + input, + toolbar.onDidTriggerToolbarItem(item => { + if (item === backItem12) { + reject(InputFlowAction12.back); + } + }), + list.onDidSelectItem(item => resolve(item)), + input.onHide(() => { + (async () => { + reject(shouldResume && await shouldResume() ? InputFlowAction12.resume : InputFlowAction12.cancel); + })() + .catch(reject); + }) + ); + if (this.current) { + this.current.replace(input); + } else { + input.show(); + } + this.current = input; + }); + } finally { + disposables.forEach(d => d.dispose()); + } + } + + async showInputBox12

({ prompt, validate, toolbarItems, shouldResume }: P) { + const disposables: Disposable[] = []; + try { + return await new Promise((resolve, reject) => { + const input = window.createInputBox11(); + const { inputBox, toolbar, message } = input; + message.text = prompt; + message.severity = 0; + toolbar.toolbarItems = [ + ...(this.steps.length > 1 ? [backItem12] : []), + ...(toolbarItems || []) + ]; + let validating = validate(''); + disposables.push( + input, + toolbar.onDidTriggerToolbarItem(item => { + if (item === backItem12) { + reject(InputFlowAction12.back); + } + }), + inputBox.onDidAccept(async text => { + if (!(await validate(text))) { + resolve(text); + } + }), + inputBox.onDidTextChange(async text => { + const current = validate(text); + validating = current; + const validationMessage = await current; + if (current === validating) { + if (validationMessage) { + message.text = validationMessage; + message.severity = 2; + } else { + message.text = prompt; + message.severity = 0; + } + } + }), + input.onHide(() => { + (async () => { + reject(shouldResume && await shouldResume() ? InputFlowAction12.resume : InputFlowAction12.cancel); + })() + .catch(reject); + }) + ); + if (this.current) { + this.current.replace(input); + } else { + input.show(); + } + this.current = input; + }); + } finally { + disposables.forEach(d => d.dispose()); + } + } +} + +// #endregion + +// #region Take 13 -------------------------------------------------------------------------------- + +interface Result13 { + resourceGroup: QuickPickItem | string; + name: string; + runtime: QuickPickItem; +} + +async function collectInputs13() { + const result = {} as Partial; + await MultiStepInput13.run(input => pickResourceGroup13(input, {})); + return result; +} + +class MyCommand13 implements QuickInputCommand13 { + constructor(public iconPath: string) { } +} + +async function pickResourceGroup13(input: MultiStepInput13, state: Partial) { + const createResourceGroupItem13 = new MyCommand13('createResourceGroup.svg'); + const pick = await input.showQuickPick13({ + placeHolder: 'Pick a resource group', + items: resourceGroups, + commands: [createResourceGroupItem13], + shouldResume: shouldResume13 + }); + if (pick instanceof MyCommand13) { + return (input: MultiStepInput13) => inputResourceGroupName13(input, state); + } + state.resourceGroup = pick; + return (input: MultiStepInput13) => inputName13(input, state); +} + +async function inputResourceGroupName13(input: MultiStepInput13, state: Partial) { + state.resourceGroup = await input.showInputBox13({ + prompt: 'Choose a unique name for the resource group', + validate: validateNameIsUnique, + shouldResume: shouldResume13 + }); + return (input: MultiStepInput13) => inputName13(input, state); +} + +async function inputName13(input: MultiStepInput13, state: Partial) { + state.name = await input.showInputBox13({ + prompt: 'Choose a unique name for the application service', + validate: validateNameIsUnique, + shouldResume: shouldResume13 + }); + return (input: MultiStepInput13) => pickRuntime13(input, state); +} + +async function pickRuntime13(input: MultiStepInput13, state: Partial) { + const runtimes = await getAvailableRuntimes(state.resourceGroup, null /* token */); + state.runtime = await input.showQuickPick13({ + placeHolder: 'Pick a runtime', + items: runtimes, + shouldResume: shouldResume13 + }); +} + +function shouldResume13() { + // Could show a notification with the option to resume. + return new Promise((resolve, reject) => { + + }); +} + +class InputFlowAction13 { + private constructor() { } + static back = new InputFlowAction13(); + static cancel = new InputFlowAction13(); + static resume = new InputFlowAction13(); +} + +type InputStep13 = (input: MultiStepInput13) => Thenable; + +interface QuickPickParameters13 { + items: QuickPickItem[]; + placeHolder: string; + commands?: QuickInputCommand13[]; + shouldResume: () => Thenable; +} + +interface InputBoxParameters13 { + prompt: string; + validate: (value: string) => Promise; + commands?: QuickInputCommand13[]; + shouldResume: () => Thenable; +} + +const backItem13: QuickInputCommand13 = { iconPath: 'back.svg' }; + +class MultiStepInput13 { + + static async run(start: InputStep13) { + const input = new MultiStepInput13(); + return input.stepThrough13(start); + } + + private current?: QuickInput13; + private steps: InputStep13[] = []; + + private async stepThrough13(start: InputStep13) { + let step: InputStep13 | void = start; + while (step) { + this.steps.push(step); + if (this.current) { + this.current.enabled = false; + this.current.busy = true; + } + try { + step = await step(this); + } catch (err) { + if (err === InputFlowAction13.back) { + this.steps.pop(); + step = this.steps.pop(); + } else if (err === InputFlowAction13.resume) { + step = this.steps.pop(); + } else if (err === InputFlowAction13.cancel) { + step = undefined; + } else { + throw err; + } + } + } + if (this.current) { + this.current.dispose(); + } + } + + async showQuickPick13

({ items, placeHolder, commands, shouldResume }: P) { + const disposables: Disposable[] = []; + try { + return await new Promise((resolve, reject) => { + const input = window.createQuickPick13(); + input.placeholder = placeHolder; + input.items = items; + input.commands = [ + ...(this.steps.length > 1 ? [backItem13] : []), + ...(commands || []) + ]; + disposables.push( + input, + input.onDidTriggerCommand(item => { + if (item === backItem13) { + reject(InputFlowAction13.back); + } + }), + input.onDidSelectItem(item => resolve(item)), + input.onHide(() => { + (async () => { + reject(shouldResume && await shouldResume() ? InputFlowAction13.resume : InputFlowAction13.cancel); + })() + .catch(reject); + }) + ); + if (this.current) { + this.current.hide(); + } + this.current = input; + this.current.show(); + }); + } finally { + disposables.forEach(d => d.dispose()); + } + } + + async showInputBox13

({ prompt, validate, commands, shouldResume }: P) { + const disposables: Disposable[] = []; + try { + return await new Promise((resolve, reject) => { + const input = window.createInputBox13(); + input.prompt = prompt; + input.commands = [ + ...(this.steps.length > 1 ? [backItem13] : []), + ...(commands || []) + ]; + let validating = validate(''); + disposables.push( + input, + input.onDidTriggerCommand(item => { + if (item === backItem13) { + reject(InputFlowAction13.back); + } + }), + input.onDidAccept(async text => { + if (!(await validate(text))) { + resolve(text); + } + }), + input.onDidValueChange(async text => { + const current = validate(text); + validating = current; + const validationMessage = await current; + if (current === validating) { + input.validationMessage = validationMessage; + } + }), + input.onHide(() => { + (async () => { + reject(shouldResume && await shouldResume() ? InputFlowAction13.resume : InputFlowAction13.cancel); + })() + .catch(reject); + }) + ); + if (this.current) { + this.current.hide(); + } + this.current = input; + this.current.show(); + }); + } finally { + disposables.forEach(d => d.dispose()); + } + } +} + +// #endregion + + + +// --------------------------------------------------------------------------------------- + + +interface Result { + resourceGroup: QuickPickItem; + name: string; + runtime: QuickPickItem; +} + +async function validateNameIsUnique(name: string) { + // ...validate... + await new Promise(resolve => setTimeout(resolve, 1000)); + return name === 'vscode' ? 'Name not unique' : undefined; +} + +async function getAvailableRuntimes(resourceGroup: QuickPickItem | string, token: CancellationToken): Promise { + // ...retrieve... + await new Promise(resolve => setTimeout(resolve, 2000)); + return ['Node 8.9', 'Node 6.11', 'Node 4.5'] + .map(label => ({ label })); +} diff --git a/extensions/extension-editing/src/typings/ref.d.ts b/extensions/extension-editing/src/typings/ref.d.ts index 216911a680eb2..c9849d48e083f 100644 --- a/extensions/extension-editing/src/typings/ref.d.ts +++ b/extensions/extension-editing/src/typings/ref.d.ts @@ -4,3 +4,4 @@ *--------------------------------------------------------------------------------------------*/ /// +/// diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 97a964e0335a2..7c89352d7be1f 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -5,6 +5,8 @@ // This is the place for API experiments and proposal. +import { QuickPickItem } from 'vscode'; + declare module 'vscode' { export namespace window { @@ -513,6 +515,8 @@ declare module 'vscode' { //#region Multi-step input + //#region Take 1 + export namespace window { /** @@ -534,4 +538,415 @@ declare module 'vscode' { } //#endregion + + //#region Take 2 + + export namespace window { + + /** + * Start collecting multiple inputs from the user. The returned + * [`QuickInput2`](#QuickInput2) should be used to control the UI. + * + * Note that this API is only needed when collecting multiple inputs. + * For single inputs, the input functions on [`window`](#window) can be used. + * + * @return A [`QuickInput2`](#QuickInput2) to control the UI. + */ + export function createQuickInput2(): QuickInput2; + } + + /** + * Controls the UI within a multi-step input session. + */ + export interface QuickInput2 { + + /** + * A cancellation token indicating when this multi-step input session was + * canceled by the user (either directly or by triggering another input session). + */ + token: CancellationToken; + + showQuickPick: typeof window.showQuickPick; + + showInputBox: typeof window.showInputBox; + + /** + * Dispose the input object and associated resources. + */ + dispose(): void; + } + + //#endregion + + //#region Take 3 + + export namespace window { + + export function createQuickInput3(): QuickInput3; + } + + /** + * Controls the UI within a input session. + */ + export interface QuickInput3 { + + inputBox: { + visible: boolean; + + text: string | undefined; + + placeholder: string | undefined; + + onDidTextChange: Event; + + onDidAccept: Event; + }; + + toolbar: { + // TODO: Investigate using commands. + + visible: boolean; + + toolbarItems: QuickInputToolbarItem3[] | undefined; + + onDidTriggerToolbarItem: Event; + }; + + list: { + visible: boolean; + + items: QuickPickItem[] | undefined; + + canSelectMany: boolean; + + selectedItems: QuickPickItem[]; + + onDidSelectItem: Event; + }; + + message: { + visible: boolean; + + text: string; + + severity: number; + }; + + enabled: boolean; + busy: boolean; + + show(): void; + + hide(): void; + + onHide: Event; + + dispose(): void; + } + + export interface QuickInputToolbarItem3 { + iconPath: string | Uri | { light: string | Uri; dark: string | Uri } | ThemeIcon; + tooltip?: string | undefined; + } + + //#endregion + + //#region Take 4 + + type MultiStepInput = { [id: string]: InputStep }; + + type InputStep = SinglePickStep | TextInputStep; + interface SinglePickStep { + kind: 'singlePick'; + items: T[]; + placeHolder: string; + nextStep?: string; + } + interface TextInputStep { + kind: 'textInput'; + prompt: string; + validateInput: (value: string) => Thenable; + nextStep?: string; + } + + type Inputs = { [P in keyof T]: SingleInput }; + type SingleInput = + T extends SinglePickStep ? R : + T extends TextInputStep ? R : + never; + + export namespace window { + export function multiStepInput4(firstStep: keyof T, steps: T): Thenable>; + } + + //#endregion + + //#region Take 5 + + type MultiStepInput5 = { [id: string]: InputStep5 }; + + interface InputStep5 { + (input: QuickInput5, values: any): Thenable<{ value: T, next?: string; }>; + } + + type Inputs5 = { [P in keyof T]: SingleInput5 }; + type SingleInput5 = T extends InputStep5 ? R : never; + + export namespace window { + export function multiStepInput5(firstStep: keyof T, steps: T): Thenable>; + } + + export interface QuickInput5 { + token: CancellationToken; + showQuickPick: typeof window.showQuickPick; + showInputBox: typeof window.showInputBox; + } + + //#endregion + + //#region Take 6 + + export interface QuickPickOptions { + next?: (input: QuickInput6, result: QuickPickItem) => Promise | undefined; + } + + export interface InputBoxOptions { + next?: (input: QuickInput6, result: string) => Promise | undefined; + } + + export interface QuickInput6 { + token: CancellationToken; + showQuickPick: typeof window.showQuickPick; + showInputBox: typeof window.showInputBox; + } + + //#endregion + + //#region Take 7 + + export namespace window { + export function multiStepInput7(nav: InputNavigation): Thenable; + } + + export interface InputNavigation { + next(input: QuickInput7): Thenable; + previous(input: QuickInput7): Thenable; + cancel(input: QuickInput7): Thenable; + } + + export interface QuickInput7 { + token: CancellationToken; + showQuickPick: typeof window.showQuickPick; + showInputBox: typeof window.showInputBox; + close: () => void; + } + + //#endregion + + //#region Take 8 + + export namespace window { + export function createQuickInputSession(): QuickInputSession8; + } + + export interface QuickInputSession8 { + token: CancellationToken; + dispose(): void; + } + + export interface QuickPickOptions { + session?: QuickInputSession8; + } + + export interface InputBoxOptions { + session?: QuickInputSession8; + } + + //#endregion + + //#region Take 9 + + type InputStep9 = (session: QuickInputSession9) => Thenable; + + export namespace window { + export function multiStepInput9(step: InputStep9): Thenable; + } + + export interface QuickInputSession9 { + token: CancellationToken; + dispose(): void; + } + + //#endregion + + //#region Take 11 + + export namespace window { + + export function createQuickPick11(): QuickPick11; + export function createInputBox11(): InputBox11; + } + + export interface QuickInput11 { + + enabled: boolean; + busy: boolean; + + show(): void; + + hide(): void; + + onHide: Event; + + replace(input: QuickInput11): void; + + dispose(): void; + } + + export interface QuickPick11 extends QuickInput11 { + + inputBox: { + text: string; + + placeholder: string; + + onDidTextChange: Event; + + onDidAccept: Event; + }; + + toolbar: { + // TODO: Investigate using commands. + + toolbarItems: QuickInputToolbarItem11[]; + + onDidTriggerToolbarItem: Event; + }; + + list: { + items: QuickPickItem[]; + + canSelectMany: boolean; + + builtInFilter: boolean; + + selectedItems: QuickPickItem[]; + + onDidSelectItem: Event; + }; + } + + export interface InputBox11 extends QuickInput11 { + + inputBox: { + text: string; + + placeholder: string; + + password: boolean; + + onDidTextChange: Event; + + onDidAccept: Event; + }; + + toolbar: { + // TODO: Investigate using commands. + + toolbarItems: QuickInputToolbarItem11[]; + + onDidTriggerToolbarItem: Event; + }; + + message: { + text: string | undefined; + + severity: number; + }; + } + + export interface QuickInputToolbarItem11 { + iconPath: string | Uri | { light: string | Uri; dark: string | Uri } | ThemeIcon; + tooltip?: string | undefined; + } + + //#endregion + + //#region Take 13 + + export namespace window { + + export function createQuickPick13(): QuickPick13; + export function createInputBox13(): InputBox13; + } + + export interface QuickInput13 { + + enabled: boolean; + + busy: boolean; + + show(): void; + + hide(): void; + + onHide: Event; + + dispose(): void; + } + + export interface QuickPick13 extends QuickInput13 { + + value: string; + + placeholder: string; + + onDidValueChange: Event; + + onDidAccept: Event; + + commands: QuickInputCommand13[]; + + onDidTriggerCommand: Event; + + items: QuickPickItem[]; + + canSelectMany: boolean; + + builtInFilter: boolean; + + selectedItems: QuickPickItem[]; + + onDidSelectItem: Event; + } + + export interface InputBox13 extends QuickInput13 { + + value: string; + + placeholder: string; + + password: boolean; + + onDidValueChange: Event; + + onDidAccept: Event; + + commands: QuickInputCommand13[]; + + onDidTriggerCommand: Event; + + prompt: string; + + validationMessage: string; + } + + export interface QuickInputCommand13 { + iconPath: string | Uri | { light: string | Uri; dark: string | Uri } | ThemeIcon; + tooltip?: string | undefined; + } + + //#endregion + + //#endregion }