<commit_before>/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

import * as nls from 'vs/nls';
import { TPromise } from 'vs/base/common/winjs.base';
import * as objects from 'vs/base/common/objects';
import * as lifecycle from 'vs/base/common/lifecycle';
import { Constants } from 'vs/editor/common/core/uint';
import { Range } from 'vs/editor/common/core/range';
import { IModel, TrackedRangeStickiness, IModelDeltaDecoration, IModelDecorationOptions } from 'vs/editor/common/editorCommon';
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { IDebugService, IBreakpoint, IRawBreakpoint, State } from 'vs/workbench/parts/debug/common/debug';
import { IModelService } from 'vs/editor/common/services/modelService';
import { MarkdownString } from 'vs/base/common/htmlContent';

interface IDebugEditorModelData {
	model: IModel;
	toDispose: lifecycle.IDisposable[];
	breakpointDecorationIds: string[];
	breakpointModelIds: string[];
	breakpointDecorationsAsMap: Map<string, Range>;
	currentStackDecorations: string[];
	dirty: boolean;
	topStackFrameRange: Range;
}

const stickiness = TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges;

export class DebugEditorModelManager implements IWorkbenchContribution {
	static ID = 'breakpointManager';

	private modelDataMap: Map<string, IDebugEditorModelData>;
	private toDispose: lifecycle.IDisposable[];

	constructor(
		@IModelService private modelService: IModelService,
		@IDebugService private debugService: IDebugService
	) {
		this.modelDataMap = new Map<string, IDebugEditorModelData>();
		this.toDispose = [];
		this.registerListeners();
	}

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

	public dispose(): void {
		this.modelDataMap.forEach(modelData => {
			lifecycle.dispose(modelData.toDispose);
			modelData.model.deltaDecorations(modelData.breakpointDecorationIds, []);
			modelData.model.deltaDecorations(modelData.currentStackDecorations, []);
		});
		this.toDispose = lifecycle.dispose(this.toDispose);

		this.modelDataMap.clear();
	}

	private registerListeners(): void {
		this.toDispose.push(this.modelService.onModelAdded(this.onModelAdded, this));
		this.modelService.getModels().forEach(model => this.onModelAdded(model));
		this.toDispose.push(this.modelService.onModelRemoved(this.onModelRemoved, this));

		this.toDispose.push(this.debugService.getModel().onDidChangeBreakpoints(() => this.onBreakpointsChange()));
		this.toDispose.push(this.debugService.getViewModel().onDidFocusStackFrame(() => this.onFocusStackFrame()));
		this.toDispose.push(this.debugService.onDidChangeState(state => {
			if (state === State.Inactive) {
				this.modelDataMap.forEach(modelData => {
					modelData.dirty = false;
					modelData.topStackFrameRange = undefined;
				});
			}
		}));
	}

	private onModelAdded(model: IModel): void {
		const modelUrlStr = model.uri.toString();
		const breakpoints = this.debugService.getModel().getBreakpoints().filter(bp => bp.uri.toString() === modelUrlStr);

		const currentStackDecorations = model.deltaDecorations([], this.createCallStackDecorations(modelUrlStr));
		const desiredDecorations = this.createBreakpointDecorations(model, breakpoints);
		const breakPointDecorations = model.deltaDecorations([], desiredDecorations);

		const toDispose: lifecycle.IDisposable[] = [model.onDidChangeDecorations((e) => this.onModelDecorationsChanged(modelUrlStr))];
		const breakpointDecorationsAsMap = new Map<string, Range>();
		breakPointDecorations.forEach((decorationId, index) => breakpointDecorationsAsMap.set(decorationId, desiredDecorations[index].range));

		this.modelDataMap.set(modelUrlStr, {
			model: model,
			toDispose: toDispose,
			breakpointDecorationIds: breakPointDecorations,
			breakpointModelIds: breakpoints.map(bp => bp.getId()),
			breakpointDecorationsAsMap,
			currentStackDecorations: currentStackDecorations,
			dirty: false,
			topStackFrameRange: undefined
		});
	}

	private onModelRemoved(model: IModel): void {
		const modelUriStr = model.uri.toString();
		if (this.modelDataMap.has(modelUriStr)) {
			lifecycle.dispose(this.modelDataMap.get(modelUriStr).toDispose);
			this.modelDataMap.delete(modelUriStr);
		}
	}

