diff --git a/packages/task/src/browser/task-schema-updater.ts b/packages/task/src/browser/task-schema-updater.ts index 45d7e83173619..db496d154c5ab 100644 --- a/packages/task/src/browser/task-schema-updater.ts +++ b/packages/task/src/browser/task-schema-updater.ts @@ -539,6 +539,7 @@ const problemMatcher = { const presentation: IJSONSchema = { type: 'object', default: { + echo: true, reveal: 'always', focus: false, panel: 'shared', @@ -548,6 +549,11 @@ const presentation: IJSONSchema = { description: 'Configures the panel that is used to present the task\'s output and reads its input.', additionalProperties: true, properties: { + echo: { + type: 'boolean', + default: true, + description: 'Controls whether the executed command is echoed to the panel. Default is true.' + }, focus: { type: 'boolean', default: false, diff --git a/packages/task/src/browser/task-service.ts b/packages/task/src/browser/task-service.ts index cac958ed376d4..84d7b948666f5 100644 --- a/packages/task/src/browser/task-service.ts +++ b/packages/task/src/browser/task-service.ts @@ -1000,7 +1000,6 @@ export class TaskService implements TaskConfigurationClient { } } } - // Create / find a terminal widget to display an execution output of a task that was launched as a command inside a shell. const widget = await this.taskTerminalWidgetManager.open({ created: new Date().toString(), @@ -1013,7 +1012,7 @@ export class TaskService implements TaskConfigurationClient { taskId, widgetOptions: { area: 'bottom' }, mode: widgetOpenMode, - taskConfig: taskInfo ? taskInfo.config : undefined + taskInfo }); widget.start(terminalId); } diff --git a/packages/task/src/browser/task-terminal-widget-manager.ts b/packages/task/src/browser/task-terminal-widget-manager.ts index 971282ca71666..4c1ded00db3f0 100644 --- a/packages/task/src/browser/task-terminal-widget-manager.ts +++ b/packages/task/src/browser/task-terminal-widget-manager.ts @@ -19,7 +19,8 @@ import { ApplicationShell, WidgetOpenerOptions } from '@theia/core/lib/browser'; import { TerminalWidget } from '@theia/terminal/lib/browser/base/terminal-widget'; import { TerminalWidgetFactoryOptions } from '@theia/terminal/lib/browser/terminal-widget-impl'; import { TerminalService } from '@theia/terminal/lib/browser/base/terminal-service'; -import { PanelKind, TaskConfiguration, TaskWatcher, TaskExitedEvent, TaskServer, TaskOutputPresentation } from '../common'; +import { PanelKind, TaskConfiguration, TaskWatcher, TaskExitedEvent, TaskServer, TaskOutputPresentation, TaskInfo } from '../common'; +import { ProcessTaskInfo } from '../common/process/task-protocol'; import { TaskDefinitionRegistry } from './task-definition-registry'; import { WorkspaceService } from '@theia/workspace/lib/browser/workspace-service'; @@ -38,20 +39,27 @@ export namespace TaskTerminalWidget { export interface TaskTerminalWidgetOpenerOptions extends WidgetOpenerOptions { taskId: number; - taskConfig?: TaskConfiguration; + taskInfo?: TaskInfo; } export namespace TaskTerminalWidgetOpenerOptions { export function isDedicatedTerminal(options: TaskTerminalWidgetOpenerOptions): boolean { - return !!options.taskConfig && !!options.taskConfig.presentation && options.taskConfig.presentation.panel === PanelKind.Dedicated; + const taskConfig = options.taskInfo ? options.taskInfo.config : undefined; + return !!taskConfig && !!taskConfig.presentation && taskConfig.presentation.panel === PanelKind.Dedicated; } export function isNewTerminal(options: TaskTerminalWidgetOpenerOptions): boolean { - return !!options.taskConfig && !!options.taskConfig.presentation && options.taskConfig.presentation.panel === PanelKind.New; + const taskConfig = options.taskInfo ? options.taskInfo.config : undefined; + return !!taskConfig && !!taskConfig.presentation && taskConfig.presentation.panel === PanelKind.New; } export function isSharedTerminal(options: TaskTerminalWidgetOpenerOptions): boolean { - return !!options.taskConfig && - (options.taskConfig.presentation === undefined || options.taskConfig.presentation.panel === undefined || options.taskConfig.presentation.panel === PanelKind.Shared); + const taskConfig = options.taskInfo ? options.taskInfo.config : undefined; + return !!taskConfig && (taskConfig.presentation === undefined || taskConfig.presentation.panel === undefined || taskConfig.presentation.panel === PanelKind.Shared); + } + + export function echoExecutedCommand(options: TaskTerminalWidgetOpenerOptions): boolean { + const taskConfig = options.taskInfo ? options.taskInfo.config : undefined; + return !!taskConfig && (taskConfig.presentation === undefined || taskConfig.presentation.echo === undefined || taskConfig.presentation.echo); } } @@ -120,8 +128,8 @@ export class TaskTerminalWidgetManager { async open(factoryOptions: TerminalWidgetFactoryOptions, openerOptions: TaskTerminalWidgetOpenerOptions): Promise { const dedicated = TaskTerminalWidgetOpenerOptions.isDedicatedTerminal(openerOptions); - if (dedicated && !openerOptions.taskConfig) { - throw new Error('"taskConfig" must be included as part of the "option" if "isDedicated" is true'); + if (dedicated && (!openerOptions.taskInfo || !openerOptions.taskInfo.config)) { + throw new Error('"taskConfig" must be included as part of the "option.taskInfo" if "isDedicated" is true'); } const { isNew, widget } = await this.getWidgetToRunTask(factoryOptions, openerOptions); @@ -132,12 +140,18 @@ export class TaskTerminalWidgetManager { if (factoryOptions.title) { widget.setTitle(factoryOptions.title); } - if (openerOptions.taskConfig && TaskOutputPresentation.shouldClearTerminalBeforeRun(openerOptions.taskConfig)) { + const taskConfig = openerOptions.taskInfo ? openerOptions.taskInfo.config : undefined; + if (taskConfig && TaskOutputPresentation.shouldClearTerminalBeforeRun(taskConfig)) { widget.clearOutput(); } } this.terminalService.open(widget, openerOptions); - + const taskInfo = openerOptions.taskInfo; + if (TaskTerminalWidgetOpenerOptions.echoExecutedCommand(openerOptions) && + taskInfo && ProcessTaskInfo.is(taskInfo) && taskInfo.command && taskInfo.command.length > 0 + ) { + widget.writeLine(`\x1b[1m> Executing task: ${taskInfo.command} <\x1b[0m\n`); + } return widget; } @@ -151,8 +165,8 @@ export class TaskTerminalWidgetManager { // 1) dedicated, 2) idle, 3) the one that ran the same task if (widget.dedicated && !widget.busy && - widget.taskConfig && openerOptions.taskConfig && - this.taskDefinitionRegistry.compareTasks(openerOptions.taskConfig, widget.taskConfig)) { + widget.taskConfig && openerOptions.taskInfo && + this.taskDefinitionRegistry.compareTasks(openerOptions.taskInfo.taskConfig, widget.taskConfig)) { reusableTerminalWidget = widget; break; diff --git a/packages/task/src/common/process/task-protocol.ts b/packages/task/src/common/process/task-protocol.ts index 441c192c46ba4..999063eae563c 100644 --- a/packages/task/src/common/process/task-protocol.ts +++ b/packages/task/src/common/process/task-protocol.ts @@ -78,14 +78,14 @@ export interface ProcessTaskConfiguration extends TaskConfiguration, CommandProp } export interface ProcessTaskInfo extends TaskInfo { - /** terminal id. Defined if task is run as a terminal process */ - readonly terminalId?: number; /** process id. Defined if task is run as a process */ readonly processId?: number; + /** process task command */ + readonly command?: string; } export namespace ProcessTaskInfo { export function is(info: TaskInfo): info is ProcessTaskInfo { - return info['terminalId'] !== undefined || info['processId'] !== undefined; + return info['processId'] !== undefined; } } diff --git a/packages/task/src/common/task-protocol.ts b/packages/task/src/common/task-protocol.ts index e4f29513ec834..542457e2301c6 100644 --- a/packages/task/src/common/task-protocol.ts +++ b/packages/task/src/common/task-protocol.ts @@ -40,6 +40,7 @@ export enum PanelKind { } export interface TaskOutputPresentation { + echo?: boolean; focus?: boolean; reveal?: RevealKind; panel?: PanelKind; @@ -51,6 +52,7 @@ export interface TaskOutputPresentation { export namespace TaskOutputPresentation { export function getDefault(): TaskOutputPresentation { return { + echo: true, reveal: RevealKind.Always, focus: false, panel: PanelKind.Shared, @@ -83,6 +85,7 @@ export namespace TaskOutputPresentation { } outputPresentation = { ...outputPresentation, + echo: task.presentation.echo === undefined || task.presentation.echo, focus: shouldSetFocusToTerminal(task), showReuseMessage: shouldShowReuseMessage(task), clear: shouldClearTerminalBeforeRun(task) diff --git a/packages/task/src/node/process/process-task-runner.ts b/packages/task/src/node/process/process-task-runner.ts index 094318e1c4480..5f348af90eaa8 100644 --- a/packages/task/src/node/process/process-task-runner.ts +++ b/packages/task/src/node/process/process-task-runner.ts @@ -72,7 +72,8 @@ export class ProcessTaskRunner implements TaskRunner { // way the command is passed: // - process: directly look for an executable and pass a specific set of arguments/options. // - shell: defer the spawning to a shell that will evaluate a command line with our executable. - const terminal: Process = this.terminalProcessFactory(this.getResolvedCommand(taskConfig)); + const terminalProcessOptions = this.getResolvedCommand(taskConfig); + const terminal: Process = this.terminalProcessFactory(terminalProcessOptions); // Wait for the confirmation that the process is successfully started, or has failed to start. await new Promise((resolve, reject) => { @@ -82,12 +83,14 @@ export class ProcessTaskRunner implements TaskRunner { }); }); + const processType = taskConfig.type as 'process' | 'shell'; return this.taskFactory({ label: taskConfig.label, process: terminal, - processType: taskConfig.type as 'process' | 'shell', + processType, context: ctx, - config: taskConfig + config: taskConfig, + command: this.getCommand(processType, terminalProcessOptions) }); } catch (error) { this.logger.error(`Error occurred while creating task: ${error}`); @@ -249,6 +252,16 @@ export class ProcessTaskRunner implements TaskRunner { return { command, args, commandLine, options }; } + private getCommand(processType: 'process' | 'shell', terminalProcessOptions: TerminalProcessOptions): string | undefined { + if (terminalProcessOptions.args) { + if (processType === 'shell') { + return terminalProcessOptions.args[terminalProcessOptions.args.length - 1]; + } else if (processType === 'process') { + return `${terminalProcessOptions.command} ${terminalProcessOptions.args.join(' ')}`; + } + } + } + /** * This is task specific, to align with VS Code's behavior. * diff --git a/packages/task/src/node/process/process-task.ts b/packages/task/src/node/process/process-task.ts index 3117ff40d3bee..d5295881cb1ed 100644 --- a/packages/task/src/node/process/process-task.ts +++ b/packages/task/src/node/process/process-task.ts @@ -48,6 +48,7 @@ export const TaskProcessOptions = Symbol('TaskProcessOptions'); export interface TaskProcessOptions extends TaskOptions { process: Process; processType: ProcessType; + command?: string; } export const TaskFactory = Symbol('TaskFactory'); @@ -57,6 +58,8 @@ export type TaskFactory = (options: TaskProcessOptions) => ProcessTask; @injectable() export class ProcessTask extends Task { + protected command: string | undefined; + constructor( @inject(TaskManager) protected readonly taskManager: TaskManager, @inject(ILogger) @named('task') protected readonly logger: ILogger, @@ -92,6 +95,8 @@ export class ProcessTask extends Task { }); } }); + + this.command = this.options.command; this.logger.info(`Created new task, id: ${this.id}, process id: ${this.options.process.id}, OS PID: ${this.process.pid}, context: ${this.context}`); } @@ -128,6 +133,7 @@ export class ProcessTask extends Task { config: this.options.config, terminalId: this.process.id, processId: this.process.id, + command: this.command }; }