src/vs/editor/common/model/editStack.ts

451 LOC · 321 covered · 130 uncovered · 83 ranges · 1716 concepts · 13 introducers · 862 tests

File neighbourhood

The centred file is linked to every concept that introduces one of its ranges, every test that runs code from the file, and the gray connector concepts standing between those tests and the file's own introducer concepts. Undirected links join concepts to every file where they introduce source and concepts to the tests they introduce; arrows show specialization between the displayed concepts and bridge only concepts omitted from this view. Concept colors match the source ranges below; connector concepts have no source color and are shown in gray.

Focused file, its introducer and connector concepts, their introduced files, and tests that run code from the file

In the embedded map, ordinary wheel input scrolls the page; use the visible controls to zoom and drag to pan. Open the full-screen map for canvas navigation: wheel pans, Ctrl/Command plus wheel zooms, and arrow keys pan when this region is focused. On touch screens, open the full-screen map to pan or pinch. If JavaScript or WebGL is unavailable, use the related-file, concept, and source links on this page.

Graph controls are ready.

Interactive rendering requires JavaScript and WebGL. Use the related-file, concept, and source links on this page while the interactive map is unavailable.

1 > /*--------------------------------------------------------------------------------------------- editStack.ts ×46
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import * as nls from '../../../nls.js';
7 > import { onUnexpectedError } from '../../../base/common/errors.js';
8 > import { Selection } from '../core/selection.js';
9 > import { EndOfLineSequence, ICursorStateComputer, IValidEditOperation, ITextModel } from '../model.js';
10 > import { TextModel } from './textModel.js';
11 > import { IUndoRedoService, IResourceUndoRedoElement, UndoRedoElementType, IWorkspaceUndoRedoElement, UndoRedoGroup } from '../../../platform/undoRedo/common/undoRedo.js';
12 > import { URI } from '../../../base/common/uri.js';
13 > import { TextChange, compressConsecutiveTextChanges } from '../core/textChange.js';
14 > import * as buffer from '../../../base/common/buffer.js';
15 > import { IDisposable } from '../../../base/common/lifecycle.js';
16 > import { basename } from '../../../base/common/resources.js';
17 > import { ISingleEditOperation } from '../core/editOperation.js';
18 > import { EditSources, TextModelEditSource } from '../textModelEditSource.js';
19 >
20 function uriGetComparisonKey(resource: URI): string {
21 return resource.toString();
22 }
24 > export class SingleModelEditStackData {
25 >
26 > public static create(model: ITextModel, beforeCursorState: Selection[] | null): SingleModelEditStackData {
27 > const alternativeVersionId = model.getAlternativeVersionId();
28 > const eol = getModelEOL(model);
29 > return new SingleModelEditStackData(
30 > alternativeVersionId,
31 > alternativeVersionId,
32 > eol,
33 > eol,
34 > beforeCursorState,
35 > beforeCursorState,
36 > []
37 > );
38 > }
39 >
40 > constructor(
41 > public readonly beforeVersionId: number, editStack.ts ×1
42 > public afterVersionId: number,
43 > public readonly beforeEOL: EndOfLineSequence,
44 > public afterEOL: EndOfLineSequence,
45 > public readonly beforeCursorState: Selection[] | null,
46 > public afterCursorState: Selection[] | null,
47 > public changes: TextChange[]
48 > ) { }
50 > public append(model: ITextModel, textChanges: TextChange[], afterEOL: EndOfLineSequence, afterVersionId: number, afterCursorState: Selection[] | null): void {
51 > if (textChanges.length > 0) { editStack.ts ×15
52 > this.changes = compressConsecutiveTextChanges(this.changes, textChanges);
53 > }
54 > this.afterEOL = afterEOL;
55 > this.afterVersionId = afterVersionId;
56 > this.afterCursorState = afterCursorState;
57 > }
59 > private static _writeSelectionsSize(selections: Selection[] | null): number {
60 > return 4 + 4 * 4 * (selections ? selections.length : 0); editStack.ts ×4
61 > }
63 > private static _writeSelections(b: Uint8Array, selections: Selection[] | null, offset: number): number {
64 > buffer.writeUInt32BE(b, (selections ? selections.length : 0), offset); offset += 4; editStack.ts ×4
65 > if (selections) {
66 > for (const selection of selections) { editStack.ts ×1
67 > buffer.writeUInt32BE(b, selection.selectionStartLineNumber, offset); offset += 4;
68 > buffer.writeUInt32BE(b, selection.selectionStartColumn, offset); offset += 4;
69 > buffer.writeUInt32BE(b, selection.positionLineNumber, offset); offset += 4;
70 > buffer.writeUInt32BE(b, selection.positionColumn, offset); offset += 4;
71 > }
72 > }
73 > return offset; editStack.ts ×4
74 > }
76 > private static _readSelections(b: Uint8Array, offset: number, dest: Selection[]): number {
77 > const count = buffer.readUInt32BE(b, offset); offset += 4; editStack.ts ×3
78 > for (let i = 0; i < count; i++) {
79 > const selectionStartLineNumber = buffer.readUInt32BE(b, offset); offset += 4; editStack.ts ×1
80 > const selectionStartColumn = buffer.readUInt32BE(b, offset); offset += 4;
81 > const positionLineNumber = buffer.readUInt32BE(b, offset); offset += 4;
82 > const positionColumn = buffer.readUInt32BE(b, offset); offset += 4;
83 > dest.push(new Selection(selectionStartLineNumber, selectionStartColumn, positionLineNumber, positionColumn));
84 > }
85 > return offset; editStack.ts ×3
86 > }
88 > public serialize(): ArrayBuffer {
89 > let necessarySize = ( editStack.ts ×4
90 > + 4 // beforeVersionId
91 > + 4 // afterVersionId
92 > + 1 // beforeEOL
93 > + 1 // afterEOL
94 > + SingleModelEditStackData._writeSelectionsSize(this.beforeCursorState)
95 > + SingleModelEditStackData._writeSelectionsSize(this.afterCursorState)
96 > + 4 // change count
97 > );
98 > for (const change of this.changes) {
99 > necessarySize += change.writeSize();
100 > }
101 >
102 > const b = new Uint8Array(necessarySize);
103 > let offset = 0;
104 > buffer.writeUInt32BE(b, this.beforeVersionId, offset); offset += 4;
105 > buffer.writeUInt32BE(b, this.afterVersionId, offset); offset += 4;
106 > buffer.writeUInt8(b, this.beforeEOL, offset); offset += 1;
107 > buffer.writeUInt8(b, this.afterEOL, offset); offset += 1;
108 > offset = SingleModelEditStackData._writeSelections(b, this.beforeCursorState, offset);
109 > offset = SingleModelEditStackData._writeSelections(b, this.afterCursorState, offset);
110 > buffer.writeUInt32BE(b, this.changes.length, offset); offset += 4;
111 > for (const change of this.changes) {
112 > offset = change.write(b, offset);
113 > }
114 > return b.buffer;
115 > }
117 > public static deserialize(source: ArrayBuffer): SingleModelEditStackData {
118 > const b = new Uint8Array(source); editStack.ts ×3
119 > let offset = 0;
120 > const beforeVersionId = buffer.readUInt32BE(b, offset); offset += 4;
121 > const afterVersionId = buffer.readUInt32BE(b, offset); offset += 4;
122 > const beforeEOL = buffer.readUInt8(b, offset); offset += 1;
123 > const afterEOL = buffer.readUInt8(b, offset); offset += 1;
124 > const beforeCursorState: Selection[] = [];
125 > offset = SingleModelEditStackData._readSelections(b, offset, beforeCursorState);
126 > const afterCursorState: Selection[] = [];
127 > offset = SingleModelEditStackData._readSelections(b, offset, afterCursorState);
128 > const changeCount = buffer.readUInt32BE(b, offset); offset += 4;
129 > const changes: TextChange[] = [];
130 > for (let i = 0; i < changeCount; i++) {
131 > offset = TextChange.read(b, offset, changes);
132 > }
133 > return new SingleModelEditStackData(
134 > beforeVersionId,
135 > afterVersionId,
136 > beforeEOL,
137 > afterEOL,
138 > beforeCursorState,
139 > afterCursorState,
140 > changes
141 > );
142 > }
144 >
145 > export interface IUndoRedoDelegate {
146 > prepareUndoRedo(element: MultiModelEditStackElement): Promise<IDisposable> | IDisposable | void;
147 > }
148 >
149 > export class SingleModelEditStackElement implements IResourceUndoRedoElement {
150 >
151 > public model: ITextModel | URI;
152 > private _data: SingleModelEditStackData | ArrayBuffer;
153 >
154 > public get type(): UndoRedoElementType.Resource {
155 > return UndoRedoElementType.Resource;
156 > }
157 >
158 > public get resource(): URI {
159 > if (URI.isUri(this.model)) { editStack.ts ×15
160 return this.model;
161 }
162 > return this.model.uri; editStack.ts ×15
163 > }
165 > constructor(
166 > public readonly label: string, editStack.ts ×15
167 > public readonly code: string,
168 > model: ITextModel,
169 > beforeCursorState: Selection[] | null
170 > ) {
171 > this.model = model;
172 > this._data = SingleModelEditStackData.create(model, beforeCursorState);
173 > }
175 > public toString(): string {
176 const data = (this._data instanceof SingleModelEditStackData ? this._data : SingleModelEditStackData.deserialize(this._data));
177 return data.changes.map(change => change.toString()).join(', ');
178 }
180 > public matchesResource(resource: URI): boolean {
181 > const uri = (URI.isUri(this.model) ? this.model : this.model.uri); modelService.ts ×14
182 > return (uri.toString() === resource.toString());
183 > }
185 > public setModel(model: ITextModel | URI): void {
186 > this.model = model; modelService.ts ×14
187 > }
189 > public canAppend(model: ITextModel): boolean {
190 > return (this.model === model && this._data instanceof SingleModelEditStackData); textModel.ts ×6
191 > }
193 > public append(model: ITextModel, textChanges: TextChange[], afterEOL: EndOfLineSequence, afterVersionId: number, afterCursorState: Selection[] | null): void {
194 > if (this._data instanceof SingleModelEditStackData) { editStack.ts ×15
195 > this._data.append(model, textChanges, afterEOL, afterVersionId, afterCursorState);
196 > }
197 > }
199 > public close(): void {
200 if (this._data instanceof SingleModelEditStackData) {
201 this._data = this._data.serialize();
202 }
203 }
205 > public open(): void {
206 if (!(this._data instanceof SingleModelEditStackData)) {
207 this._data = SingleModelEditStackData.deserialize(this._data);
208 }
209 }
211 > public undo(): void {
212 > if (URI.isUri(this.model)) { editStack.ts ×3
213 // don't have a model
214 throw new Error(`Invalid SingleModelEditStackElement`);
215 }
216 > if (this._data instanceof SingleModelEditStackData) { editStack.ts ×3
217 > this._data = this._data.serialize(); textModel.ts ×6
218 > }
219 > const data = SingleModelEditStackData.deserialize(this._data); editStack.ts ×3
220 > this.model._applyUndo(data.changes, data.beforeEOL, data.beforeVersionId, data.beforeCursorState);
221 > }
223 > public redo(): void {
224 if (URI.isUri(this.model)) {
225 // don't have a model
226 throw new Error(`Invalid SingleModelEditStackElement`);
227 }
228 if (this._data instanceof SingleModelEditStackData) {
229 this._data = this._data.serialize();
230 }
231 const data = SingleModelEditStackData.deserialize(this._data);
232 this.model._applyRedo(data.changes, data.afterEOL, data.afterVersionId, data.afterCursorState);
233 }
235 > public heapSize(): number {
236 > if (this._data instanceof SingleModelEditStackData) { modelService.ts ×14
237 > this._data = this._data.serialize();
238 > }
239 > return this._data.byteLength + 168/*heap overhead*/;
240 > }
242 >
243 > export class MultiModelEditStackElement implements IWorkspaceUndoRedoElement {
244 >
245 > public readonly type = UndoRedoElementType.Workspace;
246 > private _isOpen: boolean;
247 >
248 > private readonly _editStackElementsArr: SingleModelEditStackElement[];
249 > private readonly _editStackElementsMap: Map<string, SingleModelEditStackElement>;
250 >
251 > private _delegate: IUndoRedoDelegate | null;
252 >
253 > public get resources(): readonly URI[] {
254 > return this._editStackElementsArr.map(editStackElement => editStackElement.resource);
255 > }
256 >
257 > constructor(
258 public readonly label: string,
259 public readonly code: string,
260 editStackElements: SingleModelEditStackElement[]
261 ) {
262 this._isOpen = true;
263 this._editStackElementsArr = editStackElements.slice(0);
264 this._editStackElementsMap = new Map<string, SingleModelEditStackElement>();
265 for (const editStackElement of this._editStackElementsArr) {
266 const key = uriGetComparisonKey(editStackElement.resource);
267 this._editStackElementsMap.set(key, editStackElement);
268 }
269 this._delegate = null;
270 }
272 > public setDelegate(delegate: IUndoRedoDelegate): void {
273 this._delegate = delegate;
274 }
276 > public prepareUndoRedo(): Promise<IDisposable> | IDisposable | void {
277 if (this._delegate) {
278 return this._delegate.prepareUndoRedo(this);
279 }
280 }
282 > public getMissingModels(): URI[] {
283 const result: URI[] = [];
284 for (const editStackElement of this._editStackElementsArr) {
285 if (URI.isUri(editStackElement.model)) {
286 result.push(editStackElement.model);
287 }
288 }
289 return result;
290 }
292 > public matchesResource(resource: URI): boolean {
293 const key = uriGetComparisonKey(resource);
294 return (this._editStackElementsMap.has(key));
295 }
297 > public setModel(model: ITextModel | URI): void {
298 const key = uriGetComparisonKey(URI.isUri(model) ? model : model.uri);
299 if (this._editStackElementsMap.has(key)) {
300 this._editStackElementsMap.get(key)!.setModel(model);
301 }
302 }
304 > public canAppend(model: ITextModel): boolean {
305 if (!this._isOpen) {
306 return false;
307 }
308 const key = uriGetComparisonKey(model.uri);
309 if (this._editStackElementsMap.has(key)) {
310 const editStackElement = this._editStackElementsMap.get(key)!;
311 return editStackElement.canAppend(model);
312 }
313 return false;
314 }
316 > public append(model: ITextModel, textChanges: TextChange[], afterEOL: EndOfLineSequence, afterVersionId: number, afterCursorState: Selection[] | null): void {
317 const key = uriGetComparisonKey(model.uri);
318 const editStackElement = this._editStackElementsMap.get(key)!;
319 editStackElement.append(model, textChanges, afterEOL, afterVersionId, afterCursorState);
320 }
322 > public close(): void {
323 this._isOpen = false;
324 }
326 > public open(): void {
327 // cannot reopen
328 }
330 > public undo(): void {
331 this._isOpen = false;
332
333 for (const editStackElement of this._editStackElementsArr) {
334 editStackElement.undo();
335 }
336 }
338 > public redo(): void {
339 for (const editStackElement of this._editStackElementsArr) {
340 editStackElement.redo();
341 }
342 }
344 > public heapSize(resource: URI): number {
345 const key = uriGetComparisonKey(resource);
346 if (this._editStackElementsMap.has(key)) {
347 const editStackElement = this._editStackElementsMap.get(key)!;
348 return editStackElement.heapSize();
349 }
350 return 0;
351 }
353 > public split(): IResourceUndoRedoElement[] {
354 return this._editStackElementsArr;
355 }
357 > public toString(): string {
358 const result: string[] = [];
359 for (const editStackElement of this._editStackElementsArr) {
360 result.push(`${basename(editStackElement.resource)}: ${editStackElement}`);
361 }
362 return `{${result.join(', ')}}`;
363 }
365 >
366 > export type EditStackElement = SingleModelEditStackElement | MultiModelEditStackElement;
367 >
368 > function getModelEOL(model: ITextModel): EndOfLineSequence { editStack.ts ×15
369 > const eol = model.getEOL();
370 > if (eol === '\n') {
371 > return EndOfLineSequence.LF;
372 > } else {
373 return EndOfLineSequence.CRLF;
374 }
377 > export function isEditStackElement(element: IResourceUndoRedoElement | IWorkspaceUndoRedoElement | null): element is EditStackElement {
378 > if (!element) { editStack.ts ×15
379 > return false;
380 > }
381 > return ((element instanceof SingleModelEditStackElement) || (element instanceof MultiModelEditStackElement)); editStack.ts ×1
384 > export class EditStack {
385 >
386 > private readonly _model: TextModel;
387 > private readonly _undoRedoService: IUndoRedoService;
388 >
389 > constructor(model: TextModel, undoRedoService: IUndoRedoService) {
390 > this._model = model; textModel.ts ×20
391 > this._undoRedoService = undoRedoService;
392 > }
394 > public pushStackElement(): void {
395 const lastElement = this._undoRedoService.getLastElement(this._model.uri);
396 if (isEditStackElement(lastElement)) {
397 lastElement.close();
398 }
399 }
401 > public popStackElement(): void {
402 const lastElement = this._undoRedoService.getLastElement(this._model.uri);
403 if (isEditStackElement(lastElement)) {
404 lastElement.open();
405 }
406 }
408 > public clear(): void {
409 > this._undoRedoService.removeElements(this._model.uri); textModel.ts ×3
410 > }
412 > private _getOrCreateEditStackElement(beforeCursorState: Selection[] | null, group: UndoRedoGroup | undefined): EditStackElement {
413 > const lastElement = this._undoRedoService.getLastElement(this._model.uri); editStack.ts ×15
414 > if (isEditStackElement(lastElement) && lastElement.canAppend(this._model)) {
415 > return lastElement; textModel.ts ×6
416 > }
417 > const newElement = new SingleModelEditStackElement(nls.localize('edit', "Typing"), 'undoredo.textBufferEdit', this._model, beforeCursorState); editStack.ts ×15
418 > this._undoRedoService.pushElement(newElement, group);
419 > return newElement;
420 > }
422 > public pushEOL(eol: EndOfLineSequence): void {
423 const editStackElement = this._getOrCreateEditStackElement(null, undefined);
424 this._model.setEOL(eol);
425 editStackElement.append(this._model, [], getModelEOL(this._model), this._model.getAlternativeVersionId(), null);
426 }
428 > public pushEditOperation(beforeCursorState: Selection[] | null, editOperations: ISingleEditOperation[], cursorStateComputer: ICursorStateComputer | null, group?: UndoRedoGroup, reason: TextModelEditSource = EditSources.unknown({ name: 'pushEditOperation' })): Selection[] | null {
429 > const editStackElement = this._getOrCreateEditStackElement(beforeCursorState, group); editStack.ts ×15
430 > const inverseEditOperations = this._model.applyEdits(editOperations, true, reason);
431 > const afterCursorState = EditStack._computeCursorState(cursorStateComputer, inverseEditOperations);
432 > const textChanges = inverseEditOperations.map((op, index) => ({ index: index, textChange: op.textChange }));
433 > textChanges.sort((a, b) => {
434 if (a.textChange.oldPosition === b.textChange.oldPosition) {
435 return a.index - b.index;
436 }
437 return a.textChange.oldPosition - b.textChange.oldPosition;
438 > }); editStack.ts ×15
439 > editStackElement.append(this._model, textChanges.map(op => op.textChange), getModelEOL(this._model), this._model.getAlternativeVersionId(), afterCursorState);
440 > return afterCursorState;
441 > }
443 > private static _computeCursorState(cursorStateComputer: ICursorStateComputer | null, inverseEditOperations: IValidEditOperation[]): Selection[] | null {
444 > try { editStack.ts ×15
445 > return cursorStateComputer ? cursorStateComputer(inverseEditOperations) : null;
446 > } catch (e) {
447 onUnexpectedError(e);
448 return null;
449 }