debugEditorModelManager.ts 14.8 KB
Newer Older
E
Erich Gamma 已提交
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.
 *--------------------------------------------------------------------------------------------*/

I
isidor 已提交
6
import nls = require('vs/nls');
I
isidor 已提交
7
import objects = require('vs/base/common/objects');
E
Erich Gamma 已提交
8 9
import lifecycle = require('vs/base/common/lifecycle');
import editorcommon = require('vs/editor/common/editorCommon');
10
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
11
import { IDebugService, ModelEvents, ViewModelEvents, IBreakpoint, IRawBreakpoint, State, ServiceEvents } from 'vs/workbench/parts/debug/common/debug';
E
Erich Gamma 已提交
12 13 14
import { IModelService } from 'vs/editor/common/services/modelService';

function toMap(arr: string[]): { [key: string]: boolean; } {
15
	const result: { [key: string]: boolean; } = {};
I
isidor 已提交
16
	for (let i = 0, len = arr.length; i < len; i++) {
17
		result[arr[i]] = true;
E
Erich Gamma 已提交
18
	}
19 20 21 22 23 24 25 26 27 28 29

	return result;
}

function createRange(startLineNUmber: number, startColumn: number, endLineNumber: number, endColumn: number): editorcommon.IRange {
	return {
		startLineNumber: startLineNUmber,
		startColumn: startColumn,
		endLineNumber: endLineNumber,
		endColumn: endColumn
	};
E
Erich Gamma 已提交
30 31 32 33 34 35 36 37 38 39
}

interface IDebugEditorModelData {
	model: editorcommon.IModel;
	toDispose: lifecycle.IDisposable[];
	breakpointDecorationIds: string[];
	breakpointLines: number[];
	breakpointDecorationsAsMap: { [decorationId: string]: boolean; };
	currentStackDecorations: string[];
	topStackFrameRange: editorcommon.IRange;
40
	dirty: boolean;
E
Erich Gamma 已提交
41 42
}

43
export class DebugEditorModelManager implements IWorkbenchContribution {
E
Erich Gamma 已提交
44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
	static ID = 'breakpointManager';

	private modelData: {
		[modelUrl: string]: IDebugEditorModelData;
	};
	private toDispose: lifecycle.IDisposable[];

	constructor(
		@IModelService private modelService: IModelService,
		@IDebugService private debugService: IDebugService
	) {
		this.modelData = {};
		this.toDispose = [];
		this.registerListeners();
	}

	public getId(): string {
		return DebugEditorModelManager.ID;
	}

	public dispose(): void {
I
isidor 已提交
65
		for (let modelUrlStr in this.modelData) {
E
Erich Gamma 已提交
66
			if (this.modelData.hasOwnProperty(modelUrlStr)) {
I
isidor 已提交
67
				const modelData = this.modelData[modelUrlStr];
E
Erich Gamma 已提交
68 69 70 71 72 73 74 75 76 77 78
				lifecycle.disposeAll(modelData.toDispose);
				modelData.model.deltaDecorations(modelData.breakpointDecorationIds, []);
				modelData.model.deltaDecorations(modelData.currentStackDecorations, []);
			}
		}
		this.toDispose = lifecycle.disposeAll(this.toDispose);

		this.modelData = null;
	}

	private registerListeners(): void {
79
		this.toDispose.push(this.modelService.onModelAdded(this.onModelAdded, this));
80
		this.modelService.getModels().forEach(model => this.onModelAdded(model));
81
		this.toDispose.push(this.modelService.onModelRemoved(this.onModelRemoved, this));
E
Erich Gamma 已提交
82

83 84
		this.toDispose.push(this.debugService.getModel().addListener2(ModelEvents.BREAKPOINTS_UPDATED, () => this.onBreakpointsChanged()));
		this.toDispose.push(this.debugService.getViewModel().addListener2(ViewModelEvents.FOCUSED_STACK_FRAME_UPDATED, () => this.onFocusedStackFrameUpdated()));
85 86 87 88 89
		this.toDispose.push(this.debugService.addListener2(ServiceEvents.STATE_CHANGED, () => {
			if (this.debugService.getState() === State.Inactive) {
				Object.keys(this.modelData).forEach(key => this.modelData[key].dirty = false);
			}
		}));
E
Erich Gamma 已提交
90 91 92
	}

