UNPKG

monaco-editor-core

Version:

A browser based code editor

246 lines (245 loc) • 9.66 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 buffer from '../../../base/common/buffer.js'; import { decodeUTF16LE } from './stringBuilder.js'; function escapeNewLine(str) { return (str .replace(/\n/g, '\\n') .replace(/\r/g, '\\r')); } export class TextChange { get oldLength() { return this.oldText.length; } get oldEnd() { return this.oldPosition + this.oldText.length; } get newLength() { return this.newText.length; } get newEnd() { return this.newPosition + this.newText.length; } constructor(oldPosition, oldText, newPosition, newText) { this.oldPosition = oldPosition; this.oldText = oldText; this.newPosition = newPosition; this.newText = newText; } toString() { if (this.oldText.length === 0) { return `(insert@${this.oldPosition} "${escapeNewLine(this.newText)}")`; } if (this.newText.length === 0) { return `(delete@${this.oldPosition} "${escapeNewLine(this.oldText)}")`; } return `(replace@${this.oldPosition} "${escapeNewLine(this.oldText)}" with "${escapeNewLine(this.newText)}")`; } static _writeStringSize(str) { return (4 + 2 * str.length); } static _writeString(b, str, offset) { const len = str.length; buffer.writeUInt32BE(b, len, offset); offset += 4; for (let i = 0; i < len; i++) { buffer.writeUInt16LE(b, str.charCodeAt(i), offset); offset += 2; } return offset; } static _readString(b, offset) { const len = buffer.readUInt32BE(b, offset); offset += 4; return decodeUTF16LE(b, offset, len); } writeSize() { return (+4 // oldPosition + 4 // newPosition + TextChange._writeStringSize(this.oldText) + TextChange._writeStringSize(this.newText)); } write(b, offset) { buffer.writeUInt32BE(b, this.oldPosition, offset); offset += 4; buffer.writeUInt32BE(b, this.newPosition, offset); offset += 4; offset = TextChange._writeString(b, this.oldText, offset); offset = TextChange._writeString(b, this.newText, offset); return offset; } static read(b, offset, dest) { const oldPosition = buffer.readUInt32BE(b, offset); offset += 4; const newPosition = buffer.readUInt32BE(b, offset); offset += 4; const oldText = TextChange._readString(b, offset); offset += TextChange._writeStringSize(oldText); const newText = TextChange._readString(b, offset); offset += TextChange._writeStringSize(newText); dest.push(new TextChange(oldPosition, oldText, newPosition, newText)); return offset; } } export function compressConsecutiveTextChanges(prevEdits, currEdits) { if (prevEdits === null || prevEdits.length === 0) { return currEdits; } const compressor = new TextChangeCompressor(prevEdits, currEdits); return compressor.compress(); } class TextChangeCompressor { constructor(prevEdits, currEdits) { this._prevEdits = prevEdits; this._currEdits = currEdits; this._result = []; this._resultLen = 0; this._prevLen = this._prevEdits.length; this._prevDeltaOffset = 0; this._currLen = this._currEdits.length; this._currDeltaOffset = 0; } compress() { let prevIndex = 0; let currIndex = 0; let prevEdit = this._getPrev(prevIndex); let currEdit = this._getCurr(currIndex); while (prevIndex < this._prevLen || currIndex < this._currLen) { if (prevEdit === null) { this._acceptCurr(currEdit); currEdit = this._getCurr(++currIndex); continue; } if (currEdit === null) { this._acceptPrev(prevEdit); prevEdit = this._getPrev(++prevIndex); continue; } if (currEdit.oldEnd <= prevEdit.newPosition) { this._acceptCurr(currEdit); currEdit = this._getCurr(++currIndex); continue; } if (prevEdit.newEnd <= currEdit.oldPosition) { this._acceptPrev(prevEdit); prevEdit = this._getPrev(++prevIndex); continue; } if (currEdit.oldPosition < prevEdit.newPosition) { const [e1, e2] = TextChangeCompressor._splitCurr(currEdit, prevEdit.newPosition - currEdit.oldPosition); this._acceptCurr(e1); currEdit = e2; continue; } if (prevEdit.newPosition < currEdit.oldPosition) { const [e1, e2] = TextChangeCompressor._splitPrev(prevEdit, currEdit.oldPosition - prevEdit.newPosition); this._acceptPrev(e1); prevEdit = e2; continue; } // At this point, currEdit.oldPosition === prevEdit.newPosition let mergePrev; let mergeCurr; if (currEdit.oldEnd === prevEdit.newEnd) { mergePrev = prevEdit; mergeCurr = currEdit; prevEdit = this._getPrev(++prevIndex); currEdit = this._getCurr(++currIndex); } else if (currEdit.oldEnd < prevEdit.newEnd) { const [e1, e2] = TextChangeCompressor._splitPrev(prevEdit, currEdit.oldLength); mergePrev = e1; mergeCurr = currEdit; prevEdit = e2; currEdit = this._getCurr(++currIndex); } else { const [e1, e2] = TextChangeCompressor._splitCurr(currEdit, prevEdit.newLength); mergePrev = prevEdit; mergeCurr = e1; prevEdit = this._getPrev(++prevIndex); currEdit = e2; } this._result[this._resultLen++] = new TextChange(mergePrev.oldPosition, mergePrev.oldText, mergeCurr.newPosition, mergeCurr.newText); this._prevDeltaOffset += mergePrev.newLength - mergePrev.oldLength; this._currDeltaOffset += mergeCurr.newLength - mergeCurr.oldLength; } const merged = TextChangeCompressor._merge(this._result); const cleaned = TextChangeCompressor._removeNoOps(merged); return cleaned; } _acceptCurr(currEdit) { this._result[this._resultLen++] = TextChangeCompressor._rebaseCurr(this._prevDeltaOffset, currEdit); this._currDeltaOffset += currEdit.newLength - currEdit.oldLength; } _getCurr(currIndex) { return (currIndex < this._currLen ? this._currEdits[currIndex] : null); } _acceptPrev(prevEdit) { this._result[this._resultLen++] = TextChangeCompressor._rebasePrev(this._currDeltaOffset, prevEdit); this._prevDeltaOffset += prevEdit.newLength - prevEdit.oldLength; } _getPrev(prevIndex) { return (prevIndex < this._prevLen ? this._prevEdits[prevIndex] : null); } static _rebaseCurr(prevDeltaOffset, currEdit) { return new TextChange(currEdit.oldPosition - prevDeltaOffset, currEdit.oldText, currEdit.newPosition, currEdit.newText); } static _rebasePrev(currDeltaOffset, prevEdit) { return new TextChange(prevEdit.oldPosition, prevEdit.oldText, prevEdit.newPosition + currDeltaOffset, prevEdit.newText); } static _splitPrev(edit, offset) { const preText = edit.newText.substr(0, offset); const postText = edit.newText.substr(offset); return [ new TextChange(edit.oldPosition, edit.oldText, edit.newPosition, preText), new TextChange(edit.oldEnd, '', edit.newPosition + offset, postText) ]; } static _splitCurr(edit, offset) { const preText = edit.oldText.substr(0, offset); const postText = edit.oldText.substr(offset); return [ new TextChange(edit.oldPosition, preText, edit.newPosition, edit.newText), new TextChange(edit.oldPosition + offset, postText, edit.newEnd, '') ]; } static _merge(edits) { if (edits.length === 0) { return edits; } const result = []; let resultLen = 0; let prev = edits[0]; for (let i = 1; i < edits.length; i++) { const curr = edits[i]; if (prev.oldEnd === curr.oldPosition) { // Merge into `prev` prev = new TextChange(prev.oldPosition, prev.oldText + curr.oldText, prev.newPosition, prev.newText + curr.newText); } else { result[resultLen++] = prev; prev = curr; } } result[resultLen++] = prev; return result; } static _removeNoOps(edits) { if (edits.length === 0) { return edits; } const result = []; let resultLen = 0; for (let i = 0; i < edits.length; i++) { const edit = edits[i]; if (edit.oldText === edit.newText) { continue; } result[resultLen++] = edit; } return result; } }