Quick Input Sample

This commit is contained in:
Christof Marti
2018-06-15 16:47:11 +02:00
parent c9f5ae193d
commit a1df545cb7
18 changed files with 2872 additions and 0 deletions

3
quickinput-sample/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
out
node_modules
*.vsix

22
quickinput-sample/.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,22 @@
// A launch configuration that compiles the extension and then opens it inside a new window
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
{
"version": "0.2.0",
"configurations": [
{
"name": "Extension",
"type": "extensionHost",
"request": "launch",
"runtimeExecutable": "${execPath}",
"args": [
"--extensionDevelopmentPath=${workspaceFolder}"
],
"outFiles": [
"${workspaceFolder}/out/**/*.js"
],
"preLaunchTask": "npm: watch"
}
]
}

11
quickinput-sample/.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,11 @@
// Place your settings in this file to overwrite default and user settings.
{
"files.exclude": {
"out": false // set this to true to hide the "out" folder with the compiled JS files
},
"search.exclude": {
"out": true // set this to false to include "out" folder in search results
},
// Turn off tsc task auto detection since we have the necessary tasks as npm scripts
"typescript.tsc.autoDetect": "off"
}

20
quickinput-sample/.vscode/tasks.json vendored Normal file
View File

@ -0,0 +1,20 @@
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
{
"version": "2.0.0",
"tasks": [
{
"type": "npm",
"script": "watch",
"problemMatcher": "$tsc-watch",
"isBackground": true,
"presentation": {
"reveal": "never"
},
"group": {
"kind": "build",
"isDefault": true
}
}
]
}

View File