	// call stack management. Represent data coming from the debug service.

	private onFocusStackFrame(): void {
		this.modelDataMap.forEach((modelData, uri) => {
			modelData.currentStackDecorations = modelData.model.deltaDecorations(modelData.currentStackDecorations, this.createCallStackDecorations(uri));
		});
	}

	private createCallStackDecorations(modelUriStr: string): IModelDeltaDecoration[] {
		const result: IModelDeltaDecoration[] = [];
		const stackFrame = this.debugService.getViewModel().focusedStackFrame;
		if (!stackFrame || stackFrame.source.uri.toString() !== modelUriStr) {
			return result;
		}

		// only show decorations for the currently focused thread.
		const columnUntilEOLRange = new Range(stackFrame.range.startLineNumber, stackFrame.range.startColumn, stackFrame.range.startLineNumber, Constants.MAX_SAFE_SMALL_INTEGER);
		const range = new Range(stackFrame.range.startLineNumber, stackFrame.range.startColumn, stackFrame.range.startLineNumber, stackFrame.range.startColumn + 1);

		// compute how to decorate the editor. Different decorations are used if this is a top stack frame, focused stack frame,
		// an exception or a stack frame that did not change the line number (we only decorate the columns, not the whole line).
		const callStack = stackFrame.thread.getCallStack();
		if (callStack && callStack.length && stackFrame === callStack[0]) {
			result.push({
				options: DebugEditorModelManager.TOP_STACK_FRAME_MARGIN,
				range
			});

			if (stackFrame.thread.stoppedDetails && stackFrame.thread.stoppedDetails.reason === 'exception') {
				result.push({
					options: DebugEditorModelManager.TOP_STACK_FRAME_EXCEPTION_DECORATION,
					range: columnUntilEOLRange
				});
			} else {
				result.push({
					options: DebugEditorModelManager.TOP_STACK_FRAME_DECORATION,
					range: columnUntilEOLRange
				});
				if (stackFrame.range.endLineNumber && stackFrame.range.endColumn) {
					result.push({
						options: { className: 'debug-top-stack-frame-range' },
						range: stackFrame.range
					});
				}

				if (this.modelDataMap.has(modelUriStr)) {
					const modelData = this.modelDataMap.get(modelUriStr);
					if (modelData.topStackFrameRange && modelData.topStackFrameRange.startLineNumber === stackFrame.range.startLineNumber && modelData.topStackFrameRange.startColumn !== stackFrame.range.startColumn) {
						result.push({
							options: DebugEditorModelManager.TOP_STACK_FRAME_INLINE_DECORATION,
							range: columnUntilEOLRange
						});
					}
					modelData.topStackFrameRange = columnUntilEOLRange;
				}
			}
		} else {
			result.push({
				options: DebugEditorModelManager.FOCUSED_STACK_FRAME_MARGIN,
				range
			});
			if (stackFrame.range.endLineNumber && stackFrame.range.endColumn) {
				result.push({
					options: { className: 'debug-focused-stack-frame-range' },
					range: stackFrame.range
				});
			}

			result.push({
				options: DebugEditorModelManager.FOCUSED_STACK_FRAME_DECORATION,
				range: columnUntilEOLRange
			});
		}

		return result;
	}