	private onModelAdded(model: editorcommon.IModel): void {
93 94
		const modelUrlStr = model.getAssociatedResource().toString();
		const breakpoints = this.debugService.getModel().getBreakpoints().filter(bp => bp.source.uri.toString() === modelUrlStr);
E
Erich Gamma 已提交
95

I
isidor 已提交
96 97
		const currentStackDecorations = model.deltaDecorations([], this.createCallStackDecorations(modelUrlStr));
		const breakPointDecorations = model.deltaDecorations([], this.createBreakpointDecorations(breakpoints));
E
Erich Gamma 已提交
98

99 100
		const toDispose: lifecycle.IDisposable[] = [model.addListener2(editorcommon.EventType.ModelDecorationsChanged, (e: editorcommon.IModelDecorationsChangedEvent) =>
			this.onModelDecorationsChanged(modelUrlStr, e))];
E
Erich Gamma 已提交
101

102
		this.modelData[modelUrlStr] = {
E
Erich Gamma 已提交
103 104 105 106 107 108
			model: model,
			toDispose: toDispose,
			breakpointDecorationIds: breakPointDecorations,
			breakpointLines: breakpoints.map(bp => bp.lineNumber),
			breakpointDecorationsAsMap: toMap(breakPointDecorations),
			currentStackDecorations: currentStackDecorations,
109 110
			topStackFrameRange: null,
			dirty: false
E
Erich Gamma 已提交
111 112 113
		};
	}

114 115 116 117 118 119 120 121 122 123
	private onModelRemoved(model: editorcommon.IModel): void {
		const modelUrlStr = model.getAssociatedResource().toString();
		if (this.modelData.hasOwnProperty(modelUrlStr)) {
			const modelData = this.modelData[modelUrlStr];
			delete this.modelData[modelUrlStr];

			lifecycle.disposeAll(modelData.toDispose);
		}
	}

I
isidor 已提交
124
	// call stack management. Represent data coming from the debug service.
125 126 127 128 129

	private onFocusedStackFrameUpdated(): void {
		Object.keys(this.modelData).forEach(modelUrlStr => {
			const modelData = this.modelData[modelUrlStr];
			modelData.currentStackDecorations = modelData.model.deltaDecorations(modelData.currentStackDecorations, this.createCallStackDecorations(modelUrlStr));
E
Erich Gamma 已提交
130 131 132 133
		});
	}

