提交 8bcd40f1 编写于 作者: D Dirk Baeumer

First version to support keybindings for tasks

上级 f60744fa
......@@ -14,8 +14,12 @@ import * as UUID from 'vs/base/common/uuid';
import { Config as ProcessConfig } from 'vs/base/common/processes';
import { ValidationStatus, ValidationState, ILogger } from 'vs/base/common/parsers';
import { NamedProblemMatcher, ProblemMatcher, ProblemMatcherParser, Config as ProblemMatcherConfig, registry as ProblemMatcherRegistry, isNamedProblemMatcher } from 'vs/platform/markers/common/problemMatcher';
import * as TaskSystem from 'vs/workbench/parts/tasks/common/taskSystem';
import {
NamedProblemMatcher, ProblemMatcher, ProblemMatcherParser, Config as ProblemMatcherConfig,
registry as ProblemMatcherRegistry, isNamedProblemMatcher
} from 'vs/platform/markers/common/problemMatcher';
import * as TaskSystem from './taskSystem';
/**
* Defines the problem handling strategy
......@@ -55,6 +59,24 @@ export interface PlatformTaskDescription {
args?: string[];
}
export interface CommandBinding {
/**
* The command Id the task is bound to.
*/
commandId?: string;
/**
* The title to use
*/
title?: string;
/**
* An optional category
*/
category?: string;
}
/**
* The description of a task.
*/
......@@ -122,6 +144,11 @@ export interface TaskDescription extends PlatformTaskDescription {
*/
suppressTaskName?: boolean;
/**
* The command this task is bound to.
*/
bindTo?: CommandBinding;
/**
* The problem matcher(s) to use to capture problems in the tasks
* output.
......@@ -623,6 +650,37 @@ namespace ProblemMatcherConverter {
}
}
namespace CommandBinding {
export function isEmpty(value: TaskSystem.CommandBinding): boolean {
return !value || value.commandId === void 0 && value.title === void 0 && value.category === void 0;
}
export function from(this: void, binding: CommandBinding, context: ParseContext): TaskSystem.CommandBinding {
if (!binding) {
return undefined;
}
if (!Types.isString(binding.commandId)) {
context.validationStatus.state = ValidationState.Warning;
context.logger.log(nls.localize('noCommandId', 'Warning: a command binding must defined a commandId. Ignoring binding.'));
return undefined;
}
if (!Types.isString(binding.title)) {
context.validationStatus.state = ValidationState.Warning;
context.logger.log(nls.localize('noTitle', 'Warning: a command binding must defined a title. Ignoring binding.'));
return undefined;
}
let result: TaskSystem.CommandBinding = {
commandId: binding.commandId,
title: binding.title
};
if (Types.isString(binding.category)) {
result.category = binding.category;
}
return result;
}
}
namespace TaskDescription {
export interface TaskConfiguration {
......@@ -682,6 +740,9 @@ namespace TaskDescription {
task.suppressTaskName = !!externalTask.suppressTaskName;
}
if (externalTask.bindTo) {
task.bindTo = CommandBinding.from(externalTask.bindTo, context);
}
if (problemMatchers) {
task.problemMatchers = problemMatchers;
}
......
......@@ -136,6 +136,23 @@ export interface CommandConfiguration {
echo?: boolean;
}
export interface CommandBinding {
/**
* The command Id the task is bound to.
*/
commandId: string;
/**
* The title to use
*/
title: string;
/**
* An optional category
*/
category?: string;
}
/**
* A task description
*/
......@@ -183,6 +200,11 @@ export interface TaskDescription {
*/
showOutput: ShowOutput;
/**
* The command this task is bound to.
*/
bindTo?: CommandBinding;
/**
* The problem watchers to use for this task
*/
......
......@@ -28,7 +28,7 @@ import * as strings from 'vs/base/common/strings';
import { Registry } from 'vs/platform/platform';
import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
import { SyncActionDescriptor } from 'vs/platform/actions/common/actions';
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';
......@@ -37,6 +37,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IFileService, FileChangeType } from 'vs/platform/files/common/files';
import { IExtensionService } from 'vs/platform/extensions/common/extensions';
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
import { IModeService } from 'vs/editor/common/services/modeService';
import { IModelService } from 'vs/editor/common/services/modelService';
......@@ -65,7 +66,7 @@ import { ITaskSystem, ITaskSummary, ITaskExecuteResult, TaskExecuteKind, TaskErr
import { ITaskService, TaskServiceEvents } from 'vs/workbench/parts/tasks/common/taskService';
import { templates as taskTemplates } from 'vs/workbench/parts/tasks/common/taskTemplates';
import * as FileConfig from 'vs/workbench/parts/tasks/node/processRunnerConfiguration';
import * as TaskConfig from 'vs/workbench/parts/tasks/common/taskConfiguration';
import { ProcessRunnerSystem } from 'vs/workbench/parts/tasks/node/processRunnerSystem';
import { TerminalTaskSystem } from './terminalTaskSystem';
import { ProcessRunnerDetector } from 'vs/workbench/parts/tasks/node/processRunnerDetector';
......@@ -73,6 +74,7 @@ import { ProcessRunnerDetector } from 'vs/workbench/parts/tasks/node/processRunn
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
let $ = Builder.$;
let tasksCategory = nls.localize('tasksCategory', "Tasks");
class AbstractTaskAction extends Action {
......@@ -170,6 +172,20 @@ class CleanAction extends AbstractTaskAction {
}
}
class CommandAction extends AbstractTaskAction {
constructor(id: string, label: string, private taskId: string, @ITaskService taskService: ITaskService, @ITelemetryService telemetryService: ITelemetryService,
@IMessageService messageService: IMessageService, @IWorkspaceContextService contextService: IWorkspaceContextService) {
super(id, label, taskService, telemetryService, messageService, contextService);
}
public run(): TPromise<ITaskSummary> {
if (!this.canRun()) {
return TPromise.as(undefined);
}
return this.taskService.run(this.taskId);
}
}
abstract class OpenTaskConfigurationAction extends Action {
private configurationService: IConfigurationService;
......@@ -196,6 +212,8 @@ abstract class OpenTaskConfigurationAction extends Action {
this.outputService = outputService;
this.messageService = messageService;
this.quickOpenService = quickOpenService;
}
public run(event?: any): TPromise<IEditor> {
......@@ -421,7 +439,6 @@ class RunTaskAction extends AbstractTaskAction {
}
}
class StatusBarItem implements IStatusbarItem {
private panelService: IPanelService;
......@@ -697,7 +714,7 @@ class TaskService extends EventEmitter implements ITaskService {
}
this.emit(TaskServiceEvents.ConfigChanged);
if (this._inTerminal) {
this.readConfiguration().then((config) => {
this.createConfiguration().then((config) => {
if (!config) {
return;
}
......@@ -720,6 +737,19 @@ class TaskService extends EventEmitter implements ITaskService {
});
lifecycleService.onWillShutdown(event => event.veto(this.beforeShutdown()));
CommandsRegistry.registerCommand('workbench.action.tasks.runTask', (accessor, arg) => {
if (Types.isString(arg)) {
this.tasks().then(tasks => {
for (let task of tasks) {
if (task.name === arg) {
this.run(task.id);
}
}
});
} else {
this.quickOpenService.show('task ');
}
});
}
public log(value: string): void {
......@@ -747,7 +777,7 @@ class TaskService extends EventEmitter implements ITaskService {
this._taskSystem = new NullTaskSystem();
this._taskSystemPromise = TPromise.as(this._taskSystem);
} else {
let clearOutput = true;
let hasError = false;
this._taskSystemPromise = TPromise.as(this.configurationService.getConfiguration<TaskConfiguration>('tasks')).then((config: TaskConfiguration) => {
let parseErrors: string[] = config ? (<any>config).$parseErrors : null;
if (parseErrors) {
......@@ -766,16 +796,16 @@ class TaskService extends EventEmitter implements ITaskService {
}
let configPromise: TPromise<TaskConfiguration>;
if (config) {
if (this.isRunnerConfig(config) && this.hasDetectorSupport(<FileConfig.ExternalTaskRunnerConfiguration>config)) {
let fileConfig = <FileConfig.ExternalTaskRunnerConfiguration>config;
if (this.isRunnerConfig(config) && this.hasDetectorSupport(<TaskConfig.ExternalTaskRunnerConfiguration>config)) {
let fileConfig = <TaskConfig.ExternalTaskRunnerConfiguration>config;
configPromise = new ProcessRunnerDetector(this.fileService, this.contextService, this.configurationResolverService, fileConfig).detect(true).then((value) => {
clearOutput = this.printStderr(value.stderr);
hasError = this.printStderr(value.stderr);
let detectedConfig = value.config;
if (!detectedConfig) {
return config;
}
let result: FileConfig.ExternalTaskRunnerConfiguration = Objects.clone(fileConfig);
let configuredTasks: IStringDictionary<FileConfig.TaskDescription> = Object.create(null);
let result: TaskConfig.ExternalTaskRunnerConfiguration = Objects.clone(fileConfig);
let configuredTasks: IStringDictionary<TaskConfig.TaskDescription> = Object.create(null);
if (!result.tasks) {
if (detectedConfig.tasks) {
result.tasks = detectedConfig.tasks;
......@@ -795,7 +825,7 @@ class TaskService extends EventEmitter implements ITaskService {
}
} else {
configPromise = new ProcessRunnerDetector(this.fileService, this.contextService, this.configurationResolverService).detect(true).then((value) => {
clearOutput = this.printStderr(value.stderr);
hasError = this.printStderr(value.stderr);
return value.config;
});
}
......@@ -805,16 +835,18 @@ class TaskService extends EventEmitter implements ITaskService {
throw new TaskError(Severity.Info, nls.localize('TaskSystem.noConfiguration', 'No task runner configured.'), TaskErrors.NotConfigured);
}
let result: ITaskSystem = null;
let parseResult = FileConfig.parse(<FileConfig.ExternalTaskRunnerConfiguration>config, this);
let parseResult = TaskConfig.parse(<TaskConfig.ExternalTaskRunnerConfiguration>config, this);
if (!parseResult.validationStatus.isOK()) {
this.outputChannel.show(true);
hasError = true;
}
if (parseResult.validationStatus.isFatal()) {
throw new TaskError(Severity.Error, nls.localize('TaskSystem.fatalError', 'The provided task configuration has validation errors. See tasks output log for details.'), TaskErrors.ConfigValidationError);
}
if (this.isRunnerConfig(config)) {
this._inTerminal = false;
result = new ProcessRunnerSystem(parseResult.configuration, this.markerService, this.modelService, this.telemetryService, this.outputService, this.configurationResolverService, TaskService.OutputChannelId, clearOutput);
result = new ProcessRunnerSystem(parseResult.configuration, this.markerService, this.modelService,
this.telemetryService, this.outputService, this.configurationResolverService, TaskService.OutputChannelId, hasError);
} else if (this.isTerminalConfig(config)) {
this._inTerminal = true;
result = new TerminalTaskSystem(
......@@ -842,7 +874,7 @@ class TaskService extends EventEmitter implements ITaskService {
return this._taskSystemPromise;
}
private readConfiguration(): TPromise<TaskRunnerConfiguration> {
private createConfiguration(): TPromise<TaskRunnerConfiguration> {
let config = this.configurationService.getConfiguration<TaskConfiguration>('tasks');
let parseErrors: string[] = config ? (<any>config).$parseErrors : null;
if (parseErrors) {
......@@ -861,16 +893,16 @@ class TaskService extends EventEmitter implements ITaskService {
}
let configPromise: TPromise<TaskConfiguration>;
if (config) {
if (this.isRunnerConfig(config) && this.hasDetectorSupport(<FileConfig.ExternalTaskRunnerConfiguration>config)) {
let fileConfig = <FileConfig.ExternalTaskRunnerConfiguration>config;
if (this.isRunnerConfig(config) && this.hasDetectorSupport(<TaskConfig.ExternalTaskRunnerConfiguration>config)) {
let fileConfig = <TaskConfig.ExternalTaskRunnerConfiguration>config;
configPromise = new ProcessRunnerDetector(this.fileService, this.contextService, this.configurationResolverService, fileConfig).detect(true).then((value) => {
this.printStderr(value.stderr);
let detectedConfig = value.config;
if (!detectedConfig) {
return config;
}
let result: FileConfig.ExternalTaskRunnerConfiguration = Objects.clone(fileConfig);
let configuredTasks: IStringDictionary<FileConfig.TaskDescription> = Object.create(null);
let result: TaskConfig.ExternalTaskRunnerConfiguration = Objects.clone(fileConfig);
let configuredTasks: IStringDictionary<TaskConfig.TaskDescription> = Object.create(null);
if (!result.tasks) {
if (detectedConfig.tasks) {
result.tasks = detectedConfig.tasks;
......@@ -898,7 +930,7 @@ class TaskService extends EventEmitter implements ITaskService {
if (!config) {
return undefined;
}
let parseResult = FileConfig.parse(<FileConfig.ExternalTaskRunnerConfiguration>config, this);
let parseResult = TaskConfig.parse(<TaskConfig.ExternalTaskRunnerConfiguration>config, this);
if (!parseResult.validationStatus.isOK()) {
this.showOutput();
}
......@@ -911,10 +943,10 @@ class TaskService extends EventEmitter implements ITaskService {
}
private printStderr(stderr: string[]): boolean {
let result = true;
let result = false;
if (stderr && stderr.length > 0) {
stderr.forEach((line) => {
result = false;
result = true;
this.outputChannel.append(line + '\n');
});
this.outputChannel.show(true);
......@@ -934,7 +966,7 @@ class TaskService extends EventEmitter implements ITaskService {
return this._inTerminal !== void 0 && this._inTerminal;
}
private hasDetectorSupport(config: FileConfig.ExternalTaskRunnerConfiguration): boolean {
private hasDetectorSupport(config: TaskConfig.ExternalTaskRunnerConfiguration): boolean {
if (!config.command) {
return false;
}
......@@ -1122,7 +1154,7 @@ class TaskService extends EventEmitter implements ITaskService {
}
}
let tasksCategory = nls.localize('tasksCategory', "Tasks");
let workbenchActionsRegistry = <IWorkbenchActionRegistry>Registry.as(WorkbenchActionExtensions.WorkbenchActions);
workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(ConfigureTaskRunnerAction, ConfigureTaskRunnerAction.ID, ConfigureTaskRunnerAction.TEXT), 'Tasks: Configure Task Runner', tasksCategory);
workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(BuildAction, BuildAction.ID, BuildAction.TEXT, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_B }), 'Tasks: Run Build Task', tasksCategory);
......@@ -1131,7 +1163,9 @@ workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(TestAc
// workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(CleanAction, CleanAction.ID, CleanAction.TEXT), tasksCategory);
workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(TerminateAction, TerminateAction.ID, TerminateAction.TEXT), 'Tasks: Terminate Running Task', tasksCategory);
workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(ShowLogAction, ShowLogAction.ID, ShowLogAction.TEXT), 'Tasks: Show Task Log', tasksCategory);
workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(RunTaskAction, RunTaskAction.ID, RunTaskAction.TEXT), 'Tasks: Run Task', tasksCategory);
// workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(RunTaskAction, RunTaskAction.ID, RunTaskAction.TEXT), 'Tasks: Run Task', tasksCategory);
MenuRegistry.addCommand({ id: 'workbench.action.tasks.runTask', title: nls.localize('RunTaskAction.label', "Run Task"), category: tasksCategory });
// Task Service
registerSingleton(ITaskService, TaskService);
......
......@@ -20,7 +20,7 @@ import { IConfigurationResolverService } from 'vs/workbench/services/configurati
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import * as FileConfig from './processRunnerConfiguration';
import * as TaskConfig from '../common/taskConfiguration';
let build: string = 'build';
let test: string = 'test';
......@@ -109,7 +109,7 @@ class GruntTaskMatcher implements TaskDetectorMatcher {
}
export interface DetectorResult {
config: FileConfig.ExternalTaskRunnerConfiguration;
config: TaskConfig.ExternalTaskRunnerConfiguration;
stdout: string[];
stderr: string[];
}
......@@ -143,12 +143,12 @@ export class ProcessRunnerDetector {
private fileService: IFileService;
private contextService: IWorkspaceContextService;
private configurationResolverService: IConfigurationResolverService;
private taskConfiguration: FileConfig.ExternalTaskRunnerConfiguration;
private taskConfiguration: TaskConfig.ExternalTaskRunnerConfiguration;
private _stderr: string[];
private _stdout: string[];
private _cwd: string;
constructor(fileService: IFileService, contextService: IWorkspaceContextService, configurationResolverService: IConfigurationResolverService, config: FileConfig.ExternalTaskRunnerConfiguration = null) {
constructor(fileService: IFileService, contextService: IWorkspaceContextService, configurationResolverService: IConfigurationResolverService, config: TaskConfig.ExternalTaskRunnerConfiguration = null) {
this.fileService = fileService;
this.contextService = contextService;
this.configurationResolverService = configurationResolverService;
......@@ -225,27 +225,27 @@ export class ProcessRunnerDetector {
return result;
}
private tryDetectGulp(list: boolean): TPromise<{ config: FileConfig.ExternalTaskRunnerConfiguration; stderr: string[]; }> {
private tryDetectGulp(list: boolean): TPromise<{ config: TaskConfig.ExternalTaskRunnerConfiguration; stderr: string[]; }> {
return this.fileService.resolveFile(this.contextService.toResource('gulpfile.js')).then((stat) => {
let config = ProcessRunnerDetector.detectorConfig('gulp');
let process = new LineProcess('gulp', [config.arg, '--no-color'], true, { cwd: this._cwd });
return this.runDetection(process, 'gulp', true, config.matcher, ProcessRunnerDetector.DefaultProblemMatchers, list);
}, (err: any): FileConfig.ExternalTaskRunnerConfiguration => {
}, (err: any): TaskConfig.ExternalTaskRunnerConfiguration => {
return null;
});
}
private tryDetectGrunt(list: boolean): TPromise<{ config: FileConfig.ExternalTaskRunnerConfiguration; stderr: string[]; }> {
private tryDetectGrunt(list: boolean): TPromise<{ config: TaskConfig.ExternalTaskRunnerConfiguration; stderr: string[]; }> {
return this.fileService.resolveFile(this.contextService.toResource('Gruntfile.js')).then((stat) => {
let config = ProcessRunnerDetector.detectorConfig('grunt');
let process = new LineProcess('grunt', [config.arg, '--no-color'], true, { cwd: this._cwd });
return this.runDetection(process, 'grunt', true, config.matcher, ProcessRunnerDetector.DefaultProblemMatchers, list);
}, (err: any): FileConfig.ExternalTaskRunnerConfiguration => {
}, (err: any): TaskConfig.ExternalTaskRunnerConfiguration => {
return null;
});
}
private tryDetectJake(list: boolean): TPromise<{ config: FileConfig.ExternalTaskRunnerConfiguration; stderr: string[]; }> {
private tryDetectJake(list: boolean): TPromise<{ config: TaskConfig.ExternalTaskRunnerConfiguration; stderr: string[]; }> {
let run = () => {
let config = ProcessRunnerDetector.detectorConfig('jake');
let process = new LineProcess('jake', [config.arg], true, { cwd: this._cwd });
......@@ -256,7 +256,7 @@ export class ProcessRunnerDetector {
}, (err: any) => {
return this.fileService.resolveFile(this.contextService.toResource('Jakefile.js')).then((stat) => {
return run();
}, (err: any): FileConfig.ExternalTaskRunnerConfiguration => {
}, (err: any): TaskConfig.ExternalTaskRunnerConfiguration => {
return null;
});
});
......@@ -276,7 +276,7 @@ export class ProcessRunnerDetector {
}
return { config: null, stdout: this._stdout, stderr: this._stderr };
}
let result: FileConfig.ExternalTaskRunnerConfiguration = {
let result: TaskConfig.ExternalTaskRunnerConfiguration = {
version: ProcessRunnerDetector.Version,
command: command,
isShellCommand: isShellCommand
......@@ -314,8 +314,8 @@ export class ProcessRunnerDetector {
});
}
private createTaskDescriptions(tasks: string[], problemMatchers: string[], list: boolean): FileConfig.TaskDescription[] {
let taskConfigs: FileConfig.TaskDescription[] = [];
private createTaskDescriptions(tasks: string[], problemMatchers: string[], list: boolean): TaskConfig.TaskDescription[] {
let taskConfigs: TaskConfig.TaskDescription[] = [];
if (list) {
tasks.forEach((task) => {
taskConfigs.push({
......
......@@ -53,7 +53,7 @@ export class ProcessRunnerSystem extends EventEmitter implements ITaskSystem {
private activeTaskPromise: TPromise<ITaskSummary>;
constructor(configuration: TaskRunnerConfiguration, markerService: IMarkerService, modelService: IModelService, telemetryService: ITelemetryService,
outputService: IOutputService, configurationResolverService: IConfigurationResolverService, outputChannelId: string, clearOutput: boolean = true) {
outputService: IOutputService, configurationResolverService: IConfigurationResolverService, outputChannelId: string, hasErrors: boolean) {
super();
this.configuration = configuration;
this.markerService = markerService;
......@@ -66,11 +66,7 @@ export class ProcessRunnerSystem extends EventEmitter implements ITaskSystem {
this.activeTaskIdentifier = null;
this.activeTaskPromise = null;
this.outputChannel = this.outputService.getChannel(outputChannelId);
if (clearOutput) {
this.clearOutput();
}
this.errorsShown = false;
this.errorsShown = !hasErrors;
}
......
......@@ -12,7 +12,7 @@ import * as Platform from 'vs/base/common/platform';
import { ProblemMatcher, FileLocationKind, ProblemPattern, ApplyToKind } from 'vs/platform/markers/common/problemMatcher';
import * as TaskSystem from 'vs/workbench/parts/tasks/common/taskSystem';
import { parse, ParseResult, ILogger, ExternalTaskRunnerConfiguration } from 'vs/workbench/parts/tasks/node/processRunnerConfiguration';
import { parse, ParseResult, ILogger, ExternalTaskRunnerConfiguration } from 'vs/workbench/parts/tasks/common/taskConfiguration';
class Logger implements ILogger {
public receivedMessage: boolean = false;
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册