Skip to content

Commit bd96c22

Browse files
committed
More feature parity when running tasks in terminal
1 parent 64be660 commit bd96c22

1 file changed

Lines changed: 82 additions & 4 deletions

File tree

src/vs/workbench/parts/tasks/electron-browser/terminalTaskSystem.ts

Lines changed: 82 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@
44
*--------------------------------------------------------------------------------------------*/
55
'use strict';
66

7+
import fs = require('fs');
78
import path = require('path');
89

910
import * as nls from 'vs/nls';
1011
import * as Objects from 'vs/base/common/objects';
12+
import * as Types from 'vs/base/common/types';
1113
import { CharCode } from 'vs/base/common/charCode';
1214
import * as Platform from 'vs/base/common/platform';
1315
import * as Async from 'vs/base/common/async';
@@ -17,6 +19,7 @@ import Severity from 'vs/base/common/severity';
1719
import { EventEmitter } from 'vs/base/common/eventEmitter';
1820
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
1921
import { TerminateResponse } from 'vs/base/common/processes';
22+
import * as TPath from 'vs/base/common/paths';
2023

2124
import { IMarkerService } from 'vs/platform/markers/common/markers';
2225
import { ValidationStatus } from 'vs/base/common/parsers';
@@ -29,7 +32,7 @@ import { ITerminalService, ITerminalInstance, IShellLaunchConfig } from 'vs/work
2932
import { TerminalConfigHelper } from 'vs/workbench/parts/terminal/electron-browser/terminalConfigHelper';
3033
import { IOutputService, IOutputChannel } from 'vs/workbench/parts/output/common/output';
3134
import { StartStopProblemCollector, WatchingProblemCollector, ProblemCollectorEvents } from 'vs/workbench/parts/tasks/common/problemCollectors';
32-
import { ITaskSystem, ITaskSummary, ITaskExecuteResult, TaskExecuteKind, TaskError, TaskErrors, TaskRunnerConfiguration, TaskDescription, ShowOutput, TelemetryEvent, Triggers, TaskSystemEvents, TaskEvent, TaskType } from 'vs/workbench/parts/tasks/common/taskSystem';
35+
import { ITaskSystem, ITaskSummary, ITaskExecuteResult, TaskExecuteKind, TaskError, TaskErrors, TaskRunnerConfiguration, TaskDescription, ShowOutput, TelemetryEvent, Triggers, TaskSystemEvents, TaskEvent, TaskType, CommandOptions } from 'vs/workbench/parts/tasks/common/taskSystem';
3336
import * as FileConfig from '../node/processRunnerConfiguration';
3437

3538
interface TerminalData {
@@ -285,6 +288,7 @@ export class TerminalTaskSystem extends EventEmitter implements ITaskSystem {
285288
startStopProblemMatcher.dispose();
286289
this.emit(TaskSystemEvents.Inactive, event);
287290
delete this.activeTasks[task.id];
291+
terminal.reuseTerminal();
288292
resolve({ exitCode });
289293
});
290294
this.terminalService.setActiveInstance(terminal);
@@ -303,13 +307,28 @@ export class TerminalTaskSystem extends EventEmitter implements ITaskSystem {
303307
}
304308

