提交 8e7bbd0f 编写于 作者: A Alex Dima

New logic for brackets in electricCharacter

上级 deb966b9
......@@ -1156,13 +1156,13 @@ export class OneCursorOp {
}
if (electricAction) {
var matchBracketType = electricAction.matchBracketType;
let matchOpenBracket = electricAction.matchOpenBracket;
var appendText = electricAction.appendText;
if (matchBracketType) {
var match:EditorCommon.IEditorRange = null;
if (matchBracketType) {
match = cursor.model.findMatchingBracketUp(matchBracketType, position);
}
if (matchOpenBracket) {
var match = cursor.model.findMatchingBracketUp(matchOpenBracket, {
lineNumber: position.lineNumber,
column: position.column - matchOpenBracket.close.length
});
if (match) {
var matchLineNumber = match.startLineNumber;
var matchLine = cursor.model.getLineContent(matchLineNumber);
......
......@@ -1376,6 +1376,12 @@ export interface ITextModel {
isDisposed(): boolean;
}
export interface IFindBracketRequest {
modeId: string;
open: string;
close: string;
}
export interface IFoundBracket {
range: IEditorRange;
open: string;
......@@ -1475,12 +1481,12 @@ export interface ITokenizedModel extends ITextModel {
tokenIterator(position: IPosition, callback: (it: ITokenIterator) =>any): any;
/**
* Find the matching bracket of `tokenType` up, counting brackets.
* @param tokenType The token type of the bracket we're searching for
* Find the matching bracket of `request` up, counting brackets.
* @param request The bracket we're searching for
* @param position The position at which to start the search.
* @return The range of the matching bracket, or null if the bracket match was not found.
*/
findMatchingBracketUp(tokenType:string, position:IPosition): IEditorRange;
findMatchingBracketUp(request:IFindBracketRequest, position:IPosition): IEditorRange;
/**
* Find the first bracket in the model before `position`.
......
......@@ -8,7 +8,7 @@ import nls = require('vs/nls');
import Timer = require('vs/base/common/timer');
import {NullMode, NullState, nullTokenize} from 'vs/editor/common/modes/nullMode';
import {WordHelper, BracketsHelper} from 'vs/editor/common/model/textModelWithTokensHelpers';
import {WordHelper} from 'vs/editor/common/model/textModelWithTokensHelpers';
import {TokenIterator} from 'vs/editor/common/model/tokenIterator';
import {ModelLine} from 'vs/editor/common/model/modelLine';
import {TextModel} from 'vs/editor/common/model/textModel';
......@@ -23,6 +23,7 @@ import {StopWatch} from 'vs/base/common/stopwatch';
import {TPromise} from 'vs/base/common/winjs.base';
import {Range} from 'vs/editor/common/core/range';
import * as Strings from 'vs/base/common/strings';
import {ignoreBracketsInToken} from 'vs/editor/common/modes/supports';
export class TokensInflatorMap implements EditorCommon.ITokensInflatorMap {
......@@ -846,12 +847,12 @@ export class TextModelWithTokens extends TextModel implements EditorCommon.IToke
return result;
}
public findMatchingBracketUp(tokenType:string, position:EditorCommon.IPosition): EditorCommon.IEditorRange {
public findMatchingBracketUp(request:EditorCommon.IFindBracketRequest, _position:EditorCommon.IPosition): EditorCommon.IEditorRange {
if (this._isDisposed) {
throw new Error('TextModelWithTokens.findMatchingBracketUp: Model is disposed');
}
return BracketsHelper.findMatchingBracketUp(this, tokenType, this.validatePosition(position));
return this._findMatchingBracketUp(request.open, request.close, this.validatePosition(_position));
}
public matchBracket(position:EditorCommon.IPosition, inaccurateResultAcceptable:boolean = false): EditorCommon.IMatchBracketResult {
......@@ -882,7 +883,7 @@ export class TextModelWithTokens extends TextModel implements EditorCommon.IToke
let prevTokenType = getType(tokensMap, tokens[prevTokenIndex]);
// check that previous token is not to be ignored
if (!TextModelWithTokens._ignoreBracketsInToken(prevTokenType)) {
if (!ignoreBracketsInToken(prevTokenType)) {
let prevTokenStart = getStartIndex(tokens[prevTokenIndex]);
// limit search in case previous token is very large, there's no need to go beyond `maxBracketLength`
......@@ -903,7 +904,7 @@ export class TextModelWithTokens extends TextModel implements EditorCommon.IToke
}
// check that the token is not to be ignored
if (!TextModelWithTokens._ignoreBracketsInToken(getType(tokensMap, tokens[currentTokenIndex]))) {
if (!ignoreBracketsInToken(getType(tokensMap, tokens[currentTokenIndex]))) {
// limit search to not go before `maxBracketLength`
currentTokenStart = Math.max(currentTokenStart, position.column - 1 - maxBracketLength);
......@@ -1027,7 +1028,7 @@ export class TextModelWithTokens extends TextModel implements EditorCommon.IToke
let currentTokenType = getType(tokensMap, currentToken);
let currentTokenStart = getStartIndex(currentToken);
if (!TextModelWithTokens._ignoreBracketsInToken(currentTokenType)) {
if (!ignoreBracketsInToken(currentTokenType)) {
while (true) {
let r = TextModelWithTokens._findPrevBracketInToken(reversedBracketRegex, lineNumber, lineText, currentTokenStart, currentTokenEnd);
......@@ -1083,7 +1084,7 @@ export class TextModelWithTokens extends TextModel implements EditorCommon.IToke
let currentTokenEnd = tokenIndex + 1 < tokensLength ? getStartIndex(tokens[tokenIndex + 1]) : lineText.length;
if (!TextModelWithTokens._ignoreBracketsInToken(currentTokenType)) {
if (!ignoreBracketsInToken(currentTokenType)) {
while (true) {
let r = TextModelWithTokens._findNextBracketInToken(bracketRegex, lineNumber, lineText, currentTokenStart, currentTokenEnd);
if (!r) {
......@@ -1113,11 +1114,6 @@ export class TextModelWithTokens extends TextModel implements EditorCommon.IToke
return null;
}
private static _ignoreBracketsInToken(tokenType:string): boolean {
return /\b(comment|string|regex)\b/.test(tokenType);
}
private static _findPrevBracketInText(reversedBracketRegex:RegExp, lineNumber:number, reversedText:string, offset:number): Range {
let m = reversedText.match(reversedBracketRegex);
......@@ -1168,7 +1164,7 @@ export class TextModelWithTokens extends TextModel implements EditorCommon.IToke
let currentTokenType = getType(tokensMap, currentToken);
let currentTokenStart = getStartIndex(currentToken);
if (!TextModelWithTokens._ignoreBracketsInToken(currentTokenType)) {
if (!ignoreBracketsInToken(currentTokenType)) {
let r = TextModelWithTokens._findPrevBracketInToken(reversedBracketRegex, lineNumber, lineText, currentTokenStart, currentTokenEnd);
if (r) {
return this._toFoundBracket(r);
......@@ -1228,7 +1224,7 @@ 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 (!TextModelWithTokens._ignoreBracketsInToken(currentTokenType)) {
if (!ignoreBracketsInToken(currentTokenType)) {
let r = TextModelWithTokens._findNextBracketInToken(bracketRegex, lineNumber, lineText, currentTokenStart, currentTokenEnd);
if (r) {
return this._toFoundBracket(r);
......
......@@ -222,60 +222,3 @@ export class WordHelper {
return null;
}
}
export class BracketsHelper {
private static _sign(n:number): number {
return n < 0 ? -1 : n > 0 ? 1 : 0;
}
private static _findMatchingBracketUp(textSource:ITextSource, type:string, lineNumber:number, tokenIndex:number, initialCount:number): Range {
let mode = textSource.getMode();
let count = initialCount;
for (let i = lineNumber; i >= 1; i--) {
let lineTokens = textSource.getLineTokens(i, false);
let tokens = lineTokens.getBinaryEncodedTokens();
let tokensMap = lineTokens.getBinaryEncodedTokensMap();
let lineText = textSource.getLineContent(i);
for (let j = (i === lineNumber ? tokenIndex : tokens.length) - 1; j >= 0; j--) {
if (getType(tokensMap, tokens[j]) === type) {
let start = getStartIndex(tokens[j]);
let end = (j === tokens.length - 1 ? lineText.length : getStartIndex(tokens[j + 1]));
count += BracketsHelper._sign(getBracketFor(type, lineText.substring(start, end), mode));
if (count === 0) {
return new Range(i, start + 1, i, end + 1);
}
}
}
}
return null;
}
public static findMatchingBracketUp(textSource:ITextSource, tokenType:string, position:EditorCommon.IPosition): EditorCommon.IEditorRange {
var i:number,
len:number,
end:number,
columnIndex = position.column - 1,
tokenIndex = -1,
lineTokens = textSource.getLineTokens(position.lineNumber, false),
tokens = lineTokens.getBinaryEncodedTokens(),
lineText = textSource.getLineContent(position.lineNumber);
for (i = 0, len = tokens.length; tokenIndex === -1 && i < len; i++) {
end = i === len - 1 ? lineText.length : getStartIndex(tokens[i + 1]);
if (getStartIndex(tokens[i]) <= columnIndex && columnIndex <= end) {
tokenIndex = i;
}
}
// Start looking one token after the bracket
return BracketsHelper._findMatchingBracketUp(textSource, tokenType, position.lineNumber, tokenIndex + 1, 0);
}
}
\ No newline at end of file
......@@ -697,7 +697,7 @@ export interface IElectricAction {
// The line will be indented at the same level of the line
// which contains the matching given bracket type.
matchBracketType?:string;
matchOpenBracket?:EditorCommon.IFindBracketRequest;
// The text will be appended after the electric character.
appendText?:string;
......
......@@ -148,6 +148,10 @@ export class FilteredLineContext implements Modes.ILineContext {
}
}
export function ignoreBracketsInToken(tokenType:string): boolean {
return /\b(comment|string|regex)\b/.test(tokenType);
}
// TODO@Alex -> refactor to use `brackets` from language configuration
export function getBracketFor(tokenType:string, tokenText:string, mode:Modes.IMode): Modes.Bracket {
if (tokenText === '{' || tokenText === '(' || tokenText === '[') {
......
......@@ -5,8 +5,11 @@
'use strict';
import * as Modes from 'vs/editor/common/modes';
import {handleEvent} from 'vs/editor/common/modes/supports';
import {handleEvent, ignoreBracketsInToken} from 'vs/editor/common/modes/supports';
import Strings = require('vs/base/common/strings');
import {Range} from 'vs/editor/common/core/range';
import {IFoundBracket} from 'vs/editor/common/editorCommon';
import {Arrays} from 'vs/editor/common/core/arrays';
/**
* Definition of documentation comments (e.g. Javadoc/JSdoc)
......@@ -34,7 +37,7 @@ export class BracketElectricCharacterSupport implements Modes.IRichEditElectricC
constructor(modeId: string, contribution: IBracketElectricCharacterContribution) {
this._modeId = modeId;
this.contribution = contribution;
this.brackets = new Brackets(contribution.brackets, contribution.docComment, contribution.caseInsensitive);
this.brackets = new Brackets(modeId, contribution.brackets, contribution.docComment, contribution.caseInsensitive);
}
public getElectricCharacters(): string[]{
......@@ -61,6 +64,7 @@ enum Lettercase { Unknown, Lowercase, Uppercase, Camelcase}
export class Brackets {
private _modeId: string;
private brackets: Modes.IBracketPair[];
private docComment: IDocComment;
private caseInsensitive: boolean;
......@@ -74,8 +78,8 @@ export class Brackets {
* - stringIsBracket
*
*/
constructor(brackets: Modes.IBracketPair[], docComment: IDocComment = null,
caseInsensitive: boolean = false) {
constructor(modeId: string, brackets: Modes.IBracketPair[], docComment: IDocComment = null, caseInsensitive: boolean = false) {
this._modeId = modeId;
this.brackets = brackets;
this.docComment = docComment ? docComment : null;
this.caseInsensitive = caseInsensitive ? caseInsensitive : false;
......@@ -152,29 +156,82 @@ export class Brackets {
return true;
}
private _onElectricCharacterStandardBrackets(context: Modes.ILineContext, offset: number): Modes.IElectricAction {
var tokenIndex = context.findIndexOfOffset(offset);
var tokenText = context.getTokenText(tokenIndex);
var tokenType = context.getTokenType(tokenIndex);
if (!this.stringIsBracket(tokenText)) {
// This is not a brace type that we are aware of.
// Keep in mind that tokenType above might be different than what this.tokenTypeFromString(tokenText)
// returns, which could happen when using TextMate bundles.
// TODO: dup in textModelWithTokens
private static _findPrevBracketInText(reversedBracketRegex:RegExp, lineNumber:number, reversedText:string, offset:number): Range {
let m = reversedText.match(reversedBracketRegex);
if (!m) {
return null;
}
if (tokenIndex >= 0 && context.getTokenEndIndex(tokenIndex)-1 > offset) {
// We're in the middle of a token, do not do anything
let matchOffset = reversedText.length - 1 - m.index;
let matchLength = m[0].length;
let absoluteMatchOffset = offset + matchOffset;
return new Range(lineNumber, absoluteMatchOffset + 1, lineNumber, absoluteMatchOffset + 1 + matchLength);
}
// TODO: dup in textModelWithTokens
private static _findPrevBracketInToken(reversedBracketRegex:RegExp, lineNumber:number, lineText:string, currentTokenStart:number, currentTokenEnd:number): Range {
// Because JS does not support backwards regex search, we search forwards in a reversed string with a reversed regex ;)
let currentTokenReversedText = '';
for (let index = currentTokenEnd - 1; index >= currentTokenStart; index--) {
currentTokenReversedText += lineText.charAt(index);
}
return this._findPrevBracketInText(reversedBracketRegex, lineNumber, currentTokenReversedText, currentTokenStart);
}
// TODO: dup in textModelWithTokens
private static _toFoundBracket(r:Range, text:string): IFoundBracket {
if (!r) {
return null;
}
var firstNonWhitespaceIndex = Strings.firstNonWhitespaceIndex(context.getLineContent());
// TODO@Alex: use mode's brackets
switch (text) {
case '(': return { range: r, open: '(', close: ')', isOpen: true };
case ')': return { range: r, open: '(', close: ')', isOpen: false };
case '[': return { range: r, open: '[', close: ']', isOpen: true };
case ']': return { range: r, open: '[', close: ']', isOpen: false };
case '{': return { range: r, open: '{', close: '}', isOpen: true };
case '}': return { range: r, open: '{', close: '}', isOpen: false };
}
return null;
}
private _onElectricCharacterStandardBrackets(context: Modes.ILineContext, offset: number): Modes.IElectricAction {
let reversedBracketRegex = /[\(\)\[\]\{\}]/; // TODO@Alex: use mode's brackets
let lineText = context.getLineContent();
let tokenIndex = context.findIndexOfOffset(offset);
let tokenStart = context.getTokenStartIndex(tokenIndex);
let tokenEnd = offset + 1;
if (firstNonWhitespaceIndex !== -1 && firstNonWhitespaceIndex <= offset-tokenText.length) {
var firstNonWhitespaceIndex = Strings.firstNonWhitespaceIndex(context.getLineContent());
if (firstNonWhitespaceIndex !== -1 && firstNonWhitespaceIndex < tokenStart) {
return null;
}
return { matchBracketType: tokenType };
if (!ignoreBracketsInToken(context.getTokenType(tokenIndex))) {
let r = Brackets._findPrevBracketInToken(reversedBracketRegex, 1, lineText, tokenStart, tokenEnd);
if (r) {
let text = lineText.substring(r.startColumn - 1, r.endColumn - 1);
let data = Brackets._toFoundBracket(r, text);
if (!data.isOpen) {
return {
matchOpenBracket: {
modeId: this._modeId,
open: data.open,
close: data.close
}
}
}
}
}
return null;
}
private _onElectricCharacterDocComment(context: Modes.ILineContext, offset: number): Modes.IElectricAction {
......
......@@ -12,7 +12,7 @@ import modesUtil = require('vs/editor/test/common/modesTestUtils');
suite('Editor Modes - Auto Indentation', () => {
test('Bracket Pairs', () => {
var brackets = new autoIndentation.Brackets([
var brackets = new autoIndentation.Brackets('test', [
{ tokenType:'b', open: '{', close: '}', isElectric: false },
{ tokenType:'a', open: '[', close: ']', isElectric: true },
{ tokenType:'p', open: '(', close: ')', isElectric: false }
......@@ -29,7 +29,7 @@ suite('Editor Modes - Auto Indentation', () => {
});
test('Doc comments', () => {
var brackets = new autoIndentation.Brackets([],
var brackets = new autoIndentation.Brackets('test', [],
{ scope: 'doc', open: '/**', lineStart: ' * ', close: ' */' });
assert.equal(brackets.onElectricCharacter(modesUtil.createLineContextFromTokenText([
......
......@@ -25,18 +25,6 @@ export interface ITestItem {
tokens: IRelaxedToken[];
}
export interface IOnElectricCharacterFunc {
(line:string, offset:number, state?:modes.IState): modes.IElectricAction;
}
export function createOnElectricCharacter(mode:modes.IMode): IOnElectricCharacterFunc {
return function onElectricCharacter(line:string, offset:number, state?:modes.IState): modes.IElectricAction {
state = state || mode.tokenizationSupport.getInitialState();
var lineTokens = mode.tokenizationSupport.tokenize(line, state);
return mode.richEditSupport.electricCharacter.onElectricCharacter(createLineContext(line, lineTokens), offset);
};
}
export function assertWords(actual:string[], expected:string[], message?:string): void {
assert.deepEqual(actual, expected, message);
}
......
......@@ -4,23 +4,25 @@
*--------------------------------------------------------------------------------------------*/
'use strict';
import 'vs/languages/javascript/common/javascript.contribution';
import assert = require('assert');
import javascriptMode = require('vs/languages/javascript/common/javascript');
import EditorCommon = require('vs/editor/common/editorCommon');
import Modes = require('vs/editor/common/modes');
import modesUtil = require('vs/editor/test/common/modesUtil');
import {createLineContext} from 'vs/editor/test/common/modesTestUtils';
suite('JS - Auto Indent', () => {
var wordDefinition:RegExp;
var assertOnEnter: modesUtil.IOnEnterAsserter;
var onElectricCharacter: modesUtil.IOnElectricCharacterFunc;
var _mode: Modes.IMode;
var assertWords = modesUtil.assertWords;
suiteSetup((done) => {
modesUtil.load('javascript').then(mode => {
_mode = mode;
assertOnEnter = modesUtil.createOnEnterAsserter(mode.getId(), mode.richEditSupport);
onElectricCharacter = modesUtil.createOnElectricCharacter(mode);
wordDefinition = mode.richEditSupport.wordDefinition;
done();
});
......@@ -57,24 +59,37 @@ suite('JS - Auto Indent', () => {
});
test('onElectricCharacter', function() {
assert.equal(onElectricCharacter('var f = function() {}', 20), null);
assert.deepEqual(onElectricCharacter('}', 0), { matchBracketType: 'delimiter.bracket.js' });
assert.deepEqual(onElectricCharacter(' }', 3), { matchBracketType: 'delimiter.bracket.js' });
assert.deepEqual(onElectricCharacter(' }', 2), { matchBracketType: 'delimiter.bracket.js' });
assert.deepEqual(onElectricCharacter(' }; // stuff', 4), { matchBracketType: 'delimiter.bracket.js' });
assert.equal(onElectricCharacter('[1,2]', 4), null);
assert.deepEqual(onElectricCharacter(']', 0), { matchBracketType: 'delimiter.array.js' });
assert.deepEqual(onElectricCharacter(' ]', 3), { matchBracketType: 'delimiter.array.js' });
assert.deepEqual(onElectricCharacter(' ]', 2), { matchBracketType: 'delimiter.array.js' });
assert.deepEqual(onElectricCharacter(' ]; // stuff', 4), { matchBracketType: 'delimiter.array.js' });
assert.equal(onElectricCharacter('f()', 2), null);
assert.deepEqual(onElectricCharacter(')', 0), { matchBracketType: 'delimiter.parenthesis.js' });
assert.deepEqual(onElectricCharacter(' )', 3), { matchBracketType: 'delimiter.parenthesis.js' });
assert.deepEqual(onElectricCharacter(' )', 2), { matchBracketType: 'delimiter.parenthesis.js' });
assert.deepEqual(onElectricCharacter(' )', 4), { matchBracketType: 'delimiter.parenthesis.js' });
assert.deepEqual(onElectricCharacter(' ); // stuff', 4), { matchBracketType: 'delimiter.parenthesis.js' });
function testElectricCharacter(line:string, offset:number, expected:Modes.IElectricAction): void {
let state = _mode.tokenizationSupport.getInitialState();
var lineTokens = _mode.tokenizationSupport.tokenize(line, state);
let actual = _mode.richEditSupport.electricCharacter.onElectricCharacter(createLineContext(line, lineTokens), offset);
assert.deepEqual(actual, expected, 'LINE <<<' + line + '>>>, OFFSET: <<<' + offset + '>>>');
}
const CURLY = { matchOpenBracket: { modeId:'javascript', open:'{', close:'}' } };
const ROUND = { matchOpenBracket: { modeId:'javascript', open:'(', close:')' } };
const SQUARE = { matchOpenBracket: { modeId:'javascript', open:'[', close:']' } };
testElectricCharacter('var f = function() {}', 20, null);
testElectricCharacter('}', 0, CURLY);
testElectricCharacter(' }', 3, CURLY);
testElectricCharacter(' }', 2, CURLY);
testElectricCharacter(' }; // stuff', 4, CURLY);
testElectricCharacter('[1,2]', 4, null);
testElectricCharacter(']', 0, SQUARE);
testElectricCharacter(' ]', 3, SQUARE);
testElectricCharacter(' ]', 2, SQUARE);
testElectricCharacter(' ]; // stuff', 4, SQUARE);
testElectricCharacter('f()', 2, null);
testElectricCharacter(')', 0, ROUND);
testElectricCharacter(' )', 3, ROUND);
testElectricCharacter(' )', 2, ROUND);
testElectricCharacter(' )', 4, ROUND);
testElectricCharacter(' ); // stuff', 4, ROUND);
});
test('Word definition', function() {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册