mainThreadDebugService.ts 14.2 KB
Newer Older
1 2 3 4 5
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

6
import { DisposableStore } from 'vs/base/common/lifecycle';
7
import { URI as uri } from 'vs/base/common/uri';
I
isidor 已提交
8
import { IDebugService, IConfig, IDebugConfigurationProvider, IBreakpoint, IFunctionBreakpoint, IBreakpointData, IDebugAdapter, IDebugAdapterDescriptorFactory, IDebugSession, IDebugAdapterFactory, IDataBreakpoint } from 'vs/workbench/contrib/debug/common/debug';
9 10
import {
	ExtHostContext, ExtHostDebugServiceShape, MainThreadDebugServiceShape, DebugSessionUUID, MainContext,
I
isidor 已提交
11
	IExtHostContext, IBreakpointsDeltaDto, ISourceMultiBreakpointDto, ISourceBreakpointDto, IFunctionBreakpointDto, IDebugSessionDto, IDataBreakpointDto
12
} from 'vs/workbench/api/common/extHost.protocol';
13
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
14
import severity from 'vs/base/common/severity';
15
import { AbstractDebugAdapter } from 'vs/workbench/contrib/debug/common/abstractDebugAdapter';
A
Andre Weinand 已提交
16
import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
17
import { convertToVSCPaths, convertToDAPaths } from 'vs/workbench/contrib/debug/common/debugUtils';
18

19
@extHostNamedCustomer(MainContext.MainThreadDebugService)
20
export class MainThreadDebugService implements MainThreadDebugServiceShape, IDebugAdapterFactory {
21

22
	private readonly _proxy: ExtHostDebugServiceShape;
23
	private readonly _toDispose = new DisposableStore();
B
Benjamin Pasero 已提交
24
	private _breakpointEventsActive: boolean | undefined;
25
	private readonly _debugAdapters: Map<number, ExtensionHostDebugAdapter>;
26
	private _debugAdaptersHandleCounter = 1;
27 28 29
	private readonly _debugConfigurationProviders: Map<number, IDebugConfigurationProvider>;
	private readonly _debugAdapterDescriptorFactories: Map<number, IDebugAdapterDescriptorFactory>;
	private readonly _sessions: Set<DebugSessionUUID>;
30

31
	constructor(
32
		extHostContext: IExtHostContext,
33
		@IDebugService private readonly debugService: IDebugService
34
	) {
35
		this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostDebugService);
36
		this._toDispose.add(debugService.onDidNewSession(session => {
37
			this._proxy.$acceptDebugSessionStarted(this.getSessionDto(session));
38
		}));
A
Andre Weinand 已提交
39
		// Need to start listening early to new session events because a custom event can come while a session is initialising
40 41
		this._toDispose.add(debugService.onWillNewSession(session => {
			this._toDispose.add(session.onDidCustomEvent(event => this._proxy.$acceptDebugSessionCustomEvent(this.getSessionDto(session), event)));
I
isidor 已提交
42
		}));
43
		this._toDispose.add(debugService.onDidEndSession(session => {
44
			this._proxy.$acceptDebugSessionTerminated(this.getSessionDto(session));
45
			this._sessions.delete(session.getId());
A
Andre Weinand 已提交
46
		}));
47
		this._toDispose.add(debugService.getViewModel().onDidFocusSession(session => {
48
			this._proxy.$acceptDebugSessionActiveChanged(this.getSessionDto(session));
49
		}));
50

51 52
		this._debugAdapters = new Map();
		this._debugConfigurationProviders = new Map();
A
Andre Weinand 已提交
53
		this._debugAdapterDescriptorFactories = new Map();
A
Andre Weinand 已提交
54
		this._sessions = new Set();
A
Andre Weinand 已提交
55
	}
56

57
	public dispose(): void {
58
		this._toDispose.dispose();
59 60
	}

61 62
	// interface IDebugAdapterProvider

A
Andre Weinand 已提交
63
	createDebugAdapter(session: IDebugSession): IDebugAdapter {
64
		const handle = this._debugAdaptersHandleCounter++;
65
		const da = new ExtensionHostDebugAdapter(this, handle, this._proxy, session);
66 67 68 69
		this._debugAdapters.set(handle, da);
		return da;
	}

I
isidor 已提交
70
	substituteVariables(folder: IWorkspaceFolder | undefined, config: IConfig): Promise<IConfig> {
71
		return Promise.resolve(this._proxy.$substituteVariables(folder ? folder.uri : undefined, config));
A
Andre Weinand 已提交
72 73
	}

