Skip to content

Commit 0dd9617

Browse files
committed
cleanup integrated terminal support for debugging
1 parent 7a9c080 commit 0dd9617

5 files changed

Lines changed: 226 additions & 164 deletions

File tree

src/vs/workbench/api/electron-browser/mainThreadDebugService.ts

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
88
import uri from 'vs/base/common/uri';
9-
import { IDebugService, IConfig, IDebugConfigurationProvider, IBreakpoint, IFunctionBreakpoint, IBreakpointData, IAdapterExecutable, ITerminalSettings, IDebugAdapter, IDebugAdapterProvider } from 'vs/workbench/parts/debug/common/debug';
9+
import { IDebugService, IConfig, IDebugConfigurationProvider, IBreakpoint, IFunctionBreakpoint, IBreakpointData, IAdapterExecutable, ITerminalSettings, IDebugAdapter, IDebugAdapterProvider, ITerminalLauncher } from 'vs/workbench/parts/debug/common/debug';
1010
import { TPromise } from 'vs/base/common/winjs.base';
1111
import {
1212
ExtHostContext, ExtHostDebugServiceShape, MainThreadDebugServiceShape, DebugSessionUUID, MainContext,
@@ -18,6 +18,8 @@ import { AbstractDebugAdapter } from 'vs/workbench/parts/debug/node/debugAdapter
1818
import * as paths from 'vs/base/common/paths';
1919
import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
2020
import { convertToVSCPaths, convertToDAPaths } from 'vs/workbench/parts/debug/common/debugUtils';
21+
import { ITerminalService } from 'vs/workbench/parts/terminal/common/terminal';
22+
import { AbstractTerminalLauncher } from 'vs/workbench/parts/debug/electron-browser/terminalSupport';
2123

2224

2325
@extHostNamedCustomer(MainContext.MainThreadDebugService)
@@ -28,11 +30,13 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb
2830
private _breakpointEventsActive: boolean;
2931
private _debugAdapters: Map<number, ExtensionHostDebugAdapter>;
3032
private _debugAdaptersHandleCounter = 1;
33+
private _terminalLauncher: ITerminalLauncher;
3134

3235

3336
constructor(
3437
extHostContext: IExtHostContext,
35-
@IDebugService private debugService: IDebugService
38+
@IDebugService private debugService: IDebugService,
39+
@ITerminalService private terminalService: ITerminalService,
3640
) {
3741
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostDebugService);
3842
this._toDispose = [];
@@ -73,7 +77,10 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb
7377
}
7478

7579
runInTerminal(args: DebugProtocol.RunInTerminalRequestArguments, config: ITerminalSettings): TPromise<void> {
76-
return this._proxy.$runInTerminal(args, config);
80+
if (!this._terminalLauncher) {
81+
this._terminalLauncher = new ExtensionTerminalLauncher(this.terminalService, this._proxy);
82+
}
83+
return this._terminalLauncher.runInTerminal(args, config);
7784
}
7885

7986
public dispose(): void {
@@ -295,3 +302,25 @@ class ExtensionHostDebugAdapter extends AbstractDebugAdapter {
295302
return this._proxy.$stopDASession(this._handle);
296303
}
297304
}
305+
306+
export class ExtensionTerminalLauncher extends AbstractTerminalLauncher {
307+
308+
constructor(
309+
@ITerminalService terminalService: ITerminalService,
310+
private _proxy: ExtHostDebugServiceShape
311+
) {
312+
super(terminalService);
313+
}
314+
315+
protected runInExternalTerminal(args: DebugProtocol.RunInTerminalRequestArguments, config: ITerminalSettings): TPromise<void> {
316+
return this._proxy.$runInTerminal(args, config);
317+
}
318+
319+
protected isBusy(processId: number): TPromise<boolean> {
320+
return this._proxy.$isTerminalBusy(processId);
321+
}
322+
323+
protected prepareCommand(args: DebugProtocol.RunInTerminalRequestArguments, config: ITerminalSettings): TPromise<any> {
324+
return this._proxy.$prepareCommandForTerminal(args, config);
325+
}
326+
}

