Skip to content

Commit 37981d8

Browse files
committed
Fixes #79430: Bring all auto-closing logic in one place which can now also handle multi-character auto-closing pairs
1 parent 1271e25 commit 37981d8

12 files changed

+235
-358
lines changed

src/vs/editor/common/controller/cursor.ts

+15-11
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,14 @@ export class CursorModelState {
8686

8787
class AutoClosedAction {
8888

89+
public static getAllAutoClosedCharacters(autoClosedActions: AutoClosedAction[]): Range[] {
90+
let autoClosedCharacters: Range[] = [];
91+
for (const autoClosedAction of autoClosedActions) {
92+
autoClosedCharacters = autoClosedCharacters.concat(autoClosedAction.getAutoClosedCharactersRanges());
93+
}
94+
return autoClosedCharacters;
95+
}
96+
8997
private readonly _model: ITextModel;
9098

9199
private _autoClosedCharactersDecorations: string[];
@@ -593,11 +601,12 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors {
593601
}
594602
const closeChar = m[1];
595603

596-
const openChar = this.context.config.autoClosingPairsClose[closeChar];
597-
if (!openChar) {
604+
const autoClosingPairsCandidates = this.context.config.autoClosingPairsClose2.get(closeChar);
605+
if (!autoClosingPairsCandidates || autoClosingPairsCandidates.length !== 1) {
598606
return null;
599607
}
600608

609+
const openChar = autoClosingPairsCandidates[0].open;
601610
const closeCharIndex = edit.text.length - m[2].length - 1;
602611
const openCharIndex = edit.text.lastIndexOf(openChar, closeCharIndex - 1);
603612
if (openCharIndex === -1) {
@@ -738,7 +747,8 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors {
738747
private _interpretCompositionEnd(source: string) {
739748
if (!this._isDoingComposition && source === 'keyboard') {
740749
// composition finishes, let's check if we need to auto complete if necessary.
741-
this._executeEditOperation(TypeOperations.compositionEndWithInterceptors(this._prevEditOperationType, this.context.config, this.context.model, this.getSelections()));
750+
const autoClosedCharacters = AutoClosedAction.getAllAutoClosedCharacters(this._autoClosedActions);
751+
this._executeEditOperation(TypeOperations.compositionEndWithInterceptors(this._prevEditOperationType, this.context.config, this.context.model, this.getSelections(), autoClosedCharacters));
742752
}
743753
}
744754

@@ -756,14 +766,8 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors {
756766
chr = text.charAt(i);
757767
}
758768

759-
let autoClosedCharacters: Range[] = [];
760-
if (this._autoClosedActions.length > 0) {
761-
for (let i = 0, len = this._autoClosedActions.length; i < len; i++) {
762-
autoClosedCharacters = autoClosedCharacters.concat(this._autoClosedActions[i].getAutoClosedCharactersRanges());
763-
}
764-
}
765-
766-
// Here we must interpret each typed character individually, that's why we create a new context
769+
// Here we must interpret each typed character individually
770+
const autoClosedCharacters = AutoClosedAction.getAllAutoClosedCharacters(this._autoClosedActions);
767771
this._executeEditOperation(TypeOperations.typeWithInterceptors(this._prevEditOperationType, this.context.config, this.context.model, this.getSelections(), autoClosedCharacters, chr));
768772
}
769773

src/vs/editor/common/controller/cursorCommon.ts

+21-8
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import { ICommand, IConfiguration, ScrollType } from 'vs/editor/common/editorCom
1515
import { ITextModel, TextModelResolvedOptions } from 'vs/editor/common/model';
1616
import { TextModel } from 'vs/editor/common/model/textModel';
1717
import { LanguageIdentifier } from 'vs/editor/common/modes';
18-
import { IAutoClosingPair } from 'vs/editor/common/modes/languageConfiguration';
18+
import { IAutoClosingPair, StandardAutoClosingPairConditional } from 'vs/editor/common/modes/languageConfiguration';
1919
import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry';
2020
import { VerticalRevealType } from 'vs/editor/common/view/viewEvents';
2121
import { IViewModel } from 'vs/editor/common/viewModel/viewModel';
@@ -67,11 +67,22 @@ export interface ICursors {
6767
export interface CharacterMap {
6868
[char: string]: string;
6969
}
70+
export interface MultipleCharacterMap {
71+
[char: string]: string[];
72+
}
7073

7174
const autoCloseAlways = () => true;
7275
const autoCloseNever = () => false;
7376
const autoCloseBeforeWhitespace = (chr: string) => (chr === ' ' || chr === '\t');
7477

78+
function appendEntry<K, V>(target: Map<K, V[]>, key: K, value: V): void {
79+
if (target.has(key)) {
80+
target.get(key)!.push(value);
81+
} else {
82+
target.set(key, [value]);
83+
}
84+
}
85+
7586
export class CursorConfiguration {
7687
_cursorMoveConfigurationBrand: void;
7788

@@ -90,8 +101,8 @@ export class CursorConfiguration {
90101
public readonly autoClosingQuotes: EditorAutoClosingStrategy;
91102
public readonly autoSurround: EditorAutoSurroundStrategy;
92103
public readonly autoIndent: boolean;
93-
public readonly autoClosingPairsOpen: CharacterMap;
94-
public readonly autoClosingPairsClose: CharacterMap;
104+
public readonly autoClosingPairsOpen2: Map<string, StandardAutoClosingPairConditional[]>;
105+
public readonly autoClosingPairsClose2: Map<string, StandardAutoClosingPairConditional[]>;
95106
public readonly surroundingPairs: CharacterMap;
96107
public readonly shouldAutoCloseBefore: { quote: (ch: string) => boolean, bracket: (ch: string) => boolean };
97108

@@ -138,8 +149,8 @@ export class CursorConfiguration {
138149
this.autoSurround = c.autoSurround;
139150
this.autoIndent = c.autoIndent;
140151

141-
this.autoClosingPairsOpen = {};
142-
this.autoClosingPairsClose = {};
152+
this.autoClosingPairsOpen2 = new Map<string, StandardAutoClosingPairConditional[]>();
153+
this.autoClosingPairsClose2 = new Map<string, StandardAutoClosingPairConditional[]>();
143154
this.surroundingPairs = {};
144155
this._electricChars = null;
145156

@@ -151,8 +162,10 @@ export class CursorConfiguration {
151162
let autoClosingPairs = CursorConfiguration._getAutoClosingPairs(languageIdentifier);
152163
if (autoClosingPairs) {
153164
for (const pair of autoClosingPairs) {
154-
this.autoClosingPairsOpen[pair.open] = pair.close;
155-
this.autoClosingPairsClose[pair.close] = pair.open;
165+
appendEntry(this.autoClosingPairsOpen2, pair.open.charAt(pair.open.length - 1), pair);
166+
if (pair.close.length === 1) {
167+
appendEntry(this.autoClosingPairsClose2, pair.close, pair);
168+
}
156169
}
157170
}
158171

@@ -190,7 +203,7 @@ export class CursorConfiguration {
190203
}
191204
}
192205

193-
private static _getAutoClosingPairs(languageIdentifier: LanguageIdentifier): IAutoClosingPair[] | null {
206+
private static _getAutoClosingPairs(languageIdentifier: LanguageIdentifier): StandardAutoClosingPairConditional[] | null {
194207
try {
195208
return LanguageConfigurationRegistry.getAutoClosingPairs(languageIdentifier.id);
196209
} catch (e) {

src/vs/editor/common/controller/cursorDeleteOperations.ts

+9-3
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,8 @@ export class DeleteOperations {
6363
const lineText = model.getLineContent(position.lineNumber);
6464
const character = lineText[position.column - 2];
6565

66-
if (!config.autoClosingPairsOpen.hasOwnProperty(character)) {
66+
const autoClosingPairCandidates = config.autoClosingPairsOpen2.get(character);
67+
if (!autoClosingPairCandidates) {
6768
return false;
6869
}
6970

@@ -78,9 +79,14 @@ export class DeleteOperations {
7879
}
7980

8081
const afterCharacter = lineText[position.column - 1];
81-
const closeCharacter = config.autoClosingPairsOpen[character];
8282

83-
if (afterCharacter !== closeCharacter) {
83+
let foundAutoClosingPair = false;
84+
for (const autoClosingPairCandidate of autoClosingPairCandidates) {
85+
if (autoClosingPairCandidate.open === character && autoClosingPairCandidate.close === afterCharacter) {
86+
foundAutoClosingPair = true;
87+
}
88+
}
89+
if (!foundAutoClosingPair) {
8490
return false;
8591
}
8692
}

0 commit comments

Comments
 (0)