74 75
	runInTerminal(args: DebugProtocol.RunInTerminalRequestArguments): Promise<number | undefined> {
		return Promise.resolve(this._proxy.$runInTerminal(args));
76 77
	}

78 79 80
	// RPC methods (MainThreadDebugServiceShape)

	public $registerDebugTypes(debugTypes: string[]) {
81
		this._toDispose.add(this.debugService.getConfigurationManager().registerDebugAdapterFactory(debugTypes, this));
82 83
	}

84
	public $startBreakpointEvents(): void {
85 86 87 88 89

		if (!this._breakpointEventsActive) {
			this._breakpointEventsActive = true;

			// set up a handler to send more
90
			this._toDispose.add(this.debugService.getModel().onDidChangeBreakpoints(e => {
91 92
				// Ignore session only breakpoint events since they should only reflect in the UI
				if (e && !e.sessionOnly) {
93
					const delta: IBreakpointsDeltaDto = {};
94
					if (e.added) {
95
						delta.added = this.convertToDto(e.added);
96 97 98 99 100
					}
					if (e.removed) {
						delta.removed = e.removed.map(x => x.getId());
					}
					if (e.changed) {
101
						delta.changed = this.convertToDto(e.changed);
102 103 104 105 106 107 108 109 110 111 112
					}

					if (delta.added || delta.removed || delta.changed) {
						this._proxy.$acceptBreakpointsDelta(delta);
					}
				}
			}));

			// send all breakpoints
			const bps = this.debugService.getModel().getBreakpoints();
			const fbps = this.debugService.getModel().getFunctionBreakpoints();
I
isidor 已提交
113
			const dbps = this.debugService.getModel().getDataBreakpoints();
114 115
			if (bps.length > 0 || fbps.length > 0) {
				this._proxy.$acceptBreakpointsDelta({
I
isidor 已提交
116
					added: this.convertToDto(bps).concat(this.convertToDto(fbps)).concat(this.convertToDto(dbps))
117 118 119 120 121
				});
			}
		}
	}

I
isidor 已提交
122
	public $registerBreakpoints(DTOs: Array<ISourceMultiBreakpointDto | IFunctionBreakpointDto | IDataBreakpointDto>): Promise<void> {
123 124 125 126

		for (let dto of DTOs) {
			if (dto.type === 'sourceMulti') {
				const rawbps = dto.lines.map(l =>
I
isidor 已提交
127
					<IBreakpointData>{
128
						id: l.id,
129 130
						enabled: l.enabled,
						lineNumber: l.line + 1,
131
						column: l.character > 0 ? l.character + 1 : undefined, // a column value of 0 results in an omitted column attribute; see #46784
132
						condition: l.condition,
133 134
						hitCondition: l.hitCondition,
						logMessage: l.logMessage
135 136
					}
				);
137
				this.debugService.addBreakpoints(uri.revive(dto.uri), rawbps, 'extension');
138
			} else if (dto.type === 'function') {
139
				this.debugService.addFunctionBreakpoint(dto.functionName, dto.id);
I
isidor 已提交
140 141
			} else if (dto.type === 'data') {
				this.debugService.addDataBreakpoint(dto.label, dto.dataId, dto.canPersist);
142 143
			}
		}
144
		return Promise.resolve();
145 146
	}

I
isidor 已提交
147
	public $unregisterBreakpoints(breakpointIds: string[], functionBreakpointIds: string[], dataBreakpointIds: string[]): Promise<void> {
148 149
		breakpointIds.forEach(id => this.debugService.removeBreakpoints(id));
		functionBreakpointIds.forEach(id => this.debugService.removeFunctionBreakpoints(id));
I
isidor 已提交
150
		dataBreakpointIds.forEach(id => this.debugService.removeDataBreakpoints(id));
151
		return Promise.resolve();
152
	}
153

A
Andre Weinand 已提交
154
	public $registerDebugConfigurationProvider(debugType: string, hasProvide: boolean, hasResolve: boolean, hasProvideDebugAdapter: boolean, handle: number): Promise<void> {
155 156

		const provider = <IDebugConfigurationProvider>{
A
Andre Weinand 已提交
157
			type: debugType
158 159
		};
		if (hasProvide) {
160 161
			provider.provideDebugConfigurations = (folder, token) => {
				return this._proxy.$provideDebugConfigurations(handle, folder, token);
162 163 164
			};
		}
		if (hasResolve) {
165 166
			provider.resolveDebugConfiguration = (folder, config, token) => {
				return this._proxy.$resolveDebugConfiguration(handle, folder, config, token);
167 168
			};
		}
169
		if (hasProvideDebugAdapter) {
170
			console.info('DebugConfigurationProvider.debugAdapterExecutable is deprecated and will be removed soon; please use DebugAdapterDescriptorFactory.createDebugAdapterDescriptor instead.');
171
			provider.debugAdapterExecutable = (folder) => {
172
				return this._proxy.$legacyDebugAdapterExecutable(handle, folder);
173 174
			};
		}
175
		this._debugConfigurationProviders.set(handle, provider);
176
		this._toDispose.add(this.debugService.getConfigurationManager().registerDebugConfigurationProvider(provider));
177

178
		return Promise.resolve(undefined);
179 180
	}

