From c8b8854e8154cb595ed442478c55f756313107bb Mon Sep 17 00:00:00 2001 From: Liang Huang Date: Fri, 3 Jan 2020 21:47:24 -0500 Subject: [PATCH] support presentation.reveal and presentation.focus in task config - show the terminal if presentation.reveal = "always", - do not show the terminal if presentation.reveal = "never", - when presentation.reveal = "silent", show the terminal only if errors are found from the task output by the parser - when users start a task that is already active, put focus on the terminal if presentation.focus = true, or reveal the terminal if presentation.reveal = "always" Signed-off-by: Liang Huang --- .../task/src/browser/task-configurations.ts | 58 ++++++++++++------- .../task/src/browser/task-schema-updater.ts | 31 +++++++++- packages/task/src/browser/task-service.ts | 58 +++++++++++++++++-- packages/task/src/common/task-protocol.ts | 36 ++++++++++++ packages/task/src/node/task-server.ts | 1 + 5 files changed, 155 insertions(+), 29 deletions(-) diff --git a/packages/task/src/browser/task-configurations.ts b/packages/task/src/browser/task-configurations.ts index 3e4f55f3dfa4e..c03b9e7050ac6 100644 --- a/packages/task/src/browser/task-configurations.ts +++ b/packages/task/src/browser/task-configurations.ts @@ -16,7 +16,13 @@ import * as Ajv from 'ajv'; import { inject, injectable, postConstruct } from 'inversify'; -import { ContributedTaskConfiguration, TaskConfiguration, TaskCustomization, TaskDefinition } from '../common'; +import { + ContributedTaskConfiguration, + TaskConfiguration, + TaskCustomization, + TaskDefinition, + TaskOutputPresentation +} from '../common'; import { TaskDefinitionRegistry } from './task-definition-registry'; import { ProvidedTaskConfigurations } from './provided-task-configurations'; import { TaskConfigurationManager } from './task-configuration-manager'; @@ -266,27 +272,12 @@ export class TaskConfigurations implements Disposable { /** parses a config file and extracts the tasks launch configurations */ protected async readTasks(rootFolderUri: string): Promise<(TaskCustomization | TaskConfiguration)[] | undefined> { - const configArray = this.taskConfigurationManager.getTasks(rootFolderUri); + const rawConfigArray = this.taskConfigurationManager.getTasks(rootFolderUri); if (this.rawTaskConfigurations.has(rootFolderUri)) { this.rawTaskConfigurations.delete(rootFolderUri); } - const tasks = configArray.map(config => { - if (this.isDetectedTask(config)) { - const def = this.getTaskDefinition(config); - return { - ...config, - _source: def!.source, - _scope: rootFolderUri - }; - } - return { - ...config, - _source: rootFolderUri, - _scope: rootFolderUri - }; - }); - this.rawTaskConfigurations.set(rootFolderUri, tasks); - return tasks; + this.rawTaskConfigurations.set(rootFolderUri, rawConfigArray); + return rawConfigArray; } /** Adds given task to a config file and opens the file to provide ability to edit task configuration. */ @@ -386,10 +377,11 @@ export class TaskConfigurations implements Disposable { if (!isValid) { continue; } - if (this.isDetectedTask(taskConfig)) { - addCustomization(rootFolder, taskConfig); + const transformedTask = this.getTransformedRawTask(taskConfig, rootFolder); + if (this.isDetectedTask(transformedTask)) { + addCustomization(rootFolder, transformedTask); } else { - addConfiguredTask(rootFolder, taskConfig['label'] as string, taskConfig); + addConfiguredTask(rootFolder, transformedTask['label'] as string, transformedTask); } } } @@ -398,6 +390,28 @@ export class TaskConfigurations implements Disposable { this.tasksMap = newTaskMap; } + private getTransformedRawTask(rawTask: TaskCustomization | TaskConfiguration, rootFolderUri: string): TaskCustomization | TaskConfiguration { + let taskConfig: TaskCustomization | TaskConfiguration; + if (this.isDetectedTask(rawTask)) { + const def = this.getTaskDefinition(rawTask); + taskConfig = { + ...rawTask, + _source: def!.source, + _scope: rootFolderUri + }; + } else { + taskConfig = { + ...rawTask, + _source: rootFolderUri, + _scope: rootFolderUri + }; + } + return { + ...taskConfig, + presentation: TaskOutputPresentation.fromJson(rawTask) + }; + } + /** * Returns `true` if the given task configuration is valid as per the task schema defined in Theia * or contributed by Theia extensions and plugins, `false` otherwise. diff --git a/packages/task/src/browser/task-schema-updater.ts b/packages/task/src/browser/task-schema-updater.ts index e95f9d10d47b7..f49946184cbf1 100644 --- a/packages/task/src/browser/task-schema-updater.ts +++ b/packages/task/src/browser/task-schema-updater.ts @@ -532,6 +532,34 @@ const problemMatcher = { ] }; +const presentation: IJSONSchema = { + type: 'object', + default: { + reveal: 'always', + focus: false + }, + description: 'Configures the panel that is used to present the task\'s output and reads its input.', + additionalProperties: true, + properties: { + focus: { + type: 'boolean', + default: false, + description: 'Controls whether the panel takes focus. Default is false. If set to true the panel is revealed as well.' + }, + reveal: { + type: 'string', + enum: ['always', 'silent', 'never'], + enumDescriptions: [ + 'Always reveals the terminal when this task is executed.', + 'Only reveals the terminal if the task exits with an error or the problem matcher finds an error.', + 'Never reveals the terminal when this task is executed.' + ], + default: 'always', + description: 'Controls whether the terminal running the task is revealed or not. May be overridden by option \"revealProblems\". Default is \"always\".' + } + } +}; + const taskIdentifier: IJSONSchema = { type: 'object', additionalProperties: true, @@ -603,7 +631,8 @@ const processTaskConfigurationSchema: IJSONSchema = { properties: commandAndArgs }, group, - problemMatcher + problemMatcher, + presentation }, additionalProperties: true }; diff --git a/packages/task/src/browser/task-service.ts b/packages/task/src/browser/task-service.ts index 9a8add08576c2..ffd6ca9503e78 100644 --- a/packages/task/src/browser/task-service.ts +++ b/packages/task/src/browser/task-service.ts @@ -30,7 +30,7 @@ import { TerminalWidgetFactoryOptions, TERMINAL_WIDGET_FACTORY_ID } from '@theia import { VariableResolverService } from '@theia/variable-resolver/lib/browser'; import { WorkspaceService } from '@theia/workspace/lib/browser/workspace-service'; import { inject, injectable, named, postConstruct } from 'inversify'; -import { Range } from 'vscode-languageserver-types'; +import { DiagnosticSeverity, Range } from 'vscode-languageserver-types'; import { NamedProblemMatcher, ProblemMatchData, @@ -45,7 +45,8 @@ import { TaskDefinition, TaskServer, TaskIdentifier, - DependsOrder + DependsOrder, + RevealKind } from '../common'; import { TaskWatcher } from '../common/task-watcher'; import { ProvidedTaskConfigurations } from './provided-task-configurations'; @@ -201,16 +202,39 @@ export class TaskService implements TaskConfigurationClient { this.messageService.info(`Task '${taskIdentifier}' has been started.`); }); - this.taskWatcher.onOutputProcessed((event: TaskOutputProcessedEvent) => { + this.taskWatcher.onOutputProcessed(async (event: TaskOutputProcessedEvent) => { if (!this.isEventForThisClient(event.ctx)) { return; } if (event.problems) { + const runningTasksInfo: TaskInfo[] = await this.getRunningTasks(); + // check if the task is active + const matchedRunningTaskInfo = runningTasksInfo.find(taskInfo => { + const taskConfig = taskInfo.config; + return this.taskDefinitionRegistry.compareTasks(taskConfig, event.config); + }); + const isTaskActiveAndOutputSilent = matchedRunningTaskInfo && + matchedRunningTaskInfo.config.presentation && matchedRunningTaskInfo.config.presentation.reveal === RevealKind.Silent; event.problems.forEach(problem => { const existingMarkers = this.problemManager.findMarkers({ owner: problem.description.owner }); const uris = new Set(); existingMarkers.forEach(marker => uris.add(marker.uri)); if (ProblemMatchData.is(problem) && problem.resource) { + // When task.presentation.reveal === RevealKind.Silent, put focus on the terminal only if it is an error + if (isTaskActiveAndOutputSilent && problem.marker.severity === DiagnosticSeverity.Error) { + const terminalId = matchedRunningTaskInfo!.terminalId; + if (terminalId) { + const terminal = this.terminalService.getById(this.getTerminalWidgetId(terminalId)); + if (terminal) { + const focus = !!matchedRunningTaskInfo!.config.presentation!.focus; + if (focus) { // assign focus to the terminal if presentation.focus is true + this.shell.activateWidget(terminal.id); + } else { // show the terminal but not assign focus + this.shell.revealWidget(terminal.id); + } + } + } + } const uri = new URI(problem.resource.path).withScheme(problem.resource.scheme); if (uris.has(uri.toString())) { const newData = [ @@ -268,6 +292,18 @@ export class TaskService implements TaskConfigurationClient { if (event.code === 0) { this.messageService.info(message); } else { + const eventTaskConfig = event.config; + if (eventTaskConfig && eventTaskConfig.presentation && eventTaskConfig.presentation.reveal === RevealKind.Silent && event.terminalId) { + const terminal = this.terminalService.getById(this.getTerminalWidgetId(event.terminalId)); + const focus = !!eventTaskConfig.presentation.focus; + if (terminal) { + if (focus) { // assign focus to the terminal if presentation.focus is true + this.shell.activateWidget(terminal.id); + } else { // show the terminal but not assign focus + this.shell.revealWidget(terminal.id); + } + } + } this.messageService.error(message); } } else if (event.signal !== undefined) { @@ -655,8 +691,12 @@ export class TaskService implements TaskConfigurationClient { const terminalId = matchedRunningTaskInfo.terminalId; if (terminalId) { const terminal = this.terminalService.getById(this.getTerminalWidgetId(terminalId)); - if (terminal) { - this.shell.activateWidget(terminal.id); // make the terminal visible and assign focus + if (terminal && task.presentation) { + if (task.presentation.focus) { // assign focus to the terminal if presentation.focus is true + this.shell.activateWidget(terminal.id); + } else if (task.presentation.reveal === RevealKind.Always) { // show the terminal but not assign focus + this.shell.revealWidget(terminal.id); + } } } const selectedAction = await this.messageService.info(`The task '${taskName}' is already active`, 'Terminate Task', 'Restart Task'); @@ -947,7 +987,13 @@ export class TaskService implements TaskConfigurationClient { } ); this.shell.addWidget(widget, { area: 'bottom' }); - this.shell.activateWidget(widget.id); + if (taskInfo && taskInfo.config.presentation && taskInfo.config.presentation.reveal === RevealKind.Always) { + if (taskInfo.config.presentation.focus) { // assign focus to the terminal if presentation.focus is true + this.shell.activateWidget(widget.id); + } else { // show the terminal but not assign focus + this.shell.revealWidget(widget.id); + } + } widget.start(processId); } diff --git a/packages/task/src/common/task-protocol.ts b/packages/task/src/common/task-protocol.ts index 839895e02d732..892bc8d6a5e7c 100644 --- a/packages/task/src/common/task-protocol.ts +++ b/packages/task/src/common/task-protocol.ts @@ -27,10 +27,45 @@ export enum DependsOrder { Parallel = 'parallel', } +export enum RevealKind { + Always, + Silent, + Never +} + +export interface TaskOutputPresentation { + focus?: boolean; + reveal?: RevealKind; + // tslint:disable-next-line:no-any + [name: string]: any; +} +export namespace TaskOutputPresentation { + // tslint:disable-next-line:no-any + export function fromJson(task: any): TaskOutputPresentation { + if (task && task.presentation) { + let reveal = RevealKind.Always; + if (task.presentation.reveal === 'silent') { + reveal = RevealKind.Silent; + } else if (task.presentation.reveal === 'never') { + reveal = RevealKind.Never; + } + return { + reveal, + focus: !!task.presentation.focus + }; + } + return { + reveal: RevealKind.Always, + focus: false + }; + } +} + export interface TaskCustomization { type: string; group?: 'build' | 'test' | 'none' | { kind: 'build' | 'test' | 'none', isDefault: true }; problemMatcher?: string | ProblemMatcherContribution | (string | ProblemMatcherContribution)[]; + presentation?: TaskOutputPresentation; /** Whether the task is a background task or not. */ isBackground?: boolean; @@ -157,6 +192,7 @@ export interface TaskOutputEvent { export interface TaskOutputProcessedEvent { readonly taskId: number; + readonly config: TaskConfiguration; readonly ctx?: string; readonly problems?: ProblemMatch[]; } diff --git a/packages/task/src/node/task-server.ts b/packages/task/src/node/task-server.ts index bdbdec8d621e3..1d96fee068d17 100644 --- a/packages/task/src/node/task-server.ts +++ b/packages/task/src/node/task-server.ts @@ -123,6 +123,7 @@ export class TaskServerImpl implements TaskServer, Disposable { if (problems.length > 0) { this.fireTaskOutputProcessedEvent({ taskId: event.taskId, + config: taskConfiguration, ctx: event.ctx, problems });