From 58aec707b7099331b0ee1bd75aac4308ca8ca1bd Mon Sep 17 00:00:00 2001 From: Anton Kosyakov Date: Thu, 1 Aug 2019 15:09:28 +0000 Subject: [PATCH] [vscode] support `inputs` variable substitution for debug See https://code.visualstudio.com/docs/editor/variables-reference#_input-variables Signed-off-by: Anton Kosyakov --- .../debug/src/browser/debug-schema-updater.ts | 4 +- .../src/browser/debug-session-manager.ts | 5 +- .../browser/common-variable-contribution.ts | 81 +++++++++++ .../src/browser/variable-input-schema.ts | 131 ++++++++++++++++++ .../src/browser/variable-input.ts | 47 +++++++ .../src/browser/variable-resolver-service.ts | 6 +- .../variable-resolver/src/browser/variable.ts | 2 +- .../workspace-variable-contribution.ts | 16 +-- 8 files changed, 274 insertions(+), 18 deletions(-) create mode 100644 packages/variable-resolver/src/browser/variable-input-schema.ts create mode 100644 packages/variable-resolver/src/browser/variable-input.ts diff --git a/packages/debug/src/browser/debug-schema-updater.ts b/packages/debug/src/browser/debug-schema-updater.ts index da1f6094332ce..4ce7a67c00e23 100644 --- a/packages/debug/src/browser/debug-schema-updater.ts +++ b/packages/debug/src/browser/debug-schema-updater.ts @@ -21,6 +21,7 @@ import { IJSONSchema } from '@theia/core/lib/common/json-schema'; import URI from '@theia/core/lib/common/uri'; import { DebugService } from '../common/debug-service'; import { debugPreferencesSchema } from './debug-preferences'; +import { inputsSchema } from '@theia/variable-resolver/lib/browser/variable-input-schema'; @injectable() export class DebugSchemaUpdater { @@ -82,6 +83,7 @@ const launchSchema: IJSONSchema = { 'type': 'object', oneOf: [] } - } + }, + inputs: inputsSchema.definitions!.inputs } }; diff --git a/packages/debug/src/browser/debug-session-manager.ts b/packages/debug/src/browser/debug-session-manager.ts index 0c874bed686cd..6eda1df7b2ace 100644 --- a/packages/debug/src/browser/debug-session-manager.ts +++ b/packages/debug/src/browser/debug-session-manager.ts @@ -167,7 +167,10 @@ export class DebugSessionManager { } const { workspaceFolderUri } = options; const resolvedConfiguration = await this.resolveDebugConfiguration(options.configuration, workspaceFolderUri); - const configuration = await this.variableResolver.resolve(resolvedConfiguration); + const configuration = await this.variableResolver.resolve(resolvedConfiguration, { + context: options.workspaceFolderUri ? new URI(options.workspaceFolderUri) : undefined, + configurationSection: 'launch' + }); const key = configuration.name + workspaceFolderUri; const id = this.configurationIds.has(key) ? this.configurationIds.get(key)! + 1 : 0; this.configurationIds.set(key, id); diff --git a/packages/variable-resolver/src/browser/common-variable-contribution.ts b/packages/variable-resolver/src/browser/common-variable-contribution.ts index f6a92fa8be40e..88533377474b4 100644 --- a/packages/variable-resolver/src/browser/common-variable-contribution.ts +++ b/packages/variable-resolver/src/browser/common-variable-contribution.ts @@ -18,6 +18,12 @@ import { injectable, inject } from 'inversify'; import { VariableContribution, VariableRegistry } from './variable'; import { EnvVariablesServer } from '@theia/core/lib/common/env-variables'; import { CommandService } from '@theia/core/lib/common/command'; +import { PreferenceService } from '@theia/core/lib/browser/preferences/preference-service'; +import { ResourceContextKey } from '@theia/core/lib/browser/resource-context-key'; +import { VariableInput } from './variable-input'; +import { QuickInputService } from '@theia/core/lib/browser/quick-open/quick-input-service'; +import { QuickPickService, QuickPickItem } from '@theia/core/lib/common/quick-pick-service'; +import { MaybeArray, RecursivePartial } from '@theia/core/lib/common/types'; @injectable() export class CommonVariableContribution implements VariableContribution { @@ -28,6 +34,18 @@ export class CommonVariableContribution implements VariableContribution { @inject(CommandService) protected readonly commands: CommandService; + @inject(PreferenceService) + protected readonly preferences: PreferenceService; + + @inject(ResourceContextKey) + protected readonly resourceContextKey: ResourceContextKey; + + @inject(QuickInputService) + protected readonly quickInputService: QuickInputService; + + @inject(QuickPickService) + protected readonly quickPickService: QuickPickService; + async registerVariables(variables: VariableRegistry): Promise { const execPath = await this.env.getExecPath(); variables.registerVariable({ @@ -41,12 +59,75 @@ export class CommonVariableContribution implements VariableContribution { return envVariable && envVariable.value; } }); + variables.registerVariable({ + name: 'config', + resolve: (resourceUri = this.resourceContextKey.get(), preferenceName) => { + if (!preferenceName) { + return undefined; + } + return this.preferences.get(preferenceName, undefined, resourceUri && resourceUri.toString()); + } + }); variables.registerVariable({ name: 'command', resolve: async (_, command) => // tslint:disable-next-line:no-return-await command && await this.commands.executeCommand(command) }); + variables.registerVariable({ + name: 'input', + resolve: async (resourceUri = this.resourceContextKey.get(), variable, section) => { + if (!variable || !section) { + return undefined; + } + const configuration = this.preferences.get }>>(section, undefined, resourceUri && resourceUri.toString()); + const inputs = !!configuration && 'inputs' in configuration ? configuration.inputs : undefined; + const input = Array.isArray(inputs) && inputs.find(item => !!item && item.id === variable); + if (!input) { + return undefined; + } + if (input.type === 'promptString') { + if (typeof input.description !== 'string') { + return undefined; + } + return this.quickInputService.open({ + prompt: input.description, + value: input.default + }); + } + if (input.type === 'pickString') { + if (typeof input.description !== 'string' || !Array.isArray(input.options)) { + return undefined; + } + const elements: QuickPickItem[] = []; + for (const option of input.options) { + if (typeof option !== 'string') { + return undefined; + } + if (option === input.default) { + elements.unshift({ + description: 'Default', + label: option, + value: option + }); + } else { + elements.push({ + label: option, + value: option + }); + } + } + return this.quickPickService.show(elements, { placeholder: input.description }); + } + if (input.type === 'command') { + if (typeof input.command !== 'string') { + return undefined; + } + return this.commands.executeCommand(input.command, input.args); + } + return undefined; + } + }); } } diff --git a/packages/variable-resolver/src/browser/variable-input-schema.ts b/packages/variable-resolver/src/browser/variable-input-schema.ts new file mode 100644 index 0000000000000..cf2d24fae053a --- /dev/null +++ b/packages/variable-resolver/src/browser/variable-input-schema.ts @@ -0,0 +1,131 @@ +/******************************************************************************** + * Copyright (C) 2019 TypeFox and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +/* + * copied from + * https://github.com/microsoft/vscode/blob/0a34756cae4fc67739e60c708b04637089f8bb0d/src/vs/workbench/services/configurationResolver/common/configurationResolverSchema.ts#L23 + */ + +const idDescription = "The input's id is used to associate an input with a variable of the form ${input:id}."; +const typeDescription = 'The type of user input prompt to use.'; +const descriptionDescription = 'The description is shown when the user is prompted for input.'; +const defaultDescription = 'The default value for the input.'; + +import { IJSONSchema } from '@theia/core/lib/common/json-schema'; + +export const inputsSchema: IJSONSchema = { + definitions: { + inputs: { + type: 'array', + description: 'User inputs. Used for defining user input prompts, such as free string input or a choice from several options.', + items: { + oneOf: [ + { + type: 'object', + required: ['id', 'type', 'description'], + additionalProperties: false, + properties: { + id: { + type: 'string', + description: idDescription + }, + type: { + type: 'string', + description: typeDescription, + enum: ['promptString'], + enumDescriptions: [ + "The 'promptString' type opens an input box to ask the user for input." + ] + }, + description: { + type: 'string', + description: descriptionDescription + }, + default: { + type: 'string', + description: defaultDescription + }, + } + }, + { + type: 'object', + required: ['id', 'type', 'description', 'options'], + additionalProperties: false, + properties: { + id: { + type: 'string', + description: idDescription + }, + type: { + type: 'string', + description: typeDescription, + enum: ['pickString'], + enumDescriptions: [ + "The 'pickString' type shows a selection list.", + ] + }, + description: { + type: 'string', + description: descriptionDescription + }, + default: { + type: 'string', + description: defaultDescription + }, + options: { + type: 'array', + description: 'An array of strings that defines the options for a quick pick.', + items: { + type: 'string' + } + } + } + }, + { + type: 'object', + required: ['id', 'type', 'command'], + additionalProperties: false, + properties: { + id: { + type: 'string', + description: idDescription + }, + type: { + type: 'string', + description: typeDescription, + enum: ['command'], + enumDescriptions: [ + "The 'command' type executes a command.", + ] + }, + command: { + type: 'string', + description: 'The command to execute for this input variable.' + }, + args: { + type: 'object', + description: 'Optional arguments passed to the command.' + } + } + } + ] + } + } + } +}; diff --git a/packages/variable-resolver/src/browser/variable-input.ts b/packages/variable-resolver/src/browser/variable-input.ts new file mode 100644 index 0000000000000..6951b8265a202 --- /dev/null +++ b/packages/variable-resolver/src/browser/variable-input.ts @@ -0,0 +1,47 @@ +/******************************************************************************** + * Copyright (C) 2019 TypeFox and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +/* + * copied from + * https://github.com/microsoft/vscode/blob/0a34756cae4fc67739e60c708b04637089f8bb0d/src/vs/workbench/services/configurationResolver/common/configurationResolver.ts#L41-L63 + */ +export interface VariablePromptStringInput { + id: string; + type: 'promptString'; + description: string; + default?: string; +} + +export interface VariablePickStringInput { + id: string; + type: 'pickString'; + description: string; + options: string[]; + default?: string; +} + +export interface VariableCommandInput { + id: string; + type: 'command'; + command: string; + // tslint:disable-next-line:no-any + args?: any; +} + +export type VariableInput = VariablePromptStringInput | VariablePickStringInput | VariableCommandInput; diff --git a/packages/variable-resolver/src/browser/variable-resolver-service.ts b/packages/variable-resolver/src/browser/variable-resolver-service.ts index b6b1b9fd215fc..0b0fb157bc112 100644 --- a/packages/variable-resolver/src/browser/variable-resolver-service.ts +++ b/packages/variable-resolver/src/browser/variable-resolver-service.ts @@ -23,6 +23,10 @@ import { JSONExt, ReadonlyJSONValue } from '@phosphor/coreutils/lib/json'; export interface VariableResolveOptions { context?: URI; + /** + * Used for resolving inputs, see https://code.visualstudio.com/docs/editor/variables-reference#_input-variables + */ + configurationSection?: string; } /** @@ -140,7 +144,7 @@ export namespace VariableResolverService { argument = parts[1]; } const variable = this.variableRegistry.getVariable(variableName); - const value = variable && await variable.resolve(this.options.context, argument); + const value = variable && await variable.resolve(this.options.context, argument, this.options.configurationSection); const stringValue = value !== undefined && value !== null && JSONExt.isPrimitive(value as ReadonlyJSONValue) ? String(value) : undefined; this.resolved.set(name, stringValue); } catch (e) { diff --git a/packages/variable-resolver/src/browser/variable.ts b/packages/variable-resolver/src/browser/variable.ts index 12be028eba9ed..724cc00a279f9 100644 --- a/packages/variable-resolver/src/browser/variable.ts +++ b/packages/variable-resolver/src/browser/variable.ts @@ -38,7 +38,7 @@ export interface Variable { * `undefined` if variable cannot be resolved. * Never reject. */ - resolve(context?: URI, argment?: string): MaybePromise; + resolve(context?: URI, argment?: string, configurationSection?: string): MaybePromise; } export const VariableContribution = Symbol('VariableContribution'); diff --git a/packages/workspace/src/browser/workspace-variable-contribution.ts b/packages/workspace/src/browser/workspace-variable-contribution.ts index 58e7df2c2ebf6..af84d20329f3c 100644 --- a/packages/workspace/src/browser/workspace-variable-contribution.ts +++ b/packages/workspace/src/browser/workspace-variable-contribution.ts @@ -19,7 +19,7 @@ import URI from '@theia/core/lib/common/uri'; import { Path } from '@theia/core/lib/common/path'; import { FileSystem } from '@theia/filesystem/lib/common'; import { Disposable, DisposableCollection } from '@theia/core/lib/common/disposable'; -import { ApplicationShell, NavigatableWidget, PreferenceService } from '@theia/core/lib/browser'; +import { ApplicationShell, NavigatableWidget } from '@theia/core/lib/browser'; import { VariableContribution, VariableRegistry, Variable } from '@theia/variable-resolver/lib/browser'; import { WorkspaceService } from './workspace-service'; @@ -32,8 +32,6 @@ export class WorkspaceVariableContribution implements VariableContribution { protected readonly shell: ApplicationShell; @inject(FileSystem) protected readonly fileSystem: FileSystem; - @inject(PreferenceService) - protected readonly preferences: PreferenceService; protected currentWidget: NavigatableWidget | undefined; @@ -65,17 +63,6 @@ export class WorkspaceVariableContribution implements VariableContribution { registerVariables(variables: VariableRegistry): void { this.registerWorkspaceRootVariables(variables); - variables.registerVariable({ - name: 'config', - resolve: (context, preferenceName) => { - if (!preferenceName) { - return undefined; - } - const resourceUri = context || this.getResourceUri(); - return this.preferences.get(preferenceName, undefined, resourceUri && resourceUri.toString()); - } - }); - variables.registerVariable({ name: 'file', description: 'The path of the currently opened file', @@ -191,6 +178,7 @@ export class WorkspaceVariableContribution implements VariableContribution { } getResourceUri(): URI | undefined { + // TODO replace with ResourceContextKey.get? return this.currentWidget && this.currentWidget.getResourceUri(); }