From ead952302ae3f71d94fad327c1e532264e253801 Mon Sep 17 00:00:00 2001 From: Amy Qiu Date: Mon, 21 Aug 2017 11:43:35 -0700 Subject: [PATCH] Integrate native module for WindowsShellHelper --- npm-shrinkwrap.json | 5 ++ package.json | 1 + src/typings/windows-process-tree.d.ts | 9 ++ .../electron-browser/windowsShellHelper.ts | 83 +++++++------------ 4 files changed, 44 insertions(+), 54 deletions(-) create mode 100644 src/typings/windows-process-tree.d.ts diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 1112f9fcfdb..cb99e134107 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -561,6 +561,11 @@ "from": "vscode-textmate@3.1.5", "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-3.1.5.tgz" }, + "windows-process-tree": { + "version": "0.1.1", + "from": "windows-process-tree@0.1.1", + "resolved": "https://registry.npmjs.org/windows-process-tree/-/windows-process-tree-0.1.1.tgz" + }, "winreg": { "version": "1.2.0", "from": "winreg@1.2.0", diff --git a/package.json b/package.json index 5c0a1adc7e9..f69459ad508 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "vscode-debugprotocol": "1.22.0", "vscode-ripgrep": "0.0.25", "vscode-textmate": "^3.1.5", + "windows-process-tree": "0.1.1", "winreg": "1.2.0", "xterm": "Tyriar/xterm.js#vscode-release/1.15", "yauzl": "2.8.0" diff --git a/src/typings/windows-process-tree.d.ts b/src/typings/windows-process-tree.d.ts new file mode 100644 index 00000000000..5594f085a94 --- /dev/null +++ b/src/typings/windows-process-tree.d.ts @@ -0,0 +1,9 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'windows-process-tree' { + function get(rootPid: number, callback: Function): void; + export = get; +} \ No newline at end of file diff --git a/src/vs/workbench/parts/terminal/electron-browser/windowsShellHelper.ts b/src/vs/workbench/parts/terminal/electron-browser/windowsShellHelper.ts index f9a49ee9a4e..e9853a0a165 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/windowsShellHelper.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/windowsShellHelper.ts @@ -5,7 +5,7 @@ import * as cp from 'child_process'; import * as platform from 'vs/base/common/platform'; -import * as path from 'path'; +import windowsProcessTree = require('windows-process-tree'); import { TPromise } from 'vs/base/common/winjs.base'; import { Emitter, debounceEvent } from 'vs/base/common/event'; import { ITerminalInstance } from 'vs/workbench/parts/terminal/common/terminal'; @@ -34,8 +34,10 @@ export class WindowsShellHelper { this._onCheckShell = new Emitter>(); // The debounce is necessary to prevent multiple processes from spawning when // the enter key or output is spammed - debounceEvent(this._onCheckShell.event, (l, e) => e, 200, true)(() => { - this.checkShell(); + debounceEvent(this._onCheckShell.event, (l, e) => e, 150, true)(() => { + setTimeout(() => { + this.checkShell(); + }, 50); }); this._xterm.on('lineFeed', () => this._onCheckShell.fire()); @@ -52,58 +54,27 @@ export class WindowsShellHelper { } } - private getChildProcessDetails(pid: number): TPromise<{ executable: string, pid: number }[]> { - return new TPromise((resolve, reject) => { - this._wmicProcess = cp.execFile('wmic.exe', ['process', 'where', `parentProcessId=${pid}`, 'get', 'ExecutablePath,ProcessId'], (err, stdout, stderr) => { - this._wmicProcess = null; - if (this._isDisposed) { - reject(null); - } - if (err) { - reject(err); - } else if (stderr.length > 0) { - resolve([]); // No processes found - } else { - 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 { - return this.getChildProcessDetails(pid).then(result => { - // When we didn't find any child processes of the process - if (result.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); - } - // 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); - } - // 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); + private traverseTree(tree: any): string { + if (SHELL_EXECUTABLES.indexOf(tree.name) === -1) { + return tree.name; + } + if (!tree.children || tree.children.length === 0) { + return tree.name; + } + let favouriteChild = 0; + for (; favouriteChild < tree.children.length; favouriteChild++) { + const child = tree.children[favouriteChild]; + if (!child.children || child.children.length === 0) { + break; } - // 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); + if (child.children[0].name !== 'conhost.exe') { + break; } - // 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 => { - if (!this._isDisposed) { - return error; - } - }); + } + if (favouriteChild >= tree.children.length) { + return tree.name; + } + return this.traverseTree(tree.children[favouriteChild]); } public dispose(): void { @@ -117,6 +88,10 @@ export class WindowsShellHelper { * Returns the innermost shell executable running in the terminal */ public getShellName(): TPromise { - return this.refreshShellProcessTree(this._childProcessIdStack[this._childProcessIdStack.length - 1], null); + return new TPromise(resolve => { + windowsProcessTree(this._rootProcessId, (tree) => { + resolve(this.traverseTree(tree)); + }); + }); } } \ No newline at end of file