<commit_before>import * as _ from "lodash";
import * as path from "path";
import * as vs from "vscode";
import { DebugCommands } from "../commands/debug";
import { CoverageData } from "../debug/utils";
import { extensionPath } from "../extension";
import { fsPath } from "../utils";
import { logError } from "../utils/log";

export class HotReloadCoverageDecorations implements vs.Disposable {
	private subscriptions: vs.Disposable[] = [];
	private fileState: {
		[key: string]: {
			modified: number[],
			notRun: number[],
		},
	} = {};
	private isDebugging = false;

	private readonly modifiedDecorationType = vs.window.createTextEditorDecorationType({
		gutterIconPath: vs.Uri.file(path.join(extensionPath, `media/icons/reload_coverage/modified.svg`)),
		isWholeLine: true,
		rangeBehavior: vs.DecorationRangeBehavior.OpenOpen,
	});
	private readonly notRunDecorationType = vs.window.createTextEditorDecorationType({
		gutterIconPath: vs.Uri.file(path.join(extensionPath, `media/icons/reload_coverage/not_run.svg`)),
		isWholeLine: true,
		rangeBehavior: vs.DecorationRangeBehavior.OpenOpen,
	});

	constructor(debug: DebugCommands) {
		this.subscriptions.push(vs.workspace.onDidChangeTextDocument((e) => this.onDidChangeTextDocument(e)));
		this.subscriptions.push(debug.onFirstFrame(() => this.onFirstFrame()));
		this.subscriptions.push(vs.window.onDidChangeVisibleTextEditors((e) => this.onDidChangeVisibleTextEditors(e)));
		this.subscriptions.push(debug.onWillHotReload(() => this.onWillHotReload()));
		this.subscriptions.push(debug.onWillHotRestart(() => this.onWillFullRestart()));
		this.subscriptions.push(vs.debug.onDidStartDebugSession((e) => this.onDidStartDebugSession()));
		this.subscriptions.push(vs.debug.onDidTerminateDebugSession((e) => this.onDidTerminateDebugSession()));
		this.subscriptions.push(debug.onReceiveCoverage((c) => this.onReceiveCoverage(c)));
		// TODO: On execution, remove from notRun list
		// TODO: If file modified externally, we may need to drop all markers?
	}

	private async onFirstFrame(): Promise<void> {
		await this.coverageFilesUpdate();
	}

	private async onDidChangeVisibleTextEditors(editors: vs.TextEditor[]): Promise<void> {
		this.redrawDecorations(editors);
		await this.coverageFilesUpdate();
		await this.requestCoverageUpdate();
	}

