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.
/*---------------------------------------------------------------------------------------------
editStack.ts ×46
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as nls from '../../../nls.js';
import { onUnexpectedError } from '../../../base/common/errors.js';
import { Selection } from '../core/selection.js';
import { EndOfLineSequence, ICursorStateComputer, IValidEditOperation, ITextModel } from '../model.js';
import { TextModel } from './textModel.js';
import { IUndoRedoService, IResourceUndoRedoElement, UndoRedoElementType, IWorkspaceUndoRedoElement, UndoRedoGroup } from '../../../platform/undoRedo/common/undoRedo.js';
import { URI } from '../../../base/common/uri.js';
import { TextChange, compressConsecutiveTextChanges } from '../core/textChange.js';
import * as buffer from '../../../base/common/buffer.js';
import { IDisposable } from '../../../base/common/lifecycle.js';
import { basename } from '../../../base/common/resources.js';
import { ISingleEditOperation } from '../core/editOperation.js';
import { EditSources, TextModelEditSource } from '../textModelEditSource.js';
function uriGetComparisonKey(resource: URI): string {
return resource.toString();
}
export class SingleModelEditStackData {
public static create(model: ITextModel, beforeCursorState: Selection[] | null): SingleModelEditStackData {
const alternativeVersionId = model.getAlternativeVersionId();
const eol = getModelEOL(model);
return new SingleModelEditStackData(
alternativeVersionId,
alternativeVersionId,
eol,
eol,
beforeCursorState,
beforeCursorState,
[]
);
}
constructor(
public afterVersionId: number,
public readonly beforeEOL: EndOfLineSequence,
public afterEOL: EndOfLineSequence,
public readonly beforeCursorState: Selection[] | null,
public afterCursorState: Selection[] | null,
public changes: TextChange[]
) { }
public append(model: ITextModel, textChanges: TextChange[], afterEOL: EndOfLineSequence, afterVersionId: number, afterCursorState: Selection[] | null): void {
this.changes = compressConsecutiveTextChanges(this.changes, textChanges);
}
this.afterEOL = afterEOL;
this.afterVersionId = afterVersionId;
this.afterCursorState = afterCursorState;
}
private static _writeSelectionsSize(selections: Selection[] | null): number {
}
private static _writeSelections(b: Uint8Array, selections: Selection[] | null, offset: number): number {
buffer.writeUInt32BE(b, (selections ? selections.length : 0), offset); offset += 4;
editStack.ts ×4
if (selections) {
buffer.writeUInt32BE(b, selection.selectionStartLineNumber, offset); offset += 4;
buffer.writeUInt32BE(b, selection.selectionStartColumn, offset); offset += 4;
buffer.writeUInt32BE(b, selection.positionLineNumber, offset); offset += 4;
buffer.writeUInt32BE(b, selection.positionColumn, offset); offset += 4;
}
}
}
private static _readSelections(b: Uint8Array, offset: number, dest: Selection[]): number {
for (let i = 0; i < count; i++) {
const selectionStartColumn = buffer.readUInt32BE(b, offset); offset += 4;
const positionLineNumber = buffer.readUInt32BE(b, offset); offset += 4;
const positionColumn = buffer.readUInt32BE(b, offset); offset += 4;
dest.push(new Selection(selectionStartLineNumber, selectionStartColumn, positionLineNumber, positionColumn));
}
}
public serialize(): ArrayBuffer {
+ 4 // beforeVersionId
+ 4 // afterVersionId
+ 1 // beforeEOL
+ 1 // afterEOL
+ SingleModelEditStackData._writeSelectionsSize(this.beforeCursorState)
+ SingleModelEditStackData._writeSelectionsSize(this.afterCursorState)
+ 4 // change count
);
for (const change of this.changes) {
necessarySize += change.writeSize();
}
const b = new Uint8Array(necessarySize);
let offset = 0;
buffer.writeUInt32BE(b, this.beforeVersionId, offset); offset += 4;
buffer.writeUInt32BE(b, this.afterVersionId, offset); offset += 4;
buffer.writeUInt8(b, this.beforeEOL, offset); offset += 1;
buffer.writeUInt8(b, this.afterEOL, offset); offset += 1;
offset = SingleModelEditStackData._writeSelections(b, this.beforeCursorState, offset);
offset = SingleModelEditStackData._writeSelections(b, this.afterCursorState, offset);
buffer.writeUInt32BE(b, this.changes.length, offset); offset += 4;
for (const change of this.changes) {
offset = change.write(b, offset);
}
return b.buffer;
}
public static deserialize(source: ArrayBuffer): SingleModelEditStackData {
let offset = 0;
const beforeVersionId = buffer.readUInt32BE(b, offset); offset += 4;
const afterVersionId = buffer.readUInt32BE(b, offset); offset += 4;
const beforeEOL = buffer.readUInt8(b, offset); offset += 1;
const afterEOL = buffer.readUInt8(b, offset); offset += 1;
const beforeCursorState: Selection[] = [];
offset = SingleModelEditStackData._readSelections(b, offset, beforeCursorState);
const afterCursorState: Selection[] = [];
offset = SingleModelEditStackData._readSelections(b, offset, afterCursorState);
const changeCount = buffer.readUInt32BE(b, offset); offset += 4;
const changes: TextChange[] = [];
for (let i = 0; i < changeCount; i++) {
offset = TextChange.read(b, offset, changes);
}
return new SingleModelEditStackData(
beforeVersionId,
afterVersionId,
beforeEOL,
afterEOL,
beforeCursorState,
afterCursorState,
changes
);
}
export interface IUndoRedoDelegate {
prepareUndoRedo(element: MultiModelEditStackElement): Promise<IDisposable> | IDisposable | void;
}
export class SingleModelEditStackElement implements IResourceUndoRedoElement {
public model: ITextModel | URI;
private _data: SingleModelEditStackData | ArrayBuffer;
public get type(): UndoRedoElementType.Resource {
return UndoRedoElementType.Resource;
}
public get resource(): URI {
return this.model;
}
}
constructor(
public readonly code: string,
model: ITextModel,
beforeCursorState: Selection[] | null
) {
this.model = model;
this._data = SingleModelEditStackData.create(model, beforeCursorState);
}
public toString(): string {
const data = (this._data instanceof SingleModelEditStackData ? this._data : SingleModelEditStackData.deserialize(this._data));
return data.changes.map(change => change.toString()).join(', ');
}
public matchesResource(resource: URI): boolean {
return (uri.toString() === resource.toString());
}
public setModel(model: ITextModel | URI): void {
}
public canAppend(model: ITextModel): boolean {
return (this.model === model && this._data instanceof SingleModelEditStackData);
textModel.ts ×6
}
public append(model: ITextModel, textChanges: TextChange[], afterEOL: EndOfLineSequence, afterVersionId: number, afterCursorState: Selection[] | null): void {
this._data.append(model, textChanges, afterEOL, afterVersionId, afterCursorState);
}
}
public close(): void {
if (this._data instanceof SingleModelEditStackData) {
this._data = this._data.serialize();
}
}
public open(): void {
if (!(this._data instanceof SingleModelEditStackData)) {
this._data = SingleModelEditStackData.deserialize(this._data);
}
}
public undo(): void {
// don't have a model
throw new Error(`Invalid SingleModelEditStackElement`);
}
}
this.model._applyUndo(data.changes, data.beforeEOL, data.beforeVersionId, data.beforeCursorState);
}
public redo(): void {
if (URI.isUri(this.model)) {
// don't have a model
throw new Error(`Invalid SingleModelEditStackElement`);
}
if (this._data instanceof SingleModelEditStackData) {
this._data = this._data.serialize();
}
const data = SingleModelEditStackData.deserialize(this._data);
this.model._applyRedo(data.changes, data.afterEOL, data.afterVersionId, data.afterCursorState);
}
public heapSize(): number {
this._data = this._data.serialize();
}
return this._data.byteLength + 168/*heap overhead*/;
}
export class MultiModelEditStackElement implements IWorkspaceUndoRedoElement {
public readonly type = UndoRedoElementType.Workspace;
private _isOpen: boolean;
private readonly _editStackElementsArr: SingleModelEditStackElement[];
private readonly _editStackElementsMap: Map<string, SingleModelEditStackElement>;
private _delegate: IUndoRedoDelegate | null;
public get resources(): readonly URI[] {
return this._editStackElementsArr.map(editStackElement => editStackElement.resource);
}
constructor(
public readonly label: string,
public readonly code: string,
editStackElements: SingleModelEditStackElement[]
) {
this._isOpen = true;
this._editStackElementsArr = editStackElements.slice(0);
this._editStackElementsMap = new Map<string, SingleModelEditStackElement>();
for (const editStackElement of this._editStackElementsArr) {
const key = uriGetComparisonKey(editStackElement.resource);
this._editStackElementsMap.set(key, editStackElement);
}
this._delegate = null;
}
public setDelegate(delegate: IUndoRedoDelegate): void {
this._delegate = delegate;
}
public prepareUndoRedo(): Promise<IDisposable> | IDisposable | void {
if (this._delegate) {
return this._delegate.prepareUndoRedo(this);
}
}
public getMissingModels(): URI[] {
const result: URI[] = [];
for (const editStackElement of this._editStackElementsArr) {
if (URI.isUri(editStackElement.model)) {
result.push(editStackElement.model);
}
}
return result;
}
public matchesResource(resource: URI): boolean {
const key = uriGetComparisonKey(resource);
return (this._editStackElementsMap.has(key));
}
public setModel(model: ITextModel | URI): void {
const key = uriGetComparisonKey(URI.isUri(model) ? model : model.uri);
if (this._editStackElementsMap.has(key)) {
this._editStackElementsMap.get(key)!.setModel(model);
}
}
public canAppend(model: ITextModel): boolean {
if (!this._isOpen) {
return false;
}
const key = uriGetComparisonKey(model.uri);
if (this._editStackElementsMap.has(key)) {
const editStackElement = this._editStackElementsMap.get(key)!;
return editStackElement.canAppend(model);
}
return false;
}
public append(model: ITextModel, textChanges: TextChange[], afterEOL: EndOfLineSequence, afterVersionId: number, afterCursorState: Selection[] | null): void {
const key = uriGetComparisonKey(model.uri);
const editStackElement = this._editStackElementsMap.get(key)!;
editStackElement.append(model, textChanges, afterEOL, afterVersionId, afterCursorState);
}
public close(): void {
this._isOpen = false;
}
public open(): void {
// cannot reopen
}
public undo(): void {
this._isOpen = false;
for (const editStackElement of this._editStackElementsArr) {
editStackElement.undo();
}
}
public redo(): void {
for (const editStackElement of this._editStackElementsArr) {
editStackElement.redo();
}
}
public heapSize(resource: URI): number {
const key = uriGetComparisonKey(resource);
if (this._editStackElementsMap.has(key)) {
const editStackElement = this._editStackElementsMap.get(key)!;
return editStackElement.heapSize();
}
return 0;
}
public split(): IResourceUndoRedoElement[] {
return this._editStackElementsArr;
}
public toString(): string {
const result: string[] = [];
for (const editStackElement of this._editStackElementsArr) {
result.push(`${basename(editStackElement.resource)}: ${editStackElement}`);
}
return `{${result.join(', ')}}`;
}
export type EditStackElement = SingleModelEditStackElement | MultiModelEditStackElement;
const eol = model.getEOL();
if (eol === '\n') {
return EndOfLineSequence.LF;
} else {
return EndOfLineSequence.CRLF;
}
export function isEditStackElement(element: IResourceUndoRedoElement | IWorkspaceUndoRedoElement | null): element is EditStackElement {
return false;
}
return ((element instanceof SingleModelEditStackElement) || (element instanceof MultiModelEditStackElement));
editStack.ts ×1
export class EditStack {
private readonly _model: TextModel;
private readonly _undoRedoService: IUndoRedoService;
constructor(model: TextModel, undoRedoService: IUndoRedoService) {
this._undoRedoService = undoRedoService;
}
public pushStackElement(): void {
const lastElement = this._undoRedoService.getLastElement(this._model.uri);
if (isEditStackElement(lastElement)) {
lastElement.close();
}
}
public popStackElement(): void {
const lastElement = this._undoRedoService.getLastElement(this._model.uri);
if (isEditStackElement(lastElement)) {
lastElement.open();
}
}
public clear(): void {
}
private _getOrCreateEditStackElement(beforeCursorState: Selection[] | null, group: UndoRedoGroup | undefined): EditStackElement {
if (isEditStackElement(lastElement) && lastElement.canAppend(this._model)) {
}
const newElement = new SingleModelEditStackElement(nls.localize('edit', "Typing"), 'undoredo.textBufferEdit', this._model, beforeCursorState);
editStack.ts ×15
this._undoRedoService.pushElement(newElement, group);
return newElement;
}
public pushEOL(eol: EndOfLineSequence): void {
const editStackElement = this._getOrCreateEditStackElement(null, undefined);
this._model.setEOL(eol);
editStackElement.append(this._model, [], getModelEOL(this._model), this._model.getAlternativeVersionId(), null);
}
public pushEditOperation(beforeCursorState: Selection[] | null, editOperations: ISingleEditOperation[], cursorStateComputer: ICursorStateComputer | null, group?: UndoRedoGroup, reason: TextModelEditSource = EditSources.unknown({ name: 'pushEditOperation' })): Selection[] | null {
const editStackElement = this._getOrCreateEditStackElement(beforeCursorState, group);
editStack.ts ×15
const inverseEditOperations = this._model.applyEdits(editOperations, true, reason);
const afterCursorState = EditStack._computeCursorState(cursorStateComputer, inverseEditOperations);
const textChanges = inverseEditOperations.map((op, index) => ({ index: index, textChange: op.textChange }));
textChanges.sort((a, b) => {
if (a.textChange.oldPosition === b.textChange.oldPosition) {
return a.index - b.index;
}
return a.textChange.oldPosition - b.textChange.oldPosition;
editStackElement.append(this._model, textChanges.map(op => op.textChange), getModelEOL(this._model), this._model.getAlternativeVersionId(), afterCursorState);
return afterCursorState;
}
private static _computeCursorState(cursorStateComputer: ICursorStateComputer | null, inverseEditOperations: IValidEditOperation[]): Selection[] | null {
return cursorStateComputer ? cursorStateComputer(inverseEditOperations) : null;
} catch (e) {
onUnexpectedError(e);
return null;
}