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

Adding support for TaskProvider.resolveTask #71027

Merged
merged 6 commits into from
Jul 3, 2019
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 22 additions & 7 deletions src/vs/workbench/api/browser/mainThreadTask.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { IDisposable } from 'vs/base/common/lifecycle';
import { IWorkspaceContextService, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';

import {
ContributedTask, KeyedTaskIdentifier, TaskExecution, Task, TaskEvent, TaskEventKind,
ContributedTask, ConfiguringTask, KeyedTaskIdentifier, TaskExecution, Task, TaskEvent, TaskEventKind,
PresentationOptions, CommandOptions, CommandConfiguration, RuntimeType, CustomTask, TaskScope, TaskSource,
TaskSourceKind, ExtensionTaskSource, RunOptions, TaskSet, TaskDefinition
} from 'vs/workbench/contrib/tasks/common/tasks';
Expand Down Expand Up @@ -304,8 +304,8 @@ namespace TaskHandleDTO {
}

namespace TaskDTO {
export function from(task: Task): TaskDTO | undefined {
if (task === undefined || task === null || (!CustomTask.is(task) && !ContributedTask.is(task))) {
export function from(task: Task | ConfiguringTask): TaskDTO | undefined {
if (task === undefined || task === null || (!CustomTask.is(task) && !ContributedTask.is(task) && !ConfiguringTask.is(task))) {
return undefined;
}
const result: TaskDTO = {
Expand All @@ -314,7 +314,7 @@ namespace TaskDTO {
definition: TaskDefinitionDTO.from(task.getDefinition()),
source: TaskSourceDTO.from(task._source),
execution: undefined,
presentationOptions: task.command ? TaskPresentationOptionsDTO.from(task.command.presentation) : undefined,
presentationOptions: !ConfiguringTask.is(task) && task.command ? TaskPresentationOptionsDTO.from(task.command.presentation) : undefined,
isBackground: task.configurationProperties.isBackground,
problemMatchers: [],
hasDefinedMatchers: ContributedTask.is(task) ? task.hasDefinedMatchers : false,
Expand All @@ -323,7 +323,7 @@ namespace TaskDTO {
if (task.configurationProperties.group) {
result.group = task.configurationProperties.group;
}
if (task.command) {
if (!ConfiguringTask.is(task) && task.command) {
if (task.command.runtime === RuntimeType.Process) {
result.execution = ProcessExecutionDTO.from(task.command);
} else if (task.command.runtime === RuntimeType.Shell) {
Expand Down Expand Up @@ -442,7 +442,7 @@ export class MainThreadTask implements MainThreadTaskShape {
});
}

public $registerTaskProvider(handle: number): Promise<void> {
public $registerTaskProvider(handle: number, type: string): Promise<void> {
const provider: ITaskProvider = {
provideTasks: (validTypes: IStringDictionary<boolean>) => {
return Promise.resolve(this._proxy.$provideTasks(handle, validTypes)).then((value) => {
Expand All @@ -460,9 +460,24 @@ export class MainThreadTask implements MainThreadTaskShape {
extension: value.extension
} as TaskSet;
});
},
resolveTask: (task: ConfiguringTask) => {
const dto = TaskDTO.from(task);

// Only named tasks can be resolved. (That should be the case, right?)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As far as I can see, not having a name is fine. The important thing is that the task has a definition so that the task provider knows what to do with it.

if (dto && dto.name) {
return Promise.resolve(this._proxy.$resolveTask(handle, dto)).then(resolvedTask => {
if (resolvedTask) {
return TaskDTO.to(resolvedTask, this._workspaceContextServer, true);
}

return undefined;
});
}
return Promise.resolve<ContributedTask | undefined>(undefined);
}
};
const disposable = this._taskService.registerTaskProvider(provider);
const disposable = this._taskService.registerTaskProvider(provider, type);
this._providers.set(handle, { disposable, provider });
return Promise.resolve(undefined);
}
Expand Down
3 changes: 2 additions & 1 deletion src/vs/workbench/api/common/extHost.protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -579,7 +579,7 @@ export interface MainThreadSearchShape extends IDisposable {

export interface MainThreadTaskShape extends IDisposable {
$createTaskId(task: tasks.TaskDTO): Promise<string>;
$registerTaskProvider(handle: number): Promise<void>;
$registerTaskProvider(handle: number, type: string): Promise<void>;
$unregisterTaskProvider(handle: number): Promise<void>;
$fetchTasks(filter?: tasks.TaskFilterDTO): Promise<tasks.TaskDTO[]>;
$executeTask(task: tasks.TaskHandleDTO | tasks.TaskDTO): Promise<tasks.TaskExecutionDTO>;
Expand Down Expand Up @@ -1042,6 +1042,7 @@ export interface ExtHostSCMShape {

export interface ExtHostTaskShape {
$provideTasks(handle: number, validTypes: { [key: string]: boolean; }): Thenable<tasks.TaskSetDTO>;
$resolveTask(handle: number, taskDTO: tasks.TaskDTO): Thenable<tasks.TaskDTO | undefined>;
$onDidStartTask(execution: tasks.TaskExecutionDTO, terminalId: number): void;
$onDidStartTaskProcess(value: tasks.TaskProcessStartedDTO): void;
$onDidEndTaskProcess(value: tasks.TaskProcessEndedDTO): void;
Expand Down
4 changes: 2 additions & 2 deletions src/vs/workbench/api/node/extHost.api.impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -614,7 +614,7 @@ export function createApiFactory(
return extHostDocumentContentProviders.registerTextDocumentContentProvider(scheme, provider);
},
registerTaskProvider: (type: string, provider: vscode.TaskProvider) => {
return extHostTask.registerTaskProvider(extension, provider);
return extHostTask.registerTaskProvider(extension, type, provider);
},
registerFileSystemProvider(scheme, provider, options) {
return extHostFileSystem.registerFileSystemProvider(scheme, provider, options);
Expand Down Expand Up @@ -713,7 +713,7 @@ export function createApiFactory(

const tasks: typeof vscode.tasks = {
registerTaskProvider: (type: string, provider: vscode.TaskProvider) => {
return extHostTask.registerTaskProvider(extension, provider);
return extHostTask.registerTaskProvider(extension, type, provider);
},
fetchTasks: (filter?: vscode.TaskFilter): Thenable<vscode.Task[]> => {
return extHostTask.fetchTasks(filter);
Expand Down
57 changes: 45 additions & 12 deletions src/vs/workbench/api/node/extHostTask.ts
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,7 @@ namespace TaskExecutionDTO {
}

interface HandlerData {
type: string;
provider: vscode.TaskProvider;
extension: IExtensionDescription;
}
Expand Down Expand Up @@ -491,13 +492,13 @@ export class ExtHostTask implements ExtHostTaskShape {
this._activeCustomExecutions = new Map<string, CustomExecutionData>();
}

public registerTaskProvider(extension: IExtensionDescription, provider: vscode.TaskProvider): vscode.Disposable {
public registerTaskProvider(extension: IExtensionDescription, type: string, provider: vscode.TaskProvider): vscode.Disposable {
if (!provider) {
return new types.Disposable(() => { });
}
const handle = this.nextHandle();
this._handlers.set(handle, { provider, extension });
this._proxy.$registerTaskProvider(handle);
this._handlers.set(handle, { type, provider, extension });
this._proxy.$registerTaskProvider(handle, type);
return new types.Disposable(() => {
this._handlers.delete(handle);
this._proxy.$unregisterTaskProvider(handle);
Expand Down Expand Up @@ -651,15 +652,10 @@ export class ExtHostTask implements ExtHostTaskShape {
taskDTOs.push(taskDTO);

if (CustomExecutionDTO.is(taskDTO.execution)) {
taskIdPromises.push(new Promise((resolve) => {
// The ID is calculated on the main thread task side, so, let's call into it here.
// We need the task id's pre-computed for custom task executions because when OnDidStartTask
// is invoked, we have to be able to map it back to our data.
this._proxy.$createTaskId(taskDTO).then((taskId) => {
this._providedCustomExecutions.set(taskId, new CustomExecutionData(<vscode.CustomExecution>(<vscode.Task2>task).execution2, this._terminalService));
resolve();
});
}));
// The ID is calculated on the main thread task side, so, let's call into it here.
// We need the task id's pre-computed for custom task executions because when OnDidStartTask
// is invoked, we have to be able to map it back to our data.
taskIdPromises.push(this.addCustomExecution(taskDTO, <vscode.Task2>task));
}
}
}
Expand All @@ -679,6 +675,38 @@ export class ExtHostTask implements ExtHostTaskShape {
});
}

public async $resolveTask(handle: number, taskDTO: TaskDTO): Promise<TaskDTO | undefined> {
const handler = this._handlers.get(handle);
if (!handler) {
return Promise.reject(new Error('no handler found'));
}

if (taskDTO.definition.type !== handler.type) {
throw new Error(`Unexpected: Task of type [${taskDTO.definition.type}] cannot be resolved by provider of type [${handler.type}].`);
}

const task = await TaskDTO.to(taskDTO, this._workspaceProvider);
if (!task) {
throw new Error('Unexpected: Task cannot be resolved.');
}

const resolvedTask = await handler.provider.resolveTask(task, CancellationToken.None);
if (!resolvedTask) {
return;
}

const resolvedTaskDTO: TaskDTO | undefined = TaskDTO.from(resolvedTask, handler.extension);
if (!resolvedTaskDTO) {
throw new Error('Unexpected: Task cannot be resolved.');
}

if (CustomExecutionDTO.is(resolvedTaskDTO.execution)) {
await this.addCustomExecution(taskDTO, <vscode.Task2>task);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@joelday & @alexr00 -> Joel, this is the wrong DTO and task. It should be resolvedTaskDTO. and resolvedTaskDTO

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we allow the provider to change the DTO properties, then we have a different task since those properties are the fingerprint of the task.

}

return resolvedTaskDTO;
}

public async $resolveVariables(uriComponents: UriComponents, toResolve: { process?: { name: string; cwd?: string; path?: string }, variables: string[] }): Promise<{ process?: string, variables: { [key: string]: string; } }> {
const configProvider = await this._configurationService.getConfigProvider();
const uri: URI = URI.revive(uriComponents);
Expand Down Expand Up @@ -724,6 +752,11 @@ export class ExtHostTask implements ExtHostTaskShape {
return this._handleCounter++;
}

private async addCustomExecution(taskDTO: TaskDTO, task: vscode.Task2): Promise<void> {
const taskId = await this._proxy.$createTaskId(taskDTO);
this._providedCustomExecutions.set(taskId, new CustomExecutionData(<vscode.CustomExecution>(<vscode.Task2>task).execution2, this._terminalService));
}

private async getTaskExecution(execution: TaskExecutionDTO | string, task?: vscode.Task): Promise<TaskExecutionImpl> {
if (typeof execution === 'string') {
const taskExecution = this._taskExecutions.get(execution);
Expand Down
15 changes: 13 additions & 2 deletions src/vs/workbench/contrib/tasks/common/taskConfiguration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import * as nls from 'vs/nls';

import * as Objects from 'vs/base/common/objects';
import { IStringDictionary } from 'vs/base/common/collections';
import { IJSONSchemaMap } from 'vs/base/common/jsonSchema';
import { Platform } from 'vs/base/common/platform';
import * as Types from 'vs/base/common/types';
import * as UUID from 'vs/base/common/uuid';
Expand All @@ -22,6 +23,7 @@ import * as Tasks from './tasks';
import { TaskDefinitionRegistry } from './taskDefinitionRegistry';
import { ConfiguredInput } from 'vs/workbench/services/configurationResolver/common/configurationResolver';


export const enum ShellQuoting {
/**
* Default is character escaping.
Expand Down Expand Up @@ -1213,11 +1215,20 @@ namespace ConfigurationProperties {
{ property: 'presentation', type: CommandConfiguration.PresentationOptions }, { property: 'problemMatchers' }
];

export function from(this: void, external: ConfigurationProperties, context: ParseContext, includeCommandOptions: boolean): Tasks.ConfigurationProperties | undefined {
export function from(this: void, external: ConfigurationProperties, context: ParseContext, includeCommandOptions: boolean, properties?: IJSONSchemaMap): Tasks.ConfigurationProperties | undefined {
if (!external) {
return undefined;
}
let result: Tasks.ConfigurationProperties = {};

if (properties) {
for (const propertyName of Object.keys(properties)) {
if (external[propertyName] !== undefined) {
result[propertyName] = Objects.deepClone(external[propertyName]);
}
}
}

if (Types.isString(external.taskName)) {
result.name = external.taskName;
}
Expand Down Expand Up @@ -1352,7 +1363,7 @@ namespace ConfiguringTask {
RunOptions.fromConfiguration(external.runOptions),
{}
);
let configuration = ConfigurationProperties.from(external, context, true);
let configuration = ConfigurationProperties.from(external, context, true, typeDeclaration.properties);
if (configuration) {
result.configurationProperties = Objects.assign(result.configurationProperties, configuration);
if (result.configurationProperties.name) {
Expand Down
3 changes: 2 additions & 1 deletion src/vs/workbench/contrib/tasks/common/taskService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export const ITaskService = createDecorator<ITaskService>('taskService');

export interface ITaskProvider {
provideTasks(validTypes: IStringDictionary<boolean>): Promise<TaskSet>;
resolveTask(task: ConfiguringTask): Promise<ContributedTask | undefined>;
}

export interface ProblemMatcherRunOptions {
Expand Down Expand Up @@ -79,7 +80,7 @@ export interface ITaskService {
customize(task: ContributedTask | CustomTask, properties?: {}, openConfig?: boolean): Promise<void>;
openConfig(task: CustomTask | undefined): Promise<void>;

registerTaskProvider(taskProvider: ITaskProvider): IDisposable;
registerTaskProvider(taskProvider: ITaskProvider, type: string): IDisposable;

registerTaskSystem(scheme: string, taskSystemInfo: TaskSystemInfo): void;

Expand Down
3 changes: 3 additions & 0 deletions src/vs/workbench/contrib/tasks/common/tasks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -694,6 +694,9 @@ export class ConfiguringTask extends CommonTask {
return object;
}

public getDefinition(): KeyedTaskIdentifier {
return this.configures;
}
}

export class ContributedTask extends CommonTask {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,7 @@ class TaskService extends Disposable implements ITaskService {
private _ignoredWorkspaceFolders: IWorkspaceFolder[];
private _showIgnoreMessage?: boolean;
private _providers: Map<number, ITaskProvider>;
private _providerTypes: Map<number, string>;
private _taskSystemInfos: Map<string, TaskSystemInfo>;

private _workspaceTasksPromise?: Promise<Map<string, WorkspaceFolderTaskResult>>;
Expand Down Expand Up @@ -494,6 +495,7 @@ class TaskService extends Disposable implements ITaskService {
this._taskSystemListener = undefined;
this._outputChannel = this.outputService.getChannel(TaskService.OutputChannelId)!;
this._providers = new Map<number, ITaskProvider>();
this._providerTypes = new Map<number, string>();
this._taskSystemInfos = new Map<string, TaskSystemInfo>();
this._register(this.contextService.onDidChangeWorkspaceFolders(() => {
if (!this._taskSystem && !this._workspaceTasksPromise) {
Expand Down Expand Up @@ -698,17 +700,19 @@ class TaskService extends Disposable implements ITaskService {
}
}

public registerTaskProvider(provider: ITaskProvider): IDisposable {
public registerTaskProvider(provider: ITaskProvider, type: string): IDisposable {
if (!provider) {
return {
dispose: () => { }
};
}
let handle = TaskService.nextHandle++;
this._providers.set(handle, provider);
this._providerTypes.set(handle, type);
return {
dispose: () => {
this._providers.delete(handle);
this._providerTypes.delete(handle);
}
};
}
Expand Down Expand Up @@ -1425,8 +1429,8 @@ class TaskService extends Disposable implements ITaskService {
}
}
}
return this.getWorkspaceTasks().then((customTasks) => {
customTasks.forEach((folderTasks, key) => {
return this.getWorkspaceTasks().then(async (customTasks) => {
await Promise.all(Array.from(customTasks).map(async ([key, folderTasks]) => {
joelday marked this conversation as resolved.
Show resolved Hide resolved
let contributed = contributedTasks.get(key);
if (!folderTasks.set) {
if (contributed) {
Expand Down Expand Up @@ -1484,22 +1488,39 @@ class TaskService extends Disposable implements ITaskService {
} else {
result.add(key, ...folderTasks.set.tasks);
}
unUsedConfigurations.forEach((value) => {

await Promise.all(Array.from(unUsedConfigurations).map(async (value) => {
let configuringTask = configurations!.byIdentifier[value];

for (const [handle, provider] of this._providers) {
if (configuringTask.type === this._providerTypes.get(handle)) {
try {
const resolvedTask = await provider.resolveTask(configuringTask);
if (resolvedTask) {
result.add(key, resolvedTask);
return;
}
}
catch (error) {
// Ignore?
}
}
}

this._outputChannel.append(nls.localize(
'TaskService.noConfiguration',
'Error: The {0} task detection didn\'t contribute a task for the following configuration:\n{1}\nThe task will be ignored.\n',
configuringTask.configures.type,
JSON.stringify(configuringTask._source.config.element, undefined, 4)
));
this.showOutput();
});
}));
} else {
result.add(key, ...folderTasks.set.tasks);
result.add(key, ...contributed);
}
}
});
}));
return result;
}, () => {
// If we can't read the tasks.json file provide at least the contributed tasks
Expand Down