Skip to content

Commit b6089b1

Browse files
joeldayalexr00
authored andcommitted
Adding support for TaskProvider.resolveTask (microsoft#71027)
* Adding support for TaskProvider.resolveTask. * Reorganize task grouping and resolution async code for readability. * Made small changes and implmented resolveTask for gulp
1 parent 9b28638 commit b6089b1

9 files changed

Lines changed: 197 additions & 62 deletions

File tree

extensions/gulp/src/main.ts

Lines changed: 74 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,24 @@ function showError() {
6767
});
6868
}
6969

70+
async function findGulpCommand(rootPath: string): Promise<string> {
71+
let gulpCommand: string;
72+
let platform = process.platform;
73+
if (platform === 'win32' && await exists(path.join(rootPath, 'node_modules', '.bin', 'gulp.cmd'))) {
74+
const globalGulp = path.join(process.env.APPDATA ? process.env.APPDATA : '', 'npm', 'gulp.cmd');
75+
if (await exists(globalGulp)) {
76+
gulpCommand = '"' + globalGulp + '"';
77+
} else {
78+
gulpCommand = path.join('.', 'node_modules', '.bin', 'gulp.cmd');
79+
}
80+
} else if ((platform === 'linux' || platform === 'darwin') && await exists(path.join(rootPath, 'node_modules', '.bin', 'gulp'))) {
81+
gulpCommand = path.join('.', 'node_modules', '.bin', 'gulp');
82+
} else {
83+
gulpCommand = 'gulp';
84+
}
85+
return gulpCommand;
86+
}
87+
7088
interface GulpTaskDefinition extends vscode.TaskDefinition {
7189
task: string;
7290
file?: string;
@@ -77,7 +95,9 @@ class FolderDetector {
7795
private fileWatcher: vscode.FileSystemWatcher | undefined;
7896
private promise: Thenable<vscode.Task[]> | undefined;
7997

80-
constructor(private _workspaceFolder: vscode.WorkspaceFolder) {
98+
constructor(
99+
private _workspaceFolder: vscode.WorkspaceFolder,
100+
private _gulpCommand: Promise<string>) {
81101
}
82102

83103
public get workspaceFolder(): vscode.WorkspaceFolder {
@@ -97,10 +117,28 @@ class FolderDetector {
97117
}
98118

99119
public async getTasks(): Promise<vscode.Task[]> {
100-
if (!this.promise) {
101-
this.promise = this.computeTasks();
120+
if (this.isEnabled()) {
121+
if (!this.promise) {
122+
this.promise = this.computeTasks();
123+
}
124+
return this.promise;
125+
} else {
126+
return [];
127+
}
128+
}
129+
130+
public async getTask(_task: vscode.Task): Promise<vscode.Task | undefined> {
131+
const gulpTask = (<any>_task.definition).task;
132+
if (gulpTask) {
133+
let kind: GulpTaskDefinition = {
134+
type: 'gulp',
135+
task: gulpTask
136+
};
137+
let options: vscode.ShellExecutionOptions = { cwd: this.workspaceFolder.uri.fsPath };
138+
let task = new vscode.Task(kind, this.workspaceFolder, gulpTask, 'gulp', new vscode.ShellExecution(await this._gulpCommand, [gulpTask], options));
139+
return task;
102140
}
103-
return this.promise;
141+
return undefined;
104142
}
105143

106144
private async computeTasks(): Promise<vscode.Task[]> {
@@ -117,22 +155,7 @@ class FolderDetector {
117155
}
118156
}
119157

120-
let gulpCommand: string;
121-
let platform = process.platform;
122-
if (platform === 'win32' && await exists(path.join(rootPath!, 'node_modules', '.bin', 'gulp.cmd'))) {
123-
const globalGulp = path.join(process.env.APPDATA ? process.env.APPDATA : '', 'npm', 'gulp.cmd');
124-
if (await exists(globalGulp)) {
125-
gulpCommand = '"' + globalGulp + '"';
126-
} else {
127-
gulpCommand = path.join('.', 'node_modules', '.bin', 'gulp.cmd');
128-
}
129-
} else if ((platform === 'linux' || platform === 'darwin') && await exists(path.join(rootPath!, 'node_modules', '.bin', 'gulp'))) {
130-
gulpCommand = path.join('.', 'node_modules', '.bin', 'gulp');
131-
} else {
132-
gulpCommand = 'gulp';
133-
}
134-
135-
let commandLine = `${gulpCommand} --tasks-simple --no-color`;
158+
let commandLine = `${await this._gulpCommand} --tasks-simple --no-color`;
136159
try {
137160
let { stdout, stderr } = await exec(commandLine, { cwd: rootPath });
138161
if (stderr && stderr.length > 0) {
@@ -151,7 +174,7 @@ class FolderDetector {
151174
task: line
152175
};
153176
let options: vscode.ShellExecutionOptions = { cwd: this.workspaceFolder.uri.fsPath };
154-
let task = new vscode.Task(kind, this.workspaceFolder, line, 'gulp', new vscode.ShellExecution(gulpCommand, [line], options));
177+
let task = new vscode.Task(kind, this.workspaceFolder, line, 'gulp', new vscode.ShellExecution(await this._gulpCommand, [line], options));
155178
result.push(task);
156179
let lowerCaseLine = line.toLowerCase();
157180
if (isBuildTask(lowerCaseLine)) {
@@ -218,9 +241,9 @@ class TaskDetector {
218241
}
219242
}
220243
for (let add of added) {
221-
let detector = new FolderDetector(add);
244+
let detector = new FolderDetector(add, findGulpCommand(add.uri.fsPath));
245+
this.detectors.set(add.uri.toString(), detector);
222246
if (detector.isEnabled()) {
223-
this.detectors.set(add.uri.toString(), detector);
224247
detector.start();
225248
}
226249
}
@@ -229,18 +252,16 @@ class TaskDetector {
229252

230253
private updateConfiguration(): void {
231254
for (let detector of this.detectors.values()) {
232-
if (!detector.isEnabled()) {
233-
detector.dispose();
234-
this.detectors.delete(detector.workspaceFolder.uri.toString());
235-
}
255+
detector.dispose();
256+
this.detectors.delete(detector.workspaceFolder.uri.toString());
236257
}
237258
let folders = vscode.workspace.workspaceFolders;
238259
if (folders) {
239260
for (let folder of folders) {
240261
if (!this.detectors.has(folder.uri.toString())) {
241-
let detector = new FolderDetector(folder);
262+
let detector = new FolderDetector(folder, findGulpCommand(folder.uri.fsPath));
263+
this.detectors.set(folder.uri.toString(), detector);
242264
if (detector.isEnabled()) {
243-
this.detectors.set(folder.uri.toString(), detector);
244265
detector.start();
245266
}
246267
}
@@ -251,12 +272,13 @@ class TaskDetector {
251272

252273
private updateProvider(): void {
253274
if (!this.taskProvider && this.detectors.size > 0) {
275+
const thisCapture = this;
254276
this.taskProvider = vscode.workspace.registerTaskProvider('gulp', {
255-
provideTasks: () => {
256-
return this.getTasks();
277+
provideTasks(): Promise<vscode.Task[]> {
278+
return thisCapture.getTasks();
257279
},
258-
resolveTask(_task: vscode.Task): vscode.Task | undefined {
259-
return undefined;
280+
resolveTask(_task: vscode.Task): Promise<vscode.Task | undefined> {
281+
return thisCapture.getTask(_task);
260282
}
261283
});
262284
}
@@ -291,6 +313,25 @@ class TaskDetector {
291313
});
292314
}
293315
}
316+
317+
public async getTask(task: vscode.Task): Promise<vscode.Task | undefined> {
318+
if (this.detectors.size === 0) {
319+
return undefined;
320+
} else if (this.detectors.size === 1) {
321+
return this.detectors.values().next().value.getTask(task);
322+
} else {
323+
if ((task.scope === vscode.TaskScope.Workspace) || (task.scope === vscode.TaskScope.Global)) {
324+
// Not supported, we don't have enough info to create the task.
325+
return undefined;
326+
} else if (task.scope) {
327+
const detector = this.detectors.get(task.scope.uri.toString());
328+
if (detector) {
329+
return detector.getTask(task);
330+
}
331+
}
332+
return undefined;
333+
}
334+
}
294335
}
295336

296337
let detector: TaskDetector;

src/vs/workbench/api/browser/mainThreadTask.ts

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import { IDisposable } from 'vs/base/common/lifecycle';
1616
import { IWorkspaceContextService, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
1717

1818
import {
19-
ContributedTask, KeyedTaskIdentifier, TaskExecution, Task, TaskEvent, TaskEventKind,
19+
ContributedTask, ConfiguringTask, KeyedTaskIdentifier, TaskExecution, Task, TaskEvent, TaskEventKind,
2020
PresentationOptions, CommandOptions, CommandConfiguration, RuntimeType, CustomTask, TaskScope, TaskSource,
2121
TaskSourceKind, ExtensionTaskSource, RunOptions, TaskSet, TaskDefinition
2222
} from 'vs/workbench/contrib/tasks/common/tasks';
@@ -304,8 +304,8 @@ namespace TaskHandleDTO {
304304
}
305305

306306
namespace TaskDTO {
307-
export function from(task: Task): TaskDTO | undefined {
308-
if (task === undefined || task === null || (!CustomTask.is(task) && !ContributedTask.is(task))) {
307+
export function from(task: Task | ConfiguringTask): TaskDTO | undefined {
308+
if (task === undefined || task === null || (!CustomTask.is(task) && !ContributedTask.is(task) && !ConfiguringTask.is(task))) {
309309
return undefined;
310310
}
311311
const result: TaskDTO = {
@@ -314,7 +314,7 @@ namespace TaskDTO {
314314
definition: TaskDefinitionDTO.from(task.getDefinition()),
315315
source: TaskSourceDTO.from(task._source),
316316
execution: undefined,
317-
presentationOptions: task.command ? TaskPresentationOptionsDTO.from(task.command.presentation) : undefined,
317+
presentationOptions: !ConfiguringTask.is(task) && task.command ? TaskPresentationOptionsDTO.from(task.command.presentation) : undefined,
318318
isBackground: task.configurationProperties.isBackground,
319319
problemMatchers: [],
320320
hasDefinedMatchers: ContributedTask.is(task) ? task.hasDefinedMatchers : false,
@@ -323,7 +323,7 @@ namespace TaskDTO {
323323
if (task.configurationProperties.group) {
324324
result.group = task.configurationProperties.group;
325325
}
326-
if (task.command) {
326+
if (!ConfiguringTask.is(task) && task.command) {
327327
if (task.command.runtime === RuntimeType.Process) {
328328
result.execution = ProcessExecutionDTO.from(task.command);
329329
} else if (task.command.runtime === RuntimeType.Shell) {
@@ -442,7 +442,7 @@ export class MainThreadTask implements MainThreadTaskShape {
442442
});
443443
}
444444

445-
public $registerTaskProvider(handle: number): Promise<void> {
445+
public $registerTaskProvider(handle: number, type: string): Promise<void> {
446446
const provider: ITaskProvider = {
447447
provideTasks: (validTypes: IStringDictionary<boolean>) => {
448448
return Promise.resolve(this._proxy.$provideTasks(handle, validTypes)).then((value) => {
@@ -460,9 +460,24 @@ export class MainThreadTask implements MainThreadTaskShape {
460460
extension: value.extension
461461
} as TaskSet;
462462
});
463+
},
464+
resolveTask: (task: ConfiguringTask) => {
465+
const dto = TaskDTO.from(task);
466+
467+
if (dto) {
468+
dto.name = ((dto.name === undefined) ? '' : dto.name); // Using an empty name causes the name to default to the one given by the provider.
469+
return Promise.resolve(this._proxy.$resolveTask(handle, dto)).then(resolvedTask => {
470+
if (resolvedTask) {
471+
return TaskDTO.to(resolvedTask, this._workspaceContextServer, true);
472+
}
473+
474+
return undefined;
475+
});
476+
}
477+
return Promise.resolve<ContributedTask | undefined>(undefined);
463478
}
464479
};
465-
const disposable = this._taskService.registerTaskProvider(provider);
480+
const disposable = this._taskService.registerTaskProvider(provider, type);
466481
this._providers.set(handle, { disposable, provider });
467482
return Promise.resolve(undefined);
468483
}

src/vs/workbench/api/common/extHost.protocol.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -635,7 +635,7 @@ export interface MainThreadSearchShape extends IDisposable {
635635

636636
export interface MainThreadTaskShape extends IDisposable {
637637
$createTaskId(task: tasks.TaskDTO): Promise<string>;
638-
$registerTaskProvider(handle: number): Promise<void>;
638+
$registerTaskProvider(handle: number, type: string): Promise<void>;
639639
$unregisterTaskProvider(handle: number): Promise<void>;
640640
$fetchTasks(filter?: tasks.TaskFilterDTO): Promise<tasks.TaskDTO[]>;
641641
$executeTask(task: tasks.TaskHandleDTO | tasks.TaskDTO): Promise<tasks.TaskExecutionDTO>;
@@ -1183,6 +1183,7 @@ export interface ExtHostSCMShape {
11831183

11841184
export interface ExtHostTaskShape {
11851185
$provideTasks(handle: number, validTypes: { [key: string]: boolean; }): Thenable<tasks.TaskSetDTO>;
1186+
$resolveTask(handle: number, taskDTO: tasks.TaskDTO): Thenable<tasks.TaskDTO | undefined>;
11861187
$onDidStartTask(execution: tasks.TaskExecutionDTO, terminalId: number): void;
11871188
$onDidStartTaskProcess(value: tasks.TaskProcessStartedDTO): void;
11881189
$onDidEndTaskProcess(value: tasks.TaskProcessEndedDTO): void;

src/vs/workbench/api/node/extHost.api.impl.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -675,7 +675,7 @@ export function createApiFactory(
675675
return extHostDocumentContentProviders.registerTextDocumentContentProvider(scheme, provider);
676676
},
677677
registerTaskProvider: (type: string, provider: vscode.TaskProvider) => {
678-
return extHostTask.registerTaskProvider(extension, provider);
678+
return extHostTask.registerTaskProvider(extension, type, provider);
679679
},
680680
registerFileSystemProvider(scheme, provider, options) {
681681
return extHostFileSystem.registerFileSystemProvider(scheme, provider, options);
@@ -776,7 +776,7 @@ export function createApiFactory(
776776

777777
const tasks: typeof vscode.tasks = {
778778
registerTaskProvider: (type: string, provider: vscode.TaskProvider) => {
779-
return extHostTask.registerTaskProvider(extension, provider);
779+
return extHostTask.registerTaskProvider(extension, type, provider);
780780
},
781781
fetchTasks: (filter?: vscode.TaskFilter): Thenable<vscode.Task[]> => {
782782
return extHostTask.fetchTasks(filter);

src/vs/workbench/api/node/extHostTask.ts

Lines changed: 45 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,7 @@ namespace TaskExecutionDTO {
347347
}
348348

349349
interface HandlerData {
350+
type: string;
350351
provider: vscode.TaskProvider;
351352
extension: IExtensionDescription;
352353
}
@@ -492,13 +493,13 @@ export class ExtHostTask implements ExtHostTaskShape {
492493
this._activeCustomExecutions = new Map<string, CustomExecutionData>();
493494
}
494495

495-
public registerTaskProvider(extension: IExtensionDescription, provider: vscode.TaskProvider): vscode.Disposable {
496+
public registerTaskProvider(extension: IExtensionDescription, type: string, provider: vscode.TaskProvider): vscode.Disposable {
496497
if (!provider) {
497498
return new types.Disposable(() => { });
498499
}
499500
const handle = this.nextHandle();
500-
this._handlers.set(handle, { provider, extension });
501-
this._proxy.$registerTaskProvider(handle);
501+
this._handlers.set(handle, { type, provider, extension });
502+
this._proxy.$registerTaskProvider(handle, type);
502503
return new types.Disposable(() => {
503504
this._handlers.delete(handle);
504505
this._proxy.$unregisterTaskProvider(handle);
@@ -652,15 +653,10 @@ export class ExtHostTask implements ExtHostTaskShape {
652653
taskDTOs.push(taskDTO);
653654

654655
if (CustomExecutionDTO.is(taskDTO.execution)) {
655-
taskIdPromises.push(new Promise((resolve) => {
656-
// The ID is calculated on the main thread task side, so, let's call into it here.
657-
// We need the task id's pre-computed for custom task executions because when OnDidStartTask
658-
// is invoked, we have to be able to map it back to our data.
659-
this._proxy.$createTaskId(taskDTO).then((taskId) => {
660-
this._providedCustomExecutions.set(taskId, new CustomExecutionData(<vscode.CustomExecution>(<vscode.Task2>task).execution2, this._terminalService));
661-
resolve();
662-
});
663-
}));
656+
// The ID is calculated on the main thread task side, so, let's call into it here.
657+
// We need the task id's pre-computed for custom task executions because when OnDidStartTask
658+
// is invoked, we have to be able to map it back to our data.
659+
taskIdPromises.push(this.addCustomExecution(taskDTO, <vscode.Task2>task));
664660
}
665661
}
666662
}
@@ -680,6 +676,38 @@ export class ExtHostTask implements ExtHostTaskShape {
680676
});
681677
}
682678

679+
public async $resolveTask(handle: number, taskDTO: TaskDTO): Promise<TaskDTO | undefined> {
680+
const handler = this._handlers.get(handle);
681+
if (!handler) {
682+
return Promise.reject(new Error('no handler found'));
683+
}
684+
685+
if (taskDTO.definition.type !== handler.type) {
686+
throw new Error(`Unexpected: Task of type [${taskDTO.definition.type}] cannot be resolved by provider of type [${handler.type}].`);
687+
}
688+
689+
const task = await TaskDTO.to(taskDTO, this._workspaceProvider);
690+
if (!task) {
691+
throw new Error('Unexpected: Task cannot be resolved.');
692+
}
693+
694+
const resolvedTask = await handler.provider.resolveTask(task, CancellationToken.None);
695+
if (!resolvedTask) {
696+
return;
697+
}
698+
699+
const resolvedTaskDTO: TaskDTO | undefined = TaskDTO.from(resolvedTask, handler.extension);
700+
if (!resolvedTaskDTO) {
701+
throw new Error('Unexpected: Task cannot be resolved.');
702+
}
703+
704+
if (CustomExecutionDTO.is(resolvedTaskDTO.execution)) {
705+
await this.addCustomExecution(taskDTO, <vscode.Task2>task);
706+
}
707+
708+
return resolvedTaskDTO;
709+
}
710+
683711
public async $resolveVariables(uriComponents: UriComponents, toResolve: { process?: { name: string; cwd?: string; path?: string }, variables: string[] }): Promise<{ process?: string, variables: { [key: string]: string; } }> {
684712
const configProvider = await this._configurationService.getConfigProvider();
685713
const uri: URI = URI.revive(uriComponents);
@@ -725,6 +753,11 @@ export class ExtHostTask implements ExtHostTaskShape {
725753
return this._handleCounter++;
726754
}
727755

756+
private async addCustomExecution(taskDTO: TaskDTO, task: vscode.Task2): Promise<void> {
757+
const taskId = await this._proxy.$createTaskId(taskDTO);
758+
this._providedCustomExecutions.set(taskId, new CustomExecutionData(<vscode.CustomExecution>(<vscode.Task2>task).execution2, this._terminalService));
759+
}
760+
728761
private async getTaskExecution(execution: TaskExecutionDTO | string, task?: vscode.Task): Promise<TaskExecutionImpl> {
729762
if (typeof execution === 'string') {
730763
const taskExecution = this._taskExecutions.get(execution);

0 commit comments

Comments
 (0)