src/vs/workbench/api/node/extHost.protocol.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -831,6 +831,8 @@ export interface ISourceMultiBreakpointDto {
831831
export interface ExtHostDebugServiceShape {
832832
$substituteVariables(folder: UriComponents | undefined, config: IConfig): TPromise<IConfig>;
833833
$runInTerminal(args: DebugProtocol.RunInTerminalRequestArguments, config: ITerminalSettings): TPromise<void>;
834+
$isTerminalBusy(processId: number): TPromise<boolean>;
835+
$prepareCommandForTerminal(args: DebugProtocol.RunInTerminalRequestArguments, config: ITerminalSettings): TPromise<any>;
834836
$startDASession(handle: number, debugType: string, adapterExecutableInfo: IAdapterExecutable | null, debugPort: number): TPromise<void>;
835837
$stopDASession(handle: number): TPromise<void>;
836838
$sendDAMessage(handle: number, message: DebugProtocol.ProtocolMessage): TPromise<void>;

src/vs/workbench/api/node/extHostDebugService.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import { ExtHostWorkspace } from 'vs/workbench/api/node/extHostWorkspace';
2222
import { ExtHostExtensionService } from 'vs/workbench/api/node/extHostExtensionService';
2323
import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/node/extHostDocumentsAndEditors';
2424
import { IAdapterExecutable, ITerminalSettings, IDebuggerContribution, IConfig, IDebugAdapter } from 'vs/workbench/parts/debug/common/debug';
25-
import { getTerminalLauncher } from 'vs/workbench/parts/debug/node/terminals';
25+
import { getTerminalLauncher, hasChildprocesses, prepareCommand } from 'vs/workbench/parts/debug/node/terminals';
2626
import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
2727
import { VariableResolver } from 'vs/workbench/services/configurationResolver/node/variableResolver';
2828
import { IStringDictionary } from 'vs/base/common/collections';
@@ -125,6 +125,14 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape {
125125
return void 0;
126126
}
127127

128+
public $isTerminalBusy(processId: number): TPromise<boolean> {
129+
return asWinJsPromise(token => hasChildprocesses(processId));
130+
}
131+
132+
public $prepareCommandForTerminal(args: DebugProtocol.RunInTerminalRequestArguments, config: ITerminalSettings): TPromise<any> {
133+
return asWinJsPromise(token => prepareCommand(args, config));
134+
}
135+
128136
public $substituteVariables(folderUri: UriComponents | undefined, config: IConfig): TPromise<IConfig> {
129137
if (!this._variableResolver) {
130138
this._variableResolver = new ExtHostVariableResolverService(this._workspace, this._editorsService, this._configurationService);

src/vs/workbench/parts/debug/electron-browser/terminalSupport.ts

Lines changed: 25 additions & 160 deletions
Original file line numberDiff line numberDiff line change
@@ -4,31 +4,25 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import * as nls from 'vs/nls';
7-
import * as platform from 'vs/base/common/platform';
8-
import * as cp from 'child_process';
97
import { IDisposable } from 'vs/base/common/lifecycle';
108
import { TPromise } from 'vs/base/common/winjs.base';
119
import { ITerminalService, ITerminalInstance } from 'vs/workbench/parts/terminal/common/terminal';
1210
import { ITerminalService as IExternalTerminalService } from 'vs/workbench/parts/execution/common/execution';
1311
import { ITerminalLauncher, ITerminalSettings } from 'vs/workbench/parts/debug/common/debug';
12+
import { hasChildprocesses, prepareCommand } from 'vs/workbench/parts/debug/node/terminals';
1413

15-
const enum ShellType { cmd, powershell, bash }
16-
17-
export class TerminalLauncher implements ITerminalLauncher {
14+
export class AbstractTerminalLauncher implements ITerminalLauncher {
1815

1916
private integratedTerminalInstance: ITerminalInstance;
2017
private terminalDisposedListener: IDisposable;
2118

22-
constructor(
23-
@ITerminalService private terminalService: ITerminalService,
24-
@IExternalTerminalService private nativeTerminalService: IExternalTerminalService
25-
) {
19+
constructor(private terminalService: ITerminalService) {
2620
}
2721

28-
runInTerminal(args: DebugProtocol.RunInTerminalRequestArguments, config: ITerminalSettings): TPromise<void> {
22+
async runInTerminal(args: DebugProtocol.RunInTerminalRequestArguments, config: ITerminalSettings): TPromise<void> {
2923

3024
if (args.kind === 'external') {
31-
return this.nativeTerminalService.runInTerminal(args.title, args.cwd, args.args, args.env || {});
25+
return this.runInExternalTerminal(args, config);
3226
}
3327

3428
if (!this.terminalDisposedListener) {
@@ -41,14 +35,14 @@ export class TerminalLauncher implements ITerminalLauncher {
4135
}
4236

4337
let t = this.integratedTerminalInstance;
44-
if ((t && this.isBusy(t)) || !t) {
38+
if ((t && await this.isBusy(t.processId)) || !t) {
4539
t = this.terminalService.createTerminal({ name: args.title || nls.localize('debug.terminal.title', "debuggee") });
4640
this.integratedTerminalInstance = t;
4741
}
4842
this.terminalService.setActiveInstance(t);
4943
this.terminalService.showPanel(true);
5044

51-
const command = this.prepareCommand(args, config);
45+
const command = await this.prepareCommand(args, config);
5246

5347
return new TPromise((resolve, error) => {
5448
setTimeout(_ => {
@@ -58,158 +52,29 @@ export class TerminalLauncher implements ITerminalLauncher {
5852
});
5953
}
6054

61-
private isBusy(t: ITerminalInstance): boolean {
62-
if (t.processId) {
63-
try {
64-
// if shell has at least one child process, assume that shell is busy
65-
if (platform.isWindows) {
66-
const result = cp.spawnSync('wmic', ['process', 'get', 'ParentProcessId']);
67-
if (result.stdout) {
68-
const pids = result.stdout.toString().split('\r\n');
69-
if (!pids.some(p => parseInt(p) === t.processId)) {
70-
return false;
71-
}
72-
}
73-
} else {
74-
const result = cp.spawnSync('/usr/bin/pgrep', ['-lP', String(t.processId)]);
75-
if (result.stdout) {
76-
const r = result.stdout.toString().trim();
77-
if (r.length === 0 || r.indexOf(' tmux') >= 0) { // ignore 'tmux'; see #43683
78-
return false;
79-
}
80-
}
81-
}
82-
}
83-
catch (e) {
84-
// silently ignore
85-
}
86-
}
87-
// fall back to safe side
88-
return true;
55+
protected runInExternalTerminal(args: DebugProtocol.RunInTerminalRequestArguments, config: ITerminalSettings): TPromise<void> {
56+
return void 0;
8957
}
9058

91-
private prepareCommand(args: DebugProtocol.RunInTerminalRequestArguments, config: ITerminalSettings): string {
92-
93-
let shellType: ShellType;
94-
95-
// get the shell configuration for the current platform
96-
let shell: string;
97-
const shell_config = config.integrated.shell;
98-
if (platform.isWindows) {
99-
shell = shell_config.windows;
100-
shellType = ShellType.cmd;
101-
} else if (platform.isLinux) {
102-
shell = shell_config.linux;
103-
shellType = ShellType.bash;
104-
} else if (platform.isMacintosh) {
105-
shell = shell_config.osx;
106-
shellType = ShellType.bash;
107-
}
108-
109-
// try to determine the shell type
110-
shell = shell.trim().toLowerCase();
111-
if (shell.indexOf('powershell') >= 0) {
112-
shellType = ShellType.powershell;
113-
} else if (shell.indexOf('cmd.exe') >= 0) {
114-
shellType = ShellType.cmd;
115-
} else if (shell.indexOf('bash') >= 0) {
116-
shellType = ShellType.bash;
117-
} else if (shell.indexOf('git\\bin\\bash.exe') >= 0) {
118-
shellType = ShellType.bash;
119-
}
120-
121-
let quote: (s: string) => string;
122-
let command = '';
123-
124-
switch (shellType) {
125-
126-
case ShellType.powershell:
127-
128-
quote = (s: string) => {
129-
s = s.replace(/\'/g, '\'\'');
130-
return `'${s}'`;
131-
//return s.indexOf(' ') >= 0 || s.indexOf('\'') >= 0 || s.indexOf('"') >= 0 ? `'${s}'` : s;
132-
};
133-
134-
if (args.cwd) {
135-
command += `cd '${args.cwd}'; `;
136-
}
137-
if (args.env) {
138-
for (let key in args.env) {
139-
const value = args.env[key];
140-
if (value === null) {
141-
command += `Remove-Item env:${key}; `;
142-
} else {
143-
command += `\${env:${key}}='${value}'; `;
144-
}
145-
}
146-
}
147-
if (args.args && args.args.length > 0) {
148-
const cmd = quote(args.args.shift());
149-
command += (cmd[0] === '\'') ? `& ${cmd} ` : `${cmd} `;
150-
for (let a of args.args) {
151-
command += `${quote(a)} `;
152-
}
153-
}
154-
break;
155-
156-
case ShellType.cmd:
157-
158-
quote = (s: string) => {
159-
s = s.replace(/\"/g, '""');
160-
return (s.indexOf(' ') >= 0 || s.indexOf('"') >= 0) ? `"${s}"` : s;
161-
};
162-
163-
if (args.cwd) {
164-
command += `cd ${quote(args.cwd)} && `;
165-
}
166-
if (args.env) {
167-
command += 'cmd /C "';
168-
for (let key in args.env) {
169-
const value = args.env[key];
170-
if (value === null) {
171-
command += `set "${key}=" && `;
172-
} else {
173-
command += `set "${key}=${args.env[key]}" && `;
174-
}
175-
}
176-
}
177-
for (let a of args.args) {
178-
command += `${quote(a)} `;
179-
}
180-
if (args.env) {
181-
command += '"';
182-
}
183-
break;
59+
protected isBusy(processId: number): TPromise<boolean> {
60+
return TPromise.as(hasChildprocesses(processId));
61+
}
18462

185-
case ShellType.bash:
63+
protected prepareCommand(args: DebugProtocol.RunInTerminalRequestArguments, config: ITerminalSettings): TPromise<string> {
64+
return TPromise.as(prepareCommand(args, config));
65+
}
66+
}
18667

187-
quote = (s: string) => {
188-
s = s.replace(/\"/g, '\\"');
189-
return (s.indexOf(' ') >= 0 || s.indexOf('\\') >= 0) ? `"${s}"` : s;
190-
};
68+
export class TerminalLauncher extends AbstractTerminalLauncher {
19169

192-
if (args.cwd) {
193-
command += `cd ${quote(args.cwd)} ; `;
194-
}
195-
if (args.env) {
196-
command += 'env';
197-
for (let key in args.env) {
198-
const value = args.env[key];
199-
if (value === null) {
200-
command += ` -u "${key}"`;
201-
} else {
202-
command += ` "${key}=${value}"`;
203-
}
204-
}
205-
command += ' ';
206-
}
207-
for (let a of args.args) {
208-
command += `${quote(a)} `;
209-
}
210-
break;
211-
}
70+
constructor(
71+
@ITerminalService terminalService: ITerminalService,
72+
@IExternalTerminalService private nativeTerminalService: IExternalTerminalService
73+
) {
74+
super(terminalService);
75+
}
21276

213-
return command;
77+
runInExternalTerminal(args: DebugProtocol.RunInTerminalRequestArguments, config: ITerminalSettings): TPromise<void> {
78+
return this.nativeTerminalService.runInTerminal(args.title, args.cwd, args.args, args.env || {});
21479
}
21580
}

0 commit comments

Comments
 (0)