Skip to content

Commit 39b3e87

Browse files
committed
Add support for overlapping brackets (microsoft#26121)
1 parent c941854 commit 39b3e87

7 files changed

Lines changed: 220 additions & 84 deletions

File tree

src/vs/editor/common/model.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -459,8 +459,8 @@ export class FindMatch {
459459
*/
460460
export interface IFoundBracket {
461461
range: Range;
462-
open: string;
463-
close: string;
462+
open: string[];
463+
close: string[];
464464
isOpen: boolean;
465465
}
466466

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2011,9 +2011,9 @@ export class TextModel extends Disposable implements model.ITextModel {
20112011
}
20122012

20132013
const hitText = lineText.substring(r.startColumn - 1, r.endColumn - 1).toLowerCase();
2014-
if (hitText === bracket.open) {
2014+
if (bracket.isOpen(hitText)) {
20152015
count++;
2016-
} else if (hitText === bracket.close) {
2016+
} else if (bracket.isClose(hitText)) {
20172017
count--;
20182018
}
20192019

@@ -2094,9 +2094,9 @@ export class TextModel extends Disposable implements model.ITextModel {
20942094
}
20952095

20962096
const hitText = lineText.substring(r.startColumn - 1, r.endColumn - 1).toLowerCase();
2097-
if (hitText === bracket.open) {
2097+
if (bracket.isOpen(hitText)) {
20982098
count++;
2099-
} else if (hitText === bracket.close) {
2099+
} else if (bracket.isClose(hitText)) {
21002100
count--;
21012101
}
21022102

src/vs/editor/common/modes/supports/electricCharacter.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,11 @@ export class BracketElectricCharacterSupport {
2828
let result: string[] = [];
2929

3030
if (this._richEditBrackets) {
31-
for (let i = 0, len = this._richEditBrackets.brackets.length; i < len; i++) {
32-
let bracketPair = this._richEditBrackets.brackets[i];
33-
let lastChar = bracketPair.close.charAt(bracketPair.close.length - 1);
34-
result.push(lastChar);
31+
for (const bracket of this._richEditBrackets.brackets) {
32+
for (const close of bracket.close) {
33+
const lastChar = close.charAt(close.length - 1);
34+
result.push(lastChar);
35+
}
3536
}
3637
}
3738

src/vs/editor/common/modes/supports/richEditBrackets.ts

Lines changed: 171 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -8,29 +8,107 @@ import { Range } from 'vs/editor/common/core/range';
88
import { LanguageIdentifier } from 'vs/editor/common/modes';
99
import { CharacterPair } from 'vs/editor/common/modes/languageConfiguration';
1010

11-
interface ISimpleInternalBracket {
12-
open: string;
13-
close: string;
11+
interface InternalBracket {
12+
open: string[];
13+
close: string[];
1414
}
1515

1616
export class RichEditBracket {
1717
_richEditBracketBrand: void;
1818

1919
readonly languageIdentifier: LanguageIdentifier;
20-
readonly open: string;
21-
readonly close: string;
20+
readonly open: string[];
21+
readonly close: string[];
2222
readonly forwardRegex: RegExp;
2323
readonly reversedRegex: RegExp;
24+
private readonly _openSet: Set<string>;
25+
private readonly _closeSet: Set<string>;
2426

25-
constructor(languageIdentifier: LanguageIdentifier, open: string, close: string, forwardRegex: RegExp, reversedRegex: RegExp) {
27+
constructor(languageIdentifier: LanguageIdentifier, open: string[], close: string[], forwardRegex: RegExp, reversedRegex: RegExp) {
2628
this.languageIdentifier = languageIdentifier;
2729
this.open = open;
2830
this.close = close;
2931
this.forwardRegex = forwardRegex;
3032
this.reversedRegex = reversedRegex;
33+
this._openSet = RichEditBracket._toSet(this.open);
34+
this._closeSet = RichEditBracket._toSet(this.close);
35+
}
36+
37+
public isOpen(text: string) {
38+
return this._openSet.has(text);
39+
}
40+
41+
public isClose(text: string) {
42+
return this._closeSet.has(text);
43+
}
44+
45+
private static _toSet(arr: string[]): Set<string> {
46+
const result = new Set<string>();
47+
for (const element of arr) {
48+
result.add(element);
49+
}
50+
return result;
3151
}
3252
}
3353

54+
function groupFuzzyBrackets(brackets: CharacterPair[]): InternalBracket[] {
55+
const N = brackets.length;
56+
57+
brackets = brackets.map(b => [b[0].toLowerCase(), b[1].toLowerCase()]);
58+
59+
const group: number[] = [];
60+
for (let i = 0; i < N; i++) {
61+
group[i] = i;
62+
}
63+
64+
const areOverlapping = (a: CharacterPair, b: CharacterPair) => {
65+
const [aOpen, aClose] = a;
66+
const [bOpen, bClose] = b;
67+
return (aOpen === bOpen || aOpen === bClose || aClose === bOpen || aClose === bClose);
68+
};
69+
70+
const mergeGroups = (g1: number, g2: number) => {
71+
const newG = Math.min(g1, g2);
72+
const oldG = Math.max(g1, g2);
73+
for (let i = 0; i < N; i++) {
74+
if (group[i] === oldG) {
75+
group[i] = newG;
76+
}
77+
}
78+
};
79+
80+
// group together brackets that have the same open or the same close sequence
81+
for (let i = 0; i < N; i++) {
82+
const a = brackets[i];
83+
for (let j = i + 1; j < N; j++) {
84+
const b = brackets[j];
85+
if (areOverlapping(a, b)) {
86+
mergeGroups(group[i], group[j]);
87+
}
88+
}
89+
}
90+
91+
const result: InternalBracket[] = [];
92+
for (let g = 0; g < N; g++) {
93+
let currentOpen: string[] = [];
94+
let currentClose: string[] = [];
95+
for (let i = 0; i < N; i++) {
96+
if (group[i] === g) {
97+
const [open, close] = brackets[i];
98+
currentOpen.push(open);
99+
currentClose.push(close);
100+
}
101+
}
102+
if (currentOpen.length > 0) {
103+
result.push({
104+
open: currentOpen,
105+
close: currentClose
106+
});
107+
}
108+
}
109+
return result;
110+
}
111+
34112
export class RichEditBrackets {
35113
_richEditBracketsBrand: void;
36114

@@ -41,58 +119,56 @@ export class RichEditBrackets {
41119
public readonly textIsBracket: { [text: string]: RichEditBracket; };
42120
public readonly textIsOpenBracket: { [text: string]: boolean; };
43121

44-
constructor(languageIdentifier: LanguageIdentifier, brackets: CharacterPair[]) {
45-
brackets = brackets.map(b => [b[0].toLowerCase(), b[1].toLowerCase()]);
122+
constructor(languageIdentifier: LanguageIdentifier, _brackets: CharacterPair[]) {
123+
const brackets = groupFuzzyBrackets(_brackets);
124+
46125
this.brackets = brackets.map((b, index) => {
47126
return new RichEditBracket(
48127
languageIdentifier,
49-
b[0],
50-
b[1],
51-
getRegexForBracketPair(b[0], b[1], brackets, index),
52-
getReversedRegexForBracketPair(b[0], b[1], brackets, index)
128+
b.open,
129+
b.close,
130+
getRegexForBracketPair(b.open, b.close, brackets, index),
131+
getReversedRegexForBracketPair(b.open, b.close, brackets, index)
53132
);
54133
});
134+
55135
this.forwardRegex = getRegexForBrackets(this.brackets);
56136
this.reversedRegex = getReversedRegexForBrackets(this.brackets);
57137

58138
this.textIsBracket = {};
59139
this.textIsOpenBracket = {};
60140

61-
let maxBracketLength = 0;
62-
this.brackets.forEach((b) => {
63-
this.textIsBracket[b.open] = b;
64-
this.textIsBracket[b.close] = b;
65-
this.textIsOpenBracket[b.open] = true;
66-
this.textIsOpenBracket[b.close] = false;
67-
maxBracketLength = Math.max(maxBracketLength, b.open.length);
68-
maxBracketLength = Math.max(maxBracketLength, b.close.length);
69-
});
70-
this.maxBracketLength = maxBracketLength;
71-
}
72-
}
73-
74-
function once<T, R>(keyFn: (input: T) => string, computeFn: (input: T) => R): (input: T) => R {
75-
let cache: { [key: string]: R; } = {};
76-
return (input: T): R => {
77-
let key = keyFn(input);
78-
if (!cache.hasOwnProperty(key)) {
79-
cache[key] = computeFn(input);
141+
this.maxBracketLength = 0;
142+
for (const bracket of this.brackets) {
143+
for (const open of bracket.open) {
144+
this.textIsBracket[open] = bracket;
145+
this.textIsOpenBracket[open] = true;
146+
this.maxBracketLength = Math.max(this.maxBracketLength, open.length);
147+
}
148+
for (const close of bracket.close) {
149+
this.textIsBracket[close] = bracket;
150+
this.textIsOpenBracket[close] = false;
151+
this.maxBracketLength = Math.max(this.maxBracketLength, close.length);
152+
}
80153
}
81-
return cache[key];
82-
};
154+
}
83155
}
84156

85-
function collectSuperstrings(str: string, brackets: CharacterPair[], currentIndex: number, dest: string[]): void {
157+
function collectSuperstrings(str: string, brackets: InternalBracket[], currentIndex: number, dest: string[]): void {
86158
for (let i = 0, len = brackets.length; i < len; i++) {
87159
if (i === currentIndex) {
88160
continue;
89161
}
90-
const [open, close] = brackets[i];
91-
if (open.indexOf(str) >= 0) {
92-
dest.push(open);
162+
const bracket = brackets[i];
163+
for (const open of bracket.open) {
164+
if (open.indexOf(str) >= 0) {
165+
dest.push(open);
166+
}
93167
}
94-
if (close.indexOf(str) >= 0) {
95-
dest.push(close);
168+
for (const close of bracket.close) {
169+
if (close.indexOf(str) >= 0) {
170+
dest.push(close);
171+
}
96172
}
97173
}
98174
}
@@ -101,49 +177,77 @@ function lengthcmp(a: string, b: string) {
101177
return a.length - b.length;
102178
}
103179

104-
function getRegexForBracketPair(open: string, close: string, brackets: CharacterPair[], currentIndex: number): RegExp {
180+
function unique(arr: string[]): string[] {
181+
if (arr.length <= 1) {
182+
return arr;
183+
}
184+
const result: string[] = [];
185+
const seen = new Set<string>();
186+
for (const element of arr) {
187+
if (seen.has(element)) {
188+
continue;
189+
}
190+
result.push(element);
191+
seen.add(element);
192+
}
193+
return result;
194+
}
195+
196+
function getRegexForBracketPair(open: string[], close: string[], brackets: InternalBracket[], currentIndex: number): RegExp {
105197
// search in all brackets for other brackets that are a superstring of these brackets
106-
const pieces: string[] = [open, close];
107-
collectSuperstrings(open, brackets, currentIndex, pieces);
108-
collectSuperstrings(close, brackets, currentIndex, pieces);
198+
let pieces: string[] = [];
199+
pieces = pieces.concat(open);
200+
pieces = pieces.concat(close);
201+
for (let i = 0, len = pieces.length; i < len; i++) {
202+
collectSuperstrings(pieces[i], brackets, currentIndex, pieces);
203+
}
204+
pieces = unique(pieces);
109205
pieces.sort(lengthcmp);
110206
pieces.reverse();
111207
return createBracketOrRegExp(pieces);
112208
}
113209

114-
function getReversedRegexForBracketPair(open: string, close: string, brackets: CharacterPair[], currentIndex: number): RegExp {
210+
function getReversedRegexForBracketPair(open: string[], close: string[], brackets: InternalBracket[], currentIndex: number): RegExp {
115211
// search in all brackets for other brackets that are a superstring of these brackets
116-
const pieces: string[] = [open, close];
117-
collectSuperstrings(open, brackets, currentIndex, pieces);
118-
collectSuperstrings(close, brackets, currentIndex, pieces);
212+
let pieces: string[] = [];
213+
pieces = pieces.concat(open);
214+
pieces = pieces.concat(close);
215+
for (let i = 0, len = pieces.length; i < len; i++) {
216+
collectSuperstrings(pieces[i], brackets, currentIndex, pieces);
217+
}
218+
pieces = unique(pieces);
119219
pieces.sort(lengthcmp);
120220
pieces.reverse();
121221
return createBracketOrRegExp(pieces.map(toReversedString));
122222
}
123223

124-
const getRegexForBrackets = once<ISimpleInternalBracket[], RegExp>(
125-
(input) => input.map(b => `${b.open};${b.close}`).join(';'),
126-
(input) => {
127-
let pieces: string[] = [];
128-
input.forEach((b) => {
129-
pieces.push(b.open);
130-
pieces.push(b.close);
131-
});
132-
return createBracketOrRegExp(pieces);
224+
function getRegexForBrackets(brackets: RichEditBracket[]): RegExp {
225+
let pieces: string[] = [];
226+
for (const bracket of brackets) {
227+
for (const open of bracket.open) {
228+
pieces.push(open);
229+
}
230+
for (const close of bracket.close) {
231+
pieces.push(close);
232+
}
133233
}
134-
);
234+
pieces = unique(pieces);
235+
return createBracketOrRegExp(pieces);
236+
}
135237

136-
const getReversedRegexForBrackets = once<ISimpleInternalBracket[], RegExp>(
137-
(input) => input.map(b => `${b.open};${b.close}`).join(';'),
138-
(input) => {
139-
let pieces: string[] = [];
140-
input.forEach((b) => {
141-
pieces.push(toReversedString(b.open));
142-
pieces.push(toReversedString(b.close));
143-
});
144-
return createBracketOrRegExp(pieces);
238+
function getReversedRegexForBrackets(brackets: RichEditBracket[]): RegExp {
239+
let pieces: string[] = [];
240+
for (const bracket of brackets) {
241+
for (const open of bracket.open) {
242+
pieces.push(open);
243+
}
244+
for (const close of bracket.close) {
245+
pieces.push(close);
246+
}
145247
}
146-
);
248+
pieces = unique(pieces);
249+
return createBracketOrRegExp(pieces.map(toReversedString));
250+
}
147251

148252
function prepareBracketForRegExp(str: string): string {
149253
// This bracket pair uses letters like e.g. "begin" - "end"

src/vs/editor/contrib/goToDefinition/goToDefinitionMouse.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ class GotoDefinitionWithMouseEditorContribution implements editorCommon.IEditorC
220220
brackets.push(currentBracket);
221221
} else {
222222
const lastBracket = brackets[brackets.length - 1];
223-
if (lastBracket.open === currentBracket.open && lastBracket.isOpen && !currentBracket.isOpen) {
223+
if (lastBracket.open[0] === currentBracket.open[0] && lastBracket.isOpen && !currentBracket.isOpen) {
224224
brackets.pop();
225225
} else {
226226
brackets.push(currentBracket);

src/vs/editor/contrib/smartSelect/bracketSelections.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ export class BracketSelectionRangeProvider implements SelectionRangeProvider {
5151
setTimeout(() => BracketSelectionRangeProvider._bracketsRightYield(resolve, round + 1, model, pos, ranges));
5252
break;
5353
}
54-
const key = bracket.close;
54+
const key = bracket.close[0];
5555
if (bracket.isOpen) {
5656
// wait for closing
5757
let val = counts.has(key) ? counts.get(key)! : 0;
@@ -96,7 +96,7 @@ export class BracketSelectionRangeProvider implements SelectionRangeProvider {
9696
setTimeout(() => BracketSelectionRangeProvider._bracketsLeftYield(resolve, round + 1, model, pos, ranges, bucket));
9797
break;
9898
}
99-
const key = bracket.close;
99+
const key = bracket.close[0];
100100
if (!bracket.isOpen) {
101101
// wait for opening
102102
let val = counts.has(key) ? counts.get(key)! : 0;

0 commit comments

Comments
 (0)