-
Notifications
You must be signed in to change notification settings - Fork 31.3k
/
Copy pathwindowsShellHelper.ts
137 lines (123 loc) · 3.96 KB
/
windowsShellHelper.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as platform from 'vs/base/common/platform';
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';
import { Terminal as XTermTerminal } from 'vscode-xterm';
import WindowsProcessTreeType = require('windows-process-tree');
const SHELL_EXECUTABLES = [
'cmd.exe',
'powershell.exe',
'bash.exe',
'wsl.exe',
'ubuntu.exe',
'ubuntu1804.exe',
'kali.exe',
'debian.exe',
'opensuse-42.exe',
'sles-12.exe'
];
let windowsProcessTree: typeof WindowsProcessTreeType;
export class WindowsShellHelper {
private _onCheckShell: Emitter<TPromise<string>>;
private _isDisposed: boolean;
private _currentRequest: TPromise<string>;
private _newLineFeed: boolean;
public constructor(
private _rootProcessId: number,
private _terminalInstance: ITerminalInstance,
private _xterm: XTermTerminal
) {
if (!platform.isWindows) {
throw new Error(`WindowsShellHelper cannot be instantiated on ${platform.platform}`);
}
this._isDisposed = false;
(import('windows-process-tree')).then(mod => {
if (this._isDisposed) {
return;
}
windowsProcessTree = mod;
this._onCheckShell = new Emitter<TPromise<string>>();
// 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, 150, true)(() => {
setTimeout(() => {
this.checkShell();
}, 50);
});
// We want to fire a new check for the shell on a linefeed, but only
// when parsing has finished which is indicated by the cursormove event.
// If this is done on every linefeed, parsing ends up taking
// significantly longer due to resetting timers. Note that this is
// private API.
this._xterm.on('linefeed', () => this._newLineFeed = true);
this._xterm.on('cursormove', () => {
if (this._newLineFeed) {
this._onCheckShell.fire();
}
});
// Fire a new check for the shell when any key is pressed.
this._xterm.on('keypress', () => this._onCheckShell.fire());
});
}
private checkShell(): void {
if (platform.isWindows && this._terminalInstance.isTitleSetByProcess) {
this.getShellName().then(title => {
if (!this._isDisposed) {
this._terminalInstance.setTitle(title, true);
}
});
}
}
private traverseTree(tree: any): string {
if (!tree) {
return '';
}
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;
}
if (child.children[0].name !== 'conhost.exe') {
break;
}
}
if (favouriteChild >= tree.children.length) {
return tree.name;
}
return this.traverseTree(tree.children[favouriteChild]);
}
public dispose(): void {
this._isDisposed = true;
}
/**
* Returns the innermost shell executable running in the terminal
*/
public getShellName(): TPromise<string> {
if (this._isDisposed) {
return TPromise.as('');
}
// Prevent multiple requests at once, instead return current request
if (this._currentRequest) {
return this._currentRequest;
}
this._currentRequest = new TPromise<string>(resolve => {
windowsProcessTree.getProcessTree(this._rootProcessId, (tree) => {
const name = this.traverseTree(tree);
this._currentRequest = null;
resolve(name);
});
});
return this._currentRequest;
}
}