From d561058791ef4f37ccf85dbd3ab5f11b0ad20e4b Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Sat, 13 Feb 2016 14:19:25 +0100 Subject: [PATCH] Better handling of cross-mode bracket matching --- src/vs/editor/common/editorCommon.ts | 1 + .../common/model/textModelWithTokens.ts | 36 ++++++++++++-- .../modes/supports/electricCharacter.ts | 1 + src/vs/languages/html/common/html.ts | 8 ++- .../languages/html/test/common/html.test.ts | 49 ++++++++++++++++++- 5 files changed, 87 insertions(+), 8 deletions(-) diff --git a/src/vs/editor/common/editorCommon.ts b/src/vs/editor/common/editorCommon.ts index dc0e9f0609f..0356a9e1b19 100644 --- a/src/vs/editor/common/editorCommon.ts +++ b/src/vs/editor/common/editorCommon.ts @@ -1377,6 +1377,7 @@ export interface ITextModel { } export interface IRichEditBracket { + modeId: string; open: string; close: string; forwardRegex: RegExp; diff --git a/src/vs/editor/common/model/textModelWithTokens.ts b/src/vs/editor/common/model/textModelWithTokens.ts index c6ab2d6fcb4..abbb992d994 100644 --- a/src/vs/editor/common/model/textModelWithTokens.ts +++ b/src/vs/editor/common/model/textModelWithTokens.ts @@ -998,9 +998,8 @@ export class TextModelWithTokens extends TextModel implements EditorCommon.IToke private _findMatchingBracketUp(bracket:EditorCommon.IRichEditBracket, position:EditorCommon.IEditorPosition): Range { // console.log('_findMatchingBracketUp: ', 'bracket: ', JSON.stringify(bracket), 'startPosition: ', String(position)); - + let modeId = bracket.modeId; let tokensMap = this._tokensInflatorMap; - // TODO@Alex: account for mode transitions let reversedBracketRegex = bracket.reversedRegex; let count = -1; @@ -1008,12 +1007,20 @@ export class TextModelWithTokens extends TextModel implements EditorCommon.IToke let lineTokens = this._lines[lineNumber - 1].getTokens(); let lineText = this._lines[lineNumber - 1].text; let tokens = lineTokens.getBinaryEncodedTokens(); + let modeTransitions = this._lines[lineNumber - 1].getModeTransitions().toArray(this._mode); + let currentModeIndex = modeTransitions.length - 1; + let currentModeStart = modeTransitions[currentModeIndex].startIndex; + let currentModeId = modeTransitions[currentModeIndex].mode.getId(); let tokensLength = tokens.length - 1; let currentTokenEnd = lineText.length; if (lineNumber === position.lineNumber) { tokensLength = lineTokens.findIndexOfOffset(position.column - 1); currentTokenEnd = position.column - 1; + + currentModeIndex = Arrays.findIndexInSegmentsArray(modeTransitions, position.column - 1); + currentModeStart = modeTransitions[currentModeIndex].startIndex; + currentModeId = modeTransitions[currentModeIndex].mode.getId(); } for (let tokenIndex = tokensLength; tokenIndex >= 0; tokenIndex--) { @@ -1021,7 +1028,13 @@ export class TextModelWithTokens extends TextModel implements EditorCommon.IToke let currentTokenType = getType(tokensMap, currentToken); let currentTokenStart = getStartIndex(currentToken); - if (!ignoreBracketsInToken(currentTokenType)) { + if (currentTokenStart < currentModeStart) { + currentModeIndex--; + currentModeStart = modeTransitions[currentModeIndex].startIndex; + currentModeId = modeTransitions[currentModeIndex].mode.getId(); + } + + if (currentModeId === modeId && !ignoreBracketsInToken(currentTokenType)) { while (true) { let r = BracketsUtils.findPrevBracketInToken(reversedBracketRegex, lineNumber, lineText, currentTokenStart, currentTokenEnd); @@ -1055,8 +1068,8 @@ export class TextModelWithTokens extends TextModel implements EditorCommon.IToke private _findMatchingBracketDown(bracket:EditorCommon.IRichEditBracket, position:EditorCommon.IEditorPosition): Range { // console.log('_findMatchingBracketDown: ', 'bracket: ', JSON.stringify(bracket), 'startPosition: ', String(position)); + let modeId = bracket.modeId; let tokensMap = this._tokensInflatorMap; - // TODO@Alex: account for mode transitions let bracketRegex = bracket.forwardRegex; let count = 1; @@ -1064,12 +1077,20 @@ export class TextModelWithTokens extends TextModel implements EditorCommon.IToke let lineTokens = this._lines[lineNumber - 1].getTokens(); let lineText = this._lines[lineNumber - 1].text; let tokens = lineTokens.getBinaryEncodedTokens(); + let modeTransitions = this._lines[lineNumber - 1].getModeTransitions().toArray(this._mode); + let currentModeIndex = 0; + let nextModeStart = (currentModeIndex + 1 < modeTransitions.length ? modeTransitions[currentModeIndex + 1].startIndex : lineText.length); + let currentModeId = modeTransitions[currentModeIndex].mode.getId(); let startTokenIndex = 0; let currentTokenStart = getStartIndex(startTokenIndex); if (lineNumber === position.lineNumber) { startTokenIndex = lineTokens.findIndexOfOffset(position.column - 1); currentTokenStart = Math.max(currentTokenStart, position.column - 1); + + currentModeIndex = Arrays.findIndexInSegmentsArray(modeTransitions, position.column - 1); + nextModeStart = (currentModeIndex + 1 < modeTransitions.length ? modeTransitions[currentModeIndex + 1].startIndex : lineText.length); + currentModeId = modeTransitions[currentModeIndex].mode.getId(); } for (let tokenIndex = startTokenIndex, tokensLength = tokens.length; tokenIndex < tokensLength; tokenIndex++) { @@ -1077,8 +1098,13 @@ export class TextModelWithTokens extends TextModel implements EditorCommon.IToke let currentTokenType = getType(tokensMap, currentToken); let currentTokenEnd = tokenIndex + 1 < tokensLength ? getStartIndex(tokens[tokenIndex + 1]) : lineText.length; + if (currentTokenStart >= nextModeStart) { + currentModeIndex++; + nextModeStart = (currentModeIndex + 1 < modeTransitions.length ? modeTransitions[currentModeIndex + 1].startIndex : lineText.length); + currentModeId = modeTransitions[currentModeIndex].mode.getId(); + } - if (!ignoreBracketsInToken(currentTokenType)) { + if (currentModeId === modeId && !ignoreBracketsInToken(currentTokenType)) { while (true) { let r = BracketsUtils.findNextBracketInToken(bracketRegex, lineNumber, lineText, currentTokenStart, currentTokenEnd); if (!r) { diff --git a/src/vs/editor/common/modes/supports/electricCharacter.ts b/src/vs/editor/common/modes/supports/electricCharacter.ts index 665feb9adc6..1afe4c25ab5 100644 --- a/src/vs/editor/common/modes/supports/electricCharacter.ts +++ b/src/vs/editor/common/modes/supports/electricCharacter.ts @@ -84,6 +84,7 @@ export class Brackets { this._modeId = modeId; this._brackets = brackets.map((b) => { return { + modeId: modeId, open: b.open, close: b.close, forwardRegex: getRegexForBracketPair({ open: b.open, close: b.close }), diff --git a/src/vs/languages/html/common/html.ts b/src/vs/languages/html/common/html.ts index 10d0839f33b..d3d0065221a 100644 --- a/src/vs/languages/html/common/html.ts +++ b/src/vs/languages/html/common/html.ts @@ -347,11 +347,15 @@ export class HTMLMode extends AbstractMode i }, brackets: [ - [''] + [''], + ['<', '>'], ], __electricCharacterSupport: { - brackets: [], + brackets: [ + { tokenType: 'bla', open: '', isElectric: true }, + { tokenType: 'bla', open: '<', close: '>', isElectric: true } + ], caseInsensitive: true, embeddedElectricCharacters: ['*', '}', ']', ')'] }, diff --git a/src/vs/languages/html/test/common/html.test.ts b/src/vs/languages/html/test/common/html.test.ts index 6a0579a339f..243fe1375fd 100644 --- a/src/vs/languages/html/test/common/html.test.ts +++ b/src/vs/languages/html/test/common/html.test.ts @@ -17,6 +17,7 @@ import {getTag, DELIM_END, DELIM_START, DELIM_ASSIGN, ATTRIB_NAME, ATTRIB_VALUE, import {getRawEnterActionAtPosition} from 'vs/editor/common/modes/supports/onEnter'; import {TextModelWithTokens} from 'vs/editor/common/model/textModelWithTokens'; import {TextModel} from 'vs/editor/common/model/textModel'; +import {Range} from 'vs/editor/common/core/range'; suite('Colorizing - HTML', () => { @@ -653,5 +654,51 @@ suite('Colorizing - HTML', () => { assertOnEnter(' ', 6, Modes.IndentAction.Indent); assertOnEnter(' ', 7, Modes.IndentAction.IndentOutdent); }); -}); + test('matchBracket', () => { + + function toString(brackets:EditorCommon.IEditorRange[]): string[] { + if (!brackets) { + return null; + } + brackets.sort(Range.compareRangesUsingStarts); + return brackets.map(b => b.toString()); + } + + function assertBracket(lines:string[], lineNumber:number, column:number, expected:EditorCommon.IEditorRange[]): void { + let model = new TextModelWithTokens([], TextModel.toRawText(lines.join('\n')), false, _mode); + // force tokenization + model.getLineContext(model.getLineCount()); + let actual = model.matchBracket({ + lineNumber: lineNumber, + column: column + }); + let actualStr = actual ? toString(actual.brackets) : null; + let expectedStr = toString(expected); + assert.deepEqual(actualStr, expectedStr, 'TEXT <<' + lines.join('\n') + '>>, POS: ' + lineNumber + ', ' + column); + } + + assertBracket(['

'], 1, 1, [new Range(1, 1, 1, 2), new Range(1, 3, 1, 4)]); + assertBracket(['

'], 1, 2, [new Range(1, 1, 1, 2), new Range(1, 3, 1, 4)]); + assertBracket(['

'], 1, 3, [new Range(1, 1, 1, 2), new Range(1, 3, 1, 4)]); + assertBracket(['

'], 1, 4, [new Range(1, 1, 1, 2), new Range(1, 3, 1, 4)]); + assertBracket(['

'], 1, 5, [new Range(1, 4, 1, 5), new Range(1, 7, 1, 8)]); + assertBracket(['

'], 1, 6, null); + assertBracket(['

'], 1, 7, [new Range(1, 4, 1, 5), new Range(1, 7, 1, 8)]); + assertBracket(['

'], 1, 8, [new Range(1, 4, 1, 5), new Range(1, 7, 1, 8)]); + + assertBracket(['a[aa[aa[aa[aa[aa[aa]aa]aa]aa]aa]aa]a