44 *--------------------------------------------------------------------------------------------*/
55'use strict' ;
66
7+ import fs = require( 'fs' ) ;
78import path = require( 'path' ) ;
89
910import * as nls from 'vs/nls' ;
1011import * as Objects from 'vs/base/common/objects' ;
12+ import * as Types from 'vs/base/common/types' ;
1113import { CharCode } from 'vs/base/common/charCode' ;
1214import * as Platform from 'vs/base/common/platform' ;
1315import * as Async from 'vs/base/common/async' ;
@@ -17,6 +19,7 @@ import Severity from 'vs/base/common/severity';
1719import { EventEmitter } from 'vs/base/common/eventEmitter' ;
1820import { IDisposable , dispose } from 'vs/base/common/lifecycle' ;
1921import { TerminateResponse } from 'vs/base/common/processes' ;
22+ import * as TPath from 'vs/base/common/paths' ;
2023
2124import { IMarkerService } from 'vs/platform/markers/common/markers' ;
2225import { ValidationStatus } from 'vs/base/common/parsers' ;
@@ -29,7 +32,7 @@ import { ITerminalService, ITerminalInstance, IShellLaunchConfig } from 'vs/work
2932import { TerminalConfigHelper } from 'vs/workbench/parts/terminal/electron-browser/terminalConfigHelper' ;
3033import { IOutputService , IOutputChannel } from 'vs/workbench/parts/output/common/output' ;
3134import { 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' ;
3336import * as FileConfig from '../node/processRunnerConfiguration' ;
3437
3538interface 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