Fixes #1772: Have bracket actions go to enclosing brackets when not on a bracket

上级 157a3ac5
......@@ -881,6 +881,13 @@ export interface ITextModel {
*/
findNextBracket(position: IPosition): IFoundBracket | null;
/**
* Find the enclosing brackets that contain `position`.
* @param position The position at which to start the search.
* @internal
*/
findEnclosingBrackets(position: IPosition): [Range, Range] | null;
/**
* Given a `position`, if the position is on top or near a bracket,
* find the matching bracket of that bracket and return the ranges of both brackets.
......
......@@ -2317,6 +2317,118 @@ export class TextModel extends Disposable implements model.ITextModel {
return null;
}
public findEnclosingBrackets(_position: IPosition): [Range, Range] | null {
const position = this.validatePosition(_position);
const lineCount = this.getLineCount();
let counts: number[] = [];
const resetCounts = (modeBrackets: RichEditBrackets | null) => {
counts = [];
for (let i = 0, len = modeBrackets ? modeBrackets.brackets.length : 0; i < len; i++) {
counts[i] = 0;
}
};
const searchInRange = (modeBrackets: RichEditBrackets, lineNumber: number, lineText: string, searchStartOffset: number, searchEndOffset: number): [Range, Range] | null => {
while (true) {
const r = BracketsUtils.findNextBracketInRange(modeBrackets.forwardRegex, lineNumber, lineText, searchStartOffset, searchEndOffset);
if (!r) {
break;
}
const hitText = lineText.substring(r.startColumn - 1, r.endColumn - 1).toLowerCase();
const bracket = modeBrackets.textIsBracket[hitText];
if (bracket) {
if (bracket.isOpen(hitText)) {
counts[bracket.index]++;
} else if (bracket.isClose(hitText)) {
counts[bracket.index]--;
}
if (counts[bracket.index] === -1) {
return this._matchFoundBracket(r, bracket, false);
}
}
searchStartOffset = r.endColumn - 1;
}
return null;
};
let languageId: LanguageId = -1;
let modeBrackets: RichEditBrackets | null = null;
for (let lineNumber = position.lineNumber; lineNumber <= lineCount; lineNumber++) {
const lineTokens = this._getLineTokens(lineNumber);
const tokenCount = lineTokens.getCount();
const lineText = this._buffer.getLineContent(lineNumber);
let tokenIndex = 0;
let searchStartOffset = 0;
let searchEndOffset = 0;
if (lineNumber === position.lineNumber) {
tokenIndex = lineTokens.findTokenIndexAtOffset(position.column - 1);
searchStartOffset = position.column - 1;
searchEndOffset = position.column - 1;
const tokenLanguageId = lineTokens.getLanguageId(tokenIndex);
if (languageId !== tokenLanguageId) {
languageId = tokenLanguageId;
modeBrackets = LanguageConfigurationRegistry.getBracketsSupport(languageId);
resetCounts(modeBrackets);
}
}
let prevSearchInToken = true;
for (; tokenIndex < tokenCount; tokenIndex++) {
const tokenLanguageId = lineTokens.getLanguageId(tokenIndex);
if (languageId !== tokenLanguageId) {
// language id change!
if (modeBrackets && prevSearchInToken && searchStartOffset !== searchEndOffset) {
const r = searchInRange(modeBrackets, lineNumber, lineText, searchStartOffset, searchEndOffset);
if (r) {
return r;
}
prevSearchInToken = false;
}
languageId = tokenLanguageId;
modeBrackets = LanguageConfigurationRegistry.getBracketsSupport(languageId);
resetCounts(modeBrackets);
}
const searchInToken = (!!modeBrackets && !ignoreBracketsInToken(lineTokens.getStandardTokenType(tokenIndex)));
if (searchInToken) {
// this token should be searched
if (prevSearchInToken) {
// the previous token should be searched, simply extend searchEndOffset
searchEndOffset = lineTokens.getEndOffset(tokenIndex);
} else {
// the previous token should not be searched
searchStartOffset = lineTokens.getStartOffset(tokenIndex);
searchEndOffset = lineTokens.getEndOffset(tokenIndex);
}
} else {
// this token should not be searched
if (modeBrackets && prevSearchInToken && searchStartOffset !== searchEndOffset) {
const r = searchInRange(modeBrackets, lineNumber, lineText, searchStartOffset, searchEndOffset);
if (r) {
return r;
}
}
}
prevSearchInToken = searchInToken;
}
if (modeBrackets && prevSearchInToken && searchStartOffset !== searchEndOffset) {
const r = searchInRange(modeBrackets, lineNumber, lineText, searchStartOffset, searchEndOffset);
if (r) {
return r;
}
}
}
return null;
}
private _toFoundBracket(modeBrackets: RichEditBrackets, r: Range): model.IFoundBracket | null {
if (!r) {
return null;
......
......@@ -17,6 +17,7 @@ export class RichEditBracket {
_richEditBracketBrand: void;
readonly languageIdentifier: LanguageIdentifier;
readonly index: number;
readonly open: string[];
readonly close: string[];
readonly forwardRegex: RegExp;
......@@ -24,8 +25,9 @@ export class RichEditBracket {
private readonly _openSet: Set<string>;
private readonly _closeSet: Set<string>;
constructor(languageIdentifier: LanguageIdentifier, open: string[], close: string[], forwardRegex: RegExp, reversedRegex: RegExp) {
constructor(languageIdentifier: LanguageIdentifier, index: number, open: string[], close: string[], forwardRegex: RegExp, reversedRegex: RegExp) {
this.languageIdentifier = languageIdentifier;
this.index = index;
this.open = open;
this.close = close;
this.forwardRegex = forwardRegex;
......@@ -125,6 +127,7 @@ export class RichEditBrackets {
this.brackets = brackets.map((b, index) => {
return new RichEditBracket(
languageIdentifier,
index,
b.open,
b.close,
getRegexForBracketPair(b.open, b.close, brackets, index),
......
......@@ -159,10 +159,16 @@ export class BracketMatchingController extends Disposable implements editorCommo
newCursorPosition = brackets[0].getStartPosition();
}
} else {
// find the next bracket if the position isn't on a matching bracket
const nextBracket = model.findNextBracket(position);
if (nextBracket && nextBracket.range) {
newCursorPosition = nextBracket.range.getStartPosition();
// find the enclosing brackets if the position isn't on a matching bracket
const enclosingBrackets = model.findEnclosingBrackets(position);
if (enclosingBrackets) {
newCursorPosition = enclosingBrackets[0].getStartPosition();
} else {
// no enclosing brackets, try the very first next bracket
const nextBracket = model.findNextBracket(position);
if (nextBracket && nextBracket.range) {
newCursorPosition = nextBracket.range.getStartPosition();
}
}
}
......@@ -192,9 +198,12 @@ export class BracketMatchingController extends Disposable implements editorCommo
let closeBracket: Position | null = null;
if (!brackets) {
const nextBracket = model.findNextBracket(position);
if (nextBracket && nextBracket.range) {
brackets = model.matchBracket(nextBracket.range.getStartPosition());
brackets = model.findEnclosingBrackets(position);
if (!brackets) {
const nextBracket = model.findNextBracket(position);
if (nextBracket && nextBracket.range) {
brackets = model.matchBracket(nextBracket.range.getStartPosition());
}
}
}
......
......@@ -143,6 +143,31 @@ suite('bracket matching', () => {
mode.dispose();
});
test('issue #1772: jump to enclosing brackets', () => {
const text = [
'const x = {',
' something: [0, 1, 2],',
' another: true,',
' somethingmore: [0, 2, 4]',
'};',
].join('\n');
const mode = new BracketMode();
const model = TextModel.createFromString(text, undefined, mode.getLanguageIdentifier());
withTestCodeEditor(null, { model: model }, (editor, cursor) => {
const bracketMatchingController = editor.registerAndInstantiateContribution<BracketMatchingController>(BracketMatchingController.ID, BracketMatchingController);
editor.setPosition(new Position(3, 5));
bracketMatchingController.jumpToBracket();
assert.deepEqual(editor.getSelection(), new Selection(5, 1, 5, 1));
bracketMatchingController.dispose();
});
model.dispose();
mode.dispose();
});
test('issue #45369: Select to Bracket with multicursor', () => {
let mode = new BracketMode();
let model = TextModel.createFromString('{ } { } { }', undefined, mode.getLanguageIdentifier());
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册