181
	public $unregisterDebugConfigurationProvider(handle: number): void {
182 183 184 185 186
		const provider = this._debugConfigurationProviders.get(handle);
		if (provider) {
			this._debugConfigurationProviders.delete(handle);
			this.debugService.getConfigurationManager().unregisterDebugConfigurationProvider(provider);
		}
187 188
	}

J
Johannes Rieken 已提交
189
	public $registerDebugAdapterDescriptorFactory(debugType: string, handle: number): Promise<void> {
190

A
Andre Weinand 已提交
191
		const provider = <IDebugAdapterDescriptorFactory>{
192
			type: debugType,
A
Andre Weinand 已提交
193
			createDebugAdapterDescriptor: session => {
A
Andre Weinand 已提交
194
				return Promise.resolve(this._proxy.$provideDebugAdapter(handle, this.getSessionDto(session)));
195 196
			}
		};
A
Andre Weinand 已提交
197
		this._debugAdapterDescriptorFactories.set(handle, provider);
198
		this._toDispose.add(this.debugService.getConfigurationManager().registerDebugAdapterDescriptorFactory(provider));
199 200 201 202

		return Promise.resolve(undefined);
	}

A
Andre Weinand 已提交
203 204
	public $unregisterDebugAdapterDescriptorFactory(handle: number): void {
		const provider = this._debugAdapterDescriptorFactories.get(handle);
205
		if (provider) {
A
Andre Weinand 已提交
206 207 208 209 210
			this._debugAdapterDescriptorFactories.delete(handle);
			this.debugService.getConfigurationManager().unregisterDebugAdapterDescriptorFactory(provider);
		}
	}

211 212
	private getSession(sessionId: DebugSessionUUID | undefined): IDebugSession | undefined {
		if (sessionId) {
213
			return this.debugService.getModel().getSession(sessionId, true);
214 215 216 217 218
		}
		return undefined;
	}

	public $startDebugging(_folderUri: uri | undefined, nameOrConfiguration: string | IConfig, parentSessionID: DebugSessionUUID | undefined): Promise<boolean> {
219 220
		const folderUri = _folderUri ? uri.revive(_folderUri) : undefined;
		const launch = this.debugService.getConfigurationManager().getLaunch(folderUri);
221
		return this.debugService.startDebugging(launch, nameOrConfiguration, false, this.getSession(parentSessionID)).then(success => {
222
			return success;
223
		}, err => {
224
			return Promise.reject(new Error(err && err.message ? err.message : 'cannot start debugging'));
225
		});
226 227
	}

J
Johannes Rieken 已提交
228
	public $customDebugAdapterRequest(sessionId: DebugSessionUUID, request: string, args: any): Promise<any> {
229
		const session = this.debugService.getModel().getSession(sessionId, true);
A
Andre Weinand 已提交
230
		if (session) {
A
Andre Weinand 已提交
231
			return session.customRequest(request, args).then(response => {
232
				if (response && response.success) {
233 234
					return response.body;
				} else {
235
					return Promise.reject(new Error(response ? response.message : 'custom request failed'));
236 237 238
				}
			});
		}
239
		return Promise.reject(new Error('debug session not found'));
240
	}
241

242
	public $appendDebugConsole(value: string): void {
243
		// Use warning as severity to get the orange color for messages coming from the debug extension
I
isidor 已提交
244 245 246 247
		const session = this.debugService.getViewModel().focusedSession;
		if (session) {
			session.appendToRepl(value, severity.Warning);
		}
248
	}
249 250

	public $acceptDAMessage(handle: number, message: DebugProtocol.ProtocolMessage) {
251
		this.getDebugAdapter(handle).acceptMessage(convertToVSCPaths(message, false));
252 253
	}

254