	// breakpoints management. Represent data coming from the debug service and also send data back.
	private onModelDecorationsChanged(modelUrlStr: string): void {
		const modelData = this.modelDataMap.get(modelUrlStr);
		if (modelData.breakpointDecorationsAsMap.size === 0) {
			// I have no decorations
			return;
		}
		let somethingChanged = false;
		modelData.breakpointDecorationsAsMap.forEach((breakpointRange, decorationId) => {
			if (somethingChanged) {
				return;
			}
			const newBreakpointRange = modelData.model.getDecorationRange(decorationId);
			if (newBreakpointRange && !breakpointRange.equalsRange(newBreakpointRange)) {
				somethingChanged = true;
			}
		});
		if (!somethingChanged) {
			// nothing to do, my decorations did not change.
			return;
		}
<commit_msg>fixes #37506<commit_after>/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

import * as nls from 'vs/nls';
import { TPromise } from 'vs/base/common/winjs.base';
import * as objects from 'vs/base/common/objects';
import * as lifecycle from 'vs/base/common/lifecycle';
import { Constants } from 'vs/editor/common/core/uint';
import { Range } from 'vs/editor/common/core/range';
import { IModel, TrackedRangeStickiness, IModelDeltaDecoration, IModelDecorationOptions } from 'vs/editor/common/editorCommon';
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { IDebugService, IBreakpoint, IRawBreakpoint, State } from 'vs/workbench/parts/debug/common/debug';
import { IModelService } from 'vs/editor/common/services/modelService';
import { MarkdownString } from 'vs/base/common/htmlContent';

interface IDebugEditorModelData {
	model: IModel;
	toDispose: lifecycle.IDisposable[];
	breakpointDecorationIds: string[];
	breakpointModelIds: string[];
	breakpointDecorationsAsMap: Map<string, Range>;
	currentStackDecorations: string[];
	dirty: boolean;
	topStackFrameRange: Range;
}

const stickiness = TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges;

export class DebugEditorModelManager implements IWorkbenchContribution {
	static ID = 'breakpointManager';

	private modelDataMap: Map<string, IDebugEditorModelData>;
	private toDispose: lifecycle.IDisposable[];

	constructor(
		@IModelService private modelService: IModelService,
		@IDebugService private debugService: IDebugService
	) {
		this.modelDataMap = new Map<string, IDebugEditorModelData>();
		this.toDispose = [];
		this.registerListeners();
	}

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

	public dispose(): void {
		this.modelDataMap.forEach(modelData => {
			lifecycle.dispose(modelData.toDispose);
			modelData.model.deltaDecorations(modelData.breakpointDecorationIds, []);
			modelData.model.deltaDecorations(modelData.currentStackDecorations, []);
		});
		this.toDispose = lifecycle.dispose(this.toDispose);

		this.modelDataMap.clear();
	}

	private registerListeners(): void {
		this.toDispose.push(this.modelService.onModelAdded(this.onModelAdded, this));
		this.modelService.getModels().forEach(model => this.onModelAdded(model));
		this.toDispose.push(this.modelService.onModelRemoved(this.onModelRemoved, this));

		this.toDispose.push(this.debugService.getModel().onDidChangeBreakpoints(() => this.onBreakpointsChange()));
		this.toDispose.push(this.debugService.getViewModel().onDidFocusStackFrame(() => this.onFocusStackFrame()));
		this.toDispose.push(this.debugService.onDidChangeState(state => {
			if (state === State.Inactive) {
				this.modelDataMap.forEach(modelData => {
					modelData.dirty = false;
					modelData.topStackFrameRange = undefined;
				});
			}
		}));
	}

	private onModelAdded(model: IModel): void {
		const modelUrlStr = model.uri.toString();
		const breakpoints = this.debugService.getModel().getBreakpoints().filter(bp => bp.uri.toString() === modelUrlStr);

		const currentStackDecorations = model.deltaDecorations([], this.createCallStackDecorations(modelUrlStr));
		const desiredDecorations = this.createBreakpointDecorations(model, breakpoints);
		const breakPointDecorations = model.deltaDecorations([], desiredDecorations);

		const toDispose: lifecycle.IDisposable[] = [model.onDidChangeDecorations((e) => this.onModelDecorationsChanged(modelUrlStr))];
		const breakpointDecorationsAsMap = new Map<string, Range>();
		breakPointDecorations.forEach((decorationId, index) => breakpointDecorationsAsMap.set(decorationId, desiredDecorations[index].range));

		this.modelDataMap.set(modelUrlStr, {
			model: model,
			toDispose: toDispose,
			breakpointDecorationIds: breakPointDecorations,
			breakpointModelIds: breakpoints.map(bp => bp.getId()),
			breakpointDecorationsAsMap,
			currentStackDecorations: currentStackDecorations,
			dirty: false,
			topStackFrameRange: undefined
		});
	}

	private onModelRemoved(model: IModel): void {
		const modelUriStr = model.uri.toString();
		if (this.modelDataMap.has(modelUriStr)) {
			lifecycle.dispose(this.modelDataMap.get(modelUriStr).toDispose);
			this.modelDataMap.delete(modelUriStr);
		}
	}

	// call stack management. Represent data coming from the debug service.

	private onFocusStackFrame(): void {
		this.modelDataMap.forEach((modelData, uri) => {
			modelData.currentStackDecorations = modelData.model.deltaDecorations(modelData.currentStackDecorations, this.createCallStackDecorations(uri));
		});
	}

	private createCallStackDecorations(modelUriStr: string): IModelDeltaDecoration[] {
		const result: IModelDeltaDecoration[] = [];
		const stackFrame = this.debugService.getViewModel().focusedStackFrame;
		if (!stackFrame || stackFrame.source.uri.toString() !== modelUriStr) {
			return result;
		}

		// only show decorations for the currently focused thread.
		const columnUntilEOLRange = new Range(stackFrame.range.startLineNumber, stackFrame.range.startColumn, stackFrame.range.startLineNumber, Constants.MAX_SAFE_SMALL_INTEGER);
		const range = new Range(stackFrame.range.startLineNumber, stackFrame.range.startColumn, stackFrame.range.startLineNumber, stackFrame.range.startColumn + 1);

		// compute how to decorate the editor. Different decorations are used if this is a top stack frame, focused stack frame,
		// an exception or a stack frame that did not change the line number (we only decorate the columns, not the whole line).
		const callStack = stackFrame.thread.getCallStack();
		if (callStack && callStack.length && stackFrame === callStack[0]) {
			result.push({
				options: DebugEditorModelManager.TOP_STACK_FRAME_MARGIN,
				range
			});

			if (stackFrame.thread.stoppedDetails && stackFrame.thread.stoppedDetails.reason === 'exception') {
				result.push({
					options: DebugEditorModelManager.TOP_STACK_FRAME_EXCEPTION_DECORATION,
					range: columnUntilEOLRange
				});
			} else {
				result.push({
					options: DebugEditorModelManager.TOP_STACK_FRAME_DECORATION,
					range: columnUntilEOLRange
				});
				if (stackFrame.range.endLineNumber && stackFrame.range.endColumn) {
					result.push({
						options: { className: 'debug-top-stack-frame-range' },
						range: stackFrame.range
					});
				}

				if (this.modelDataMap.has(modelUriStr)) {
					const modelData = this.modelDataMap.get(modelUriStr);
					if (modelData.topStackFrameRange && modelData.topStackFrameRange.startLineNumber === stackFrame.range.startLineNumber && modelData.topStackFrameRange.startColumn !== stackFrame.range.startColumn) {
						result.push({
							options: DebugEditorModelManager.TOP_STACK_FRAME_INLINE_DECORATION,
							range: columnUntilEOLRange
						});
					}
					modelData.topStackFrameRange = columnUntilEOLRange;
				}
			}
		} else {
			result.push({
				options: DebugEditorModelManager.FOCUSED_STACK_FRAME_MARGIN,
				range
			});
			if (stackFrame.range.endLineNumber && stackFrame.range.endColumn) {
				result.push({
					options: { className: 'debug-focused-stack-frame-range' },
					range: stackFrame.range
				});
			}

			result.push({
				options: DebugEditorModelManager.FOCUSED_STACK_FRAME_DECORATION,
				range: columnUntilEOLRange
			});
		}

		return result;
	}

	// breakpoints management. Represent data coming from the debug service and also send data back.
	private onModelDecorationsChanged(modelUrlStr: string): void {
		const modelData = this.modelDataMap.get(modelUrlStr);
		if (modelData.breakpointDecorationsAsMap.size === 0) {
			// I have no decorations
			return;
		}
		let somethingChanged = false;
		modelData.breakpointDecorationsAsMap.forEach((breakpointRange, decorationId) => {
			if (somethingChanged) {
				return;
			}
			const newBreakpointRange = modelData.model.getDecorationRange(decorationId);
			if (newBreakpointRange && (breakpointRange.startColumn !== newBreakpointRange.startColumn || breakpointRange.endLineNumber !== newBreakpointRange.endLineNumber)) {
				somethingChanged = true;
			}
		});
		if (!somethingChanged) {
			// nothing to do, my decorations did not change.
			return;
		}

{'fraction_non_alphanumeric': 0.13120546140487455, 'fraction_numerical': 0.0006587220791664172, 'mean_word_length': 4.455733420450833, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 35, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4823141', 'n_tokens_mistral': 5524, 'n_tokens_neox': 4900, 'n_words': 924}