From 13cbcaf75492d25e864d563439a8126e5cef1025 Mon Sep 17 00:00:00 2001 From: Anton Kosyakov Date: Thu, 1 Aug 2019 08:22:31 +0000 Subject: [PATCH 1/9] [vscode] support `selectedText` variable substitution Signed-off-by: Anton Kosyakov --- .../editor/src/browser/editor-variable-contribution.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/editor/src/browser/editor-variable-contribution.ts b/packages/editor/src/browser/editor-variable-contribution.ts index 6e69509ec5379..4e1d505b80436 100644 --- a/packages/editor/src/browser/editor-variable-contribution.ts +++ b/packages/editor/src/browser/editor-variable-contribution.ts @@ -34,6 +34,14 @@ export class EditorVariableContribution implements VariableContribution { return editor ? `${editor.cursor.line + 1}` : undefined; } }); + variables.registerVariable({ + name: 'selectedText', + description: 'The current selected text in the active file', + resolve: () => { + const editor = this.getCurrentEditor(); + return editor ? editor.document.getText(editor.selection) : undefined; + } + }); } protected getCurrentEditor(): TextEditor | undefined { From d08704e36d0fa6819cb723c9f8201cf07661f695 Mon Sep 17 00:00:00 2001 From: Anton Kosyakov Date: Thu, 1 Aug 2019 08:35:53 +0000 Subject: [PATCH 2/9] [vscode] support `execPath` variable substitution Signed-off-by: Anton Kosyakov --- .../core/src/common/env-variables/env-variables-protocol.ts | 1 + .../core/src/node/env-variables/env-variables-server.ts | 6 +++++- .../src/browser/env-variable-contribution.ts | 5 +++++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/core/src/common/env-variables/env-variables-protocol.ts b/packages/core/src/common/env-variables/env-variables-protocol.ts index 7685b644c283c..fca91897abdda 100644 --- a/packages/core/src/common/env-variables/env-variables-protocol.ts +++ b/packages/core/src/common/env-variables/env-variables-protocol.ts @@ -18,6 +18,7 @@ export const envVariablesPath = '/services/envs'; export const EnvVariablesServer = Symbol('EnvVariablesServer'); export interface EnvVariablesServer { + getExecPath(): Promise getVariables(): Promise getValue(key: string): Promise } diff --git a/packages/core/src/node/env-variables/env-variables-server.ts b/packages/core/src/node/env-variables/env-variables-server.ts index c9491f731d64c..a878c75ec718d 100644 --- a/packages/core/src/node/env-variables/env-variables-server.ts +++ b/packages/core/src/node/env-variables/env-variables-server.ts @@ -25,10 +25,14 @@ export class EnvVariablesServerImpl implements EnvVariablesServer { constructor() { const prEnv = process.env; Object.keys(prEnv).forEach((key: string) => { - this.envs[key] = {'name' : key, 'value' : prEnv[key]}; + this.envs[key] = { 'name': key, 'value': prEnv[key] }; }); } + async getExecPath(): Promise { + return process.execPath; + } + async getVariables(): Promise { return Object.keys(this.envs).map(key => this.envs[key]); } diff --git a/packages/variable-resolver/src/browser/env-variable-contribution.ts b/packages/variable-resolver/src/browser/env-variable-contribution.ts index 2b71627aa33e1..0d8b910802566 100644 --- a/packages/variable-resolver/src/browser/env-variable-contribution.ts +++ b/packages/variable-resolver/src/browser/env-variable-contribution.ts @@ -25,6 +25,11 @@ export class EnvVariableContribution implements VariableContribution { protected readonly env: EnvVariablesServer; async registerVariables(variables: VariableRegistry): Promise { + const execPath = await this.env.getExecPath(); + variables.registerVariable({ + name: 'execPath', + resolve: () => execPath + }); for (const variable of await this.env.getVariables()) { variables.registerVariable({ name: 'env:' + variable.name, From 22b36708fe16ede2f21ddabc76c3a90e6e006029 Mon Sep 17 00:00:00 2001 From: Anton Kosyakov Date: Thu, 1 Aug 2019 09:28:07 +0000 Subject: [PATCH 3/9] [vscode] support workspace scoped variable substitution see https://code.visualstudio.com/docs/editor/variables-reference#_variables-scoped-per-workspace-folder Signed-off-by: Anton Kosyakov --- .../workspace-variable-contribution.ts | 133 ++++++++++++------ 1 file changed, 90 insertions(+), 43 deletions(-) diff --git a/packages/workspace/src/browser/workspace-variable-contribution.ts b/packages/workspace/src/browser/workspace-variable-contribution.ts index 3ec504150bf73..a9368de379b40 100644 --- a/packages/workspace/src/browser/workspace-variable-contribution.ts +++ b/packages/workspace/src/browser/workspace-variable-contribution.ts @@ -16,10 +16,11 @@ import { injectable, inject, postConstruct } from 'inversify'; import URI from '@theia/core/lib/common/uri'; -import { FileSystem } from '@theia/filesystem/lib/common'; +import { Path } from '@theia/core/lib/common/path'; +import { FileSystem, FileStat } from '@theia/filesystem/lib/common'; import { Disposable, DisposableCollection } from '@theia/core/lib/common/disposable'; import { ApplicationShell, NavigatableWidget } from '@theia/core/lib/browser'; -import { VariableContribution, VariableRegistry } from '@theia/variable-resolver/lib/browser'; +import { VariableContribution, VariableRegistry, Variable } from '@theia/variable-resolver/lib/browser'; import { WorkspaceService } from './workspace-service'; @injectable() @@ -60,38 +61,10 @@ export class WorkspaceVariableContribution implements VariableContribution { } registerVariables(variables: VariableRegistry): void { - variables.registerVariable({ - name: 'workspaceRoot', - description: 'The path of the workspace root folder', - resolve: (context?: URI) => { - const uri = this.getWorkspaceRootUri(context); - return uri && this.fileSystem.getFsPath(uri.toString()); - } - }); - variables.registerVariable({ - name: 'workspaceFolder', - description: 'The path of the workspace root folder', - resolve: (context?: URI) => { - const uri = this.getWorkspaceRootUri(context); - return uri && this.fileSystem.getFsPath(uri.toString()); - } - }); - variables.registerVariable({ - name: 'workspaceFolderBasename', - description: 'The name of the workspace root folder', - resolve: (context?: URI) => { - const uri = this.getWorkspaceRootUri(context); - return uri && uri.displayName; - } - }); - variables.registerVariable({ - name: 'cwd', - description: 'The path of the current working directory', - resolve: (context?: URI) => { - const uri = this.getWorkspaceRootUri(context); - return (uri && this.fileSystem.getFsPath(uri.toString())) || ''; - } - }); + this.registerWorkspaceRootVariables(variables); + this.updateWorkspaceRootVariables(variables); + this.workspaceService.onWorkspaceChanged(() => this.updateWorkspaceRootVariables(variables)); + variables.registerVariable({ name: 'file', description: 'The path of the currently opened file', @@ -132,14 +105,88 @@ export class WorkspaceVariableContribution implements VariableContribution { return uri && uri.path.ext; } }); - variables.registerVariable({ - name: 'relativeFile', - description: "The currently opened file's path relative to the workspace root", - resolve: () => { - const uri = this.getResourceUri(); - return uri && this.getWorkspaceRelativePath(uri); + } + + protected readonly toDisposeOnUpdateWorkspaceRootVariables = new DisposableCollection(); + + protected updateWorkspaceRootVariables(variables: VariableRegistry): void { + this.toDisposeOnUpdateWorkspaceRootVariables.dispose(); + for (const root of this.workspaceService.tryGetRoots()) { + this.toDisposeOnUpdateWorkspaceRootVariables.push(this.registerWorkspaceRootVariables(variables, root)); + } + } + + protected registerWorkspaceRootVariables(variables: VariableRegistry, workspaceRoot?: FileStat): Disposable { + const scoped = (variable: Variable) => { + if (!workspaceRoot) { + return variable; } - }); + const workspaceRootUri = new URI(workspaceRoot.uri); + return { + name: variable.name + ':' + workspaceRootUri.path.name, + description: variable.description, + resolve: () => variable.resolve(workspaceRootUri) + }; + }; + return new DisposableCollection( + variables.registerVariable(scoped({ + name: 'workspaceRoot', + description: 'The path of the workspace root folder', + resolve: (context?: URI) => { + const uri = this.getWorkspaceRootUri(context); + return uri && this.fileSystem.getFsPath(uri.toString()); + } + })), + variables.registerVariable(scoped({ + name: 'workspaceFolder', + description: 'The path of the workspace root folder', + resolve: (context?: URI) => { + const uri = this.getWorkspaceRootUri(context); + return uri && this.fileSystem.getFsPath(uri.toString()); + } + })), + variables.registerVariable(scoped({ + name: 'workspaceRootFolderName', + description: 'The name of the workspace root folder', + resolve: (context?: URI) => { + const uri = this.getWorkspaceRootUri(context); + return uri && uri.displayName; + } + })), + variables.registerVariable(scoped({ + name: 'workspaceFolderBasename', + description: 'The name of the workspace root folder', + resolve: (context?: URI) => { + const uri = this.getWorkspaceRootUri(context); + return uri && uri.displayName; + } + })), + variables.registerVariable(scoped({ + name: 'cwd', + description: "The task runner's current working directory on startup", + resolve: (context?: URI) => { + const uri = this.getWorkspaceRootUri(context); + return (uri && this.fileSystem.getFsPath(uri.toString())) || ''; + } + })), + variables.registerVariable(scoped({ + name: 'relativeFile', + description: "The currently opened file's path relative to the workspace root", + resolve: (context?: URI) => { + const uri = this.getResourceUri(); + return uri && this.getWorkspaceRelativePath(uri, context); + } + })), + variables.registerVariable(scoped({ + name: 'relativeFileDirname', + description: "The current opened file's dirname relative to ${workspaceFolder}", + resolve: (context?: URI) => { + const uri = this.getResourceUri(); + const relativePath = uri && this.getWorkspaceRelativePath(uri, context); + return relativePath && new Path(relativePath).dir.toString(); + } + })) + ); } getWorkspaceRootUri(uri: URI | undefined = this.getResourceUri()): URI | undefined { @@ -150,8 +197,8 @@ export class WorkspaceVariableContribution implements VariableContribution { return this.currentWidget && this.currentWidget.getResourceUri(); } - getWorkspaceRelativePath(uri: URI): string | undefined { - const workspaceRootUri = this.getWorkspaceRootUri(uri); + getWorkspaceRelativePath(uri: URI, context?: URI): string | undefined { + const workspaceRootUri = this.getWorkspaceRootUri(context || uri); const path = workspaceRootUri && workspaceRootUri.path.relative(uri.path); return path && path.toString(); } From 76faecbc487fa3ac74b387e92d084ef06891c0aa Mon Sep 17 00:00:00 2001 From: Anton Kosyakov Date: Thu, 1 Aug 2019 09:29:30 +0000 Subject: [PATCH 4/9] [quick-variable] show value on open in order to simplify testing of variable substitution Signed-off-by: Anton Kosyakov --- CHANGELOG.md | 9 ++-- .../browser/variable-quick-open-service.ts | 46 +++++++++---------- ...ble-resolver-frontend-contribution.spec.ts | 5 +- 3 files changed, 28 insertions(+), 32 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b6b075e725d5..4861220896e8c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,11 +17,10 @@ Breaking changes: - [plugin] files from 'plugin-ext/src/api' moved to 'plugin-ext/src/common', renamed 'model.ts' to 'plugin-api-rpc-model.ts', 'plugin-api.ts' to 'plugin-api-rpc.ts' - [task] ensure that plugin tasks are registered before accessing them [5869](https://github.com/theia-ide/theia/pull/5869) - `TaskProviderRegistry` and `TaskResolverRegistry` are promisified - -Breaking changes: - - [shell][plugin] integrated view containers and views [#5665](https://github.com/theia-ide/theia/pull/5665) - - `Source Control` and `Explorer` are view containers now and previous layout data cannot be loaded for them. Because of it the layout is completely reset. - +- [shell][plugin] integrated view containers and views [#5665](https://github.com/theia-ide/theia/pull/5665) + - `Source Control` and `Explorer` are view containers now and previous layout data cannot be loaded for them. Because of it the layout is completely reset. +- [vscode] complete support of variable substitution [#5835](https://github.com/theia-ide/theia/pull/5835) + - inline `VariableQuickOpenItem` ## v0.9.0 - [core] added `theia-widget-noInfo` css class to be used by widgets when displaying no information messages [#5717](https://github.com/theia-ide/theia/pull/5717) diff --git a/packages/variable-resolver/src/browser/variable-quick-open-service.ts b/packages/variable-resolver/src/browser/variable-quick-open-service.ts index c1738fccf4018..d97479cabab51 100644 --- a/packages/variable-resolver/src/browser/variable-quick-open-service.ts +++ b/packages/variable-resolver/src/browser/variable-quick-open-service.ts @@ -15,23 +15,35 @@ ********************************************************************************/ import { inject, injectable } from 'inversify'; -import { QuickOpenService, QuickOpenModel, QuickOpenItem, QuickOpenMode } from '@theia/core/lib/browser/quick-open/'; -import { VariableRegistry } from './variable'; +import { MessageService } from '@theia/core/lib/common/message-service'; +import { QuickOpenService, QuickOpenModel, QuickOpenItem, QuickOpenMode } from '@theia/core/lib/browser/quick-open'; +import { VariableRegistry, Variable } from './variable'; @injectable() export class VariableQuickOpenService implements QuickOpenModel { protected items: QuickOpenItem[]; + @inject(MessageService) + protected readonly messages: MessageService; + constructor( @inject(VariableRegistry) protected readonly variableRegistry: VariableRegistry, @inject(QuickOpenService) protected readonly quickOpenService: QuickOpenService ) { } open(): void { - this.items = this.variableRegistry.getVariables().map( - v => new VariableQuickOpenItem(v.name, v.description) - ); + this.items = this.variableRegistry.getVariables().map(v => new QuickOpenItem({ + label: '${' + v.name + '}', + detail: v.description, + run: mode => { + if (mode === QuickOpenMode.OPEN) { + this.showValue(v); + return true; + } + return false; + } + })); this.quickOpenService.open(this, { placeholder: 'Registered variables', @@ -44,26 +56,12 @@ export class VariableQuickOpenService implements QuickOpenModel { onType(lookFor: string, acceptor: (items: QuickOpenItem[]) => void): void { acceptor(this.items); } -} - -export class VariableQuickOpenItem extends QuickOpenItem { - constructor( - protected readonly name: string, - protected readonly description?: string - ) { - super(); + protected async showValue(variable: Variable): Promise { + const value = await variable.resolve(); + if (value) { + this.messages.info(value); + } } - getLabel(): string { - return '${' + this.name + '}'; - } - - getDetail(): string { - return this.description || ''; - } - - run(mode: QuickOpenMode): boolean { - return false; - } } diff --git a/packages/variable-resolver/src/browser/variable-resolver-frontend-contribution.spec.ts b/packages/variable-resolver/src/browser/variable-resolver-frontend-contribution.spec.ts index 692e986801e4f..865a830878a2b 100644 --- a/packages/variable-resolver/src/browser/variable-resolver-frontend-contribution.spec.ts +++ b/packages/variable-resolver/src/browser/variable-resolver-frontend-contribution.spec.ts @@ -20,7 +20,6 @@ let disableJSDOM = enableJSDOM(); import * as chai from 'chai'; import { Container, ContainerModule } from 'inversify'; -import { QuickOpenService } from '@theia/core/lib/browser'; import { ILogger, bindContributionProvider } from '@theia/core/lib/common'; import { MockLogger } from '@theia/core/lib/common/test/mock-logger'; import { VariableContribution, VariableRegistry } from './variable'; @@ -52,8 +51,8 @@ describe('variable-resolver-frontend-contribution', () => { bind(ILogger).to(MockLogger); bind(VariableRegistry).toSelf().inSingletonScope(); - bind(QuickOpenService).toSelf(); - bind(VariableQuickOpenService).toSelf(); + // tslint:disable-next-line:no-any mocking VariableQuickOpenService + bind(VariableQuickOpenService).toConstantValue({} as any); bind(VariableResolverFrontendContribution).toSelf(); }); From 942f087e37c8ac530b03579a4009852cd78efb6f Mon Sep 17 00:00:00 2001 From: Anton Kosyakov Date: Thu, 1 Aug 2019 09:58:18 +0000 Subject: [PATCH 5/9] [vscode] support `config` variable substitution See https://code.visualstudio.com/docs/editor/variables-reference#_configuration-variables Signed-off-by: Anton Kosyakov --- .../workspace-variable-contribution.ts | 45 ++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/packages/workspace/src/browser/workspace-variable-contribution.ts b/packages/workspace/src/browser/workspace-variable-contribution.ts index a9368de379b40..04eced80e8721 100644 --- a/packages/workspace/src/browser/workspace-variable-contribution.ts +++ b/packages/workspace/src/browser/workspace-variable-contribution.ts @@ -15,11 +15,12 @@ ********************************************************************************/ import { injectable, inject, postConstruct } from 'inversify'; +import { JSONExt, ReadonlyJSONValue } from '@phosphor/coreutils/lib/json'; import URI from '@theia/core/lib/common/uri'; import { Path } from '@theia/core/lib/common/path'; import { FileSystem, FileStat } from '@theia/filesystem/lib/common'; import { Disposable, DisposableCollection } from '@theia/core/lib/common/disposable'; -import { ApplicationShell, NavigatableWidget } from '@theia/core/lib/browser'; +import { ApplicationShell, NavigatableWidget, PreferenceSchemaProvider, PreferenceService } from '@theia/core/lib/browser'; import { VariableContribution, VariableRegistry, Variable } from '@theia/variable-resolver/lib/browser'; import { WorkspaceService } from './workspace-service'; @@ -32,6 +33,10 @@ export class WorkspaceVariableContribution implements VariableContribution { protected readonly shell: ApplicationShell; @inject(FileSystem) protected readonly fileSystem: FileSystem; + @inject(PreferenceSchemaProvider) + protected readonly preferenceSchema: PreferenceSchemaProvider; + @inject(PreferenceService) + protected readonly preferences: PreferenceService; protected currentWidget: NavigatableWidget | undefined; @@ -65,6 +70,9 @@ export class WorkspaceVariableContribution implements VariableContribution { this.updateWorkspaceRootVariables(variables); this.workspaceService.onWorkspaceChanged(() => this.updateWorkspaceRootVariables(variables)); + this.updatePreferenceVariables(variables); + this.preferenceSchema.onDidPreferenceSchemaChanged(() => this.updatePreferenceVariables(variables)); + variables.registerVariable({ name: 'file', description: 'The path of the currently opened file', @@ -202,4 +210,39 @@ export class WorkspaceVariableContribution implements VariableContribution { const path = workspaceRootUri && workspaceRootUri.path.relative(uri.path); return path && path.toString(); } + + protected readonly toDisposeOnUpdatePreferenceVariables = new DisposableCollection(); + + protected updatePreferenceVariables(variables: VariableRegistry): void { + this.toDisposeOnUpdatePreferenceVariables.dispose(); + for (const preferenceName of this.preferenceSchema.getPreferenceNames()) { + this.toDisposeOnUpdatePreferenceVariables.push(this.registerPreferenceVariable(variables, preferenceName)); + } + } + + protected registerPreferenceVariable(variables: VariableRegistry, preferenceName: string): Disposable { + if (!preferenceName) { + return Disposable.NULL; + } + const name = 'config:' + preferenceName; + if (variables.getVariable(name)) { + return Disposable.NULL; + } + const toDispose = new DisposableCollection( + variables.registerVariable({ + name, + resolve: context => { + const resourceUri = context || this.getResourceUri(); + const value = this.preferences.get(preferenceName, undefined, resourceUri && resourceUri.toString()); + return value !== undefined && value !== null && JSONExt.isPrimitive(value) ? String(value) : undefined; + } + }) + ); + const index = preferenceName.lastIndexOf('.'); + if (index !== -1) { + toDispose.push(this.registerPreferenceVariable(variables, preferenceName.substr(0, index))); + } + return toDispose; + } + } From 169e370125a57a3c55afe75efb2196cc8c531200 Mon Sep 17 00:00:00 2001 From: Anton Kosyakov Date: Thu, 1 Aug 2019 10:34:04 +0000 Subject: [PATCH 6/9] [variable-resolver] match argument to simplify contributions Signed-off-by: Anton Kosyakov --- .../src/browser/env-variable-contribution.ts | 13 +- .../browser/variable-quick-open-service.ts | 12 +- .../src/browser/variable-resolver-service.ts | 11 +- .../variable-resolver/src/browser/variable.ts | 2 +- .../workspace-variable-contribution.ts | 199 +++++++----------- 5 files changed, 104 insertions(+), 133 deletions(-) diff --git a/packages/variable-resolver/src/browser/env-variable-contribution.ts b/packages/variable-resolver/src/browser/env-variable-contribution.ts index 0d8b910802566..9e1e88da52713 100644 --- a/packages/variable-resolver/src/browser/env-variable-contribution.ts +++ b/packages/variable-resolver/src/browser/env-variable-contribution.ts @@ -30,12 +30,13 @@ export class EnvVariableContribution implements VariableContribution { name: 'execPath', resolve: () => execPath }); - for (const variable of await this.env.getVariables()) { - variables.registerVariable({ - name: 'env:' + variable.name, - resolve: () => variable.value - }); - } + variables.registerVariable({ + name: 'env', + resolve: async (_, argument) => { + const envVariable = argument && await this.env.getValue(argument); + return envVariable && envVariable.value; + } + }); } } diff --git a/packages/variable-resolver/src/browser/variable-quick-open-service.ts b/packages/variable-resolver/src/browser/variable-quick-open-service.ts index d97479cabab51..a3aa6da9141fd 100644 --- a/packages/variable-resolver/src/browser/variable-quick-open-service.ts +++ b/packages/variable-resolver/src/browser/variable-quick-open-service.ts @@ -16,7 +16,7 @@ import { inject, injectable } from 'inversify'; import { MessageService } from '@theia/core/lib/common/message-service'; -import { QuickOpenService, QuickOpenModel, QuickOpenItem, QuickOpenMode } from '@theia/core/lib/browser/quick-open'; +import { QuickOpenService, QuickOpenModel, QuickOpenItem, QuickOpenMode, QuickInputService } from '@theia/core/lib/browser/quick-open'; import { VariableRegistry, Variable } from './variable'; @injectable() @@ -27,6 +27,9 @@ export class VariableQuickOpenService implements QuickOpenModel { @inject(MessageService) protected readonly messages: MessageService; + @inject(QuickInputService) + protected readonly quickInputService: QuickInputService; + constructor( @inject(VariableRegistry) protected readonly variableRegistry: VariableRegistry, @inject(QuickOpenService) protected readonly quickOpenService: QuickOpenService @@ -38,7 +41,7 @@ export class VariableQuickOpenService implements QuickOpenModel { detail: v.description, run: mode => { if (mode === QuickOpenMode.OPEN) { - this.showValue(v); + setTimeout(() => this.showValue(v)); return true; } return false; @@ -58,7 +61,10 @@ export class VariableQuickOpenService implements QuickOpenModel { } protected async showValue(variable: Variable): Promise { - const value = await variable.resolve(); + const argument = await this.quickInputService.open({ + placeHolder: 'Type a variable argument' + }); + const value = await variable.resolve(undefined, argument); if (value) { this.messages.info(value); } diff --git a/packages/variable-resolver/src/browser/variable-resolver-service.ts b/packages/variable-resolver/src/browser/variable-resolver-service.ts index c35a1c058ef78..41db65d164443 100644 --- a/packages/variable-resolver/src/browser/variable-resolver-service.ts +++ b/packages/variable-resolver/src/browser/variable-resolver-service.ts @@ -131,8 +131,15 @@ export namespace VariableResolverService { return; } try { - const variable = this.variableRegistry.getVariable(name); - const value = variable && await variable.resolve(this.options.context); + let variableName = name; + let argument: string | undefined; + const parts = name.split(':'); + if (parts.length > 1) { + variableName = parts[0]; + argument = parts[1]; + } + const variable = this.variableRegistry.getVariable(variableName); + const value = variable && await variable.resolve(this.options.context, argument); this.resolved.set(name, value); } catch (e) { console.error(`Failed to resolved '${name}' variable`, e); diff --git a/packages/variable-resolver/src/browser/variable.ts b/packages/variable-resolver/src/browser/variable.ts index 63bf0515d34ad..40ba9a8ad4163 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): MaybePromise; + resolve(context?: URI, argment?: 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 04eced80e8721..9bfcc66ae0bb0 100644 --- a/packages/workspace/src/browser/workspace-variable-contribution.ts +++ b/packages/workspace/src/browser/workspace-variable-contribution.ts @@ -18,9 +18,9 @@ import { injectable, inject, postConstruct } from 'inversify'; import { JSONExt, ReadonlyJSONValue } from '@phosphor/coreutils/lib/json'; import URI from '@theia/core/lib/common/uri'; import { Path } from '@theia/core/lib/common/path'; -import { FileSystem, FileStat } from '@theia/filesystem/lib/common'; +import { FileSystem } from '@theia/filesystem/lib/common'; import { Disposable, DisposableCollection } from '@theia/core/lib/common/disposable'; -import { ApplicationShell, NavigatableWidget, PreferenceSchemaProvider, PreferenceService } from '@theia/core/lib/browser'; +import { ApplicationShell, NavigatableWidget, PreferenceService } from '@theia/core/lib/browser'; import { VariableContribution, VariableRegistry, Variable } from '@theia/variable-resolver/lib/browser'; import { WorkspaceService } from './workspace-service'; @@ -33,8 +33,6 @@ export class WorkspaceVariableContribution implements VariableContribution { protected readonly shell: ApplicationShell; @inject(FileSystem) protected readonly fileSystem: FileSystem; - @inject(PreferenceSchemaProvider) - protected readonly preferenceSchema: PreferenceSchemaProvider; @inject(PreferenceService) protected readonly preferences: PreferenceService; @@ -67,11 +65,18 @@ export class WorkspaceVariableContribution implements VariableContribution { registerVariables(variables: VariableRegistry): void { this.registerWorkspaceRootVariables(variables); - this.updateWorkspaceRootVariables(variables); - this.workspaceService.onWorkspaceChanged(() => this.updateWorkspaceRootVariables(variables)); - this.updatePreferenceVariables(variables); - this.preferenceSchema.onDidPreferenceSchemaChanged(() => this.updatePreferenceVariables(variables)); + variables.registerVariable({ + name: 'config', + resolve: (context, preferenceName) => { + if (!preferenceName) { + return undefined; + } + const resourceUri = context || this.getResourceUri(); + const value = this.preferences.get(preferenceName, undefined, resourceUri && resourceUri.toString()); + return value !== undefined && value !== null && JSONExt.isPrimitive(value) ? String(value) : undefined; + } + }); variables.registerVariable({ name: 'file', @@ -115,86 +120,72 @@ export class WorkspaceVariableContribution implements VariableContribution { }); } - protected readonly toDisposeOnUpdateWorkspaceRootVariables = new DisposableCollection(); - - protected updateWorkspaceRootVariables(variables: VariableRegistry): void { - this.toDisposeOnUpdateWorkspaceRootVariables.dispose(); - for (const root of this.workspaceService.tryGetRoots()) { - this.toDisposeOnUpdateWorkspaceRootVariables.push(this.registerWorkspaceRootVariables(variables, root)); - } - } - - protected registerWorkspaceRootVariables(variables: VariableRegistry, workspaceRoot?: FileStat): Disposable { - const scoped = (variable: Variable) => { - if (!workspaceRoot) { - return variable; + protected registerWorkspaceRootVariables(variables: VariableRegistry): void { + const scoped = (variable: Variable): Variable => ({ + name: variable.name, + description: variable.description, + resolve: (context, argument) => { + const workspaceRoot = argument && this.workspaceService.tryGetRoots().find(r => new URI(r.uri).path.name === argument); + return variable.resolve(workspaceRoot ? new URI(workspaceRoot.uri) : context); } - const workspaceRootUri = new URI(workspaceRoot.uri); - return { - name: variable.name + ':' + workspaceRootUri.path.name, - description: variable.description, - resolve: () => variable.resolve(workspaceRootUri) - }; - }; - return new DisposableCollection( - variables.registerVariable(scoped({ - name: 'workspaceRoot', - description: 'The path of the workspace root folder', - resolve: (context?: URI) => { - const uri = this.getWorkspaceRootUri(context); - return uri && this.fileSystem.getFsPath(uri.toString()); - } - })), - variables.registerVariable(scoped({ - name: 'workspaceFolder', - description: 'The path of the workspace root folder', - resolve: (context?: URI) => { - const uri = this.getWorkspaceRootUri(context); - return uri && this.fileSystem.getFsPath(uri.toString()); - } - })), - variables.registerVariable(scoped({ - name: 'workspaceRootFolderName', - description: 'The name of the workspace root folder', - resolve: (context?: URI) => { - const uri = this.getWorkspaceRootUri(context); - return uri && uri.displayName; - } - })), - variables.registerVariable(scoped({ - name: 'workspaceFolderBasename', - description: 'The name of the workspace root folder', - resolve: (context?: URI) => { - const uri = this.getWorkspaceRootUri(context); - return uri && uri.displayName; - } - })), - variables.registerVariable(scoped({ - name: 'cwd', - description: "The task runner's current working directory on startup", - resolve: (context?: URI) => { - const uri = this.getWorkspaceRootUri(context); - return (uri && this.fileSystem.getFsPath(uri.toString())) || ''; - } - })), - variables.registerVariable(scoped({ - name: 'relativeFile', - description: "The currently opened file's path relative to the workspace root", - resolve: (context?: URI) => { - const uri = this.getResourceUri(); - return uri && this.getWorkspaceRelativePath(uri, context); - } - })), - variables.registerVariable(scoped({ - name: 'relativeFileDirname', - description: "The current opened file's dirname relative to ${workspaceFolder}", - resolve: (context?: URI) => { - const uri = this.getResourceUri(); - const relativePath = uri && this.getWorkspaceRelativePath(uri, context); - return relativePath && new Path(relativePath).dir.toString(); - } - })) - ); + }); + variables.registerVariable(scoped({ + name: 'workspaceRoot', + description: 'The path of the workspace root folder', + resolve: (context?: URI) => { + const uri = this.getWorkspaceRootUri(context); + return uri && this.fileSystem.getFsPath(uri.toString()); + } + })); + variables.registerVariable(scoped({ + name: 'workspaceFolder', + description: 'The path of the workspace root folder', + resolve: (context?: URI) => { + const uri = this.getWorkspaceRootUri(context); + return uri && this.fileSystem.getFsPath(uri.toString()); + } + })); + variables.registerVariable(scoped({ + name: 'workspaceRootFolderName', + description: 'The name of the workspace root folder', + resolve: (context?: URI) => { + const uri = this.getWorkspaceRootUri(context); + return uri && uri.displayName; + } + })); + variables.registerVariable(scoped({ + name: 'workspaceFolderBasename', + description: 'The name of the workspace root folder', + resolve: (context?: URI) => { + const uri = this.getWorkspaceRootUri(context); + return uri && uri.displayName; + } + })); + variables.registerVariable(scoped({ + name: 'cwd', + description: "The task runner's current working directory on startup", + resolve: (context?: URI) => { + const uri = this.getWorkspaceRootUri(context); + return (uri && this.fileSystem.getFsPath(uri.toString())) || ''; + } + })); + variables.registerVariable(scoped({ + name: 'relativeFile', + description: "The currently opened file's path relative to the workspace root", + resolve: (context?: URI) => { + const uri = this.getResourceUri(); + return uri && this.getWorkspaceRelativePath(uri, context); + } + })); + variables.registerVariable(scoped({ + name: 'relativeFileDirname', + description: "The current opened file's dirname relative to ${workspaceFolder}", + resolve: (context?: URI) => { + const uri = this.getResourceUri(); + const relativePath = uri && this.getWorkspaceRelativePath(uri, context); + return relativePath && new Path(relativePath).dir.toString(); + } + })); } getWorkspaceRootUri(uri: URI | undefined = this.getResourceUri()): URI | undefined { @@ -211,38 +202,4 @@ export class WorkspaceVariableContribution implements VariableContribution { return path && path.toString(); } - protected readonly toDisposeOnUpdatePreferenceVariables = new DisposableCollection(); - - protected updatePreferenceVariables(variables: VariableRegistry): void { - this.toDisposeOnUpdatePreferenceVariables.dispose(); - for (const preferenceName of this.preferenceSchema.getPreferenceNames()) { - this.toDisposeOnUpdatePreferenceVariables.push(this.registerPreferenceVariable(variables, preferenceName)); - } - } - - protected registerPreferenceVariable(variables: VariableRegistry, preferenceName: string): Disposable { - if (!preferenceName) { - return Disposable.NULL; - } - const name = 'config:' + preferenceName; - if (variables.getVariable(name)) { - return Disposable.NULL; - } - const toDispose = new DisposableCollection( - variables.registerVariable({ - name, - resolve: context => { - const resourceUri = context || this.getResourceUri(); - const value = this.preferences.get(preferenceName, undefined, resourceUri && resourceUri.toString()); - return value !== undefined && value !== null && JSONExt.isPrimitive(value) ? String(value) : undefined; - } - }) - ); - const index = preferenceName.lastIndexOf('.'); - if (index !== -1) { - toDispose.push(this.registerPreferenceVariable(variables, preferenceName.substr(0, index))); - } - return toDispose; - } - } From 910dd8ead2d0dde900951e4503c0382e558d56b7 Mon Sep 17 00:00:00 2001 From: Anton Kosyakov Date: Thu, 1 Aug 2019 11:04:15 +0000 Subject: [PATCH 7/9] [vscode] support `command` variable substitution See https://code.visualstudio.com/docs/editor/variables-reference#_command-variables Signed-off-by: Anton Kosyakov --- ...bution.ts => common-variable-contribution.ts} | 16 +++++++++++++--- .../src/browser/variable-quick-open-service.ts | 12 +++++++++--- .../browser/variable-resolver-frontend-module.ts | 6 +++--- .../src/browser/variable-resolver-service.ts | 4 +++- .../variable-resolver/src/browser/variable.ts | 2 +- .../browser/workspace-variable-contribution.ts | 8 +++----- 6 files changed, 32 insertions(+), 16 deletions(-) rename packages/variable-resolver/src/browser/{env-variable-contribution.ts => common-variable-contribution.ts} (70%) diff --git a/packages/variable-resolver/src/browser/env-variable-contribution.ts b/packages/variable-resolver/src/browser/common-variable-contribution.ts similarity index 70% rename from packages/variable-resolver/src/browser/env-variable-contribution.ts rename to packages/variable-resolver/src/browser/common-variable-contribution.ts index 9e1e88da52713..f6a92fa8be40e 100644 --- a/packages/variable-resolver/src/browser/env-variable-contribution.ts +++ b/packages/variable-resolver/src/browser/common-variable-contribution.ts @@ -17,13 +17,17 @@ 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'; @injectable() -export class EnvVariableContribution implements VariableContribution { +export class CommonVariableContribution implements VariableContribution { @inject(EnvVariablesServer) protected readonly env: EnvVariablesServer; + @inject(CommandService) + protected readonly commands: CommandService; + async registerVariables(variables: VariableRegistry): Promise { const execPath = await this.env.getExecPath(); variables.registerVariable({ @@ -32,11 +36,17 @@ export class EnvVariableContribution implements VariableContribution { }); variables.registerVariable({ name: 'env', - resolve: async (_, argument) => { - const envVariable = argument && await this.env.getValue(argument); + resolve: async (_, envVariableName) => { + const envVariable = envVariableName && await this.env.getValue(envVariableName); return envVariable && envVariable.value; } }); + variables.registerVariable({ + name: 'command', + resolve: async (_, command) => + // tslint:disable-next-line:no-return-await + command && await this.commands.executeCommand(command) + }); } } diff --git a/packages/variable-resolver/src/browser/variable-quick-open-service.ts b/packages/variable-resolver/src/browser/variable-quick-open-service.ts index a3aa6da9141fd..0943fde4569b8 100644 --- a/packages/variable-resolver/src/browser/variable-quick-open-service.ts +++ b/packages/variable-resolver/src/browser/variable-quick-open-service.ts @@ -16,8 +16,11 @@ import { inject, injectable } from 'inversify'; import { MessageService } from '@theia/core/lib/common/message-service'; -import { QuickOpenService, QuickOpenModel, QuickOpenItem, QuickOpenMode, QuickInputService } from '@theia/core/lib/browser/quick-open'; +import { QuickOpenModel, QuickOpenItem, QuickOpenMode } from '@theia/core/lib/common/quick-open-model'; +import { QuickOpenService } from '@theia/core/lib/browser/quick-open/quick-open-service'; +import { QuickInputService } from '@theia/core/lib/browser/quick-open/quick-input-service'; import { VariableRegistry, Variable } from './variable'; +import { VariableResolverService } from './variable-resolver-service'; @injectable() export class VariableQuickOpenService implements QuickOpenModel { @@ -30,6 +33,9 @@ export class VariableQuickOpenService implements QuickOpenModel { @inject(QuickInputService) protected readonly quickInputService: QuickInputService; + @inject(VariableResolverService) + protected readonly variableResolver: VariableResolverService; + constructor( @inject(VariableRegistry) protected readonly variableRegistry: VariableRegistry, @inject(QuickOpenService) protected readonly quickOpenService: QuickOpenService @@ -64,8 +70,8 @@ export class VariableQuickOpenService implements QuickOpenModel { const argument = await this.quickInputService.open({ placeHolder: 'Type a variable argument' }); - const value = await variable.resolve(undefined, argument); - if (value) { + const value = await this.variableResolver.resolve('${' + variable.name + ':' + argument + '}'); + if (typeof value === 'string') { this.messages.info(value); } } diff --git a/packages/variable-resolver/src/browser/variable-resolver-frontend-module.ts b/packages/variable-resolver/src/browser/variable-resolver-frontend-module.ts index c3656032df801..a738a4b674d4e 100644 --- a/packages/variable-resolver/src/browser/variable-resolver-frontend-module.ts +++ b/packages/variable-resolver/src/browser/variable-resolver-frontend-module.ts @@ -21,7 +21,7 @@ import { VariableRegistry, VariableContribution } from './variable'; import { VariableQuickOpenService } from './variable-quick-open-service'; import { VariableResolverFrontendContribution } from './variable-resolver-frontend-contribution'; import { VariableResolverService } from './variable-resolver-service'; -import { EnvVariableContribution } from './env-variable-contribution'; +import { CommonVariableContribution } from './common-variable-contribution'; export default new ContainerModule(bind => { bind(VariableRegistry).toSelf().inSingletonScope(); @@ -35,6 +35,6 @@ export default new ContainerModule(bind => { bind(VariableQuickOpenService).toSelf().inSingletonScope(); - bind(EnvVariableContribution).toSelf().inSingletonScope(); - bind(VariableContribution).toService(EnvVariableContribution); + bind(CommonVariableContribution).toSelf().inSingletonScope(); + bind(VariableContribution).toService(CommonVariableContribution); }); diff --git a/packages/variable-resolver/src/browser/variable-resolver-service.ts b/packages/variable-resolver/src/browser/variable-resolver-service.ts index 41db65d164443..b6b1b9fd215fc 100644 --- a/packages/variable-resolver/src/browser/variable-resolver-service.ts +++ b/packages/variable-resolver/src/browser/variable-resolver-service.ts @@ -19,6 +19,7 @@ import { injectable, inject } from 'inversify'; import { VariableRegistry } from './variable'; import URI from '@theia/core/lib/common/uri'; +import { JSONExt, ReadonlyJSONValue } from '@phosphor/coreutils/lib/json'; export interface VariableResolveOptions { context?: URI; @@ -140,7 +141,8 @@ export namespace VariableResolverService { } const variable = this.variableRegistry.getVariable(variableName); const value = variable && await variable.resolve(this.options.context, argument); - this.resolved.set(name, value); + const stringValue = value !== undefined && value !== null && JSONExt.isPrimitive(value as ReadonlyJSONValue) ? String(value) : undefined; + this.resolved.set(name, stringValue); } catch (e) { console.error(`Failed to resolved '${name}' variable`, e); this.resolved.set(name, undefined); diff --git a/packages/variable-resolver/src/browser/variable.ts b/packages/variable-resolver/src/browser/variable.ts index 40ba9a8ad4163..12be028eba9ed 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): 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 9bfcc66ae0bb0..4864270ddae99 100644 --- a/packages/workspace/src/browser/workspace-variable-contribution.ts +++ b/packages/workspace/src/browser/workspace-variable-contribution.ts @@ -15,7 +15,6 @@ ********************************************************************************/ import { injectable, inject, postConstruct } from 'inversify'; -import { JSONExt, ReadonlyJSONValue } from '@phosphor/coreutils/lib/json'; import URI from '@theia/core/lib/common/uri'; import { Path } from '@theia/core/lib/common/path'; import { FileSystem } from '@theia/filesystem/lib/common'; @@ -73,8 +72,7 @@ export class WorkspaceVariableContribution implements VariableContribution { return undefined; } const resourceUri = context || this.getResourceUri(); - const value = this.preferences.get(preferenceName, undefined, resourceUri && resourceUri.toString()); - return value !== undefined && value !== null && JSONExt.isPrimitive(value) ? String(value) : undefined; + return this.preferences.get(preferenceName, undefined, resourceUri && resourceUri.toString()); } }); @@ -124,8 +122,8 @@ export class WorkspaceVariableContribution implements VariableContribution { const scoped = (variable: Variable): Variable => ({ name: variable.name, description: variable.description, - resolve: (context, argument) => { - const workspaceRoot = argument && this.workspaceService.tryGetRoots().find(r => new URI(r.uri).path.name === argument); + resolve: (context, workspaceRootName) => { + const workspaceRoot = workspaceRootName && this.workspaceService.tryGetRoots().find(r => new URI(r.uri).path.name === workspaceRootName); return variable.resolve(workspaceRoot ? new URI(workspaceRoot.uri) : context); } }); From 2c97bc588c19de816350c51734675f5fdcfbe05d Mon Sep 17 00:00:00 2001 From: Anton Kosyakov Date: Thu, 1 Aug 2019 15:09:28 +0000 Subject: [PATCH 8/9] [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 dd5a52ee319f1..a7274cee84b86 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 4864270ddae99..6721829a1342f 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(); } From 010d035d65b47a8d0019b91ceed5919b9d0ba85d Mon Sep 17 00:00:00 2001 From: Anton Kosyakov Date: Sun, 11 Aug 2019 20:21:35 +0000 Subject: [PATCH 9/9] [vscode] fix resolution of env variables - if an env variable does not exist it should resolve to an empty string to emulate the shell - normalize env variable name to lower case in order to retrieve them from process.env on windows Signed-off-by: Anton Kosyakov --- packages/core/src/node/env-variables/env-variables-server.ts | 4 ++++ .../src/browser/common-variable-contribution.ts | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/core/src/node/env-variables/env-variables-server.ts b/packages/core/src/node/env-variables/env-variables-server.ts index a878c75ec718d..166de48dedfed 100644 --- a/packages/core/src/node/env-variables/env-variables-server.ts +++ b/packages/core/src/node/env-variables/env-variables-server.ts @@ -16,6 +16,7 @@ import { injectable } from 'inversify'; import { EnvVariable, EnvVariablesServer } from '../../common/env-variables'; +import { isWindows } from '../../common/os'; @injectable() export class EnvVariablesServerImpl implements EnvVariablesServer { @@ -38,6 +39,9 @@ export class EnvVariablesServerImpl implements EnvVariablesServer { } async getValue(key: string): Promise { + if (isWindows) { + key = key.toLowerCase(); + } return this.envs[key]; } } diff --git a/packages/variable-resolver/src/browser/common-variable-contribution.ts b/packages/variable-resolver/src/browser/common-variable-contribution.ts index 88533377474b4..4d09b988d44f7 100644 --- a/packages/variable-resolver/src/browser/common-variable-contribution.ts +++ b/packages/variable-resolver/src/browser/common-variable-contribution.ts @@ -56,7 +56,8 @@ export class CommonVariableContribution implements VariableContribution { name: 'env', resolve: async (_, envVariableName) => { const envVariable = envVariableName && await this.env.getValue(envVariableName); - return envVariable && envVariable.value; + const envValue = envVariable && envVariable.value; + return envValue || ''; } }); variables.registerVariable({