diff --git a/packages/debug/package.json b/packages/debug/package.json index 597e3289def7e..409ae92911073 100644 --- a/packages/debug/package.json +++ b/packages/debug/package.json @@ -19,6 +19,7 @@ "@theia/userstorage": "^0.11.0", "@theia/variable-resolver": "^0.11.0", "@theia/workspace": "^0.11.0", + "@theia/task": "^0.11.0", "@types/p-debounce": "^1.0.1", "jsonc-parser": "^2.0.2", "mkdirp": "^0.5.0", diff --git a/packages/debug/src/browser/debug-session-manager.ts b/packages/debug/src/browser/debug-session-manager.ts index 3d9c7c1c7b40f..9325fbe66cc77 100644 --- a/packages/debug/src/browser/debug-session-manager.ts +++ b/packages/debug/src/browser/debug-session-manager.ts @@ -16,22 +16,25 @@ // tslint:disable:no-any -import { injectable, inject, postConstruct } from 'inversify'; -import { Emitter, Event, DisposableCollection, MessageService, WaitUntilEvent, ProgressService } from '@theia/core'; +import { DisposableCollection, Emitter, Event, MessageService, ProgressService, WaitUntilEvent } from '@theia/core'; import { LabelProvider } from '@theia/core/lib/browser'; +import { ContextKey, ContextKeyService } from '@theia/core/lib/browser/context-key-service'; +import URI from '@theia/core/lib/common/uri'; import { EditorManager } from '@theia/editor/lib/browser'; -import { ContextKeyService, ContextKey } from '@theia/core/lib/browser/context-key-service'; +import { QuickOpenTask } from '@theia/task/lib/browser/quick-open-task'; +import { TaskService } from '@theia/task/lib/browser/task-service'; +import { VariableResolverService } from '@theia/variable-resolver/lib/browser'; +import { inject, injectable, postConstruct } from 'inversify'; +import { DebugConfiguration } from '../common/debug-common'; import { DebugError, DebugService } from '../common/debug-service'; -import { DebugState, DebugSession } from './debug-session'; -import { DebugSessionFactory, DebugSessionContributionRegistry } from './debug-session-contribution'; -import { DebugThread } from './model/debug-thread'; -import { DebugStackFrame } from './model/debug-stack-frame'; -import { DebugBreakpoint } from './model/debug-breakpoint'; import { BreakpointManager } from './breakpoint/breakpoint-manager'; -import URI from '@theia/core/lib/common/uri'; -import { VariableResolverService } from '@theia/variable-resolver/lib/browser'; +import { DebugConfigurationManager } from './debug-configuration-manager'; +import { DebugSession, DebugState } from './debug-session'; +import { DebugSessionContributionRegistry, DebugSessionFactory } from './debug-session-contribution'; import { DebugSessionOptions, InternalDebugSessionOptions } from './debug-session-options'; -import { DebugConfiguration } from '../common/debug-common'; +import { DebugBreakpoint } from './model/debug-breakpoint'; +import { DebugStackFrame } from './model/debug-stack-frame'; +import { DebugThread } from './model/debug-thread'; export interface WillStartDebugSession extends WaitUntilEvent { } @@ -127,6 +130,15 @@ export class DebugSessionManager { @inject(ContextKeyService) protected readonly contextKeyService: ContextKeyService; + @inject(TaskService) + protected readonly taskService: TaskService; + + @inject(DebugConfigurationManager) + protected readonly debugConfigurationManager: DebugConfigurationManager; + + @inject(QuickOpenTask) + protected readonly quickOpenTask: QuickOpenTask; + protected debugTypeKey: ContextKey; protected inDebugModeKey: ContextKey; @@ -142,23 +154,32 @@ export class DebugSessionManager { } async start(options: DebugSessionOptions): Promise { - try { - return this.progressService.withProgress('Start...', 'debug', async () => { + return this.progressService.withProgress('Start...', 'debug', async () => { + try { await this.fireWillStartDebugSession(); const resolved = await this.resolveConfiguration(options); + + // preLaunchTask isn't run in case of auto restart as well as postDebugTask + if (!options.configuration.__restart) { + const taskRun = await this.runTask(options.workspaceFolderUri, resolved.configuration.preLaunchTask, true); + if (!taskRun) { + return undefined; + } + } + const sessionId = await this.debug.createDebugSession(resolved.configuration); return this.doStart(sessionId, resolved); - }); - } catch (e) { - if (DebugError.NotFound.is(e)) { - this.messageService.error(`The debug session type "${e.data.type}" is not supported.`); - return undefined; - } + } catch (e) { + if (DebugError.NotFound.is(e)) { + this.messageService.error(`The debug session type "${e.data.type}" is not supported.`); + return undefined; + } - this.messageService.error('There was an error starting the debug session, check the logs for more details.'); - console.error('Error starting the debug session', e); - throw e; - } + this.messageService.error('There was an error starting the debug session, check the logs for more details.'); + console.error('Error starting the debug session', e); + throw e; + } + }); } protected async fireWillStartDebugSession(): Promise { @@ -214,12 +235,14 @@ export class DebugSessionManager { this.updateCurrentSession(session); }); session.onDidChangeBreakpoints(uri => this.fireDidChangeBreakpoints({ session, uri })); - session.on('terminated', event => { + session.on('terminated', async event => { const restart = event.body && event.body.restart; if (restart) { + // postDebugTask isn't run in case of auto restart as well as preLaunchTask this.doRestart(session, restart); } else { session.terminate(); + await this.runTask(session.options.workspaceFolderUri, session.configuration.postDebugTask); } }); session.on('exited', () => this.destroy(session.id)); @@ -374,4 +397,54 @@ export class DebugSessionManager { return origin && new DebugBreakpoint(origin, this.labelProvider, this.breakpoints, this.editorManager); } + /** + * Runs the given tasks. + * @param taskName the task name to run, see [TaskNameResolver](#TaskNameResolver) + * @return true if it allowed to continue debugging otherwise it returns false + */ + protected async runTask(workspaceFolderUri: string | undefined, taskName: string | undefined, checkErrors?: boolean): Promise { + if (!taskName) { + return true; + } + + const taskInfo = await this.taskService.runWorkspaceTask(workspaceFolderUri, taskName); + if (!checkErrors) { + return true; + } + + if (!taskInfo) { + return this.doPostTaskAction(`Could not run the task '${taskName}'.`); + } + + const code = await this.taskService.getExitCode(taskInfo.taskId); + if (code === 0) { + return true; + } else if (code !== undefined) { + return this.doPostTaskAction(`Task '${taskName}' terminated with exit code ${code}.`); + } else { + const signal = await this.taskService.getTerminateSignal(taskInfo.taskId); + if (signal !== undefined) { + return this.doPostTaskAction(`Task '${taskName}' terminated by signal ${signal}.`); + } else { + return this.doPostTaskAction(`Task '${taskName}' terminated for unknown reason.`); + } + } + } + + protected async doPostTaskAction(errorMessage: string): Promise { + const actions = ['Open launch.json', 'Cancel', 'Configure Task', 'Debug Anyway']; + const result = await this.messageService.error(errorMessage, ...actions); + switch (result) { + case actions[0]: // open launch.json + this.debugConfigurationManager.openConfiguration(); + return false; + case actions[1]: // cancel + return false; + case actions[2]: // configure tasks + this.quickOpenTask.configure(); + return false; + default: // continue debugging + return true; + } + } } diff --git a/packages/debug/src/common/debug-configuration.ts b/packages/debug/src/common/debug-configuration.ts index 5616026c84c9b..4515b8af1dcc0 100644 --- a/packages/debug/src/common/debug-configuration.ts +++ b/packages/debug/src/common/debug-configuration.ts @@ -62,6 +62,12 @@ export interface DebugConfiguration { /** default: neverOpen */ internalConsoleOptions?: 'neverOpen' | 'openOnSessionStart' | 'openOnFirstSessionStart' + + /** Task to run before debug session starts */ + preLaunchTask?: string; + + /** Task to run after debug session ends */ + postDebugTask?: string; } export namespace DebugConfiguration { export function is(arg: DebugConfiguration | any): arg is DebugConfiguration { diff --git a/packages/debug/src/node/vscode/vscode-debug-adapter-contribution.ts b/packages/debug/src/node/vscode/vscode-debug-adapter-contribution.ts index 22cff4072535b..2091a938f8da3 100644 --- a/packages/debug/src/node/vscode/vscode-debug-adapter-contribution.ts +++ b/packages/debug/src/node/vscode/vscode-debug-adapter-contribution.ts @@ -170,16 +170,18 @@ export abstract class AbstractVSCodeDebugAdapterContribution implements DebugAda }; properties['preLaunchTask'] = { anyOf: [taskSchema, { - type: ['string', 'null'], + type: ['string'], }], default: '', + defaultSnippets: [{ body: { task: '', type: '' } }], description: nls.localize('debugPrelaunchTask', 'Task to run before debug session starts.') }; properties['postDebugTask'] = { anyOf: [taskSchema, { - type: ['string', 'null'], + type: ['string', ], }], default: '', + defaultSnippets: [{ body: { task: '', type: '' } }], description: nls.localize('debugPostDebugTask', 'Task to run after debug session ends.') }; properties['internalConsoleOptions'] = INTERNAL_CONSOLE_OPTIONS_SCHEMA; diff --git a/packages/plugin-ext/src/hosted/node/scanners/scanner-theia.ts b/packages/plugin-ext/src/hosted/node/scanners/scanner-theia.ts index e8a81f6cec7f7..034a896963745 100644 --- a/packages/plugin-ext/src/hosted/node/scanners/scanner-theia.ts +++ b/packages/plugin-ext/src/hosted/node/scanners/scanner-theia.ts @@ -493,14 +493,14 @@ export class TheiaPluginScanner implements PluginScanner { }; properties['preLaunchTask'] = { anyOf: [taskSchema, { - type: ['string', 'null'], + type: ['string'], }], default: '', description: nls.localize('debugPrelaunchTask', 'Task to run before debug session starts.') }; properties['postDebugTask'] = { anyOf: [taskSchema, { - type: ['string', 'null'], + type: ['string'], }], default: '', description: nls.localize('debugPostDebugTask', 'Task to run after debug session ends.') diff --git a/packages/task/src/browser/quick-open-task.ts b/packages/task/src/browser/quick-open-task.ts index 3f62fb8638551..a8fccd3fdddc6 100644 --- a/packages/task/src/browser/quick-open-task.ts +++ b/packages/task/src/browser/quick-open-task.ts @@ -20,10 +20,11 @@ import { TaskInfo, TaskConfiguration } from '../common/task-protocol'; import { TaskDefinitionRegistry } from './task-definition-registry'; import URI from '@theia/core/lib/common/uri'; import { TaskActionProvider } from './task-action-provider'; -import { LabelProvider, QuickOpenHandler, QuickOpenService, QuickOpenOptions } from '@theia/core/lib/browser'; +import { QuickOpenHandler, QuickOpenService, QuickOpenOptions } from '@theia/core/lib/browser'; import { WorkspaceService } from '@theia/workspace/lib/browser'; import { TerminalService } from '@theia/terminal/lib/browser/base/terminal-service'; import { QuickOpenModel, QuickOpenItem, QuickOpenActionProvider, QuickOpenMode, QuickOpenGroupItem, QuickOpenGroupItemOptions } from '@theia/core/lib/common/quick-open-model'; +import { TaskNameResolver } from './task-name-resolver'; @injectable() export class QuickOpenTask implements QuickOpenModel, QuickOpenHandler { @@ -44,15 +45,15 @@ export class QuickOpenTask implements QuickOpenModel, QuickOpenHandler { @inject(TaskActionProvider) protected readonly taskActionProvider: TaskActionProvider; - @inject(LabelProvider) - protected readonly labelProvider: LabelProvider; - @inject(WorkspaceService) protected readonly workspaceService: WorkspaceService; @inject(TaskDefinitionRegistry) protected readonly taskDefinitionRegistry: TaskDefinitionRegistry; + @inject(TaskNameResolver) + protected readonly taskNameResolver: TaskNameResolver; + /** Initialize this quick open model with the tasks. */ async init(): Promise { const recentTasks = this.taskService.recentTasks; @@ -67,7 +68,7 @@ export class QuickOpenTask implements QuickOpenModel, QuickOpenHandler { const item = new TaskRunQuickOpenItem(task, this.taskService, isMulti, { groupLabel: index === 0 ? 'recently used tasks' : undefined, showBorder: false - }); + }, this.taskNameResolver); item['taskDefinitionRegistry'] = this.taskDefinitionRegistry; return item; }), @@ -79,7 +80,7 @@ export class QuickOpenTask implements QuickOpenModel, QuickOpenHandler { ? false : index === 0 ? true : false ) - }); + }, this.taskNameResolver); item['taskDefinitionRegistry'] = this.taskDefinitionRegistry; return item; }), @@ -91,7 +92,7 @@ export class QuickOpenTask implements QuickOpenModel, QuickOpenHandler { ? false : index === 0 ? true : false ) - }); + }, this.taskNameResolver); item['taskDefinitionRegistry'] = this.taskDefinitionRegistry; return item; }) @@ -176,12 +177,12 @@ export class QuickOpenTask implements QuickOpenModel, QuickOpenHandler { const { filteredConfiguredTasks, filteredProvidedTasks } = this.getFilteredTasks([], configuredTasks, providedTasks); this.items.push( ...filteredConfiguredTasks.map((task, index) => { - const item = new TaskConfigureQuickOpenItem(task, this.taskService, this.labelProvider, this.workspaceService, isMulti); + const item = new TaskConfigureQuickOpenItem(task, this.taskService, this.taskNameResolver, this.workspaceService, isMulti); item['taskDefinitionRegistry'] = this.taskDefinitionRegistry; return item; }), ...filteredProvidedTasks.map((task, index) => { - const item = new TaskConfigureQuickOpenItem(task, this.taskService, this.labelProvider, this.workspaceService, isMulti); + const item = new TaskConfigureQuickOpenItem(task, this.taskService, this.taskNameResolver, this.workspaceService, isMulti); item['taskDefinitionRegistry'] = this.taskDefinitionRegistry; return item; }), @@ -244,6 +245,7 @@ export class TaskRunQuickOpenItem extends QuickOpenGroupItem { protected taskService: TaskService, protected isMulti: boolean, protected readonly options: QuickOpenGroupItemOptions, + protected readonly taskNameResolver: TaskNameResolver, ) { super(options); } @@ -253,10 +255,7 @@ export class TaskRunQuickOpenItem extends QuickOpenGroupItem { } getLabel(): string { - if (this.taskDefinitionRegistry && !!this.taskDefinitionRegistry.getDefinition(this.task)) { - return `${this.task.source}: ${this.task.label}`; - } - return `${this.task.type}: ${this.task.label}`; + return this.taskNameResolver.resolve(this.task); } getGroupLabel(): string { @@ -323,7 +322,7 @@ export class TaskConfigureQuickOpenItem extends QuickOpenGroupItem { constructor( protected readonly task: TaskConfiguration, protected readonly taskService: TaskService, - protected readonly labelProvider: LabelProvider, + protected readonly taskNameResolver: TaskNameResolver, protected readonly workspaceService: WorkspaceService, protected readonly isMulti: boolean ) { @@ -333,10 +332,7 @@ export class TaskConfigureQuickOpenItem extends QuickOpenGroupItem { } getLabel(): string { - if (this.taskDefinitionRegistry && !!this.taskDefinitionRegistry.getDefinition(this.task)) { - return `${this.task.source}: ${this.task.label}`; - } - return `${this.task.type}: ${this.task.label}`; + return this.taskNameResolver.resolve(this.task); } getDescription(): string { diff --git a/packages/task/src/browser/task-configurations.ts b/packages/task/src/browser/task-configurations.ts index 21d8c1df8aa3e..f98b090c93b1b 100644 --- a/packages/task/src/browser/task-configurations.ts +++ b/packages/task/src/browser/task-configurations.ts @@ -257,7 +257,8 @@ export class TaskConfigurations implements Disposable { } return { ...config, - _source: rootFolderUri + _source: rootFolderUri, + _scope: rootFolderUri }; }); this.rawTaskConfigurations.set(rootFolderUri, tasks); diff --git a/packages/task/src/browser/task-frontend-module.ts b/packages/task/src/browser/task-frontend-module.ts index ddd1af7a22789..e015e3090408f 100644 --- a/packages/task/src/browser/task-frontend-module.ts +++ b/packages/task/src/browser/task-frontend-module.ts @@ -37,6 +37,7 @@ import { TaskConfigurationManager } from './task-configuration-manager'; import { bindTaskPreferences } from './task-preferences'; import '../../src/browser/style/index.css'; import './tasks-monaco-contribution'; +import { TaskNameResolver } from './task-name-resolver'; export default new ContainerModule(bind => { bind(TaskFrontendContribution).toSelf().inSingletonScope(); @@ -71,6 +72,7 @@ export default new ContainerModule(bind => { bind(TaskResolverRegistry).toSelf().inSingletonScope(); bindContributionProvider(bind, TaskContribution); bind(TaskSchemaUpdater).toSelf().inSingletonScope(); + bind(TaskNameResolver).toSelf().inSingletonScope(); bindProcessTaskModule(bind); bindTaskPreferences(bind); diff --git a/packages/task/src/browser/task-name-resolver.ts b/packages/task/src/browser/task-name-resolver.ts new file mode 100644 index 0000000000000..849ca1da2eaf1 --- /dev/null +++ b/packages/task/src/browser/task-name-resolver.ts @@ -0,0 +1,38 @@ +/******************************************************************************** + * Copyright (C) 2019 Red Hat, Inc. and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { inject, injectable } from 'inversify'; +import { TaskConfiguration } from '../common'; +import { TaskDefinitionRegistry } from './task-definition-registry'; + +@injectable() +export class TaskNameResolver { + @inject(TaskDefinitionRegistry) + protected taskDefinitionRegistry: TaskDefinitionRegistry; + + /** + * Returns task name to display. + * It is aligned with VS Code. + */ + resolve(task: TaskConfiguration): string { + if (this.taskDefinitionRegistry.getDefinition(task)) { + return `${task.source}: ${task.label}`; + } + + // it is a hack, when task is customized but extension is absent + return task.label || `${task.type}: ${task.task}`; + } +} diff --git a/packages/task/src/browser/task-service.ts b/packages/task/src/browser/task-service.ts index 336237c7f5230..2e366f5a272cc 100644 --- a/packages/task/src/browser/task-service.ts +++ b/packages/task/src/browser/task-service.ts @@ -14,39 +14,41 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { inject, injectable, named, postConstruct } from 'inversify'; -import { EditorManager } from '@theia/editor/lib/browser'; -import { ILogger } from '@theia/core/lib/common'; import { ApplicationShell, FrontendApplication, WidgetManager } from '@theia/core/lib/browser'; -import { QuickPickService, QuickPickItem } from '@theia/core/lib/common/quick-pick-service'; -import { TaskResolverRegistry, TaskProviderRegistry } from './task-contribution'; -import { TERMINAL_WIDGET_FACTORY_ID, TerminalWidgetFactoryOptions } from '@theia/terminal/lib/browser/terminal-widget-impl'; -import { TerminalService } from '@theia/terminal/lib/browser/base/terminal-service'; -import { TerminalWidget } from '@theia/terminal/lib/browser/base/terminal-widget'; +import { open, OpenerService } from '@theia/core/lib/browser/opener-service'; +import { ILogger } from '@theia/core/lib/common'; import { MessageService } from '@theia/core/lib/common/message-service'; -import { OpenerService, open } from '@theia/core/lib/browser/opener-service'; +import { Deferred } from '@theia/core/lib/common/promise-util'; +import { QuickPickItem, QuickPickService } from '@theia/core/lib/common/quick-pick-service'; +import URI from '@theia/core/lib/common/uri'; +import { EditorManager } from '@theia/editor/lib/browser'; import { ProblemManager } from '@theia/markers/lib/browser/problem/problem-manager'; -import { WorkspaceService } from '@theia/workspace/lib/browser/workspace-service'; +import { TerminalService } from '@theia/terminal/lib/browser/base/terminal-service'; +import { TerminalWidget } from '@theia/terminal/lib/browser/base/terminal-widget'; +import { TerminalWidgetFactoryOptions, TERMINAL_WIDGET_FACTORY_ID } from '@theia/terminal/lib/browser/terminal-widget-impl'; import { VariableResolverService } from '@theia/variable-resolver/lib/browser'; +import { WorkspaceService } from '@theia/workspace/lib/browser/workspace-service'; +import { inject, injectable, named, postConstruct } from 'inversify'; +import { Range } from 'vscode-languageserver-types'; import { NamedProblemMatcher, - ProblemMatcher, ProblemMatchData, + ProblemMatcher, + RunTaskOption, TaskConfiguration, TaskCustomization, TaskExitedEvent, TaskInfo, TaskOutputProcessedEvent, - TaskServer, - RunTaskOption + TaskServer } from '../common'; import { TaskWatcher } from '../common/task-watcher'; -import { TaskConfigurationClient, TaskConfigurations } from './task-configurations'; import { ProvidedTaskConfigurations } from './provided-task-configurations'; +import { TaskConfigurationClient, TaskConfigurations } from './task-configurations'; +import { TaskProviderRegistry, TaskResolverRegistry } from './task-contribution'; import { TaskDefinitionRegistry } from './task-definition-registry'; +import { TaskNameResolver } from './task-name-resolver'; import { ProblemMatcherRegistry } from './task-problem-matcher-registry'; -import { Range } from 'vscode-languageserver-types'; -import URI from '@theia/core/lib/common/uri'; export interface QuickPickProblemMatcherItem { problemMatchers: NamedProblemMatcher[] | undefined; @@ -61,6 +63,10 @@ export class TaskService implements TaskConfigurationClient { */ protected lastTask: { source: string, taskLabel: string } | undefined = undefined; protected cachedRecentTasks: TaskConfiguration[] = []; + protected runningTasks = new Map, + terminateSignal: Deferred + }>(); @inject(FrontendApplication) protected readonly app: FrontendApplication; @@ -119,6 +125,9 @@ export class TaskService implements TaskConfigurationClient { @inject(OpenerService) protected readonly openerService: OpenerService; + @inject(TaskNameResolver) + protected readonly taskNameResolver: TaskNameResolver; + /** * @deprecated To be removed in 0.5.0 */ @@ -127,16 +136,25 @@ export class TaskService implements TaskConfigurationClient { @postConstruct() protected init(): void { + this.getRunningTasks().then(tasks => + tasks.forEach(task => { + if (!this.runningTasks.has(task.taskId)) { + this.runningTasks.set(task.taskId, { exitCode: new Deferred(), terminateSignal: new Deferred() }); + } + })); + // notify user that task has started this.taskWatcher.onTaskCreated((event: TaskInfo) => { - if (this.isEventForThisClient(event.ctx)) { - const task = event.config; - let taskIdentifier = event.taskId.toString(); - if (task) { - taskIdentifier = !!this.taskDefinitionRegistry.getDefinition(task) ? `${task._source}: ${task.label}` : `${task.type}: ${task.label}`; - } - this.messageService.info(`Task ${taskIdentifier} has been started`); + if (!this.isEventForThisClient(event.ctx)) { + return; } + this.runningTasks.set(event.taskId, { exitCode: new Deferred(), terminateSignal: new Deferred() }); + const task = event.config; + let taskIdentifier = event.taskId.toString(); + if (task) { + taskIdentifier = this.taskNameResolver.resolve(task); + } + this.messageService.info(`Task ${taskIdentifier} has been started`); }); this.taskWatcher.onOutputProcessed((event: TaskOutputProcessedEvent) => { @@ -173,13 +191,17 @@ export class TaskService implements TaskConfigurationClient { if (!this.isEventForThisClient(event.ctx)) { return; } + if (!this.runningTasks.has(event.taskId)) { + this.runningTasks.set(event.taskId, { exitCode: new Deferred(), terminateSignal: new Deferred() }); + } + this.runningTasks.get(event.taskId)!.exitCode.resolve(event.code); + this.runningTasks.get(event.taskId)!.terminateSignal.resolve(event.signal); + setTimeout(() => this.runningTasks.delete(event.taskId), 60 * 1000); const taskConfiguration = event.config; let taskIdentifier = event.taskId.toString(); if (taskConfiguration) { - taskIdentifier = !!this.taskDefinitionRegistry.getDefinition(taskConfiguration) - ? `${taskConfiguration._source}: ${taskConfiguration.label}` - : `${taskConfiguration.type}: ${taskConfiguration.label}`; + taskIdentifier = this.taskNameResolver.resolve(taskConfiguration); } if (event.code !== undefined) { @@ -302,24 +324,14 @@ export class TaskService implements TaskConfigurationClient { */ async run(source: string, taskLabel: string): Promise { let task = await this.getProvidedTask(source, taskLabel); - const customizationObject: TaskCustomization = { type: '' }; if (!task) { // if a detected task cannot be found, search from tasks.json task = this.taskConfigurations.getTask(source, taskLabel); if (!task) { this.logger.error(`Can't get task launch configuration for label: ${taskLabel}`); return; - } else { - Object.assign(customizationObject, { - type: task.type, - problemMatcher: task.problemMatcher - }); - } - } else { // if a detected task is found, check if it is customized in tasks.json - const customizationFound = this.taskConfigurations.getCustomizationForTask(task); - if (customizationFound) { - Object.assign(customizationObject, customizationFound); } } + const customizationObject = await this.getTaskCustomization(task); if (!customizationObject.problemMatcher) { // ask the user what s/he wants to use to parse the task output @@ -348,6 +360,65 @@ export class TaskService implements TaskConfigurationClient { } } + const resolvedMatchers = await this.resolveProblemMatchers(task, customizationObject); + return this.runTask(task, { + customization: { ...customizationObject, ...{ problemMatcher: resolvedMatchers } } + }); + } + + async runTask(task: TaskConfiguration, option?: RunTaskOption): Promise { + if (option && option.customization) { + const taskDefinition = this.taskDefinitionRegistry.getDefinition(task); + if (taskDefinition) { // use the customization object to override the task config + Object.keys(option.customization).forEach(customizedProperty => { + // properties used to define the task cannot be customized + if (customizedProperty !== 'type' && !taskDefinition.properties.all.some(pDefinition => pDefinition === customizedProperty)) { + task[customizedProperty] = option.customization![customizedProperty]; + } + }); + } + } + + const resolvedTask = await this.getResolvedTask(task); + if (resolvedTask) { + // remove problem markers from the same source before running the task + await this.removeProblemMarks(option); + return this.runResolvedTask(resolvedTask, option); + } + } + + async runTaskByLabel(taskLabel: string): Promise { + const tasks: TaskConfiguration[] = await this.getTasks(); + for (const task of tasks) { + if (task.label === taskLabel) { + return this.runTask(task); + } + } + + return; + } + + async runWorkspaceTask(workspaceFolderUri: string | undefined, taskName: string): Promise { + const tasks = await this.getWorkspaceTasks(workspaceFolderUri); + const task = tasks.filter(t => taskName === this.taskNameResolver.resolve(t))[0]; + if (!task) { + return undefined; + } + + const taskCustomization = await this.getTaskCustomization(task); + const resolvedMatchers = await this.resolveProblemMatchers(task, taskCustomization); + + return this.runTask(task, { + customization: { ...taskCustomization, ...{ problemMatcher: resolvedMatchers } } + }); + } + + protected async getWorkspaceTasks(workspaceFolderUri: string | undefined): Promise { + const tasks = await this.getTasks(); + return tasks.filter(t => t._scope === workspaceFolderUri || t._scope === undefined); + } + + protected async resolveProblemMatchers(task: TaskConfiguration, customizationObject: TaskCustomization): Promise { const notResolvedMatchers = customizationObject.problemMatcher ? (Array.isArray(customizationObject.problemMatcher) ? customizationObject.problemMatcher : [customizationObject.problemMatcher]) : undefined; let resolvedMatchers: ProblemMatcher[] | undefined = []; @@ -377,42 +448,21 @@ export class TaskService implements TaskConfigurationClient { } else { resolvedMatchers = undefined; } - - return this.runTask(task, { - customization: { ...customizationObject, ...{ problemMatcher: resolvedMatchers } } - }); + return resolvedMatchers; } - async runTask(task: TaskConfiguration, option?: RunTaskOption): Promise { - if (option && option.customization) { - const taskDefinition = this.taskDefinitionRegistry.getDefinition(task); - if (taskDefinition) { // use the customization object to override the task config - Object.keys(option.customization).forEach(customizedProperty => { - // properties used to define the task cannot be customized - if (customizedProperty !== 'type' && !taskDefinition.properties.all.some(pDefinition => pDefinition === customizedProperty)) { - task[customizedProperty] = option.customization![customizedProperty]; - } - }); - } - } - - const resolvedTask = await this.getResolvedTask(task); - if (resolvedTask) { - // remove problem markers from the same source before running the task - await this.removeProblemMarks(option); - return this.runResolvedTask(resolvedTask, option); - } - } - - async runTaskByLabel(taskLabel: string): Promise { - const tasks: TaskConfiguration[] = await this.getTasks(); - for (const task of tasks) { - if (task.label === taskLabel) { - await this.runTask(task); - return true; - } + protected async getTaskCustomization(task: TaskConfiguration): Promise { + const customizationObject: TaskCustomization = { type: '' }; + const customizationFound = this.taskConfigurations.getCustomizationForTask(task); + if (customizationFound) { + Object.assign(customizationObject, customizationFound); + } else { + Object.assign(customizationObject, { + type: task.type, + problemMatcher: task.problemMatcher + }); } - return false; + return customizationObject; } private async removeProblemMarks(option?: RunTaskOption): Promise { @@ -575,4 +625,14 @@ export class TaskService implements TaskConfigurationClient { } this.logger.debug(`Task killed. Task id: ${id}`); } + + async getExitCode(id: number): Promise { + const completedTask = this.runningTasks.get(id); + return completedTask && completedTask.exitCode.promise; + } + + async getTerminateSignal(id: number): Promise { + const completedTask = this.runningTasks.get(id); + return completedTask && completedTask.terminateSignal.promise; + } } diff --git a/packages/task/src/common/task-protocol.ts b/packages/task/src/common/task-protocol.ts index 6b066496ff607..0955f1d42a322 100644 --- a/packages/task/src/common/task-protocol.ts +++ b/packages/task/src/common/task-protocol.ts @@ -32,6 +32,12 @@ export interface TaskCustomization { export interface TaskConfiguration extends TaskCustomization { /** A label that uniquely identifies a task configuration per source */ readonly label: string; + /** + * For a provided task, it is the string representation of the URI where the task is supposed to run from. It is `undefined` for global tasks. + * For a configured task, it is workspace URI that task belongs to. + * This field is not supposed to be used in `tasks.json` + */ + readonly _scope: string | undefined; } export interface ContributedTaskConfiguration extends TaskConfiguration { @@ -41,11 +47,6 @@ export interface ContributedTaskConfiguration extends TaskConfiguration { * This field is not supposed to be used in `tasks.json` */ readonly _source: string; - /** - * For a provided task, it is the string representation of the URI where the task is supposed to run from. It is `undefined` for global tasks. - * This field is not supposed to be used in `tasks.json` - */ - readonly _scope: string | undefined; } /** Runtime information about Task. */