Skip to content

Commit 29f4c53

Browse files
committed
Fixes microsoft#86303: Fix adjusting of tokens after the deleted range
1 parent 64b177b commit 29f4c53

2 files changed

Lines changed: 126 additions & 2 deletions

File tree

src/vs/editor/common/model/tokensStore.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -293,8 +293,13 @@ export class SparseEncodedTokens implements IEncodedTokens {
293293
} else if (tokenDeltaLine === endDeltaLine && tokenStartCharacter >= endCharacter) {
294294
// 4. (continued) The token starts after the deletion range, on the last line where a deletion occurs
295295
tokenDeltaLine -= deletedLineCount;
296-
tokenStartCharacter -= endCharacter;
297-
tokenEndCharacter -= endCharacter;
296+
if (deletedLineCount === 0) {
297+
tokenStartCharacter -= (endCharacter - startCharacter);
298+
tokenEndCharacter -= (endCharacter - startCharacter);
299+
} else {
300+
tokenStartCharacter -= endCharacter;
301+
tokenEndCharacter -= endCharacter;
302+
}
298303
} else {
299304
throw new Error(`Not possible!`);
300305
}
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import * as assert from 'assert';
7+
import { MultilineTokens2, SparseEncodedTokens } from 'vs/editor/common/model/tokensStore';
8+
import { Range } from 'vs/editor/common/core/range';
9+
import { TextModel } from 'vs/editor/common/model/textModel';
10+
import { IIdentifiedSingleEditOperation } from 'vs/editor/common/model';
11+
import { MetadataConsts, TokenMetadata } from 'vs/editor/common/modes';
12+
13+
suite('TokensStore', () => {
14+
15+
const SEMANTIC_COLOR = 5;
16+
17+
function parseTokensState(state: string[]): { text: string; tokens: number[]; } {
18+
let text: string[] = [];
19+
let tokens: number[] = [];
20+
for (let i = 0; i < state.length; i++) {
21+
const line = state[i];
22+
23+
let startOffset = 0;
24+
let lineText = '';
25+
while (true) {
26+
const firstPipeOffset = line.indexOf('|', startOffset);
27+
if (firstPipeOffset === -1) {
28+
break;
29+
}
30+
const secondPipeOffset = line.indexOf('|', firstPipeOffset + 1);
31+
if (secondPipeOffset === -1) {
32+
break;
33+
}
34+
if (firstPipeOffset + 1 === secondPipeOffset) {
35+
// skip ||
36+
lineText += line.substring(startOffset, secondPipeOffset + 1);
37+
startOffset = secondPipeOffset + 1;
38+
continue;
39+
}
40+
41+
lineText += line.substring(startOffset, firstPipeOffset);
42+
const tokenStartCharacter = lineText.length;
43+
const tokenLength = secondPipeOffset - firstPipeOffset - 1;
44+
const metadata = (SEMANTIC_COLOR << MetadataConsts.FOREGROUND_OFFSET);
45+
46+
tokens.push(i, tokenStartCharacter, tokenStartCharacter + tokenLength, metadata);
47+
48+
lineText += line.substr(firstPipeOffset + 1, tokenLength);
49+
startOffset = secondPipeOffset + 1;
50+
}
51+
52+
lineText += line.substring(startOffset);
53+
54+
text.push(lineText);
55+
}
56+
57+
return {
58+
text: text.join('\n'),
59+
tokens: tokens
60+
};
61+
}
62+
63+
function extractState(model: TextModel): string[] {
64+
let result: string[] = [];
65+
for (let lineNumber = 1; lineNumber <= model.getLineCount(); lineNumber++) {
66+
const lineTokens = model.getLineTokens(lineNumber);
67+
const lineContent = model.getLineContent(lineNumber);
68+
69+
let lineText = '';
70+
for (let i = 0; i < lineTokens.getCount(); i++) {
71+
const tokenStartCharacter = lineTokens.getStartOffset(i);
72+
const tokenEndCharacter = lineTokens.getEndOffset(i);
73+
const metadata = lineTokens.getMetadata(i);
74+
const color = TokenMetadata.getForeground(metadata);
75+
const tokenText = lineContent.substring(tokenStartCharacter, tokenEndCharacter);
76+
if (color === SEMANTIC_COLOR) {
77+
lineText += `|${tokenText}|`;
78+
} else {
79+
lineText += tokenText;
80+
}
81+
}
82+
83+
result.push(lineText);
84+
}
85+
return result;
86+
}
87+
88+
// function extractState
89+
90+
function testTokensAdjustment(rawInitialState: string[], edits: IIdentifiedSingleEditOperation[], rawFinalState: string[]) {
91+
const initialState = parseTokensState(rawInitialState);
92+
const model = TextModel.createFromString(initialState.text);
93+
model.setSemanticTokens([new MultilineTokens2(1, new SparseEncodedTokens(new Uint32Array(initialState.tokens)))]);
94+
95+
model.applyEdits(edits);
96+
97+
const actualState = extractState(model);
98+
assert.deepEqual(actualState, rawFinalState);
99+
100+
model.dispose();
101+
}
102+
103+
test('issue #86303 - color shifting between different tokens', () => {
104+
testTokensAdjustment(
105+
[
106+
`import { |URI| } from 'vs/base/common/uri';`,
107+
`const foo = |URI|.parse('hey');`
108+
],
109+
[
110+
{ range: new Range(2, 9, 2, 10), text: '' }
111+
],
112+
[
113+
`import { |URI| } from 'vs/base/common/uri';`,
114+
`const fo = |URI|.parse('hey');`
115+
]
116+
);
117+
});
118+
119+
});

0 commit comments

Comments
 (0)