	private createCallStackDecorations(modelUrlStr: string): editorcommon.IModelDeltaDecoration[] {
134 135 136 137
		const result: editorcommon.IModelDeltaDecoration[] = [];
		const focusedStackFrame = this.debugService.getViewModel().getFocusedStackFrame();
		const allThreads = this.debugService.getModel().getThreads();
		if (!focusedStackFrame || !allThreads[focusedStackFrame.threadId] || !allThreads[focusedStackFrame.threadId].callStack) {
E
Erich Gamma 已提交
138 139 140
			return result;
		}

I
isidor 已提交
141
		// only show decorations for the currently focussed thread.
142 143 144
		const thread = allThreads[focusedStackFrame.threadId];
		thread.callStack.filter(sf => sf.source.uri.toString() === modelUrlStr).forEach(sf => {
			const wholeLineRange = createRange(sf.lineNumber, sf.column, sf.lineNumber, Number.MAX_VALUE);
E
Erich Gamma 已提交
145

I
isidor 已提交
146
			// compute how to decorate the editor. Different decorations are used if this is a top stack frame, focussed stack frame,
147 148
			// an exception or a stack frame that did not change the line number (we only decorate the columns, not the whole line).
			if (sf === thread.callStack[0]) {
E
Erich Gamma 已提交
149 150
				result.push({
					options: DebugEditorModelManager.TOP_STACK_FRAME_MARGIN,
151
					range: createRange(sf.lineNumber, sf.column, sf.lineNumber, sf.column + 1)
E
Erich Gamma 已提交
152 153
				});

I
isidor 已提交
154
				if (thread.stoppedReason === 'exception') {
E
Erich Gamma 已提交
155 156 157 158 159 160 161 162 163
					result.push({
						options: DebugEditorModelManager.TOP_STACK_FRAME_EXCEPTION_DECORATION,
						range: wholeLineRange
					});
				} else {
					result.push({
						options: DebugEditorModelManager.TOP_STACK_FRAME_DECORATION,
						range: wholeLineRange
					});
164

E
Erich Gamma 已提交
165 166 167 168 169 170 171 172 173 174 175
					if (this.modelData[modelUrlStr]) {
						if (this.modelData[modelUrlStr].topStackFrameRange && this.modelData[modelUrlStr].topStackFrameRange.startLineNumber === wholeLineRange.startLineNumber &&
							this.modelData[modelUrlStr].topStackFrameRange.startColumn !== wholeLineRange.startColumn) {
							result.push({
								options: DebugEditorModelManager.TOP_STACK_FRAME_COLUMN_DECORATION,
								range: wholeLineRange
							});
						}
						this.modelData[modelUrlStr].topStackFrameRange = wholeLineRange;
					}
				}
176
			} else if (sf === focusedStackFrame) {
E
Erich Gamma 已提交
177 178
				result.push({
					options: DebugEditorModelManager.FOCUSED_STACK_FRAME_MARGIN,
179
					range: createRange(sf.lineNumber, sf.column, sf.lineNumber, sf.column + 1)
E
Erich Gamma 已提交
180 181 182 183 184 185 186
				});

				result.push({
					options: DebugEditorModelManager.FOCUSED_STACK_FRAME_DECORATION,
					range: wholeLineRange
				});
			}
187
		});
E
Erich Gamma 已提交
188 189 190 191

		return result;
	}

I
isidor 已提交
192
	// breakpoints management. Represent data coming from the debug service and also send data back.
193 194 195
	private onModelDecorationsChanged(modelUrlStr: string, e: editorcommon.IModelDecorationsChangedEvent): void {
		const modelData = this.modelData[modelUrlStr];
		if (!e.addedOrChangedDecorations.some(d => modelData.breakpointDecorationsAsMap.hasOwnProperty(d.id))) {
I
isidor 已提交
196
			// nothing to do, my decorations did not change.
E
Erich Gamma 已提交
197 198 199
			return;
		}

200
		const data: IRawBreakpoint[] = [];
E
Erich Gamma 已提交
201

I
isidor 已提交
202
		const enabledAndConditions: { [key: number]: { enabled: boolean, condition: string } } = {};
203 204 205 206
		this.debugService.getModel().getBreakpoints().filter(bp => bp.source.uri.toString() === modelUrlStr).forEach(bp => {
			enabledAndConditions[bp.lineNumber] = {
				enabled: bp.enabled,
				condition: bp.condition
I
isidor 已提交
207
			};
208
		});
E
Erich Gamma 已提交
209

210 211 212
		const modelUrl = modelData.model.getAssociatedResource();
		for (let i = 0, len = modelData.breakpointDecorationIds.length; i < len; i++) {
			const decorationRange = modelData.model.getDecorationRange(modelData.breakpointDecorationIds[i]);
I
isidor 已提交
213
			// check if the line got deleted.
E
Erich Gamma 已提交
214
			if (decorationRange.endColumn - decorationRange.startColumn > 0) {
I
isidor 已提交
215
				// since we know it is collapsed, it cannot grow to multiple lines
I
isidor 已提交
216
				data.push({
217
					uri: modelUrl,
I
isidor 已提交
218 219 220 221
					lineNumber: decorationRange.startLineNumber,
					enabled: enabledAndConditions[modelData.breakpointLines[i]].enabled,
					condition: enabledAndConditions[modelData.breakpointLines[i]].condition
				});
E
Erich Gamma 已提交
222 223
			}
		}
224
		modelData.dirty = !!this.debugService.getActiveSession();
E
Erich Gamma 已提交
225

226
		this.debugService.setBreakpointsForModel(modelUrl, data);
E
Erich Gamma 已提交
227 228
	}

229 230 231 232
	private onBreakpointsChanged(): void {
		const breakpointsMap: { [key: string]: IBreakpoint[] } = {};
		this.debugService.getModel().getBreakpoints().forEach(bp => {
			const uriStr = bp.source.uri.toString();
E
Erich Gamma 已提交
233
			if (breakpointsMap[uriStr]) {
234
				breakpointsMap[uriStr].push(bp);
E
Erich Gamma 已提交
235
			} else {
236
				breakpointsMap[uriStr] = [bp];
E
Erich Gamma 已提交
237
			}
238
		});
E
Erich Gamma 已提交
239

240 241 242
		Object.keys(breakpointsMap).forEach(modelUriStr => {
			if (this.modelData.hasOwnProperty(modelUriStr)) {
				this.updateBreakpoints(this.modelData[modelUriStr], breakpointsMap[modelUriStr]);
E
Erich Gamma 已提交
243
			}
244 245 246
		});
		Object.keys(this.modelData).forEach(modelUriStr => {
			if (!breakpointsMap.hasOwnProperty(modelUriStr)) {
E
Erich Gamma 已提交
247 248
				this.updateBreakpoints(this.modelData[modelUriStr], []);
			}
249
		});
E
Erich Gamma 已提交
250 251
	}

252 253 254 255
	private updateBreakpoints(modelData: IDebugEditorModelData, newBreakpoints: IBreakpoint[]): void {
		modelData.breakpointDecorationIds = modelData.model.deltaDecorations(modelData.breakpointDecorationIds, this.createBreakpointDecorations(newBreakpoints));
		modelData.breakpointDecorationsAsMap = toMap(modelData.breakpointDecorationIds);
		modelData.breakpointLines = newBreakpoints.map(bp => bp.lineNumber);
E
Erich Gamma 已提交
256 257
	}

258 259 260
	private createBreakpointDecorations(breakpoints: IBreakpoint[]): editorcommon.IModelDeltaDecoration[] {
		return breakpoints.map((breakpoint) => {
			return {
I
isidor 已提交
261
				options: this.getBreakpointDecorationOptions(breakpoint),
262 263 264 265 266
				range: createRange(breakpoint.lineNumber, 1, breakpoint.lineNumber, 2)
			};
		});
	}

I
isidor 已提交
267 268 269 270
	private getBreakpointDecorationOptions(breakpoint: IBreakpoint): editorcommon.IModelDecorationOptions {
		const activated = this.debugService.getModel().areBreakpointsActivated();
		const state = this.debugService.getState();
		const debugActive = state === State.Running || state === State.Stopped || state === State.Initializing;
271
		const modelData = this.modelData[breakpoint.source.uri.toString()];
272
		const session = this.debugService.getActiveSession();
273

I
isidor 已提交
274
		let result = (!breakpoint.enabled || !activated) ? DebugEditorModelManager.BREAKPOINT_DISABLED_DECORATION :
275
			debugActive && modelData && modelData.dirty ? DebugEditorModelManager.BREAKPOINT_DIRTY_DECORATION :
I
isidor 已提交
276 277 278
			debugActive && !breakpoint.verified ? DebugEditorModelManager.BREAKPOINT_UNVERIFIED_DECORATION :
			!breakpoint.condition ? DebugEditorModelManager.BREAKPOINT_DECORATION : null;

I
isidor 已提交
279 280 281 282 283
		if (result && breakpoint.message) {
			result = objects.clone(result);
			result.hoverMessage = breakpoint.message;
		}

284
		return result ? result :
285
			!session || session.capablities.supportsConditionalBreakpoints ? {
286 287 288 289
				glyphMarginClassName: 'debug-breakpoint-conditional-glyph',
				hoverMessage: breakpoint.condition,
				stickiness: editorcommon.TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges
			} : DebugEditorModelManager.BREAKPOINT_UNSUPPORTED_DECORATION;
I
isidor 已提交
290 291
	}

I
isidor 已提交
292
	// editor decorations
293

E
Erich Gamma 已提交
294 295
	private static BREAKPOINT_DECORATION: editorcommon.IModelDecorationOptions = {
		glyphMarginClassName: 'debug-breakpoint-glyph',
I
isidor 已提交
296
		hoverMessage: nls.localize('breakpointHover', "Breakpoint"),
297
		stickiness: editorcommon.TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges
E
Erich Gamma 已提交
298 299 300
	};

