mirror of
https://github.com/microsoft/vscode.git
synced 2026-07-25 15:35:43 +01:00
Support customiying tasks
This commit is contained in:
@@ -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;
|
||||
}
|
||||
@@ -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,17 +93,36 @@ 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 task type
|
||||
*/
|
||||
type: CommandType;
|
||||
|
||||
/**
|
||||
* The command to execute
|
||||
*/
|
||||
name: string;
|
||||
|
||||
/**
|
||||
* Whether the command is a shell command or not
|
||||
*/
|
||||
isShellCommand: boolean | ShellConfiguration;
|
||||
|
||||
/**
|
||||
* 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);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user