Path: blob/main/src/vs/editor/common/model/mirrorTextModel.ts
3294 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*--------------------------------------------------------------------------------------------*/45import { splitLines } from '../../../base/common/strings.js';6import { URI } from '../../../base/common/uri.js';7import { Position } from '../core/position.js';8import { IRange } from '../core/range.js';9import { PrefixSumComputer } from './prefixSumComputer.js';1011export interface IModelContentChange {12/**13* The old range that got replaced.14*/15readonly range: IRange;16/**17* The offset of the range that got replaced.18*/19readonly rangeOffset: number;20/**21* The length of the range that got replaced.22*/23readonly rangeLength: number;24/**25* The new text for the range.26*/27readonly text: string;28}2930export interface IModelChangedEvent {31/**32* The actual changes.33*/34readonly changes: IModelContentChange[];35/**36* The (new) end-of-line character.37*/38readonly eol: string;39/**40* The new version id the model has transitioned to.41*/42readonly versionId: number;43/**44* Flag that indicates that this event was generated while undoing.45*/46readonly isUndoing: boolean;47/**48* Flag that indicates that this event was generated while redoing.49*/50readonly isRedoing: boolean;51}5253export interface IMirrorTextModel {54readonly version: number;55}5657export class MirrorTextModel implements IMirrorTextModel {5859protected _uri: URI;60protected _lines: string[];61protected _eol: string;62protected _versionId: number;63protected _lineStarts: PrefixSumComputer | null;64private _cachedTextValue: string | null;6566constructor(uri: URI, lines: string[], eol: string, versionId: number) {67this._uri = uri;68this._lines = lines;69this._eol = eol;70this._versionId = versionId;71this._lineStarts = null;72this._cachedTextValue = null;73}7475dispose(): void {76this._lines.length = 0;77}7879get version(): number {80return this._versionId;81}8283getText(): string {84if (this._cachedTextValue === null) {85this._cachedTextValue = this._lines.join(this._eol);86}87return this._cachedTextValue;88}8990onEvents(e: IModelChangedEvent): void {91if (e.eol && e.eol !== this._eol) {92this._eol = e.eol;93this._lineStarts = null;94}9596// Update my lines97const changes = e.changes;98for (const change of changes) {99this._acceptDeleteRange(change.range);100this._acceptInsertText(new Position(change.range.startLineNumber, change.range.startColumn), change.text);101}102103this._versionId = e.versionId;104this._cachedTextValue = null;105}106107protected _ensureLineStarts(): void {108if (!this._lineStarts) {109const eolLength = this._eol.length;110const linesLength = this._lines.length;111const lineStartValues = new Uint32Array(linesLength);112for (let i = 0; i < linesLength; i++) {113lineStartValues[i] = this._lines[i].length + eolLength;114}115this._lineStarts = new PrefixSumComputer(lineStartValues);116}117}118119/**120* All changes to a line's text go through this method121*/122private _setLineText(lineIndex: number, newValue: string): void {123this._lines[lineIndex] = newValue;124if (this._lineStarts) {125// update prefix sum126this._lineStarts.setValue(lineIndex, this._lines[lineIndex].length + this._eol.length);127}128}129130private _acceptDeleteRange(range: IRange): void {131132if (range.startLineNumber === range.endLineNumber) {133if (range.startColumn === range.endColumn) {134// Nothing to delete135return;136}137// Delete text on the affected line138this._setLineText(range.startLineNumber - 1,139this._lines[range.startLineNumber - 1].substring(0, range.startColumn - 1)140+ this._lines[range.startLineNumber - 1].substring(range.endColumn - 1)141);142return;143}144145// Take remaining text on last line and append it to remaining text on first line146this._setLineText(range.startLineNumber - 1,147this._lines[range.startLineNumber - 1].substring(0, range.startColumn - 1)148+ this._lines[range.endLineNumber - 1].substring(range.endColumn - 1)149);150151// Delete middle lines152this._lines.splice(range.startLineNumber, range.endLineNumber - range.startLineNumber);153if (this._lineStarts) {154// update prefix sum155this._lineStarts.removeValues(range.startLineNumber, range.endLineNumber - range.startLineNumber);156}157}158159private _acceptInsertText(position: Position, insertText: string): void {160if (insertText.length === 0) {161// Nothing to insert162return;163}164const insertLines = splitLines(insertText);165if (insertLines.length === 1) {166// Inserting text on one line167this._setLineText(position.lineNumber - 1,168this._lines[position.lineNumber - 1].substring(0, position.column - 1)169+ insertLines[0]170+ this._lines[position.lineNumber - 1].substring(position.column - 1)171);172return;173}174175// Append overflowing text from first line to the end of text to insert176insertLines[insertLines.length - 1] += this._lines[position.lineNumber - 1].substring(position.column - 1);177178// Delete overflowing text from first line and insert text on first line179this._setLineText(position.lineNumber - 1,180this._lines[position.lineNumber - 1].substring(0, position.column - 1)181+ insertLines[0]182);183184// Insert new lines & store lengths185const newLengths = new Uint32Array(insertLines.length - 1);186for (let i = 1; i < insertLines.length; i++) {187this._lines.splice(position.lineNumber + i - 1, 0, insertLines[i]);188newLengths[i - 1] = insertLines[i].length + this._eol.length;189}190191if (this._lineStarts) {192// update prefix sum193this._lineStarts.insertValues(position.lineNumber, newLengths);194}195}196}197198199