diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index 817521450c396..33044f955f688 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -3497,9 +3497,10 @@ declare namespace vscode { * [Terminal.show](#Terminal.show) in order to show the terminal panel. * * @param name Optional human-readable string which will be used to represent the terminal in the UI. + * @param shellPath Optional path to a custom shell executable to be used in the terminal. * @return A new Terminal. */ - export function createTerminal(name?: string): Terminal; + export function createTerminal(name?: string, shellPath?: string): Terminal; } /** diff --git a/src/vs/workbench/api/node/extHost.api.impl.ts b/src/vs/workbench/api/node/extHost.api.impl.ts index 60c3d773de3b1..0533d77c825f4 100644 --- a/src/vs/workbench/api/node/extHost.api.impl.ts +++ b/src/vs/workbench/api/node/extHost.api.impl.ts @@ -260,8 +260,8 @@ export class ExtHostAPIImplementation { createOutputChannel(name: string): vscode.OutputChannel { return extHostOutputService.createOutputChannel(name); }, - createTerminal(name?: string): vscode.Terminal { - return extHostTerminalService.createTerminal(name); + createTerminal(name?: string, shellPath?: string): vscode.Terminal { + return extHostTerminalService.createTerminal(name, shellPath); } }; diff --git a/src/vs/workbench/api/node/extHost.protocol.ts b/src/vs/workbench/api/node/extHost.protocol.ts index a2031bbce9aee..dc6228bb4dde0 100644 --- a/src/vs/workbench/api/node/extHost.protocol.ts +++ b/src/vs/workbench/api/node/extHost.protocol.ts @@ -153,7 +153,7 @@ export abstract class MainThreadOutputServiceShape { } export abstract class MainThreadTerminalServiceShape { - $createTerminal(name?: string): TPromise { throw ni(); } + $createTerminal(name?: string, shellPath?: string): TPromise { throw ni(); } $dispose(terminalId: number): void { throw ni(); } $hide(terminalId: number): void { throw ni(); } $sendText(terminalId: number, text: string, addNewLine: boolean): void { throw ni(); } diff --git a/src/vs/workbench/api/node/extHostTerminalService.ts b/src/vs/workbench/api/node/extHostTerminalService.ts index e1dead17d1b98..7138051a741c8 100644 --- a/src/vs/workbench/api/node/extHostTerminalService.ts +++ b/src/vs/workbench/api/node/extHostTerminalService.ts @@ -11,16 +11,18 @@ import {MainContext, MainThreadTerminalServiceShape} from './extHost.protocol'; export class ExtHostTerminal implements vscode.Terminal { public _name: string; + public _shellPath: string; private _id: number; private _proxy: MainThreadTerminalServiceShape; private _disposed: boolean; private _queuedRequests: ApiRequest[] = []; - constructor(proxy: MainThreadTerminalServiceShape, id: number, name?: string) { + constructor(proxy: MainThreadTerminalServiceShape, id: number, name?: string, shellPath?: string) { this._name = name; + this._shellPath = shellPath; this._proxy = proxy; - this._proxy.$createTerminal(name).then((terminalId) => { + this._proxy.$createTerminal(name, shellPath).then((terminalId) => { this._id = terminalId; this._queuedRequests.forEach((r) => { r.run(this._proxy, this._id); @@ -85,8 +87,8 @@ export class ExtHostTerminalService { this._proxy = threadService.get(MainContext.MainThreadTerminalService); } - public createTerminal(name?: string): vscode.Terminal { - return new ExtHostTerminal(this._proxy, -1, name); + public createTerminal(name?: string, shellPath?: string): vscode.Terminal { + return new ExtHostTerminal(this._proxy, -1, name, shellPath); } } diff --git a/src/vs/workbench/api/node/mainThreadTerminalService.ts b/src/vs/workbench/api/node/mainThreadTerminalService.ts index ae6d299c0270a..c45ffd111fbb1 100644 --- a/src/vs/workbench/api/node/mainThreadTerminalService.ts +++ b/src/vs/workbench/api/node/mainThreadTerminalService.ts @@ -19,8 +19,8 @@ export class MainThreadTerminalService extends MainThreadTerminalServiceShape { this._terminalService = terminalService; } - public $createTerminal(name?: string): TPromise { - return this._terminalService.createNew(name); + public $createTerminal(name?: string, shellPath?: string): TPromise { + return this._terminalService.createNew(name, shellPath); } public $show(terminalId: number, preserveFocus: boolean): void { diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminal.ts b/src/vs/workbench/parts/terminal/electron-browser/terminal.ts index 4ea36de635706..360c7136f3bb1 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminal.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminal.ts @@ -65,7 +65,7 @@ export interface ITerminalService { close(): TPromise; copySelection(): TPromise; - createNew(name?: string): TPromise; + createNew(name?: string, shellPath?: string): TPromise; focusNext(): TPromise; focusPrevious(): TPromise; hide(): TPromise; diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalService.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalService.ts index 11cdcefdaa033..7ec7be3c98f5c 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalService.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalService.ts @@ -30,7 +30,7 @@ export class TerminalService implements ITerminalService { private activeTerminalIndex: number = 0; private terminalProcesses: ITerminalProcess[] = []; - private nextTerminalName: string; + private _nextTerminalProcessConfiguration: ITerminalProcessConfiguration = { name: null, shell: { executable: '', args: [] } }; protected _terminalFocusContextKey: IContextKey; private configHelper: TerminalConfigHelper; @@ -180,7 +180,7 @@ export class TerminalService implements ITerminalService { return this.focus(); } - public createNew(name?: string): TPromise { + public createNew(name?: string, shellPath?: string): TPromise { let processCount = this.terminalProcesses.length; // When there are 0 processes it means that the panel is not yet created, so the name needs @@ -189,12 +189,22 @@ export class TerminalService implements ITerminalService { // the TerminalPanel is restored on launch if it was open previously. if (processCount === 0 && !name) { - name = this.nextTerminalName; - this.nextTerminalName = undefined; + name = this._nextTerminalProcessConfiguration.name; + shellPath = this._nextTerminalProcessConfiguration.shell.executable; + this._nextTerminalProcessConfiguration = undefined; } else { - this.nextTerminalName = name; + this._nextTerminalProcessConfiguration.name = name; + this._nextTerminalProcessConfiguration.shell.executable = shellPath; } + // If user doesn't specify a shell, set it to null and let terminalConfigHelper to getShell() + // later. + // Todo: Also let user specify shell args + const terminalProcessConfiguration: ITerminalProcessConfiguration = { + name: name ? name : '', + shell: shellPath ? { executable: shellPath, args: [] } : null + }; + return this.focus().then((terminalPanel) => { // If the terminal panel has not been initialized yet skip this, the terminal will be // created via a call from TerminalPanel.setVisible @@ -210,7 +220,7 @@ export class TerminalService implements ITerminalService { } this.initConfigHelper(terminalPanel.getContainer()); - return terminalPanel.createNewTerminalInstance(this.createTerminalProcess(name), this._terminalFocusContextKey).then((terminalId) => { + return terminalPanel.createNewTerminalInstance(this.createTerminalProcess(terminalProcessConfiguration), this._terminalFocusContextKey).then((terminalId) => { this._onInstancesChanged.fire(); return TPromise.as(terminalId); }); @@ -291,11 +301,12 @@ export class TerminalService implements ITerminalService { } } - private createTerminalProcess(name?: string): ITerminalProcess { - let locale = this.configHelper.isSetLocaleVariables() ? platform.locale : undefined; - let env = TerminalService.createTerminalEnv(process.env, this.configHelper.getShell(), this.contextService.getWorkspace(), locale); - let terminalProcess = { - title: name ? name : '', + private createTerminalProcess(terminalProcessConfiguration: ITerminalProcessConfiguration): ITerminalProcess { + const locale = this.configHelper.isSetLocaleVariables() ? platform.locale : undefined; + const shell = terminalProcessConfiguration.shell ? terminalProcessConfiguration.shell : this.configHelper.getShell(); + const env = TerminalService.createTerminalEnv(process.env, shell, this.contextService.getWorkspace(), locale); + const terminalProcess = { + title: terminalProcessConfiguration.name, process: cp.fork('./terminalProcess', [], { env: env, cwd: URI.parse(path.dirname(require.toUrl('./terminalProcess'))).fsPath @@ -305,7 +316,7 @@ export class TerminalService implements ITerminalService { this._onInstancesChanged.fire(); this.activeTerminalIndex = this.terminalProcesses.length - 1; this._onActiveInstanceChanged.fire(); - if (!name) { + if (!terminalProcessConfiguration.name) { // Only listen for process title changes when a name is not provided terminalProcess.process.on('message', (message) => { if (message.type === 'title') { @@ -347,4 +358,9 @@ export class TerminalService implements ITerminalService { } return parts.join('_') + '.UTF-8'; } +} + +export interface ITerminalProcessConfiguration { + name: string; + shell: IShell; } \ No newline at end of file diff --git a/src/vs/workbench/parts/terminal/test/terminalService.test.ts b/src/vs/workbench/parts/terminal/test/terminalService.test.ts index 698287b007d0a..19e2ee2a9bba6 100644 --- a/src/vs/workbench/parts/terminal/test/terminalService.test.ts +++ b/src/vs/workbench/parts/terminal/test/terminalService.test.ts @@ -26,7 +26,7 @@ suite('Workbench - TerminalService', () => { assert.ok(env1['ok'], 'Parent environment is copied'); assert.deepStrictEqual(parentEnv1, { ok: true }, 'Parent environment is unchanged'); assert.equal(env1['PTYPID'], process.pid.toString(), 'PTYPID is equal to the current PID'); - assert.equal(env1['PTYSHELL'], '/bin/foosh', 'PTYSHELL is equal to the requested shell'); + assert.equal(env1['PTYSHELL'], '/bin/foosh', 'PTYSHELL is equal to the provided shell'); assert.equal(env1['PTYSHELLARG0'], '-bar', 'PTYSHELLARG0 is equal to the first shell argument'); assert.equal(env1['PTYSHELLARG1'], 'baz', 'PTYSHELLARG1 is equal to the first shell argument'); assert.ok(!('PTYSHELLARG2' in env1), 'PTYSHELLARG2 is unset');