diff --git a/src/vs/workbench/api/node/extHostDebugService.ts b/src/vs/workbench/api/node/extHostDebugService.ts index fd0d9cdcc85fb..7683b5e94786e 100644 --- a/src/vs/workbench/api/node/extHostDebugService.ts +++ b/src/vs/workbench/api/node/extHostDebugService.ts @@ -21,7 +21,7 @@ import { IExtHostWorkspaceProvider } from 'vs/workbench/api/common/extHostWorksp import { ExtHostExtensionService } from 'vs/workbench/api/node/extHostExtensionService'; import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; import { ITerminalSettings, IDebuggerContribution, IConfig, IDebugAdapter, IDebugAdapterServer, IDebugAdapterExecutable, IAdapterDescriptor } from 'vs/workbench/contrib/debug/common/debug'; -import { getTerminalLauncher, hasChildProcesses, prepareCommand } from 'vs/workbench/contrib/debug/node/terminals'; +import { hasChildProcesses, prepareCommand, runInExternalTerminal } from 'vs/workbench/contrib/debug/node/terminals'; import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { AbstractVariableResolverService } from 'vs/workbench/services/configurationResolver/common/variableResolver'; import { ExtHostConfiguration, ExtHostConfigProvider } from '../common/extHostConfiguration'; @@ -357,10 +357,7 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape { } else if (args.kind === 'external') { - const terminalLauncher = getTerminalLauncher(); - if (terminalLauncher) { - return terminalLauncher.runInTerminal(args, config); - } + runInExternalTerminal(args, config); } return Promise.resolve(undefined); } diff --git a/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts b/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts index 317b77e3e78ff..0fc13734752b4 100644 --- a/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts +++ b/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts @@ -21,7 +21,7 @@ import { IFileService } from 'vs/platform/files/common/files'; import { IWorkspaceContextService, IWorkspaceFolder, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ICommandService } from 'vs/platform/commands/common/commands'; -import { IDebugConfigurationProvider, ICompound, IDebugConfiguration, IConfig, IGlobalConfig, IConfigurationManager, ILaunch, IDebugAdapterDescriptorFactory, IDebugAdapter, ITerminalSettings, ITerminalLauncher, IDebugSession, IAdapterDescriptor, CONTEXT_DEBUG_CONFIGURATION_TYPE, IDebugAdapterFactory, IDebugService, IDebugHelperService } from 'vs/workbench/contrib/debug/common/debug'; +import { IDebugConfigurationProvider, ICompound, IDebugConfiguration, IConfig, IGlobalConfig, IConfigurationManager, ILaunch, IDebugAdapterDescriptorFactory, IDebugAdapter, ITerminalSettings, IDebugSession, IAdapterDescriptor, CONTEXT_DEBUG_CONFIGURATION_TYPE, IDebugAdapterFactory, IDebugService } from 'vs/workbench/contrib/debug/common/debug'; import { Debugger } from 'vs/workbench/contrib/debug/common/debugger'; import { IEditorService, ACTIVE_GROUP, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; @@ -52,7 +52,6 @@ export class ConfigurationManager implements IConfigurationManager { private configProviders: IDebugConfigurationProvider[]; private adapterDescriptorFactories: IDebugAdapterDescriptorFactory[]; private debugAdapterFactories = new Map(); - private terminalLauncher: ITerminalLauncher; private debugConfigurationTypeContext: IContextKey; constructor( @@ -66,8 +65,7 @@ export class ConfigurationManager implements IConfigurationManager { @IStorageService private readonly storageService: IStorageService, @ILifecycleService lifecycleService: ILifecycleService, @IExtensionService private readonly extensionService: IExtensionService, - @IContextKeyService contextKeyService: IContextKeyService, - @IDebugHelperService private readonly debugHelperService: IDebugHelperService + @IContextKeyService contextKeyService: IContextKeyService ) { this.configProviders = []; this.adapterDescriptorFactories = []; @@ -111,14 +109,11 @@ export class ConfigurationManager implements IConfigurationManager { } runInTerminal(debugType: string, args: DebugProtocol.RunInTerminalRequestArguments, config: ITerminalSettings): Promise { - let tl: ITerminalLauncher | undefined = this.debugAdapterFactories.get(debugType); - if (!tl) { - if (!this.terminalLauncher) { - this.terminalLauncher = this.debugHelperService.createTerminalLauncher(this.instantiationService); - } - tl = this.terminalLauncher; + let tl = this.debugAdapterFactories.get(debugType); + if (tl) { + return tl.runInTerminal(args, config); } - return tl.runInTerminal(args, config); + return Promise.resolve(void 0); } // debug adapter diff --git a/src/vs/workbench/contrib/debug/browser/debugHelperService.ts b/src/vs/workbench/contrib/debug/browser/debugHelperService.ts index be123b31a5e6e..37d46f87b4559 100644 --- a/src/vs/workbench/contrib/debug/browser/debugHelperService.ts +++ b/src/vs/workbench/contrib/debug/browser/debugHelperService.ts @@ -3,8 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ServiceIdentifier, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { ITerminalLauncher, IDebugHelperService } from 'vs/workbench/contrib/debug/common/debug'; +import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; +import { IDebugHelperService } from 'vs/workbench/contrib/debug/common/debug'; import { TelemetryService } from 'vs/platform/telemetry/common/telemetryService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; @@ -13,10 +13,6 @@ export class BrowserDebugHelperService implements IDebugHelperService { _serviceBrand: ServiceIdentifier; - createTerminalLauncher(instantiationService: IInstantiationService): ITerminalLauncher { - throw new Error('Method createTerminalLauncher not implemented.'); - } - createTelemetryService(configurationService: IConfigurationService, args: string[]): TelemetryService | undefined { return undefined; } diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts index f36d6529d77bb..ab30e1ce3d036 100644 --- a/src/vs/workbench/contrib/debug/common/debug.ts +++ b/src/vs/workbench/contrib/debug/common/debug.ts @@ -8,7 +8,7 @@ import { URI as uri } from 'vs/base/common/uri'; import severity from 'vs/base/common/severity'; import { Event } from 'vs/base/common/event'; import { IJSONSchemaSnippet } from 'vs/base/common/jsonSchema'; -import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { ITextModel as EditorIModel } from 'vs/editor/common/model'; import { IEditor, ITextEditor } from 'vs/workbench/common/editor'; @@ -846,7 +846,5 @@ export const IDebugHelperService = createDecorator(DEBUG_HE export interface IDebugHelperService { _serviceBrand: any; - createTerminalLauncher(instantiationService: IInstantiationService): ITerminalLauncher; - createTelemetryService(configurationService: IConfigurationService, args: string[]): TelemetryService | undefined; } diff --git a/src/vs/workbench/contrib/debug/node/debugHelperService.ts b/src/vs/workbench/contrib/debug/node/debugHelperService.ts index 76fcf6e30e857..29aa2f3156b09 100644 --- a/src/vs/workbench/contrib/debug/node/debugHelperService.ts +++ b/src/vs/workbench/contrib/debug/node/debugHelperService.ts @@ -3,9 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { TerminalLauncher } from 'vs/workbench/contrib/debug/node/terminalSupport'; -import { ITerminalLauncher, IDebugHelperService } from 'vs/workbench/contrib/debug/common/debug'; +import { IDebugHelperService } from 'vs/workbench/contrib/debug/common/debug'; import { Client as TelemetryClient } from 'vs/base/parts/ipc/node/ipc.cp'; import { TelemetryAppenderClient } from 'vs/platform/telemetry/node/telemetryIpc'; import { getPathFromAmdModule } from 'vs/base/common/amd'; @@ -20,10 +18,6 @@ export class NodeDebugHelperService implements IDebugHelperService { ) { } - createTerminalLauncher(instantiationService: IInstantiationService): ITerminalLauncher { - return instantiationService.createInstance(TerminalLauncher); - } - createTelemetryService(configurationService: IConfigurationService, args: string[]): TelemetryService | undefined { const client = new TelemetryClient( diff --git a/src/vs/workbench/contrib/debug/node/terminalSupport.ts b/src/vs/workbench/contrib/debug/node/terminalSupport.ts deleted file mode 100644 index 4692d3021a0be..0000000000000 --- a/src/vs/workbench/contrib/debug/node/terminalSupport.ts +++ /dev/null @@ -1,73 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as nls from 'vs/nls'; -import { IDisposable } from 'vs/base/common/lifecycle'; -import { ITerminalService, ITerminalInstance } from 'vs/workbench/contrib/terminal/common/terminal'; -import { IExternalTerminalService } from 'vs/workbench/contrib/externalTerminal/common/externalTerminal'; -import { ITerminalLauncher, ITerminalSettings } from 'vs/workbench/contrib/debug/common/debug'; -import { hasChildProcesses, prepareCommand } from 'vs/workbench/contrib/debug/node/terminals'; -import { IProcessEnvironment } from 'vs/base/common/platform'; - -export class TerminalLauncher implements ITerminalLauncher { - - private integratedTerminalInstance: ITerminalInstance | undefined; - private terminalDisposedListener: IDisposable; - - constructor( - @ITerminalService private readonly terminalService: ITerminalService, - @IExternalTerminalService private readonly externalTerminalService: IExternalTerminalService - ) { - } - - runInTerminal(args: DebugProtocol.RunInTerminalRequestArguments, config: ITerminalSettings): Promise { - - if (args.kind === 'external') { - return this.externalTerminalService.runInTerminal(args.title || '', args.cwd, args.args, args.env || {}); - } - - if (!this.terminalDisposedListener) { - // React on terminal disposed and check if that is the debug terminal #12956 - this.terminalDisposedListener = this.terminalService.onInstanceDisposed(terminal => { - if (this.integratedTerminalInstance && this.integratedTerminalInstance.id === terminal.id) { - this.integratedTerminalInstance = undefined; - } - }); - } - - let t = this.integratedTerminalInstance; - if ((t && (typeof t.processId === 'number') && hasChildProcesses(t.processId)) || !t) { - t = this.terminalService.createTerminal({ name: args.title || nls.localize('debug.terminal.title', "debuggee") }); - this.integratedTerminalInstance = t; - } - this.terminalService.setActiveInstance(t); - this.terminalService.showPanel(true); - - return new Promise((resolve, error) => { - if (t && typeof t.processId === 'number') { - // no need to wait - resolve(t.processId); - } - - // shell not ready: wait for ready event - const toDispose = t!.onProcessIdReady(t => { - toDispose.dispose(); - resolve(t.processId); - }); - - // do not wait longer than 5 seconds - setTimeout(_ => { - error(new Error('terminal shell timeout')); - }, 5000); - - }).then(shellProcessId => { - - const command = prepareCommand(args, config); - t!.sendText(command, true); - - return shellProcessId; - }); - } -} diff --git a/src/vs/workbench/contrib/debug/node/terminals.ts b/src/vs/workbench/contrib/debug/node/terminals.ts index da3bfa8e8abc2..1c74432574d9a 100644 --- a/src/vs/workbench/contrib/debug/node/terminals.ts +++ b/src/vs/workbench/contrib/debug/node/terminals.ts @@ -4,277 +4,30 @@ *--------------------------------------------------------------------------------------------*/ import * as cp from 'child_process'; -import * as nls from 'vs/nls'; import * as env from 'vs/base/common/platform'; -import * as pfs from 'vs/base/node/pfs'; -import { assign } from 'vs/base/common/objects'; -import { ITerminalLauncher, ITerminalSettings } from 'vs/workbench/contrib/debug/common/debug'; -import { getPathFromAmdModule } from 'vs/base/common/amd'; +import { ITerminalSettings } from 'vs/workbench/contrib/debug/common/debug'; import { getSystemShell } from 'vs/workbench/contrib/terminal/node/terminal'; +import { WindowsExternalTerminalService, MacExternalTerminalService, LinuxExternalTerminalService } from 'vs/workbench/contrib/externalTerminal/node/externalTerminalService'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IExternalTerminalService } from 'vs/workbench/contrib/externalTerminal/common/externalTerminal'; -const TERMINAL_TITLE = nls.localize('console.title', "VS Code Console"); +let externalTerminalService: IExternalTerminalService | undefined = undefined; -let terminalLauncher: ITerminalLauncher | undefined = undefined; - -export function getTerminalLauncher() { - if (!terminalLauncher) { +export function runInExternalTerminal(args: DebugProtocol.RunInTerminalRequestArguments, config: ITerminalSettings): void { + if (!externalTerminalService) { if (env.isWindows) { - terminalLauncher = new WinTerminalService(); + externalTerminalService = new WindowsExternalTerminalService(undefined); } else if (env.isMacintosh) { - terminalLauncher = new MacTerminalService(); + externalTerminalService = new MacExternalTerminalService(undefined); } else if (env.isLinux) { - terminalLauncher = new LinuxTerminalService(); + externalTerminalService = new LinuxExternalTerminalService(undefined); } } - return terminalLauncher; -} - -let _DEFAULT_TERMINAL_LINUX_READY: Promise | null = null; - -export function getDefaultTerminalLinuxReady(): Promise { - if (!_DEFAULT_TERMINAL_LINUX_READY) { - _DEFAULT_TERMINAL_LINUX_READY = new Promise(resolve => { - if (env.isLinux) { - Promise.all([pfs.exists('/etc/debian_version'), process.lazyEnv]).then(([isDebian]) => { - if (isDebian) { - resolve('x-terminal-emulator'); - } else if (process.env.DESKTOP_SESSION === 'gnome' || process.env.DESKTOP_SESSION === 'gnome-classic') { - resolve('gnome-terminal'); - } else if (process.env.DESKTOP_SESSION === 'kde-plasma') { - resolve('konsole'); - } else if (process.env.COLORTERM) { - resolve(process.env.COLORTERM); - } else if (process.env.TERM) { - resolve(process.env.TERM); - } else { - resolve('xterm'); - } - }); - return; - } - - resolve('xterm'); - }); - } - return _DEFAULT_TERMINAL_LINUX_READY; -} - -let _DEFAULT_TERMINAL_WINDOWS: string | null = null; - -export function getDefaultTerminalWindows(): string { - if (!_DEFAULT_TERMINAL_WINDOWS) { - const isWoW64 = !!process.env.hasOwnProperty('PROCESSOR_ARCHITEW6432'); - _DEFAULT_TERMINAL_WINDOWS = `${process.env.windir ? process.env.windir : 'C:\\Windows'}\\${isWoW64 ? 'Sysnative' : 'System32'}\\cmd.exe`; - } - return _DEFAULT_TERMINAL_WINDOWS; -} - -abstract class TerminalLauncher implements ITerminalLauncher { - runInTerminal(args: DebugProtocol.RunInTerminalRequestArguments, config: ITerminalSettings): Promise { - return this.runInTerminal0(args.title!, args.cwd, args.args, args.env || {}, config); - } - - abstract runInTerminal0(title: string, dir: string, args: string[], envVars: env.IProcessEnvironment | {}, config: ITerminalSettings): Promise; -} - -class WinTerminalService extends TerminalLauncher { - - private static readonly CMD = 'cmd.exe'; - - runInTerminal0(title: string, dir: string, args: string[], envVars: env.IProcessEnvironment, configuration: ITerminalSettings): Promise { - - const exec = configuration.external.windowsExec || getDefaultTerminalWindows(); - - return new Promise((resolve, reject) => { - - const title = `"${dir} - ${TERMINAL_TITLE}"`; - const command = `""${args.join('" "')}" & pause"`; // use '|' to only pause on non-zero exit code - - const cmdArgs = [ - '/c', 'start', title, '/wait', exec, '/c', command - ]; - - // merge environment variables into a copy of the process.env - const env = assign({}, process.env, envVars); - - // delete environment variables that have a null value - Object.keys(env).filter(v => env[v] === null).forEach(key => delete env[key]); - - const options: any = { - cwd: dir, - env: env, - windowsVerbatimArguments: true - }; - - const cmd = cp.spawn(WinTerminalService.CMD, cmdArgs, options); - cmd.on('error', err => { - reject(improveError(err)); - }); - - resolve(undefined); - }); - } -} - -class MacTerminalService extends TerminalLauncher { - - private static readonly DEFAULT_TERMINAL_OSX = 'Terminal.app'; - private static readonly OSASCRIPT = '/usr/bin/osascript'; // osascript is the AppleScript interpreter on OS X - - runInTerminal0(title: string, dir: string, args: string[], envVars: env.IProcessEnvironment, configuration: ITerminalSettings): Promise { - - const terminalApp = configuration.external.osxExec || MacTerminalService.DEFAULT_TERMINAL_OSX; - - return new Promise((resolve, reject) => { - - if (terminalApp === MacTerminalService.DEFAULT_TERMINAL_OSX || terminalApp === 'iTerm.app') { - - // On OS X we launch an AppleScript that creates (or reuses) a Terminal window - // and then launches the program inside that window. - - const script = terminalApp === MacTerminalService.DEFAULT_TERMINAL_OSX ? 'TerminalHelper' : 'iTermHelper'; - const scriptpath = getPathFromAmdModule(require, `vs/workbench/contrib/externalTerminal/node/${script}.scpt`); - - const osaArgs = [ - scriptpath, - '-t', title || TERMINAL_TITLE, - '-w', dir, - ]; - - for (let a of args) { - osaArgs.push('-a'); - osaArgs.push(a); - } - - if (envVars) { - for (let key in envVars) { - const value = envVars[key]; - if (value === null) { - osaArgs.push('-u'); - osaArgs.push(key); - } else { - osaArgs.push('-e'); - osaArgs.push(`${key}=${value}`); - } - } - } - - let stderr = ''; - const osa = cp.spawn(MacTerminalService.OSASCRIPT, osaArgs); - osa.on('error', err => { - reject(improveError(err)); - }); - osa.stderr.on('data', (data) => { - stderr += data.toString(); - }); - osa.on('exit', (code: number) => { - if (code === 0) { // OK - resolve(undefined); - } else { - if (stderr) { - const lines = stderr.split('\n', 1); - reject(new Error(lines[0])); - } else { - reject(new Error(nls.localize('mac.terminal.script.failed', "Script '{0}' failed with exit code {1}", script, code))); - } - } - }); - } else { - reject(new Error(nls.localize('mac.terminal.type.not.supported', "'{0}' not supported", terminalApp))); - } - }); - } -} - -class LinuxTerminalService extends TerminalLauncher { - - private static readonly WAIT_MESSAGE = nls.localize('press.any.key', "Press any key to continue..."); - - runInTerminal0(title: string, dir: string, args: string[], envVars: env.IProcessEnvironment, configuration: ITerminalSettings): Promise { - - const terminalConfig = configuration.external; - const execThenable: Promise = terminalConfig.linuxExec ? Promise.resolve(terminalConfig.linuxExec) : getDefaultTerminalLinuxReady(); - - return new Promise((resolve, reject) => { - - let termArgs: string[] = []; - //termArgs.push('--title'); - //termArgs.push(`"${TERMINAL_TITLE}"`); - execThenable.then(exec => { - if (exec.indexOf('gnome-terminal') >= 0) { - termArgs.push('-x'); - } else { - termArgs.push('-e'); - } - termArgs.push('bash'); - termArgs.push('-c'); - - const bashCommand = `${quote(args)}; echo; read -p "${LinuxTerminalService.WAIT_MESSAGE}" -n1;`; - termArgs.push(`''${bashCommand}''`); // wrapping argument in two sets of ' because node is so "friendly" that it removes one set... - - // merge environment variables into a copy of the process.env - const env = assign({}, process.env, envVars); - - // delete environment variables that have a null value - Object.keys(env).filter(v => env[v] === null).forEach(key => delete env[key]); - - const options: any = { - cwd: dir, - env: env - }; - - let stderr = ''; - const cmd = cp.spawn(exec, termArgs, options); - cmd.on('error', err => { - reject(improveError(err)); - }); - cmd.stderr.on('data', (data) => { - stderr += data.toString(); - }); - cmd.on('exit', (code: number) => { - if (code === 0) { // OK - resolve(undefined); - } else { - if (stderr) { - const lines = stderr.split('\n', 1); - reject(new Error(lines[0])); - } else { - reject(new Error(nls.localize('linux.term.failed', "'{0}' failed with exit code {1}", exec, code))); - } - } - }); - }); - }); - } -} - -/** - * tries to turn OS errors into more meaningful error messages - */ -function improveError(err: Error): Error { - if (err['errno'] === 'ENOENT' && err['path']) { - return new Error(nls.localize('ext.term.app.not.found', "can't find terminal application '{0}'", err['path'])); + if (externalTerminalService) { + externalTerminalService.runInTerminal(args.title!, args.cwd, args.args, args.env || {}, config); } - return err; } -/** - * Quote args if necessary and combine into a space separated string. - */ -function quote(args: string[]): string { - let r = ''; - for (let a of args) { - if (a.indexOf(' ') >= 0) { - r += '"' + a + '"'; - } else { - r += a; - } - r += ' '; - } - return r; -} - - export function hasChildProcesses(processId: number): boolean { if (processId) { try { diff --git a/src/vs/workbench/contrib/externalTerminal/common/externalTerminal.ts b/src/vs/workbench/contrib/externalTerminal/common/externalTerminal.ts index 8b7f051b339d2..54980485f491e 100644 --- a/src/vs/workbench/contrib/externalTerminal/common/externalTerminal.ts +++ b/src/vs/workbench/contrib/externalTerminal/common/externalTerminal.ts @@ -4,14 +4,14 @@ *--------------------------------------------------------------------------------------------*/ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IProcessEnvironment } from 'vs/base/common/platform'; +import { ITerminalSettings } from 'vs/workbench/contrib/debug/common/debug'; export const IExternalTerminalService = createDecorator('nativeTerminalService'); export interface IExternalTerminalService { _serviceBrand: any; openTerminal(path: string): void; - runInTerminal(title: string, cwd: string, args: string[], env: IProcessEnvironment): Promise; + runInTerminal(title: string, cwd: string, args: string[], env: { [key: string]: string | null; }, configuration: ITerminalSettings): Promise; } export interface IExternalTerminalConfiguration { diff --git a/src/vs/workbench/contrib/externalTerminal/node/externalTerminalService.ts b/src/vs/workbench/contrib/externalTerminal/node/externalTerminalService.ts index 64bd1ddda5e1a..38f4bc89d92f0 100644 --- a/src/vs/workbench/contrib/externalTerminal/node/externalTerminalService.ts +++ b/src/vs/workbench/contrib/externalTerminal/node/externalTerminalService.ts @@ -16,11 +16,11 @@ import { getPathFromAmdModule } from 'vs/base/common/amd'; import { IConfigurationRegistry, Extensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { Registry } from 'vs/platform/registry/common/platform'; +import { ITerminalSettings } from 'vs/workbench/contrib/debug/common/debug'; -export const DEFAULT_TERMINAL_OSX = 'Terminal.app'; const TERMINAL_TITLE = nls.localize('console.title', "VS Code Console"); - +export const DEFAULT_TERMINAL_OSX = 'Terminal.app'; enum WinSpawnType { CMD, @@ -43,13 +43,18 @@ export class WindowsExternalTerminalService implements IExternalTerminalService this.spawnTerminal(cp, configuration, processes.getWindowsShell(), cwd); } + /* public runInTerminal(title: string, dir: string, args: string[], envVars: env.IProcessEnvironment): Promise { - const configuration = this._configurationService.getValue(); - const terminalConfig = configuration.terminal.external; - const exec = terminalConfig.windowsExec || getDefaultTerminalWindows(); + return this.runInTerminal0(title, dir, args, envVars, configuration.terminal); + } + */ + + public runInTerminal(title: string, dir: string, args: string[], envVars: env.IProcessEnvironment, configuration: ITerminalSettings): Promise { - return new Promise((c, e) => { + const exec = configuration.external.windowsExec || getDefaultTerminalWindows(); + + return new Promise((resolve, reject) => { const title = `"${dir} - ${TERMINAL_TITLE}"`; const command = `""${args.join('" "')}" & pause"`; // use '|' to only pause on non-zero exit code @@ -71,9 +76,11 @@ export class WindowsExternalTerminalService implements IExternalTerminalService }; const cmd = cp.spawn(WindowsExternalTerminalService.CMD, cmdArgs, options); - cmd.on('error', e); + cmd.on('error', err => { + reject(improveError(err)); + }); - c(undefined); + resolve(undefined); }); } @@ -134,13 +141,18 @@ export class MacExternalTerminalService implements IExternalTerminalService { this.spawnTerminal(cp, configuration, cwd); } + /* public runInTerminal(title: string, dir: string, args: string[], envVars: env.IProcessEnvironment): Promise { - const configuration = this._configurationService.getValue(); - const terminalConfig = configuration.terminal.external; - const terminalApp = terminalConfig.osxExec || DEFAULT_TERMINAL_OSX; + return this.runInTerminal0(title, dir, args, envVars, configuration.terminal); + } + */ + + public runInTerminal(title: string, dir: string, args: string[], envVars: env.IProcessEnvironment, configuration: ITerminalSettings): Promise { - return new Promise((c, e) => { + const terminalApp = configuration.external.osxExec || DEFAULT_TERMINAL_OSX; + + return new Promise((resolve, reject) => { if (terminalApp === DEFAULT_TERMINAL_OSX || terminalApp === 'iTerm.app') { @@ -176,24 +188,26 @@ export class MacExternalTerminalService implements IExternalTerminalService { let stderr = ''; const osa = cp.spawn(MacExternalTerminalService.OSASCRIPT, osaArgs); - osa.on('error', e); + osa.on('error', err => { + reject(improveError(err)); + }); osa.stderr.on('data', (data) => { stderr += data.toString(); }); osa.on('exit', (code: number) => { if (code === 0) { // OK - c(undefined); + resolve(undefined); } else { if (stderr) { const lines = stderr.split('\n', 1); - e(new Error(lines[0])); + reject(new Error(lines[0])); } else { - e(new Error(nls.localize('mac.terminal.script.failed', "Script '{0}' failed with exit code {1}", script, code))); + reject(new Error(nls.localize('mac.terminal.script.failed', "Script '{0}' failed with exit code {1}", script, code))); } } }); } else { - e(new Error(nls.localize('mac.terminal.type.not.supported', "'{0}' not supported", terminalApp))); + reject(new Error(nls.localize('mac.terminal.type.not.supported', "'{0}' not supported", terminalApp))); } }); } @@ -223,20 +237,25 @@ export class LinuxExternalTerminalService implements IExternalTerminalService { @IConfigurationService private readonly _configurationService: IConfigurationService ) { } - public openTerminal(cwd?: string): void { const configuration = this._configurationService.getValue(); this.spawnTerminal(cp, configuration, cwd); } + /* public runInTerminal(title: string, dir: string, args: string[], envVars: env.IProcessEnvironment): Promise { - const configuration = this._configurationService.getValue(); - const terminalConfig = configuration.terminal.external; + return this.runInTerminal0(title, dir, args, envVars, configuration.terminal); + } + */ + + public runInTerminal(title: string, dir: string, args: string[], envVars: env.IProcessEnvironment, configuration: ITerminalSettings): Promise { + + const terminalConfig = configuration.external; const execPromise = terminalConfig.linuxExec ? Promise.resolve(terminalConfig.linuxExec) : getDefaultTerminalLinuxReady(); - return new Promise((c, e) => { + return new Promise((resolve, reject) => { let termArgs: string[] = []; //termArgs.push('--title'); @@ -266,19 +285,21 @@ export class LinuxExternalTerminalService implements IExternalTerminalService { let stderr = ''; const cmd = cp.spawn(exec, termArgs, options); - cmd.on('error', e); + cmd.on('error', err => { + reject(improveError(err)); + }); cmd.stderr.on('data', (data) => { stderr += data.toString(); }); cmd.on('exit', (code: number) => { if (code === 0) { // OK - c(undefined); + resolve(undefined); } else { if (stderr) { const lines = stderr.split('\n', 1); - e(new Error(lines[0])); + reject(new Error(lines[0])); } else { - e(new Error(nls.localize('linux.term.failed', "'{0}' failed with exit code {1}", exec, code))); + reject(new Error(nls.localize('linux.term.failed', "'{0}' failed with exit code {1}", exec, code))); } } }); @@ -301,6 +322,16 @@ export class LinuxExternalTerminalService implements IExternalTerminalService { } } +/** + * tries to turn OS errors into more meaningful error messages + */ +function improveError(err: Error): Error { + if (err['errno'] === 'ENOENT' && err['path']) { + return new Error(nls.localize('ext.term.app.not.found', "can't find terminal application '{0}'", err['path'])); + } + return err; +} + /** * Quote args if necessary and combine into a space separated string. */