提交 e030a071 编写于 作者: D Dirk Baeumer

Support customiying tasks

上级 5be2260d
......@@ -230,7 +230,10 @@ namespace Strings {
}
namespace CommandOptions {
export function from(value: { cwd?: string; env?: { [key: string]: string; } }): TaskSystem.CommandOptions {
function isShellOptions(value: any): value is vscode.ShellOptions {
return value && typeof value.executable === 'string';
}
export function from(value: vscode.ShellOptions | vscode.ProcessOptions): TaskSystem.CommandOptions {
if (value === void 0 || value === null) {
return undefined;
}
......@@ -248,14 +251,17 @@ namespace CommandOptions {
}
});
}
if (isShellOptions(value)) {
result.shell = ShellConfiguration.from(value);
}
return result;
}
}
namespace ShellConfiguration {
export function from(value: { executable?: string, args?: string[] }): boolean | TaskSystem.ShellConfiguration {
if (value === void 0 || value === null || typeof value.executable !== 'string') {
return true;
export function from(value: { executable?: string, args?: string[] }): TaskSystem.ShellConfiguration {
if (value === void 0 || value === null || !value.executable) {
return undefined;
}
let result: TaskSystem.ShellConfiguration = {
......@@ -323,7 +329,7 @@ namespace Tasks {
let result: TaskSystem.CommandConfiguration = {
name: value.process,
args: Strings.from(value.args),
isShellCommand: false,
type: TaskSystem.CommandType.Process,
terminal: TerminalBehaviour.from(value.terminal)
};
if (value.options) {
......@@ -338,7 +344,7 @@ namespace Tasks {
}
let result: TaskSystem.CommandConfiguration = {
name: value.commandLine,
isShellCommand: ShellConfiguration.from(value.options),
type: TaskSystem.CommandType.Shell,
terminal: TerminalBehaviour.from(value.terminal)
};
if (value.options) {
......
......@@ -97,10 +97,10 @@ export abstract class QuickOpenHandler extends Quickopen.QuickOpenHandler {
if (task._source.kind === TaskSourceKind.Workspace && groupWorkspace) {
groupWorkspace = false;
hadWorkspace = true;
entries.push(new TaskGroupEntry(this.createEntry(this.taskService, task, highlights), nls.localize('workspace', 'From Workspace'), false));
entries.push(new TaskGroupEntry(this.createEntry(this.taskService, task, highlights), nls.localize('configured', 'Configured Tasks'), false));
} else if (task._source.kind === TaskSourceKind.Extension && groupExtension) {
groupExtension = false;
entries.push(new TaskGroupEntry(this.createEntry(this.taskService, task, highlights), nls.localize('extension', 'From Extensions'), hadWorkspace));
entries.push(new TaskGroupEntry(this.createEntry(this.taskService, task, highlights), nls.localize('detected', 'Detected Tasks'), hadWorkspace));
} else {
entries.push(this.createEntry(this.taskService, task, highlights));
}
......@@ -125,7 +125,7 @@ class CustomizeTaskAction extends Action {
private static ID = 'workbench.action.tasks.customizeTask';
private static LABEL = nls.localize('customizeTask', "Customize Task");
constructor() {
constructor(private taskService: ITaskService, private task: Task) {
super(CustomizeTaskAction.ID, CustomizeTaskAction.LABEL);
this.updateClass();
}
......@@ -134,14 +134,14 @@ class CustomizeTaskAction extends Action {
this.class = 'quick-open-task-configure';
}
public run(context: any): TPromise<any> {
return TPromise.as(false);
public run(context: any): TPromise<boolean> {
return this.taskService.customize(this.task, true).then(_ => false, _ => false);
}
}
export class QuickOpenActionContributor extends ActionBarContributor {
constructor() {
constructor( @ITaskService private taskService: ITaskService) {
super();
}
......@@ -156,7 +156,7 @@ export class QuickOpenActionContributor extends ActionBarContributor {
const entry = this.getEntry(context);
if (entry && entry.task._source.kind === TaskSourceKind.Extension) {
actions.push(new CustomizeTaskAction());
actions.push(new CustomizeTaskAction(this.taskService, entry.task));
}
return actions;
}
......
......@@ -11,7 +11,6 @@ import { IStringDictionary } from 'vs/base/common/collections';
import * as Platform from 'vs/base/common/platform';
import * as Types from 'vs/base/common/types';
import * as UUID from 'vs/base/common/uuid';
import { Config as ProcessConfig } from 'vs/base/common/processes';
import { ValidationStatus, IProblemReporter as IProblemReporterBase } from 'vs/base/common/parsers';
import {
......@@ -32,7 +31,37 @@ export class ProblemHandling {
public static clean: string = 'cleanMatcherMatchers';
}
export interface ShellConfiguration {
executable: string;
args?: string[];
}
export interface CommandOptions {
/**
* The current working directory of the executed program or shell.
* If omitted VSCode's current workspace root is used.
*/
cwd?: string;
/**
* The additional environment of the executed program or shell. If omitted
* the parent process' environment is used.
*/
env?: IStringDictionary<string>;
/**
* The shell configuration;
*/
shell?: ShellConfiguration;
}
export interface PlatformTaskDescription {
/**
* Whether the task is a shell task or a process task.
*/
type?: string;
/**
* The command to be executed. Can be an external program or a shell
* command.
......@@ -40,17 +69,18 @@ export interface PlatformTaskDescription {
command?: string;
/**
* @deprecated use the task type instead.
* Specifies whether the command is a shell command and therefore must
* be executed in a shell interpreter (e.g. cmd.exe, bash, ...).
*
* Defaults to false if omitted.
*/
isShellCommand?: boolean;
isShellCommand?: boolean | ShellConfiguration;
/**
* The command options used when the command is executed. Can be omitted.
*/
options?: ProcessConfig.CommandOptions;
options?: CommandOptions;
/**
* The arguments passed to the command or additional arguments passed to the
......@@ -166,7 +196,7 @@ export interface BaseTaskRunnerConfiguration {
/**
* The command options used when the command is executed. Can be omitted.
*/
options?: ProcessConfig.CommandOptions;
options?: CommandOptions;
/**
* The arguments passed to the command. Can be omitted.
......@@ -308,7 +338,7 @@ interface ParseContext {
}
namespace CommandOptions {
export function from(this: void, options: ProcessConfig.CommandOptions, context: ParseContext): Tasks.CommandOptions {
export function from(this: void, options: CommandOptions, context: ParseContext): Tasks.CommandOptions {
let result: Tasks.CommandOptions = {};
if (options.cwd !== void 0) {
if (Types.isString(options.cwd)) {
......@@ -320,11 +350,12 @@ namespace CommandOptions {
if (options.env !== void 0) {
result.env = Objects.clone(options.env);
}
result.shell = ShellConfiguration.from(options.shell, context);
return isEmpty(result) ? undefined : result;
}
export function isEmpty(value: Tasks.CommandOptions): boolean {
return !value || value.cwd === void 0 && value.env === void 0;
return !value || value.cwd === void 0 && value.env === void 0 && value.shell === void 0;
}
export function merge(target: Tasks.CommandOptions, source: Tasks.CommandOptions): Tasks.CommandOptions {
......@@ -343,6 +374,7 @@ namespace CommandOptions {
Object.keys(source.env).forEach(key => env[key = source.env[key]]);
target.env = env;
}
target.shell = ShellConfiguration.merge(target.shell, source.shell);
return target;
}
......@@ -356,6 +388,7 @@ namespace CommandOptions {
if (value.cwd === void 0) {
value.cwd = '${workspaceRoot}';
}
ShellConfiguration.fillDefaults(value.shell);
return value;
}
......@@ -364,14 +397,10 @@ namespace CommandOptions {
if (value.env) {
Object.freeze(value.env);
}
ShellConfiguration.freeze(value.shell);
}
}
interface ShellConfiguration {
executable: string;
args?: string[];
}
namespace ShellConfiguration {
export function is(value: any): value is ShellConfiguration {
let candidate: ShellConfiguration = value;
......@@ -424,9 +453,10 @@ namespace CommandConfiguration {
interface BaseCommandConfiguationShape {
command?: string;
type?: string;
isShellCommand?: boolean | ShellConfiguration;
args?: string[];
options?: ProcessConfig.CommandOptions;
options?: CommandOptions;
echoCommand?: boolean;
showOutput?: string;
terminal?: TerminalBehavior;
......@@ -524,21 +554,20 @@ namespace CommandConfiguration {
function fromBase(this: void, config: BaseCommandConfiguationShape, context: ParseContext): Tasks.CommandConfiguration {
let result: Tasks.CommandConfiguration = {
name: undefined,
isShellCommand: undefined,
type: undefined,
terminal: undefined
};
if (Types.isString(config.command)) {
result.name = config.command;
}
if (Types.isBoolean(config.isShellCommand)) {
result.isShellCommand = config.isShellCommand;
} else if (ShellConfiguration.is(config.isShellCommand)) {
result.isShellCommand = ShellConfiguration.from(config.isShellCommand, context);
if (!context.isTermnial) {
context.problemReporter.warn(nls.localize('ConfigurationParser.noShell', 'Warning: shell configuration is only supported when executing tasks in the terminal.'));
}
if (Types.isString(config.type)) {
result.type = Tasks.CommandType.fromString(config.type);
}
let isShellConfiguration = ShellConfiguration.is(config.isShellCommand);
if (Types.isBoolean(config.isShellCommand) || isShellConfiguration) {
result.type = Tasks.CommandType.Shell;
} else if (config.isShellCommand !== void 0) {
result.isShellCommand = !!config.isShellCommand;
result.type = !!config.isShellCommand ? Tasks.CommandType.Shell : Tasks.CommandType.Process;
}
if (config.args !== void 0) {
if (Types.isStringArray(config.args)) {
......@@ -549,6 +578,12 @@ namespace CommandConfiguration {
}
if (config.options !== void 0) {
result.options = CommandOptions.from(config.options, context);
if (result.options && result.options.shell === void 0 && isShellConfiguration) {
result.options.shell = ShellConfiguration.from(config.isShellCommand as ShellConfiguration, context);
if (!context.isTermnial) {
context.problemReporter.warn(nls.localize('ConfigurationParser.noShell', 'Warning: shell configuration is only supported when executing tasks in the terminal.'));
}
}
}
let terminal = TerminalBehavior.from(config, context);
if (terminal) {
......@@ -561,13 +596,13 @@ namespace CommandConfiguration {
}
export function isEmpty(value: Tasks.CommandConfiguration): boolean {
return !value || value.name === void 0 && value.isShellCommand === void 0 && value.args === void 0 && CommandOptions.isEmpty(value.options) && value.terminal === void 0;
return !value || value.name === void 0 && value.type === void 0 && value.args === void 0 && CommandOptions.isEmpty(value.options) && value.terminal === void 0;
}
export function onlyTerminalBehaviour(value: Tasks.CommandConfiguration): boolean {
return value &&
value.terminal && (value.terminal.echo !== void 0 || value.terminal.reveal === void 0) &&
value.name === void 0 && value.isShellCommand === void 0 && value.args === void 0 && CommandOptions.isEmpty(value.options);
value.terminal && (value.terminal.echo !== void 0 || value.terminal.reveal !== void 0) &&
value.name === void 0 && value.type === void 0 && value.args === void 0 && CommandOptions.isEmpty(value.options);
}
export function merge(target: Tasks.CommandConfiguration, source: Tasks.CommandConfiguration): Tasks.CommandConfiguration {
......@@ -578,15 +613,10 @@ namespace CommandConfiguration {
return source;
}
mergeProperty(target, source, 'name');
mergeProperty(target, source, 'type');
// Merge isShellCommand
if (target.isShellCommand === void 0) {
target.isShellCommand = source.isShellCommand;
} if (Types.isBoolean(target.isShellCommand) && Types.isBoolean(source.isShellCommand)) {
mergeProperty(target, source, 'isShellCommand');
} else if (ShellConfiguration.is(target.isShellCommand) && ShellConfiguration.is(source.isShellCommand)) {
ShellConfiguration.merge(target.isShellCommand, source.isShellCommand);
} else if (Types.isBoolean(target.isShellCommand) && ShellConfiguration.is(source.isShellCommand)) {
target.isShellCommand = source.isShellCommand;
if (target.type === void 0) {
target.type = source.type;
}
target.terminal = TerminalBehavior.merge(target.terminal, source.terminal);
......@@ -606,8 +636,8 @@ namespace CommandConfiguration {
if (!value || Object.isFrozen(value)) {
return;
}
if (value.name !== void 0 && value.isShellCommand === void 0) {
value.isShellCommand = false;
if (value.name !== void 0 && value.type === void 0) {
value.type = Tasks.CommandType.Process;
}
value.terminal = TerminalBehavior.fillDefault(value.terminal);
if (value.args === void 0) {
......@@ -629,9 +659,6 @@ namespace CommandConfiguration {
if (value.terminal) {
TerminalBehavior.freeze(value.terminal);
}
if (ShellConfiguration.is(value.isShellCommand)) {
ShellConfiguration.freeze(value.isShellCommand);
}
}
}
......@@ -751,7 +778,7 @@ namespace TaskDescription {
let command: Tasks.CommandConfiguration = externalTask.command !== void 0
? CommandConfiguration.from(externalTask, context)
: externalTask.echoCommand !== void 0
? { name: undefined, isShellCommand: undefined, terminal: CommandConfiguration.TerminalBehavior.from(externalTask, context) }
? { name: undefined, type: undefined, terminal: CommandConfiguration.TerminalBehavior.from(externalTask, context) }
: undefined;
let identifer = Types.isString(externalTask.identifier) ? externalTask.identifier : taskName;
let task: Tasks.Task = {
......@@ -797,7 +824,7 @@ namespace TaskDescription {
}
fillDefaults(task);
let addTask: boolean = true;
if (context.isTermnial && task.command && task.command.name && task.command.isShellCommand && task.command.args && task.command.args.length > 0) {
if (context.isTermnial && task.command && task.command.name && task.command.type === Tasks.CommandType.Shell && task.command.args && task.command.args.length > 0) {
if (hasUnescapedSpaces(task.command.name) || task.command.args.some(hasUnescapedSpaces)) {
context.problemReporter.warn(nls.localize('taskConfiguration.shellArgs', 'Warning: the task \'{0}\' is a shell command and either the command name or one of its arguments has unescaped spaces. To ensure correct command line quoting please merge args into the command.', task.name));
}
......
......@@ -43,6 +43,8 @@ export interface ITaskService extends IEventEmitter {
terminateAll(): TPromise<TerminateResponse>;
tasks(): TPromise<Task[]>;
customize(task: Task, openConfig?: boolean): TPromise<void>;
registerTaskProvider(handle: number, taskProvider: ITaskProvider): void;
unregisterTaskProvider(handle: number): boolean;
}
\ No newline at end of file
......@@ -14,104 +14,6 @@ export interface TaskEntry extends IPickOpenEntry {
content: string;
}
const gulp: TaskEntry = {
id: 'gulp',
label: 'Gulp',
autoDetect: true,
content: [
'{',
'\t// See https://go.microsoft.com/fwlink/?LinkId=733558',
'\t// for the documentation about the tasks.json format',
'\t"version": "2.0.0",',
'\t"command": "gulp --no-color",',
'\t"isShellCommand": true,',
'}'
].join('\n')
};
const grunt: TaskEntry = {
id: 'grunt',
label: 'Grunt',
autoDetect: true,
content: [
'{',
'\t// See https://go.microsoft.com/fwlink/?LinkId=733558',
'\t// for the documentation about the tasks.json format',
'\t"version": "2.0.0",',
'\t"command": "grunt --no-color",',
'\t"isShellCommand": true,',
'}'
].join('\n')
};
const npm: TaskEntry = {
id: 'npm',
label: 'npm',
sort: 'NPM',
autoDetect: false,
content: [
'{',
'\t// See https://go.microsoft.com/fwlink/?LinkId=733558',
'\t// for the documentation about the tasks.json format',
'\t"version": "2.0.0",',
'\t"tasks": [',
'\t\t{',
'\t\t\t"taskName": "install",',
'\t\t\t"command": "npm install",',
'\t\t\t"isShellCommand": true,',
'\t\t},',
'\t\t{',
'\t\t\t"taskName": "update",',
'\t\t\t"command": "npm update",',
'\t\t\t"isShellCommand": true,',
'\t\t},',
'\t\t{',
'\t\t\t"taskName": "test",',
'\t\t\t"command": "npm run test",',
'\t\t\t"isShellCommand": true,',
'\t\t}',
'\t]',
'}'
].join('\n')
};
const tscConfig: TaskEntry = {
id: 'tsc.config',
label: 'TypeScript - tsconfig.json',
autoDetect: false,
description: nls.localize('tsc.config', 'Compiles a TypeScript project'),
content: [
'{',
'\t// See https://go.microsoft.com/fwlink/?LinkId=733558',
'\t// for the documentation about the tasks.json format',
'\t"version": "2.0.0",',
'\t"command": "tsc -p .",',
'\t"isShellCommand": true,',
'\t"showOutput": "silent",',
'\t"problemMatcher": "$tsc"',
'}'
].join('\n')
};
const tscWatch: TaskEntry = {
id: 'tsc.watch',
label: 'TypeScript - Watch Mode',
autoDetect: false,
description: nls.localize('tsc.watch', 'Compiles a TypeScript project in watch mode'),
content: [
'{',
'\t// See https://go.microsoft.com/fwlink/?LinkId=733558',
'\t// for the documentation about the tasks.json format',
'\t"version": "2.0.0",',
'\t"command": "tsc -w -p .",',
'\t"isShellCommand": true,',
'\t"showOutput": "silent",',
'\t"isBackground": true,',
'\t"problemMatcher": "$tsc-watch"',
'}'
].join('\n')
};
const dotnetBuild: TaskEntry = {
id: 'dotnetCore',
label: '.NET Core',
......@@ -122,16 +24,16 @@ const dotnetBuild: TaskEntry = {
'{',
'\t// See https://go.microsoft.com/fwlink/?LinkId=733558',
'\t// for the documentation about the tasks.json format',
'\t"version": "0.1.0",',
'\t"command": "dotnet",',
'\t"isShellCommand": true,',
'\t"args": [],',
'\t"version": "2.0.0",',
'\t"tasks": [',
'\t\t{',
'\t\t\t"taskName": "build",',
'\t\t\t"args": [ ],',
'\t\t\t"isBuildCommand": true,',
'\t\t\t"showOutput": "silent",',
'\t\t\t"command": "dotnet",',
'\t\t\t"isShellCommand": true,',
'\t\t\t"group": "build",',
'\t\t\t"terminal": {',
'\t\t\t\t"reveal": "silent"',
'\t\t\t},',
'\t\t\t"problemMatcher": "$msCompile"',
'\t\t}',
'\t]',
......@@ -149,18 +51,20 @@ const msbuild: TaskEntry = {
'\t// See https://go.microsoft.com/fwlink/?LinkId=733558',
'\t// for the documentation about the tasks.json format',
'\t"version": "2.0.0",',
'\t"command": "msbuild",',
'\t"args": [',
'\t\t// Ask msbuild to generate full paths for file names.',
'\t\t"/property:GenerateFullPaths=true"',
'\t],',
'\t"taskSelector": "/t:",',
'\t"showOutput": "silent",',
'\t"tasks": [',
'\t\t{',
'\t\t\t"taskName": "build",',
'\t\t\t// Show the output window only if unrecognized errors occur.',
'\t\t\t"showOutput": "silent",',
'\t\t\t"command": "msbuild",',
'\t\t\t"args": [',
'\t\t\t\t// Ask msbuild to generate full paths for file names.',
'\t\t\t\t"/property:GenerateFullPaths=true",',
'\t\t\t\t"/t:build"',
'\t\t\t],',
'\t\t\t"group": "build",',
'\t\t\t"terminal": {',
'\t\t\t\t// Reveal the terminal only if unrecognized errors occur.',
'\t\t\t\t"reveal": "silent"',
'\t\t\t},',
'\t\t\t// Use the standard MS compiler pattern to detect errors, warnings and infos',
'\t\t\t"problemMatcher": "$msCompile"',
'\t\t}',
......@@ -201,26 +105,25 @@ const maven: TaskEntry = {
'\t// See https://go.microsoft.com/fwlink/?LinkId=733558',
'\t// for the documentation about the tasks.json format',
'\t"version": "2.0.0",',
'\t"showOutput": "always",',
'\t"tasks": [',
'\t\t{',
'\t\t\t"taskName": "verify",',
'\t\t\t"command": "mvn -B verify",',
'\t\t\t"isShellCommand": true,',
'\t\t\t"isBuildCommand": true',
'\t\t\t"group": "build"',
'\t\t},',
'\t\t{',
'\t\t\t"taskName": "test",',
'\t\t\t"command": "mvn -B test",',
'\t\t\t"isShellCommand": true,',
'\t\t\t"isTestCommand": true',
'\t\t\t"group": "test"',
'\t\t}',
'\t]',
'}'
].join('\n')
};
export let templates: TaskEntry[] = [gulp, grunt, tscConfig, tscWatch, dotnetBuild, msbuild, npm, maven].sort((a, b) => {
export let templates: TaskEntry[] = [dotnetBuild, msbuild, maven].sort((a, b) => {
return (a.sort || a.label).localeCompare(b.sort || b.label);
});
templates.push(command);
......@@ -9,20 +9,6 @@ import * as Types from 'vs/base/common/types';
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { ProblemMatcher } from 'vs/platform/markers/common/problemMatcher';
export interface CommandOptions {
/**
* The current working directory of the executed program or shell.
* If omitted VSCode's current workspace root is used.
*/
cwd?: string;
/**
* The environment of the executed program or shell. If omitted
* the parent process' environment is used.
*/
env?: { [key: string]: string; };
}
export interface ShellConfiguration {
/**
* The shell executable.
......@@ -41,6 +27,26 @@ export namespace ShellConfiguration {
}
}
export interface CommandOptions {
/**
* The shell to use if the task is a shell command.
*/
shell?: ShellConfiguration;
/**
* The current working directory of the executed program or shell.
* If omitted VSCode's current workspace root is used.
*/
cwd?: string;
/**
* The environment of the executed program or shell. If omitted
* the parent process' environment is used.
*/
env?: { [key: string]: string; };
}
export enum RevealKind {
/**
* Always brings the terminal to front if the task is executed.
......@@ -87,16 +93,35 @@ export interface TerminalBehavior {
echo: boolean;
}
export enum CommandType {
Shell = 1,
Process = 2
}
export namespace CommandType {
export function fromString(value: string): CommandType {
switch (value.toLowerCase()) {
case 'shell':
return CommandType.Shell;
case 'process':
return CommandType.Process;
default:
return CommandType.Process;
}
}
}
export interface CommandConfiguration {
/**
* The command to execute
* The task type
*/
name: string;
type: CommandType;
/**
* Whether the command is a shell command or not
* The command to execute
*/
isShellCommand: boolean | ShellConfiguration;
name: string;
/**
* Additional command options.
......
......@@ -104,6 +104,13 @@ const group: IJSONSchema = {
description: nls.localize('JsonSchema.tasks.group', 'Defines to which execution group this task belongs to. If omitted the task belongs to no group')
};
const taskType: IJSONSchema = {
type: 'string',
enum: ['shell', 'process'],
default: 'process',
description: nls.localize('JsonSchema.tasks.type', 'Defines whether the task is run as a process or as a command inside a shell. Default is process')
};
schema.definitions = Objects.deepClone(commonSchema.definitions);
let definitions = schema.definitions;
definitions.commandConfiguration.properties.isShellCommand = Objects.deepClone(shellCommand);
......@@ -113,6 +120,8 @@ definitions.showOutputType.deprecationMessage = nls.localize('JsonSchema.tasks.s
definitions.taskDescription.properties.echoCommand.deprecationMessage = nls.localize('JsonSchema.tasks.echoCommand.deprecated', 'The property echoCommand is deprecated. Use the terminal property instead.');
definitions.taskDescription.properties.isBuildCommand.deprecationMessage = nls.localize('JsonSchema.tasks.isBuildCommand.deprecated', 'The property isBuildCommand is deprecated. Use the group property instead.');
definitions.taskDescription.properties.isTestCommand.deprecationMessage = nls.localize('JsonSchema.tasks.isTestCommand.deprecated', 'The property isTestCommand is deprecated. Use the group property instead.');
definitions.taskDescription.properties.type = taskType;
definitions.taskDescription.properties.isShellCommand.deprecationMessage = nls.localize('JsonSchema.tasks.isShellCommand.deprecated', 'The property isShellCommand is deprecated. Use the type property instead.');
definitions.taskDescription.properties.terminal = terminal;
definitions.taskDescription.properties.group = group;
definitions.taskRunnerConfiguration.properties.isShellCommand = Objects.deepClone(shellCommand);
......
......@@ -61,6 +61,7 @@ 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 } from 'vs/workbench/services/configuration/common/configurationEditing';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
......@@ -118,40 +119,13 @@ abstract class OpenTaskConfigurationAction extends Action {
if (!selection) {
return undefined;
}
let contentPromise: TPromise<string>;
if (selection.autoDetect) {
contentPromise = this.extensionService.activateByEvent('onCommand:workbench.action.tasks.runTask').then(() => {
return this.taskService.tasks().then((tasks) => {
let tasksToInsert: Task[] = tasks.filter((task) => {
return task.identifier && task.identifier.indexOf(selection.id) === 0 && task.identifier[selection.id.length] === '.' && task.group !== void 0;
});
if (tasksToInsert.length === 0) {
return selection.content;
}
let config: TaskConfig.ExternalTaskRunnerConfiguration = {
version: '2.0.0',
tasks: tasksToInsert.map<TaskConfig.TaskDescription>((task) => { return { taskName: task.name }; })
};
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 {
contentPromise = TPromise.as(selection.content);
let content = selection.content;
let editorConfig = this.configurationService.getConfiguration<any>();
if (editorConfig.editor.insertSpaces) {
content = content.replace(/(\n)(\t+)/g, (_, s1, s2) => s1 + strings.repeat(' ', s2.length * editorConfig.editor.tabSize));
}
return contentPromise.then(content => {
let editorConfig = this.configurationService.getConfiguration<any>();
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);
});
configFileCreated = true;
return this.fileService.createFile(this.contextService.toResource('.vscode/tasks.json'), content);
});
}).then((stat) => {
if (!stat) {
......@@ -187,7 +161,6 @@ class ConfigureTaskRunnerAction extends OpenTaskConfigurationAction {
outputService, messageService, quickOpenService, environmentService, configurationResolverService,
extensionService);
}
}
class ConfigureBuildTaskAction extends OpenTaskConfigurationAction {
......@@ -503,6 +476,7 @@ class TaskService extends EventEmitter implements ITaskService {
private modeService: IModeService;
private configurationService: IConfigurationService;
private configurationEditingService: IConfigurationEditingService;
private markerService: IMarkerService;
private outputService: IOutputService;
private messageService: IMessageService;
......@@ -526,6 +500,7 @@ class TaskService extends EventEmitter implements ITaskService {
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,
......@@ -542,6 +517,7 @@ class TaskService extends EventEmitter implements ITaskService {
super();
this.modeService = modeService;
this.configurationService = configurationService;
this.configurationEditingService = configurationEditingService;
this.markerService = markerService;
this.outputService = outputService;
this.messageService = messageService;
......@@ -723,6 +699,43 @@ class TaskService extends EventEmitter implements ITaskService {
});
}
public customize(task: Task, openConfig: boolean = false): TPromise<void> {
if (task._source.kind !== TaskSourceKind.Extension) {
return TPromise.as<void>(undefined);
}
let configuration = this.getConfiguration();
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<void>(undefined);
}
let fileConfig = configuration.config;
let customize = { taskName: task.name };
if (!fileConfig) {
fileConfig = {
version: '2.0.0',
tasks: [customize]
};
} else {
if (Array.isArray(fileConfig.tasks)) {
fileConfig.tasks.push(customize);
} else {
fileConfig.tasks = [customize];
}
};
return this.configurationEditingService.writeConfiguration(ConfigurationTarget.WORKSPACE, { key: 'tasks', value: fileConfig }).then(() => {
if (openConfig) {
let resource = this.contextService.toResource('.vscode/tasks.json');
this.editorService.openEditor({
resource: resource,
options: {
forceOpen: true,
pinned: false
}
}, false);
}
});
}
private createRunnableTask(sets: TaskSet[], group: TaskGroup): { task: Task; resolver: ITaskResolver } {
let uuidMap: IStringDictionary<Task> = Object.create(null);
let identifierMap: IStringDictionary<Task> = Object.create(null);
......@@ -740,6 +753,8 @@ class TaskService extends EventEmitter implements ITaskService {
if (primaryTasks.length === 0) {
return undefined;
}
// check for a WORKSPACE build task and use that onemptied.apply
let resolver: ITaskResolver = {
resolve: (id: string) => {
let result = uuidMap[id];
......
......@@ -33,7 +33,7 @@ import { IConfigurationResolverService } from 'vs/workbench/services/configurati
import { ITerminalService, ITerminalInstance, IShellLaunchConfig } from 'vs/workbench/parts/terminal/common/terminal';
import { IOutputService, IOutputChannel } from 'vs/workbench/parts/output/common/output';
import { StartStopProblemCollector, WatchingProblemCollector, ProblemCollectorEvents } from 'vs/workbench/parts/tasks/common/problemCollectors';
import { Task, RevealKind, CommandOptions, ShellConfiguration } from 'vs/workbench/parts/tasks/common/tasks';
import { Task, RevealKind, CommandOptions, ShellConfiguration, CommandType } from 'vs/workbench/parts/tasks/common/tasks';
import {
ITaskSystem, ITaskSummary, ITaskExecuteResult, TaskExecuteKind, TaskError, TaskErrors, ITaskResolver,
TelemetryEvent, Triggers, TaskSystemEvents, TaskEvent, TaskType
......@@ -368,17 +368,19 @@ export class TerminalTaskSystem extends EventEmitter implements ITaskSystem {
let terminalName = nls.localize('TerminalTaskSystem.terminalName', 'Task - {0}', task.name);
let waitOnExit = task.command.terminal.reveal !== RevealKind.Never || !task.isBackground;
let shellLaunchConfig: IShellLaunchConfig = undefined;
if (task.command.isShellCommand) {
let isShellCommand = task.command.type === CommandType.Shell;
if (isShellCommand) {
if (Platform.isWindows && ((options.cwd && TPath.isUNC(options.cwd)) || (!options.cwd && TPath.isUNC(process.cwd())))) {
throw new TaskError(Severity.Error, nls.localize('TerminalTaskSystem', 'Can\'t execute a shell command on an UNC drive.'), TaskErrors.UnknownError);
}
shellLaunchConfig = { name: terminalName, executable: null, args: null, waitOnExit };
let shellSpecified: boolean = false;
if (ShellConfiguration.is(task.command.isShellCommand)) {
shellLaunchConfig.executable = task.command.isShellCommand.executable;
let shellOptions: ShellConfiguration = task.command.options && task.command.options.shell;
if (shellOptions && shellOptions.executable) {
shellLaunchConfig.executable = shellOptions.executable;
shellSpecified = true;
if (task.command.isShellCommand.args) {
shellLaunchConfig.args = task.command.isShellCommand.args.slice();
if (shellOptions.args) {
shellLaunchConfig.args = shellOptions.args.slice();
} else {
shellLaunchConfig.args = [];
}
......@@ -422,7 +424,7 @@ export class TerminalTaskSystem extends EventEmitter implements ITaskSystem {
let cwd = options && options.cwd ? options.cwd : process.cwd();
// On Windows executed process must be described absolute. Since we allowed command without an
// absolute path (e.g. "command": "node") we need to find the executable in the CWD or PATH.
let executable = Platform.isWindows && !task.command.isShellCommand ? this.findExecutable(command, cwd) : command;
let executable = Platform.isWindows && !isShellCommand ? this.findExecutable(command, cwd) : command;
shellLaunchConfig = {
name: terminalName,
executable: executable,
......
......@@ -29,7 +29,7 @@ import { StartStopProblemCollector, WatchingProblemCollector, ProblemCollectorEv
import {
ITaskSystem, ITaskSummary, ITaskExecuteResult, TaskExecuteKind, TaskError, TaskErrors, TelemetryEvent, Triggers, TaskSystemEvents, TaskEvent, TaskType
} from 'vs/workbench/parts/tasks/common/taskSystem';
import { Task, CommandOptions, RevealKind, CommandConfiguration } from 'vs/workbench/parts/tasks/common/tasks';
import { Task, CommandOptions, RevealKind, CommandConfiguration, CommandType } from 'vs/workbench/parts/tasks/common/tasks';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
......@@ -175,7 +175,7 @@ export class ProcessTaskSystem extends EventEmitter implements ITaskSystem {
}
args = this.resolveVariables(args);
let command: string = this.resolveVariable(commandConfig.name);
this.childProcess = new LineProcess(command, args, !!commandConfig.isShellCommand, this.resolveOptions(commandConfig.options));
this.childProcess = new LineProcess(command, args, commandConfig.type === CommandType.Shell, this.resolveOptions(commandConfig.options));
telemetryEvent.command = this.childProcess.getSanitizedCommand();
// we have no problem matchers defined. So show the output log
let reveal = task.command.terminal.reveal;
......
......@@ -96,7 +96,7 @@ class CommandConfigurationBuilder {
this.terminalBuilder = new TerminalBehaviorBuilder(this);
this.result = {
name: command,
isShellCommand: false,
type: Tasks.CommandType.Process,
args: [],
options: {
cwd: '${workspaceRoot}'
......@@ -110,8 +110,8 @@ class CommandConfigurationBuilder {
return this;
}
public shell(value: boolean): CommandConfigurationBuilder {
this.result.isShellCommand = value;
public type(value: Tasks.CommandType): CommandConfigurationBuilder {
this.result.type = value;
return this;
}
......@@ -433,7 +433,7 @@ function assertCommandConfiguration(actual: Tasks.CommandConfiguration, expected
if (actual && expected) {
assertTerminalBehavior(actual.terminal, expected.terminal);
assert.strictEqual(actual.name, expected.name, 'name');
assert.strictEqual(actual.isShellCommand, expected.isShellCommand, 'isShellCommand');
assert.strictEqual(actual.type, expected.type, 'task type');
assert.deepEqual(actual.args, expected.args, 'args');
assert.strictEqual(typeof actual.options, typeof expected.options);
if (actual.options && expected.options) {
......@@ -531,7 +531,7 @@ suite('Tasks Configuration parsing tests', () => {
group(Tasks.TaskGroup.Build).
suppressTaskName(true).
command().
shell(true);
type(Tasks.CommandType.Shell);
testConfiguration(
{
version: '0.1.0',
......@@ -730,7 +730,7 @@ suite('Tasks Configuration parsing tests', () => {
group(Tasks.TaskGroup.Build).
suppressTaskName(true).
command().
shell(true);
type(Tasks.CommandType.Shell);
let external: ExternalTaskRunnerConfiguration = {
version: '0.1.0',
command: 'tsc',
......@@ -1280,7 +1280,7 @@ suite('Tasks Configuration parsing tests', () => {
};
let builder = new ConfiguationBuilder();
builder.task('taskNameOne', 'tsc').suppressTaskName(true).command().
shell(true).args(['arg']).options({ cwd: 'cwd', env: { env: 'env' } });
type(Tasks.CommandType.Shell).args(['arg']).options({ cwd: 'cwd', env: { env: 'env' } });
testConfiguration(external, builder);
});
......@@ -1355,7 +1355,7 @@ suite('Tasks Configuration parsing tests', () => {
]
};
let builder = new ConfiguationBuilder();
builder.task('taskNameOne', 'tsc').command().shell(false);
builder.task('taskNameOne', 'tsc').command().type(Tasks.CommandType.Process);
testConfiguration(external, builder);
});
});
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册