/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ 'use strict'; import 'vs/css!./media/task.contribution'; import 'vs/workbench/parts/tasks/browser/taskQuickOpen'; import * as nls from 'vs/nls'; import { TPromise } from 'vs/base/common/winjs.base'; import Severity from 'vs/base/common/severity'; import * as Objects from 'vs/base/common/objects'; import URI from 'vs/base/common/uri'; import { IStringDictionary } from 'vs/base/common/collections'; import { Action } from 'vs/base/common/actions'; import * as Dom from 'vs/base/browser/dom'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { EventEmitter } from 'vs/base/common/eventEmitter'; import * as Builder from 'vs/base/browser/builder'; import * as Types from 'vs/base/common/types'; import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { TerminateResponseCode } from 'vs/base/common/processes'; import * as strings from 'vs/base/common/strings'; import { ValidationStatus, ValidationState } from 'vs/base/common/parsers'; import * as UUID from 'vs/base/common/uuid'; import { LinkedMap, Touch } from 'vs/base/common/map'; import { OcticonLabel } from 'vs/base/browser/ui/octiconLabel/octiconLabel'; import { Registry } from 'vs/platform/registry/common/platform'; import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { SyncActionDescriptor, MenuRegistry } from 'vs/platform/actions/common/actions'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IEditor } from 'vs/platform/editor/common/editor'; import { IMessageService } from 'vs/platform/message/common/message'; import { IMarkerService, MarkerStatistics } from 'vs/platform/markers/common/markers'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IFileService } from 'vs/platform/files/common/files'; import { IExtensionService } from 'vs/platform/extensions/common/extensions'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { ProblemMatcherRegistry, NamedProblemMatcher } from 'vs/platform/markers/common/problemMatcher'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { IProgressService2, IProgressOptions, ProgressLocation } from 'vs/platform/progress/common/progress'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IWindowService } from 'vs/platform/windows/common/windows'; import { IModeService } from 'vs/editor/common/services/modeService'; import { IModelService } from 'vs/editor/common/services/modelService'; import jsonContributionRegistry = require('vs/platform/jsonschemas/common/jsonContributionRegistry'); import { IJSONSchema } from 'vs/base/common/jsonSchema'; import { IWorkbenchActionRegistry, Extensions as WorkbenchActionExtensions } from 'vs/workbench/common/actions'; import { IStatusbarItem, IStatusbarRegistry, Extensions as StatusbarExtensions, StatusbarItemDescriptor, StatusbarAlignment } from 'vs/workbench/browser/parts/statusbar/statusbar'; import { IQuickOpenRegistry, Extensions as QuickOpenExtensions, QuickOpenHandlerDescriptor } from 'vs/workbench/browser/quickopen'; import { IQuickOpenService, IPickOpenEntry } from 'vs/platform/quickOpen/common/quickOpen'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import Constants from 'vs/workbench/parts/markers/common/constants'; import { IPartService } from 'vs/workbench/services/part/common/partService'; import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver'; import { IConfigurationEditingService, ConfigurationTarget, IConfigurationValue } from 'vs/workbench/services/configuration/common/configurationEditing'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IOutputService, IOutputChannelRegistry, Extensions as OutputExt, IOutputChannel } from 'vs/workbench/parts/output/common/output'; import { Scope, IActionBarRegistry, Extensions as ActionBarExtensions } from 'vs/workbench/browser/actions'; import { ITerminalService } from 'vs/workbench/parts/terminal/common/terminal'; import { ITaskSystem, ITaskResolver, ITaskSummary, ITaskExecuteResult, TaskExecuteKind, TaskError, TaskErrors, TaskSystemEvents, TaskTerminateResponse } from 'vs/workbench/parts/tasks/common/taskSystem'; import { Task, CustomTask, ConfiguringTask, ContributedTask, CompositeTask, TaskSet, TaskGroup, ExecutionEngine, JsonSchemaVersion, TaskSourceKind, TaskIdentifier, WorkspaceFolder } from 'vs/workbench/parts/tasks/common/tasks'; import { ITaskService, TaskServiceEvents, ITaskProvider, TaskEvent, RunOptions, CustomizationProperties } from 'vs/workbench/parts/tasks/common/taskService'; import { templates as taskTemplates } from 'vs/workbench/parts/tasks/common/taskTemplates'; import * as TaskConfig from '../node/taskConfiguration'; import { ProcessTaskSystem } from 'vs/workbench/parts/tasks/node/processTaskSystem'; import { TerminalTaskSystem } from './terminalTaskSystem'; import { ProcessRunnerDetector } from 'vs/workbench/parts/tasks/node/processRunnerDetector'; import { QuickOpenActionContributor } from '../browser/quickOpen'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { Themable, STATUS_BAR_FOREGROUND, STATUS_BAR_NO_FOLDER_FOREGROUND } from 'vs/workbench/common/theme'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { ReloadWindowAction } from 'vs/workbench/electron-browser/actions'; let $ = Builder.$; let tasksCategory = nls.localize('tasksCategory', "Tasks"); abstract class OpenTaskConfigurationAction extends Action { constructor(id: string, label: string, private taskService: ITaskService, private configurationService: IConfigurationService, private editorService: IWorkbenchEditorService, private fileService: IFileService, private contextService: IWorkspaceContextService, private outputService: IOutputService, private messageService: IMessageService, private quickOpenService: IQuickOpenService, private environmentService: IEnvironmentService, private configurationResolverService: IConfigurationResolverService, private extensionService: IExtensionService, private telemetryService: ITelemetryService) { super(id, label); } public run(event?: any): TPromise { if (this.contextService.getWorkbenchState() === WorkbenchState.EMPTY) { this.messageService.show(Severity.Info, nls.localize('ConfigureTaskRunnerAction.noWorkspace', 'Tasks are only available on a workspace folder.')); return TPromise.as(undefined); } let sideBySide = !!(event && (event.ctrlKey || event.metaKey)); let configFileCreated = false; return this.fileService.resolveFile(this.contextService.toResource('.vscode/tasks.json', this.contextService.getWorkspace().folders[0])).then((success) => { // TODO@Dirk (https://github.com/Microsoft/vscode/issues/29454) return success; }, (err: any) => { return this.quickOpenService.pick(taskTemplates, { placeHolder: nls.localize('ConfigureTaskRunnerAction.quickPick.template', 'Select a Task Runner') }).then(selection => { if (!selection) { return undefined; } let contentPromise: TPromise; if (selection.autoDetect) { const outputChannel = this.outputService.getChannel(TaskService.OutputChannelId); outputChannel.show(true); outputChannel.append(nls.localize('ConfigureTaskRunnerAction.autoDetecting', 'Auto detecting tasks for {0}', selection.id) + '\n'); let detector = new ProcessRunnerDetector(this.fileService, this.contextService, this.configurationResolverService); contentPromise = detector.detect(false, selection.id).then((value) => { let config = value.config; if (value.stderr && value.stderr.length > 0) { value.stderr.forEach((line) => { outputChannel.append(line + '\n'); }); if (config && (!config.tasks || config.tasks.length === 0)) { this.messageService.show(Severity.Warning, nls.localize('ConfigureTaskRunnerAction.autoDetect', 'Auto detecting the task system failed. Using default template. Consult the task output for details.')); return selection.content; } else { this.messageService.show(Severity.Warning, nls.localize('ConfigureTaskRunnerAction.autoDetectError', 'Auto detecting the task system produced errors. Consult the task output for details.')); } } if (config) { if (value.stdout && value.stdout.length > 0) { value.stdout.forEach(line => outputChannel.append(line + '\n')); } let content = JSON.stringify(config, null, '\t'); content = [ '{', '\t// See https://go.microsoft.com/fwlink/?LinkId=733558', '\t// for the documentation about the tasks.json format', ].join('\n') + content.substr(1); return content; } else { return selection.content; } }); } else { contentPromise = TPromise.as(selection.content); } return contentPromise.then(content => { let editorConfig = this.configurationService.getConfiguration(); if (editorConfig.editor.insertSpaces) { content = content.replace(/(\n)(\t+)/g, (_, s1, s2) => s1 + strings.repeat(' ', s2.length * editorConfig.editor.tabSize)); } configFileCreated = true; return this.fileService.createFile(this.contextService.toResource('.vscode/tasks.json', this.contextService.getWorkspace().folders[0]), content).then((result) => { this.telemetryService.publicLog(TaskService.TemplateTelemetryEventName, { templateId: selection.id, autoDetect: selection.autoDetect }); return result; }); // TODO@Dirk (https://github.com/Microsoft/vscode/issues/29454) }); /* 2.0 version let content = selection.content; let editorConfig = this.configurationService.getConfiguration(); if (editorConfig.editor.insertSpaces) { content = content.replace(/(\n)(\t+)/g, (_, s1, s2) => s1 + strings.repeat(' ', s2.length * editorConfig.editor.tabSize)); } configFileCreated = true; return this.fileService.createFile(this.contextService.toResource('.vscode/tasks.json'), content); */ }); }).then((stat) => { if (!stat) { return undefined; } // // (2) Open editor with configuration file return this.editorService.openEditor({ resource: stat.resource, options: { forceOpen: true, pinned: configFileCreated // pin only if config file is created #8727 } }, sideBySide); }, (error) => { throw new Error(nls.localize('ConfigureTaskRunnerAction.failed', "Unable to create the 'tasks.json' file inside the '.vscode' folder. Consult the task output for details.")); }); } } class ConfigureTaskRunnerAction extends OpenTaskConfigurationAction { public static ID = 'workbench.action.tasks.configureTaskRunner'; public static TEXT = nls.localize('ConfigureTaskRunnerAction.label', "Configure Task Runner"); constructor(id: string, label: string, @ITaskService taskService: ITaskService, @IConfigurationService configurationService: IConfigurationService, @IWorkbenchEditorService editorService: IWorkbenchEditorService, @IFileService fileService: IFileService, @IWorkspaceContextService contextService: IWorkspaceContextService, @IOutputService outputService: IOutputService, @IMessageService messageService: IMessageService, @IQuickOpenService quickOpenService: IQuickOpenService, @IEnvironmentService environmentService: IEnvironmentService, @IConfigurationResolverService configurationResolverService: IConfigurationResolverService, @IExtensionService extensionService: IExtensionService, @ITelemetryService telemetryService: ITelemetryService) { super(id, label, taskService, configurationService, editorService, fileService, contextService, outputService, messageService, quickOpenService, environmentService, configurationResolverService, extensionService, telemetryService); } } class ConfigureBuildTaskAction extends OpenTaskConfigurationAction { public static ID = 'workbench.action.tasks.configureBuildTask'; public static TEXT = nls.localize('ConfigureBuildTaskAction.label', "Configure Build Task"); constructor(id: string, label: string, @ITaskService taskService: ITaskService, @IConfigurationService configurationService: IConfigurationService, @IWorkbenchEditorService editorService: IWorkbenchEditorService, @IFileService fileService: IFileService, @IWorkspaceContextService contextService: IWorkspaceContextService, @IOutputService outputService: IOutputService, @IMessageService messageService: IMessageService, @IQuickOpenService quickOpenService: IQuickOpenService, @IEnvironmentService environmentService: IEnvironmentService, @IConfigurationResolverService configurationResolverService: IConfigurationResolverService, @IExtensionService extensionService: IExtensionService, @ITelemetryService telemetryService: ITelemetryService) { super(id, label, taskService, configurationService, editorService, fileService, contextService, outputService, messageService, quickOpenService, environmentService, configurationResolverService, extensionService, telemetryService); } } class CloseMessageAction extends Action { public static ID = 'workbench.action.build.closeMessage'; public static TEXT = nls.localize('CloseMessageAction.label', 'Close'); public closeFunction: () => void; constructor() { super(CloseMessageAction.ID, CloseMessageAction.TEXT); } public run(): TPromise { if (this.closeFunction) { this.closeFunction(); } return TPromise.as(undefined); } } class ViewTerminalAction extends Action { public static ID = 'workbench.action.build.viewTerminal'; public static TEXT = nls.localize('ShowTerminalAction.label', 'View Terminal'); constructor( @ITerminalService private terminalService: ITerminalService) { super(ViewTerminalAction.ID, ViewTerminalAction.TEXT); } public run(): TPromise { this.terminalService.showPanel(); return TPromise.as(undefined); } } class BuildStatusBarItem extends Themable implements IStatusbarItem { private intervalToken: any; private activeCount: number; private static progressChars: string = '|/-\\'; private icons: HTMLElement[]; constructor( @IPanelService private panelService: IPanelService, @IMarkerService private markerService: IMarkerService, @IOutputService private outputService: IOutputService, @ITaskService private taskService: ITaskService, @IPartService private partService: IPartService, @IThemeService themeService: IThemeService, @IWorkspaceContextService private contextService: IWorkspaceContextService ) { super(themeService); this.activeCount = 0; this.icons = []; this.registerListeners(); } private registerListeners(): void { this.toUnbind.push(this.contextService.onDidChangeWorkbenchState(() => this.updateStyles())); } protected updateStyles(): void { super.updateStyles(); this.icons.forEach(icon => { icon.style.backgroundColor = this.getColor(this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY ? STATUS_BAR_FOREGROUND : STATUS_BAR_NO_FOLDER_FOREGROUND); }); } public render(container: HTMLElement): IDisposable { let callOnDispose: IDisposable[] = []; const element = document.createElement('div'); const progress = document.createElement('div'); const label = document.createElement('a'); const errorIcon = document.createElement('div'); const warningIcon = document.createElement('div'); const infoIcon = document.createElement('div'); const error = document.createElement('div'); const warning = document.createElement('div'); const info = document.createElement('div'); Dom.addClass(element, 'task-statusbar-item'); Dom.addClass(progress, 'task-statusbar-item-progress'); element.appendChild(progress); progress.innerHTML = BuildStatusBarItem.progressChars[0]; $(progress).hide(); Dom.addClass(label, 'task-statusbar-item-label'); element.appendChild(label); element.title = nls.localize('problems', "Problems"); Dom.addClass(errorIcon, 'task-statusbar-item-label-error'); Dom.addClass(errorIcon, 'mask-icon'); label.appendChild(errorIcon); this.icons.push(errorIcon); Dom.addClass(error, 'task-statusbar-item-label-counter'); error.innerHTML = '0'; label.appendChild(error); Dom.addClass(warningIcon, 'task-statusbar-item-label-warning'); Dom.addClass(warningIcon, 'mask-icon'); label.appendChild(warningIcon); this.icons.push(warningIcon); Dom.addClass(warning, 'task-statusbar-item-label-counter'); warning.innerHTML = '0'; label.appendChild(warning); Dom.addClass(infoIcon, 'task-statusbar-item-label-info'); Dom.addClass(infoIcon, 'mask-icon'); label.appendChild(infoIcon); this.icons.push(infoIcon); $(infoIcon).hide(); Dom.addClass(info, 'task-statusbar-item-label-counter'); label.appendChild(info); $(info).hide(); callOnDispose.push(Dom.addDisposableListener(label, 'click', (e: MouseEvent) => { const panel = this.panelService.getActivePanel(); if (panel && panel.getId() === Constants.MARKERS_PANEL_ID) { this.partService.setPanelHidden(true); } else { this.panelService.openPanel(Constants.MARKERS_PANEL_ID, true); } })); let updateStatus = (element: HTMLDivElement, icon: HTMLDivElement, stats: number): boolean => { if (stats > 0) { element.innerHTML = stats.toString(); $(element).show(); $(icon).show(); return true; } else { $(element).hide(); $(icon).hide(); return false; } }; let manyMarkers = nls.localize('manyMarkers', "99+"); let updateLabel = (stats: MarkerStatistics) => { error.innerHTML = stats.errors < 100 ? stats.errors.toString() : manyMarkers; warning.innerHTML = stats.warnings < 100 ? stats.warnings.toString() : manyMarkers; updateStatus(info, infoIcon, stats.infos); }; this.markerService.onMarkerChanged((changedResources) => { updateLabel(this.markerService.getStatistics()); }); callOnDispose.push(this.taskService.addListener(TaskServiceEvents.Active, (event: TaskEvent) => { if (this.ignoreEvent(event)) { return; } this.activeCount++; if (this.activeCount === 1) { let index = 1; let chars = BuildStatusBarItem.progressChars; progress.innerHTML = chars[0]; this.intervalToken = setInterval(() => { progress.innerHTML = chars[index]; index++; if (index >= chars.length) { index = 0; } }, 50); $(progress).show(); } })); callOnDispose.push(this.taskService.addListener(TaskServiceEvents.Inactive, (event: TaskEvent) => { if (this.ignoreEvent(event)) { return; } // Since the exiting of the sub process is communicated async we can't order inactive and terminate events. // So try to treat them accordingly. if (this.activeCount > 0) { this.activeCount--; if (this.activeCount === 0) { $(progress).hide(); if (this.intervalToken) { clearInterval(this.intervalToken); this.intervalToken = null; } } } })); callOnDispose.push(this.taskService.addListener(TaskServiceEvents.Terminated, (event: TaskEvent) => { if (this.ignoreEvent(event)) { return; } if (this.activeCount !== 0) { $(progress).hide(); if (this.intervalToken) { clearInterval(this.intervalToken); this.intervalToken = null; } this.activeCount = 0; } })); container.appendChild(element); this.updateStyles(); return { dispose: () => { callOnDispose = dispose(callOnDispose); } }; } private ignoreEvent(event: TaskEvent): boolean { if (!this.taskService.inTerminal()) { return false; } if (event.group !== TaskGroup.Build) { return true; } if (!event.__task) { return false; } return event.__task.problemMatchers === void 0 || event.__task.problemMatchers.length === 0; } } class TaskStatusBarItem extends Themable implements IStatusbarItem { constructor( @IPanelService private panelService: IPanelService, @IMarkerService private markerService: IMarkerService, @IOutputService private outputService: IOutputService, @ITaskService private taskService: ITaskService, @IPartService private partService: IPartService, @IThemeService themeService: IThemeService, @IWorkspaceContextService private contextService: IWorkspaceContextService, ) { super(themeService); } protected updateStyles(): void { super.updateStyles(); } public render(container: HTMLElement): IDisposable { let callOnDispose: IDisposable[] = []; const element = document.createElement('a'); Dom.addClass(element, 'task-statusbar-runningItem'); let labelElement = document.createElement('div'); Dom.addClass(labelElement, 'task-statusbar-runningItem-label'); element.appendChild(labelElement); let label = new OcticonLabel(labelElement); label.title = nls.localize('runningTasks', "Show Running Tasks"); $(element).hide(); callOnDispose.push(Dom.addDisposableListener(labelElement, 'click', (e: MouseEvent) => { (this.taskService as TaskService).runShowTasks(); })); let updateStatus = (): void => { this.taskService.getActiveTasks().then(tasks => { if (tasks.length === 0) { $(element).hide(); } else { label.text = `$(tools) ${tasks.length}`; $(element).show(); } }); }; callOnDispose.push(this.taskService.addListener(TaskServiceEvents.Changed, (event: TaskEvent) => { updateStatus(); })); container.appendChild(element); this.updateStyles(); updateStatus(); return { dispose: () => { callOnDispose = dispose(callOnDispose); } }; } } interface TaskServiceEventData { error?: any; } class NullTaskSystem extends EventEmitter implements ITaskSystem { public run(task: Task): ITaskExecuteResult { return { kind: TaskExecuteKind.Started, promise: TPromise.as({}) }; } public revealTask(task: Task): boolean { return false; } public isActive(): TPromise { return TPromise.as(false); } public isActiveSync(): boolean { return false; } public getActiveTasks(): Task[] { return []; } public canAutoTerminate(): boolean { return true; } public terminate(task: string | Task): TPromise { return TPromise.as({ success: true, task: undefined }); } public terminateAll(): TPromise { return TPromise.as([]); } } class ProblemReporter implements TaskConfig.IProblemReporter { private _validationStatus: ValidationStatus; constructor(private _outputChannel: IOutputChannel) { this._validationStatus = new ValidationStatus(); } public info(message: string): void { this._validationStatus.state = ValidationState.Info; this._outputChannel.append(message + '\n'); } public warn(message: string): void { this._validationStatus.state = ValidationState.Warning; this._outputChannel.append(message + '\n'); } public error(message: string): void { this._validationStatus.state = ValidationState.Error; this._outputChannel.append(message + '\n'); } public fatal(message: string): void { this._validationStatus.state = ValidationState.Fatal; this._outputChannel.append(message + '\n'); } public get status(): ValidationStatus { return this._validationStatus; } public clearOutput(): void { this._outputChannel.clear(); } } interface WorkspaceTaskResult { set: TaskSet; configurations: { byIdentifier: IStringDictionary; }; hasErrors: boolean; } interface WorkspaceFolderTaskResult extends WorkspaceTaskResult { workspaceFolder: WorkspaceFolder; } interface WorkspaceFolderConfigurationResult { workspaceFolder: WorkspaceFolder; config: TaskConfig.ExternalTaskRunnerConfiguration; hasErrors: boolean; } interface TaskCustomizationTelementryEvent { properties: string[]; } class TaskService extends EventEmitter implements ITaskService { // private static autoDetectTelemetryName: string = 'taskServer.autoDetect'; private static RecentlyUsedTasks_Key = 'workbench.tasks.recentlyUsedTasks'; private static RanTaskBefore_Key = 'workbench.tasks.ranTaskBefore'; private static CustomizationTelemetryEventName: string = 'taskService.customize'; public static TemplateTelemetryEventName: string = 'taskService.template'; public _serviceBrand: any; public static SERVICE_ID: string = 'taskService'; public static OutputChannelId: string = 'tasks'; public static OutputChannelLabel: string = nls.localize('tasks', "Tasks"); private modeService: IModeService; private configurationService: IConfigurationService; private configurationEditingService: IConfigurationEditingService; private markerService: IMarkerService; private outputService: IOutputService; private messageService: IMessageService; private fileService: IFileService; private telemetryService: ITelemetryService; private editorService: IWorkbenchEditorService; private contextService: IWorkspaceContextService; private textFileService: ITextFileService; private modelService: IModelService; private extensionService: IExtensionService; private quickOpenService: IQuickOpenService; private _configHasErrors: boolean; private _schemaVersion: JsonSchemaVersion; private _executionEngine: ExecutionEngine; private _workspaceFolders: WorkspaceFolder[]; private _providers: Map; private _workspaceTasksPromise: TPromise>; private _taskSystem: ITaskSystem; private _taskSystemListeners: IDisposable[]; private _recentlyUsedTasks: LinkedMap; private _outputChannel: IOutputChannel; constructor( @IModeService modeService: IModeService, @IConfigurationService configurationService: IConfigurationService, @IConfigurationEditingService configurationEditingService: IConfigurationEditingService, @IMarkerService markerService: IMarkerService, @IOutputService outputService: IOutputService, @IMessageService messageService: IMessageService, @IWorkbenchEditorService editorService: IWorkbenchEditorService, @IFileService fileService: IFileService, @IWorkspaceContextService contextService: IWorkspaceContextService, @ITelemetryService telemetryService: ITelemetryService, @ITextFileService textFileService: ITextFileService, @ILifecycleService lifecycleService: ILifecycleService, @IModelService modelService: IModelService, @IExtensionService extensionService: IExtensionService, @IQuickOpenService quickOpenService: IQuickOpenService, @IEnvironmentService private environmentService: IEnvironmentService, @IConfigurationResolverService private configurationResolverService: IConfigurationResolverService, @ITerminalService private terminalService: ITerminalService, @IWorkbenchEditorService private workbenchEditorService: IWorkbenchEditorService, @IStorageService private storageService: IStorageService, @IProgressService2 private progressService: IProgressService2, @IOpenerService private openerService: IOpenerService, @IWindowService private _windowServive: IWindowService ) { super(); this.modeService = modeService; this.configurationService = configurationService; this.configurationEditingService = configurationEditingService; this.markerService = markerService; this.outputService = outputService; this.messageService = messageService; this.editorService = editorService; this.fileService = fileService; this.contextService = contextService; this.telemetryService = telemetryService; this.textFileService = textFileService; this.modelService = modelService; this.extensionService = extensionService; this.quickOpenService = quickOpenService; this._configHasErrors = false; this._workspaceTasksPromise = undefined; this._taskSystemListeners = []; this._outputChannel = this.outputService.getChannel(TaskService.OutputChannelId); this._providers = new Map(); this.configurationService.onDidUpdateConfiguration(() => { if (!this._taskSystem && !this._workspaceTasksPromise) { return; } let folderSetup = this.computeWorkspaceFolders(); if (this._executionEngine !== folderSetup[1] && this._taskSystem && this._taskSystem.getActiveTasks().length > 0) { this.messageService.show( Severity.Info, { message: nls.localize( 'TaskSystem.noHotSwap', 'Changing the task execution engine with an active task running requires to reload the Window' ), actions: [ new ReloadWindowAction(ReloadWindowAction.ID, ReloadWindowAction.LABEL, this._windowServive), new CloseMessageAction() ] } ); return; } this._workspaceFolders = folderSetup[0]; this._executionEngine = folderSetup[1]; this._schemaVersion = folderSetup[2]; this.updateWorkspaceTasks(); }); let folderSetup = this.computeWorkspaceFolders(); this._workspaceFolders = folderSetup[0]; this._executionEngine = folderSetup[1]; this._schemaVersion = folderSetup[2]; lifecycleService.onWillShutdown(event => event.veto(this.beforeShutdown())); this.registerCommands(); } private registerCommands(): void { CommandsRegistry.registerCommand('workbench.action.tasks.runTask', (accessor, arg) => { this.runTaskCommand(accessor, arg); }); CommandsRegistry.registerCommand('workbench.action.tasks.restartTask', (accessor, arg) => { this.runRestartTaskCommand(accessor, arg); }); CommandsRegistry.registerCommand('workbench.action.tasks.terminate', (accessor, arg) => { this.runTerminateCommand(); }); CommandsRegistry.registerCommand('workbench.action.tasks.showLog', () => { if (!this.canRunCommand()) { return; } this.showOutput(); }); CommandsRegistry.registerCommand('workbench.action.tasks.build', () => { if (!this.canRunCommand()) { return; } this.runBuildCommand(); }); KeybindingsRegistry.registerKeybindingRule({ id: 'workbench.action.tasks.build', weight: KeybindingsRegistry.WEIGHT.workbenchContrib(), when: undefined, primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_B }); CommandsRegistry.registerCommand('workbench.action.tasks.test', () => { if (!this.canRunCommand()) { return; } this.runTestCommand(); }); CommandsRegistry.registerCommand('workbench.action.tasks.configureDefaultBuildTask', () => { this.runConfigureDefaultBuildTask(); }); CommandsRegistry.registerCommand('workbench.action.tasks.configureDefaultTestTask', () => { this.runConfigureDefaultTestTask(); }); CommandsRegistry.registerCommand('workbench.action.tasks.showTasks', () => { this.runShowTasks(); }); } private showOutput(): void { this._outputChannel.show(true); } private disposeTaskSystemListeners(): void { this._taskSystemListeners = dispose(this._taskSystemListeners); } public registerTaskProvider(handle: number, provider: ITaskProvider): void { if (!provider) { return; } this._providers.set(handle, provider); } public unregisterTaskProvider(handle: number): boolean { return this._providers.delete(handle); } public getTask(identifier: string): TPromise { return this.getAllTasks().then((tasks) => { let resolver = this.createResolver(tasks); return resolver.resolve(identifier); }); } public tasks(): TPromise { return this.getAllTasks(); }; public isActive(): TPromise { if (!this._taskSystem) { return TPromise.as(false); } return this._taskSystem.isActive(); } public getActiveTasks(): TPromise { if (!this._taskSystem) { return TPromise.as([]); } return TPromise.as(this._taskSystem.getActiveTasks()); } public getRecentlyUsedTasks(): LinkedMap { if (this._recentlyUsedTasks) { return this._recentlyUsedTasks; } this._recentlyUsedTasks = new LinkedMap(); let storageValue = this.storageService.get(TaskService.RecentlyUsedTasks_Key, StorageScope.WORKSPACE); if (storageValue) { try { let values: string[] = JSON.parse(storageValue); if (Array.isArray(values)) { for (let value of values) { this._recentlyUsedTasks.set(value, value); } } } catch (error) { // Ignore. We use the empty result } } return this._recentlyUsedTasks; } private saveRecentlyUsedTasks(): void { if (!this._recentlyUsedTasks) { return; } let values = this._recentlyUsedTasks.values(); if (values.length > 30) { values = values.slice(0, 30); } this.storageService.store(TaskService.RecentlyUsedTasks_Key, JSON.stringify(values), StorageScope.WORKSPACE); } private openDocumentation(): void { this.openerService.open(URI.parse('https://go.microsoft.com/fwlink/?LinkId=733558')); } public build(): TPromise { return this.getAllTasks().then((tasks) => { let runnable = this.createRunnableTask(tasks, TaskGroup.Build); if (!runnable || !runnable.task) { if (this._schemaVersion === JsonSchemaVersion.V0_1_0) { throw new TaskError(Severity.Info, nls.localize('TaskService.noBuildTask1', 'No build task defined. Mark a task with \'isBuildCommand\' in the tasks.json file.'), TaskErrors.NoBuildTask); } else { throw new TaskError(Severity.Info, nls.localize('TaskService.noBuildTask2', 'No build task defined. Mark a task with as a \'build\' group in the tasks.json file.'), TaskErrors.NoBuildTask); } } return this.executeTask(runnable.task, runnable.resolver); }).then(value => value, (error) => { this.handleError(error); return TPromise.wrapError(error); }); } public rebuild(): TPromise { return TPromise.wrapError(new Error('Not implemented')); } public clean(): TPromise { return TPromise.wrapError(new Error('Not implemented')); } public runTest(): TPromise { return this.getAllTasks().then((tasks) => { let runnable = this.createRunnableTask(tasks, TaskGroup.Test); if (!runnable || !runnable.task) { if (this._schemaVersion === JsonSchemaVersion.V0_1_0) { throw new TaskError(Severity.Info, nls.localize('TaskService.noTestTask1', 'No test task defined. Mark a task with \'isTestCommand\' in the tasks.json file.'), TaskErrors.NoTestTask); } else { throw new TaskError(Severity.Info, nls.localize('TaskService.noTestTask2', 'No test task defined. Mark a task with as a \'test\' group in the tasks.json file.'), TaskErrors.NoTestTask); } } return this.executeTask(runnable.task, runnable.resolver); }).then(value => value, (error) => { this.handleError(error); return TPromise.wrapError(error); }); } public run(task: string | Task, options?: RunOptions): TPromise { return this.getAllTasks().then((tasks) => { let resolver = this.createResolver(tasks); let requested: string; let toExecute: Task; if (Types.isString(task)) { requested = task; toExecute = resolver.resolve(task); } else { requested = task.name; toExecute = task; } if (!toExecute) { throw new TaskError(Severity.Info, nls.localize('TaskServer.noTask', 'Requested task {0} to execute not found.', requested), TaskErrors.TaskNotFound); } else { if (options && options.attachProblemMatcher && this.shouldAttachProblemMatcher(toExecute) && !CompositeTask.is(toExecute)) { return this.attachProblemMatcher(toExecute).then((toExecute) => { if (toExecute) { return this.executeTask(toExecute, resolver); } else { return TPromise.as(undefined); } }); } return this.executeTask(toExecute, resolver); } }).then(value => value, (error) => { this.handleError(error); return TPromise.wrapError(error); }); } private shouldAttachProblemMatcher(task: Task): boolean { if (!this.canCustomize(task)) { return false; } if (task.group !== void 0 && task.group !== TaskGroup.Build) { return false; } if (task.problemMatchers !== void 0 && task.problemMatchers.length > 0) { return false; } if (ContributedTask.is(task)) { return !task.hasDefinedMatchers && task.problemMatchers.length === 0; } if (CustomTask.is(task)) { let configProperties: TaskConfig.ConfigurationProperties = task._source.config.element; return configProperties.problemMatcher === void 0; } return false; } private attachProblemMatcher(task: ContributedTask | CustomTask): TPromise { interface ProblemMatcherPickEntry extends IPickOpenEntry { matcher: NamedProblemMatcher; never?: boolean; learnMore?: boolean; } let entries: ProblemMatcherPickEntry[] = []; for (let key of ProblemMatcherRegistry.keys()) { let matcher = ProblemMatcherRegistry.get(key); if (matcher.deprecated) { continue; } if (matcher.name === matcher.label) { entries.push({ label: matcher.name, matcher: matcher }); } else { entries.push({ label: matcher.label, description: `$${matcher.name}`, matcher: matcher }); } } if (entries.length > 0) { entries = entries.sort((a, b) => a.label.localeCompare(b.label)); entries[0].separator = { border: true }; entries.unshift( { label: nls.localize('TaskService.attachProblemMatcher.continueWithout', 'Continue without scanning the task output'), matcher: undefined }, { label: nls.localize('TaskService.attachProblemMatcher.never', 'Never scan the task output'), matcher: undefined, never: true }, { label: nls.localize('TaskService.attachProblemMatcher.learnMoreAbout', 'Learn more about scanning the task output'), matcher: undefined, learnMore: true } ); return this.quickOpenService.pick(entries, { placeHolder: nls.localize('selectProblemMatcher', 'Select for which kind of errors and warnings to scan the task output'), autoFocus: { autoFocusFirstEntry: true } }).then((selected) => { if (selected) { if (selected.learnMore) { this.openDocumentation(); return undefined; } else if (selected.never) { this.customize(task, { problemMatcher: [] }, true); return task; } else if (selected.matcher) { let newTask = Task.clone(task); let matcherReference = `$${selected.matcher.name}`; newTask.problemMatchers = [matcherReference]; this.customize(task, { problemMatcher: [matcherReference] }, true); return newTask; } else { return task; } } else { return undefined; } }); } return TPromise.as(task); } public getTasksForGroup(group: string): TPromise { return this.getAllTasks().then((tasks) => { let result: Task[] = []; for (let task of tasks) { if (task.group === group) { result.push(task); } } return result; }); } public hasMultipleFolders(): boolean { return this._workspaceFolders && this._workspaceFolders.length > 1; } public canCustomize(task: Task): boolean { if (this._schemaVersion !== JsonSchemaVersion.V2_0_0) { return false; } if (CustomTask.is(task)) { return true; } if (ContributedTask.is(task)) { return !!Task.getWorkspaceFolder(task); } return false; } public customize(task: ContributedTask | CustomTask, properties?: CustomizationProperties, openConfig?: boolean): TPromise { let workspaceFolder = Task.getWorkspaceFolder(task); if (!workspaceFolder) { return TPromise.as(undefined); } let configuration = this.getConfiguration(workspaceFolder); if (configuration.hasParseErrors) { this.messageService.show(Severity.Warning, nls.localize('customizeParseErrors', 'The current task configuration has errors. Please fix the errors first before customizing a task.')); return TPromise.as(undefined); } let fileConfig = configuration.config; let index: number; let toCustomize: TaskConfig.CustomTask | TaskConfig.ConfiguringTask; let taskConfig = CustomTask.is(task) ? task._source.config : undefined; if (taskConfig && taskConfig.element) { index = taskConfig.index; toCustomize = taskConfig.element; } else if (ContributedTask.is(task)) { toCustomize = { }; let identifier: TaskConfig.TaskIdentifier = Objects.assign(Object.create(null), task.defines); delete identifier['_key']; Object.keys(identifier).forEach(key => toCustomize[key] = identifier[key]); if (task.problemMatchers && task.problemMatchers.length > 0 && Types.isStringArray(task.problemMatchers)) { toCustomize.problemMatcher = task.problemMatchers; } } if (!toCustomize) { return TPromise.as(undefined); } if (properties) { for (let property of Object.getOwnPropertyNames(properties)) { let value = properties[property]; if (value !== void 0 && value !== null) { toCustomize[property] = value; } } } else { if (toCustomize.problemMatcher === void 0 && task.problemMatchers === void 0 || task.problemMatchers.length === 0) { toCustomize.problemMatcher = []; } } let promise: TPromise; if (!fileConfig) { let value = { version: '2.0.0', tasks: [toCustomize] }; let content = [ '{', '\t// See https://go.microsoft.com/fwlink/?LinkId=733558', '\t// for the documentation about the tasks.json format', ].join('\n') + JSON.stringify(value, null, '\t').substr(1); let editorConfig = this.configurationService.getConfiguration(); if (editorConfig.editor.insertSpaces) { content = content.replace(/(\n)(\t+)/g, (_, s1, s2) => s1 + strings.repeat(' ', s2.length * editorConfig.editor.tabSize)); } promise = this.fileService.createFile(this.contextService.toResource('.vscode/tasks.json', this.contextService.getWorkspace().folders[0]), content).then(() => { }); // TODO@Dirk (https://github.com/Microsoft/vscode/issues/29454) } else { let value: IConfigurationValue = { key: undefined, value: undefined }; // We have a global task configuration if (index === -1) { if (properties.problemMatcher !== void 0) { fileConfig.problemMatcher = properties.problemMatcher; value.key = 'tasks.problemMatchers'; value.value = fileConfig.problemMatcher; promise = this.writeConfiguration(workspaceFolder, value); } else if (properties.group !== void 0) { fileConfig.group = properties.group; value.key = 'tasks.group'; value.value = fileConfig.group; promise = this.writeConfiguration(workspaceFolder, value); } } else { if (!Array.isArray(fileConfig.tasks)) { fileConfig.tasks = []; } value.key = 'tasks.tasks'; value.value = fileConfig.tasks; if (index === void 0) { fileConfig.tasks.push(toCustomize); } else { fileConfig.tasks[index] = toCustomize; } promise = this.writeConfiguration(workspaceFolder, value); } }; if (!promise) { return TPromise.as(undefined); } return promise.then(() => { let event: TaskCustomizationTelementryEvent = { properties: properties ? Object.getOwnPropertyNames(properties) : [] }; this.telemetryService.publicLog(TaskService.CustomizationTelemetryEventName, event); if (openConfig) { let resource = this.contextService.toResource('.vscode/tasks.json', this.contextService.getWorkspace().folders[0]); // TODO@Dirk (https://github.com/Microsoft/vscode/issues/29454) this.editorService.openEditor({ resource: resource, options: { forceOpen: true, pinned: false } }, false); } }); } private writeConfiguration(workspaceFolder: WorkspaceFolder, value: IConfigurationValue): TPromise { if (this.contextService.getWorkbenchState() === WorkbenchState.FOLDER) { return this.configurationEditingService.writeConfiguration(ConfigurationTarget.WORKSPACE, value); } else if (this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE) { return this.configurationEditingService.writeConfiguration(ConfigurationTarget.FOLDER, value, { scopes: { resource: workspaceFolder.uri } }); } else { return undefined; } } public openConfig(task: CustomTask): TPromise { // @ToDo need to adopt since this is not working anymore let resource = this.contextService.toResource(task._source.config.file, this.contextService.getWorkspace().folders[0]); return this.editorService.openEditor({ resource: resource, options: { forceOpen: true, pinned: false } }, false).then(() => undefined); } private createRunnableTask(tasks: Task[], group: TaskGroup): { task: Task; resolver: ITaskResolver } { let idMap: IStringDictionary = Object.create(null); let labelMap: IStringDictionary = Object.create(null); let identifierMap: IStringDictionary = Object.create(null); let workspaceTasks: Task[] = []; let extensionTasks: Task[] = []; tasks.forEach((task) => { idMap[task._id] = task; labelMap[task._label] = task; identifierMap[task.identifier] = task; if (group && task.group === group) { if (task._source.kind === TaskSourceKind.Workspace) { workspaceTasks.push(task); } else { extensionTasks.push(task); } } }); let resolver: ITaskResolver = { resolve: (id: string) => { return idMap[id] || labelMap[id] || identifierMap[id]; } }; if (workspaceTasks.length > 0) { if (workspaceTasks.length > 1) { this._outputChannel.append(nls.localize('moreThanOneBuildTask', 'There are many build tasks defined in the tasks.json. Executing the first one.\n')); } return { task: workspaceTasks[0], resolver }; } if (extensionTasks.length === 0) { return undefined; } // We can only have extension tasks if we are in version 2.0.0. Then we can even run // multiple build tasks. if (extensionTasks.length === 1) { return { task: extensionTasks[0], resolver }; } else { let id: string = UUID.generateUuid(); let task: CompositeTask = { _id: id, _source: { kind: TaskSourceKind.Composite, label: 'composite' }, _label: id, type: 'composite', name: id, identifier: id, dependsOn: extensionTasks.map(task => task._id) }; return { task, resolver }; } } private createResolver(tasks: Task[]): ITaskResolver { let labelMap: IStringDictionary = Object.create(null); let identifierMap: IStringDictionary = Object.create(null); tasks.forEach((task) => { labelMap[task._label] = task; identifierMap[task.identifier] = task; }); return { resolve: (id: string) => { return labelMap[id] || identifierMap[id]; } }; } private executeTask(task: Task, resolver: ITaskResolver): TPromise { if (!this.storageService.get(TaskService.RanTaskBefore_Key, StorageScope.GLOBAL)) { this.storageService.store(TaskService.RanTaskBefore_Key, true, StorageScope.GLOBAL); } return ProblemMatcherRegistry.onReady().then(() => { return this.textFileService.saveAll().then((value) => { // make sure all dirty files are saved let executeResult = this.getTaskSystem().run(task, resolver); this.getRecentlyUsedTasks().set(Task.getKey(task), Task.getKey(task), Touch.First); if (executeResult.kind === TaskExecuteKind.Active) { let active = executeResult.active; if (active.same) { if (active.background) { this.messageService.show(Severity.Info, nls.localize('TaskSystem.activeSame.background', 'The task \'{0}\' is already active and in background mode. To terminate it use `Terminate Task...` from the Tasks menu.', task._label)); } else { this.messageService.show(Severity.Info, nls.localize('TaskSystem.activeSame.noBackground', 'The task \'{0}\' is already active. To terminate it use `Terminate Task...` from the Tasks menu.', task._label)); } } else { throw new TaskError(Severity.Warning, nls.localize('TaskSystem.active', 'There is already a task running. Terminate it first before executing another task.'), TaskErrors.RunningTask); } } return executeResult.promise; }); }); } public restart(task: string | Task): void { if (!this._taskSystem) { return; } const id: string = Types.isString(task) ? task : task._id; this._taskSystem.terminate(id).then((response) => { if (response.success) { this.emit(TaskServiceEvents.Terminated, {}); this.run(task); } else { this.messageService.show(Severity.Warning, nls.localize('TaskSystem.restartFailed', 'Failed to terminate and restart task {0}', Types.isString(task) ? task : task.name)); } return response; }); } public terminate(task: string | Task): TPromise { if (!this._taskSystem) { return TPromise.as({ success: true, task: undefined }); } const id: string = Types.isString(task) ? task : task._id; return this._taskSystem.terminate(id); } public terminateAll(): TPromise { if (!this._taskSystem) { return TPromise.as([]); } return this._taskSystem.terminateAll(); } private getTaskSystem(): ITaskSystem { if (this._taskSystem) { return this._taskSystem; } if (this._executionEngine === ExecutionEngine.Terminal) { this._taskSystem = new TerminalTaskSystem( this.terminalService, this.outputService, this.markerService, this.modelService, this.configurationResolverService, this.telemetryService, this.workbenchEditorService, this.contextService, TaskService.OutputChannelId ); } else { let system = new ProcessTaskSystem( this.markerService, this.modelService, this.telemetryService, this.outputService, this.configurationResolverService, this.contextService, TaskService.OutputChannelId, ); system.hasErrors(this._configHasErrors); this._taskSystem = system; } this._taskSystemListeners.push(this._taskSystem.addListener(TaskSystemEvents.Active, (event) => this.emit(TaskServiceEvents.Active, event))); this._taskSystemListeners.push(this._taskSystem.addListener(TaskSystemEvents.Inactive, (event) => this.emit(TaskServiceEvents.Inactive, event))); this._taskSystemListeners.push(this._taskSystem.addListener(TaskSystemEvents.Terminated, (event) => this.emit(TaskServiceEvents.Terminated, event))); this._taskSystemListeners.push(this._taskSystem.addListener(TaskSystemEvents.Changed, () => this.emit(TaskServiceEvents.Changed))); return this._taskSystem; } private getAllTasks(): TPromise { return this.extensionService.activateByEvent('onCommand:workbench.action.tasks.runTask').then(() => { return new TPromise((resolve, reject) => { let result: TaskSet[] = []; let counter: number = 0; let done = (value: TaskSet) => { if (value) { result.push(value); } if (--counter === 0) { resolve(result); } }; let error = () => { if (--counter === 0) { resolve(result); } }; if (this._schemaVersion === JsonSchemaVersion.V2_0_0 && this._providers.size > 0) { this._providers.forEach((provider) => { counter++; provider.provideTasks().done(done, error); }); } else { resolve(result); } }); }).then((contributedTaskSets) => { let result: Task[] = []; let contributedTasks: Map = new Map(); for (let set of contributedTaskSets) { for (let task of set.tasks) { if (!ContributedTask.is(task)) { continue; } let workspaceFolder = task._source.workspaceFolder; if (workspaceFolder) { let values = contributedTasks.get(workspaceFolder.uri.toString()); if (!values) { values = [task]; contributedTasks.set(workspaceFolder.uri.toString(), values); } else { values.push(task); } } else { result.push(task); } } } return this.getWorkspaceTasks().then((customTasks) => { customTasks.forEach((folderTasks, key) => { let contributed = contributedTasks.get(key); if (!folderTasks.set) { if (contributed) { result.push(...contributed); } return; } if (!contributed) { result.push(...folderTasks.set.tasks); } else { let configurations = folderTasks.configurations; let legacyTaskConfigurations = folderTasks.set ? this.getLegacyTaskConfigurations(folderTasks.set) : undefined; let customTasksToDelete: Task[] = []; if (configurations || legacyTaskConfigurations) { for (let task of contributed) { if (!ContributedTask.is(task)) { continue; } if (configurations) { let configuringTask = configurations.byIdentifier[task.defines._key]; if (configuringTask) { result.push(TaskConfig.createCustomTask(task, configuringTask)); } else { result.push(task); } } else if (legacyTaskConfigurations) { let configuringTask = legacyTaskConfigurations[task.defines._key]; if (configuringTask) { result.push(TaskConfig.createCustomTask(task, configuringTask)); customTasksToDelete.push(configuringTask); } else { result.push(task); } } else { result.push(task); } } if (customTasksToDelete.length > 0) { let toDelete = customTasksToDelete.reduce>((map, task) => { map[task._id] = true; return map; }, Object.create(null)); for (let task of folderTasks.set.tasks) { if (toDelete[task._id]) { continue; } result.push(task); } } else { result.push(...folderTasks.set.tasks); } } else { result.push(...folderTasks.set.tasks); result.push(...contributed); } } }); return result; }, () => { // If we can't read the tasks.json file provide at least the contributed tasks let result: Task[] = []; for (let set of contributedTaskSets) { result.push(...set.tasks); } return result; }); }); } private getLegacyTaskConfigurations(workspaceTasks: TaskSet): IStringDictionary { let result: IStringDictionary; function getResult() { if (result) { return result; } result = Object.create(null); return result; } for (let task of workspaceTasks.tasks) { if (CustomTask.is(task)) { let commandName = task.command && task.command.name; // This is for backwards compatibility with the 0.1.0 task annotation code // if we had a gulp, jake or grunt command a task specification was a annotation if (commandName === 'gulp' || commandName === 'grunt' || commandName === 'jake') { let identifier: TaskIdentifier = TaskConfig.getTaskIdentifier({ type: commandName, task: task.name } as TaskConfig.TaskIdentifier); getResult()[identifier._key] = task; } } } return result; } private getWorkspaceTasks(): TPromise> { if (this._workspaceTasksPromise) { return this._workspaceTasksPromise; } this.updateWorkspaceTasks(); return this._workspaceTasksPromise; } private updateWorkspaceTasks(): void { this._workspaceTasksPromise = this.computeWorkspaceTasks().then(value => { if (this._executionEngine === ExecutionEngine.Process && this._taskSystem instanceof ProcessTaskSystem) { // We can only have a process engine if we have one folder. value.forEach((value) => { this._configHasErrors = value.hasErrors; (this._taskSystem as ProcessTaskSystem).hasErrors(this._configHasErrors); }); } return value; }); } private computeWorkspaceTasks(): TPromise> { if (this._workspaceFolders.length === 0) { return TPromise.as(new Map()); } else { let promises: TPromise[] = []; for (let folder of this._workspaceFolders) { promises.push(this.computeWorkspaceFolderTasks(folder).then((value) => value, () => undefined)); } return TPromise.join(promises).then((values) => { let result = new Map(); for (let value of values) { if (value) { result.set(value.workspaceFolder.uri.toString(), value); } } return result; }); } } private computeWorkspaceFolderTasks(workspaceFolder: WorkspaceFolder): TPromise { return (this._executionEngine === ExecutionEngine.Process ? this.computeLegacyConfiguration(workspaceFolder) : this.computeConfiguration(workspaceFolder)). then((workspaceFolderConfiguration) => { if (!workspaceFolderConfiguration || !workspaceFolderConfiguration.config || workspaceFolderConfiguration.hasErrors) { return TPromise.as({ workspaceFolder, set: undefined, configurations: undefined, hasErrors: workspaceFolderConfiguration ? workspaceFolderConfiguration.hasErrors : false }); } return ProblemMatcherRegistry.onReady().then((): WorkspaceFolderTaskResult => { let problemReporter = new ProblemReporter(this._outputChannel); let parseResult = TaskConfig.parse(workspaceFolder, workspaceFolderConfiguration.config, problemReporter); let hasErrors = false; if (!parseResult.validationStatus.isOK()) { hasErrors = true; this.showOutput(); } if (problemReporter.status.isFatal()) { problemReporter.fatal(nls.localize('TaskSystem.configurationErrors', 'Error: the provided task configuration has validation errors and can\'t not be used. Please correct the errors first.')); return { workspaceFolder, set: undefined, configurations: undefined, hasErrors }; } let customizedTasks: { byIdentifier: IStringDictionary; }; if (parseResult.configured && parseResult.configured.length > 0) { customizedTasks = { byIdentifier: Object.create(null) }; for (let task of parseResult.configured) { customizedTasks.byIdentifier[task.configures._key] = task; } } return { workspaceFolder, set: { tasks: parseResult.custom }, configurations: customizedTasks, hasErrors }; }); }); } private computeConfiguration(workspaceFolder: WorkspaceFolder): TPromise { let { config, hasParseErrors } = this.getConfiguration(workspaceFolder); return TPromise.as({ workspaceFolder, config, hasErrors: hasParseErrors }); } private computeLegacyConfiguration(workspaceFolder: WorkspaceFolder): TPromise { let { config, hasParseErrors } = this.getConfiguration(workspaceFolder); if (hasParseErrors) { return TPromise.as({ workspaceFolder: workspaceFolder, hasErrors: true, config: undefined }); } if (config) { if (this.hasDetectorSupport(config)) { return new ProcessRunnerDetector(this.fileService, this.contextService, this.configurationResolverService, config).detect(true).then((value): WorkspaceFolderConfigurationResult => { let hasErrors = this.printStderr(value.stderr); let detectedConfig = value.config; if (!detectedConfig) { return { workspaceFolder, config, hasErrors }; } let result: TaskConfig.ExternalTaskRunnerConfiguration = Objects.clone(config); let configuredTasks: IStringDictionary = Object.create(null); if (!result.tasks) { if (detectedConfig.tasks) { result.tasks = detectedConfig.tasks; } } else { result.tasks.forEach(task => configuredTasks[task.taskName] = task); detectedConfig.tasks.forEach((task) => { if (!configuredTasks[task.taskName]) { result.tasks.push(task); } }); } return { workspaceFolder, config: result, hasErrors }; }); } else { return TPromise.as({ workspaceFolder, config, hasErrors: false }); } } else { return new ProcessRunnerDetector(this.fileService, this.contextService, this.configurationResolverService).detect(true).then((value) => { let hasErrors = this.printStderr(value.stderr); return { workspaceFolder, config: value.config, hasErrors }; }); } } private computeWorkspaceFolders(): [WorkspaceFolder[], ExecutionEngine, JsonSchemaVersion] { let workspaceFolders: WorkspaceFolder[] = []; let executionEngine = ExecutionEngine.Terminal; let schemaVersion = JsonSchemaVersion.V2_0_0; if (this.contextService.getWorkbenchState() === WorkbenchState.FOLDER) { let workspaceFolder: WorkspaceFolder = { uri: this.contextService.getWorkspace().folders[0].uri }; workspaceFolders.push(workspaceFolder); executionEngine = this.computeExecutionEngine(workspaceFolder); schemaVersion = this.computeJsonSchemaVersion(workspaceFolder); } else if (this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE) { for (let folder of this.contextService.getWorkspace().folders) { let workspaceFolder = { uri: folder.uri }; if (schemaVersion === this.computeJsonSchemaVersion(workspaceFolder)) { workspaceFolders.push(workspaceFolder); } else { this._outputChannel.append(nls.localize( 'taskService.ignoreingFolder', 'Ignoring task configurations for workspace folder {0}. Multi root folder support requires that all folders use task version 2.0.', folder.uri.fsPath)); } } } return [workspaceFolders, executionEngine, schemaVersion]; } private computeExecutionEngine(workspaceFolder: WorkspaceFolder): ExecutionEngine { let { config } = this.getConfiguration(workspaceFolder); if (!config) { return ExecutionEngine._default; } return TaskConfig.ExecutionEngine.from(config); } private computeJsonSchemaVersion(workspaceFolder: WorkspaceFolder): JsonSchemaVersion { let { config } = this.getConfiguration(workspaceFolder); if (!config) { return JsonSchemaVersion.V2_0_0; } return TaskConfig.JsonSchemaVersion.from(config); } private getConfiguration(workspaceFolder: WorkspaceFolder): { config: TaskConfig.ExternalTaskRunnerConfiguration; hasParseErrors: boolean } { let result = this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY ? this.configurationService.getConfiguration('tasks', { resource: workspaceFolder.uri }) : undefined; if (!result) { return { config: undefined, hasParseErrors: false }; } let parseErrors: string[] = (result as any).$parseErrors; if (parseErrors) { let isAffected = false; for (let i = 0; i < parseErrors.length; i++) { if (/tasks\.json$/.test(parseErrors[i])) { isAffected = true; break; } } if (isAffected) { this._outputChannel.append(nls.localize('TaskSystem.invalidTaskJson', 'Error: The content of the tasks.json file has syntax errors. Please correct them before executing a task.\n')); this.showOutput(); return { config: undefined, hasParseErrors: true }; } } return { config: result, hasParseErrors: false }; } private printStderr(stderr: string[]): boolean { let result = false; if (stderr && stderr.length > 0) { stderr.forEach((line) => { result = true; this._outputChannel.append(line + '\n'); }); this._outputChannel.show(true); } return result; } public inTerminal(): boolean { if (this._taskSystem) { return this._taskSystem instanceof TerminalTaskSystem; } return this._executionEngine === ExecutionEngine.Terminal; } private hasDetectorSupport(config: TaskConfig.ExternalTaskRunnerConfiguration): boolean { if (!config.command || this.contextService.getWorkbenchState() === WorkbenchState.EMPTY) { return false; } return ProcessRunnerDetector.supports(config.command); } public configureAction(): Action { return new ConfigureTaskRunnerAction(ConfigureTaskRunnerAction.ID, ConfigureTaskRunnerAction.TEXT, this, this.configurationService, this.editorService, this.fileService, this.contextService, this.outputService, this.messageService, this.quickOpenService, this.environmentService, this.configurationResolverService, this.extensionService, this.telemetryService); } private configureBuildTask(): Action { return new ConfigureBuildTaskAction(ConfigureBuildTaskAction.ID, ConfigureBuildTaskAction.TEXT, this, this.configurationService, this.editorService, this.fileService, this.contextService, this.outputService, this.messageService, this.quickOpenService, this.environmentService, this.configurationResolverService, this.extensionService, this.telemetryService); } public beforeShutdown(): boolean | TPromise { if (!this._taskSystem) { return false; } this.saveRecentlyUsedTasks(); if (!this._taskSystem.isActiveSync()) { return false; } // The terminal service kills all terminal on shutdown. So there // is nothing we can do to prevent this here. if (this._taskSystem instanceof TerminalTaskSystem) { return false; } if (this._taskSystem.canAutoTerminate() || this.messageService.confirm({ message: nls.localize('TaskSystem.runningTask', 'There is a task running. Do you want to terminate it?'), primaryButton: nls.localize({ key: 'TaskSystem.terminateTask', comment: ['&& denotes a mnemonic'] }, "&&Terminate Task"), type: 'question' })) { return this._taskSystem.terminateAll().then((responses) => { let success = true; let code: number = undefined; for (let response of responses) { success = success && response.success; // We only have a code in the old output runner which only has one task // So we can use the first code. if (code === void 0 && response.code !== void 0) { code = response.code; } } if (success) { this.emit(TaskServiceEvents.Terminated, {}); this._taskSystem = null; this.disposeTaskSystemListeners(); return false; // no veto } else if (code && code === TerminateResponseCode.ProcessNotFound) { return !this.messageService.confirm({ message: nls.localize('TaskSystem.noProcess', 'The launched task doesn\'t exist anymore. If the task spawned background processes exiting VS Code might result in orphaned processes. To avoid this start the last background process with a wait flag.'), primaryButton: nls.localize({ key: 'TaskSystem.exitAnyways', comment: ['&& denotes a mnemonic'] }, "&&Exit Anyways"), type: 'info' }); } return true; // veto }, (err) => { return true; // veto }); } else { return true; // veto } } private getConfigureAction(code: TaskErrors): Action { switch (code) { case TaskErrors.NoBuildTask: return this.configureBuildTask(); default: return this.configureAction(); } } private handleError(err: any): void { let showOutput = true; if (err instanceof TaskError) { let buildError = err; let needsConfig = buildError.code === TaskErrors.NotConfigured || buildError.code === TaskErrors.NoBuildTask || buildError.code === TaskErrors.NoTestTask; let needsTerminate = buildError.code === TaskErrors.RunningTask; if (needsConfig || needsTerminate) { let closeAction = new CloseMessageAction(); let action: Action = needsConfig ? this.getConfigureAction(buildError.code) : new Action( 'workbench.action.tasks.terminate', nls.localize('TerminateAction.label', "Terminate Task"), undefined, true, () => { this.runTerminateCommand(); return TPromise.as(undefined); }); closeAction.closeFunction = this.messageService.show(buildError.severity, { message: buildError.message, actions: [action, closeAction] }); } else { this.messageService.show(buildError.severity, buildError.message); } } else if (err instanceof Error) { let error = err; this.messageService.show(Severity.Error, error.message); } else if (Types.isString(err)) { this.messageService.show(Severity.Error, err); } else { this.messageService.show(Severity.Error, nls.localize('TaskSystem.unknownError', 'An error has occurred while running a task. See task log for details.')); } if (showOutput) { this._outputChannel.show(true); } } private canRunCommand(): boolean { if (this.contextService.getWorkbenchState() === WorkbenchState.EMPTY) { this.messageService.show(Severity.Info, nls.localize('TaskService.noWorkspace', 'Tasks are only available on a workspace folder.')); return false; } return true; } private showQuickPick(tasks: Task[], placeHolder: string, group: boolean = false, sort: boolean = false): TPromise { if (tasks === void 0 || tasks === null || tasks.length === 0) { return TPromise.as(undefined); } interface TaskQickPickEntry extends IPickOpenEntry { task: Task; } function TaskQickPickEntry(task: Task): TaskQickPickEntry { return { label: task._label, task }; } function fillEntries(entries: TaskQickPickEntry[], tasks: Task[], groupLabel: string, withBorder: boolean = false): void { let first = true; for (let task of tasks) { if (first) { first = false; let entry = TaskQickPickEntry(task); entry.separator = { label: groupLabel, border: withBorder }; entries.push(entry); } else { entries.push(TaskQickPickEntry(task)); } } } let entries: TaskQickPickEntry[]; if (group) { entries = []; if (tasks.length === 1) { entries.push(TaskQickPickEntry(tasks[0])); } else { let recentlyUsedTasks = this.getRecentlyUsedTasks(); let recent: Task[] = []; let configured: Task[] = []; let detected: Task[] = []; let taskMap: IStringDictionary = Object.create(null); tasks.forEach(task => taskMap[Task.getKey(task)] = task); recentlyUsedTasks.keys().forEach(key => { let task = taskMap[key]; if (task) { recent.push(task); } }); for (let task of tasks) { if (!recentlyUsedTasks.has(Task.getKey(task))) { if (task._source.kind === TaskSourceKind.Workspace) { configured.push(task); } else { detected.push(task); } } } let hasRecentlyUsed: boolean = recent.length > 0; fillEntries(entries, recent, nls.localize('recentlyUsed', 'recently used tasks')); configured = configured.sort((a, b) => a._label.localeCompare(b._label)); let hasConfigured = configured.length > 0; fillEntries(entries, configured, nls.localize('configured', 'configured tasks'), hasRecentlyUsed); detected = detected.sort((a, b) => a._label.localeCompare(b._label)); fillEntries(entries, detected, nls.localize('detected', 'detected tasks'), hasRecentlyUsed || hasConfigured); } } else { entries = tasks.map(task => { return { label: task._label, task }; }); if (sort) { entries = entries.sort((a, b) => a.task._label.localeCompare(b.task._label)); } } return this.quickOpenService.pick(entries, { placeHolder, autoFocus: { autoFocusFirstEntry: true } }).then(entry => entry ? entry.task : undefined); } private runTaskCommand(accessor: ServicesAccessor, arg: any): void { if (!this.canRunCommand()) { return; } if (Types.isString(arg)) { this.getTask(arg).then((task) => { if (task) { this.run(task); } else { this.quickOpenService.show('task '); } }, () => { this.quickOpenService.show('task '); }); } else { this.quickOpenService.show('task '); } } private runBuildCommand(): void { if (!this.canRunCommand()) { return; } if (this._schemaVersion === JsonSchemaVersion.V0_1_0) { this.build(); return; } let options: IProgressOptions = { location: ProgressLocation.Window, title: nls.localize('TaskService.fetchingBuildTasks', 'Fetching build tasks...') }; let promise = this.getTasksForGroup(TaskGroup.Build).then((tasks) => { if (tasks.length === 0) { this.messageService.show( Severity.Info, { message: nls.localize('TaskService.noBuildTaskTerminal', 'No Build Task found. Press \'Configure Build Task\' to define one.'), actions: [this.configureBuildTask(), new CloseMessageAction()] } ); return; } let primaries: Task[] = []; for (let task of tasks) { // We only have build tasks here if (task.isDefaultGroupEntry) { primaries.push(task); } } if (primaries.length === 1) { this.run(primaries[0]); return; } this.showQuickPick(tasks, nls.localize('TaskService.pickBuildTask', 'Select the build task to run'), true).then((task) => { if (task) { this.run(task, { attachProblemMatcher: true }); } }); }); this.progressService.withProgress(options, () => promise); } private runTestCommand(): void { if (!this.canRunCommand()) { return; } if (this._schemaVersion === JsonSchemaVersion.V0_1_0) { this.runTest(); return; } let options: IProgressOptions = { location: ProgressLocation.Window, title: nls.localize('TaskService.fetchingTestTasks', 'Fetching test tasks...') }; let promise = this.getTasksForGroup(TaskGroup.Test).then((tasks) => { if (tasks.length === 0) { this.messageService.show( Severity.Info, { message: nls.localize('TaskService.noTestTaskTerminal', 'No Test Task found. Press \'Configure Task Runner\' to define one.'), actions: [this.configureAction(), new CloseMessageAction()] } ); return; } let primaries: Task[] = []; for (let task of tasks) { // We only have test task here. if (task.isDefaultGroupEntry) { primaries.push(task); } } if (primaries.length === 1) { this.run(primaries[0]); return; } this.showQuickPick(tasks, nls.localize('TaskService.pickTestTask', 'Select the test task to run'), true).then((task) => { if (task) { this.run(task); } }); }); this.progressService.withProgress(options, () => promise); } private runTerminateCommand(): void { if (!this.canRunCommand()) { return; } if (this.inTerminal()) { this.getActiveTasks().then((activeTasks) => { if (activeTasks.length === 0) { this.messageService.show(Severity.Info, nls.localize('TaskService.noTaskRunning', 'No task is currently running.')); return; } this.showQuickPick(activeTasks, nls.localize('TaskService.tastToTerminate', 'Select task to terminate'), false, true).then(task => { if (task) { this.terminate(task); } }); }); } else { this.isActive().then((active) => { if (active) { this.terminateAll().then((responses) => { // the output runner has only one task let response = responses[0]; if (response.success) { return; } if (response.code && response.code === TerminateResponseCode.ProcessNotFound) { this.messageService.show(Severity.Error, nls.localize('TerminateAction.noProcess', 'The launched process doesn\'t exist anymore. If the task spawned background tasks exiting VS Code might result in orphaned processes.')); } else { this.messageService.show(Severity.Error, nls.localize('TerminateAction.failed', 'Failed to terminate running task')); } }); } }); } } private runRestartTaskCommand(accessor: ServicesAccessor, arg: any): void { if (!this.canRunCommand()) { return; } if (this.inTerminal()) { this.getActiveTasks().then((activeTasks) => { if (activeTasks.length === 0) { this.messageService.show(Severity.Info, nls.localize('TaskService.noTaskToRestart', 'No task to restart.')); return; } this.showQuickPick(activeTasks, nls.localize('TaskService.tastToRestart', 'Select the task to restart'), false, true).then(task => { if (task) { this.restart(task); } }); }); } else { this.getActiveTasks().then((activeTasks) => { if (activeTasks.length === 0) { return; } let task = activeTasks[0]; this.restart(task); }); } } private runConfigureDefaultBuildTask(): void { if (!this.canRunCommand()) { return; } if (this._schemaVersion === JsonSchemaVersion.V2_0_0) { this.tasks().then((tasks => { if (tasks.length === 0) { this.configureBuildTask().run(); return; } let defaultTask: Task; for (let task of tasks) { if (task.group === TaskGroup.Build && task.isDefaultGroupEntry) { defaultTask = task; break; } } if (defaultTask) { this.messageService.show(Severity.Info, nls.localize('TaskService.defaultBuildTaskExists', '{0} is already marked as the default build task.', defaultTask._label)); return; } this.showQuickPick(tasks, nls.localize('TaskService.pickDefaultBuildTask', 'Select the task to be used as the default build task'), true).then((task) => { if (!task) { return; } if (!CompositeTask.is(task)) { this.customize(task, { group: { kind: 'build', isDefault: true } }, true); } }); })); } else { this.configureBuildTask().run(); } } private runConfigureDefaultTestTask(): void { if (!this.canRunCommand()) { return; } if (this._schemaVersion === JsonSchemaVersion.V2_0_0) { this.tasks().then((tasks => { if (tasks.length === 0) { this.configureAction().run(); } let defaultTask: Task; for (let task of tasks) { if (task.group === TaskGroup.Test && task.isDefaultGroupEntry) { defaultTask = task; break; } } if (defaultTask) { this.messageService.show(Severity.Info, nls.localize('TaskService.defaultTestTaskExists', '{0} is already marked as the default test task.', defaultTask._label)); return; } this.showQuickPick(tasks, nls.localize('TaskService.pickDefaultTestTask', 'Select the task to be used as the default test task'), true).then((task) => { if (!task) { return; } if (!CompositeTask.is(task)) { this.customize(task, { group: { kind: 'test', isDefault: true } }, true); } }); })); } else { this.configureAction().run(); } } public runShowTasks(): void { if (!this.canRunCommand()) { return; } if (!this._taskSystem) { this.messageService.show(Severity.Info, nls.localize('TaskService.noTaskIsRunning', 'No task is running.')); return; } this.getActiveTasks().then((tasks) => { if (tasks.length === 0) { this.messageService.show(Severity.Info, nls.localize('TaskService.noTaskIsRunning', 'No task is running.')); } else if (tasks.length === 1) { if (this._taskSystem) { this._taskSystem.revealTask(tasks[0]); } } else { this.showQuickPick(tasks, nls.localize('TaskService.pickShowTask', 'Select the task to show its output'), false, true).then((task) => { if (!task || !this._taskSystem) { return; } this._taskSystem.revealTask(task); }); } }); } } let workbenchActionsRegistry = Registry.as(WorkbenchActionExtensions.WorkbenchActions); workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(ConfigureTaskRunnerAction, ConfigureTaskRunnerAction.ID, ConfigureTaskRunnerAction.TEXT), 'Tasks: Configure Task Runner', tasksCategory); MenuRegistry.addCommand({ id: 'workbench.action.tasks.showLog', title: { value: nls.localize('ShowLogAction.label', "Show Task Log"), original: 'Show Task Log' }, category: { value: tasksCategory, original: 'Tasks' } }); MenuRegistry.addCommand({ id: 'workbench.action.tasks.runTask', title: { value: nls.localize('RunTaskAction.label', "Run Task"), original: 'Run Task' }, category: { value: tasksCategory, original: 'Tasks' } }); MenuRegistry.addCommand({ id: 'workbench.action.tasks.restartTask', title: { value: nls.localize('RestartTaskAction.label', "Restart Running Task"), original: 'Restart Running Task' }, category: { value: tasksCategory, original: 'Tasks' } }); MenuRegistry.addCommand({ id: 'workbench.action.tasks.showTasks', title: { value: nls.localize('ShowTasksAction.label', "Show Running Tasks"), original: 'Show Running Tasks' }, category: { value: tasksCategory, original: 'Tasks' } }); MenuRegistry.addCommand({ id: 'workbench.action.tasks.terminate', title: { value: nls.localize('TerminateAction.label', "Terminate Task"), original: 'Terminate Task' }, category: { value: tasksCategory, original: 'Tasks' } }); MenuRegistry.addCommand({ id: 'workbench.action.tasks.build', title: { value: nls.localize('BuildAction.label', "Run Build Task"), original: 'Run Build Task' }, category: { value: tasksCategory, original: 'Tasks' } }); MenuRegistry.addCommand({ id: 'workbench.action.tasks.test', title: { value: nls.localize('TestAction.label', "Run Test Task"), original: 'Run Test Task' }, category: { value: tasksCategory, original: 'Tasks' } }); MenuRegistry.addCommand({ id: 'workbench.action.tasks.configureDefaultBuildTask', title: { value: nls.localize('ConfigureDefaultBuildTask.label', "Configure Default Build Task"), original: 'Configure Default Build Task' }, category: { value: tasksCategory, original: 'Tasks' } }); MenuRegistry.addCommand({ id: 'workbench.action.tasks.configureDefaultTestTask', title: { value: nls.localize('ConfigureDefaultTestTask.label', "Configure Default Test Task"), original: 'Configure Default Test Task' }, category: { value: tasksCategory, original: 'Tasks' } }); // MenuRegistry.addCommand( { id: 'workbench.action.tasks.rebuild', title: nls.localize('RebuildAction.label', 'Run Rebuild Task'), category: tasksCategory }); // MenuRegistry.addCommand( { id: 'workbench.action.tasks.clean', title: nls.localize('CleanAction.label', 'Run Clean Task'), category: tasksCategory }); // Task Service registerSingleton(ITaskService, TaskService); // Register Quick Open const quickOpenRegistry = (Registry.as(QuickOpenExtensions.Quickopen)); const tasksPickerContextKey = 'inTasksPicker'; quickOpenRegistry.registerQuickOpenHandler( new QuickOpenHandlerDescriptor( 'vs/workbench/parts/tasks/browser/taskQuickOpen', 'QuickOpenHandler', 'task ', tasksPickerContextKey, nls.localize('quickOpen.task', "Run Task") ) ); const actionBarRegistry = Registry.as(ActionBarExtensions.Actionbar); actionBarRegistry.registerActionBarContributor(Scope.VIEWER, QuickOpenActionContributor); // Status bar let statusbarRegistry = Registry.as(StatusbarExtensions.Statusbar); statusbarRegistry.registerStatusbarItem(new StatusbarItemDescriptor(BuildStatusBarItem, StatusbarAlignment.LEFT, 50 /* Medium Priority */)); statusbarRegistry.registerStatusbarItem(new StatusbarItemDescriptor(TaskStatusBarItem, StatusbarAlignment.LEFT, 50 /* Medium Priority */)); // Output channel let outputChannelRegistry = Registry.as(OutputExt.OutputChannels); outputChannelRegistry.registerChannel(TaskService.OutputChannelId, TaskService.OutputChannelLabel); // (Registry.as(WorkbenchExtensions.Workbench)).registerWorkbenchContribution(TaskServiceParticipant); // tasks.json validation let schemaId = 'vscode://schemas/tasks'; let schema: IJSONSchema = { id: schemaId, description: 'Task definition file', type: 'object', default: { version: '0.1.0', command: 'myCommand', isShellCommand: false, args: [], showOutput: 'always', tasks: [ { taskName: 'build', showOutput: 'silent', isBuildCommand: true, problemMatcher: ['$tsc', '$lessCompile'] } ] } }; import schemaVersion1 from './jsonSchema_v1'; import schemaVersion2 from './jsonSchema_v2'; schema.definitions = { ...schemaVersion1.definitions, ...schemaVersion2.definitions, }; schema.oneOf = [...schemaVersion2.oneOf, ...schemaVersion1.oneOf]; let jsonRegistry = Registry.as(jsonContributionRegistry.Extensions.JSONContribution); jsonRegistry.registerSchema(schemaId, schema);