/
mirrorTextModel.ts
161 lines (139 loc) · 4.9 KB
/
mirrorTextModel.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { URI } from 'vs/base/common/uri';
import { IRange } from 'vs/editor/common/core/range';
import { PrefixSumComputer } from 'vs/editor/common/viewModel/prefixSumComputer';
import { IModelContentChange } from 'vs/editor/common/model/textModelEvents';
import { Position } from 'vs/editor/common/core/position';
export interface IModelChangedEvent {
/**
* The actual changes.
*/
readonly changes: IModelContentChange[];
/**
* The (new) end-of-line character.
*/
readonly eol: string;
/**
* The new version id the model has transitioned to.
*/
readonly versionId: number;
}
export class MirrorTextModel {
protected _uri: URI;
protected _lines: string[];
protected _eol: string;
protected _versionId: number;
protected _lineStarts: PrefixSumComputer | null;
constructor(uri: URI, lines: string[], eol: string, versionId: number) {
this._uri = uri;
this._lines = lines;
this._eol = eol;
this._versionId = versionId;
this._lineStarts = null;
}
dispose(): void {
this._lines.length = 0;
}
get version(): number {
return this._versionId;
}
getText(): string {
return this._lines.join(this._eol);
}
onEvents(e: IModelChangedEvent): void {
if (e.eol && e.eol !== this._eol) {
this._eol = e.eol;
this._lineStarts = null;
}
// Update my lines
const changes = e.changes;
for (let i = 0, len = changes.length; i < len; i++) {
const change = changes[i];
this._acceptDeleteRange(change.range);
this._acceptInsertText(new Position(change.range.startLineNumber, change.range.startColumn), change.text);
}
this._versionId = e.versionId;
}
protected _ensureLineStarts(): void {
if (!this._lineStarts) {
const eolLength = this._eol.length;
const linesLength = this._lines.length;
const lineStartValues = new Uint32Array(linesLength);
for (let i = 0; i < linesLength; i++) {
lineStartValues[i] = this._lines[i].length + eolLength;
}
this._lineStarts = new PrefixSumComputer(lineStartValues);
}
}
/**
* All changes to a line's text go through this method
*/
private _setLineText(lineIndex: number, newValue: string): void {
this._lines[lineIndex] = newValue;
if (this._lineStarts) {
// update prefix sum
this._lineStarts.changeValue(lineIndex, this._lines[lineIndex].length + this._eol.length);
}
}
private _acceptDeleteRange(range: IRange): void {
if (range.startLineNumber === range.endLineNumber) {
if (range.startColumn === range.endColumn) {
// Nothing to delete
return;
}
// Delete text on the affected line
this._setLineText(range.startLineNumber - 1,
this._lines[range.startLineNumber - 1].substring(0, range.startColumn - 1)
+ this._lines[range.startLineNumber - 1].substring(range.endColumn - 1)
);
return;
}
// Take remaining text on last line and append it to remaining text on first line
this._setLineText(range.startLineNumber - 1,
this._lines[range.startLineNumber - 1].substring(0, range.startColumn - 1)
+ this._lines[range.endLineNumber - 1].substring(range.endColumn - 1)
);
// Delete middle lines
this._lines.splice(range.startLineNumber, range.endLineNumber - range.startLineNumber);
if (this._lineStarts) {
// update prefix sum
this._lineStarts.removeValues(range.startLineNumber, range.endLineNumber - range.startLineNumber);
}
}
private _acceptInsertText(position: Position, insertText: string): void {
if (insertText.length === 0) {
// Nothing to insert
return;
}
let insertLines = insertText.split(/\r\n|\r|\n/);
if (insertLines.length === 1) {
// Inserting text on one line
this._setLineText(position.lineNumber - 1,
this._lines[position.lineNumber - 1].substring(0, position.column - 1)
+ insertLines[0]
+ this._lines[position.lineNumber - 1].substring(position.column - 1)
);
return;
}
// Append overflowing text from first line to the end of text to insert
insertLines[insertLines.length - 1] += this._lines[position.lineNumber - 1].substring(position.column - 1);
// Delete overflowing text from first line and insert text on first line
this._setLineText(position.lineNumber - 1,
this._lines[position.lineNumber - 1].substring(0, position.column - 1)
+ insertLines[0]
);
// Insert new lines & store lengths
let newLengths = new Uint32Array(insertLines.length - 1);
for (let i = 1; i < insertLines.length; i++) {
this._lines.splice(position.lineNumber + i - 1, 0, insertLines[i]);
newLengths[i - 1] = insertLines[i].length + this._eol.length;
}
if (this._lineStarts) {
// update prefix sum
this._lineStarts.insertValues(position.lineNumber, newLengths);
}
}
}