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/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..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 { @@ -25,15 +26,22 @@ 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]); } async getValue(key: string): Promise { + if (isWindows) { + key = key.toLowerCase(); + } return this.envs[key]; } } 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/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 { diff --git a/packages/variable-resolver/src/browser/common-variable-contribution.ts b/packages/variable-resolver/src/browser/common-variable-contribution.ts new file mode 100644 index 0000000000000..4d09b988d44f7 --- /dev/null +++ b/packages/variable-resolver/src/browser/common-variable-contribution.ts @@ -0,0 +1,134 @@ +/******************************************************************************** + * 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 + ********************************************************************************/ + +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 { + + @inject(EnvVariablesServer) + protected readonly env: EnvVariablesServer; + + @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({ + name: 'execPath', + resolve: () => execPath + }); + variables.registerVariable({ + name: 'env', + resolve: async (_, envVariableName) => { + const envVariable = envVariableName && await this.env.getValue(envVariableName); + const envValue = envVariable && envVariable.value; + return envValue || ''; + } + }); + 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/env-variable-contribution.ts b/packages/variable-resolver/src/browser/env-variable-contribution.ts deleted file mode 100644 index 2b71627aa33e1..0000000000000 --- a/packages/variable-resolver/src/browser/env-variable-contribution.ts +++ /dev/null @@ -1,36 +0,0 @@ -/******************************************************************************** - * 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 - ********************************************************************************/ - -import { injectable, inject } from 'inversify'; -import { VariableContribution, VariableRegistry } from './variable'; -import { EnvVariablesServer } from '@theia/core/lib/common/env-variables'; - -@injectable() -export class EnvVariableContribution implements VariableContribution { - - @inject(EnvVariablesServer) - protected readonly env: EnvVariablesServer; - - async registerVariables(variables: VariableRegistry): Promise { - for (const variable of await this.env.getVariables()) { - variables.registerVariable({ - name: 'env:' + variable.name, - resolve: () => variable.value - }); - } - } - -} 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-quick-open-service.ts b/packages/variable-resolver/src/browser/variable-quick-open-service.ts index c1738fccf4018..0943fde4569b8 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,44 @@ ********************************************************************************/ 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 { 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 { protected items: QuickOpenItem[]; + @inject(MessageService) + protected readonly messages: MessageService; + + @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 ) { } 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) { + setTimeout(() => this.showValue(v)); + return true; + } + return false; + } + })); this.quickOpenService.open(this, { placeholder: 'Registered variables', @@ -44,26 +65,15 @@ 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(); - } - - getLabel(): string { - return '${' + this.name + '}'; - } - getDetail(): string { - return this.description || ''; + protected async showValue(variable: Variable): Promise { + const argument = await this.quickInputService.open({ + placeHolder: 'Type a variable argument' + }); + const value = await this.variableResolver.resolve('${' + variable.name + ':' + argument + '}'); + if (typeof value === 'string') { + this.messages.info(value); + } } - 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(); }); 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 c35a1c058ef78..0b0fb157bc112 100644 --- a/packages/variable-resolver/src/browser/variable-resolver-service.ts +++ b/packages/variable-resolver/src/browser/variable-resolver-service.ts @@ -19,9 +19,14 @@ 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; + /** + * Used for resolving inputs, see https://code.visualstudio.com/docs/editor/variables-reference#_input-variables + */ + configurationSection?: string; } /** @@ -131,9 +136,17 @@ export namespace VariableResolverService { return; } try { - const variable = this.variableRegistry.getVariable(name); - const value = variable && await variable.resolve(this.options.context); - this.resolved.set(name, value); + 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.options.configurationSection); + 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 63bf0515d34ad..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): 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 3ec504150bf73..6721829a1342f 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 { 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 } 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,8 @@ 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); + variables.registerVariable({ name: 'file', description: 'The path of the currently opened file', @@ -132,14 +103,74 @@ export class WorkspaceVariableContribution implements VariableContribution { return uri && uri.path.ext; } }); - variables.registerVariable({ + } + + protected registerWorkspaceRootVariables(variables: VariableRegistry): void { + const scoped = (variable: Variable): Variable => ({ + name: variable.name, + description: variable.description, + 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); + } + }); + 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: () => { + resolve: (context?: URI) => { const uri = this.getResourceUri(); - return uri && this.getWorkspaceRelativePath(uri); + 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 { @@ -147,12 +178,14 @@ export class WorkspaceVariableContribution implements VariableContribution { } getResourceUri(): URI | undefined { + // TODO replace with ResourceContextKey.get? 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(); } + }