From 51d7dd1a7319cca932d2db8bb6560e2fc8dc3c03 Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Tue, 7 Dec 2021 17:43:38 +0100 Subject: [PATCH] Selective task provider activation using a new `onTaskType:` activation event (#137662) --- extensions/grunt/package.json | 2 +- extensions/gulp/package.json | 2 +- extensions/jake/package.json | 2 +- extensions/npm/package.json | 2 +- .../typescript-language-features/package.json | 2 +- .../tasks/browser/abstractTaskService.ts | 165 ++++++++++++++---- .../contrib/tasks/browser/tasksQuickAccess.ts | 7 - .../extensions/common/extensionsRegistry.ts | 5 + 8 files changed, 139 insertions(+), 48 deletions(-) diff --git a/extensions/grunt/package.json b/extensions/grunt/package.json index c784b1ed83a..7e5c605ec00 100644 --- a/extensions/grunt/package.json +++ b/extensions/grunt/package.json @@ -24,7 +24,7 @@ }, "main": "./out/main", "activationEvents": [ - "onCommand:workbench.action.tasks.runTask" + "onTaskType:grunt" ], "capabilities": { "virtualWorkspaces": false, diff --git a/extensions/gulp/package.json b/extensions/gulp/package.json index 21800e96cfb..995da7fbe5d 100644 --- a/extensions/gulp/package.json +++ b/extensions/gulp/package.json @@ -24,7 +24,7 @@ }, "main": "./out/main", "activationEvents": [ - "onCommand:workbench.action.tasks.runTask" + "onTaskType:gulp" ], "capabilities": { "virtualWorkspaces": false, diff --git a/extensions/jake/package.json b/extensions/jake/package.json index 244d12d96e5..4bb8793f3ee 100644 --- a/extensions/jake/package.json +++ b/extensions/jake/package.json @@ -24,7 +24,7 @@ }, "main": "./out/main", "activationEvents": [ - "onCommand:workbench.action.tasks.runTask" + "onTaskType:jake" ], "capabilities": { "virtualWorkspaces": false, diff --git a/extensions/npm/package.json b/extensions/npm/package.json index 6aa1de094b8..8c4bc7ba071 100644 --- a/extensions/npm/package.json +++ b/extensions/npm/package.json @@ -37,7 +37,7 @@ "main": "./out/npmMain", "browser": "./dist/browser/npmBrowserMain", "activationEvents": [ - "onCommand:workbench.action.tasks.runTask", + "onTaskType:npm", "onCommand:npm.runScriptFromFolder", "onLanguage:json", "workspaceContains:package.json", diff --git a/extensions/typescript-language-features/package.json b/extensions/typescript-language-features/package.json index 6651308b424..b5e11e8892a 100644 --- a/extensions/typescript-language-features/package.json +++ b/extensions/typescript-language-features/package.json @@ -63,7 +63,7 @@ "onCommand:javascript.goToProjectConfig", "onCommand:typescript.goToProjectConfig", "onCommand:typescript.openTsServerLog", - "onCommand:workbench.action.tasks.runTask", + "onTaskType:typescript", "onCommand:_typescript.configurePlugin", "onCommand:_typescript.learnMoreAboutRefactorings", "onCommand:typescript.fileReferences", diff --git a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts index 2b3f346c6d8..c739e6f6c95 100644 --- a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts +++ b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts @@ -150,7 +150,7 @@ class TaskMap { this._store.forEach(callback); } - private getKey(workspaceFolder: IWorkspace | IWorkspaceFolder | string): string { + public static getKey(workspaceFolder: IWorkspace | IWorkspaceFolder | string): string { let key: string | undefined; if (Types.isString(workspaceFolder)) { key = workspaceFolder; @@ -162,7 +162,7 @@ class TaskMap { } public get(workspaceFolder: IWorkspace | IWorkspaceFolder | string): Task[] { - const key = this.getKey(workspaceFolder); + const key = TaskMap.getKey(workspaceFolder); let result: Task[] | undefined = this._store.get(key); if (!result) { result = []; @@ -172,7 +172,7 @@ class TaskMap { } public add(workspaceFolder: IWorkspace | IWorkspaceFolder | string, ...task: Task[]): void { - const key = this.getKey(workspaceFolder); + const key = TaskMap.getKey(workspaceFolder); let values = this._store.get(key); if (!values) { values = []; @@ -493,6 +493,31 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer return this._showIgnoreMessage; } + private _getActivationEvents(type: string | undefined): string[] { + const result: string[] = []; + result.push('onCommand:workbench.action.tasks.runTask'); + if (type) { + // send a specific activation event for this task type + result.push(`onTaskType:${type}`); + } else { + // send activation events for all task types + for (const definition of TaskDefinitionRegistry.all()) { + result.push(`onTaskType:${definition.taskType}`); + } + } + return result; + } + + private async _activateTaskProviders(type: string | undefined): Promise { + // We need to first wait for extensions to be registered because we might read + // the `TaskDefinitionRegistry` in case `type` is `undefined` + await this.extensionService.whenInstalledExtensionsRegistered(); + + await Promise.all( + this._getActivationEvents(type).map(activationEvent => this.extensionService.activateByEvent(activationEvent)) + ); + } + private updateSetup(setup?: [IWorkspaceFolder[], IWorkspaceFolder[], ExecutionEngine, JsonSchemaVersion, IWorkspace | undefined]): void { if (!setup) { setup = this.computeWorkspaceFolderSetup(); @@ -575,6 +600,44 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer return this._taskSystem.customExecutionComplete(task, result); } + /** + * Get a subset of workspace tasks that match a certain predicate. + */ + private async _findWorkspaceTasks(predicate: (task: ConfiguringTask | Task, workspaceFolder: IWorkspaceFolder) => boolean): Promise<(ConfiguringTask | Task)[]> { + const result: (ConfiguringTask | Task)[] = []; + + const tasks = await this.getWorkspaceTasks(); + for (const [, workspaceTasks] of tasks) { + if (workspaceTasks.configurations) { + for (const taskName in workspaceTasks.configurations.byIdentifier) { + const task = workspaceTasks.configurations.byIdentifier[taskName]; + if (predicate(task, workspaceTasks.workspaceFolder)) { + result.push(task); + } + } + } + if (workspaceTasks.set) { + for (const task of workspaceTasks.set.tasks) { + if (predicate(task, workspaceTasks.workspaceFolder)) { + result.push(task); + } + } + } + } + + return result; + } + + private async _findWorkspaceTasksInGroup(group: TaskGroup, isDefault: boolean): Promise<(ConfiguringTask | Task)[]> { + return this._findWorkspaceTasks((task) => { + const taskGroup = task.configurationProperties.group; + if (taskGroup && typeof taskGroup !== 'string') { + return (taskGroup._id === group._id && (!isDefault || !!taskGroup.isDefault)); + } + return false; + }); + } + public async getTask(folder: IWorkspace | IWorkspaceFolder | string, identifier: string | TaskIdentifier, compareId: boolean = false): Promise { if (!(await this.trust())) { return; @@ -590,6 +653,28 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer if (key === undefined) { return Promise.resolve(undefined); } + + // Try to find the task in the workspace + const requestedFolder = TaskMap.getKey(folder); + const matchedTasks = await this._findWorkspaceTasks((task, workspaceFolder) => { + const taskFolder = TaskMap.getKey(workspaceFolder); + if (taskFolder !== requestedFolder || taskFolder !== USER_TASKS_GROUP_KEY) { + return false; + } + return task.matches(key, compareId); + }); + matchedTasks.sort(task => task._source.kind === TaskSourceKind.Extension ? 1 : -1); + if (matchedTasks.length > 0) { + // Nice, we found a configured task! + const task = matchedTasks[0]; + if (ConfiguringTask.is(task)) { + return this.tryResolveTask(task); + } else { + return task; + } + } + + // We didn't find the task, so we need to ask all resolvers about it return this.getGroupedTasks().then((map) => { let values = map.get(folder); values = values.concat(map.get(USER_TASKS_GROUP_KEY)); @@ -606,7 +691,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer if (!(await this.trust())) { return; } - await Promise.all([this.extensionService.activateByEvent('onCommand:workbench.action.tasks.runTask'), this.extensionService.whenInstalledExtensionsRegistered()]); + await this._activateTaskProviders(configuringTask.type); let matchingProvider: ITaskProvider | undefined; let matchingProviderUnavailable: boolean = false; for (const [handle, provider] of this._providers) { @@ -690,10 +775,9 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer public taskTypes(): string[] { const types: string[] = []; if (this.isProvideTasksEnabled()) { - for (const [handle] of this._providers) { - const type = this._providerTypes.get(handle); - if (type && this.isTaskProviderEnabled(type)) { - types.push(type); + for (const definition of TaskDefinitionRegistry.all()) { + if (this.isTaskProviderEnabled(definition.taskType)) { + types.push(definition.taskType); } } } @@ -900,7 +984,28 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer this.openerService.open(URI.parse('https://code.visualstudio.com/docs/editor/tasks#_defining-a-problem-matcher')); } - private build(): Promise { + private async _findSingleWorkspaceTaskOfGroup(group: TaskGroup): Promise { + const tasksOfGroup = await this._findWorkspaceTasksInGroup(group, true); + if ((tasksOfGroup.length === 1) && (typeof tasksOfGroup[0].configurationProperties.group !== 'string') && tasksOfGroup[0].configurationProperties.group?.isDefault) { + let resolvedTask: Task | undefined; + if (ConfiguringTask.is(tasksOfGroup[0])) { + resolvedTask = await this.tryResolveTask(tasksOfGroup[0]); + } else { + resolvedTask = tasksOfGroup[0]; + } + if (resolvedTask) { + return this.run(resolvedTask, undefined, TaskRunSource.User); + } + } + return undefined; + } + + private async build(): Promise { + const tryBuildShortcut = await this._findSingleWorkspaceTaskOfGroup(TaskGroup.Build); + if (tryBuildShortcut) { + return tryBuildShortcut; + } + return this.getGroupedTasks().then((tasks) => { let runnable = this.createRunnableTask(tasks, TaskGroup.Build); if (!runnable || !runnable.task) { @@ -917,7 +1022,12 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer }); } - private runTest(): Promise { + private async runTest(): Promise { + const tryTestShortcut = await this._findSingleWorkspaceTaskOfGroup(TaskGroup.Test); + if (tryTestShortcut) { + return tryTestShortcut; + } + return this.getGroupedTasks().then((tasks) => { let runnable = this.createRunnableTask(tasks, TaskGroup.Test); if (!runnable || !runnable.task) { @@ -1683,7 +1793,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer private getGroupedTasks(type?: string): Promise { const needsRecentTasksMigration = this.needsRecentTasksMigration(); - return Promise.all([this.extensionService.activateByEvent('onCommand:workbench.action.tasks.runTask'), this.extensionService.whenInstalledExtensionsRegistered()]).then(() => { + return this._activateTaskProviders(type).then(() => { let validTypes: IStringDictionary = Object.create(null); TaskDefinitionRegistry.all().forEach(definition => validTypes[definition.taskType] = true); validTypes['shell'] = true; @@ -1717,12 +1827,14 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } }; if (this.isProvideTasksEnabled() && (this.schemaVersion === JsonSchemaVersion.V2_0_0) && (this._providers.size > 0)) { + let foundAnyProviders = false; for (const [handle, provider] of this._providers) { const providerType = this._providerTypes.get(handle); if ((type === undefined) || (type === providerType)) { if (providerType && !this.isTaskProviderEnabled(providerType)) { continue; } + foundAnyProviders = true; counter++; provider.provideTasks(validTypes).then((taskSet: TaskSet) => { // Check that the tasks provided are of the correct type @@ -1739,6 +1851,9 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer }, error); } } + if (!foundAnyProviders) { + resolve(result); + } } else { resolve(result); } @@ -2647,30 +2762,8 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer location: ProgressLocation.Window, title: nls.localize('TaskService.fetchingBuildTasks', 'Fetching build tasks...') }; - let promise = this.getWorkspaceTasks().then(tasks => { - const buildTasks: (ConfiguringTask | Task)[] = []; - for (const taskSource of tasks) { - for (const task in taskSource[1].configurations?.byIdentifier) { - if (taskSource[1].configurations) { - const taskGroup: TaskGroup = taskSource[1].configurations.byIdentifier[task].configurationProperties.group as TaskGroup; - - if (taskGroup && taskGroup._id === TaskGroup.Build._id && taskGroup.isDefault) { - buildTasks.push(taskSource[1].configurations.byIdentifier[task]); - } - } - } - if (taskSource[1].set) { - for (const task of taskSource[1].set?.tasks) { - const taskGroup: TaskGroup = task.configurationProperties.group as TaskGroup; - if (taskGroup && taskGroup._id === TaskGroup.Build._id && taskGroup.isDefault) { - buildTasks.push(task); - } - } - } - if (buildTasks.length > 0) { - break; - } - } + let promise = (async () => { + const buildTasks = await this._findWorkspaceTasksInGroup(TaskGroup.Build, false); async function runSingleBuildTask(task: Task | undefined, problemMatcherOptions: ProblemMatcherRunOptions | undefined, that: AbstractTaskService) { that.run(task, problemMatcherOptions, TaskRunSource.User).then(undefined, reason => { @@ -2720,7 +2813,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer }); }); }); - }); + })(); this.progressService.withProgress(options, () => promise); } diff --git a/src/vs/workbench/contrib/tasks/browser/tasksQuickAccess.ts b/src/vs/workbench/contrib/tasks/browser/tasksQuickAccess.ts index 3686ad45cbc..24b9b2d9779 100644 --- a/src/vs/workbench/contrib/tasks/browser/tasksQuickAccess.ts +++ b/src/vs/workbench/contrib/tasks/browser/tasksQuickAccess.ts @@ -22,8 +22,6 @@ export class TasksQuickAccessProvider extends PickerQuickAccessProvider; - constructor( @IExtensionService extensionService: IExtensionService, @ITaskService private taskService: ITaskService, @@ -37,14 +35,9 @@ export class TasksQuickAccessProvider extends PickerQuickAccessProvider> { - // always await extensions - await this.activationPromise; - if (token.isCancellationRequested) { return []; } diff --git a/src/vs/workbench/services/extensions/common/extensionsRegistry.ts b/src/vs/workbench/services/extensions/common/extensionsRegistry.ts index a32b31f59a3..7804f575dc3 100644 --- a/src/vs/workbench/services/extensions/common/extensionsRegistry.ts +++ b/src/vs/workbench/services/extensions/common/extensionsRegistry.ts @@ -288,6 +288,11 @@ export const schema: IJSONSchema = { description: nls.localize('vscode.extension.activationEvents.onStartupFinished', 'An activation event emitted after the start-up finished (after all `*` activated extensions have finished activating).'), body: 'onStartupFinished' }, + { + label: 'onTaskType', + description: nls.localize('vscode.extension.activationEvents.onTaskType', 'An activation event emitted whenever tasks of a certain type need to be listed or resolved.'), + body: 'onTaskType:${1:taskType}' + }, { label: 'onFileSystem', description: nls.localize('vscode.extension.activationEvents.onFileSystem', 'An activation event emitted whenever a file or folder is accessed with the given scheme.'),