Skip to content

Commit 4a9caec

Browse files
committed
1 parent 615de73 commit 4a9caec

8 files changed

Lines changed: 149 additions & 70 deletions

File tree

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

Lines changed: 3 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -55,14 +55,6 @@ const autoCloseAlways = () => true;
5555
const autoCloseNever = () => false;
5656
const autoCloseBeforeWhitespace = (chr: string) => (chr === ' ' || chr === '\t');
5757

58-
function appendEntry<K, V>(target: Map<K, V[]>, key: K, value: V): void {
59-
if (target.has(key)) {
60-
target.get(key)!.push(value);
61-
} else {
62-
target.set(key, [value]);
63-
}
64-
}
65-
6658
export class CursorConfiguration {
6759
_cursorMoveConfigurationBrand: void;
6860

@@ -136,8 +128,6 @@ export class CursorConfiguration {
136128
this.autoSurround = options.get(EditorOption.autoSurround);
137129
this.autoIndent = options.get(EditorOption.autoIndent);
138130

139-
this.autoClosingPairsOpen2 = new Map<string, StandardAutoClosingPairConditional[]>();
140-
this.autoClosingPairsClose2 = new Map<string, StandardAutoClosingPairConditional[]>();
141131
this.surroundingPairs = {};
142132
this._electricChars = null;
143133

@@ -146,15 +136,9 @@ export class CursorConfiguration {
146136
bracket: CursorConfiguration._getShouldAutoClose(languageIdentifier, this.autoClosingBrackets)
147137
};
148138

149-
let autoClosingPairs = CursorConfiguration._getAutoClosingPairs(languageIdentifier);
150-
if (autoClosingPairs) {
151-
for (const pair of autoClosingPairs) {
152-
appendEntry(this.autoClosingPairsOpen2, pair.open.charAt(pair.open.length - 1), pair);
153-
if (pair.close.length === 1) {
154-
appendEntry(this.autoClosingPairsClose2, pair.close, pair);
155-
}
156-
}
157-
}
139+
const autoClosingPairs = LanguageConfigurationRegistry.getAutoClosingPairs(languageIdentifier.id);
140+
this.autoClosingPairsOpen2 = autoClosingPairs.autoClosingPairsOpen;
141+
this.autoClosingPairsClose2 = autoClosingPairs.autoClosingPairsClose;
158142

159143
let surroundingPairs = CursorConfiguration._getSurroundingPairs(languageIdentifier);
160144
if (surroundingPairs) {
@@ -190,15 +174,6 @@ export class CursorConfiguration {
190174
}
191175
}
192176

193-
private static _getAutoClosingPairs(languageIdentifier: LanguageIdentifier): StandardAutoClosingPairConditional[] | null {
194-
try {
195-
return LanguageConfigurationRegistry.getAutoClosingPairs(languageIdentifier.id);
196-
} catch (e) {
197-
onUnexpectedError(e);
198-
return null;
199-
}
200-
}
201-
202177
private static _getShouldAutoClose(languageIdentifier: LanguageIdentifier, autoCloseConfig: EditorAutoClosingStrategy): (ch: string) => boolean {
203178
switch (autoCloseConfig) {
204179
case 'beforeWhitespace':

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

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@
55

66
import * as strings from 'vs/base/common/strings';
77
import { ReplaceCommand } from 'vs/editor/common/commands/replaceCommand';
8+
import { EditorAutoClosingStrategy } from 'vs/editor/common/config/editorOptions';
89
import { CursorColumns, CursorConfiguration, EditOperationResult, EditOperationType, ICursorSimpleModel, isQuote } from 'vs/editor/common/controller/cursorCommon';
910
import { MoveOperations } from 'vs/editor/common/controller/cursorMoveOperations';
1011
import { Range } from 'vs/editor/common/core/range';
1112
import { Selection } from 'vs/editor/common/core/selection';
1213
import { ICommand } from 'vs/editor/common/editorCommon';
14+
import { StandardAutoClosingPairConditional } from 'vs/editor/common/modes/languageConfiguration';
1315

1416
export class DeleteOperations {
1517

@@ -47,8 +49,14 @@ export class DeleteOperations {
4749
return [shouldPushStackElementBefore, commands];
4850
}
4951

50-
private static _isAutoClosingPairDelete(config: CursorConfiguration, model: ICursorSimpleModel, selections: Selection[]): boolean {
51-
if (config.autoClosingBrackets === 'never' && config.autoClosingQuotes === 'never') {
52+
public static isAutoClosingPairDelete(
53+
autoClosingBrackets: EditorAutoClosingStrategy,
54+
autoClosingQuotes: EditorAutoClosingStrategy,
55+
autoClosingPairsOpen: Map<string, StandardAutoClosingPairConditional[]>,
56+
model: ICursorSimpleModel,
57+
selections: Selection[]
58+
): boolean {
59+
if (autoClosingBrackets === 'never' && autoClosingQuotes === 'never') {
5260
return false;
5361
}
5462

@@ -61,24 +69,27 @@ export class DeleteOperations {
6169
}
6270

6371
const lineText = model.getLineContent(position.lineNumber);
64-
const character = lineText[position.column - 2];
72+
if (position.column < 2 || position.column >= lineText.length + 1) {
73+
return false;
74+
}
75+
const character = lineText.charAt(position.column - 2);
6576

66-
const autoClosingPairCandidates = config.autoClosingPairsOpen2.get(character);
77+
const autoClosingPairCandidates = autoClosingPairsOpen.get(character);
6778
if (!autoClosingPairCandidates) {
6879
return false;
6980
}
7081

7182
if (isQuote(character)) {
72-
if (config.autoClosingQuotes === 'never') {
83+
if (autoClosingQuotes === 'never') {
7384
return false;
7485
}
7586
} else {
76-
if (config.autoClosingBrackets === 'never') {
87+
if (autoClosingBrackets === 'never') {
7788
return false;
7889
}
7990
}
8091

81-
const afterCharacter = lineText[position.column - 1];
92+
const afterCharacter = lineText.charAt(position.column - 1);
8293

8394
let foundAutoClosingPair = false;
8495
for (const autoClosingPairCandidate of autoClosingPairCandidates) {
@@ -111,7 +122,7 @@ export class DeleteOperations {
111122

112123
public static deleteLeft(prevEditOperationType: EditOperationType, config: CursorConfiguration, model: ICursorSimpleModel, selections: Selection[]): [boolean, Array<ICommand | null>] {
113124

114-
if (this._isAutoClosingPairDelete(config, model, selections)) {
125+
if (this.isAutoClosingPairDelete(config.autoClosingBrackets, config.autoClosingQuotes, config.autoClosingPairsOpen2, model, selections)) {
115126
return this._runAutoClosingPairDelete(config, model, selections);
116127
}
117128

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

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,15 @@
55

66
import { CharCode } from 'vs/base/common/charCode';
77
import * as strings from 'vs/base/common/strings';
8+
import { EditorAutoClosingStrategy } from 'vs/editor/common/config/editorOptions';
89
import { CursorConfiguration, ICursorSimpleModel, SingleCursorState } from 'vs/editor/common/controller/cursorCommon';
10+
import { DeleteOperations } from 'vs/editor/common/controller/cursorDeleteOperations';
911
import { WordCharacterClass, WordCharacterClassifier, getMapForWordSeparators } from 'vs/editor/common/controller/wordCharacterClassifier';
1012
import { Position } from 'vs/editor/common/core/position';
1113
import { Range } from 'vs/editor/common/core/range';
1214
import { Selection } from 'vs/editor/common/core/selection';
1315
import { ITextModel, IWordAtPosition } from 'vs/editor/common/model';
16+
import { AutoClosingPairs } from 'vs/editor/common/modes/languageConfiguration';
1417

1518
interface IFindWordResult {
1619
/**
@@ -44,6 +47,16 @@ export const enum WordNavigationType {
4447
WordAccessibility = 3 // Respect chrome defintion of a word
4548
}
4649

50+
export interface DeleteWordContext {
51+
wordSeparators: WordCharacterClassifier;
52+
model: ITextModel;
53+
selection: Selection;
54+
whitespaceHeuristics: boolean;
55+
autoClosingBrackets: EditorAutoClosingStrategy;
56+
autoClosingQuotes: EditorAutoClosingStrategy;
57+
autoClosingPairs: AutoClosingPairs;
58+
}
59+
4760
export class WordOperations {
4861

4962
private static _createWord(lineContent: string, wordType: WordType, nextCharClass: WordCharacterClass, start: number, end: number): IFindWordResult {
@@ -361,11 +374,21 @@ export class WordOperations {
361374
return null;
362375
}
363376

364-
public static deleteWordLeft(wordSeparators: WordCharacterClassifier, model: ICursorSimpleModel, selection: Selection, whitespaceHeuristics: boolean, wordNavigationType: WordNavigationType): Range | null {
377+
public static deleteWordLeft(ctx: DeleteWordContext, wordNavigationType: WordNavigationType): Range | null {
378+
const wordSeparators = ctx.wordSeparators;
379+
const model = ctx.model;
380+
const selection = ctx.selection;
381+
const whitespaceHeuristics = ctx.whitespaceHeuristics;
382+
365383
if (!selection.isEmpty()) {
366384
return selection;
367385
}
368386

387+
if (DeleteOperations.isAutoClosingPairDelete(ctx.autoClosingBrackets, ctx.autoClosingQuotes, ctx.autoClosingPairs.autoClosingPairsOpen, ctx.model, [ctx.selection])) {
388+
const position = ctx.selection.getPosition();
389+
return new Range(position.lineNumber, position.column - 1, position.lineNumber, position.column + 1);
390+
}
391+
369392
const position = new Position(selection.positionLineNumber, selection.positionColumn);
370393

371394
let lineNumber = position.lineNumber;
@@ -447,7 +470,12 @@ export class WordOperations {
447470
return null;
448471
}
449472

450-
public static deleteWordRight(wordSeparators: WordCharacterClassifier, model: ICursorSimpleModel, selection: Selection, whitespaceHeuristics: boolean, wordNavigationType: WordNavigationType): Range | null {
473+
public static deleteWordRight(ctx: DeleteWordContext, wordNavigationType: WordNavigationType): Range | null {
474+
const wordSeparators = ctx.wordSeparators;
475+
const model = ctx.model;
476+
const selection = ctx.selection;
477+
const whitespaceHeuristics = ctx.whitespaceHeuristics;
478+
451479
if (!selection.isEmpty()) {
452480
return selection;
453481
}
@@ -621,21 +649,21 @@ export class WordOperations {
621649
}
622650

623651
export class WordPartOperations extends WordOperations {
624-
public static deleteWordPartLeft(wordSeparators: WordCharacterClassifier, model: ICursorSimpleModel, selection: Selection, whitespaceHeuristics: boolean): Range {
652+
public static deleteWordPartLeft(ctx: DeleteWordContext): Range {
625653
const candidates = enforceDefined([
626-
WordOperations.deleteWordLeft(wordSeparators, model, selection, whitespaceHeuristics, WordNavigationType.WordStart),
627-
WordOperations.deleteWordLeft(wordSeparators, model, selection, whitespaceHeuristics, WordNavigationType.WordEnd),
628-
WordOperations._deleteWordPartLeft(model, selection)
654+
WordOperations.deleteWordLeft(ctx, WordNavigationType.WordStart),
655+
WordOperations.deleteWordLeft(ctx, WordNavigationType.WordEnd),
656+
WordOperations._deleteWordPartLeft(ctx.model, ctx.selection)
629657
]);
630658
candidates.sort(Range.compareRangesUsingEnds);
631659
return candidates[2];
632660
}
633661

634-
public static deleteWordPartRight(wordSeparators: WordCharacterClassifier, model: ICursorSimpleModel, selection: Selection, whitespaceHeuristics: boolean): Range {
662+
public static deleteWordPartRight(ctx: DeleteWordContext): Range {
635663
const candidates = enforceDefined([
636-
WordOperations.deleteWordRight(wordSeparators, model, selection, whitespaceHeuristics, WordNavigationType.WordStart),
637-
WordOperations.deleteWordRight(wordSeparators, model, selection, whitespaceHeuristics, WordNavigationType.WordEnd),
638-
WordOperations._deleteWordPartRight(model, selection)
664+
WordOperations.deleteWordRight(ctx, WordNavigationType.WordStart),
665+
WordOperations.deleteWordRight(ctx, WordNavigationType.WordEnd),
666+
WordOperations._deleteWordPartRight(ctx.model, ctx.selection)
639667
]);
640668
candidates.sort(Range.compareRangesUsingStarts);
641669
return candidates[0];

src/vs/editor/common/modes/languageConfiguration.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,3 +289,31 @@ export class StandardAutoClosingPairConditional {
289289
return (this._standardTokenMask & <number>standardToken) === 0;
290290
}
291291
}
292+
293+
/**
294+
* @internal
295+
*/
296+
export class AutoClosingPairs {
297+
298+
public readonly autoClosingPairsOpen: Map<string, StandardAutoClosingPairConditional[]>;
299+
public readonly autoClosingPairsClose: Map<string, StandardAutoClosingPairConditional[]>;
300+
301+
constructor(autoClosingPairs: StandardAutoClosingPairConditional[]) {
302+
this.autoClosingPairsOpen = new Map<string, StandardAutoClosingPairConditional[]>();
303+
this.autoClosingPairsClose = new Map<string, StandardAutoClosingPairConditional[]>();
304+
for (const pair of autoClosingPairs) {
305+
appendEntry(this.autoClosingPairsOpen, pair.open.charAt(pair.open.length - 1), pair);
306+
if (pair.close.length === 1) {
307+
appendEntry(this.autoClosingPairsClose, pair.close, pair);
308+
}
309+
}
310+
}
311+
}
312+
313+
function appendEntry<K, V>(target: Map<K, V[]>, key: K, value: V): void {
314+
if (target.has(key)) {
315+
target.get(key)!.push(value);
316+
} else {
317+
target.set(key, [value]);
318+
}
319+
}

src/vs/editor/common/modes/languageConfigurationRegistry.ts

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { Range } from 'vs/editor/common/core/range';
1111
import { ITextModel } from 'vs/editor/common/model';
1212
import { DEFAULT_WORD_REGEXP, ensureValidWordDefinition } from 'vs/editor/common/model/wordHelper';
1313
import { LanguageId, LanguageIdentifier } from 'vs/editor/common/modes';
14-
import { EnterAction, FoldingRules, IAutoClosingPair, IndentAction, IndentationRule, LanguageConfiguration, StandardAutoClosingPairConditional, CompleteEnterAction } from 'vs/editor/common/modes/languageConfiguration';
14+
import { EnterAction, FoldingRules, IAutoClosingPair, IndentAction, IndentationRule, LanguageConfiguration, StandardAutoClosingPairConditional, CompleteEnterAction, AutoClosingPairs } from 'vs/editor/common/modes/languageConfiguration';
1515
import { createScopedLineTokens, ScopedLineTokens } from 'vs/editor/common/modes/supports';
1616
import { CharacterPairSupport } from 'vs/editor/common/modes/supports/characterPair';
1717
import { BracketElectricCharacterSupport, IElectricAction } from 'vs/editor/common/modes/supports/electricCharacter';
@@ -235,12 +235,9 @@ export class LanguageConfigurationRegistryImpl {
235235
return value.characterPair || null;
236236
}
237237

238-
public getAutoClosingPairs(languageId: LanguageId): StandardAutoClosingPairConditional[] {
239-
let characterPairSupport = this._getCharacterPairSupport(languageId);
240-
if (!characterPairSupport) {
241-
return [];
242-
}
243-
return characterPairSupport.getAutoClosingPairs();
238+
public getAutoClosingPairs(languageId: LanguageId): AutoClosingPairs {
239+
const characterPairSupport = this._getCharacterPairSupport(languageId);
240+
return new AutoClosingPairs(characterPairSupport ? characterPairSupport.getAutoClosingPairs() : []);
244241
}
245242

246243
public getAutoCloseBeforeSet(languageId: LanguageId): string {

src/vs/editor/contrib/wordOperations/test/wordOperations.test.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ import { CursorWordEndLeft, CursorWordEndLeftSelect, CursorWordEndRight, CursorW
1313
import { withTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor';
1414
import { CoreEditingCommands } from 'vs/editor/browser/controller/coreCommands';
1515
import { ViewModel } from 'vs/editor/common/viewModel/viewModelImpl';
16+
import { LanguageIdentifier } from 'vs/editor/common/modes';
17+
import { MockMode } from 'vs/editor/test/common/mocks/mockMode';
18+
import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry';
19+
import { createTextModel } from 'vs/editor/test/common/editorTestUtils';
1620

1721
suite('WordOperations', () => {
1822

@@ -721,4 +725,29 @@ suite('WordOperations', () => {
721725
deleteWordLeft(editor); assert.equal(model.getLineContent(1), 'A line with text. And another one', '001');
722726
});
723727
});
728+
729+
test('deleteWordLeft - issue #91855: Matching (quote, bracket, paren) doesn\'t get deleted when hitting Ctrl+Backspace', () => {
730+
const languageId = new LanguageIdentifier('myTestMode', 5);
731+
class TestMode extends MockMode {
732+
constructor() {
733+
super(languageId);
734+
this._register(LanguageConfigurationRegistry.register(this.getLanguageIdentifier(), {
735+
autoClosingPairs: [
736+
{ open: '\"', close: '\"' }
737+
]
738+
}));
739+
}
740+
}
741+
742+
const mode = new TestMode();
743+
const model = createTextModel('a ""', undefined, languageId);
744+
745+
withTestCodeEditor(null, { model }, (editor, _) => {
746+
editor.setPosition(new Position(1, 4));
747+
deleteWordLeft(editor); assert.equal(model.getLineContent(1), 'a ');
748+
});
749+
750+
model.dispose();
751+
mode.dispose();
752+
});
724753
});

0 commit comments

Comments
 (0)