mirror of
https://github.com/microsoft/vscode.git
synced 2025-12-24 20:26:08 +00:00
[HTML] server contains invalid implementation of applyEdit. Fixes #45014
This commit is contained in:
37
extensions/html/server/src/test/utils.test.ts
Normal file
37
extensions/html/server/src/test/utils.test.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { applyEdits } from '../utils/edits';
|
||||
import { TextDocument, TextEdit, Position, Range } from 'vscode-languageserver-types';
|
||||
|
||||
suite('Edits', () => {
|
||||
|
||||
test('inserts', function (): any {
|
||||
let input = TextDocument.create('foo://bar/f', 'html', 0, '012345678901234567890123456789');
|
||||
assert.equal(applyEdits(input, [TextEdit.insert(Position.create(0, 0), 'Hello')]), 'Hello012345678901234567890123456789');
|
||||
assert.equal(applyEdits(input, [TextEdit.insert(Position.create(0, 1), 'Hello')]), '0Hello12345678901234567890123456789');
|
||||
assert.equal(applyEdits(input, [TextEdit.insert(Position.create(0, 1), 'Hello'), TextEdit.insert(Position.create(0, 1), 'World')]), '0HelloWorld12345678901234567890123456789');
|
||||
assert.equal(applyEdits(input, [TextEdit.insert(Position.create(0, 2), 'One'), TextEdit.insert(Position.create(0, 1), 'Hello'), TextEdit.insert(Position.create(0, 1), 'World'), TextEdit.insert(Position.create(0, 2), 'Two'), TextEdit.insert(Position.create(0, 2), 'Three')]), '0HelloWorld1OneTwoThree2345678901234567890123456789');
|
||||
});
|
||||
|
||||
test('replace', function (): any {
|
||||
let input = TextDocument.create('foo://bar/f', 'html', 0, '012345678901234567890123456789');
|
||||
assert.equal(applyEdits(input, [TextEdit.replace(Range.create(Position.create(0, 3), Position.create(0, 6)), 'Hello')]), '012Hello678901234567890123456789');
|
||||
assert.equal(applyEdits(input, [TextEdit.replace(Range.create(Position.create(0, 3), Position.create(0, 6)), 'Hello'), TextEdit.replace(Range.create(Position.create(0, 6), Position.create(0, 9)), 'World')]), '012HelloWorld901234567890123456789');
|
||||
assert.equal(applyEdits(input, [TextEdit.replace(Range.create(Position.create(0, 3), Position.create(0, 6)), 'Hello'), TextEdit.insert(Position.create(0, 6), 'World')]), '012HelloWorld678901234567890123456789');
|
||||
assert.equal(applyEdits(input, [TextEdit.insert(Position.create(0, 6), 'World'), TextEdit.replace(Range.create(Position.create(0, 3), Position.create(0, 6)), 'Hello')]), '012HelloWorld678901234567890123456789');
|
||||
assert.equal(applyEdits(input, [TextEdit.insert(Position.create(0, 3), 'World'), TextEdit.replace(Range.create(Position.create(0, 3), Position.create(0, 6)), 'Hello')]), '012WorldHello678901234567890123456789');
|
||||
|
||||
});
|
||||
|
||||
test('overlap', function (): any {
|
||||
let input = TextDocument.create('foo://bar/f', 'html', 0, '012345678901234567890123456789');
|
||||
assert.throws(_ => applyEdits(input, [TextEdit.replace(Range.create(Position.create(0, 3), Position.create(0, 6)), 'Hello'), TextEdit.insert(Position.create(0, 3), 'World')]));
|
||||
assert.throws(_ => applyEdits(input, [TextEdit.replace(Range.create(Position.create(0, 3), Position.create(0, 6)), 'Hello'), TextEdit.insert(Position.create(0, 4), 'World')]));
|
||||
});
|
||||
|
||||
});
|
||||
@@ -14,4 +14,46 @@ export function pushAll<T>(to: T[], from: T[]) {
|
||||
|
||||
export function contains<T>(arr: T[], val: T) {
|
||||
return arr.indexOf(val) !== -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Like `Array#sort` but always stable. Usually runs a little slower `than Array#sort`
|
||||
* so only use this when actually needing stable sort.
|
||||
*/
|
||||
export function mergeSort<T>(data: T[], compare: (a: T, b: T) => number): T[] {
|
||||
_divideAndMerge(data, compare);
|
||||
return data;
|
||||
}
|
||||
|
||||
function _divideAndMerge<T>(data: T[], compare: (a: T, b: T) => number): void {
|
||||
if (data.length <= 1) {
|
||||
// sorted
|
||||
return;
|
||||
}
|
||||
const p = (data.length / 2) | 0;
|
||||
const left = data.slice(0, p);
|
||||
const right = data.slice(p);
|
||||
|
||||
_divideAndMerge(left, compare);
|
||||
_divideAndMerge(right, compare);
|
||||
|
||||
let leftIdx = 0;
|
||||
let rightIdx = 0;
|
||||
let i = 0;
|
||||
while (leftIdx < left.length && rightIdx < right.length) {
|
||||
let ret = compare(left[leftIdx], right[rightIdx]);
|
||||
if (ret <= 0) {
|
||||
// smaller_equal -> take left to preserve order
|
||||
data[i++] = left[leftIdx++];
|
||||
} else {
|
||||
// greater -> take right
|
||||
data[i++] = right[rightIdx++];
|
||||
}
|
||||
}
|
||||
while (leftIdx < left.length) {
|
||||
data[i++] = left[leftIdx++];
|
||||
}
|
||||
while (rightIdx < right.length) {
|
||||
data[i++] = right[rightIdx++];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,29 +4,29 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { TextDocument, TextEdit, Position } from 'vscode-languageserver-types';
|
||||
import { TextDocument, TextEdit } from 'vscode-languageserver-types';
|
||||
import { mergeSort } from './arrays';
|
||||
|
||||
export function applyEdits(document: TextDocument, edits: TextEdit[]): string {
|
||||
let text = document.getText();
|
||||
let sortedEdits = edits.sort((a, b) => {
|
||||
let startDiff = comparePositions(a.range.start, b.range.start);
|
||||
if (startDiff === 0) {
|
||||
return comparePositions(a.range.end, b.range.end);
|
||||
let sortedEdits = mergeSort(edits, (a, b) => {
|
||||
let diff = a.range.start.line - b.range.start.line;
|
||||
if (diff === 0) {
|
||||
return a.range.start.character - b.range.start.character;
|
||||
}
|
||||
return startDiff;
|
||||
return 0;
|
||||
});
|
||||
sortedEdits.forEach(e => {
|
||||
let lastModifiedOffset = text.length;
|
||||
for (let i = sortedEdits.length - 1; i >= 0; i--) {
|
||||
let e = sortedEdits[i];
|
||||
let startOffset = document.offsetAt(e.range.start);
|
||||
let endOffset = document.offsetAt(e.range.end);
|
||||
text = text.substring(0, startOffset) + e.newText + text.substring(endOffset, text.length);
|
||||
});
|
||||
return text;
|
||||
}
|
||||
|
||||
function comparePositions(p1: Position, p2: Position) {
|
||||
let diff = p2.line - p1.line;
|
||||
if (diff === 0) {
|
||||
return p2.character - p1.character;
|
||||
if (endOffset <= lastModifiedOffset) {
|
||||
text = text.substring(0, startOffset) + e.newText + text.substring(endOffset, text.length);
|
||||
} else {
|
||||
throw new Error('Ovelapping edit');
|
||||
}
|
||||
lastModifiedOffset = startOffset;
|
||||
}
|
||||
return diff;
|
||||
return text;
|
||||
}
|
||||
Reference in New Issue
Block a user