	private onDidChangeTextDocument(e: vs.TextDocumentChangeEvent) {
		if (!this.isDebugging)
			return;

		const editor = vs.window.visibleTextEditors.find((editor) => editor.document.uri === e.document.uri);
		if (!editor)
			return;

		let fileState = this.fileState[fsPath(e.document.uri)];
		if (!fileState) {
			fileState = this.fileState[fsPath(e.document.uri)] = { modified: [], notRun: [] };
		}

		// Move all "not run" edits back into "edited" because we can't track them anymore as the coverage
		// data will be bad.
		fileState.modified = fileState.modified.concat(fileState.notRun);
		fileState.notRun = [];

		// Update all existing ranges offsets.
		for (const change of e.contentChanges) {
			const startLine = change.range.start.line;
			const endLine = change.range.end.line;
			const linesInRange = endLine - startLine;
			const linesInserted = change.text.split("\n").length - 1;
			const diff = linesInserted - linesInRange;
			if (diff === 0)
				continue;

			fileState.modified = this.translateChanges(fileState.modified, startLine, endLine, diff);
		}

		// Append the new ranges.
		for (const change of e.contentChanges) {
			if (change.rangeLength === 0 && change.text.length)
				continue;

			// If the replacement text is the same as the old text, don't mark it as changed.
			if (change.rangeLength === change.text.length && change.text === editor.document.getText(change.range))
				continue;
<commit_msg>Improve reliability of tracking<commit_after>import * as _ from "lodash";
import * as path from "path";
import * as vs from "vscode";
import { DebugCommands } from "../commands/debug";
import { CoverageData } from "../debug/utils";
import { extensionPath } from "../extension";
import { fsPath } from "../utils";
import { logError } from "../utils/log";

export class HotReloadCoverageDecorations implements vs.Disposable {
	private subscriptions: vs.Disposable[] = [];
	private fileState: {
		[key: string]: {
			modified: number[],
			notRun: number[],
		},
	} = {};
	private isDebugging = false;

	private readonly modifiedDecorationType = vs.window.createTextEditorDecorationType({
		gutterIconPath: vs.Uri.file(path.join(extensionPath, `media/icons/reload_coverage/modified.svg`)),
		isWholeLine: true,
		rangeBehavior: vs.DecorationRangeBehavior.OpenOpen,
	});
	private readonly notRunDecorationType = vs.window.createTextEditorDecorationType({
		gutterIconPath: vs.Uri.file(path.join(extensionPath, `media/icons/reload_coverage/not_run.svg`)),
		isWholeLine: true,
		rangeBehavior: vs.DecorationRangeBehavior.OpenOpen,
	});

	constructor(debug: DebugCommands) {
		this.subscriptions.push(vs.workspace.onDidChangeTextDocument((e) => this.onDidChangeTextDocument(e)));
		this.subscriptions.push(debug.onFirstFrame(() => this.onFirstFrame()));
		this.subscriptions.push(vs.window.onDidChangeVisibleTextEditors((e) => this.onDidChangeVisibleTextEditors(e)));
		this.subscriptions.push(debug.onWillHotReload(() => this.onWillHotReload()));
		this.subscriptions.push(debug.onWillHotRestart(() => this.onWillFullRestart()));
		this.subscriptions.push(vs.debug.onDidStartDebugSession((e) => this.onDidStartDebugSession()));
		this.subscriptions.push(vs.debug.onDidTerminateDebugSession((e) => this.onDidTerminateDebugSession()));
		this.subscriptions.push(debug.onReceiveCoverage((c) => this.onReceiveCoverage(c)));
		// TODO: On execution, remove from notRun list
		// TODO: If file modified externally, we may need to drop all markers?
	}

	private async onFirstFrame(): Promise<void> {
		await this.coverageFilesUpdate();
	}

	private async onDidChangeVisibleTextEditors(editors: vs.TextEditor[]): Promise<void> {
		this.redrawDecorations(editors);
		await this.coverageFilesUpdate();
		await this.requestCoverageUpdate();
	}

	private onDidChangeTextDocument(e: vs.TextDocumentChangeEvent) {
		if (!this.isDebugging)
			return;

		const editor = vs.window.visibleTextEditors.find((editor) => editor.document.uri === e.document.uri);
		if (!editor)
			return;

		let fileState = this.fileState[fsPath(e.document.uri)];
		if (!fileState) {
			fileState = this.fileState[fsPath(e.document.uri)] = { modified: [], notRun: [] };
		}

		// Move all "not run" edits back into "edited" because we can't track them anymore as the coverage
		// data will be bad.
		fileState.modified = fileState.modified.concat(fileState.notRun);
		fileState.notRun = [];

		// Update all existing ranges offsets.
		for (const change of e.contentChanges) {
			const startLine = change.range.start.line;
			const endLine = change.range.end.line;
			const linesInRange = endLine - startLine;
			const linesInserted = change.text.split("\n").length - 1;
			const diff = linesInserted - linesInRange;
			if (diff === 0)
				continue;

			fileState.modified = this.translateChanges(fileState.modified, startLine, endLine, diff);
		}

		// Append the new ranges.
		for (const change of e.contentChanges) {
			const originalText = editor.document.getText(change.range);
			const newText = change.text;

			// Don't mark if the replacement text is the same as the old text.
			if (newText === originalText)
				continue;

			// Don't mark if we're just deleting whitespace (eg. backspace on a newline).
			if (originalText.trim() === "" && !newText)
				continue;

			// If we're just adding whitespace, don't mark that.
			if (!originalText && newText.trim() === "")
				continue;

{'fraction_non_alphanumeric': 0.13505074160811867, 'fraction_numerical': 0.0006505334374186833, 'mean_word_length': 4.75374251497006, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 25, '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': '1882655', 'n_tokens_mistral': 2563, 'n_tokens_neox': 2339, 'n_words': 523}