blob: 29f2009e8a182fd97ec4d7d182956eac75bc6095 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import * as vscode from 'vscode';
class InputFlowAction {
static back = new InputFlowAction();
static cancel = new InputFlowAction();
static resume = new InputFlowAction();
}
export type InputStep = (input: MultiStepInput) => Thenable<InputStep | void>;
interface QuickPickParameters<T extends vscode.QuickPickItem> {
title: string | undefined;
step: number;
totalSteps: number;
items: T[];
placeholder: string;
canSelectMany?: boolean;
selectedItems?: readonly T[];
buttons?: vscode.QuickInputButton[];
shouldResume?: () => Thenable<boolean>;
}
interface InputBoxParameters {
title: string | undefined;
step: number;
totalSteps: number;
value: string;
prompt: string;
validate: (value: string) => Promise<string | undefined>;
password?: boolean;
buttons?: vscode.QuickInputButton[];
shouldResume?: () => Thenable<boolean>;
}
export class MultiStepInput {
static async run(start: InputStep) {
const input = new MultiStepInput();
return input.stepThrough(start);
}
private current?: vscode.QuickInput;
private steps: InputStep[] = [];
private async stepThrough(start: InputStep) {
let step: InputStep | 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 === InputFlowAction.back) {
this.steps.pop();
step = this.steps.pop();
} else if (err === InputFlowAction.resume) {
step = this.steps.pop();
} else if (err === InputFlowAction.cancel) {
step = undefined;
} else {
throw err;
}
}
}
if (this.current) {
this.current.dispose();
}
}
async showQuickPick<T extends vscode.QuickPickItem, P extends QuickPickParameters<T>>({ title, step, totalSteps, items, selectedItems, placeholder, canSelectMany, buttons, shouldResume }: P) {
const disposables: vscode.Disposable[] = [];
try {
return await new Promise<readonly T[] | (P extends { buttons: (infer I)[] } ? I : never)>((resolve, reject) => {
const input = vscode.window.createQuickPick<T>();
input.title = title;
input.step = step;
input.totalSteps = totalSteps;
input.placeholder = placeholder;
input.items = items;
if (canSelectMany) {
input.canSelectMany = canSelectMany;
}
if (selectedItems) {
input.selectedItems = selectedItems;
}
input.buttons = [
...(this.steps.length > 1 ? [vscode.QuickInputButtons.Back] : []),
...(buttons || [])
];
input.ignoreFocusOut = true;
disposables.push(
input.onDidTriggerButton(item => {
if (item === vscode.QuickInputButtons.Back) {
reject(InputFlowAction.back);
} else {
resolve(<any>item);
}
}),
input.onDidAccept(() => {
resolve(input.selectedItems);
}),
input.onDidHide(() => {
(async () => {
reject(shouldResume && await shouldResume() ? InputFlowAction.resume : InputFlowAction.cancel);
})()
.catch(reject);
})
);
if (this.current) {
this.current.dispose();
}
this.current = input;
this.current.show();
});
} finally {
disposables.forEach(d => d.dispose());
}
}
async showInputBox<P extends InputBoxParameters>({ title, step, totalSteps, value, prompt, validate, password, buttons, shouldResume }: P) {
const disposables: vscode.Disposable[] = [];
try {
return await new Promise<string | (P extends { buttons: (infer I)[] } ? I : never)>((resolve, reject) => {
const input = vscode.window.createInputBox();
input.title = title;
input.step = step;
input.totalSteps = totalSteps;
input.value = value || '';
input.prompt = prompt;
if (password) {
input.password = password;
}
input.buttons = [
...(this.steps.length > 1 ? [vscode.QuickInputButtons.Back] : []),
...(buttons || [])
];
input.ignoreFocusOut = true;
// let validating = validate('');
disposables.push(
input.onDidTriggerButton(item => {
if (item === vscode.QuickInputButtons.Back) {
reject(InputFlowAction.back);
} else {
resolve(<any>item);
}
}),
input.onDidAccept(async () => {
const value = input.value;
input.enabled = false;
input.busy = true;
const validationMessage = await validate(value);
if (validationMessage) {
input.validationMessage = validationMessage;
} else {
resolve(value);
}
input.enabled = true;
input.busy = false;
}),
input.onDidHide(() => {
(async () => {
reject(shouldResume && await shouldResume() ? InputFlowAction.resume : InputFlowAction.cancel);
})()
.catch(reject);
})
);
if (this.current) {
this.current.dispose();
}
this.current = input;
this.current.show();
});
} finally {
disposables.forEach(d => d.dispose());
}
}
}