This commit is contained in:
Alex Dima 2021-04-01 16:09:14 +02:00
parent 49a164e886
commit 11e96b2da8
No known key found for this signature in database
GPG key ID: 6E58D7B045760DA0
2 changed files with 130 additions and 27 deletions

View file

@ -2106,10 +2106,42 @@ export class TextModel extends Disposable implements model.ITextModel {
return this._matchBracket(this.validatePosition(position)); return this._matchBracket(this.validatePosition(position));
} }
private _establishBracketSearchOffsets(position: Position, lineTokens: LineTokens, modeBrackets: RichEditBrackets, tokenIndex: number) {
const tokenCount = lineTokens.getCount();
const currentLanguageId = lineTokens.getLanguageId(tokenIndex);
// limit search to not go before `maxBracketLength`
let searchStartOffset = Math.max(0, position.column - 1 - modeBrackets.maxBracketLength);
for (let i = tokenIndex - 1; i >= 0; i--) {
const tokenEndOffset = lineTokens.getEndOffset(i);
if (tokenEndOffset <= searchStartOffset) {
break;
}
if (ignoreBracketsInToken(lineTokens.getStandardTokenType(i)) || lineTokens.getLanguageId(i) !== currentLanguageId) {
searchStartOffset = tokenEndOffset;
break;
}
}
// limit search to not go after `maxBracketLength`
let searchEndOffset = Math.min(lineTokens.getLineContent().length, position.column - 1 + modeBrackets.maxBracketLength);
for (let i = tokenIndex + 1; i < tokenCount; i++) {
const tokenStartOffset = lineTokens.getStartOffset(i);
if (tokenStartOffset >= searchEndOffset) {
break;
}
if (ignoreBracketsInToken(lineTokens.getStandardTokenType(i)) || lineTokens.getLanguageId(i) !== currentLanguageId) {
searchEndOffset = tokenStartOffset;
break;
}
}
return { searchStartOffset, searchEndOffset };
}
private _matchBracket(position: Position): [Range, Range] | null { private _matchBracket(position: Position): [Range, Range] | null {
const lineNumber = position.lineNumber; const lineNumber = position.lineNumber;
const lineTokens = this._getLineTokens(lineNumber); const lineTokens = this._getLineTokens(lineNumber);
const tokenCount = lineTokens.getCount();
const lineText = this._buffer.getLineContent(lineNumber); const lineText = this._buffer.getLineContent(lineNumber);
const tokenIndex = lineTokens.findTokenIndexAtOffset(position.column - 1); const tokenIndex = lineTokens.findTokenIndexAtOffset(position.column - 1);
@ -2120,19 +2152,8 @@ export class TextModel extends Disposable implements model.ITextModel {
// check that the token is not to be ignored // check that the token is not to be ignored
if (currentModeBrackets && !ignoreBracketsInToken(lineTokens.getStandardTokenType(tokenIndex))) { if (currentModeBrackets && !ignoreBracketsInToken(lineTokens.getStandardTokenType(tokenIndex))) {
// limit search to not go before `maxBracketLength`
let searchStartOffset = Math.max(0, position.column - 1 - currentModeBrackets.maxBracketLength); let { searchStartOffset, searchEndOffset } = this._establishBracketSearchOffsets(position, lineTokens, currentModeBrackets, tokenIndex);
for (let i = tokenIndex - 1; i >= 0; i--) {
const tokenEndOffset = lineTokens.getEndOffset(i);
if (tokenEndOffset <= searchStartOffset) {
break;
}
if (ignoreBracketsInToken(lineTokens.getStandardTokenType(i))) {
searchStartOffset = tokenEndOffset;
}
}
// limit search to not go after `maxBracketLength`
const searchEndOffset = Math.min(lineText.length, position.column - 1 + currentModeBrackets.maxBracketLength);
// it might be the case that [currentTokenStart -> currentTokenEnd] contains multiple brackets // it might be the case that [currentTokenStart -> currentTokenEnd] contains multiple brackets
// `bestResult` will contain the most right-side result // `bestResult` will contain the most right-side result
@ -2171,18 +2192,9 @@ export class TextModel extends Disposable implements model.ITextModel {
// check that previous token is not to be ignored // check that previous token is not to be ignored
if (prevModeBrackets && !ignoreBracketsInToken(lineTokens.getStandardTokenType(prevTokenIndex))) { if (prevModeBrackets && !ignoreBracketsInToken(lineTokens.getStandardTokenType(prevTokenIndex))) {
// limit search in case previous token is very large, there's no need to go beyond `maxBracketLength`
const searchStartOffset = Math.max(0, position.column - 1 - prevModeBrackets.maxBracketLength); let { searchStartOffset, searchEndOffset } = this._establishBracketSearchOffsets(position, lineTokens, prevModeBrackets, prevTokenIndex);
let searchEndOffset = Math.min(lineText.length, position.column - 1 + prevModeBrackets.maxBracketLength);
for (let i = prevTokenIndex + 1; i < tokenCount; i++) {
const tokenStartOffset = lineTokens.getStartOffset(i);
if (tokenStartOffset >= searchEndOffset) {
break;
}
if (ignoreBracketsInToken(lineTokens.getStandardTokenType(i))) {
searchEndOffset = tokenStartOffset;
}
}
const foundBracket = BracketsUtils.findPrevBracketInRange(prevModeBrackets.reversedRegex, lineNumber, lineText, searchStartOffset, searchEndOffset); const foundBracket = BracketsUtils.findPrevBracketInRange(prevModeBrackets.reversedRegex, lineNumber, lineText, searchStartOffset, searchEndOffset);
// check that we didn't hit a bracket too far away from position // check that we didn't hit a bracket too far away from position

View file

@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import * as assert from 'assert'; import * as assert from 'assert';
import { IDisposable } from 'vs/base/common/lifecycle'; import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle';
import { Position } from 'vs/editor/common/core/position'; import { Position } from 'vs/editor/common/core/position';
import { Range } from 'vs/editor/common/core/range'; import { Range } from 'vs/editor/common/core/range';
import { TokenizationResult2 } from 'vs/editor/common/core/token'; import { TokenizationResult2 } from 'vs/editor/common/core/token';
@ -337,6 +337,97 @@ suite('TextModelWithTokens', () => {
registration.dispose(); registration.dispose();
}); });
test('issue #95843: Highlighting of closing braces is indicating wrong brace when cursor is behind opening brace', () => {
const mode1 = new LanguageIdentifier('testMode1', 3);
const mode2 = new LanguageIdentifier('testMode2', 4);
const otherMetadata1 = (
(mode1.id << MetadataConsts.LANGUAGEID_OFFSET)
| (StandardTokenType.Other << MetadataConsts.TOKEN_TYPE_OFFSET)
) >>> 0;
const otherMetadata2 = (
(mode2.id << MetadataConsts.LANGUAGEID_OFFSET)
| (StandardTokenType.Other << MetadataConsts.TOKEN_TYPE_OFFSET)
) >>> 0;
const tokenizationSupport: ITokenizationSupport = {
getInitialState: () => NULL_STATE,
tokenize: undefined!,
tokenize2: (line, hasEOL, state) => {
switch (line) {
case 'function f() {': {
const tokens = new Uint32Array([
0, otherMetadata1,
8, otherMetadata1,
9, otherMetadata1,
10, otherMetadata1,
11, otherMetadata1,
12, otherMetadata1,
13, otherMetadata1,
]);
return new TokenizationResult2(tokens, state);
}
case ' return <p>{true}</p>;': {
const tokens = new Uint32Array([
0, otherMetadata1,
2, otherMetadata1,
8, otherMetadata1,
9, otherMetadata2,
10, otherMetadata2,
11, otherMetadata2,
12, otherMetadata2,
13, otherMetadata1,
17, otherMetadata2,
18, otherMetadata2,
20, otherMetadata2,
21, otherMetadata2,
22, otherMetadata2,
]);
return new TokenizationResult2(tokens, state);
}
case '}': {
const tokens = new Uint32Array([
0, otherMetadata1
]);
return new TokenizationResult2(tokens, state);
}
}
throw new Error(`Unexpected`);
}
};
const disposableStore = new DisposableStore();
disposableStore.add(TokenizationRegistry.register(mode1.language, tokenizationSupport));
disposableStore.add(LanguageConfigurationRegistry.register(mode1, {
brackets: [
['{', '}'],
['[', ']'],
['(', ')']
],
}));
disposableStore.add(LanguageConfigurationRegistry.register(mode2, {
brackets: [
['{', '}'],
['[', ']'],
['(', ')']
],
}));
const model = disposableStore.add(createTextModel([
'function f() {',
' return <p>{true}</p>;',
'}',
].join('\n'), undefined, mode1));
model.forceTokenization(1);
model.forceTokenization(2);
model.forceTokenization(3);
assert.deepStrictEqual(model.matchBracket(new Position(2, 14)), [new Range(2, 13, 2, 14), new Range(2, 18, 2, 19)]);
disposableStore.dispose();
});
test('issue #88075: TypeScript brace matching is incorrect in `${}` strings', () => { test('issue #88075: TypeScript brace matching is incorrect in `${}` strings', () => {
const mode = new LanguageIdentifier('testMode', 3); const mode = new LanguageIdentifier('testMode', 3);
const otherMetadata = ( const otherMetadata = (