插件功能
在编辑器中,输入指令可以快速打开一个选项输入框,还可以根据输入框输入或者选择不同的条件进行下一步操作
使用到的api
首先activate中激活插件指令,通过createQuickPick打开快速输入弹窗,绑定事件,打开quickPick,这是第一步的用户操作
export function activate(context: ExtensionContext) {
context.subscriptions.push(commands.registerCommand('samples.quickInput', async () => {
const options: { [key: string]: (context: ExtensionContext) => Promise<void> } = {
showQuickPick,
showInputBox,
multiStepInput,
quickOpen,
};
const quickPick = window.createQuickPick();
quickPick.items = Object.keys(options).map(label => ({ label }));
quickPick.onDidChangeSelection(selection => {
if (selection[0]) {
options[selection[0].label](context)
.catch(console.error);
}
});
quickPick.onDidHide(() => quickPick.dispose());
quickPick.show();
}));
}
options 选项对应了用户选择四个事例,接下来看下四个实例的具体分析。
showQuickPick和showInputBox都比较简单,都是vscode.window
的内置方法
export async function showQuickPick() {
let i = 0;
const result = await window.showQuickPick(['eins', 'zwei', 'drei'], {
placeHolder: 'eins, zwei or drei',
onDidSelectItem: item => window.showInformationMessage(`Focus ${++i}: ${item}`)
});
window.showInformationMessage(`Got: ${result}`);
}
/**
* Shows an input box using window.showInputBox().
*/
export async function showInputBox() {
const result = await window.showInputBox({
value: 'abcdef',
valueSelection: [2, 4],
placeHolder: 'For example: fedcba. But not: 123',
validateInput: text => {
window.showInformationMessage(`Validating: ${text}`);
return text === '123' ? 'Not 123!' : null;
}
});
window.showInformationMessage(`Got: ${result}`);
}
第三个例子multiStepInput: 是一个多步骤的input弹窗,他会现实当前在第几步,还可以回退进度等功能
定制按钮、选择项
class MyButton implements QuickInputButton {
constructor(public iconPath: { light: Uri; dark: Uri; }, public tooltip: string) { }
}
const createResourceGroupButton = new MyButton({
dark: Uri.file(context.asAbsolutePath('resources/dark/add.svg')),
light: Uri.file(context.asAbsolutePath('resources/light/add.svg')),
}, 'Create Resource Group');
const resourceGroups: QuickPickItem[] = ['vscode-data-function', 'vscode-appservice-microservices', 'vscode-appservice-monitor', 'vscode-appservice-preview', 'vscode-appservice-prod']
.map(label => ({ label }));
那么具体实现弹窗下一步的操作是核心,实现多步这里利用了柯里化思想,每个函数返回一新的函数,每次执行完当前函数,在当前的while
循环体中把上一次执行的结果重新附值给step
,直到下一步操作完成跳出循环,下面是核心思想代码实现
async function collectInputs() {
const state = {} as Partial<State>;
// 这里的input实例是传入函数中的参数往后看
await MultiStepInput.run(input => pickResourceGroup(input, state));
return state as State;
}
MultiStepInput.run实现
class MultiStepInput {
static async run<T>(start: InputStep) {
const input = new MultiStepInput();
return input.stepThrough(start);
}
private current?: QuickInput;
private steps: InputStep[] = [];
private async stepThrough<T>(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); // 这里的this就是MultiStepInput.run的input实例
} 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();
}
...
比如对于每一个input实例执行showQuickPick后会收集到销毁栈里,等结束后自动销毁,下一步通过this重新创建input实例
async showQuickPick<T extends QuickPickItem, P extends QuickPickParameters<T>>({ title, step, totalSteps, items, activeItem, placeholder, buttons, shouldResume }: P) {
const disposables: Disposable[] = [];
try {
return await new Promise<T | (P extends { buttons: (infer I)[] } ? I : never)>((resolve, reject) => {
const input = window.createQuickPick<T>();
input.title = title;
input.step = step;
input.totalSteps = totalSteps;
input.placeholder = placeholder;
input.items = items;
if (activeItem) {
input.activeItems = [activeItem];
}
input.buttons = [
...(this.steps.length > 1 ? [QuickInputButtons.Back] : []),
...(buttons || [])
];
disposables.push(
input.onDidTriggerButton(item => {
if (item === QuickInputButtons.Back) {
reject(InputFlowAction.back);
} else {
resolve(<any>item);
}
}),
input.onDidChangeSelection(items => resolve(items[0])),
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()); // 绑定之后销毁
}
最后一个例子:quickOpen
为开启一个createQuickPick后通过开启子进程在当前工作目录下查找是否有匹配到的文件,匹配到后打开
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as path from 'path';
import * as cp from 'child_process';
import { Uri, window, Disposable } from 'vscode';
import { QuickPickItem } from 'vscode';
import { workspace } from 'vscode';
/**
* A file opener using window.createQuickPick().
*
* It shows how the list of items can be dynamically updated based on
* the user's input in the filter field.
*/
export async function quickOpen() {
const uri = await pickFile();
if (uri) {
const document = await workspace.openTextDocument(uri);
await window.showTextDocument(document);
}
}
class FileItem implements QuickPickItem {
label: string;
description: string;
constructor(public base: Uri, public uri: Uri) {
this.label = path.basename(uri.fsPath);
this.description = path.dirname(path.relative(base.fsPath, uri.fsPath));
}
}
class MessageItem implements QuickPickItem {
label: string;
description = '';
detail: string;
constructor(public base: Uri, public message: string) {
this.label = message.replace(/\r?\n/g, ' ');
this.detail = base.fsPath;
}
}
async function pickFile() {
const disposables: Disposable[] = [];
try {
return await new Promise<Uri | undefined>((resolve, reject) => {
const input = window.createQuickPick<FileItem | MessageItem>();
input.placeholder = 'Type to search for files';
let rgs: cp.ChildProcess[] = [];
disposables.push(
input.onDidChangeValue(value => {
rgs.forEach(rg => rg.kill());
if (!value) {
input.items = [];
return;
}
input.busy = true;
const cwds = workspace.workspaceFolders ? workspace.workspaceFolders.map(f => f.uri.fsPath) : [process.cwd()];
const q = process.platform === 'win32' ? '"' : '\'';
rgs = cwds.map(cwd => {
const rg = cp.exec(`rg --files -g ${q}*${value}*${q}`, { cwd }, (err, stdout) => {
const i = rgs.indexOf(rg);
if (i !== -1) {
if (rgs.length === cwds.length) {
input.items = [];
}
if (!err) {
input.items = input.items.concat(
stdout
.split('\n').slice(0, 50)
.map(relative => new FileItem(Uri.file(cwd), Uri.file(path.join(cwd, relative))))
);
}
if (err && !(<any>err).killed && (<any>err).code !== 1 && err.message) {
input.items = input.items.concat([
new MessageItem(Uri.file(cwd), err.message)
]);
}
rgs.splice(i, 1);
if (!rgs.length) {
input.busy = false;
}
}
});
return rg;
});
}),
input.onDidChangeSelection(items => {
const item = items[0];
if (item instanceof FileItem) {
resolve(item.uri);
input.hide();
}
}),
input.onDidHide(() => {
rgs.forEach(rg => rg.kill());
resolve(undefined);
input.dispose();
})
);
input.show();
});
} finally {
disposables.forEach(d => d.dispose());
}
}
上面这些就是官网围绕quickInput拓展的一些例子,因为showQuickPic
和showInputBox
不具有灵活性,所以createQuickPick
和createInputBox
定执行更强,主要注意的细节是busy
属性显示进度指示器,默认为false
, 如果显示进度起要在totalStep
设置总步数,step
设置当前在第几步。