	private static BREAKPOINT_DISABLED_DECORATION: editorcommon.IModelDecorationOptions = {
301
		glyphMarginClassName: 'debug-breakpoint-disabled-glyph',
I
isidor 已提交
302
		hoverMessage: nls.localize('breakpointDisabledHover', "Disabled Breakpoint"),
E
Erich Gamma 已提交
303 304 305
		stickiness: editorcommon.TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges
	};

306
	private static BREAKPOINT_UNVERIFIED_DECORATION: editorcommon.IModelDecorationOptions = {
307
		glyphMarginClassName: 'debug-breakpoint-unverified-glyph',
I
isidor 已提交
308
		hoverMessage: nls.localize('breakpointDisabledHover', "Unverified Breakpoint"),
I
isidor 已提交
309 310 311
		stickiness: editorcommon.TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges
	};

312 313 314 315 316 317
	private static BREAKPOINT_DIRTY_DECORATION: editorcommon.IModelDecorationOptions = {
		glyphMarginClassName: 'debug-breakpoint-unverified-glyph',
		hoverMessage: nls.localize('breakpointDisabledHover', "Unverified breakpoint. File is modified, please restart debug session."),
		stickiness: editorcommon.TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges
	};

318 319 320 321 322 323
	private static BREAKPOINT_UNSUPPORTED_DECORATION: editorcommon.IModelDecorationOptions = {
		glyphMarginClassName: 'debug-breakpoint-unsupported-glyph',
		hoverMessage: nls.localize('breakpointUnsupported', "Conditional breakpoints not supported by this debug type"),
		stickiness: editorcommon.TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges
	};

I
isidor 已提交
324
	// we need a separate decoration for glyph margin, since we do not want it on each line of a multi line statement.
E
Erich Gamma 已提交
325 326 327
	private static TOP_STACK_FRAME_MARGIN: editorcommon.IModelDecorationOptions = {
		glyphMarginClassName: 'debug-top-stack-frame-glyph',
		stickiness: editorcommon.TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges
I
isidor 已提交
328
	};
E
Erich Gamma 已提交
329 330 331 332

