monaco-editor-core
Version:
A browser based code editor
360 lines (359 loc) • 14.5 kB
JavaScript
/*---------------------------------------------------------------------------------------------
* 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;
}
}
}