diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index bd8dd3dd51e8906250e104bcd4d59a645d5554ef..29bde57d3183827c8577dc9278174e4d00c9dc44 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -28,6 +28,28 @@ declare module 'vscode' { Never = 3 } + /** + * Controls how the task channel is used between tasks + */ + export enum TaskInstanceKind { + + /** + * Shares a channel with other tasks. This is the default. + */ + Shared = 1, + + /** + * Uses the same task channel for every run if possible. The task channel is not + * shared with other tasks. + */ + Same = 2, + + /** + * Creates a new task channel whenever that task is executed + */ + New = 3 + } + /** * Controls terminal specific behavior. */ @@ -42,6 +64,16 @@ declare module 'vscode' { * Controls whether the command is echoed in the terminal or not. */ echo?: boolean; + + /** + * Controls whether the task pane takes focus when the task is executed + */ + focus?: boolean; + + /** + * Controls in which pane the task is executed. + */ + instance?: TaskInstanceKind; } export interface ProcessTaskOptions { diff --git a/src/vs/workbench/api/node/extHost.api.impl.ts b/src/vs/workbench/api/node/extHost.api.impl.ts index 92943aa15205df1a57e7b0dde0e653caa589c403..4b1429aa2948c38520a1427bbf6a93470f5db8f5 100644 --- a/src/vs/workbench/api/node/extHost.api.impl.ts +++ b/src/vs/workbench/api/node/extHost.api.impl.ts @@ -489,6 +489,7 @@ export function createApiFactory( ThemeColor: extHostTypes.ThemeColor, // functions TaskRevealKind: extHostTypes.TaskRevealKind, + TaskInstanceKind: extHostTypes.TaskInstanceKind, TaskGroup: extHostTypes.TaskGroup, ShellTask: extHostTypes.ShellTask, ProcessTask: extHostTypes.ProcessTask diff --git a/src/vs/workbench/api/node/extHostTask.ts b/src/vs/workbench/api/node/extHostTask.ts index 180bc9ffbf380b1119ed35020ca1a2a0ca00df4b..28721295f7df1c2f8cb8d365854e9247d5f35d06 100644 --- a/src/vs/workbench/api/node/extHostTask.ts +++ b/src/vs/workbench/api/node/extHostTask.ts @@ -207,12 +207,33 @@ namespace TaskRevealKind { } } +namespace TaskInstanceKind { + export function from(value: vscode.TaskInstanceKind): TaskSystem.InstanceKind { + if (value === void 0 || value === null) { + return TaskSystem.InstanceKind.Shared; + } + switch (value) { + case types.TaskInstanceKind.Same: + return TaskSystem.InstanceKind.Same; + case types.TaskInstanceKind.New: + return TaskSystem.InstanceKind.New; + default: + return TaskSystem.InstanceKind.Shared; + } + } +} + namespace TerminalBehaviour { export function from(value: vscode.TaskTerminalBehavior): TaskSystem.TerminalBehavior { if (value === void 0 || value === null) { - return { reveal: TaskSystem.RevealKind.Always, echo: false }; + return { reveal: TaskSystem.RevealKind.Always, echo: true, focus: false, instance: TaskSystem.InstanceKind.Shared }; } - return { reveal: TaskRevealKind.from(value.reveal), echo: !!value.echo }; + return { + reveal: TaskRevealKind.from(value.reveal), + echo: value.echo === void 0 ? true : !!value.echo, + focus: !!value.focus, + instance: TaskInstanceKind.from(value.instance) + }; } } diff --git a/src/vs/workbench/api/node/extHostTypes.ts b/src/vs/workbench/api/node/extHostTypes.ts index 34a86f3e0179f42e9ae080bb4d1cd38f31566d15..5b60f3cff85b38401612e770d0bbcef64794b573 100644 --- a/src/vs/workbench/api/node/extHostTypes.ts +++ b/src/vs/workbench/api/node/extHostTypes.ts @@ -1037,6 +1037,14 @@ export enum TaskRevealKind { Never = 3 } +export enum TaskInstanceKind { + Shared = 1, + + Same = 2, + + New = 3 +} + export class BaseTask { private _name: string; diff --git a/src/vs/workbench/parts/tasks/browser/buildQuickOpen.ts b/src/vs/workbench/parts/tasks/browser/buildQuickOpen.ts index 28b9db5251407870b9cea87e431668ca68c4645d..0ef0d65fb3e0f94036f1f9f2d4296ae4ce889cf6 100644 --- a/src/vs/workbench/parts/tasks/browser/buildQuickOpen.ts +++ b/src/vs/workbench/parts/tasks/browser/buildQuickOpen.ts @@ -16,15 +16,20 @@ import { ITaskService } from 'vs/workbench/parts/tasks/common/taskService'; import * as base from './quickOpen'; class TaskEntry extends base.TaskEntry { - constructor(taskService: ITaskService, task: Task, highlights: Model.IHighlight[] = []) { - super(taskService, task, highlights); + constructor(taskService: ITaskService, quickOpenService: IQuickOpenService, task: Task, highlights: Model.IHighlight[] = []) { + super(taskService, quickOpenService, task, highlights); } public run(mode: QuickOpen.Mode, context: Model.IContext): boolean { if (mode === QuickOpen.Mode.PREVIEW) { return false; } - this.taskService.run(this._task); + let task = this._task; + this.taskService.run(task); + if (task.command.terminalBehavior.focus) { + this.quickOpenService.close(); + return false; + } return true; } } @@ -45,8 +50,8 @@ export class QuickOpenHandler extends base.QuickOpenHandler { return this.taskService.getTasksForGroup(TaskGroup.Build); } - protected createEntry(taskService: ITaskService, task: Task, highlights: Model.IHighlight[]): base.TaskEntry { - return new TaskEntry(taskService, task, highlights); + protected createEntry(task: Task, highlights: Model.IHighlight[]): base.TaskEntry { + return new TaskEntry(this.taskService, this.quickOpenService, task, highlights); } public getEmptyLabel(searchString: string): string { diff --git a/src/vs/workbench/parts/tasks/browser/quickOpen.ts b/src/vs/workbench/parts/tasks/browser/quickOpen.ts index 9368b61de9a07206dd3d4984c73d0806ad678dcd..35b7a68008025542df650c9a63c847296f98f461 100644 --- a/src/vs/workbench/parts/tasks/browser/quickOpen.ts +++ b/src/vs/workbench/parts/tasks/browser/quickOpen.ts @@ -21,7 +21,7 @@ import { ActionBarContributor, ContributableActionProvider } from 'vs/workbench/ export class TaskEntry extends Model.QuickOpenEntry { - constructor(protected taskService: ITaskService, protected _task: Task, highlights: Model.IHighlight[] = []) { + constructor(protected taskService: ITaskService, protected quickOpenService: IQuickOpenService, protected _task: Task, highlights: Model.IHighlight[] = []) { super(highlights); } @@ -75,7 +75,8 @@ export abstract class QuickOpenHandler extends Quickopen.QuickOpenHandler { } let recentlyUsedTasks = this.taskService.getRecentlyUsedTasks(); let recent: Task[] = []; - let others: Task[] = []; + let configured: Task[] = []; + let detected: Task[] = []; let taskMap: IStringDictionary = Object.create(null); tasks.forEach(task => taskMap[task.identifier] = task); recentlyUsedTasks.keys().forEach(key => { @@ -86,37 +87,43 @@ export abstract class QuickOpenHandler extends Quickopen.QuickOpenHandler { }); for (let task of tasks) { if (!recentlyUsedTasks.has(task.identifier)) { - others.push(task); - } - } - others = others.sort((a, b) => a._source.label.localeCompare(b._source.label)); - let sortedTasks = recent.concat(others); - let recentlyUsed: boolean = recentlyUsedTasks.has(tasks[0].identifier); - let otherTasks: boolean = !recentlyUsedTasks.has(tasks[tasks.length - 1].identifier); - let hasRecentlyUsed: boolean = false; - for (let task of sortedTasks) { - let highlights = Filters.matchesContiguousSubString(input, task._label); - if (!highlights) { - continue; - } - if (recentlyUsed) { - recentlyUsed = false; - hasRecentlyUsed = true; - entries.push(new TaskGroupEntry(this.createEntry(this.taskService, task, highlights), nls.localize('recentlyUsed', 'recently used'), false)); - } else if (!recentlyUsedTasks.has(task.identifier) && otherTasks) { - otherTasks = false; - entries.push(new TaskGroupEntry(this.createEntry(this.taskService, task, highlights), nls.localize('other tasks', 'other tasks'), hasRecentlyUsed)); - } else { - entries.push(this.createEntry(this.taskService, task, highlights)); + if (task._source.kind === TaskSourceKind.Workspace) { + configured.push(task); + } else { + detected.push(task); + } } } + let hasRecentlyUsed: boolean = recent.length > 0; + this.fillEntries(entries, input, recent, nls.localize('recentlyUsed', 'recently used tasks')); + configured = configured.sort((a, b) => a._label.localeCompare(b._label)); + let hasConfigured = configured.length > 0; + this.fillEntries(entries, input, configured, nls.localize('configured', 'configured tasks'), hasRecentlyUsed); + detected = detected.sort((a, b) => a._label.localeCompare(b._label)); + this.fillEntries(entries, input, detected, nls.localize('detected', 'detected tasks'), hasRecentlyUsed || hasConfigured); return new Model.QuickOpenModel(entries, new ContributableActionProvider()); }); } + private fillEntries(entries: Model.QuickOpenEntry[], input: string, tasks: Task[], groupLabel: string, withBorder: boolean = false) { + let first = true; + for (let task of tasks) { + let highlights = Filters.matchesContiguousSubString(input, task._label); + if (!highlights) { + continue; + } + if (first) { + first = false; + entries.push(new TaskGroupEntry(this.createEntry(task, highlights), groupLabel, withBorder)); + } else { + entries.push(this.createEntry(task, highlights)); + } + } + } + protected abstract getTasks(): TPromise; - protected abstract createEntry(taskService: ITaskService, task: Task, highlights: Model.IHighlight[]): TaskEntry; + protected abstract createEntry(task: Task, highlights: Model.IHighlight[]): TaskEntry; public getAutoFocus(input: string): QuickOpen.IAutoFocus { return { diff --git a/src/vs/workbench/parts/tasks/browser/restartQuickOpen.ts b/src/vs/workbench/parts/tasks/browser/restartQuickOpen.ts index 95d4d2bd5dd90cb696a962696dad48333e908a96..eeda3971243543eb19ab013f4f7c27ff952c3bbe 100644 --- a/src/vs/workbench/parts/tasks/browser/restartQuickOpen.ts +++ b/src/vs/workbench/parts/tasks/browser/restartQuickOpen.ts @@ -16,8 +16,8 @@ import { ITaskService } from 'vs/workbench/parts/tasks/common/taskService'; import * as base from './quickOpen'; class TaskEntry extends base.TaskEntry { - constructor(taskService: ITaskService, task: Task, highlights: Model.IHighlight[] = []) { - super(taskService, task, highlights); + constructor(taskService: ITaskService, quickOpenService: IQuickOpenService, task: Task, highlights: Model.IHighlight[] = []) { + super(taskService, quickOpenService, task, highlights); } public run(mode: QuickOpen.Mode, context: Model.IContext): boolean { @@ -45,8 +45,8 @@ export class QuickOpenHandler extends base.QuickOpenHandler { return this.taskService.getActiveTasks(); } - protected createEntry(taskService: ITaskService, task: Task, highlights: Model.IHighlight[]): base.TaskEntry { - return new TaskEntry(taskService, task, highlights); + protected createEntry(task: Task, highlights: Model.IHighlight[]): base.TaskEntry { + return new TaskEntry(this.taskService, this.quickOpenService, task, highlights); } public getEmptyLabel(searchString: string): string { diff --git a/src/vs/workbench/parts/tasks/browser/taskQuickOpen.ts b/src/vs/workbench/parts/tasks/browser/taskQuickOpen.ts index b6939d7a77ce875cdced3a0e2e1f706a89331b56..544826907d49127661898ca4ef8f5b12761d7611 100644 --- a/src/vs/workbench/parts/tasks/browser/taskQuickOpen.ts +++ b/src/vs/workbench/parts/tasks/browser/taskQuickOpen.ts @@ -18,15 +18,20 @@ import { IExtensionService } from 'vs/platform/extensions/common/extensions'; import * as base from './quickOpen'; class TaskEntry extends base.TaskEntry { - constructor(taskService: ITaskService, task: Task, highlights: Model.IHighlight[] = []) { - super(taskService, task, highlights); + constructor(taskService: ITaskService, quickOpenService: IQuickOpenService, task: Task, highlights: Model.IHighlight[] = []) { + super(taskService, quickOpenService, task, highlights); } public run(mode: QuickOpen.Mode, context: Model.IContext): boolean { if (mode === QuickOpen.Mode.PREVIEW) { return false; } - this.taskService.run(this._task); + let task = this._task; + this.taskService.run(task); + if (task.command.terminalBehavior.focus) { + this.quickOpenService.close(); + return false; + } return true; } } @@ -53,8 +58,8 @@ export class QuickOpenHandler extends base.QuickOpenHandler { }); } - protected createEntry(taskService: ITaskService, task: Task, highlights: Model.IHighlight[]): base.TaskEntry { - return new TaskEntry(taskService, task, highlights); + protected createEntry(task: Task, highlights: Model.IHighlight[]): base.TaskEntry { + return new TaskEntry(this.taskService, this.quickOpenService, task, highlights); } public getEmptyLabel(searchString: string): string { diff --git a/src/vs/workbench/parts/tasks/browser/terminateQuickOpen.ts b/src/vs/workbench/parts/tasks/browser/terminateQuickOpen.ts index 80fc424f5d083793c36db4aee3086649ff3e61ae..5317d3789003e30e1a2b1a55df23e4b69b99035a 100644 --- a/src/vs/workbench/parts/tasks/browser/terminateQuickOpen.ts +++ b/src/vs/workbench/parts/tasks/browser/terminateQuickOpen.ts @@ -16,8 +16,8 @@ import { ITaskService } from 'vs/workbench/parts/tasks/common/taskService'; import * as base from './quickOpen'; class TaskEntry extends base.TaskEntry { - constructor(taskService: ITaskService, task: Task, highlights: Model.IHighlight[] = []) { - super(taskService, task, highlights); + constructor(taskService: ITaskService, quickOpenService: IQuickOpenService, task: Task, highlights: Model.IHighlight[] = []) { + super(taskService, quickOpenService, task, highlights); } public run(mode: QuickOpen.Mode, context: Model.IContext): boolean { @@ -45,8 +45,8 @@ export class QuickOpenHandler extends base.QuickOpenHandler { return this.taskService.getActiveTasks(); } - protected createEntry(taskService: ITaskService, task: Task, highlights: Model.IHighlight[]): base.TaskEntry { - return new TaskEntry(taskService, task, highlights); + protected createEntry(task: Task, highlights: Model.IHighlight[]): base.TaskEntry { + return new TaskEntry(this.taskService, this.quickOpenService, task, highlights); } public getEmptyLabel(searchString: string): string { diff --git a/src/vs/workbench/parts/tasks/browser/testQuickOpen.ts b/src/vs/workbench/parts/tasks/browser/testQuickOpen.ts index 96275341b2c7f4d9a6295936bce6e2f08d3de76c..ff9b506b36ec5e869c9df6a8ffd80c7d61c2b029 100644 --- a/src/vs/workbench/parts/tasks/browser/testQuickOpen.ts +++ b/src/vs/workbench/parts/tasks/browser/testQuickOpen.ts @@ -16,15 +16,20 @@ import { ITaskService } from 'vs/workbench/parts/tasks/common/taskService'; import * as base from './quickOpen'; class TaskEntry extends base.TaskEntry { - constructor(taskService: ITaskService, task: Task, highlights: Model.IHighlight[] = []) { - super(taskService, task, highlights); + constructor(taskService: ITaskService, quickOpenService: IQuickOpenService, task: Task, highlights: Model.IHighlight[] = []) { + super(taskService, quickOpenService, task, highlights); } public run(mode: QuickOpen.Mode, context: Model.IContext): boolean { if (mode === QuickOpen.Mode.PREVIEW) { return false; } - this.taskService.run(this._task); + let task = this._task; + this.taskService.run(task); + if (task.command.terminalBehavior.focus) { + this.quickOpenService.close(); + return false; + } return true; } } @@ -45,8 +50,8 @@ export class QuickOpenHandler extends base.QuickOpenHandler { return this.taskService.getTasksForGroup(TaskGroup.Test); } - protected createEntry(taskService: ITaskService, task: Task, highlights: Model.IHighlight[]): base.TaskEntry { - return new TaskEntry(taskService, task, highlights); + protected createEntry(task: Task, highlights: Model.IHighlight[]): base.TaskEntry { + return new TaskEntry(this.taskService, this.quickOpenService, task, highlights); } public getEmptyLabel(searchString: string): string { diff --git a/src/vs/workbench/parts/tasks/common/taskConfiguration.ts b/src/vs/workbench/parts/tasks/common/taskConfiguration.ts index 71be98158a3331ba26d231e77e639b8a505efc4a..9b07f84f16fa951b9ef46badbf743abef6055828 100644 --- a/src/vs/workbench/parts/tasks/common/taskConfiguration.ts +++ b/src/vs/workbench/parts/tasks/common/taskConfiguration.ts @@ -355,6 +355,134 @@ function fillProperty(target: T, source: T, key: K) { } +interface ParserType { + isEmpty(value: T): boolean; + assignProperties(target: T, source: T): T; + fillProperties(target: T, source: T): T; + fillDefaults(value: T, context: ParseContext): T; + freeze(value: T): Readonly; +} + +interface MetaData { + property: keyof T; + type?: ParserType; +} + + +function _isEmpty(this: void, value: T, properties: MetaData[]): boolean { + if (value === void 0 || value === null) { + return true; + } + for (let meta of properties) { + let property = value[meta.property]; + if (property !== void 0 && property !== null) { + if (meta.type !== void 0 && !meta.type.isEmpty(property)) { + return false; + } else if (!Array.isArray(property) || property.length > 0) { + return false; + } + } + } + return true; +} + +function _assignProperties(this: void, target: T, source: T, properties: MetaData[]): T { + if (_isEmpty(source, properties)) { + return target; + } + if (_isEmpty(target, properties)) { + return source; + } + for (let meta of properties) { + let property = meta.property; + let value: any; + if (meta.type !== void 0) { + value = meta.type.assignProperties(target[property], source[property]); + } else { + value = source[property]; + } + if (value !== void 0 && value !== null) { + target[property] = value; + } + } + return target; +} + +function _fillProperties(this: void, target: T, source: T, properties: MetaData[]): T { + if (_isEmpty(source, properties)) { + return target; + } + if (_isEmpty(target, properties)) { + return source; + } + for (let meta of properties) { + let property = meta.property; + if (target[property] !== void 0) { + continue; + } + let value: any; + if (meta.type) { + value = meta.type.fillProperties(target[property], source[property]); + } else { + value = source[property]; + } + + if (value !== void 0 && value !== null) { + target[property] = value; + } + } + return target; +} + +function _fillDefaults(this: void, target: T, defaults: T, properties: MetaData[], context: ParseContext): T { + if (target && Object.isFrozen(target)) { + return target; + } + if (target === void 0 || target === null) { + if (defaults !== void 0 && defaults !== null) { + return Objects.deepClone(defaults); + } else { + return undefined; + } + } + for (let meta of properties) { + let property = meta.property; + if (target[property] !== void 0) { + continue; + } + let value: any; + if (meta.type) { + value = meta.type.fillDefaults(target[property], context); + } else { + value = defaults[property]; + } + + if (value !== void 0 && value !== null) { + target[property] = value; + } + } + return target; +} + +function _freeze(this: void, target: T, properties: MetaData[]): Readonly { + if (target === void 0 || target === null) { + return undefined; + } + if (Object.isFrozen(target)) { + return target; + } + for (let meta of properties) { + if (meta.type) { + let value = target[meta.property]; + if (value) { + meta.type.freeze(value); + } + } + } + Object.freeze(target); + return target; +} + interface ParseContext { problemReporter: IProblemReporter; namedProblemMatchers: IStringDictionary; @@ -363,7 +491,11 @@ interface ParseContext { schemaVersion: Tasks.JsonSchemaVersion; } + namespace ShellConfiguration { + + const properties: MetaData[] = [{ property: 'executable' }, { property: 'args' }]; + export function is(value: any): value is ShellConfiguration { let candidate: ShellConfiguration = value; return candidate && Types.isString(candidate.executable) && (candidate.args === void 0 || Types.isStringArray(candidate.args)); @@ -380,46 +512,35 @@ namespace ShellConfiguration { return result; } - export function isEmpty(value: Tasks.ShellConfiguration): boolean { - return !value || value.executable === void 0 && (value.args === void 0 || value.args.length === 0); + export function isEmpty(this: void, value: Tasks.ShellConfiguration): boolean { + return _isEmpty(value, properties); } - export function assignProperties(target: Tasks.ShellConfiguration, source: Tasks.ShellConfiguration): Tasks.ShellConfiguration { - if (isEmpty(source)) { - return target; - } - if (isEmpty(target)) { - return source; - } - assignProperty(target, source, 'executable'); - assignProperty(target, source, 'args'); - return target; + export function assignProperties(this: void, target: Tasks.ShellConfiguration, source: Tasks.ShellConfiguration): Tasks.ShellConfiguration { + return _assignProperties(target, source, properties); } - export function fillProperties(target: Tasks.ShellConfiguration, source: Tasks.ShellConfiguration): Tasks.ShellConfiguration { - if (isEmpty(source)) { - return target; - } - if (isEmpty(target)) { - return source; - } - fillProperty(target, source, 'executable'); - fillProperty(target, source, 'args'); - return target; + export function fillProperties(this: void, target: Tasks.ShellConfiguration, source: Tasks.ShellConfiguration): Tasks.ShellConfiguration { + return _fillProperties(target, source, properties); } - export function fillDefaults(value: Tasks.ShellConfiguration): void { + export function fillDefaults(this: void, value: Tasks.ShellConfiguration, context: ParseContext): Tasks.ShellConfiguration { + return value; } - export function freeze(value: Tasks.ShellConfiguration): void { + export function freeze(this: void, value: Tasks.ShellConfiguration): Readonly { if (!value) { - return; + return undefined; } - Object.freeze(value); + return Object.freeze(value); } } namespace CommandOptions { + + const properties: MetaData[] = [{ property: 'cwd' }, { property: 'env' }, { property: 'shell', type: ShellConfiguration }]; + const defaults: CommandOptions = { cwd: '${workspaceRoot}' }; + export function from(this: void, options: CommandOptions, context: ParseContext): Tasks.CommandOptions { let result: Tasks.CommandOptions = {}; if (options.cwd !== void 0) { @@ -437,7 +558,7 @@ namespace CommandOptions { } export function isEmpty(value: Tasks.CommandOptions): boolean { - return !value || value.cwd === void 0 && value.env === void 0 && value.shell === void 0; + return _isEmpty(value, properties); } export function assignProperties(target: Tasks.CommandOptions, source: Tasks.CommandOptions): Tasks.CommandOptions { @@ -461,45 +582,25 @@ namespace CommandOptions { } export function fillProperties(target: Tasks.CommandOptions, source: Tasks.CommandOptions): Tasks.CommandOptions { - if (isEmpty(source)) { - return target; - } - if (isEmpty(target)) { - return source; - } - fillProperty(target, source, 'cwd'); - fillProperty(target, source, 'env'); - target.shell = ShellConfiguration.fillProperties(target.shell, source.shell); - return target; + return _fillProperties(target, source, properties); } - export function fillDefaults(value: Tasks.CommandOptions): Tasks.CommandOptions { - if (value && Object.isFrozen(value)) { - return value; - } - if (value === void 0) { - value = {}; - } - if (value.cwd === void 0) { - value.cwd = '${workspaceRoot}'; - } - ShellConfiguration.fillDefaults(value.shell); - return value; + export function fillDefaults(value: Tasks.CommandOptions, context: ParseContext): Tasks.CommandOptions { + return _fillDefaults(value, defaults, properties, context); } - export function freeze(value: Tasks.CommandOptions): void { - Object.freeze(value); - if (value.env) { - Object.freeze(value.env); - } - ShellConfiguration.freeze(value.shell); + export function freeze(value: Tasks.CommandOptions): Readonly { + return _freeze(value, properties); } } namespace CommandConfiguration { + interface TerminalBehavior { echo?: boolean; reveal?: string; + focus?: boolean; + instance?: string; } interface BaseCommandConfiguationShape { @@ -522,9 +623,13 @@ namespace CommandConfiguration { } export namespace TerminalBehavior { + const properties: MetaData[] = [{ property: 'echo' }, { property: 'reveal' }, { property: 'focus' }, { property: 'instance' }]; + export function from(this: void, config: BaseCommandConfiguationShape, context: ParseContext): Tasks.TerminalBehavior { - let echo: boolean = undefined; - let reveal: Tasks.RevealKind = undefined; + let echo: boolean; + let reveal: Tasks.RevealKind; + let focus: boolean; + let instance: Tasks.InstanceKind; if (Types.isBoolean(config.echoCommand)) { echo = config.echoCommand; } @@ -538,65 +643,47 @@ namespace CommandConfiguration { if (Types.isString(config.terminal.reveal)) { reveal = Tasks.RevealKind.fromString(config.terminal.reveal); } + if (Types.isBoolean(config.terminal.focus)) { + focus = config.terminal.focus; + } + if (Types.isString(config.terminal.instance)) { + instance = Tasks.InstanceKind.fromString(config.terminal.instance); + } } - if (echo === void 0 && reveal === void 0) { + if (echo === void 0 && reveal === void 0 && focus === void 0 && instance === void 0) { return undefined; } - return { echo, reveal }; + return { echo, reveal, focus, instance }; } export function assignProperties(target: Tasks.TerminalBehavior, source: Tasks.TerminalBehavior): Tasks.TerminalBehavior { - if (isEmpty(source)) { - return target; - } - if (isEmpty(target)) { - return source; - } - assignProperty(target, source, 'echo'); - assignProperty(target, source, 'reveal'); - return target; + return _assignProperties(target, source, properties); } export function fillProperties(target: Tasks.TerminalBehavior, source: Tasks.TerminalBehavior): Tasks.TerminalBehavior { - if (isEmpty(source)) { - return target; - } - if (isEmpty(target)) { - return source; - } - fillProperty(target, source, 'echo'); - fillProperty(target, source, 'reveal'); - return target; + return _fillProperties(target, source, properties); } - export function fillDefault(value: Tasks.TerminalBehavior, context: ParseContext): Tasks.TerminalBehavior { - if (value && Object.isFrozen(value)) { - return value; - } - if (value === void 0) { - return { echo: false, reveal: Tasks.RevealKind.Always }; - } - if (value.echo === void 0) { - value.echo = context.engine === Tasks.ExecutionEngine.Terminal ? true : false; - } - if (value.reveal === void 0) { - value.reveal = Tasks.RevealKind.Always; - } - return value; + export function fillDefaults(value: Tasks.TerminalBehavior, context: ParseContext): Tasks.TerminalBehavior { + let defaultEcho = context.engine === Tasks.ExecutionEngine.Terminal ? true : false; + return _fillDefaults(value, { echo: defaultEcho, reveal: Tasks.RevealKind.Always, focus: false, instance: Tasks.InstanceKind.Shared }, properties, context); } - export function freeze(value: Tasks.TerminalBehavior): void { - if (value === void 0) { - return; - } - Object.freeze(value); + export function freeze(value: Tasks.TerminalBehavior): Readonly { + return _freeze(value, properties); } export function isEmpty(this: void, value: Tasks.TerminalBehavior): boolean { - return !value || value.echo === void 0 && value.reveal === void 0; + return _isEmpty(value, properties); } } + const properties: MetaData[] = [ + { property: 'type' }, { property: 'name' }, { property: 'options', type: CommandOptions }, + { property: 'args' }, { property: 'taskSelector' }, { property: 'suppressTaskName' }, + { property: 'terminalBehavior', type: TerminalBehavior } + ]; + export function from(this: void, config: CommandConfiguationShape, context: ParseContext): Tasks.CommandConfiguration { let result: Tasks.CommandConfiguration = fromBase(config, context); @@ -662,13 +749,7 @@ namespace CommandConfiguration { } export function isEmpty(value: Tasks.CommandConfiguration): boolean { - return !value || value.name === void 0 - && value.type === void 0 - && value.args === void 0 - && value.taskSelector === void 0 - && value.suppressTaskName === void 0 - && CommandOptions.isEmpty(value.options) - && TerminalBehavior.isEmpty(value.terminalBehavior); + return _isEmpty(value, properties); } export function onlyTerminalBehaviour(value: Tasks.CommandConfiguration): boolean { @@ -739,9 +820,9 @@ namespace CommandConfiguration { if (value.name !== void 0 && value.type === void 0) { value.type = Tasks.CommandType.Process; } - value.terminalBehavior = TerminalBehavior.fillDefault(value.terminalBehavior, context); + value.terminalBehavior = TerminalBehavior.fillDefaults(value.terminalBehavior, context); if (!isEmpty(value)) { - value.options = CommandOptions.fillDefaults(value.options); + value.options = CommandOptions.fillDefaults(value.options, context); } if (value.args === void 0) { value.args = EMPTY_ARRAY; @@ -751,17 +832,8 @@ namespace CommandConfiguration { } } - export function freeze(value: Tasks.CommandConfiguration): void { - Object.freeze(value); - if (value.args) { - Object.freeze(value.args); - } - if (value.options) { - CommandOptions.freeze(value.options); - } - if (value.terminalBehavior) { - TerminalBehavior.freeze(value.terminalBehavior); - } + export function freeze(value: Tasks.CommandConfiguration): Readonly { + return _freeze(value, properties); } } diff --git a/src/vs/workbench/parts/tasks/common/taskSystem.ts b/src/vs/workbench/parts/tasks/common/taskSystem.ts index b61a8fbc86344e44d3c1fdd8c01624111f7a83e4..30e51e8a2e33d411df0d85492cdbf36451a4c3ff 100644 --- a/src/vs/workbench/parts/tasks/common/taskSystem.ts +++ b/src/vs/workbench/parts/tasks/common/taskSystem.ts @@ -100,7 +100,6 @@ export interface ITaskResolver { export interface ITaskSystem extends IEventEmitter { run(task: Task, resolver: ITaskResolver): ITaskExecuteResult; - show(task: Task, forceFocus?: boolean): void; isActive(): TPromise; isActiveSync(): boolean; getActiveTasks(): Task[]; diff --git a/src/vs/workbench/parts/tasks/common/tasks.ts b/src/vs/workbench/parts/tasks/common/tasks.ts index b75a5750e58866aee91a822159737e94ecec435f..ca4b93f5422794444c0d9735149b52bd8d80646e 100644 --- a/src/vs/workbench/parts/tasks/common/tasks.ts +++ b/src/vs/workbench/parts/tasks/common/tasks.ts @@ -80,6 +80,40 @@ export namespace RevealKind { } } +export enum InstanceKind { + + /** + * Shares a terminal with other tasks. This is the default. + */ + Shared = 1, + + /** + * Uses the same terminal for every run if possible. The terminal is not + * shared with other tasks. + */ + Same = 2, + + /** + * Creates a new terminal whenever that task is executed + */ + New = 3 +} + +export namespace InstanceKind { + export function fromString(value: string): InstanceKind { + switch (value.toLowerCase()) { + case 'shared': + return InstanceKind.Shared; + case 'same': + return InstanceKind.Same; + case 'new': + return InstanceKind.New; + default: + return InstanceKind.Shared; + } + } +} + export interface TerminalBehavior { /** * Controls whether the terminal executing a task is brought to front or not. @@ -91,6 +125,16 @@ export interface TerminalBehavior { * Controls whether the executed command is printed to the output window or terminal as well. */ echo: boolean; + + /** + * Controls whether the terminal is focus when this task is executed + */ + focus: boolean; + + /** + * Controls whether the task runs in a new terminal + */ + instance: InstanceKind; } export enum CommandType { diff --git a/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts b/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts index a5a53dae147c022e90073b71d026dfb86d5707c0..305ccdd5a4cfb70a9de58f65565a446260121d31 100644 --- a/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts +++ b/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts @@ -444,9 +444,6 @@ class NullTaskSystem extends EventEmitter implements ITaskSystem { promise: TPromise.as({}) }; } - public show(task: Task, forceFocus: boolean = false): void { - return; - } public isActive(): TPromise { return TPromise.as(false); } @@ -930,20 +927,19 @@ class TaskService extends EventEmitter implements ITaskService { 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.identifier, task.identifier, 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 is already active and in background mode. To terminate the task use `F1 > terminate task`')); } else { - this.getTaskSystem().show(task, true); - // this.messageService.show(Severity.Info, nls.localize('TaskSystem.activeSame.noBackground', 'The task is already active. To terminate the task use `F1 > terminate task`')); + this.messageService.show(Severity.Info, nls.localize('TaskSystem.activeSame.noBackground', 'The task is already active. To terminate the task use `F1 > terminate task`')); } } 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); } } - this.getRecentlyUsedTasks().set(task.identifier, task.identifier, Touch.First); return executeResult.promise; }); }); diff --git a/src/vs/workbench/parts/tasks/electron-browser/terminalTaskSystem.ts b/src/vs/workbench/parts/tasks/electron-browser/terminalTaskSystem.ts index 3bdec71f2ec29c0ac3be9080cef7f273797b5793..8f43026ea2186a0491290e76e075527d6a235d15 100644 --- a/src/vs/workbench/parts/tasks/electron-browser/terminalTaskSystem.ts +++ b/src/vs/workbench/parts/tasks/electron-browser/terminalTaskSystem.ts @@ -135,8 +135,10 @@ export class TerminalTaskSystem extends EventEmitter implements ITaskSystem { let terminalData = this.activeTasks[task._id]; if (terminalData && terminalData.promise) { let reveal = task.command.terminalBehavior.reveal; - if (reveal === RevealKind.Always) { - terminalData.terminal.setVisible(true); + let focus = task.command.terminalBehavior.focus; + if (reveal === RevealKind.Always || focus) { + this.terminalService.setActiveInstance(terminalData.terminal); + this.terminalService.showPanel(focus); } return { kind: TaskExecuteKind.Active, active: { same: true, background: task.isBackground }, promise: terminalData.promise }; } @@ -156,15 +158,6 @@ export class TerminalTaskSystem extends EventEmitter implements ITaskSystem { } } - public show(task: Task, forceFocus: boolean = false): void { - let terminalData = this.activeTasks[task._id]; - if (terminalData === void 0) { - return; - } - this.terminalService.setActiveInstance(terminalData.terminal); - this.terminalService.showPanel(forceFocus); - } - public isActive(): TPromise { return TPromise.as(this.isActiveSync()); } @@ -342,7 +335,7 @@ export class TerminalTaskSystem extends EventEmitter implements ITaskSystem { } this.terminalService.setActiveInstance(terminal); if (task.command.terminalBehavior.reveal === RevealKind.Always || (task.command.terminalBehavior.reveal === RevealKind.Silent && task.problemMatchers.length === 0)) { - this.terminalService.showPanel(false); + this.terminalService.showPanel(task.command.terminalBehavior.focus); } this.activeTasks[task._id] = { terminal, task, promise }; return promise.then((summary) => { @@ -432,7 +425,7 @@ export class TerminalTaskSystem extends EventEmitter implements ITaskSystem { shellArgs.push(commandLine); shellLaunchConfig.args = Platform.isWindows ? shellArgs.join(' ') : shellArgs; if (task.command.terminalBehavior.echo) { - shellLaunchConfig.initialText = `\x1b[1m>Executing task: ${commandLine}<\x1b[0m\n`; + shellLaunchConfig.initialText = `\x1b[1m> Executing task: ${commandLine} <\x1b[0m\n`; } } else { let cwd = options && options.cwd ? options.cwd : process.cwd(); @@ -455,7 +448,7 @@ export class TerminalTaskSystem extends EventEmitter implements ITaskSystem { } return args.join(' '); }; - shellLaunchConfig.initialText = `\x1b[1m>Executing task: ${shellLaunchConfig.executable} ${getArgsToEcho(shellLaunchConfig.args)}<\x1b[0m\n`; + shellLaunchConfig.initialText = `\x1b[1m> Executing task: ${shellLaunchConfig.executable} ${getArgsToEcho(shellLaunchConfig.args)} <\x1b[0m\n`; } } if (options.cwd) { diff --git a/src/vs/workbench/parts/tasks/node/processTaskSystem.ts b/src/vs/workbench/parts/tasks/node/processTaskSystem.ts index c6e4d70add928ed8e3b483eb1ff9b540c35d11aa..0db8946a2d29db3ff1400e63a6104adf559e3497 100644 --- a/src/vs/workbench/parts/tasks/node/processTaskSystem.ts +++ b/src/vs/workbench/parts/tasks/node/processTaskSystem.ts @@ -89,10 +89,6 @@ export class ProcessTaskSystem extends EventEmitter implements ITaskSystem { return this.executeTask(task); } - public show(task: Task, forceFocus: boolean = false): void { - this.outputChannel.show(!focus); - } - public hasErrors(value: boolean): void { this.errorsShown = !value; } diff --git a/src/vs/workbench/parts/tasks/test/node/configuration.test.ts b/src/vs/workbench/parts/tasks/test/node/configuration.test.ts index a82a75b6e7c187ed3639ed537da2a78e485324de..2d60a06a146054d66357fda50ea328a88cb1bdca 100644 --- a/src/vs/workbench/parts/tasks/test/node/configuration.test.ts +++ b/src/vs/workbench/parts/tasks/test/node/configuration.test.ts @@ -82,7 +82,7 @@ class TerminalBehaviorBuilder { public result: Tasks.TerminalBehavior; constructor(public parent: CommandConfigurationBuilder) { - this.result = { echo: false, reveal: Tasks.RevealKind.Always }; + this.result = { echo: false, reveal: Tasks.RevealKind.Always, focus: false, instance: Tasks.InstanceKind.Shared }; } public echo(value: boolean): TerminalBehaviorBuilder { @@ -95,6 +95,16 @@ class TerminalBehaviorBuilder { return this; } + public focus(value: boolean): TerminalBehaviorBuilder { + this.result.focus = value; + return this; + } + + public instance(value: Tasks.InstanceKind): TerminalBehaviorBuilder { + this.result.instance = value; + return this; + } + public done(): void { } } @@ -1446,7 +1456,8 @@ suite('Tasks version 2.0.0', () => { builder.task('dir', 'dir'). group(Tasks.TaskGroup.Build). command().suppressTaskName(true). - type(Tasks.CommandType.Shell); + type(Tasks.CommandType.Shell). + terminal().echo(true); testConfiguration(external, builder); });