123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243 |
- /*---------------------------------------------------------------------------------------------
- * 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 '../core/stringBuilder.js';
- function escapeNewLine(str) {
- return (str
- .replace(/\n/g, '\\n')
- .replace(/\r/g, '\\r'));
- }
- export class TextChange {
- constructor(oldPosition, oldText, newPosition, newText) {
- this.oldPosition = oldPosition;
- this.oldText = oldText;
- this.newPosition = newPosition;
- this.newText = newText;
- }
- 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;
- }
- 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;
- }
- let result = [], 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;
- }
- let result = [], 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;
- }
- }
|