From 53040a1ed6b11dbab158d11256816a174d1a1e3c Mon Sep 17 00:00:00 2001 From: Ryan Adolf Date: Mon, 3 Jul 2017 12:57:52 -0700 Subject: [PATCH 01/10] Properly format file path on drop in Windows --- .../parts/terminal/common/terminal.ts | 5 ++ .../electron-browser/terminalInstance.ts | 43 +++++++++++++++- .../electron-browser/terminalPanel.ts | 50 ++++++++++++++----- 3 files changed, 85 insertions(+), 13 deletions(-) diff --git a/src/vs/workbench/parts/terminal/common/terminal.ts b/src/vs/workbench/parts/terminal/common/terminal.ts index 449cf29d2fc..617c6478964 100644 --- a/src/vs/workbench/parts/terminal/common/terminal.ts +++ b/src/vs/workbench/parts/terminal/common/terminal.ts @@ -357,4 +357,9 @@ export interface ITerminalInstance { * Sets the title of the terminal instance. */ setTitle(title: string): void; + + /** + * Returns the list of nested shells running in the terminal. This is only implemented for Windows. + */ + getShellList(): Promise; } diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts index 4d73c397732..b8037d5d1b3 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts @@ -825,6 +825,47 @@ export class TerminalInstance implements ITerminalInstance { this._messageTitleListener = null; } } + + private static getChildProcesses(pid: number): Promise<{executable: string, pid: number}[]> { + return new Promise((resolve, reject) => { + cp.execFile('wmic.exe', ['process', 'where', `parentProcessId=${pid}`, 'get', 'ExecutablePath,ProcessId'], (err, stdout, stderr) => { + if (err) { + reject(err); + } else if (stderr.length > 0) { + resolve([]); // No processes found + } else { + resolve(stdout.split('\n').slice(1).filter(str => str.length > 0).map(str => { + const s = str.split(' '); + return {executable: s[0], pid: Number(s[1])}; + })); + } + }); + }); + } + + public async getShellList(): Promise { + if (platform.platform !== platform.Platform.Windows) { + return []; + } + + const shells = ['bash.exe', 'cmd.exe', 'powershell.exe']; + const pList = [this._shellLaunchConfig.executable]; + + let pid = this._processId; + while (pid !== null) { + const oldPid = pid; + pid = null; + for (const childproc of await TerminalInstance.getChildProcesses(oldPid)) { + if (shells.indexOf(path.basename(childproc.executable)) !== -1) { + pList.push(childproc.executable); + pid = childproc.pid; + break; + } + } + } + + return pList; + } } registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { @@ -848,4 +889,4 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { if (scrollbarSliderActiveBackgroundColor) { collector.addRule(`.monaco-workbench .panel.integrated-terminal .xterm .xterm-viewport::-webkit-scrollbar-thumb:active { background-color: ${scrollbarSliderActiveBackgroundColor}; }`); } -}); \ No newline at end of file +}); diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalPanel.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalPanel.ts index bec620085a1..8f00125ea3c 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalPanel.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalPanel.ts @@ -232,21 +232,47 @@ export class TerminalPanel extends Panel { return; } - // Check if the file was dragged from the tree explorer - let uri = e.dataTransfer.getData('URL'); - if (uri) { - uri = URI.parse(uri).path; - } else if (e.dataTransfer.files.length > 0) { - // Check if the file was dragged from the filesystem - uri = URI.file(e.dataTransfer.files[0].path).path; - } + const winFormatters: [RegExp, (uri: URI) => string][] = [ + // WSL bash + [/^C:\\Windows\\(System32|sysnative)\\bash.exe$/i, uri => '/mnt/' + uri.path[1] + uri.path.substring(3)], + // Git bash + [/bash.exe$/i, uri => uri.path.substring(0, 2) + uri.path.substring(3)], - if (!uri) { - return; - } + [/cmd.exe$/i, uri => uri.path[1].toUpperCase() + uri.path.substring(2).replace(/\//g, '\\')], + [/powershell.exe$/i, uri => uri.path[1].toUpperCase() + uri.path.substring(2).replace(/\//g, '\\')], + ]; const terminal = this._terminalService.getActiveInstance(); - terminal.sendText(this._preparePathForTerminal(uri), false); + + const uriForm = async (uri: URI) => { + if (platform.isWindows) { + const shells = await terminal.getShellList(); + const shell = shells[shells.length - 1]; + + for (const formatter of winFormatters) { + if (formatter[0].test(shell)) { + return formatter[1](uri); + } + } + } + return uri.path; + }; + + const uri = e.dataTransfer.getData('URL'); + let urip = Promise.resolve(uri); + if (uri) { + urip = uriForm(URI.parse(uri)); + } else if (e.dataTransfer.files.length > 0) { + // Check if the file was dragged from the filesystem + urip = uriForm(URI.file(e.dataTransfer.files[0].path)); + } + + urip.then(uri => { + if (!uri) { + return; + } + terminal.sendText(this._preparePathForTerminal(uri), false); + }); } })); } From 8ec3665b10e9bf1d85ce1ff78c16001d61a7dc9f Mon Sep 17 00:00:00 2001 From: Amy Qiu Date: Fri, 14 Jul 2017 12:50:04 -0700 Subject: [PATCH 02/10] Update Windows process name in integrated terminal on enter --- .../parts/terminal/common/terminal.ts | 2 +- .../electron-browser/terminalInstance.ts | 75 +++++++++++++------ .../electron-browser/terminalPanel.ts | 46 +++--------- 3 files changed, 63 insertions(+), 60 deletions(-) diff --git a/src/vs/workbench/parts/terminal/common/terminal.ts b/src/vs/workbench/parts/terminal/common/terminal.ts index 10818c9b1af..78d0cd9b6a3 100644 --- a/src/vs/workbench/parts/terminal/common/terminal.ts +++ b/src/vs/workbench/parts/terminal/common/terminal.ts @@ -366,5 +366,5 @@ export interface ITerminalInstance { /** * Returns the list of nested shells running in the terminal. This is only implemented for Windows. */ - getShellList(): Promise; + getShellName(): TPromise; } diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts index 3d57f34aad9..4260cd3ed37 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts @@ -12,7 +12,7 @@ import * as lifecycle from 'vs/base/common/lifecycle'; import * as nls from 'vs/nls'; import * as platform from 'vs/base/common/platform'; import * as dom from 'vs/base/browser/dom'; -import Event, { Emitter } from 'vs/base/common/event'; +import Event, { Emitter, debounceEvent } from 'vs/base/common/event'; import Uri from 'vs/base/common/uri'; import xterm = require('xterm'); import { Dimension } from 'vs/base/browser/builder'; @@ -83,6 +83,8 @@ export class TerminalInstance implements ITerminalInstance { private _messageTitleListener: (message: { type: string, content: string }) => void; private _preLaunchInputQueue: string; private _initialCwd: string; + private _pidStack: number[]; + private _checkWindowShell: Emitter; private _widgetManager: TerminalWidgetManager; private _linkHandler: TerminalLinkHandler; @@ -137,6 +139,18 @@ export class TerminalInstance implements ITerminalInstance { this._createProcess(this._shellLaunchConfig); this._createXterm(); + this._pidStack = []; + this._checkWindowShell = new Emitter(); + debounceEvent(this._checkWindowShell.event, (l, e) => e, 100, true) + (() => { + this.getShellName().then(result => { + if (result) { + const fullPathName = result.split('.exe')[0]; + this.setTitle(path.basename(fullPathName)); + } + }, e => { return e; }); + }); + // Only attach xterm.js to the DOM if the terminal panel has been opened before. if (_container) { this.attachToElement(_container); @@ -263,6 +277,11 @@ export class TerminalInstance implements ITerminalInstance { if (TabFocus.getTabFocusMode() && event.keyCode === 9) { return false; } + + if (platform.isWindows && event.keyCode === 13 /* ENTER */) { + this._checkWindowShell.fire(); + } + return undefined; }); this._instanceDisposables.push(dom.addDisposableListener(this._xterm.element, 'mouseup', (event: KeyboardEvent) => { @@ -849,15 +868,15 @@ export class TerminalInstance implements ITerminalInstance { } } - private static getChildProcesses(pid: number): Promise<{ executable: string, pid: number }[]> { - return new Promise((resolve, reject) => { + private static executeWMIC(pid: number): TPromise<{ executable: string, pid: number }[]> { + return new TPromise((resolve, reject) => { cp.execFile('wmic.exe', ['process', 'where', `parentProcessId=${pid}`, 'get', 'ExecutablePath,ProcessId'], (err, stdout, stderr) => { if (err) { reject(err); } else if (stderr.length > 0) { resolve([]); // No processes found } else { - resolve(stdout.split('\n').slice(1).filter(str => str.length > 0).map(str => { + resolve(stdout.split('\n').slice(1).filter(str => !/^\s*$/.test(str)).map(str => { const s = str.split(' '); return { executable: s[0], pid: Number(s[1]) }; })); @@ -866,28 +885,38 @@ export class TerminalInstance implements ITerminalInstance { }); } - public async getShellList(): Promise { - if (platform.platform !== platform.Platform.Windows) { - return []; - } - - const shells = ['bash.exe', 'cmd.exe', 'powershell.exe']; - const pList = [this._shellLaunchConfig.executable]; - - let pid = this._processId; - while (pid !== null) { - const oldPid = pid; - pid = null; - for (const childproc of await TerminalInstance.getChildProcesses(oldPid)) { - if (shells.indexOf(path.basename(childproc.executable)) !== -1) { - pList.push(childproc.executable); - pid = childproc.pid; - break; + private getChildProcesses(pid: number): TPromise { + return TerminalInstance.executeWMIC(pid).then(result => { + if (result.length === 0) { + if (this._pidStack.length > 1) { + this._pidStack.pop(); + return this.getChildProcesses(this._pidStack[this._pidStack.length - 1]); } + return TPromise.as([]); } - } + this._pidStack.push(result[0].pid); + return TPromise.as(result[0].executable); + }, error => { return error; }); + } - return pList; + public getShellName(): TPromise { + if (platform.platform !== platform.Platform.Windows) { + return TPromise.as(null); + } + if (this._pidStack.length === 0) { + this._pidStack.push(this._processId); + } + return new TPromise((resolve) => { + // wait 100ms before running getChildProcesses + setTimeout(() => { + this.getChildProcesses(this._pidStack[this._pidStack.length - 1]).then(result => { + if (result.length > 0) { + resolve(result); + } + resolve(this._shellLaunchConfig.executable); + }, error => { return error; }); + }, 100); + }); } } diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalPanel.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalPanel.ts index 08860b9a104..79dde8ccc9c 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalPanel.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalPanel.ts @@ -239,47 +239,21 @@ export class TerminalPanel extends Panel { return; } - const winFormatters: [RegExp, (uri: URI) => string][] = [ - // WSL bash - [/^C:\\Windows\\(System32|sysnative)\\bash.exe$/i, uri => '/mnt/' + uri.path[1] + uri.path.substring(3)], - // Git bash - [/bash.exe$/i, uri => uri.path.substring(0, 2) + uri.path.substring(3)], - - [/cmd.exe$/i, uri => uri.path[1].toUpperCase() + uri.path.substring(2).replace(/\//g, '\\')], - [/powershell.exe$/i, uri => uri.path[1].toUpperCase() + uri.path.substring(2).replace(/\//g, '\\')], - ]; - - const terminal = this._terminalService.getActiveInstance(); - - const uriForm = async (uri: URI) => { - if (platform.isWindows) { - const shells = await terminal.getShellList(); - const shell = shells[shells.length - 1]; - - for (const formatter of winFormatters) { - if (formatter[0].test(shell)) { - return formatter[1](uri); - } - } - } - return uri.path; - }; - - const uri = e.dataTransfer.getData('URL'); - let urip = Promise.resolve(uri); + // Check if the file was dragged from the tree explorer + let uri = e.dataTransfer.getData('URL'); if (uri) { - urip = uriForm(URI.parse(uri)); + uri = URI.parse(uri).path; } else if (e.dataTransfer.files.length > 0) { // Check if the file was dragged from the filesystem - urip = uriForm(URI.file(e.dataTransfer.files[0].path)); + uri = URI.file(e.dataTransfer.files[0].path).path; } - urip.then(uri => { - if (!uri) { - return; - } - terminal.sendText(this._preparePathForTerminal(uri), false); - }); + if (!uri) { + return; + } + + const terminal = this._terminalService.getActiveInstance(); + terminal.sendText(TerminalPanel.preparePathForTerminal(uri), false); } })); } From c2772f3f7ba1bf7ab45ab1c47a204137f452a9ac Mon Sep 17 00:00:00 2001 From: t-amqi Date: Mon, 17 Jul 2017 11:41:06 -0700 Subject: [PATCH 03/10] Address changes --- .../parts/terminal/common/terminal.ts | 5 -- .../electron-browser/terminalInstance.ts | 81 ++++-------------- .../electron-browser/terminalPanel.ts | 6 +- .../electron-browser/windowsShellService.ts | 82 +++++++++++++++++++ 4 files changed, 102 insertions(+), 72 deletions(-) create mode 100644 src/vs/workbench/parts/terminal/electron-browser/windowsShellService.ts diff --git a/src/vs/workbench/parts/terminal/common/terminal.ts b/src/vs/workbench/parts/terminal/common/terminal.ts index 78d0cd9b6a3..3148eb3aaaf 100644 --- a/src/vs/workbench/parts/terminal/common/terminal.ts +++ b/src/vs/workbench/parts/terminal/common/terminal.ts @@ -362,9 +362,4 @@ export interface ITerminalInstance { * Sets the title of the terminal instance. */ setTitle(title: string): void; - - /** - * Returns the list of nested shells running in the terminal. This is only implemented for Windows. - */ - getShellName(): TPromise; } diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts index 4260cd3ed37..ae17ec05f1a 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts @@ -1,9 +1,7 @@ /*--------------------------------------------------------------------------------------------- -* Copyright (c) Microsoft Corporation. All rights reserved. -* Licensed under the MIT License. See License.txt in the project root for license information. -*--------------------------------------------------------------------------------------------*/ - -'use strict'; + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ import * as cp from 'child_process'; import * as os from 'os'; @@ -15,6 +13,7 @@ import * as dom from 'vs/base/browser/dom'; import Event, { Emitter, debounceEvent } from 'vs/base/common/event'; import Uri from 'vs/base/common/uri'; import xterm = require('xterm'); +import { WindowsShellService } from 'vs/workbench/parts/terminal/electron-browser/windowsShellService'; import { Dimension } from 'vs/base/browser/builder'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; @@ -83,7 +82,7 @@ export class TerminalInstance implements ITerminalInstance { private _messageTitleListener: (message: { type: string, content: string }) => void; private _preLaunchInputQueue: string; private _initialCwd: string; - private _pidStack: number[]; + private _windowsShellService: WindowsShellService; private _checkWindowShell: Emitter; private _widgetManager: TerminalWidgetManager; @@ -111,7 +110,7 @@ export class TerminalInstance implements ITerminalInstance { @IWorkbenchEditorService private _editorService: IWorkbenchEditorService, @IInstantiationService private _instantiationService: IInstantiationService, @IClipboardService private _clipboardService: IClipboardService, - @IHistoryService private _historyService: IHistoryService + @IHistoryService private _historyService: IHistoryService, ) { this._instanceDisposables = []; this._processDisposables = []; @@ -139,17 +138,13 @@ export class TerminalInstance implements ITerminalInstance { this._createProcess(this._shellLaunchConfig); this._createXterm(); - this._pidStack = []; - this._checkWindowShell = new Emitter(); - debounceEvent(this._checkWindowShell.event, (l, e) => e, 100, true) - (() => { - this.getShellName().then(result => { - if (result) { - const fullPathName = result.split('.exe')[0]; - this.setTitle(path.basename(fullPathName)); - } - }, e => { return e; }); + if (platform.isWindows) { + this._checkWindowShell = new Emitter(); + debounceEvent(this._checkWindowShell.event, (l, e) => e, 100, true)(() => { + this.eventuallyGetShellName(); }); + this._windowsShellService = new WindowsShellService(this._processId, this._shellLaunchConfig); + } // Only attach xterm.js to the DOM if the terminal panel has been opened before. if (_container) { @@ -868,54 +863,12 @@ export class TerminalInstance implements ITerminalInstance { } } - private static executeWMIC(pid: number): TPromise<{ executable: string, pid: number }[]> { - return new TPromise((resolve, reject) => { - cp.execFile('wmic.exe', ['process', 'where', `parentProcessId=${pid}`, 'get', 'ExecutablePath,ProcessId'], (err, stdout, stderr) => { - if (err) { - reject(err); - } else if (stderr.length > 0) { - resolve([]); // No processes found - } else { - resolve(stdout.split('\n').slice(1).filter(str => !/^\s*$/.test(str)).map(str => { - const s = str.split(' '); - return { executable: s[0], pid: Number(s[1]) }; - })); - } - }); - }); - } - - private getChildProcesses(pid: number): TPromise { - return TerminalInstance.executeWMIC(pid).then(result => { - if (result.length === 0) { - if (this._pidStack.length > 1) { - this._pidStack.pop(); - return this.getChildProcesses(this._pidStack[this._pidStack.length - 1]); - } - return TPromise.as([]); + public eventuallyGetShellName(): void { + this._windowsShellService.getShellName().then(result => { + if (result) { + const fullPathName = result.split('.exe')[0]; + this.setTitle(path.basename(fullPathName)); } - this._pidStack.push(result[0].pid); - return TPromise.as(result[0].executable); - }, error => { return error; }); - } - - public getShellName(): TPromise { - if (platform.platform !== platform.Platform.Windows) { - return TPromise.as(null); - } - if (this._pidStack.length === 0) { - this._pidStack.push(this._processId); - } - return new TPromise((resolve) => { - // wait 100ms before running getChildProcesses - setTimeout(() => { - this.getChildProcesses(this._pidStack[this._pidStack.length - 1]).then(result => { - if (result.length > 0) { - resolve(result); - } - resolve(this._shellLaunchConfig.executable); - }, error => { return error; }); - }, 100); }); } } diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalPanel.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalPanel.ts index 79dde8ccc9c..ec52dcae7bb 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalPanel.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalPanel.ts @@ -1,7 +1,7 @@ /*--------------------------------------------------------------------------------------------- -* Copyright (c) Microsoft Corporation. All rights reserved. -* Licensed under the MIT License. See License.txt in the project root for license information. -*--------------------------------------------------------------------------------------------*/ + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ 'use strict'; diff --git a/src/vs/workbench/parts/terminal/electron-browser/windowsShellService.ts b/src/vs/workbench/parts/terminal/electron-browser/windowsShellService.ts new file mode 100644 index 00000000000..f68bc15935e --- /dev/null +++ b/src/vs/workbench/parts/terminal/electron-browser/windowsShellService.ts @@ -0,0 +1,82 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as cp from 'child_process'; +import * as platform from 'vs/base/common/platform'; +import { IShellLaunchConfig } from 'vs/workbench/parts/terminal/common/terminal'; +import { TPromise } from 'vs/base/common/winjs.base'; + +/** The amount of time to wait before getting the shell process name */ +const WAIT_FOR_SHELL_UPDATE = 100; + +export class WindowsShellService { + private _pidStack: number[]; + private _processId: number; + private _shellLaunchConfig: IShellLaunchConfig; + + public constructor(pid: number, shell: IShellLaunchConfig) { + this._pidStack = []; + this._processId = pid; + this._shellLaunchConfig = shell; + } + + private static getFirstWindowsChildProcess(pid: number): TPromise<{ executable: string, pid: number }[]> { + return new TPromise((resolve, reject) => { + cp.execFile('wmic.exe', ['process', 'where', `parentProcessId=${pid}`, 'get', 'ExecutablePath,ProcessId'], (err, stdout, stderr) => { + if (err) { + reject(err); + } else if (stderr.length > 0) { + resolve([]); // No processes found + } else { + resolve(stdout.split('\n').slice(1).filter(str => !/^\s*$/.test(str)).map(str => { + const s = str.split(' '); + return { executable: s[0], pid: Number(s[1]) }; + })); + } + }); + }); + } + + private refreshWindowsShellProcessTree(pid: number, flag: boolean): TPromise { + return WindowsShellService.getFirstWindowsChildProcess(pid).then(result => { + if (result.length === 0) { + if (flag) { + TPromise.as(result[0].executable); + } + if (this._pidStack.length > 1) { + this._pidStack.pop(); + return this.refreshWindowsShellProcessTree(this._pidStack[this._pidStack.length - 1], false); + } + return TPromise.as([]); + } + this._pidStack.push(result[0].pid); + return this.refreshWindowsShellProcessTree(result[0].pid, true); + }, error => { return error; }); + } + + /** + * Returns the innermost shell running in the terminal. This is only implemented for Windows. + */ + public getShellName(): TPromise { + if (platform.platform !== platform.Platform.Windows) { + throw null; + } + if (this._pidStack.length === 0) { + this._pidStack.push(this._processId); + } + return new TPromise((resolve) => { + // We wait before checking the processes to give it time to update with the new child shell. + // Otherwise, it would return old data. + setTimeout(() => { + this.refreshWindowsShellProcessTree(this._pidStack[this._pidStack.length - 1], false).then(result => { + if (result.length > 0) { + resolve(result); + } + resolve(this._shellLaunchConfig.executable); + }, error => { return error; }); + }, WAIT_FOR_SHELL_UPDATE); + }); + } +} \ No newline at end of file From a2a61b1c80d345326ba4e9841141f99169456d6b Mon Sep 17 00:00:00 2001 From: Amy Qiu Date: Mon, 17 Jul 2017 14:42:49 -0700 Subject: [PATCH 04/10] Refactoring --- .../electron-browser/terminalInstance.ts | 26 +----- .../electron-browser/windowsShellService.ts | 82 ++++++++++++------- 2 files changed, 56 insertions(+), 52 deletions(-) diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts index ae17ec05f1a..75f1f509a60 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts @@ -10,10 +10,10 @@ import * as lifecycle from 'vs/base/common/lifecycle'; import * as nls from 'vs/nls'; import * as platform from 'vs/base/common/platform'; import * as dom from 'vs/base/browser/dom'; -import Event, { Emitter, debounceEvent } from 'vs/base/common/event'; +import Event, { Emitter } from 'vs/base/common/event'; import Uri from 'vs/base/common/uri'; import xterm = require('xterm'); -import { WindowsShellService } from 'vs/workbench/parts/terminal/electron-browser/windowsShellService'; +import { WindowsShellHelper } from 'vs/workbench/parts/terminal/electron-browser/windowsShellService'; import { Dimension } from 'vs/base/browser/builder'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; @@ -82,8 +82,7 @@ export class TerminalInstance implements ITerminalInstance { private _messageTitleListener: (message: { type: string, content: string }) => void; private _preLaunchInputQueue: string; private _initialCwd: string; - private _windowsShellService: WindowsShellService; - private _checkWindowShell: Emitter; + private _windowsShellHelper: WindowsShellHelper; private _widgetManager: TerminalWidgetManager; private _linkHandler: TerminalLinkHandler; @@ -139,11 +138,7 @@ export class TerminalInstance implements ITerminalInstance { this._createXterm(); if (platform.isWindows) { - this._checkWindowShell = new Emitter(); - debounceEvent(this._checkWindowShell.event, (l, e) => e, 100, true)(() => { - this.eventuallyGetShellName(); - }); - this._windowsShellService = new WindowsShellService(this._processId, this._shellLaunchConfig); + this._windowsShellHelper = new WindowsShellHelper(this, this._shellLaunchConfig.executable); } // Only attach xterm.js to the DOM if the terminal panel has been opened before. @@ -273,10 +268,6 @@ export class TerminalInstance implements ITerminalInstance { return false; } - if (platform.isWindows && event.keyCode === 13 /* ENTER */) { - this._checkWindowShell.fire(); - } - return undefined; }); this._instanceDisposables.push(dom.addDisposableListener(this._xterm.element, 'mouseup', (event: KeyboardEvent) => { @@ -862,15 +853,6 @@ export class TerminalInstance implements ITerminalInstance { this._messageTitleListener = null; } } - - public eventuallyGetShellName(): void { - this._windowsShellService.getShellName().then(result => { - if (result) { - const fullPathName = result.split('.exe')[0]; - this.setTitle(path.basename(fullPathName)); - } - }); - } } registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { diff --git a/src/vs/workbench/parts/terminal/electron-browser/windowsShellService.ts b/src/vs/workbench/parts/terminal/electron-browser/windowsShellService.ts index f68bc15935e..e6d910aa1a4 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/windowsShellService.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/windowsShellService.ts @@ -5,24 +5,39 @@ import * as cp from 'child_process'; import * as platform from 'vs/base/common/platform'; -import { IShellLaunchConfig } from 'vs/workbench/parts/terminal/common/terminal'; +import * as path from 'path'; +import { ITerminalInstance } from 'vs/workbench/parts/terminal/common/terminal'; import { TPromise } from 'vs/base/common/winjs.base'; +import { Emitter, debounceEvent } from 'vs/base/common/event'; /** The amount of time to wait before getting the shell process name */ -const WAIT_FOR_SHELL_UPDATE = 100; +const WAIT_AFTER_ENTER_TIME = 100; -export class WindowsShellService { - private _pidStack: number[]; - private _processId: number; - private _shellLaunchConfig: IShellLaunchConfig; +export class WindowsShellHelper { + private _childProcessIdStack: number[]; + private _onCheckWindowsShell: Emitter; + private _terminalInstance: ITerminalInstance; + private _rootShellExecutable: string; - public constructor(pid: number, shell: IShellLaunchConfig) { - this._pidStack = []; - this._processId = pid; - this._shellLaunchConfig = shell; + public constructor(terminal: ITerminalInstance, rootShellName: string) { + this._childProcessIdStack = []; + this._terminalInstance = terminal; + this._rootShellExecutable = rootShellName; + if (!platform.isWindows) { + throw new Error(`WindowsShellHelper cannot be instantiated on ${platform.platform}`); + } + + this._onCheckWindowsShell = new Emitter(); + debounceEvent(this._onCheckWindowsShell.event, (l, e) => e, 100, true)(() => { + this.updateShellName(); + }); + + terminal.onData((string) => { + this.updateShellName(); + }); } - private static getFirstWindowsChildProcess(pid: number): TPromise<{ executable: string, pid: number }[]> { + private getFirstChildProcess(pid: number): TPromise<{ executable: string, pid: number }[]> { return new TPromise((resolve, reject) => { cp.execFile('wmic.exe', ['process', 'where', `parentProcessId=${pid}`, 'get', 'ExecutablePath,ProcessId'], (err, stdout, stderr) => { if (err) { @@ -39,44 +54,51 @@ export class WindowsShellService { }); } - private refreshWindowsShellProcessTree(pid: number, flag: boolean): TPromise { - return WindowsShellService.getFirstWindowsChildProcess(pid).then(result => { + private refreshShellProcessTree(pid: number, parent: string): TPromise { + return this.getFirstChildProcess(pid).then(result => { if (result.length === 0) { - if (flag) { - TPromise.as(result[0].executable); + if (parent.length > 0) { + return TPromise.as(parent); } - if (this._pidStack.length > 1) { - this._pidStack.pop(); - return this.refreshWindowsShellProcessTree(this._pidStack[this._pidStack.length - 1], false); + if (this._childProcessIdStack.length > 1) { + this._childProcessIdStack.pop(); + return this.refreshShellProcessTree(this._childProcessIdStack[this._childProcessIdStack.length - 1], ''); } return TPromise.as([]); } - this._pidStack.push(result[0].pid); - return this.refreshWindowsShellProcessTree(result[0].pid, true); + this._childProcessIdStack.push(result[0].pid); + return this.refreshShellProcessTree(result[0].pid, result[0].executable); }, error => { return error; }); } /** - * Returns the innermost shell running in the terminal. This is only implemented for Windows. + * Returns the innermost shell running in the terminal. */ - public getShellName(): TPromise { - if (platform.platform !== platform.Platform.Windows) { - throw null; - } - if (this._pidStack.length === 0) { - this._pidStack.push(this._processId); + public getShellName(pid: number, shell: string): TPromise { + if (this._childProcessIdStack.length === 0) { + this._childProcessIdStack.push(pid); } return new TPromise((resolve) => { // We wait before checking the processes to give it time to update with the new child shell. // Otherwise, it would return old data. setTimeout(() => { - this.refreshWindowsShellProcessTree(this._pidStack[this._pidStack.length - 1], false).then(result => { + this.refreshShellProcessTree(this._childProcessIdStack[this._childProcessIdStack.length - 1], '').then(result => { if (result.length > 0) { resolve(result); } - resolve(this._shellLaunchConfig.executable); + resolve(shell); }, error => { return error; }); - }, WAIT_FOR_SHELL_UPDATE); + }, WAIT_AFTER_ENTER_TIME); + }); + } + + public updateShellName(): void { + this.getShellName(this._terminalInstance.processId, this._rootShellExecutable).then(result => { + if (result) { + console.log(result); + const fullPathName = result.split('.exe')[0]; + this._terminalInstance.setTitle(path.basename(fullPathName)); + } }); } } \ No newline at end of file From aeaafd345d9b2f6385b50f4e5cbfef362985db67 Mon Sep 17 00:00:00 2001 From: Amy Qiu Date: Mon, 17 Jul 2017 15:42:44 -0700 Subject: [PATCH 05/10] Change filename --- .../parts/terminal/electron-browser/terminalInstance.ts | 2 +- .../{windowsShellService.ts => windowsShellHelper.ts} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename src/vs/workbench/parts/terminal/electron-browser/{windowsShellService.ts => windowsShellHelper.ts} (100%) diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts index 75f1f509a60..d72c869dfb3 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts @@ -13,7 +13,7 @@ import * as dom from 'vs/base/browser/dom'; import Event, { Emitter } from 'vs/base/common/event'; import Uri from 'vs/base/common/uri'; import xterm = require('xterm'); -import { WindowsShellHelper } from 'vs/workbench/parts/terminal/electron-browser/windowsShellService'; +import { WindowsShellHelper } from 'vs/workbench/parts/terminal/electron-browser/windowsShellHelper'; import { Dimension } from 'vs/base/browser/builder'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; diff --git a/src/vs/workbench/parts/terminal/electron-browser/windowsShellService.ts b/src/vs/workbench/parts/terminal/electron-browser/windowsShellHelper.ts similarity index 100% rename from src/vs/workbench/parts/terminal/electron-browser/windowsShellService.ts rename to src/vs/workbench/parts/terminal/electron-browser/windowsShellHelper.ts From d8f2e3d03342dcb7e1ab844a33410119ce06db0e Mon Sep 17 00:00:00 2001 From: t-amqi Date: Wed, 19 Jul 2017 13:16:58 -0700 Subject: [PATCH 06/10] Address feedback --- .../parts/terminal/electron-browser/terminalInstance.ts | 4 ++++ .../parts/terminal/electron-browser/windowsShellHelper.ts | 6 +----- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts index 13f175db649..4fbc1b52a94 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts @@ -271,6 +271,10 @@ export class TerminalInstance implements ITerminalInstance { return false; } + if (platform.isWindows && event.keyCode === 13 /* ENTER */ && !this._messageTitleListener) { + this._windowsShellHelper.updateShellName(); + } + return undefined; }); this._instanceDisposables.push(dom.addDisposableListener(this._xterm.element, 'mouseup', (event: KeyboardEvent) => { diff --git a/src/vs/workbench/parts/terminal/electron-browser/windowsShellHelper.ts b/src/vs/workbench/parts/terminal/electron-browser/windowsShellHelper.ts index e6d910aa1a4..a0728dde868 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/windowsShellHelper.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/windowsShellHelper.ts @@ -31,10 +31,6 @@ export class WindowsShellHelper { debounceEvent(this._onCheckWindowsShell.event, (l, e) => e, 100, true)(() => { this.updateShellName(); }); - - terminal.onData((string) => { - this.updateShellName(); - }); } private getFirstChildProcess(pid: number): TPromise<{ executable: string, pid: number }[]> { @@ -74,7 +70,7 @@ export class WindowsShellHelper { /** * Returns the innermost shell running in the terminal. */ - public getShellName(pid: number, shell: string): TPromise { + private getShellName(pid: number, shell: string): TPromise { if (this._childProcessIdStack.length === 0) { this._childProcessIdStack.push(pid); } From 509f4b38122697d7c47d8883f136ffe73fdf385d Mon Sep 17 00:00:00 2001 From: t-amqi Date: Wed, 19 Jul 2017 14:11:35 -0700 Subject: [PATCH 07/10] Reduce dependencies --- .../terminal/electron-browser/terminalInstance.ts | 8 ++++++-- .../electron-browser/windowsShellHelper.ts | 14 ++++++-------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts index 4fbc1b52a94..701f879444d 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts @@ -141,7 +141,9 @@ export class TerminalInstance implements ITerminalInstance { this._createXterm(); if (platform.isWindows) { - this._windowsShellHelper = new WindowsShellHelper(this, this._shellLaunchConfig.executable); + this._processReady.then(() => { + this._windowsShellHelper = new WindowsShellHelper(this._processId, this._shellLaunchConfig.executable); + }); } // Only attach xterm.js to the DOM if the terminal panel has been opened before. @@ -272,7 +274,9 @@ export class TerminalInstance implements ITerminalInstance { } if (platform.isWindows && event.keyCode === 13 /* ENTER */ && !this._messageTitleListener) { - this._windowsShellHelper.updateShellName(); + this._windowsShellHelper.updateShellName().then(result => { + this._title = result; + }); } return undefined; diff --git a/src/vs/workbench/parts/terminal/electron-browser/windowsShellHelper.ts b/src/vs/workbench/parts/terminal/electron-browser/windowsShellHelper.ts index a0728dde868..2a95c16f092 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/windowsShellHelper.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/windowsShellHelper.ts @@ -6,7 +6,6 @@ import * as cp from 'child_process'; import * as platform from 'vs/base/common/platform'; import * as path from 'path'; -import { ITerminalInstance } from 'vs/workbench/parts/terminal/common/terminal'; import { TPromise } from 'vs/base/common/winjs.base'; import { Emitter, debounceEvent } from 'vs/base/common/event'; @@ -16,12 +15,11 @@ const WAIT_AFTER_ENTER_TIME = 100; export class WindowsShellHelper { private _childProcessIdStack: number[]; private _onCheckWindowsShell: Emitter; - private _terminalInstance: ITerminalInstance; private _rootShellExecutable: string; + private _processId: number; - public constructor(terminal: ITerminalInstance, rootShellName: string) { + public constructor(pid: number, rootShellName: string) { this._childProcessIdStack = []; - this._terminalInstance = terminal; this._rootShellExecutable = rootShellName; if (!platform.isWindows) { throw new Error(`WindowsShellHelper cannot be instantiated on ${platform.platform}`); @@ -88,13 +86,13 @@ export class WindowsShellHelper { }); } - public updateShellName(): void { - this.getShellName(this._terminalInstance.processId, this._rootShellExecutable).then(result => { + public updateShellName(): TPromise { + return this.getShellName(this._processId, this._rootShellExecutable).then(result => { if (result) { - console.log(result); const fullPathName = result.split('.exe')[0]; - this._terminalInstance.setTitle(path.basename(fullPathName)); + return path.basename(fullPathName); } + return this._rootShellExecutable; }); } } \ No newline at end of file From ad62caf34e9d2cc59dbc611f33c0812469affb40 Mon Sep 17 00:00:00 2001 From: Amy Qiu Date: Wed, 19 Jul 2017 14:51:01 -0700 Subject: [PATCH 08/10] Remove timeout and fix event trigger --- .../electron-browser/terminalInstance.ts | 8 ++++++- .../electron-browser/windowsShellHelper.ts | 21 +++++++------------ 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts index 701f879444d..583aafa7bc8 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts @@ -143,6 +143,11 @@ export class TerminalInstance implements ITerminalInstance { if (platform.isWindows) { this._processReady.then(() => { this._windowsShellHelper = new WindowsShellHelper(this._processId, this._shellLaunchConfig.executable); + }).then(() => { + this._windowsShellHelper.updateShellName().then(result => { + this._title = result; + this._onTitleChanged.fire(result); + }); }); } @@ -273,9 +278,10 @@ export class TerminalInstance implements ITerminalInstance { return false; } - if (platform.isWindows && event.keyCode === 13 /* ENTER */ && !this._messageTitleListener) { + if (platform.isWindows && event.keyCode === 13 /* ENTER */ && this._messageTitleListener) { this._windowsShellHelper.updateShellName().then(result => { this._title = result; + this._onTitleChanged.fire(result); }); } diff --git a/src/vs/workbench/parts/terminal/electron-browser/windowsShellHelper.ts b/src/vs/workbench/parts/terminal/electron-browser/windowsShellHelper.ts index 2a95c16f092..dea9397e7b7 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/windowsShellHelper.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/windowsShellHelper.ts @@ -9,9 +9,6 @@ import * as path from 'path'; import { TPromise } from 'vs/base/common/winjs.base'; import { Emitter, debounceEvent } from 'vs/base/common/event'; -/** The amount of time to wait before getting the shell process name */ -const WAIT_AFTER_ENTER_TIME = 100; - export class WindowsShellHelper { private _childProcessIdStack: number[]; private _onCheckWindowsShell: Emitter; @@ -21,6 +18,8 @@ export class WindowsShellHelper { public constructor(pid: number, rootShellName: string) { this._childProcessIdStack = []; this._rootShellExecutable = rootShellName; + this._processId = pid; + if (!platform.isWindows) { throw new Error(`WindowsShellHelper cannot be instantiated on ${platform.platform}`); } @@ -73,16 +72,12 @@ export class WindowsShellHelper { this._childProcessIdStack.push(pid); } return new TPromise((resolve) => { - // We wait before checking the processes to give it time to update with the new child shell. - // Otherwise, it would return old data. - setTimeout(() => { - this.refreshShellProcessTree(this._childProcessIdStack[this._childProcessIdStack.length - 1], '').then(result => { - if (result.length > 0) { - resolve(result); - } - resolve(shell); - }, error => { return error; }); - }, WAIT_AFTER_ENTER_TIME); + this.refreshShellProcessTree(this._childProcessIdStack[this._childProcessIdStack.length - 1], '').then(result => { + if (result.length > 0) { + resolve(result); + } + resolve(shell); + }, error => { return error; }); }); } From ec3bfc8e915c233268413a13cd033c83a12e29d6 Mon Sep 17 00:00:00 2001 From: Amy Qiu Date: Wed, 19 Jul 2017 16:00:20 -0700 Subject: [PATCH 09/10] Fix git executable name --- .../parts/terminal/electron-browser/windowsShellHelper.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/vs/workbench/parts/terminal/electron-browser/windowsShellHelper.ts b/src/vs/workbench/parts/terminal/electron-browser/windowsShellHelper.ts index dea9397e7b7..3973ecb197b 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/windowsShellHelper.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/windowsShellHelper.ts @@ -48,6 +48,7 @@ export class WindowsShellHelper { } private refreshShellProcessTree(pid: number, parent: string): TPromise { + const shellExecutables = ['cmd.exe', 'powershell.exe', 'bash.exe']; return this.getFirstChildProcess(pid).then(result => { if (result.length === 0) { if (parent.length > 0) { @@ -58,6 +59,8 @@ export class WindowsShellHelper { return this.refreshShellProcessTree(this._childProcessIdStack[this._childProcessIdStack.length - 1], ''); } return TPromise.as([]); + } else if (shellExecutables.indexOf(path.basename(result[0].executable)) < 0){ + return TPromise.as(result[0].executable); } this._childProcessIdStack.push(result[0].pid); return this.refreshShellProcessTree(result[0].pid, result[0].executable); From b9f99bc75c7c44ec11e90296dc42bdf8c6706716 Mon Sep 17 00:00:00 2001 From: Amy Qiu Date: Wed, 19 Jul 2017 17:38:52 -0700 Subject: [PATCH 10/10] Refactoring --- .../parts/terminal/common/terminal.ts | 2 +- .../electron-browser/terminalActions.ts | 2 +- .../electron-browser/terminalInstance.ts | 46 ++++++------- .../electron-browser/windowsShellHelper.ts | 66 +++++++++---------- 4 files changed, 57 insertions(+), 59 deletions(-) diff --git a/src/vs/workbench/parts/terminal/common/terminal.ts b/src/vs/workbench/parts/terminal/common/terminal.ts index 3d03cfbba78..d3ef8db64d2 100644 --- a/src/vs/workbench/parts/terminal/common/terminal.ts +++ b/src/vs/workbench/parts/terminal/common/terminal.ts @@ -361,5 +361,5 @@ export interface ITerminalInstance { /** * Sets the title of the terminal instance. */ - setTitle(title: string): void; + setTitle(title: string, eventFromProcess: boolean): void; } diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalActions.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalActions.ts index 3b5b38af6be..4d3632ba2d0 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalActions.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalActions.ts @@ -622,7 +622,7 @@ export class RenameTerminalAction extends Action { prompt: nls.localize('workbench.action.terminal.rename.prompt', "Enter terminal name"), }).then(name => { if (name) { - terminalInstance.setTitle(name); + terminalInstance.setTitle(name, false); } }); } diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts index 583aafa7bc8..53755c84f48 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts @@ -112,7 +112,7 @@ export class TerminalInstance implements ITerminalInstance { @IWorkbenchEditorService private _editorService: IWorkbenchEditorService, @IInstantiationService private _instantiationService: IInstantiationService, @IClipboardService private _clipboardService: IClipboardService, - @IHistoryService private _historyService: IHistoryService, + @IHistoryService private _historyService: IHistoryService ) { this._instanceDisposables = []; this._processDisposables = []; @@ -143,11 +143,6 @@ export class TerminalInstance implements ITerminalInstance { if (platform.isWindows) { this._processReady.then(() => { this._windowsShellHelper = new WindowsShellHelper(this._processId, this._shellLaunchConfig.executable); - }).then(() => { - this._windowsShellHelper.updateShellName().then(result => { - this._title = result; - this._onTitleChanged.fire(result); - }); }); } @@ -278,11 +273,10 @@ export class TerminalInstance implements ITerminalInstance { return false; } + // Windows does not get a process title event from terminalProcess so we check the name on enter + // messageTitleListener is falsy when the API/user renames the terminal so we don't override it if (platform.isWindows && event.keyCode === 13 /* ENTER */ && this._messageTitleListener) { - this._windowsShellHelper.updateShellName().then(result => { - this._title = result; - this._onTitleChanged.fire(result); - }); + this._windowsShellHelper.getShellName().then(title => this.setTitle(title, true)); } return undefined; @@ -540,17 +534,17 @@ export class TerminalInstance implements ITerminalInstance { const platformKey = platform.isWindows ? 'windows' : platform.isMacintosh ? 'osx' : 'linux'; const envFromConfig = { ...process.env, ...this._configHelper.config.env[platformKey] }; const env = TerminalInstance.createTerminalEnv(envFromConfig, shell, this._initialCwd, locale, this._cols, this._rows); - this._title = shell.name || ''; this._process = cp.fork(Uri.parse(require.toUrl('bootstrap')).fsPath, ['--type=terminal'], { env, cwd: Uri.parse(path.dirname(require.toUrl('../node/terminalProcess'))).fsPath }); - if (!shell.name) { + if (shell.name) { + this.setTitle(shell.name, false); + } else { // Only listen for process title changes when a name is not provided this._messageTitleListener = (message) => { if (message.type === 'title') { - this._title = message.content ? message.content : ''; - this._onTitleChanged.fire(this._title); + this.setTitle(message.content ? message.content : '', true); } }; this._process.on('message', this._messageTitleListener); @@ -673,7 +667,7 @@ export class TerminalInstance implements ITerminalInstance { const oldTitle = this._title; this._createProcess(shell); if (oldTitle !== this._title) { - this._onTitleChanged.fire(this._title); + this.setTitle(this._title, true); } this._process.on('message', (message) => this._sendPtyDataToXterm(message)); @@ -850,19 +844,25 @@ export class TerminalInstance implements ITerminalInstance { this._terminalProcessFactory = factory; } - public setTitle(title: string): void { + public setTitle(title: string, eventFromProcess: boolean): void { + if (eventFromProcess) { + if (platform.isWindows) { + // Remove the .exe extension + title = path.basename(title.split('.exe')[0]); + } + } else { + // If the title has not been set by the API or the rename command, unregister the handler that + // automatically updates the terminal name + if (this._process && this._messageTitleListener) { + this._process.removeListener('message', this._messageTitleListener); + this._messageTitleListener = null; + } + } const didTitleChange = title !== this._title; this._title = title; if (didTitleChange) { this._onTitleChanged.fire(title); } - - // If the title was not set by the API, unregister the handler that - // automatically updates the terminal name - if (this._process && this._messageTitleListener) { - this._process.removeListener('message', this._messageTitleListener); - this._messageTitleListener = null; - } } } diff --git a/src/vs/workbench/parts/terminal/electron-browser/windowsShellHelper.ts b/src/vs/workbench/parts/terminal/electron-browser/windowsShellHelper.ts index 3973ecb197b..ab5f254324d 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/windowsShellHelper.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/windowsShellHelper.ts @@ -9,16 +9,18 @@ import * as path from 'path'; import { TPromise } from 'vs/base/common/winjs.base'; import { Emitter, debounceEvent } from 'vs/base/common/event'; +const SHELL_EXECUTABLES = ['cmd.exe', 'powershell.exe', 'bash.exe']; + export class WindowsShellHelper { private _childProcessIdStack: number[]; private _onCheckWindowsShell: Emitter; private _rootShellExecutable: string; - private _processId: number; + private _rootProcessId: number; - public constructor(pid: number, rootShellName: string) { + public constructor(rootProcessId: number, rootShellExecutable: string) { this._childProcessIdStack = []; - this._rootShellExecutable = rootShellName; - this._processId = pid; + this._rootShellExecutable = rootShellExecutable; + this._rootProcessId = rootProcessId; if (!platform.isWindows) { throw new Error(`WindowsShellHelper cannot be instantiated on ${platform.platform}`); @@ -26,11 +28,11 @@ export class WindowsShellHelper { this._onCheckWindowsShell = new Emitter(); debounceEvent(this._onCheckWindowsShell.event, (l, e) => e, 100, true)(() => { - this.updateShellName(); + this.getShellName(); }); } - private getFirstChildProcess(pid: number): TPromise<{ executable: string, pid: number }[]> { + private getChildProcessDetails(pid: number): TPromise<{ executable: string, pid: number }[]> { return new TPromise((resolve, reject) => { cp.execFile('wmic.exe', ['process', 'where', `parentProcessId=${pid}`, 'get', 'ExecutablePath,ProcessId'], (err, stdout, stderr) => { if (err) { @@ -38,59 +40,55 @@ export class WindowsShellHelper { } else if (stderr.length > 0) { resolve([]); // No processes found } else { - resolve(stdout.split('\n').slice(1).filter(str => !/^\s*$/.test(str)).map(str => { + const childProcessLines = stdout.split('\n').slice(1).filter(str => !/^\s*$/.test(str)); + const childProcessDetails = childProcessLines.map(str => { const s = str.split(' '); return { executable: s[0], pid: Number(s[1]) }; - })); + }); + resolve(childProcessDetails); } }); }); } private refreshShellProcessTree(pid: number, parent: string): TPromise { - const shellExecutables = ['cmd.exe', 'powershell.exe', 'bash.exe']; - return this.getFirstChildProcess(pid).then(result => { + return this.getChildProcessDetails(pid).then(result => { + // When we didn't find any child processes of the process if (result.length === 0) { - if (parent.length > 0) { + // Case where we found a child process already and are checking further down the pid tree + // We have reached the end here so we know that parent is the deepest first child of the tree + if (parent) { return TPromise.as(parent); } - if (this._childProcessIdStack.length > 1) { - this._childProcessIdStack.pop(); - return this.refreshShellProcessTree(this._childProcessIdStack[this._childProcessIdStack.length - 1], ''); + // Case where we haven't found a child and only the root shell is left + if (this._childProcessIdStack.length === 1) { + return TPromise.as(this._rootShellExecutable); } - return TPromise.as([]); - } else if (shellExecutables.indexOf(path.basename(result[0].executable)) < 0){ + // Otherwise, we go up the tree to find the next valid deepest child of the root + this._childProcessIdStack.pop(); + return this.refreshShellProcessTree(this._childProcessIdStack[this._childProcessIdStack.length - 1], null); + } + // We only go one level deep when checking for children of processes other then shells + if (SHELL_EXECUTABLES.indexOf(path.basename(result[0].executable)) === -1) { return TPromise.as(result[0].executable); } + // Save the pid in the stack and keep looking for children of that child this._childProcessIdStack.push(result[0].pid); return this.refreshShellProcessTree(result[0].pid, result[0].executable); }, error => { return error; }); } /** - * Returns the innermost shell running in the terminal. + * Returns the innermost shell executable running in the terminal */ - private getShellName(pid: number, shell: string): TPromise { + public getShellName(): TPromise { if (this._childProcessIdStack.length === 0) { - this._childProcessIdStack.push(pid); + this._childProcessIdStack.push(this._rootProcessId); } return new TPromise((resolve) => { - this.refreshShellProcessTree(this._childProcessIdStack[this._childProcessIdStack.length - 1], '').then(result => { - if (result.length > 0) { - resolve(result); - } - resolve(shell); + this.refreshShellProcessTree(this._childProcessIdStack[this._childProcessIdStack.length - 1], null).then(result => { + resolve(result); }, error => { return error; }); }); } - - public updateShellName(): TPromise { - return this.getShellName(this._processId, this._rootShellExecutable).then(result => { - if (result) { - const fullPathName = result.split('.exe')[0]; - return path.basename(fullPathName); - } - return this._rootShellExecutable; - }); - } } \ No newline at end of file