@ -0,0 +1,6 @@
.vscode/**
out/**/*.map
src/**
.gitignore
tsconfig.json
tslint.json

View File

@ -0,0 +1,22 @@
# QuickInput Sample
NOTE: This extension is using API in 'proposed' stage in VS Code 1.25. Please provide feedback in issue [#49340](https://github.com/Microsoft/vscode/issues/49340).
This is a sample extension that shows the QuickInput UI and usage of the QuickInput API.
It is not intended as a production quality extension.
- Open the command palette
- Run "Quick Input Samples"
- Pick one of the samples and see it run
![Multi-step sample](https://raw.githubusercontent.com/Microsoft/vscode-extension-samples/master/quickinput-sample/preview.gif)
# How it works, what it shows?
- The extension uses the [`QuickPick`](https://code.visualstudio.com/docs/extensionAPI/vscode-api#QuickPick) and [`InputBox`](https://code.visualstudio.com/docs/extensionAPI/vscode-api#InputBox) API to show a UI for user input.
- Registers a command via `package.json` that will trigger the task
# How to run locally
* open this folder in VS Code and press `F5`

2255
quickinput-sample/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,39 @@
{
"name": "quickinput-sample",
"displayName": "QuickInput Sample",
"description": "Samples for VS Code's QuickInput API",
"version": "0.0.1",
"publisher": "ms-vscode",
"engines": {
"vscode": "^1.24.0"
},
"enableProposedApi": true,
"categories": [
"Other"
],
"activationEvents": [
"onCommand:samples.quickInput"
],
"main": "./out/extension",
"contributes": {
"commands": [
{
"command": "samples.quickInput",
"title": "Quick Input Samples"
}
]
},
"scripts": {
"vscode:prepublish": "npm run compile",
"compile": "tsc -p ./",
"watch": "tsc -watch -p ./",
"postinstall": "node ./node_modules/vscode/bin/install"
},
"devDependencies": {
"typescript": "^2.6.1",
"vscode": "^1.1.6",
"tslint": "^5.8.0",
"@types/node": "^7.0.43",
"@types/mocha": "^2.2.42"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 MiB

View File

@ -0,0 +1 @@
<svg width="16" height="16" xmlns="http://www.w3.org/2000/svg"><title>Layer 1</title><rect height="11" width="3" y="3" x="7" fill="#C5C5C5"/><rect height="3" width="11" y="7" x="3" fill="#C5C5C5"/></svg>

After

Width:  |  Height:  |  Size: 203 B

View File

@ -0,0 +1 @@
<svg width="16" height="16" xmlns="http://www.w3.org/2000/svg"><title>Layer 1</title><rect height="11" width="3" y="3" x="7" fill="#424242"/><rect height="3" width="11" y="7" x="3" fill="#424242"/></svg>

After

Width:  |  Height:  |  Size: 203 B

View File

@ -0,0 +1,19 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { window } from 'vscode';
/**
* 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 => Promise.resolve(text === '123' ? 'Not 123!' : null)
});
window.showInformationMessage(`Got: ${result}`);
}

View File

@ -0,0 +1,31 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { window, commands, ExtensionContext } from 'vscode';
import { showInputBox } from './basicInput';
import { multiStepInput } from './multiStepInput';
import { quickOpen } from './quickOpen';
export function activate(context: ExtensionContext) {
context.subscriptions.push(commands.registerCommand('samples.quickInput', async () => {
const options: { [key: string]: (context: ExtensionContext) => Promise<void> } = {
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();
}));
}

View File

@ -0,0 +1,307 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { QuickPickItem, window, Disposable, CancellationToken, QuickInputButton, QuickInput, ExtensionContext } from 'vscode';
/**
* A multi-step input using window.createQuickPick() and window.createInputBox().
*
* This first part uses the helper class `MultiStepInput` that wraps the API for the multi-step case.
*/
export async function multiStepInput(context: ExtensionContext) {
class MyButton implements QuickInputButton {
constructor(public iconPath: { light: string; dark: string; }, public tooltip: string) { }
}
const createResourceGroupButton = new MyButton({
dark: context.asAbsolutePath('resources/dark/add.svg'),
light: 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 }));
interface State {
title: string;
step: number;
totalSteps: number;
resourceGroup: QuickPickItem | string;
name: string;
runtime: QuickPickItem;
}
async function collectInputs() {
const state = {} as Partial<State>;
await MultiStepInput.run(input => pickResourceGroup(input, state));
return state as State;
}
const title = 'Create Application Service';
async function pickResourceGroup(input: MultiStepInput, state: Partial<State>) {
const pick = await input.showQuickPick({
title,
step: 1,
totalSteps: 3,
placeholder: 'Pick a resource group',
items: resourceGroups,
activeItem: typeof state.resourceGroup !== 'string' ? state.resourceGroup : undefined,
buttons: [createResourceGroupButton],
shouldResume: shouldResume
});
if (pick instanceof MyButton) {
return (input: MultiStepInput) => inputResourceGroupName(input, state);
}
state.resourceGroup = pick;
return (input: MultiStepInput) => inputName(input, state);
}
async function inputResourceGroupName(input: MultiStepInput, state: Partial<State>) {
state.resourceGroup = await input.showInputBox({
title,
step: 2,
totalSteps: 4,
value: typeof state.resourceGroup === 'string' ? state.resourceGroup : '',
prompt: 'Choose a unique name for the resource group',
validate: validateNameIsUnique,
shouldResume: shouldResume
});
return (input: MultiStepInput) => inputName(input, state);
}
async function inputName(input: MultiStepInput, state: Partial<State>) {
const additionalSteps = typeof state.resourceGroup === 'string' ? 1 : 0;
// TODO: Remember current value when navigating back.
state.name = await input.showInputBox({
title,
step: 2 + additionalSteps,
totalSteps: 3 + additionalSteps,
value: state.name || '',
prompt: 'Choose a unique name for the Application Service',
validate: validateNameIsUnique,
shouldResume: shouldResume
});
return (input: MultiStepInput) => pickRuntime(input, state);
}
async function pickRuntime(input: MultiStepInput, state: Partial<State>) {
const additionalSteps = typeof state.resourceGroup === 'string' ? 1 : 0;
const runtimes = await getAvailableRuntimes(state.resourceGroup!, undefined /* TODO: token */);
// TODO: Remember currently active item when navigating back.
state.runtime = await input.showQuickPick({
title,
step: 3 + additionalSteps,
totalSteps: 3 + additionalSteps,
placeholder: 'Pick a runtime',
items: runtimes,
activeItem: state.runtime,
shouldResume: shouldResume
});
}
function shouldResume() {
// Could show a notification with the option to resume.
return new Promise<boolean>((resolve, reject) => {
});
}
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<QuickPickItem[]> {
// ...retrieve...
await new Promise(resolve => setTimeout(resolve, 1000));
return ['Node 8.9', 'Node 6.11', 'Node 4.5']
.map(label => ({ label }));
}
const state = await collectInputs();
window.showInformationMessage(`Creating Application Service '${state.name}'`);
}
// -------------------------------------------------------
// Helper code that wraps the API for the multi-step case.
// -------------------------------------------------------
class InputFlowAction {
private constructor() { }
static back = new InputFlowAction();
static cancel = new InputFlowAction();
static resume = new InputFlowAction();
}
type InputStep = (input: MultiStepInput) => Thenable<InputStep | void>;
interface QuickPickParameters<T extends QuickPickItem> {
title: string;
step: number;
totalSteps: number;
items: T[];
activeItem?: T;
placeholder: string;
buttons?: QuickInputButton[];
shouldResume: () => Thenable<boolean>;
}
interface InputBoxParameters {
title: string;
step: number;
totalSteps: number;
value: string;
prompt: string;
validate: (value: string) => Promise<string | undefined>;
buttons?: QuickInputButton[];
shouldResume: () => Thenable<boolean>;
}
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);
} 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 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 ? [window.quickInputBackButton] : []),
...(buttons || [])
];
disposables.push(
input.onDidTriggerButton(item => {
if (item === window.quickInputBackButton) {
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());
}
}
async showInputBox<P extends InputBoxParameters>({ title, step, totalSteps, value, prompt, validate, buttons, shouldResume }: P) {
const disposables: Disposable[] = [];
try {
return await new Promise<string | (P extends { buttons: (infer I)[] } ? I : never)>((resolve, reject) => {
const input = window.createInputBox();
input.title = title;
input.step = step;
input.totalSteps = totalSteps;
input.value = value || '';
input.prompt = prompt;
input.buttons = [
...(this.steps.length > 1 ? [window.quickInputBackButton] : []),
...(buttons || [])
];
let validating = validate('');
disposables.push(
input.onDidTriggerButton(item => {
if (item === window.quickInputBackButton) {
reject(InputFlowAction.back);
} else {
resolve(<any>item);
}
}),
input.onDidAccept(async () => {
const value = input.value;
input.enabled = false;
input.busy = true;
if (!(await validate(value))) {
resolve(value);
}
input.enabled = true;
input.busy = false;
}),
input.onDidChangeValue(async text => {
const current = validate(text);
validating = current;
const validationMessage = await current;
if (current === validating) {
input.validationMessage = validationMessage;
}
}),
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());
}
}
}

View File

@ -0,0 +1,91 @@
/*---------------------------------------------------------------------------------------------
* 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));
}
}
async function pickFile() {
const disposables: Disposable[] = [];
try {
return await new Promise<Uri | undefined>((resolve, reject) => {
const input = window.createQuickPick<FileItem>();
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()];
rgs = cwds.map(cwd => {
const rg = cp.exec(`rg --files -g '*${value}*'`, { 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))))
);
}
rgs.splice(i, 1);
if (!rgs.length) {
input.busy = false;
}
}
});
return rg;
});
}),
input.onDidChangeSelection(items => {
resolve(items[0].uri);
input.hide();
}),
input.onDidHide(() => {
rgs.forEach(rg => rg.kill());
resolve(undefined);
input.dispose();
})
);
input.show();
});
} finally {
disposables.forEach(d => d.dispose());
}
}

7
quickinput-sample/src/ref.d.ts vendored Normal file
View File

@ -0,0 +1,7 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
// TODO
/// <reference path='../../../vscode/src/vs/vscode.proposed.d.ts'/>

View File

@ -0,0 +1,22 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es6",
"outDir": "out",
"lib": [
"es6"
],
"sourceMap": true,
"rootDir": "src",
/* Strict Type-Checking Option */
"strict": true, /* enable all strict type-checking options */
/* Additional Checks */
"noUnusedLocals": true /* Report errors on unused locals. */
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
// "noUnusedParameters": true, /* Report errors on unused parameters. */
},
"exclude": [
"node_modules"
]
}

View File

@ -0,0 +1,15 @@
{
"rules": {
"no-string-throw": true,
"no-unused-expression": true,
"no-duplicate-variable": true,
"curly": true,
"class-name": true,
"semicolon": [
true,
"always"
],
"triple-equals": true
},
"defaultSeverity": "warning"
}