255
	public $acceptDAError(handle: number, name: string, message: string, stack: string) {
256
		this.getDebugAdapter(handle).fireError(handle, new Error(`${name}: ${message}\n${stack}`));
257 258 259
	}

	public $acceptDAExit(handle: number, code: number, signal: string) {
260 261 262 263 264 265 266 267 268
		this.getDebugAdapter(handle).fireExit(handle, code, signal);
	}

	private getDebugAdapter(handle: number): ExtensionHostDebugAdapter {
		const adapter = this._debugAdapters.get(handle);
		if (!adapter) {
			throw new Error('Invalid debug adapter');
		}
		return adapter;
269
	}
270 271 272

	// dto helpers

273 274 275 276 277 278
	public $sessionCached(sessionID: string) {
		// remember that the EH has cached the session and we do not have to send it again
		this._sessions.add(sessionID);
	}


279 280 281 282
	getSessionDto(session: undefined): undefined;
	getSessionDto(session: IDebugSession): IDebugSessionDto;
	getSessionDto(session: IDebugSession | undefined): IDebugSessionDto | undefined;
	getSessionDto(session: IDebugSession | undefined): IDebugSessionDto | undefined {
283
		if (session) {
A
Andre Weinand 已提交
284 285 286 287
			const sessionID = <DebugSessionUUID>session.getId();
			if (this._sessions.has(sessionID)) {
				return sessionID;
			} else {
288
				// this._sessions.add(sessionID); 	// #69534: see $sessionCached above
A
Andre Weinand 已提交
289 290 291 292 293 294 295 296
				return {
					id: sessionID,
					type: session.configuration.type,
					name: session.configuration.name,
					folderUri: session.root ? session.root.uri : undefined,
					configuration: session.configuration
				};
			}
297 298 299 300
		}
		return undefined;
	}

I
isidor 已提交
301
	private convertToDto(bps: (ReadonlyArray<IBreakpoint | IFunctionBreakpoint | IDataBreakpoint>)): Array<ISourceBreakpointDto | IFunctionBreakpointDto | IDataBreakpointDto> {
302 303 304 305 306 307 308 309 310 311 312 313
		return bps.map(bp => {
			if ('name' in bp) {
				const fbp = <IFunctionBreakpoint>bp;
				return <IFunctionBreakpointDto>{
					type: 'function',
					id: fbp.getId(),
					enabled: fbp.enabled,
					condition: fbp.condition,
					hitCondition: fbp.hitCondition,
					logMessage: fbp.logMessage,
					functionName: fbp.name
				};
I
isidor 已提交
314 315 316 317 318 319 320 321 322 323 324 325 326
			} else if ('dataId' in bp) {
				const dbp = <IDataBreakpoint>bp;
				return <IDataBreakpointDto>{
					type: 'data',
					id: dbp.getId(),
					dataId: dbp.dataId,
					enabled: dbp.enabled,
					condition: dbp.condition,
					hitCondition: dbp.hitCondition,
					logMessage: dbp.logMessage,
					label: dbp.label,
					canPersist: dbp.canPersist
				};
327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342
			} else {
				const sbp = <IBreakpoint>bp;
				return <ISourceBreakpointDto>{
					type: 'source',
					id: sbp.getId(),
					enabled: sbp.enabled,
					condition: sbp.condition,
					hitCondition: sbp.hitCondition,
					logMessage: sbp.logMessage,
					uri: sbp.uri,
					line: sbp.lineNumber > 0 ? sbp.lineNumber - 1 : 0,
					character: (typeof sbp.column === 'number' && sbp.column > 0) ? sbp.column - 1 : 0,
				};
			}
		});
	}
343 344
}

A
Andre Weinand 已提交
345 346 347
/**
 * DebugAdapter that communicates via extension protocol with another debug adapter.
 */
348 349
class ExtensionHostDebugAdapter extends AbstractDebugAdapter {

350
	constructor(private readonly _ds: MainThreadDebugService, private _handle: number, private _proxy: ExtHostDebugServiceShape, private _session: IDebugSession) {
351 352 353 354 355 356 357 358 359 360 361
		super();
	}

	public fireError(handle: number, err: Error) {
		this._onError.fire(err);
	}

	public fireExit(handle: number, code: number, signal: string) {
		this._onExit.fire(code);
	}

362
	public startSession(): Promise<void> {
363
		return Promise.resolve(this._proxy.$startDASession(this._handle, this._ds.getSessionDto(this._session)));
364 365 366
	}

	public sendMessage(message: DebugProtocol.ProtocolMessage): void {
A
Andre Weinand 已提交
367
		this._proxy.$sendDAMessage(this._handle, convertToDAPaths(message, true));
368 369
	}

370 371
	public stopSession(): Promise<void> {
		return Promise.resolve(this._proxy.$stopDASession(this._handle));
372
	}
373
}