mirror of
https://github.com/Microsoft/vscode
synced 2024-11-05 18:29:38 +00:00
Fixes #95843
This commit is contained in:
parent
49a164e886
commit
11e96b2da8
2 changed files with 130 additions and 27 deletions
|
@ -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
|
||||||
|
|
|
@ -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 = (
|
||||||
|
|
Loading…
Reference in a new issue