Skip to content

Commit

Permalink
[vscode] support inputs variable substitution for debug
Browse files Browse the repository at this point in the history
See https://code.visualstudio.com/docs/editor/variables-reference#_input-variables

Signed-off-by: Anton Kosyakov <anton.kosyakov@typefox.io>
  • Loading branch information
akosyakov committed Aug 1, 2019
1 parent 774c460 commit 58aec70
Show file tree
Hide file tree
Showing 8 changed files with 274 additions and 18 deletions.
4 changes: 3 additions & 1 deletion packages/debug/src/browser/debug-schema-updater.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -82,6 +83,7 @@ const launchSchema: IJSONSchema = {
'type': 'object',
oneOf: []
}
}
},
inputs: inputsSchema.definitions!.inputs
}
};
5 changes: 4 additions & 1 deletion packages/debug/src/browser/debug-session-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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<void> {
const execPath = await this.env.getExecPath();
variables.registerVariable({
Expand All @@ -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<RecursivePartial<{ inputs: MaybeArray<VariableInput> }>>(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<string>[] = [];
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;
}
});
}

}
131 changes: 131 additions & 0 deletions packages/variable-resolver/src/browser/variable-input-schema.ts
Original file line number Diff line number Diff line change
@@ -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.'
}
}
}
]
}
}
}
};
47 changes: 47 additions & 0 deletions packages/variable-resolver/src/browser/variable-input.ts
Original file line number Diff line number Diff line change
@@ -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;
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

/**
Expand Down Expand Up @@ -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) {
Expand Down
2 changes: 1 addition & 1 deletion packages/variable-resolver/src/browser/variable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export interface Variable {
* `undefined` if variable cannot be resolved.
* Never reject.
*/
resolve(context?: URI, argment?: string): MaybePromise<Object | undefined>;
resolve(context?: URI, argment?: string, configurationSection?: string): MaybePromise<Object | undefined>;
}

export const VariableContribution = Symbol('VariableContribution');
Expand Down
16 changes: 2 additions & 14 deletions packages/workspace/src/browser/workspace-variable-contribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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;

Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -191,6 +178,7 @@ export class WorkspaceVariableContribution implements VariableContribution {
}

getResourceUri(): URI | undefined {
// TODO replace with ResourceContextKey.get?
return this.currentWidget && this.currentWidget.getResourceUri();
}

Expand Down

0 comments on commit 58aec70

Please sign in to comment.