Path: blob/main/src/vs/workbench/contrib/editTelemetry/browser/telemetry/editTracker.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/456import { Disposable } from '../../../../../base/common/lifecycle.js';7import { observableSignal, runOnChange, IReader } from '../../../../../base/common/observable.js';8import { AnnotatedStringEdit } from '../../../../../editor/common/core/edits/stringEdit.js';9import { OffsetRange } from '../../../../../editor/common/core/ranges/offsetRange.js';10import { TextModelEditSource } from '../../../../../editor/common/textModelEditSource.js';11import { IDocumentWithAnnotatedEdits, EditKeySourceData, EditSource } from '../helpers/documentWithAnnotatedEdits.js';1213/**14* Tracks a single document.15*/16export class DocumentEditSourceTracker<T = void> extends Disposable {17private _edits: AnnotatedStringEdit<EditKeySourceData> = AnnotatedStringEdit.empty;18private _pendingExternalEdits: AnnotatedStringEdit<EditKeySourceData> = AnnotatedStringEdit.empty;1920private readonly _update = observableSignal(this);21private readonly _sumAddedCharactersPerKey: Map<string, number> = new Map();2223constructor(24private readonly _doc: IDocumentWithAnnotatedEdits,25public readonly data: T,26) {27super();2829this._register(runOnChange(this._doc.value, (_val, _prevVal, edits) => {30const eComposed = AnnotatedStringEdit.compose(edits.map(e => e.edit));31if (eComposed.replacements.every(e => e.data.source.category === 'external')) {32if (this._edits.isEmpty()) {33// Ignore initial external edits34} else {35// queue pending external edits36this._pendingExternalEdits = this._pendingExternalEdits.compose(eComposed);37}38} else {39if (!this._pendingExternalEdits.isEmpty()) {40this._applyEdit(this._pendingExternalEdits);41this._pendingExternalEdits = AnnotatedStringEdit.empty;42}43this._applyEdit(eComposed);44}4546this._update.trigger(undefined);47}));48}4950private _applyEdit(e: AnnotatedStringEdit<EditKeySourceData>): void {51for (const r of e.replacements) {52const existing = this._sumAddedCharactersPerKey.get(r.data.key) ?? 0;53const newCount = existing + r.getNewLength();54this._sumAddedCharactersPerKey.set(r.data.key, newCount);55}5657this._edits = this._edits.compose(e);58}5960async waitForQueue(): Promise<void> {61await this._doc.waitForQueue();62}6364public getChangedCharactersCount(key: string): number {65const val = this._sumAddedCharactersPerKey.get(key);66return val ?? 0;67}6869getTrackedRanges(reader?: IReader): TrackedEdit[] {70this._update.read(reader);71const ranges = this._edits.getNewRanges();72return ranges.map((r, idx) => {73const e = this._edits.replacements[idx];74const te = new TrackedEdit(e.replaceRange, r, e.data.key, e.data.source, e.data.representative);75return te;76});77}7879isEmpty(): boolean {80return this._edits.isEmpty();81}8283public reset(): void {84this._edits = AnnotatedStringEdit.empty;85}8687public _getDebugVisualization() {88const ranges = this.getTrackedRanges();89const txt = this._doc.value.get().value;9091return {92...{ $fileExtension: 'text.w' },93'value': txt,94'decorations': ranges.map(r => {95return {96range: [r.range.start, r.range.endExclusive],97color: r.source.getColor(),98};99})100};101}102}103104export class TrackedEdit {105constructor(106public readonly originalRange: OffsetRange,107public readonly range: OffsetRange,108public readonly sourceKey: string,109public readonly source: EditSource,110public readonly sourceRepresentative: TextModelEditSource,111) { }112}113114115