UNPKG

monaco-editor-core

Version:

A browser based code editor

360 lines (359 loc) • 14.5 kB
/*--------------------------------------------------------------------------------------------- * 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 { URI } from '../../../base/common/uri.js'; import { TextChange, compressConsecutiveTextChanges } from '../core/textChange.js'; import * as buffer from '../../../base/common/buffer.js'; import { basename } from '../../../base/common/resources.js'; function uriGetComparisonKey(resource) { return resource.toString(); } export class SingleModelEditStackData { static create(model, beforeCursorState) { const alternativeVersionId = model.getAlternativeVersionId(); const eol = getModelEOL(model); return new SingleModelEditStackData(alternativeVersionId, alternativeVersionId, eol, eol, beforeCursorState, beforeCursorState, []); } constructor(beforeVersionId, afterVersionId, beforeEOL, afterEOL, beforeCursorState, afterCursorState, changes) { this.beforeVersionId = beforeVersionId; this.afterVersionId = afterVersionId; this.beforeEOL = beforeEOL; this.afterEOL = afterEOL; this.beforeCursorState = beforeCursorState; this.afterCursorState = afterCursorState; this.changes = changes; } append(model, textChanges, afterEOL, afterVersionId, afterCursorState) { if (textChanges.length > 0) { this.changes = compressConsecutiveTextChanges(this.changes, textChanges); } this.afterEOL = afterEOL; this.afterVersionId = afterVersionId; this.afterCursorState = afterCursorState; } static _writeSelectionsSize(selections) { return 4 + 4 * 4 * (selections ? selections.length : 0); } static _writeSelections(b, selections, offset) { buffer.writeUInt32BE(b, (selections ? selections.length : 0), offset); offset += 4; if (selections) { for (const selection of 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; } } return offset; } static _readSelections(b, offset, dest) { const count = buffer.readUInt32BE(b, offset); offset += 4; for (let i = 0; i < count; i++) { const selectionStartLineNumber = buffer.readUInt32BE(b, offset); offset += 4; 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)); } return offset; } serialize() { let necessarySize = (+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; } static deserialize(source) { const b = new Uint8Array(source); 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 = []; offset = SingleModelEditStackData._readSelections(b, offset, beforeCursorState); const afterCursorState = []; offset = SingleModelEditStackData._readSelections(b, offset, afterCursorState); const changeCount = buffer.readUInt32BE(b, offset); offset += 4; const changes = []; for (let i = 0; i < changeCount; i++) { offset = TextChange.read(b, offset, changes); } return new SingleModelEditStackData(beforeVersionId, afterVersionId, beforeEOL, afterEOL, beforeCursorState, afterCursorState, changes); } } export class SingleModelEditStackElement { get type() { return 0 /* UndoRedoElementType.Resource */; } get resource() { if (URI.isUri(this.model)) { return this.model; } return this.model.uri; } constructor(label, code, model, beforeCursorState) { this.label = label; this.code = code; this.model = model; this._data = SingleModelEditStackData.create(model, beforeCursorState); } toString() { const data = (this._data instanceof SingleModelEditStackData ? this._data : SingleModelEditStackData.deserialize(this._data)); return data.changes.map(change => change.toString()).join(', '); } matchesResource(resource) { const uri = (URI.isUri(this.model) ? this.model : this.model.uri); return (uri.toString() === resource.toString()); } setModel(model) { this.model = model; } canAppend(model) { return (this.model === model && this._data instanceof SingleModelEditStackData); } append(model, textChanges, afterEOL, afterVersionId, afterCursorState) { if (this._data instanceof SingleModelEditStackData) { this._data.append(model, textChanges, afterEOL, afterVersionId, afterCursorState); } } close() { if (this._data instanceof SingleModelEditStackData) { this._data = this._data.serialize(); } } open() { if (!(this._data instanceof SingleModelEditStackData)) { this._data = SingleModelEditStackData.deserialize(this._data); } } undo() { 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._applyUndo(data.changes, data.beforeEOL, data.beforeVersionId, data.beforeCursorState); } redo() { 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); } heapSize() { if (this._data instanceof SingleModelEditStackData) { this._data = this._data.serialize(); } return this._data.byteLength + 168 /*heap overhead*/; } } export class MultiModelEditStackElement { get resources() { return this._editStackElementsArr.map(editStackElement => editStackElement.resource); } constructor(label, code, editStackElements) { this.label = label; this.code = code; this.type = 1 /* UndoRedoElementType.Workspace */; this._isOpen = true; this._editStackElementsArr = editStackElements.slice(0); this._editStackElementsMap = new Map(); for (const editStackElement of this._editStackElementsArr) { const key = uriGetComparisonKey(editStackElement.resource); this._editStackElementsMap.set(key, editStackElement); } this._delegate = null; } prepareUndoRedo() { if (this._delegate) { return this._delegate.prepareUndoRedo(this); } } matchesResource(resource) { const key = uriGetComparisonKey(resource); return (this._editStackElementsMap.has(key)); } setModel(model) { const key = uriGetComparisonKey(URI.isUri(model) ? model : model.uri); if (this._editStackElementsMap.has(key)) { this._editStackElementsMap.get(key).setModel(model); } } canAppend(model) { 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; } append(model, textChanges, afterEOL, afterVersionId, afterCursorState) { const key = uriGetComparisonKey(model.uri); const editStackElement = this._editStackElementsMap.get(key); editStackElement.append(model, textChanges, afterEOL, afterVersionId, afterCursorState); } close() { this._isOpen = false; } open() { // cannot reopen } undo() { this._isOpen = false; for (const editStackElement of this._editStackElementsArr) { editStackElement.undo(); } } redo() { for (const editStackElement of this._editStackElementsArr) { editStackElement.redo(); } } heapSize(resource) { const key = uriGetComparisonKey(resource); if (this._editStackElementsMap.has(key)) { const editStackElement = this._editStackElementsMap.get(key); return editStackElement.heapSize(); } return 0; } split() { return this._editStackElementsArr; } toString() { const result = []; for (const editStackElement of this._editStackElementsArr) { result.push(`${basename(editStackElement.resource)}: ${editStackElement}`); } return `{${result.join(', ')}}`; } } function getModelEOL(model) { const eol = model.getEOL(); if (eol === '\n') { return 0 /* EndOfLineSequence.LF */; } else { return 1 /* EndOfLineSequence.CRLF */; } } export function isEditStackElement(element) { if (!element) { return false; } return ((element instanceof SingleModelEditStackElement) || (element instanceof MultiModelEditStackElement)); } export class EditStack { constructor(model, undoRedoService) { this._model = model; this._undoRedoService = undoRedoService; } pushStackElement() { const lastElement = this._undoRedoService.getLastElement(this._model.uri); if (isEditStackElement(lastElement)) { lastElement.close(); } } popStackElement() { const lastElement = this._undoRedoService.getLastElement(this._model.uri); if (isEditStackElement(lastElement)) { lastElement.open(); } } clear() { this._undoRedoService.removeElements(this._model.uri); } _getOrCreateEditStackElement(beforeCursorState, group) { const lastElement = this._undoRedoService.getLastElement(this._model.uri); if (isEditStackElement(lastElement) && lastElement.canAppend(this._model)) { return lastElement; } const newElement = new SingleModelEditStackElement(nls.localize('edit', "Typing"), 'undoredo.textBufferEdit', this._model, beforeCursorState); this._undoRedoService.pushElement(newElement, group); return newElement; } pushEOL(eol) { const editStackElement = this._getOrCreateEditStackElement(null, undefined); this._model.setEOL(eol); editStackElement.append(this._model, [], getModelEOL(this._model), this._model.getAlternativeVersionId(), null); } pushEditOperation(beforeCursorState, editOperations, cursorStateComputer, group) { const editStackElement = this._getOrCreateEditStackElement(beforeCursorState, group); const inverseEditOperations = this._model.applyEdits(editOperations, true); 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; } static _computeCursorState(cursorStateComputer, inverseEditOperations) { try { return cursorStateComputer ? cursorStateComputer(inverseEditOperations) : null; } catch (e) { onUnexpectedError(e); return null; } } }