	private static FOCUSED_STACK_FRAME_MARGIN: editorcommon.IModelDecorationOptions = {
		glyphMarginClassName: 'debug-focused-stack-frame-glyph',
		stickiness: editorcommon.TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges
I
isidor 已提交
333
	};
E
Erich Gamma 已提交
334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354

	private static TOP_STACK_FRAME_DECORATION: editorcommon.IModelDecorationOptions = {
		isWholeLine: true,
		className: 'debug-top-stack-frame-line',
		stickiness: editorcommon.TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges
	};

	private static TOP_STACK_FRAME_EXCEPTION_DECORATION: editorcommon.IModelDecorationOptions = {
		isWholeLine: true,
		className: 'debug-top-stack-frame-exception-line',
		stickiness: editorcommon.TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges
	};

	private static TOP_STACK_FRAME_COLUMN_DECORATION: editorcommon.IModelDecorationOptions = {
		isWholeLine: false,
		className: 'debug-top-stack-frame-column',
		stickiness: editorcommon.TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges
	};

	private static FOCUSED_STACK_FRAME_DECORATION: editorcommon.IModelDecorationOptions = {
		isWholeLine: true,
F
Francois Valdy 已提交
355
		className: 'debug-focused-stack-frame-line',
E
Erich Gamma 已提交
356 357 358
		stickiness: editorcommon.TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges
	};
}