Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Selective task provider activation using a new onTaskType: activation event #137662

Merged
merged 3 commits into from Dec 7, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion extensions/grunt/package.json
Expand Up @@ -24,7 +24,7 @@
},
"main": "./out/main",
"activationEvents": [
"onCommand:workbench.action.tasks.runTask"
"onTaskType:grunt"
],
"capabilities": {
"virtualWorkspaces": false,
Expand Down
2 changes: 1 addition & 1 deletion extensions/gulp/package.json
Expand Up @@ -24,7 +24,7 @@
},
"main": "./out/main",
"activationEvents": [
"onCommand:workbench.action.tasks.runTask"
"onTaskType:gulp"
],
"capabilities": {
"virtualWorkspaces": false,
Expand Down
2 changes: 1 addition & 1 deletion extensions/jake/package.json
Expand Up @@ -24,7 +24,7 @@
},
"main": "./out/main",
"activationEvents": [
"onCommand:workbench.action.tasks.runTask"
"onTaskType:jake"
],
"capabilities": {
"virtualWorkspaces": false,
Expand Down
2 changes: 1 addition & 1 deletion extensions/npm/package.json
Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion extensions/typescript-language-features/package.json
Expand Up @@ -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",
Expand Down
165 changes: 129 additions & 36 deletions src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts
Expand Up @@ -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;
Expand All @@ -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 = [];
Expand All @@ -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 = [];
Expand Down Expand Up @@ -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<void> {
// 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();
Expand Down Expand Up @@ -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<Task | undefined> {
if (!(await this.trust())) {
return;
Expand All @@ -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));
Expand All @@ -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) {
Expand Down Expand Up @@ -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);
}
}
}
Expand Down Expand Up @@ -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<ITaskSummary> {
private async _findSingleWorkspaceTaskOfGroup(group: TaskGroup): Promise<ITaskSummary | undefined> {
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<ITaskSummary> {
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) {
Expand All @@ -917,7 +1022,12 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer
});
}

private runTest(): Promise<ITaskSummary> {
private async runTest(): Promise<ITaskSummary> {
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) {
Expand Down Expand Up @@ -1683,7 +1793,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer

private getGroupedTasks(type?: string): Promise<TaskMap> {
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<boolean> = Object.create(null);
TaskDefinitionRegistry.all().forEach(definition => validTypes[definition.taskType] = true);
validTypes['shell'] = true;
Expand Down Expand Up @@ -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
Expand All @@ -1739,6 +1851,9 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer
}, error);
}
}
if (!foundAnyProviders) {
resolve(result);
}
} else {
resolve(result);
}
Expand Down Expand Up @@ -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 => {
Expand Down Expand Up @@ -2720,7 +2813,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer
});
});
});
});
})();
this.progressService.withProgress(options, () => promise);
}

Expand Down
7 changes: 0 additions & 7 deletions src/vs/workbench/contrib/tasks/browser/tasksQuickAccess.ts
Expand Up @@ -22,8 +22,6 @@ export class TasksQuickAccessProvider extends PickerQuickAccessProvider<IPickerQ

static PREFIX = 'task ';

private activationPromise: Promise<void>;

constructor(
@IExtensionService extensionService: IExtensionService,
@ITaskService private taskService: ITaskService,
Expand All @@ -37,14 +35,9 @@ export class TasksQuickAccessProvider extends PickerQuickAccessProvider<IPickerQ
label: localize('noTaskResults', "No matching tasks")
}
});

this.activationPromise = extensionService.activateByEvent('onCommand:workbench.action.tasks.runTask');
}

protected async _getPicks(filter: string, disposables: DisposableStore, token: CancellationToken): Promise<Array<IPickerQuickAccessItem | IQuickPickSeparator>> {
// always await extensions
await this.activationPromise;

if (token.isCancellationRequested) {
return [];
}
Expand Down
Expand Up @@ -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.'),
Expand Down