305309
private createTerminal(task: TaskDescription): ITerminalInstance {
310+
let options = this.resolveOptions(this.configuration.options);
306311
let { command, args } = this.resolveCommandAndArgs(task);
307312
let terminalName = nls.localize('TerminalTaskSystem.terminalName', 'Task - {0}', task.name);
308313
let waitOnExit = task.showOutput !== ShowOutput.Never || !task.isBackground;
309314
if (this.configuration.isShellCommand) {
310-
// TODO@dirk: don't we want to use cmd.exe (32- or 64-bit) all the time? Also you can now
311-
// not set IShellLaunchConfig.executable which will grab it from settings.
315+
if (Platform.isWindows && ((options.cwd && TPath.isUNC(options.cwd)) || (!options.cwd && TPath.isUNC(process.cwd())))) {
316+
throw new TaskError(Severity.Error, nls.localize('TerminalTaskSystem', 'Can\'t execute a shell command on an UNC drive.'), TaskErrors.UnknownError);
317+
}
312318
let shellConfig: IShellLaunchConfig = { executable: null, args: null };
319+
if (options.cwd) {
320+
shellConfig.cwd = options.cwd;
321+
}
322+
if (options.env) {
323+
let env: IStringDictionary<string> = Object.create(null);
324+
Object.keys(process.env).forEach((key) => {
325+
env[key] = process.env[key];
326+
});
327+
Object.keys(options.env).forEach((key) => {
328+
env[key] = options.env[key];
329+
});
330+
shellConfig.env = env;
331+
}
313332
(this.terminalService.configHelper as TerminalConfigHelper).mergeDefaultShellPathAndArgs(shellConfig);
314333
let shellArgs = shellConfig.args.slice(0);
315334
let toAdd: string[] = [];
@@ -360,9 +379,13 @@ export class TerminalTaskSystem extends EventEmitter implements ITaskSystem {
360379
};
361380
return this.terminalService.createInstance(shellLaunchConfig);
362381
} else {
382+
let cwd = options && options.cwd ? options.cwd : process.cwd();
383+
// On Windows executed process must be described absolute. Since we allowed command without an
384+
// absolute path (e.g. "command": "node") we need to find the executable in the CWD or PATH.
385+
let executable = Platform.isWindows && !this.configuration.isShellCommand ? this.findExecutable(command, cwd) : command;
363386
const shellLaunchConfig: IShellLaunchConfig = {
364387
name: terminalName,
365-
executable: command,
388+
executable: executable,
366389
args,
367390
waitOnExit
368391
};
@@ -389,6 +412,44 @@ export class TerminalTaskSystem extends EventEmitter implements ITaskSystem {
389412
return { command, args };
390413
}
391414

415+
private findExecutable(command: string, cwd: string): string {
416+
// If we have an absolute path then we take it.
417+
if (path.isAbsolute(command)) {
418+
return command;
419+
}
420+
let dir = path.dirname(command);
421+
if (dir !== '.') {
422+
// We have a directory. So leave the command as is.
423+
return command;
424+
}
425+
// We have a simple file name. We get the path variable from the env
426+
// and try to find the executable on the path.
427+
if (!process.env.PATH) {
428+
return command;
429+
}
430+
let paths: string[] = (process.env.PATH as string).split(path.delimiter);
431+
for (let pathEntry of paths) {
432+
// The path entry is absolute.
433+
let fullPath: string;
434+
if (path.isAbsolute(pathEntry)) {
435+
fullPath = path.join(pathEntry, command);
436+
} else {
437+
fullPath = path.join(cwd, pathEntry, command);
438+
}
439+
if (fs.existsSync(fullPath)) {
440+
return fullPath;
441+
}
442+
let withExtension = fullPath + '.com';
443+
if (fs.existsSync(withExtension)) {
444+
return withExtension;
445+
}
446+
withExtension = fullPath + '.exe';
447+
if (fs.existsSync(withExtension)) {
448+
return withExtension;
449+
}
450+
}
451+
return command;
452+
}
392453

393454
private resolveVariables(value: string[]): string[] {
394455
return value.map(s => this.resolveVariable(s));
@@ -415,6 +476,22 @@ export class TerminalTaskSystem extends EventEmitter implements ITaskSystem {
415476
return this.configurationResolverService.resolve(value);
416477
}
417478

479+
private resolveOptions(options: CommandOptions): CommandOptions {
480+
let result: CommandOptions = { cwd: this.resolveVariable(options.cwd) };
481+
if (options.env) {
482+
result.env = Object.create(null);
483+
Object.keys(options.env).forEach((key) => {
484+
let value: any = options.env[key];
485+
if (Types.isString(value)) {
486+
result.env[key] = this.resolveVariable(value);
487+
} else {
488+
result.env[key] = value.toString();
489+
}
490+
});
491+
}
492+
return result;
493+
}
494+
418495
private static doubleQuotes = /^[^"].* .*[^"]$/;
419496
private ensureDoubleQuotes(value: string) {
420497
if (TerminalTaskSystem.doubleQuotes.test(value)) {
@@ -450,6 +527,7 @@ export class TerminalTaskSystem extends EventEmitter implements ITaskSystem {
450527
'tsc': true,
451528
'xbuild': true
452529
};
530+
453531
public getSanitizedCommand(cmd: string): string {
454532
let result = cmd.toLowerCase();
455533
let index = result.lastIndexOf(path.sep);

0 commit comments

Comments
 (0)