提交 86feb847 编写于 作者: D Dirk Baeumer

Make variable resolving for tasks async

上级 84782814
......@@ -491,7 +491,16 @@ export class MainThreadTask implements MainThreadTaskShape {
this._taskService.registerTaskSystem(key, {
platform: platform,
fileSystemScheme: key,
context: this._extHostContext
context: this._extHostContext,
resolveVariables: (workspaceFolder: IWorkspaceFolder, variables: Set<string>): TPromise<Map<string, string>> => {
let vars: string[] = [];
variables.forEach(item => vars.push(item));
return this._proxy.$resolveVariables(workspaceFolder.uri, vars).then(values => {
let result = new Map<string, string>();
Object.keys(values).forEach(key => result.set(key, values[key]));
return result;
});
}
});
}
}
......@@ -121,7 +121,7 @@ export function createApiFactory(
const extHostTerminalService = rpcProtocol.set(ExtHostContext.ExtHostTerminalService, new ExtHostTerminalService(rpcProtocol, extHostConfiguration, extHostLogService));
const extHostSCM = rpcProtocol.set(ExtHostContext.ExtHostSCM, new ExtHostSCM(rpcProtocol, extHostCommands, extHostLogService));
const extHostSearch = rpcProtocol.set(ExtHostContext.ExtHostSearch, new ExtHostSearch(rpcProtocol, schemeTransformer));
const extHostTask = rpcProtocol.set(ExtHostContext.ExtHostTask, new ExtHostTask(rpcProtocol, extHostWorkspace));
const extHostTask = rpcProtocol.set(ExtHostContext.ExtHostTask, new ExtHostTask(rpcProtocol, extHostWorkspace, extHostDocumentsAndEditors, extHostConfiguration));
const extHostWindow = rpcProtocol.set(ExtHostContext.ExtHostWindow, new ExtHostWindow(rpcProtocol));
rpcProtocol.set(ExtHostContext.ExtHostExtensionService, extensionService);
const extHostProgress = rpcProtocol.set(ExtHostContext.ExtHostProgress, new ExtHostProgress(rpcProtocol.getProxy(MainContext.MainThreadProgress)));
......@@ -138,7 +138,7 @@ export function createApiFactory(
const extHostLanguages = new ExtHostLanguages(rpcProtocol);
// Register API-ish commands
ExtHostApiCommands.register(extHostCommands, extHostTask);
ExtHostApiCommands.register(extHostCommands);
return function (extension: IExtensionDescription): typeof vscode {
......
......@@ -784,6 +784,7 @@ export interface ExtHostTaskShape {
$onDidStartTaskProcess(value: TaskProcessStartedDTO): void;
$onDidEndTaskProcess(value: TaskProcessEndedDTO): void;
$OnDidEndTask(execution: TaskExecutionDTO): void;
$resolveVariables(workspaceFolder: URI, variables: string[]): TPromise<any>;
}
export interface IBreakpointDto {
......
......@@ -18,22 +18,19 @@ import { ICommandHandlerDescription } from 'vs/platform/commands/common/commands
import { ExtHostCommands } from 'vs/workbench/api/node/extHostCommands';
import { IWorkspaceSymbolProvider } from 'vs/workbench/parts/search/common/search';
import { CustomCodeAction } from 'vs/workbench/api/node/extHostLanguageFeatures';
import { ExtHostTask } from './extHostTask';
import { ICommandsExecutor, PreviewHTMLAPICommand, OpenFolderAPICommand, DiffAPICommand, OpenAPICommand, RemoveFromRecentlyOpenedAPICommand } from './apiCommands';
export class ExtHostApiCommands {
static register(commands: ExtHostCommands, workspace: ExtHostTask) {
return new ExtHostApiCommands(commands, workspace).registerCommands();
static register(commands: ExtHostCommands) {
return new ExtHostApiCommands(commands).registerCommands();
}
private _commands: ExtHostCommands;
private _tasks: ExtHostTask;
private _disposables: IDisposable[] = [];
private constructor(commands: ExtHostCommands, task: ExtHostTask) {
private constructor(commands: ExtHostCommands) {
this._commands = commands;
this._tasks = task;
}
registerCommands() {
......@@ -176,11 +173,6 @@ export class ExtHostApiCommands {
],
returns: 'A promise that resolves to an array of DocumentLink-instances.'
});
this._register('vscode.executeTaskProvider', this._executeTaskProvider, {
description: 'Execute task provider',
args: [],
returns: 'An array of task handles'
});
this._register('vscode.executeDocumentColorProvider', this._executeDocumentColorProvider, {
description: 'Execute document color provider.',
args: [
......@@ -494,10 +486,6 @@ export class ExtHostApiCommands {
return this._commands.executeCommand<modes.ILink[]>('_executeLinkProvider', resource)
.then(tryMapWith(typeConverters.DocumentLink.to));
}
private _executeTaskProvider(): Thenable<vscode.Task[]> {
return this._tasks.fetchTasks();
}
}
function tryMapWith<T, R>(f: (x: T) => R) {
......
......@@ -23,6 +23,10 @@ import {
TaskDefinitionDTO, TaskExecutionDTO, TaskPresentationOptionsDTO, ProcessExecutionOptionsDTO, ProcessExecutionDTO,
ShellExecutionOptionsDTO, ShellExecutionDTO, TaskDTO, TaskHandleDTO, TaskFilterDTO, TaskProcessStartedDTO, TaskProcessEndedDTO, TaskSystemInfoDTO
} from '../shared/tasks';
import { ExtHostVariableResolverService } from 'vs/workbench/api/node/extHostDebugService';
import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/node/extHostDocumentsAndEditors';
import { ExtHostConfiguration } from 'vs/workbench/api/node/extHostConfiguration';
import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
export { TaskExecutionDTO };
......@@ -729,6 +733,8 @@ export class ExtHostTask implements ExtHostTaskShape {
private _proxy: MainThreadTaskShape;
private _workspaceService: ExtHostWorkspace;
private _editorService: ExtHostDocumentsAndEditors;
private _configurationService: ExtHostConfiguration;
private _handleCounter: number;
private _handlers: Map<number, HandlerData>;
private _taskExecutions: Map<string, TaskExecutionImpl>;
......@@ -739,9 +745,11 @@ export class ExtHostTask implements ExtHostTaskShape {
private readonly _onDidTaskProcessStarted: Emitter<vscode.TaskProcessStartEvent> = new Emitter<vscode.TaskProcessStartEvent>();
private readonly _onDidTaskProcessEnded: Emitter<vscode.TaskProcessEndEvent> = new Emitter<vscode.TaskProcessEndEvent>();
constructor(mainContext: IMainContext, workspaceService: ExtHostWorkspace) {
constructor(mainContext: IMainContext, workspaceService: ExtHostWorkspace, editorService: ExtHostDocumentsAndEditors, configurationService: ExtHostConfiguration) {
this._proxy = mainContext.getProxy(MainContext.MainThreadTask);
this._workspaceService = workspaceService;
this._editorService = editorService;
this._configurationService = configurationService;
this._handleCounter = 0;
this._handlers = new Map<number, HandlerData>();
this._taskExecutions = new Map<string, TaskExecutionImpl>();
......@@ -872,6 +880,24 @@ export class ExtHostTask implements ExtHostTaskShape {
});
}
public $resolveVariables(uri: URI, variables: string[]): any {
let result = Object.create(null);
let workspaceFolder = this._workspaceService.resolveWorkspaceFolder(uri);
let resolver = new ExtHostVariableResolverService(this._workspaceService, this._editorService, this._configurationService);
let ws: IWorkspaceFolder = {
uri: workspaceFolder.uri,
name: workspaceFolder.name,
index: workspaceFolder.index,
toResource: () => {
throw new Error('Not implemented');
}
};
for (let variable of variables) {
result.push(variable, resolver.resolve(ws, variable));
}
return result;
}
private nextHandle(): number {
return this._handleCounter++;
}
......
......@@ -106,6 +106,7 @@ export interface TaskSystemInfo {
fileSystemScheme: string;
platform: Platform;
context: any;
resolveVariables(workspaceFolder: IWorkspaceFolder, variables: Set<string>): TPromise<Map<string, string>>;
}
export interface TaskSystemInfoResovler {
......
......@@ -21,7 +21,7 @@ import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import * as TPath from 'vs/base/common/paths';
import { IMarkerService, MarkerSeverity } from 'vs/platform/markers/common/markers';
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { IWorkspaceContextService, WorkbenchState, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { IModelService } from 'vs/editor/common/services/modelService';
import { ProblemMatcher, ProblemMatcherRegistry /*, ProblemPattern, getResource */ } from 'vs/workbench/parts/tasks/common/problemMatcher';
......@@ -33,7 +33,7 @@ import { IOutputService, IOutputChannel } from 'vs/workbench/parts/output/common
import { StartStopProblemCollector, WatchingProblemCollector, ProblemCollectorEventKind } from 'vs/workbench/parts/tasks/common/problemCollectors';
import {
Task, CustomTask, ContributedTask, RevealKind, CommandOptions, ShellConfiguration, RuntimeType, PanelKind,
TaskEvent, TaskEventKind, ShellQuotingOptions, ShellQuoting, CommandString
TaskEvent, TaskEventKind, ShellQuotingOptions, ShellQuoting, CommandString, CommandConfiguration
} from 'vs/workbench/parts/tasks/common/tasks';
import {
ITaskSystem, ITaskSummary, ITaskExecuteResult, TaskExecuteKind, TaskError, TaskErrors, ITaskResolver,
......@@ -51,6 +51,24 @@ interface ActiveTerminalData {
promise: TPromise<ITaskSummary>;
}
class VariableResolver {
constructor(public workspaceFolder: IWorkspaceFolder, public taskSystemInfo: TaskSystemInfo | undefined, private _values: Map<string, string>, private _service: IConfigurationResolverService | undefined) {
}
resolve(value: string): string {
return value.replace(/\$\{(.*?)\}/g, (match: string, variable: string) => {
let result = this._values.get(match);
if (result) {
return result;
}
if (this._service) {
return this._service.resolve(this.workspaceFolder, match);
}
return match;
});
}
}
export class TerminalTaskSystem implements ITaskSystem {
public static TelemetryEventName: string = 'taskService';
......@@ -276,13 +294,36 @@ export class TerminalTaskSystem implements ITaskSystem {
}
private executeCommand(task: CustomTask | ContributedTask, trigger: string): TPromise<ITaskSummary> {
let variables = new Set<string>();
this.collectTaskVariables(variables, task);
let workspaceFolder = Task.getWorkspaceFolder(task);
let taskSystemInfo: TaskSystemInfo;
if (workspaceFolder) {
taskSystemInfo = this.taskSystemInfoResolver(workspaceFolder);
}
let resolvedVariables: TPromise<Map<string, string>>;
if (taskSystemInfo) {
resolvedVariables = taskSystemInfo.resolveVariables(workspaceFolder, variables);
} else {
let result = new Map<string, string>();
variables.forEach(variable => {
result.set(variable, this.configurationResolverService.resolve(workspaceFolder, variable));
});
resolvedVariables = TPromise.as(result);
}
return resolvedVariables.then((variables) => {
return this.executeInTerminal(task, trigger, new VariableResolver(workspaceFolder, undefined, variables, this.configurationResolverService));
});
}
private executeInTerminal(task: CustomTask | ContributedTask, trigger: string, resolver: VariableResolver): TPromise<ITaskSummary> {
let terminal: ITerminalInstance = undefined;
let executedCommand: string = undefined;
let error: TaskError = undefined;
let promise: TPromise<ITaskSummary> = undefined;
if (task.isBackground) {
promise = new TPromise<ITaskSummary>((resolve, reject) => {
const problemMatchers = this.resolveMatchers(task, task.problemMatchers);
const problemMatchers = this.resolveMatchers(resolver, task.problemMatchers);
let watchingProblemMatcher = new WatchingProblemCollector(problemMatchers, this.markerService, this.modelService);
let toUnbind: IDisposable[] = [];
let eventCounter: number = 0;
......@@ -304,7 +345,7 @@ export class TerminalTaskSystem implements ITaskSystem {
}));
watchingProblemMatcher.aboutToStart();
let delayer: Async.Delayer<any> = undefined;
[terminal, executedCommand, error] = this.createTerminal(task);
[terminal, executedCommand, error] = this.createTerminal(task, resolver);
if (error || !terminal) {
return;
}
......@@ -365,7 +406,7 @@ export class TerminalTaskSystem implements ITaskSystem {
});
} else {
promise = new TPromise<ITaskSummary>((resolve, reject) => {
[terminal, executedCommand, error] = this.createTerminal(task);
[terminal, executedCommand, error] = this.createTerminal(task, resolver);
if (error || !terminal) {
return;
}
......@@ -378,7 +419,7 @@ export class TerminalTaskSystem implements ITaskSystem {
});
this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.Start, task));
this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.Active, task));
let problemMatchers = this.resolveMatchers(task, task.problemMatchers);
let problemMatchers = this.resolveMatchers(resolver, task.problemMatchers);
let startStopProblemMatcher = new StartStopProblemCollector(problemMatchers, this.markerService, this.modelService);
const registeredLinkMatchers = this.registerLinkMatchers(terminal, problemMatchers);
const onData = terminal.onLineData((line) => {
......@@ -471,9 +512,9 @@ export class TerminalTaskSystem implements ITaskSystem {
});
}
private createTerminal(task: CustomTask | ContributedTask): [ITerminalInstance, string, TaskError | undefined] {
let options = this.resolveOptions(task, task.command.options);
let { command, args } = this.resolveCommandAndArgs(task);
private createTerminal(task: CustomTask | ContributedTask, resolver: VariableResolver): [ITerminalInstance, string, TaskError | undefined] {
let options = this.resolveOptions(resolver, task.command.options);
let { command, args } = this.resolveCommandAndArgs(resolver, task.command);
let commandExecutable = CommandString.value(command);
let workspaceFolder = Task.getWorkspaceFolder(task);
let needsFolderQualification = workspaceFolder && this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE;
......@@ -493,10 +534,10 @@ export class TerminalTaskSystem implements ITaskSystem {
let shellSpecified: boolean = false;
let shellOptions: ShellConfiguration = task.command.options && task.command.options.shell;
if (shellOptions && shellOptions.executable) {
shellLaunchConfig.executable = this.resolveVariable(task, shellOptions.executable);
shellLaunchConfig.executable = this.resolveVariable(resolver, shellOptions.executable);
shellSpecified = true;
if (shellOptions.args) {
shellLaunchConfig.args = this.resolveVariables(task, shellOptions.args.slice());
shellLaunchConfig.args = this.resolveVariables(resolver, shellOptions.args.slice());
} else {
shellLaunchConfig.args = [];
}
......@@ -750,11 +791,81 @@ export class TerminalTaskSystem implements ITaskSystem {
return TerminalTaskSystem.shellQuotes[shellBasename] || TerminalTaskSystem.osShellQuotes[process.platform];
}
private resolveCommandAndArgs(task: CustomTask | ContributedTask): { command: CommandString, args: CommandString[] } {
private collectTaskVariables(variables: Set<string>, task: CustomTask | ContributedTask): void {
if (task.command) {
this.collectCommandVariables(variables, task.command);
}
this.collectMatcherVariables(variables, task.problemMatchers);
}
private collectCommandVariables(variables: Set<string>, command: CommandConfiguration): void {
this.collectVariables(variables, command.name);
if (command.args) {
command.args.forEach(arg => this.collectVariables(variables, arg));
}
variables.add('${workspaceFolder}');
if (command.options) {
let options = command.options;
if (options.cwd) {
this.collectVariables(variables, options.cwd);
}
if (options.env) {
Object.keys(options.env).forEach((key) => {
let value: any = options.env[key];
if (Types.isString(value)) {
this.collectVariables(variables, value);
}
});
}
if (options.shell) {
if (options.shell.executable) {
this.collectVariables(variables, options.shell.executable);
}
if (options.shell.args) {
options.shell.args.forEach(arg => this.collectVariables(variables, arg));
}
}
}
}
private collectMatcherVariables(variables: Set<string>, values: (string | ProblemMatcher)[]): void {
if (values === void 0 || values === null || values.length === 0) {
return;
}
values.forEach((value) => {
let matcher: ProblemMatcher;
if (Types.isString(value)) {
if (value[0] === '$') {
matcher = ProblemMatcherRegistry.get(value.substring(1));
} else {
matcher = ProblemMatcherRegistry.get(value);
}
} else {
matcher = value;
}
if (matcher && matcher.filePrefix) {
this.collectVariables(variables, matcher.filePrefix);
}
});
}
private collectVariables(variables: Set<string>, value: string | CommandString): void {
let string: string = Types.isString(value) ? value : value.value;
let r = /\$\{(.*?)\}/g;
let matches: RegExpExecArray;
do {
matches = r.exec(string);
if (matches) {
variables.add(matches[0]);
}
} while (matches);
}
private resolveCommandAndArgs(resolver: VariableResolver, commandConfig: CommandConfiguration): { command: CommandString, args: CommandString[] } {
// First we need to use the command args:
let args: CommandString[] = task.command.args ? task.command.args.slice() : [];
args = this.resolveVariables(task, args);
let command: CommandString = this.resolveVariable(task, task.command.name);
let args: CommandString[] = commandConfig.args ? commandConfig.args.slice() : [];
args = this.resolveVariables(resolver, args);
let command: CommandString = this.resolveVariable(resolver, commandConfig.name);
return { command, args };
}
......@@ -814,13 +925,13 @@ export class TerminalTaskSystem implements ITaskSystem {
return path.join(cwd, command);
}
private resolveVariables(task: CustomTask | ContributedTask, value: string[]): string[];
private resolveVariables(task: CustomTask | ContributedTask, value: CommandString[]): CommandString[];
private resolveVariables(task: CustomTask | ContributedTask, value: CommandString[]): CommandString[] {
return value.map(s => this.resolveVariable(task, s));
private resolveVariables(resolver: VariableResolver, value: string[]): string[];
private resolveVariables(resolver: VariableResolver, value: CommandString[]): CommandString[];
private resolveVariables(resolver: VariableResolver, value: CommandString[]): CommandString[] {
return value.map(s => this.resolveVariable(resolver, s));
}
private resolveMatchers(task: CustomTask | ContributedTask, values: (string | ProblemMatcher)[]): ProblemMatcher[] {
private resolveMatchers(resolver: VariableResolver, values: (string | ProblemMatcher)[]): ProblemMatcher[] {
if (values === void 0 || values === null || values.length === 0) {
return [];
}
......@@ -840,13 +951,9 @@ export class TerminalTaskSystem implements ITaskSystem {
this.outputChannel.append(nls.localize('unkownProblemMatcher', 'Problem matcher {0} can\'t be resolved. The matcher will be ignored'));
return;
}
let workspaceFolder = Task.getWorkspaceFolder(task);
let taskSystemInfo: TaskSystemInfo;
if (workspaceFolder) {
taskSystemInfo = this.taskSystemInfoResolver(workspaceFolder);
}
let taskSystemInfo: TaskSystemInfo = resolver.taskSystemInfo;
let hasFilePrefix = matcher.filePrefix !== void 0;
let hasScheme = taskSystemInfo !== void 0 && taskSystemInfo.fileSystemScheme !== void 0 && taskSystemInfo.fileSystemScheme === 'file';
let hasScheme = taskSystemInfo !== void 0 && taskSystemInfo.fileSystemScheme !== void 0 && taskSystemInfo.fileSystemScheme !== 'file';
if (!hasFilePrefix && !hasScheme) {
result.push(matcher);
} else {
......@@ -855,7 +962,7 @@ export class TerminalTaskSystem implements ITaskSystem {
copy.fileSystemScheme = taskSystemInfo.fileSystemScheme;
}
if (hasFilePrefix) {
copy.filePrefix = this.resolveVariable(task, copy.filePrefix);
copy.filePrefix = this.resolveVariable(resolver, copy.filePrefix);
}
result.push(copy);
}
......@@ -863,33 +970,33 @@ export class TerminalTaskSystem implements ITaskSystem {
return result;
}
private resolveVariable(task: CustomTask | ContributedTask, value: string): string;
private resolveVariable(task: CustomTask | ContributedTask, value: CommandString): CommandString;
private resolveVariable(task: CustomTask | ContributedTask, value: CommandString): CommandString {
private resolveVariable(resolver: VariableResolver, value: string): string;
private resolveVariable(resolver: VariableResolver, value: CommandString): CommandString;
private resolveVariable(resolver: VariableResolver, value: CommandString): CommandString {
// TODO@Dirk Task.getWorkspaceFolder should return a WorkspaceFolder that is defined in workspace.ts
if (Types.isString(value)) {
return this.configurationResolverService.resolve(<any>Task.getWorkspaceFolder(task), value);
return resolver.resolve(value);
} else {
return {
value: this.configurationResolverService.resolve(<any>Task.getWorkspaceFolder(task), value.value),
value: resolver.resolve(value.value),
quoting: value.quoting
};
}
}
private resolveOptions(task: CustomTask | ContributedTask, options: CommandOptions): CommandOptions {
private resolveOptions(resolver: VariableResolver, options: CommandOptions): CommandOptions {
if (options === void 0 || options === null) {
return { cwd: this.resolveVariable(task, '${workspaceFolder}') };
return { cwd: this.resolveVariable(resolver, '${workspaceFolder}') };
}
let result: CommandOptions = Types.isString(options.cwd)
? { cwd: this.resolveVariable(task, options.cwd) }
: { cwd: this.resolveVariable(task, '${workspaceFolder}') };
? { cwd: this.resolveVariable(resolver, options.cwd) }
: { cwd: this.resolveVariable(resolver, '${workspaceFolder}') };
if (options.env) {
result.env = Object.create(null);
Object.keys(options.env).forEach((key) => {
let value: any = options.env[key];
if (Types.isString(value)) {
result.env[key] = this.resolveVariable(task, value);
result.env[key] = this.resolveVariable(resolver, value);
} else {
result.env[key] = value.toString();
}
......
......@@ -33,9 +33,6 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
import 'vs/workbench/parts/search/electron-browser/search.contribution';
import { NullLogService } from 'vs/platform/log/common/log';
import { ITextModel } from 'vs/editor/common/model';
import { ExtHostWorkspace } from 'vs/workbench/api/node/extHostWorkspace';
import { generateUuid } from 'vs/base/common/uuid';
import { ExtHostTask } from 'vs/workbench/api/node/extHostTask';
const defaultSelector = { scheme: 'far' };
const model: ITextModel = EditorModel.createFromString(
......@@ -52,8 +49,6 @@ let rpcProtocol: TestRPCProtocol;
let extHost: ExtHostLanguageFeatures;
let mainThread: MainThreadLanguageFeatures;
let commands: ExtHostCommands;
let task: ExtHostTask;
let workspace: ExtHostWorkspace;
let disposables: vscode.Disposable[] = [];
let originalErrorHandler: (e: any) => any;
......@@ -120,11 +115,9 @@ suite('ExtHostLanguageFeatureCommands', function () {
const heapService = new ExtHostHeapService();
commands = new ExtHostCommands(rpcProtocol, heapService, new NullLogService());
workspace = new ExtHostWorkspace(rpcProtocol, { id: generateUuid(), name: 'Test', folders: [] }, new NullLogService());
task = new ExtHostTask(rpcProtocol, workspace);
rpcProtocol.set(ExtHostContext.ExtHostCommands, commands);
rpcProtocol.set(MainContext.MainThreadCommands, inst.createInstance(MainThreadCommands, rpcProtocol));
ExtHostApiCommands.register(commands, task);
ExtHostApiCommands.register(commands);
const diagnostics = new ExtHostDiagnostics(rpcProtocol);
rpcProtocol.set(ExtHostContext.ExtHostDiagnostics, diagnostics);
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册