diff --git a/extensions/powershell/powershell.configuration.json b/extensions/powershell/powershell.configuration.json index 37f197f29c7..bc00d00c064 100644 --- a/extensions/powershell/powershell.configuration.json +++ b/extensions/powershell/powershell.configuration.json @@ -9,16 +9,14 @@ ["(", ")"] ] - /* - enhancedBrackets: [ - { tokenType:'string', openTrigger: '"', open: /@"$/, closeComplete: '"@' }, - { tokenType:'string', openTrigger: '\'', open: /@'$/, closeComplete: '\'@' }, - { tokenType:'string', openTrigger: '"', open: /"$/, closeComplete: '"' }, - { tokenType: 'string', openTrigger: '\'', open: /'$/, closeComplete: '\'' } - ], + // enhancedBrackets: [ + // { tokenType:'string', openTrigger: '"', open: /@"$/, closeComplete: '"@' }, + // { tokenType:'string', openTrigger: '\'', open: /@'$/, closeComplete: '\'@' }, + // { tokenType:'string', openTrigger: '"', open: /"$/, closeComplete: '"' }, + // { tokenType: 'string', openTrigger: '\'', open: /'$/, closeComplete: '\'' } + // ], - autoClosingPairs: [['{', '}'], ['[', ']'], ['(', ')']], // Defined explicitly, to suppress the - // default auto-closing of ' and " which is - // override above by enhancedBrackets - */ + // autoClosingPairs: [['{', '}'], ['[', ']'], ['(', ')']], // Defined explicitly, to suppress the + // // default auto-closing of ' and " which is + // // override above by enhancedBrackets } \ No newline at end of file diff --git a/extensions/sql/sql.configuration.json b/extensions/sql/sql.configuration.json index 11e0fce1527..00efa8e538b 100644 --- a/extensions/sql/sql.configuration.json +++ b/extensions/sql/sql.configuration.json @@ -8,12 +8,11 @@ ["[", "]"], ["(", ")"] ] - /* - enhancedBrackets:[ - { openTrigger: 'n', open: /begin$/i, closeComplete: 'end', matchCase: true }, - { openTrigger: 'e', open: /case$/i, closeComplete: 'end', matchCase: true }, - { openTrigger: 'n', open: /when$/i, closeComplete: 'then', matchCase: true } - ], - noindentBrackets: '()', - */ + + // enhancedBrackets:[ + // { openTrigger: 'n', open: /begin$/i, closeComplete: 'end', matchCase: true }, + // { openTrigger: 'e', open: /case$/i, closeComplete: 'end', matchCase: true }, + // { openTrigger: 'n', open: /when$/i, closeComplete: 'then', matchCase: true } + // ], + // noindentBrackets: '()', } \ No newline at end of file diff --git a/extensions/xml/xml.configuration.json b/extensions/xml/xml.configuration.json index 18f470abd3c..db36a4d72be 100644 --- a/extensions/xml/xml.configuration.json +++ b/extensions/xml/xml.configuration.json @@ -6,16 +6,16 @@ "brackets": [ ["<", ">"] ] - /* - enhancedBrackets: [{ - tokenType: 'tag.tag-$1.xml', - openTrigger: '>', - open: /<(\w[\w\d]*)([^\/>]*(?!\/)>)[^<>]*$/i, - closeComplete: '', - closeTrigger: '>', - close: /<\/(\w[\w\d]*)\s*>$/i - }], - autoClosingPairs: [['\'', '\''], ['"', '"'] ] - */ + // enhancedBrackets: [{ + // tokenType: 'tag.tag-$1.xml', + // openTrigger: '>', + // open: /<(\w[\w\d]*)([^\/>]*(?!\/)>)[^<>]*$/i, + // closeComplete: '', + // closeTrigger: '>', + // close: /<\/(\w[\w\d]*)\s*>$/i + // }], + + // autoClosingPairs: [['\'', '\''], ['"', '"'] ] + } diff --git a/extensions/xml/xsl.configuration.json b/extensions/xml/xsl.configuration.json index 18f470abd3c..7605fc4a7d1 100644 --- a/extensions/xml/xsl.configuration.json +++ b/extensions/xml/xsl.configuration.json @@ -6,16 +6,15 @@ "brackets": [ ["<", ">"] ] - /* - enhancedBrackets: [{ - tokenType: 'tag.tag-$1.xml', - openTrigger: '>', - open: /<(\w[\w\d]*)([^\/>]*(?!\/)>)[^<>]*$/i, - closeComplete: '', - closeTrigger: '>', - close: /<\/(\w[\w\d]*)\s*>$/i - }], - autoClosingPairs: [['\'', '\''], ['"', '"'] ] - */ + // enhancedBrackets: [{ + // tokenType: 'tag.tag-$1.xml', + // openTrigger: '>', + // open: /<(\w[\w\d]*)([^\/>]*(?!\/)>)[^<>]*$/i, + // closeComplete: '', + // closeTrigger: '>', + // close: /<\/(\w[\w\d]*)\s*>$/i + // }], + + // autoClosingPairs: [['\'', '\''], ['"', '"'] ] } diff --git a/src/vs/base/common/strings.ts b/src/vs/base/common/strings.ts index be0f6868321..2f6cea2a53e 100644 --- a/src/vs/base/common/strings.ts +++ b/src/vs/base/common/strings.ts @@ -179,7 +179,7 @@ export function endsWith(haystack: string, needle: string): boolean { } } -export function createRegExp(searchString: string, isRegex: boolean, matchCase: boolean, wholeWord: boolean): RegExp { +export function createRegExp(searchString: string, isRegex: boolean, matchCase: boolean, wholeWord: boolean, global:boolean): RegExp { if (searchString === '') { throw new Error('Cannot create regex from empty string'); } @@ -194,7 +194,10 @@ export function createRegExp(searchString: string, isRegex: boolean, matchCase: searchString = searchString + '\\b'; } } - let modifiers = 'g'; + let modifiers = ''; + if (global) { + modifiers += 'g'; + } if (!matchCase) { modifiers += 'i'; } @@ -213,7 +216,7 @@ export function createSafeRegExp(searchString:string, isRegex:boolean, matchCase // Try to create a RegExp out of the params var regex:RegExp = null; try { - regex = createRegExp(searchString, isRegex, matchCase, wholeWord); + regex = createRegExp(searchString, isRegex, matchCase, wholeWord, true); } catch (err) { return null; } diff --git a/src/vs/editor/common/controller/cursorCollection.ts b/src/vs/editor/common/controller/cursorCollection.ts index 7561a799d47..fe78aafd2d3 100644 --- a/src/vs/editor/common/controller/cursorCollection.ts +++ b/src/vs/editor/common/controller/cursorCollection.ts @@ -307,19 +307,21 @@ export class CursorCollection { } private getModeConfiguration(): IModeConfiguration { - var i: number; + let i: number; - var result: IModeConfiguration = { + let result: IModeConfiguration = { electricChars: {}, autoClosingPairsOpen: {}, autoClosingPairsClose: {}, surroundingPairs: {} }; - var electricChars: string[]; - if (this.model.getMode().electricCharacterSupport) { + let richEditSupport = this.model.getMode().richEditSupport; + + let electricChars: string[]; + if (richEditSupport && richEditSupport.electricCharacter) { try { - electricChars = this.model.getMode().electricCharacterSupport.getElectricCharacters(); + electricChars = richEditSupport.electricCharacter.getElectricCharacters(); } catch(e) { Errors.onUnexpectedError(e); electricChars = null; @@ -331,10 +333,10 @@ export class CursorCollection { } } - var autoClosingPairs: IAutoClosingPair[]; - if (this.model.getMode().characterPairSupport) { + let autoClosingPairs: IAutoClosingPair[]; + if (richEditSupport && richEditSupport.characterPair) { try { - autoClosingPairs = this.model.getMode().characterPairSupport.getAutoClosingPairs(); + autoClosingPairs = richEditSupport.characterPair.getAutoClosingPairs(); } catch(e) { Errors.onUnexpectedError(e); autoClosingPairs = null; @@ -347,10 +349,10 @@ export class CursorCollection { } } - var surroundingPairs: IAutoClosingPair[]; - if (this.model.getMode().characterPairSupport) { + let surroundingPairs: IAutoClosingPair[]; + if (richEditSupport && richEditSupport.characterPair) { try { - surroundingPairs = this.model.getMode().characterPairSupport.getSurroundingPairs(); + surroundingPairs = richEditSupport.characterPair.getSurroundingPairs(); } catch(e) { Errors.onUnexpectedError(e); surroundingPairs = null; diff --git a/src/vs/editor/common/controller/oneCursor.ts b/src/vs/editor/common/controller/oneCursor.ts index 44f0e4eb3c3..7e933bd0d17 100644 --- a/src/vs/editor/common/controller/oneCursor.ts +++ b/src/vs/editor/common/controller/oneCursor.ts @@ -1035,7 +1035,9 @@ export class OneCursorOp { return false; } - if(!cursor.model.getMode().characterPairSupport) { + let richEditSupport = cursor.model.getMode().richEditSupport; + + if(!richEditSupport || !richEditSupport.characterPair) { return false; } @@ -1061,7 +1063,7 @@ export class OneCursorOp { var shouldAutoClosePair = false; try { - shouldAutoClosePair = cursor.model.getMode().characterPairSupport.shouldAutoClosePair(ch, lineContext, position.column - 1); + shouldAutoClosePair = richEditSupport.characterPair.shouldAutoClosePair(ch, lineContext, position.column - 1); } catch(e) { Errors.onUnexpectedError(e); } @@ -1144,22 +1146,23 @@ export class OneCursorOp { var lineContext = cursor.model.getLineContext(position.lineNumber); var electricAction:IElectricAction; - if(cursor.model.getMode().electricCharacterSupport) { + let richEditSupport = cursor.model.getMode().richEditSupport; + if(richEditSupport && richEditSupport.electricCharacter) { try { - electricAction = cursor.model.getMode().electricCharacterSupport.onElectricCharacter(lineContext, position.column - 2); + electricAction = richEditSupport.electricCharacter.onElectricCharacter(lineContext, position.column - 2); } catch(e) { Errors.onUnexpectedError(e); } } 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.length + }); if (match) { var matchLineNumber = match.startLineNumber; var matchLine = cursor.model.getLineContent(matchLineNumber); diff --git a/src/vs/editor/common/editorCommon.ts b/src/vs/editor/common/editorCommon.ts index 0fd266f8d6e..e83b1513327 100644 --- a/src/vs/editor/common/editorCommon.ts +++ b/src/vs/editor/common/editorCommon.ts @@ -757,13 +757,9 @@ export interface IModeSupportChangedEvent { emitOutputSupport:boolean; linkSupport:boolean; configSupport:boolean; - electricCharacterSupport:boolean; - commentsSupport:boolean; - characterPairSupport:boolean; - tokenTypeClassificationSupport:boolean; quickFixSupport: boolean; codeLensSupport: boolean; - onEnterSupport: boolean; + richEditSupport: boolean; } /** @@ -1178,16 +1174,13 @@ export interface ITokensInflatorMap { export interface ILineTokensBinaryEncoding { START_INDEX_MASK: number; TYPE_MASK: number; - BRACKET_MASK: number; START_INDEX_OFFSET: number; TYPE_OFFSET: number; - BRACKET_OFFSET: number; deflateArr(map:ITokensInflatorMap, tokens:Modes.IToken[]): number[]; inflate(map:ITokensInflatorMap, binaryEncodedToken:number): Modes.IToken; getStartIndex(binaryEncodedToken:number): number; getType(map:ITokensInflatorMap, binaryEncodedToken:number): string; - getBracket(binaryEncodedToken:number): Modes.Bracket; inflateArr(map:ITokensInflatorMap, binaryEncodedTokens:number[]): Modes.IToken[]; findIndexOfOffset(binaryEncodedTokens:number[], offset:number): number; sliceAndInflate(map:ITokensInflatorMap, binaryEncodedTokens:number[], startOffset:number, endOffset:number, deltaStartIndex:number): Modes.IToken[]; @@ -1211,7 +1204,6 @@ export interface ILineTokens { getTokenCount(): number; getTokenStartIndex(tokenIndex:number): number; getTokenType(tokenIndex:number): string; - getTokenBracket(tokenIndex:number): Modes.Bracket; getTokenEndIndex(tokenIndex:number, textLength:number): number; /** @@ -1391,6 +1383,21 @@ export interface ITextModel { isDisposed(): boolean; } +export interface IRichEditBracket { + modeId: string; + open: string; + close: string; + forwardRegex: RegExp; + reversedRegex: RegExp; +} + +export interface IFoundBracket { + range: IEditorRange; + open: string; + close: string; + isOpen: boolean; +} + /** * A model that is tokenized. */ @@ -1483,12 +1490,26 @@ 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(bracket:string, position:IPosition): IEditorRange; + + /** + * Find the first bracket in the model before `position`. + * @param position The position at which to start the search. + * @return The info for the first bracket before `position`, or null if there are no more brackets before `positions`. + */ + findPrevBracket(position:IPosition): IFoundBracket; + + /** + * Find the first bracket in the model after `position`. + * @param position The position at which to start the search. + * @return The info for the first bracket after `position`, or null if there are no more brackets after `positions`. + */ + findNextBracket(position:IPosition): IFoundBracket; /** * Given a `position`, if the position is on top or near a bracket, diff --git a/src/vs/editor/common/model/modelLine.ts b/src/vs/editor/common/model/modelLine.ts index b118df9239a..1892ab87957 100644 --- a/src/vs/editor/common/model/modelLine.ts +++ b/src/vs/editor/common/model/modelLine.ts @@ -171,9 +171,8 @@ export class ModelLine { if (currentTokenStartIndex > 0 && delta !== 0) { // adjust token's `startIndex` by `delta` let deflatedType = (tokens[tokensIndex] / BIN.TYPE_OFFSET) & BIN.TYPE_MASK; - let deflatedBracket = (tokens[tokensIndex] / BIN.BRACKET_OFFSET) & BIN.BRACKET_MASK; let newStartIndex = Math.max(minimumAllowedIndex, currentTokenStartIndex + delta); - let newToken = deflatedBracket * BIN.BRACKET_OFFSET + deflatedType * BIN.TYPE_OFFSET + newStartIndex * BIN.START_INDEX_OFFSET; + let newToken = deflatedType * BIN.TYPE_OFFSET + newStartIndex * BIN.START_INDEX_OFFSET; if (delta < 0) { // pop all previous tokens that have become `collapsed` @@ -439,9 +438,8 @@ export class ModelLine { let deflatedStartIndex = (token / BIN.START_INDEX_OFFSET) & BIN.START_INDEX_MASK; let deflatedType = (token / BIN.TYPE_OFFSET) & BIN.TYPE_MASK; - let deflatedBracket = (token / BIN.BRACKET_OFFSET) & BIN.BRACKET_MASK; let newStartIndex = deflatedStartIndex + thisTextLength; - let newToken = deflatedBracket * BIN.BRACKET_OFFSET + deflatedType * BIN.TYPE_OFFSET + newStartIndex * BIN.START_INDEX_OFFSET; + let newToken = deflatedType * BIN.TYPE_OFFSET + newStartIndex * BIN.START_INDEX_OFFSET; otherTokens[i] = newToken; } @@ -618,7 +616,7 @@ function toLineTokens(map:EditorCommon.ITokensInflatorMap, tokens:Modes.IToken[] return null; } } else { - if (tokens[0].startIndex === 0 && tokens[0].type === '' && !tokens[0].bracket) { + if (tokens[0].startIndex === 0 && tokens[0].type === '') { return null; } } @@ -628,7 +626,6 @@ function toLineTokens(map:EditorCommon.ITokensInflatorMap, tokens:Modes.IToken[] var getStartIndex = EditorCommon.LineTokensBinaryEncoding.getStartIndex; var getType = EditorCommon.LineTokensBinaryEncoding.getType; -var getBracket = EditorCommon.LineTokensBinaryEncoding.getBracket; var findIndexOfOffset = EditorCommon.LineTokensBinaryEncoding.findIndexOfOffset; export class LineTokens implements EditorCommon.ILineTokens { @@ -669,10 +666,6 @@ export class LineTokens implements EditorCommon.ILineTokens { return getType(this.map, this._tokens[tokenIndex]); } - public getTokenBracket(tokenIndex:number): Modes.Bracket { - return getBracket(this._tokens[tokenIndex]); - } - public getTokenEndIndex(tokenIndex:number, textLength:number): number { if (tokenIndex + 1 < this._tokens.length) { return getStartIndex(this._tokens[tokenIndex + 1]); @@ -714,10 +707,6 @@ class EmptyLineTokens implements EditorCommon.ILineTokens { return Strings.empty; } - public getTokenBracket(tokenIndex:number): Modes.Bracket { - return 0; - } - public getTokenEndIndex(tokenIndex:number, textLength:number): number { return 0; } @@ -756,10 +745,6 @@ export class DefaultLineTokens implements EditorCommon.ILineTokens { return Strings.empty; } - public getTokenBracket(tokenIndex:number): Modes.Bracket { - return 0; - } - public getTokenEndIndex(tokenIndex:number, textLength:number): number { return textLength; } diff --git a/src/vs/editor/common/model/textModelWithTokens.ts b/src/vs/editor/common/model/textModelWithTokens.ts index 68bbe2e023f..cdaf6fffa15 100644 --- a/src/vs/editor/common/model/textModelWithTokens.ts +++ b/src/vs/editor/common/model/textModelWithTokens.ts @@ -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'; @@ -21,6 +21,10 @@ import Errors = require('vs/base/common/errors'); import {IDisposable, disposeAll} from 'vs/base/common/lifecycle'; 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'; +import {BracketsUtils} from 'vs/editor/common/modes/supports/electricCharacter'; export class TokensInflatorMap implements EditorCommon.ITokensInflatorMap { @@ -166,10 +170,6 @@ class LineContext implements Modes.ILineContext { return this._lineTokens.getTokenType(tokenIndex); } - public getTokenBracket(tokenIndex:number): Modes.Bracket { - return this._lineTokens.getTokenBracket(tokenIndex); - } - public getTokenText(tokenIndex:number): string { var startIndex = this._lineTokens.getTokenStartIndex(tokenIndex); var endIndex = this._lineTokens.getTokenEndIndex(tokenIndex, this._text.length); @@ -848,12 +848,28 @@ export class TextModelWithTokens extends TextModel implements EditorCommon.IToke return result; } - public findMatchingBracketUp(tokenType:string, position:EditorCommon.IPosition): EditorCommon.IEditorRange { + public findMatchingBracketUp(bracket:string, _position:EditorCommon.IPosition): EditorCommon.IEditorRange { if (this._isDisposed) { throw new Error('TextModelWithTokens.findMatchingBracketUp: Model is disposed'); } - return BracketsHelper.findMatchingBracketUp(this, tokenType, this.validatePosition(position)); + let position = this.validatePosition(_position); + let modeTransitions = this._lines[position.lineNumber - 1].getModeTransitions().toArray(this._mode); + let currentModeIndex = Arrays.findIndexInSegmentsArray(modeTransitions, position.column - 1); + let currentMode = modeTransitions[currentModeIndex]; + let currentModeBrackets = currentMode.mode.richEditSupport ? currentMode.mode.richEditSupport.brackets : null; + + if (!currentModeBrackets) { + return null; + } + + let data = currentModeBrackets.textIsBracket[bracket]; + + if (!data) { + return null; + } + + return this._findMatchingBracketUp(data, position); } public matchBracket(position:EditorCommon.IPosition, inaccurateResultAcceptable:boolean = false): EditorCommon.IMatchBracketResult { @@ -861,6 +877,362 @@ export class TextModelWithTokens extends TextModel implements EditorCommon.IToke throw new Error('TextModelWithTokens.matchBracket: Model is disposed'); } - return BracketsHelper.matchBracket(this, this.validatePosition(position), inaccurateResultAcceptable); + return this._matchBracket(this.validatePosition(position)); + } + + private _matchBracket(position:EditorCommon.IEditorPosition): EditorCommon.IMatchBracketResult { + let tokensMap = this._tokensInflatorMap; + let lineNumber = position.lineNumber; + let lineText = this._lines[lineNumber - 1].text; + + let lineTokens = this._lines[lineNumber - 1].getTokens(); + let tokens = lineTokens.getBinaryEncodedTokens(); + let currentTokenIndex = lineTokens.findIndexOfOffset(position.column - 1); + let currentTokenStart = getStartIndex(tokens[currentTokenIndex]); + + let modeTransitions = this._lines[lineNumber - 1].getModeTransitions().toArray(this._mode); + let currentModeIndex = Arrays.findIndexInSegmentsArray(modeTransitions, position.column - 1); + let currentMode = modeTransitions[currentModeIndex]; + let currentModeBrackets = currentMode.mode.richEditSupport ? currentMode.mode.richEditSupport.brackets : null; + + // If position is in between two tokens, try first looking in the previous token + if (currentTokenIndex > 0 && currentTokenStart === position.column - 1) { + let prevTokenIndex = currentTokenIndex - 1; + let prevTokenType = getType(tokensMap, tokens[prevTokenIndex]); + + // check that previous token is not to be ignored + if (!ignoreBracketsInToken(prevTokenType)) { + let prevTokenStart = getStartIndex(tokens[prevTokenIndex]); + + let prevMode = currentMode; + let prevModeBrackets = currentModeBrackets; + // check if previous token is in a different mode + if (currentModeIndex > 0 && currentMode.startIndex === position.column - 1) { + prevMode = modeTransitions[currentModeIndex - 1]; + prevModeBrackets = prevMode.mode.richEditSupport ? prevMode.mode.richEditSupport.brackets : null; + } + + if (prevModeBrackets) { + // limit search in case previous token is very large, there's no need to go beyond `maxBracketLength` + prevTokenStart = Math.max(prevTokenStart, position.column - 1 - prevModeBrackets.maxBracketLength); + + let foundBracket = BracketsUtils.findPrevBracketInToken(prevModeBrackets.reversedRegex, lineNumber, lineText, prevTokenStart, currentTokenStart); + + // check that we didn't hit a bracket too far away from position + if (foundBracket && foundBracket.startColumn <= position.column && position.column <= foundBracket.endColumn) { + let foundBracketText = lineText.substring(foundBracket.startColumn - 1, foundBracket.endColumn - 1); + let r = this._matchFoundBracket(foundBracket, prevModeBrackets.textIsBracket[foundBracketText], prevModeBrackets.textIsOpenBracket[foundBracketText]); + + // check that we can actually match this bracket + if (r) { + return r; + } + } + } + } + } + + // check that the token is not to be ignored + if (!ignoreBracketsInToken(getType(tokensMap, tokens[currentTokenIndex]))) { + + if (currentModeBrackets) { + // limit search to not go before `maxBracketLength` + currentTokenStart = Math.max(currentTokenStart, position.column - 1 - currentModeBrackets.maxBracketLength); + + // limit search to not go after `maxBracketLength` + let currentTokenEnd = (currentTokenIndex + 1 < tokens.length ? getStartIndex(tokens[currentTokenIndex + 1]) : lineText.length); + currentTokenEnd = Math.min(currentTokenEnd, position.column - 1 + currentModeBrackets.maxBracketLength); + + // it might still be the case that [currentTokenStart -> currentTokenEnd] contains multiple brackets + while(true) { + let foundBracket = BracketsUtils.findNextBracketInText(currentModeBrackets.forwardRegex, lineNumber, lineText.substring(currentTokenStart, currentTokenEnd), currentTokenStart); + if (!foundBracket) { + // there are no brackets in this text + break; + } + + // check that we didn't hit a bracket too far away from position + if (foundBracket.startColumn <= position.column && position.column <= foundBracket.endColumn) { + let foundBracketText = lineText.substring(foundBracket.startColumn - 1, foundBracket.endColumn - 1); + let r = this._matchFoundBracket(foundBracket, currentModeBrackets.textIsBracket[foundBracketText], currentModeBrackets.textIsOpenBracket[foundBracketText]); + + // check that we can actually match this bracket + if (r) { + return r; + } + } + + currentTokenStart = foundBracket.endColumn - 1; + } + } + } + + return { + brackets: null, + isAccurate: true + }; + } + + private _matchFoundBracket(foundBracket:Range, data:EditorCommon.IRichEditBracket, isOpen:boolean): EditorCommon.IMatchBracketResult { + if (isOpen) { + let matched = this._findMatchingBracketDown(data, foundBracket.getEndPosition()); + if (matched) { + return { + brackets: [foundBracket, matched], + isAccurate: true + }; + } + } else { + let matched = this._findMatchingBracketUp(data, foundBracket.getStartPosition()); + if (matched) { + return { + brackets: [foundBracket, matched], + isAccurate: true + }; + } + } + + return null; + } + + 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; + let reversedBracketRegex = bracket.reversedRegex; + let count = -1; + + for (let lineNumber = position.lineNumber; lineNumber >= 1; lineNumber--) { + 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--) { + let currentToken = tokens[tokenIndex]; + let currentTokenType = getType(tokensMap, currentToken); + let currentTokenStart = getStartIndex(currentToken); + + 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); + if (!r) { + break; + } + + let hitText = lineText.substring(r.startColumn - 1, r.endColumn - 1); + + if (hitText === bracket.open) { + count++; + } else if (hitText === bracket.close) { + count--; + } + + if (count === 0) { + return r; + } + + currentTokenEnd = r.startColumn - 1; + } + } + + currentTokenEnd = currentTokenStart; + } + } + + return null; + } + + 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; + let bracketRegex = bracket.forwardRegex; + let count = 1; + + for (let lineNumber = position.lineNumber, lineCount = this.getLineCount(); lineNumber <= lineCount; lineNumber++) { + 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 + 1); + 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 + 1); + currentModeId = modeTransitions[currentModeIndex].mode.getId(); + } + + for (let tokenIndex = startTokenIndex, tokensLength = tokens.length; tokenIndex < tokensLength; tokenIndex++) { + let currentToken = tokens[tokenIndex]; + 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 + 1); + currentModeId = modeTransitions[currentModeIndex].mode.getId(); + } + + if (currentModeId === modeId && !ignoreBracketsInToken(currentTokenType)) { + while (true) { + let r = BracketsUtils.findNextBracketInToken(bracketRegex, lineNumber, lineText, currentTokenStart, currentTokenEnd); + if (!r) { + break; + } + + let hitText = lineText.substring(r.startColumn - 1, r.endColumn - 1); + + if (hitText === bracket.open) { + count++; + } else if (hitText === bracket.close) { + count--; + } + + if (count === 0) { + return r; + } + + currentTokenStart = r.endColumn - 1; + } + } + + currentTokenStart = currentTokenEnd; + } + } + + return null; + } + + public findPrevBracket(_position:EditorCommon.IPosition): EditorCommon.IFoundBracket { + if (this._isDisposed) { + throw new Error('TextModelWithTokens.findPrevBracket: Model is disposed'); + } + let position = this.validatePosition(_position); + + let tokensMap = this._tokensInflatorMap; + let reversedBracketRegex = /[\(\)\[\]\{\}]/; // TODO@Alex: use mode's brackets + + for (let lineNumber = position.lineNumber; lineNumber >= 1; lineNumber--) { + let lineTokens = this._lines[lineNumber - 1].getTokens(); + let lineText = this._lines[lineNumber - 1].text; + let tokens = lineTokens.getBinaryEncodedTokens(); + + let tokensLength = tokens.length - 1; + let currentTokenEnd = lineText.length; + if (lineNumber === position.lineNumber) { + tokensLength = lineTokens.findIndexOfOffset(position.column - 1); + currentTokenEnd = position.column - 1; + } + + for (let tokenIndex = tokensLength; tokenIndex >= 0; tokenIndex--) { + let currentToken = tokens[tokenIndex]; + let currentTokenType = getType(tokensMap, currentToken); + let currentTokenStart = getStartIndex(currentToken); + + if (!ignoreBracketsInToken(currentTokenType)) { + let r = BracketsUtils.findPrevBracketInToken(reversedBracketRegex, lineNumber, lineText, currentTokenStart, currentTokenEnd); + if (r) { + return this._toFoundBracket(r); + } + } + + currentTokenEnd = currentTokenStart; + } + } + + return null; + } + + public findNextBracket(_position:EditorCommon.IPosition): EditorCommon.IFoundBracket { + if (this._isDisposed) { + throw new Error('TextModelWithTokens.findNextBracket: Model is disposed'); + } + let position = this.validatePosition(_position); + + let tokensMap = this._tokensInflatorMap; + let bracketRegex = /[\(\)\[\]\{\}]/; // TODO@Alex: use mode's brackets + + for (let lineNumber = position.lineNumber, lineCount = this.getLineCount(); lineNumber <= lineCount; lineNumber++) { + let lineTokens = this._lines[lineNumber - 1].getTokens(); + let lineText = this._lines[lineNumber - 1].text; + let tokens = lineTokens.getBinaryEncodedTokens(); + + let startTokenIndex = 0; + let currentTokenStart = getStartIndex(startTokenIndex); + if (lineNumber === position.lineNumber) { + startTokenIndex = lineTokens.findIndexOfOffset(position.column - 1); + currentTokenStart = Math.max(currentTokenStart, position.column - 1); + } + + for (let tokenIndex = startTokenIndex, tokensLength = tokens.length; tokenIndex < tokensLength; tokenIndex++) { + let currentToken = tokens[tokenIndex]; + let currentTokenType = getType(tokensMap, currentToken); + let currentTokenEnd = tokenIndex + 1 < tokensLength ? getStartIndex(tokens[tokenIndex + 1]) : lineText.length; + + if (!ignoreBracketsInToken(currentTokenType)) { + let r = BracketsUtils.findNextBracketInToken(bracketRegex, lineNumber, lineText, currentTokenStart, currentTokenEnd); + if (r) { + return this._toFoundBracket(r); + } + } + + currentTokenStart = currentTokenEnd; + } + } + + return null; + } + + private _toFoundBracket(r:Range): EditorCommon.IFoundBracket { + if (!r) { + return null; + } + + let text = this.getValueInRange(r); + + // 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; } } + +var getType = EditorCommon.LineTokensBinaryEncoding.getType; +var getStartIndex = EditorCommon.LineTokensBinaryEncoding.getStartIndex; diff --git a/src/vs/editor/common/model/textModelWithTokensHelpers.ts b/src/vs/editor/common/model/textModelWithTokensHelpers.ts index 0cbb7aed3ad..e83d06470a6 100644 --- a/src/vs/editor/common/model/textModelWithTokensHelpers.ts +++ b/src/vs/editor/common/model/textModelWithTokensHelpers.ts @@ -29,7 +29,6 @@ export interface ITextSource { } var getType = EditorCommon.LineTokensBinaryEncoding.getType; -var getBracket = EditorCommon.LineTokensBinaryEncoding.getBracket; var getStartIndex = EditorCommon.LineTokensBinaryEncoding.getStartIndex; export interface INonWordTokenMap { @@ -39,17 +38,7 @@ export interface INonWordTokenMap { export class WordHelper { private static _safeGetWordDefinition(mode:Modes.IMode): RegExp { - var result: RegExp = null; - - if (mode.tokenTypeClassificationSupport) { - try { - result = mode.tokenTypeClassificationSupport.getWordDefinition(); - } catch(e) { - Errors.onUnexpectedError(e); - } - } - - return result; + return (mode.richEditSupport ? mode.richEditSupport.wordDefinition : null); } public static ensureValidWordDefinition(wordDefinition?:RegExp): RegExp { @@ -232,159 +221,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 { - - var i:number, - end:number, - j:number, - count = initialCount; - - for (i = lineNumber; i >= 1; i--) { - - var lineTokens = textSource.getLineTokens(i, false), - tokens = lineTokens.getBinaryEncodedTokens(), - tokensMap = lineTokens.getBinaryEncodedTokensMap(), - lineText = textSource.getLineContent(i); - - for (j = (i === lineNumber ? tokenIndex : tokens.length) - 1; j >= 0; j--) { - if (getType(tokensMap, tokens[j]) === type) { - count += BracketsHelper._sign(getBracket(tokens[j])); - if (count === 0) { - end = (j === tokens.length - 1 ? lineText.length : getStartIndex(tokens[j + 1])); - return new Range(i, getStartIndex(tokens[j]) + 1, i, end + 1); - } - } - } - } - return null; - } - - private static _findMatchingBracketDown(textSource:ITextSource, type:string, lineNumber:number, tokenIndex:number, inaccurateResultAcceptable:boolean): { range:Range; isAccurate:boolean; } { - - var i:number, - len:number, - end:number, - j:number, - lenJ:number, - count = 1; - - for (i = lineNumber, len = textSource.getLineCount(); i <= len; i++) { - if (inaccurateResultAcceptable && !textSource._lineIsTokenized(i)) { - return { - range: null, - isAccurate: false - }; - } - - var lineTokens = textSource.getLineTokens(i, false), - tokens = lineTokens.getBinaryEncodedTokens(), - tokensMap = lineTokens.getBinaryEncodedTokensMap(), - lineText = textSource.getLineContent(i); - - for (j = (i === lineNumber ? tokenIndex + 1 : 0), lenJ = tokens.length; j < lenJ; j++) { - if (getType(tokensMap, tokens[j]) === type) { - count += BracketsHelper._sign(getBracket(tokens[j])); - if (count === 0) { - end = (j === tokens.length - 1 ? lineText.length : getStartIndex(tokens[j + 1])); - return { - range: new Range(i, getStartIndex(tokens[j]) + 1, i, end + 1), - isAccurate: true - }; - } - } - } - } - - return { - range: null, - isAccurate: true - }; - } - - 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); - } - - public static matchBracket(textSource:ITextSource, position:EditorCommon.IPosition, inaccurateResultAcceptable:boolean): EditorCommon.IMatchBracketResult { - if (inaccurateResultAcceptable && !textSource._lineIsTokenized(position.lineNumber)) { - return { - brackets: null, - isAccurate: false - }; - } - - var lineText = textSource.getLineContent(position.lineNumber), - i:number, - len:number; - - var result:EditorCommon.IMatchBracketResult = { - brackets: null, - isAccurate: true - }; - - if (lineText.length > 0) { - var columnIndex = position.column - 1; - var token:number; - var end:number; - - var lineTokens = textSource.getLineTokens(position.lineNumber, false), - tokens = lineTokens.getBinaryEncodedTokens(), - tokensMap = lineTokens.getBinaryEncodedTokensMap(), - tokenStartIndex:number, - tokenBracket:number, - tokenType:string; - - for (i = 0, len = tokens.length; result.brackets === null && i < len; i++) { - token = tokens[i]; - tokenStartIndex = getStartIndex(token); - tokenType = getType(tokensMap, token); - tokenBracket = getBracket(token); - - end = i === len - 1 ? lineText.length : getStartIndex(tokens[i + 1]); - - if (tokenStartIndex <= columnIndex && columnIndex <= end) { - if (tokenBracket < 0) { - var upMatch = BracketsHelper._findMatchingBracketUp(textSource, tokenType, position.lineNumber, i, -1); - if (upMatch) { - result.brackets = [new Range(position.lineNumber, tokenStartIndex + 1, position.lineNumber, end + 1), upMatch]; - } - } - if (result.brackets === null && tokenBracket > 0) { - var downMatch = BracketsHelper._findMatchingBracketDown(textSource, tokenType, position.lineNumber, i, inaccurateResultAcceptable); - result.isAccurate = downMatch.isAccurate; - if (downMatch.range) { - result.brackets = [new Range(position.lineNumber, tokenStartIndex + 1, position.lineNumber, end + 1), downMatch.range]; - } - } - } - } - } - return result; - } -} \ No newline at end of file diff --git a/src/vs/editor/common/model/tokensBinaryEncoding.ts b/src/vs/editor/common/model/tokensBinaryEncoding.ts index 3a6e06718d3..9589754ba55 100644 --- a/src/vs/editor/common/model/tokensBinaryEncoding.ts +++ b/src/vs/editor/common/model/tokensBinaryEncoding.ts @@ -12,30 +12,25 @@ import Errors = require('vs/base/common/errors'); class InflatedToken implements Modes.IToken { startIndex:number; type:string; - bracket:Modes.Bracket; - constructor(startIndex:number, type:string, bracket:Modes.Bracket) { + constructor(startIndex:number, type:string) { this.startIndex = startIndex; this.type = type; - this.bracket = bracket; } public toString(): string { - return '{ ' + this.startIndex + ', \'' + this.type + '\', ' + this.bracket + '}'; + return '{ ' + this.startIndex + ', \'' + this.type + '\'}'; } } export var START_INDEX_MASK = 0xffffffff; export var TYPE_MASK = 0xffff; -export var BRACKET_MASK = 0xff; export var START_INDEX_OFFSET = 1; export var TYPE_OFFSET = Math.pow(2, 32); -export var BRACKET_OFFSET = Math.pow(2, 48); var DEFAULT_TOKEN = { startIndex: 0, - type: '', - bracket: 0 + type: '' }; var INFLATED_TOKENS_EMPTY_TEXT = []; var DEFLATED_TOKENS_EMPTY_TEXT = []; @@ -46,14 +41,13 @@ export function deflateArr(map:EditorCommon.ITokensInflatorMap, tokens:Modes.ITo if (tokens.length === 0) { return DEFLATED_TOKENS_EMPTY_TEXT; } - if (tokens.length === 1 && tokens[0].startIndex === 0 && !tokens[0].type && !tokens[0].bracket) { + if (tokens.length === 1 && tokens[0].startIndex === 0 && !tokens[0].type) { return DEFLATED_TOKENS_NON_EMPTY_TEXT; } var i:number, len:number, deflatedToken:number, - deflatedBracket:number, deflated:number, token:Modes.IToken, inflateMap = map._inflate, @@ -80,11 +74,6 @@ export function deflateArr(map:EditorCommon.ITokensInflatorMap, tokens:Modes.ITo inflateMap.push(token.type); } - deflatedBracket = token.bracket; - if (deflatedBracket < 0) { - deflatedBracket = 2; - } - // http://stackoverflow.com/a/2803010 // All numbers in JavaScript are actually IEEE-754 compliant floating-point doubles. // These have a 53-bit mantissa which should mean that any integer value with a magnitude @@ -96,10 +85,9 @@ export function deflateArr(map:EditorCommon.ITokensInflatorMap, tokens:Modes.ITo // 32 bits for startIndex => up to 2^32 = 4,294,967,296 // 16 bits for token => up to 2^16 = 65,536 - // 2 bits for bracket => up to 2^2 = 4 - // [bracket][token][startIndex] - deflated = deflatedBracket * BRACKET_OFFSET + deflatedToken * TYPE_OFFSET + token.startIndex * START_INDEX_OFFSET; + // [token][startIndex] + deflated = deflatedToken * TYPE_OFFSET + token.startIndex * START_INDEX_OFFSET; result[i] = deflated; @@ -116,13 +104,8 @@ export function inflate(map:EditorCommon.ITokensInflatorMap, binaryEncodedToken: var startIndex = (binaryEncodedToken / START_INDEX_OFFSET) & START_INDEX_MASK; var deflatedType = (binaryEncodedToken / TYPE_OFFSET) & TYPE_MASK; - var deflatedBracket = (binaryEncodedToken / BRACKET_OFFSET) & BRACKET_MASK; - if (deflatedBracket === 2) { - deflatedBracket = -1; - } - - return new InflatedToken(startIndex, map._inflate[deflatedType], deflatedBracket); + return new InflatedToken(startIndex, map._inflate[deflatedType]); } export function getStartIndex(binaryEncodedToken:number): number { @@ -137,16 +120,6 @@ export function getType(map:EditorCommon.ITokensInflatorMap, binaryEncodedToken: return map._inflate[deflatedType]; } -export function getBracket(binaryEncodedToken:number): Modes.Bracket { - var deflatedBracket = (binaryEncodedToken / BRACKET_OFFSET) & BRACKET_MASK; - - if (deflatedBracket === 2) { - deflatedBracket = -1; - } - - return deflatedBracket; -} - export function inflateArr(map:EditorCommon.ITokensInflatorMap, binaryEncodedTokens:number[]): Modes.IToken[] { if (binaryEncodedTokens.length === 0) { return INFLATED_TOKENS_EMPTY_TEXT; @@ -160,7 +133,6 @@ export function inflateArr(map:EditorCommon.ITokensInflatorMap, binaryEncodedTok len:number, deflated:number, startIndex:number, - deflatedBracket:number, deflatedType:number, inflateMap = map._inflate; @@ -169,13 +141,8 @@ export function inflateArr(map:EditorCommon.ITokensInflatorMap, binaryEncodedTok startIndex = (deflated / START_INDEX_OFFSET) & START_INDEX_MASK; deflatedType = (deflated / TYPE_OFFSET) & TYPE_MASK; - deflatedBracket = (deflated / BRACKET_OFFSET) & BRACKET_MASK; - if (deflatedBracket === 2) { - deflatedBracket = -1; - } - - result[i] = new InflatedToken(startIndex, inflateMap[deflatedType], deflatedBracket); + result[i] = new InflatedToken(startIndex, inflateMap[deflatedType]); } return result; @@ -200,15 +167,13 @@ export function sliceAndInflate(map:EditorCommon.ITokensInflatorMap, binaryEncod originalStartIndex:number, newStartIndex:number, deflatedType:number, - deflatedBracket:number, result: Modes.IToken[] = [], inflateMap = map._inflate; originalToken = binaryEncodedTokens[startIndex]; deflatedType = (originalToken / TYPE_OFFSET) & TYPE_MASK; - deflatedBracket = (originalToken / BRACKET_OFFSET) & BRACKET_MASK; newStartIndex = 0; - result.push(new InflatedToken(newStartIndex, inflateMap[deflatedType], deflatedBracket)); + result.push(new InflatedToken(newStartIndex, inflateMap[deflatedType])); for (i = startIndex + 1, len = binaryEncodedTokens.length; i < len; i++) { originalToken = binaryEncodedTokens[i]; @@ -219,9 +184,8 @@ export function sliceAndInflate(map:EditorCommon.ITokensInflatorMap, binaryEncod } deflatedType = (originalToken / TYPE_OFFSET) & TYPE_MASK; - deflatedBracket = (originalToken / BRACKET_OFFSET) & BRACKET_MASK; newStartIndex = originalStartIndex - startOffset + deltaStartIndex; - result.push(new InflatedToken(newStartIndex, inflateMap[deflatedType], deflatedBracket)); + result.push(new InflatedToken(newStartIndex, inflateMap[deflatedType])); } return result; diff --git a/src/vs/editor/common/modes.ts b/src/vs/editor/common/modes.ts index 7a7125a8eee..f1887d8653b 100644 --- a/src/vs/editor/common/modes.ts +++ b/src/vs/editor/common/modes.ts @@ -176,7 +176,6 @@ export interface ILineContext { getTokenCount(): number; getTokenStartIndex(tokenIndex:number): number; getTokenType(tokenIndex:number): string; - getTokenBracket(tokenIndex:number): Bracket; getTokenText(tokenIndex:number): string; getTokenEndIndex(tokenIndex:number): number; findIndexOfOffset(offset:number): number; @@ -283,26 +282,6 @@ export interface IMode { */ configSupport?:IConfigurationSupport; - /** - * Optional adapter to support electric characters. - */ - electricCharacterSupport?:IElectricCharacterSupport; - - /** - * Optional adapter to support comment insertion. - */ - commentsSupport?:ICommentsSupport; - - /** - * Optional adapter to support insertion of character pair. - */ - characterPairSupport?:ICharacterPairSupport; - - /** - * Optional adapter to support classification of tokens. - */ - tokenTypeClassificationSupport?:ITokenTypeClassificationSupport; - /** * Optional adapter to support quick fix of typing errors. */ @@ -323,7 +302,10 @@ export interface IMode { */ taskSupport?: ITaskSupport; - onEnterSupport?: IOnEnterSupport; + /** + * Optional adapter to support rich editing. + */ + richEditSupport?: IRichEditSupport; } /** @@ -332,7 +314,7 @@ export interface IMode { export interface IToken { startIndex:number; type:string; - bracket:Bracket; + bracket?:Bracket; } export interface IModeTransition { @@ -456,12 +438,6 @@ export interface IParameterHints { signatures:ISignature[]; } -export interface IParameterHintsContribution { - triggerCharacters: string[]; - excludeTokens: string[]; - getParameterHints: (resource: URI, position: EditorCommon.IPosition) => TPromise; -} - /** * Interface used to get parameter hints. */ @@ -645,87 +621,6 @@ export interface IConfigurationSupport { configure(options:any):TPromise; } - - - -/** - * Interface used to support electric characters - */ -export interface IElectricAction { - // Only one of the following properties should be defined: - - // The line will be indented at the same level of the line - // which contains the matching given bracket type. - matchBracketType?:string; - - // The text will be appended after the electric character. - appendText?:string; - - // The number of characters to advance the cursor, useful with appendText - advanceCount?:number; -} - -export enum IndentAction { - None, - Indent, - IndentOutdent, - Outdent -} - -/** - * An action the editor executes when 'enter' is being pressed - */ -export interface IEnterAction { - indentAction:IndentAction; - appendText?:string; - removeText?:number; -} - -export interface IElectricCharacterSupport { - getElectricCharacters():string[]; - // Should return opening bracket type to match indentation with - onElectricCharacter(context:ILineContext, offset:number):IElectricAction; - onEnter(context:ILineContext, offset:number):IEnterAction; -} - -export interface IOnEnterSupport { - onEnter(model:EditorCommon.ITokenizedModel, position: EditorCommon.IPosition): IEnterAction; -} - -/** - * Interface used to support insertion of mode specific comments. - */ -export interface ICommentsConfiguration { - lineCommentTokens?:string[]; - blockCommentStartToken?:string; - blockCommentEndToken?:string; -} -export interface ICommentsSupport { - getCommentsConfiguration():ICommentsConfiguration; -} - - - -/** - * Interface used to support insertion of matching characters like brackets and qoutes. - */ -export interface IAutoClosingPair { - open:string; - close:string; -} -export interface ICharacterPairSupport { - getAutoClosingPairs():IAutoClosingPairConditional[]; - shouldAutoClosePair(character:string, context:ILineContext, offset:number):boolean; - getSurroundingPairs():IAutoClosingPair[]; -} - -/** - * Interface used to support the classification of tokens. - */ -export interface ITokenTypeClassificationSupport { - getWordDefinition():RegExp; -} - export interface IResourceEdit { resource: URI; range?: EditorCommon.IRange; @@ -790,41 +685,112 @@ export interface IBracketPair { isElectric:boolean; } -/** - * Regular expression based brackets. These are always electric. - */ -export interface IRegexBracketPair { - openTrigger?: string; // The character that will trigger the evaluation of 'open'. - open: RegExp; // The definition of when an opening brace is detected. This regex is matched against the entire line upto, and including the last typed character (the trigger character). - closeComplete?: string; // How to complete a matching open brace. Matches from 'open' will be expanded, e.g. '' - matchCase?: boolean; // If set to true, the case of the string captured in 'open' will be detected an applied also to 'closeComplete'. - // This is useful for cases like BEGIN/END or begin/end where the opening and closing phrases are unrelated. - // For identical phrases, use the $1 replacement syntax above directly in closeComplete, as it will - // include the proper casing from the captured string in 'open'. - // Upper/Lower/Camel cases are detected. Camel case dection uses only the first two characters and assumes - // that 'closeComplete' contains wors separated by spaces (e.g. 'End Loop') - - closeTrigger?: string; // The character that will trigger the evaluation of 'close'. - close?: RegExp; // The definition of when a closing brace is detected. This regex is matched against the entire line upto, and including the last typed character (the trigger character). - tokenType?: string; // The type of the token. Matches from 'open' or 'close' will be expanded, e.g. 'keyword.$1'. - // Only used to auto-(un)indent a closing bracket. -} - -/** - * Definition of documentation comments (e.g. Javadoc/JSdoc) - */ -export interface IDocComment { - scope: string; // What tokens should be used to detect a doc comment (e.g. 'comment.documentation'). - open: string; // The string that starts a doc comment (e.g. '/**') - lineStart: string; // The string that appears at the start of each line, except the first and last (e.g. ' * '). - close?: string; // The string that appears on the last line and closes the doc comment (e.g. ' */'). -} - export interface IAutoClosingPairConditional extends IAutoClosingPair { notIn?: string[]; } -export interface ICharacterPairContribution { - autoClosingPairs: IAutoClosingPairConditional[]; - surroundingPairs?: IAutoClosingPair[]; -} \ No newline at end of file +/** + * Interface used to support electric characters + */ +export interface IElectricAction { + // Only one of the following properties should be defined: + + // The line will be indented at the same level of the line + // which contains the matching given bracket type. + matchOpenBracket?:string; + + // The text will be appended after the electric character. + appendText?:string; + + // The number of characters to advance the cursor, useful with appendText + advanceCount?:number; +} + +export enum IndentAction { + None, + Indent, + IndentOutdent, + Outdent +} + +/** + * An action the editor executes when 'enter' is being pressed + */ +export interface IEnterAction { + indentAction:IndentAction; + appendText?:string; + removeText?:number; +} + +export interface IRichEditElectricCharacter { + getElectricCharacters():string[]; + // Should return opening bracket type to match indentation with + onElectricCharacter(context:ILineContext, offset:number):IElectricAction; +} + +export interface IRichEditOnEnter { + onEnter(model:EditorCommon.ITokenizedModel, position: EditorCommon.IPosition): IEnterAction; +} + +/** + * Interface used to support insertion of mode specific comments. + */ +export interface ICommentsConfiguration { + lineCommentToken?:string; + blockCommentStartToken?:string; + blockCommentEndToken?:string; +} + +/** + * Interface used to support insertion of matching characters like brackets and qoutes. + */ +export interface IAutoClosingPair { + open:string; + close:string; +} +export interface IRichEditCharacterPair { + getAutoClosingPairs():IAutoClosingPairConditional[]; + shouldAutoClosePair(character:string, context:ILineContext, offset:number):boolean; + getSurroundingPairs():IAutoClosingPair[]; +} + +export interface IRichEditBrackets { + maxBracketLength: number; + forwardRegex: RegExp; + reversedRegex: RegExp; + brackets: EditorCommon.IRichEditBracket[]; + textIsBracket: {[text:string]:EditorCommon.IRichEditBracket;}; + textIsOpenBracket: {[text:string]:boolean;}; +} + +export interface IRichEditSupport { + /** + * Optional adapter for electric characters. + */ + electricCharacter?:IRichEditElectricCharacter; + + /** + * Optional adapter for comment insertion. + */ + comments?:ICommentsConfiguration; + + /** + * Optional adapter for insertion of character pair. + */ + characterPair?:IRichEditCharacterPair; + + /** + * Optional adapter for classification of tokens. + */ + wordDefinition?: RegExp; + + /** + * Optional adapter for custom Enter handling. + */ + onEnter?: IRichEditOnEnter; + + /** + * Optional adapter for brackets. + */ + brackets?: IRichEditBrackets; +} diff --git a/src/vs/editor/common/modes/abstractMode.ts b/src/vs/editor/common/modes/abstractMode.ts index eb2fa8c27af..fb4775b86e1 100644 --- a/src/vs/editor/common/modes/abstractMode.ts +++ b/src/vs/editor/common/modes/abstractMode.ts @@ -41,8 +41,6 @@ export class AbstractMode implements Modes.IMode { public dirtyDiffSupport:Modes.IDirtyDiffSupport; public linkSupport:Modes.ILinkSupport; public configSupport:Modes.IConfigurationSupport; - public commentsSupport:Modes.ICommentsSupport; - public tokenTypeClassificationSupport:Modes.ITokenTypeClassificationSupport; public codeLensSupport:Modes.ICodeLensSupport; // adapters end @@ -68,8 +66,6 @@ export class AbstractMode implements Modes.IMode { this.dirtyDiffSupport = this; this.linkSupport = this; this.configSupport = this; - this.commentsSupport = this; - this.tokenTypeClassificationSupport = this; this._workerPiecePromise = null; this._simplifiedMode = null; @@ -228,24 +224,12 @@ export class AbstractMode implements Modes.IMode { } // END - - public getWordDefinition():RegExp { - return NullMode.DEFAULT_WORD_REGEXP; - } - - public getCommentsConfiguration():Modes.ICommentsConfiguration { - return null; - } } class SimplifiedMode implements Modes.IMode { tokenizationSupport: Modes.ITokenizationSupport; - electricCharacterSupport: Modes.IElectricCharacterSupport; - commentsSupport: Modes.ICommentsSupport; - characterPairSupport: Modes.ICharacterPairSupport; - tokenTypeClassificationSupport: Modes.ITokenTypeClassificationSupport; - onEnterSupport: Modes.IOnEnterSupport; + richEditSupport: Modes.IRichEditSupport; private _sourceMode: Modes.IMode; private _eventEmitter: EventEmitter; @@ -259,7 +243,7 @@ class SimplifiedMode implements Modes.IMode { if (this._sourceMode.addSupportChangedListener) { this._sourceMode.addSupportChangedListener((e) => { - if (e.tokenizationSupport || e.electricCharacterSupport || e.commentsSupport || e.characterPairSupport || e.tokenTypeClassificationSupport || e.onEnterSupport) { + if (e.tokenizationSupport || e.richEditSupport) { this._assignSupports(); let newEvent = SimplifiedMode._createModeSupportChangedEvent(e); this._eventEmitter.emit('modeSupportChanged', newEvent); @@ -278,11 +262,7 @@ class SimplifiedMode implements Modes.IMode { private _assignSupports(): void { this.tokenizationSupport = this._sourceMode.tokenizationSupport; - this.electricCharacterSupport = this._sourceMode.electricCharacterSupport; - this.commentsSupport = this._sourceMode.commentsSupport; - this.characterPairSupport = this._sourceMode.characterPairSupport; - this.tokenTypeClassificationSupport = this._sourceMode.tokenTypeClassificationSupport; - this.onEnterSupport = this._sourceMode.onEnterSupport; + this.richEditSupport = this._sourceMode.richEditSupport; } private static _createModeSupportChangedEvent(originalModeEvent:EditorCommon.IModeSupportChangedEvent): EditorCommon.IModeSupportChangedEvent { @@ -306,12 +286,8 @@ class SimplifiedMode implements Modes.IMode { emitOutputSupport:false, linkSupport:false, configSupport:false, - electricCharacterSupport: originalModeEvent.electricCharacterSupport, - commentsSupport: originalModeEvent.commentsSupport, - characterPairSupport: originalModeEvent.characterPairSupport, - tokenTypeClassificationSupport: originalModeEvent.tokenTypeClassificationSupport, quickFixSupport:false, - onEnterSupport: originalModeEvent.onEnterSupport + richEditSupport: originalModeEvent.richEditSupport, }; return event; } @@ -392,7 +368,7 @@ export class FrankensteinMode extends AbstractMode { } function _createModeSupportChangedEvent(...changedSupports: string[]): EditorCommon.IModeSupportChangedEvent { - var event = { + var event:EditorCommon.IModeSupportChangedEvent = { codeLensSupport: false, tokenizationSupport:false, occurrencesSupport:false, @@ -412,12 +388,8 @@ function _createModeSupportChangedEvent(...changedSupports: string[]): EditorCom emitOutputSupport:false, linkSupport:false, configSupport:false, - electricCharacterSupport:false, - commentsSupport:false, - characterPairSupport:false, - tokenTypeClassificationSupport:false, quickFixSupport:false, - onEnterSupport: false + richEditSupport: false }; changedSupports.forEach(support => event[support] = true); return event; diff --git a/src/vs/editor/common/modes/abstractModeWorker.ts b/src/vs/editor/common/modes/abstractModeWorker.ts index 2f62bd668d8..7d86ad6f291 100644 --- a/src/vs/editor/common/modes/abstractModeWorker.ts +++ b/src/vs/editor/common/modes/abstractModeWorker.ts @@ -11,11 +11,11 @@ import {computeLinks} from 'vs/editor/common/modes/linkComputer'; import {DiffComputer} from 'vs/editor/common/diff/diffComputer'; import {DefaultFilter} from 'vs/editor/common/modes/modesFilters'; import {TextModel} from 'vs/editor/common/model/textModel'; -import {WorkerInplaceReplaceSupport} from 'vs/editor/common/modes/supports'; import {ValidationHelper} from 'vs/editor/common/worker/validationHelper'; import EditorCommon = require('vs/editor/common/editorCommon'); import Modes = require('vs/editor/common/modes'); import {TPromise} from 'vs/base/common/winjs.base'; +import {WorkerInplaceReplaceSupport} from 'vs/editor/common/modes/supports/inplaceReplaceSupport'; export class AbstractModeWorker { diff --git a/src/vs/editor/common/modes/autoIndentation.ts b/src/vs/editor/common/modes/autoIndentation.ts deleted file mode 100644 index ad0ac92c626..00000000000 --- a/src/vs/editor/common/modes/autoIndentation.ts +++ /dev/null @@ -1,291 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -'use strict'; - -import Strings = require('vs/base/common/strings'); -import Modes = require('vs/editor/common/modes'); - -enum Lettercase { Unknown, Lowercase, Uppercase, Camelcase} - -export class Brackets { - - private brackets: Modes.IBracketPair[]; - private regexBrackets: Modes.IRegexBracketPair[]; - private docComment: Modes.IDocComment; - private caseInsensitive: boolean; - - /** - * In case of case insensitive brackets, these assumptions must be met: - * - all standard brackets are passed as lowercase - * - the passed regular expressions already contain the /i flag - * - * Brackets defined in 'regexBrackets' are not used in the following methods: - * - stringIsBracket - * - */ - constructor(brackets: Modes.IBracketPair[], regexBrackets: Modes.IRegexBracketPair[] = [], docComment: Modes.IDocComment = null, - caseInsensitive: boolean = false) { - this.brackets = brackets; - this.regexBrackets = regexBrackets ? regexBrackets : []; - this.docComment = docComment ? docComment : null; - this.caseInsensitive = caseInsensitive ? caseInsensitive : false; - } - - public getElectricCharacters():string[] { - var result: string[] = []; - - // Plain brackets - var bracketPair: Modes.IBracketPair; - var length = this.brackets.length; - for (var i = 0; i < length; i++) { - bracketPair = this.brackets[i]; - if (bracketPair.isElectric) { - var lastChar = bracketPair.close.charAt(bracketPair.close.length - 1); - result.push(this.caseInsensitive ? lastChar.toLowerCase() : lastChar); - } - } - - // Regexp brackets (always electric) - var regexBracketPair: Modes.IRegexBracketPair; - length = this.regexBrackets.length; - for (var i = 0; i < length; i++) { - regexBracketPair = this.regexBrackets[i]; - if (regexBracketPair.openTrigger) { - result.push( this.caseInsensitive ? regexBracketPair.openTrigger.toLowerCase() : regexBracketPair.openTrigger); - } - if (regexBracketPair.closeTrigger) { - result.push( this.caseInsensitive ? regexBracketPair.closeTrigger.toLowerCase() : regexBracketPair.closeTrigger); - } - } - - // Doc comments - if (this.docComment){ - result.push(this.docComment.open.charAt(this.docComment.open.length - 1)); - } - - // Add uppercase if needed - if (this.caseInsensitive) - { - var oldLength = result.length; - for (var i = 0; i < oldLength; ++i) { - result.push(result[i].toUpperCase()); - } - } - - // Filter duplicate entries - result = result.filter((item, pos, array) => { - return array.indexOf(item) === pos; - }); - - return result; - } - - public onEnter(context: Modes.ILineContext, offset: number): Modes.IEnterAction { - if (context.getTokenCount() === 0) { - return null; - } - - return this._onEnterRegexBrackets(context, offset); - } - - public onElectricCharacter(context: Modes.ILineContext, offset: number): Modes.IElectricAction { - if (context.getTokenCount() === 0) { - return null; - } - - return (this._onElectricCharacterDocComment(context, offset) || - this._onElectricCharacterRegexBrackets(context, offset) || - this._onElectricCharacterStandardBrackets(context, offset)); - } - - public stringIsBracket(text: string): boolean { - var caseCorrectString = text; - if (this.caseInsensitive) { - caseCorrectString = text.toLowerCase(); - } - - var bracketPair: Modes.IBracketPair; - for (var i = 0; i < this.brackets.length; i++) { - bracketPair = this.brackets[i]; - if (caseCorrectString === bracketPair.open || caseCorrectString === bracketPair.close) { - return true; - } - } - return false; - } - - private containsTokenTypes(fullTokenSpec: string, tokensToLookFor: string): boolean { - var array = tokensToLookFor.split('.'); - for (var i = 0; i < array.length; ++i) { - if (fullTokenSpec.indexOf(array[i]) < 0) { - return false; - } - } - return true; - } - - private _onEnterRegexBrackets(context: Modes.ILineContext, offset: number): Modes.IEnterAction { - // Handle regular expression brackets - for (var i = 0; i < this.regexBrackets.length; ++i) { - var regexBracket = this.regexBrackets[i]; - var line = context.getLineContent(); - - if (this.caseInsensitive) { - line = line.toLowerCase(); // Even with the /../i regexes we need this for the indexof below - } - - // Check if an open bracket matches the line up to offset. - var matchLine = line.substr(0, offset); - var matches = matchLine.match(regexBracket.open); - - if (matches) { - - // The opening bracket matches. Check the closing one. - if (regexBracket.closeComplete) { - matchLine = line.substring(offset); - var matchAfter = matches[0].replace(regexBracket.open, regexBracket.closeComplete); - if (matchLine.indexOf(matchAfter) === 0) { - return { indentAction: Modes.IndentAction.IndentOutdent }; - } - } - - return { indentAction: Modes.IndentAction.Indent }; - } - } - - return null; - } - - 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. - return null; - } - - if (tokenIndex >= 0 && context.getTokenEndIndex(tokenIndex)-1 > offset) { - // We're in the middle of a token, do not do anything - return null; - } - - var firstNonWhitespaceIndex = Strings.firstNonWhitespaceIndex(context.getLineContent()); - - if (firstNonWhitespaceIndex !== -1 && firstNonWhitespaceIndex <= offset-tokenText.length) { - return null; - } - - return { matchBracketType: tokenType }; - } - - private _onElectricCharacterRegexBrackets(context: Modes.ILineContext, offset: number): Modes.IElectricAction { - // Handle regular expression brackets - var line = context.getLineContent(); - for (var i = 0; i < this.regexBrackets.length; ++i) { - var regexBracket = this.regexBrackets[i]; - - // Check if an open bracket matches the line up to offset. - if (regexBracket.openTrigger && regexBracket.closeComplete && - (line.charAt(offset) === regexBracket.openTrigger || - (this.caseInsensitive && line.charAt(offset).toLowerCase() === regexBracket.openTrigger.toLowerCase()))) { - - var matchLine = line.substr(0, offset+1); - var matches = matchLine.match(regexBracket.open); - if (matches) { - // Auto-complete with closing bracket. - var finalText = matches[0].replace(regexBracket.open, regexBracket.closeComplete); - if (regexBracket.matchCase) { - finalText = this._changeLettercase(finalText, this._detectLetercase(matches[0])); - } - return { appendText: finalText }; - } - } - - // Check if a close bracket matches the line up to offset. - if (regexBracket.closeTrigger && - (line.charAt(offset) === regexBracket.closeTrigger || - (this.caseInsensitive && line.charAt(offset).toLowerCase() === regexBracket.closeTrigger.toLowerCase()))) { - var matches = matchLine.match(regexBracket.close); - if (matches) { - // Auto-indent to the level of the opening bracket. - var properCaseMatch = matches[0]; - if (this.caseInsensitive) { - properCaseMatch = properCaseMatch.toLowerCase(); - } - return { matchBracketType: properCaseMatch.replace(regexBracket.close, regexBracket.tokenType)}; - } - } - } - return null; - } - - private _onElectricCharacterDocComment(context: Modes.ILineContext, offset: number): Modes.IElectricAction { - // We only auto-close, so do nothing if there is no closing part. - if (!this.docComment || !this.docComment.close) { - return null; - } - - var line = context.getLineContent(); - var char: string = line[offset]; - - // See if the right electric character was pressed - if (char !== this.docComment.open.charAt(this.docComment.open.length - 1)) { - return null; - } - - // If this line already contains the closing tag, do nothing. - if (line.indexOf(this.docComment.close, offset) >= 0) { - return null; - } - - // If we're not in a documentation comment, do nothing. - var lastTokenIndex = context.findIndexOfOffset(offset); - if (! this.containsTokenTypes(context.getTokenType(lastTokenIndex), this.docComment.scope)) { - return null; - } - - if (line.substring(context.getTokenStartIndex(lastTokenIndex), offset+1/* include electric char*/) !== this.docComment.open) { - return null; - } - - return { appendText: this.docComment.close}; - } - - private _detectLetercase(s: string): Lettercase { - if (s.toLowerCase() === s) { - return Lettercase.Lowercase; - } - if (s.toUpperCase() === s) { - return Lettercase.Uppercase; - } - if (s.length > 1) { - if (s.charAt(0).toUpperCase() === s.charAt(0) && s.charAt(1).toLowerCase() === s.charAt(1)) { - return Lettercase.Camelcase; - } - } - - return Lettercase.Unknown; - } - - private _changeLettercase(s: string, newCase: Lettercase): string { - switch (newCase) { - case Lettercase.Lowercase: - return s.toLowerCase(); - case Lettercase.Uppercase: - return s.toUpperCase(); - case Lettercase.Camelcase: - var words = s.toLowerCase().split(' '); - for (var i = 0; i < words.length; ++i) { - words[i] = words[i].charAt(0).toUpperCase() + words[i].substr(1); - } - return words.join(' '); - default: - return s; - } - } -} \ No newline at end of file diff --git a/src/vs/editor/common/modes/monarch/monarch.ts b/src/vs/editor/common/modes/monarch/monarch.ts index 8b1302c747c..e35c05f1c45 100644 --- a/src/vs/editor/common/modes/monarch/monarch.ts +++ b/src/vs/editor/common/modes/monarch/monarch.ts @@ -21,15 +21,16 @@ import {IInstantiationService} from 'vs/platform/instantiation/common/instantiat import {IThreadService} from 'vs/platform/thread/common/thread'; import {IModeService} from 'vs/editor/common/services/modeService'; import {IModelService} from 'vs/editor/common/services/modelService'; +import {RichEditSupport} from 'vs/editor/common/modes/supports/richEditSupport'; +import {ComposableSuggestSupport} from 'vs/editor/common/modes/supports/suggestSupport'; /** * The MonarchMode creates a Monaco language mode given a certain language description */ export class MonarchMode extends AbstractMode { + public tokenizationSupport: Modes.ITokenizationSupport; - public electricCharacterSupport: Modes.IElectricCharacterSupport; - public characterPairSupport: Modes.ICharacterPairSupport; - public onEnterSupport: Modes.IOnEnterSupport; + public richEditSupport: Modes.IRichEditSupport; constructor( descriptor:Modes.IModeDescriptor, @@ -42,11 +43,9 @@ export class MonarchMode extends AbstractMode { super(descriptor, instantiationService, threadService); this.tokenizationSupport = createTokenizationSupport(modeService, this, lexer); - this.electricCharacterSupport = new Supports.BracketElectricCharacterSupport(this, MonarchDefinition.createBracketElectricCharacterContribution(lexer)); - this.commentsSupport = new Supports.CommentsSupport(MonarchDefinition.createCommentsSupport(lexer)); - this.tokenTypeClassificationSupport = new Supports.TokenTypeClassificationSupport(MonarchDefinition.createTokenTypeClassificationSupportContribution(lexer)); - this.characterPairSupport = new Supports.CharacterPairSupport(this, MonarchDefinition.createCharacterPairContribution(lexer)); - this.suggestSupport = new Supports.ComposableSuggestSupport(this, MonarchDefinition.createSuggestSupport(modelService, this, lexer)); - this.onEnterSupport = new OnEnterSupport(this.getId(), MonarchDefinition.createOnEnterSupportOptions(lexer)); + + this.richEditSupport = new RichEditSupport(this.getId(), MonarchDefinition.createRichEditSupport(lexer)); + + this.suggestSupport = new ComposableSuggestSupport(this.getId(), MonarchDefinition.createSuggestSupport(modelService, this, lexer)); } } diff --git a/src/vs/editor/common/modes/monarch/monarchCommon.ts b/src/vs/editor/common/modes/monarch/monarchCommon.ts index 4646198004a..4513110ca3f 100644 --- a/src/vs/editor/common/modes/monarch/monarchCommon.ts +++ b/src/vs/editor/common/modes/monarch/monarchCommon.ts @@ -48,7 +48,7 @@ export interface ILexer extends ILexerMin { autoClosingPairs: Modes.IAutoClosingPairConditional[]; standardBrackets: Modes.IBracketPair[]; - enhancedBrackets: Modes.IRegexBracketPair[]; + // enhancedBrackets: Modes.IRegexBracketPair[]; outdentTriggers: string; } diff --git a/src/vs/editor/common/modes/monarch/monarchCompile.ts b/src/vs/editor/common/modes/monarch/monarchCompile.ts index 5493d414dbc..4d6e79a88de 100644 --- a/src/vs/editor/common/modes/monarch/monarchCompile.ts +++ b/src/vs/editor/common/modes/monarch/monarchCompile.ts @@ -646,46 +646,46 @@ export function compile(json: MonarchTypes.ILanguage): MonarchCommonTypes.ILexer } // Set enhanced brackets - var enhancedBrackets : Modes.IRegexBracketPair[] = []; - if (json.enhancedBrackets) { - if (!(Array.isArray(json.enhancedBrackets))) { - MonarchCommonTypes.throwError(lexer, 'the \'enhancedBrackets\' attribute must be defined as an array'); - } + // var enhancedBrackets : Modes.IRegexBracketPair[] = []; + // if (json.enhancedBrackets) { + // if (!(Array.isArray(json.enhancedBrackets))) { + // MonarchCommonTypes.throwError(lexer, 'the \'enhancedBrackets\' attribute must be defined as an array'); + // } - for (var bracketIdx in json.enhancedBrackets) { - if (json.enhancedBrackets.hasOwnProperty(bracketIdx)) { - var desc = json.enhancedBrackets[bracketIdx]; - if (desc.hasOwnProperty('openTrigger') && typeof (desc.openTrigger) !== 'string') { - MonarchCommonTypes.throwError(lexer, 'openTrigger in the \'enhancedBrackets\' array must be a string'); - } - if (desc.hasOwnProperty('open') && !(desc.open instanceof RegExp)) { - MonarchCommonTypes.throwError(lexer, 'open in the \'enhancedBrackets\' array must be a regex'); - } - if (desc.hasOwnProperty('closeComplete') && typeof (desc.closeComplete) !== 'string') { - MonarchCommonTypes.throwError(lexer, 'closeComplete in the \'enhancedBrackets\' array must be a string'); - } - if (desc.hasOwnProperty('matchCase') && typeof (desc.matchCase) !== 'boolean') { - MonarchCommonTypes.throwError(lexer, 'matchCase in the \'enhancedBrackets\' array must be a boolean'); - } - if (desc.hasOwnProperty('closeTrigger') && typeof (desc.closeTrigger) !== 'string') { - MonarchCommonTypes.throwError(lexer, 'closeTrigger in the \'enhancedBrackets\' array must be a string'); - } - if (desc.hasOwnProperty('close') && !(desc.close instanceof RegExp)) { - MonarchCommonTypes.throwError(lexer, 'close in the \'enhancedBrackets\' array must be a regex'); - } - if (desc.hasOwnProperty('tokenType')) { - if (typeof (desc.tokenType) !== 'string') { - MonarchCommonTypes.throwError(lexer, 'tokenType in the \'enhancedBrackets\' array must be a string'); - } - else { - desc.tokenType += lexer.tokenPostfix; - } - } - enhancedBrackets.push(desc); - } - } - } - lexer.enhancedBrackets = enhancedBrackets; + // for (var bracketIdx in json.enhancedBrackets) { + // if (json.enhancedBrackets.hasOwnProperty(bracketIdx)) { + // var desc = json.enhancedBrackets[bracketIdx]; + // if (desc.hasOwnProperty('openTrigger') && typeof (desc.openTrigger) !== 'string') { + // MonarchCommonTypes.throwError(lexer, 'openTrigger in the \'enhancedBrackets\' array must be a string'); + // } + // if (desc.hasOwnProperty('open') && !(desc.open instanceof RegExp)) { + // MonarchCommonTypes.throwError(lexer, 'open in the \'enhancedBrackets\' array must be a regex'); + // } + // if (desc.hasOwnProperty('closeComplete') && typeof (desc.closeComplete) !== 'string') { + // MonarchCommonTypes.throwError(lexer, 'closeComplete in the \'enhancedBrackets\' array must be a string'); + // } + // if (desc.hasOwnProperty('matchCase') && typeof (desc.matchCase) !== 'boolean') { + // MonarchCommonTypes.throwError(lexer, 'matchCase in the \'enhancedBrackets\' array must be a boolean'); + // } + // if (desc.hasOwnProperty('closeTrigger') && typeof (desc.closeTrigger) !== 'string') { + // MonarchCommonTypes.throwError(lexer, 'closeTrigger in the \'enhancedBrackets\' array must be a string'); + // } + // if (desc.hasOwnProperty('close') && !(desc.close instanceof RegExp)) { + // MonarchCommonTypes.throwError(lexer, 'close in the \'enhancedBrackets\' array must be a regex'); + // } + // if (desc.hasOwnProperty('tokenType')) { + // if (typeof (desc.tokenType) !== 'string') { + // MonarchCommonTypes.throwError(lexer, 'tokenType in the \'enhancedBrackets\' array must be a string'); + // } + // else { + // desc.tokenType += lexer.tokenPostfix; + // } + // } + // enhancedBrackets.push(desc); + // } + // } + // } + // lexer.enhancedBrackets = enhancedBrackets; var standardBrackets: Modes.IBracketPair[] = []; for (var i = 0; i < brackets.length; ++i) { diff --git a/src/vs/editor/common/modes/monarch/monarchDefinition.ts b/src/vs/editor/common/modes/monarch/monarchDefinition.ts index ebf0e0f35c4..e31a20c9377 100644 --- a/src/vs/editor/common/modes/monarch/monarchDefinition.ts +++ b/src/vs/editor/common/modes/monarch/monarchDefinition.ts @@ -17,38 +17,43 @@ import EditorCommon = require('vs/editor/common/editorCommon'); import {IModelService} from 'vs/editor/common/services/modelService'; import Modes = require('vs/editor/common/modes'); import {IOnEnterSupportOptions} from 'vs/editor/common/modes/supports/onEnter'; +import {CharacterPair, IRichEditConfiguration} from 'vs/editor/common/modes/supports/richEditSupport'; +import {IComposableSuggestContribution} from 'vs/editor/common/modes/supports/suggestSupport'; + +export function createRichEditSupport(lexer: MonarchCommonTypes.ILexer): IRichEditConfiguration { + + function toBracket(input:Modes.IBracketPair): CharacterPair { + return [input.open, input.close]; + } + + function toBrackets(input:Modes.IBracketPair[]): CharacterPair[] { + return input.map(toBracket); + } -export function createCommentsSupport(lexer: MonarchCommonTypes.ILexer): Supports.ICommentsSupportContribution { return { - commentsConfiguration: { - lineCommentTokens: [lexer.lineComment], - blockCommentStartToken: lexer.blockCommentStart, - blockCommentEndToken: lexer.blockCommentEnd + + wordPattern: lexer.wordDefinition, + + comments: { + lineComment: lexer.lineComment, + blockComment: [lexer.blockCommentStart, lexer.blockCommentEnd] + }, + + brackets: toBrackets(lexer.standardBrackets), + + __electricCharacterSupport: { + brackets: lexer.standardBrackets, + // regexBrackets: lexer.enhancedBrackets, + caseInsensitive: lexer.ignoreCase, + embeddedElectricCharacters: lexer.outdentTriggers.split('') + }, + + __characterPairSupport: { + autoClosingPairs: lexer.autoClosingPairs } }; } -export function createBracketElectricCharacterContribution(lexer: MonarchCommonTypes.ILexer): Supports.IBracketElectricCharacterContribution { - return { - brackets: lexer.standardBrackets, - regexBrackets: lexer.enhancedBrackets, - caseInsensitive: lexer.ignoreCase, - embeddedElectricCharacters: lexer.outdentTriggers.split('') - }; -} - -export function createTokenTypeClassificationSupportContribution(lexer: MonarchCommonTypes.ILexer): Supports.ITokenTypeClassificationSupportContribution { - return { - wordDefinition: lexer.wordDefinition - }; -} - -export function createCharacterPairContribution(lexer: MonarchCommonTypes.ILexer): Modes.ICharacterPairContribution { - return { - autoClosingPairs: lexer.autoClosingPairs - }; -} - function _addSuggestionsAtPosition(model: EditorCommon.IModel, position:EditorCommon.IPosition, lexer: MonarchCommonTypes.ILexer, superSuggestions:Modes.ISuggestResult[]): Modes.ISuggestResult[] { var extra = lexer.suggestSupport.snippets; if (!extra || extra.length === 0) { @@ -67,13 +72,7 @@ function _addSuggestionsAtPosition(model: EditorCommon.IModel, position:EditorCo return superSuggestions; } -export function createOnEnterSupportOptions(lexer:MonarchCommonTypes.ILexer): IOnEnterSupportOptions { - return { - brackets: lexer.standardBrackets - }; -} - -export function createSuggestSupport(modelService: IModelService, mode:Modes.IMode, lexer:MonarchCommonTypes.ILexer): Supports.IComposableSuggestContribution { +export function createSuggestSupport(modelService: IModelService, mode:Modes.IMode, lexer:MonarchCommonTypes.ILexer): IComposableSuggestContribution { if (lexer.suggestSupport.textualCompletions && mode instanceof AbstractMode) { return { triggerCharacters:lexer.suggestSupport.triggerCharacters, diff --git a/src/vs/editor/common/modes/monarch/monarchLexer.ts b/src/vs/editor/common/modes/monarch/monarchLexer.ts index 13863e0974e..86acc22405e 100644 --- a/src/vs/editor/common/modes/monarch/monarchLexer.ts +++ b/src/vs/editor/common/modes/monarch/monarchLexer.ts @@ -15,6 +15,7 @@ import Supports = require('vs/editor/common/modes/supports'); import MonarchCommonTypes = require('vs/editor/common/modes/monarch/monarchCommon'); import Modes = require('vs/editor/common/modes'); import {IModeService} from 'vs/editor/common/services/modeService'; +import {TokenizationSupport, IEnteringNestedModeData} from 'vs/editor/common/modes/supports/tokenizationSupport'; /** * The MonarchLexer class implements a monaco lexer that highlights source code. @@ -387,7 +388,7 @@ function findBracket(lexer: MonarchCommonTypes.ILexer, matched: string) { } export function createTokenizationSupport(modeService:IModeService, mode:Modes.IMode, lexer: MonarchCommonTypes.ILexer): Modes.ITokenizationSupport { - return new Supports.TokenizationSupport(mode, { + return new TokenizationSupport(mode, { getInitialState: (): Modes.IState => { return new MonarchLexer(mode, modeService, lexer); }, @@ -399,7 +400,7 @@ export function createTokenizationSupport(modeService:IModeService, mode:Modes.I return false; }, - getNestedMode: (rawState: Modes.IState): Supports.IEnteringNestedModeData => { + getNestedMode: (rawState: Modes.IState): IEnteringNestedModeData => { var mime = (rawState).embeddedMode; if (!modeService.isRegisteredMode(mime)) { diff --git a/src/vs/editor/common/modes/monarch/monarchTypes.ts b/src/vs/editor/common/modes/monarch/monarchTypes.ts index 3c3040dc7ae..6d5bbaba27e 100644 --- a/src/vs/editor/common/modes/monarch/monarchTypes.ts +++ b/src/vs/editor/common/modes/monarch/monarchTypes.ts @@ -75,10 +75,10 @@ export interface ILanguage { * characters that could potentially cause outdentation */ outdentTriggers?: string; - /** - * Advanced auto completion, auto indenting, and bracket matching - */ - enhancedBrackets?: Modes.IRegexBracketPair[]; + // /** + // * Advanced auto completion, auto indenting, and bracket matching + // */ + // enhancedBrackets?: Modes.IRegexBracketPair[]; suggestSupport?: { textualCompletions?: boolean; diff --git a/src/vs/editor/common/modes/nullMode.ts b/src/vs/editor/common/modes/nullMode.ts index 12e199ee5b3..ae22a418e6b 100644 --- a/src/vs/editor/common/modes/nullMode.ts +++ b/src/vs/editor/common/modes/nullMode.ts @@ -80,10 +80,12 @@ export class NullMode implements Modes.IMode { public static ID = 'vs.editor.modes.nullMode'; - public tokenTypeClassificationSupport: Modes.ITokenTypeClassificationSupport; + public richEditSupport: Modes.IRichEditSupport; constructor() { - this.tokenTypeClassificationSupport = this; + this.richEditSupport = { + wordDefinition: NullMode.DEFAULT_WORD_REGEXP + }; } public getId():string { @@ -93,18 +95,13 @@ export class NullMode implements Modes.IMode { public toSimplifiedMode(): Modes.IMode { return this; } - - public getWordDefinition():RegExp { - return NullMode.DEFAULT_WORD_REGEXP; - } } export function nullTokenize(mode: Modes.IMode, buffer:string, state: Modes.IState, deltaOffset:number = 0, stopAtOffset?:number): Modes.ILineTokens { var tokens:Modes.IToken[] = [ { startIndex: deltaOffset, - type: '', - bracket: Modes.Bracket.None + type: '' } ]; diff --git a/src/vs/editor/common/modes/supports.ts b/src/vs/editor/common/modes/supports.ts index 03eb2368598..6741017bcc8 100644 --- a/src/vs/editor/common/modes/supports.ts +++ b/src/vs/editor/common/modes/supports.ts @@ -9,7 +9,6 @@ import Strings = require('vs/base/common/strings'); import {IModelService} from 'vs/editor/common/services/modelService'; import {LineStream} from 'vs/editor/common/modes/lineStream'; import {NullMode, NullState, nullTokenize} from 'vs/editor/common/modes/nullMode'; -import {Brackets} from 'vs/editor/common/modes/autoIndentation'; import {DefaultFilter} from 'vs/editor/common/modes/modesFilters'; import Modes = require('vs/editor/common/modes'); import EditorCommon = require('vs/editor/common/editorCommon'); @@ -21,16 +20,14 @@ import {IDisposable} from 'vs/base/common/lifecycle'; export class Token implements Modes.IToken { public startIndex:number; public type:string; - public bracket:Modes.Bracket; - constructor(startIndex:number, type:string, bracket:Modes.Bracket) { + constructor(startIndex:number, type:string) { this.startIndex = startIndex; this.type = type; - this.bracket = bracket; } public toString(): string { - return '(' + this.startIndex + ', ' + this.type + ', ' + this.bracket + ')'; + return '(' + this.startIndex + ', ' + this.type + ')'; } } @@ -146,905 +143,11 @@ export class FilteredLineContext implements Modes.ILineContext { return this._actual.getTokenType(tokenIndex + this._firstTokenInModeIndex); } - public getTokenBracket(tokenIndex:number): Modes.Bracket { - return this._actual.getTokenBracket(tokenIndex + this._firstTokenInModeIndex); - } - public getTokenText(tokenIndex:number): string { return this._actual.getTokenText(tokenIndex + this._firstTokenInModeIndex); } } - -export class AbstractSupport { - - private _mode:Modes.IMode; - - constructor(mode:Modes.IMode) { - this._mode = mode; - } - - public get mode() { - return this._mode; - } +export function ignoreBracketsInToken(tokenType:string): boolean { + return /\b(comment|string|regex)\b/.test(tokenType); } - -//--- Tokenazation support implementation - -export interface ILeavingNestedModeData { - /** - * The part of the line that will be tokenized by the nested mode - */ - nestedModeBuffer: string; - - /** - * The part of the line that will be tokenized by the parent mode when it continues after the nested mode - */ - bufferAfterNestedMode: string; - - /** - * The state that will be used for continuing tokenization by the parent mode after the nested mode - */ - stateAfterNestedMode: Modes.IState; -} - -export interface IEnteringNestedModeData { - mode:Modes.IMode; - missingModePromise:TPromise; -} - -export interface ITokenizationCustomization { - - getInitialState():Modes.IState; - - enterNestedMode?: (state:Modes.IState) => boolean; - - getNestedMode?: (state:Modes.IState) => IEnteringNestedModeData; - - getNestedModeInitialState?: (myState:Modes.IState) => { state:Modes.IState; missingModePromise:TPromise; }; - - /** - * Return null if the line does not leave the nested mode - */ - getLeavingNestedModeData?: (line:string, state:Modes.IState) => ILeavingNestedModeData; - - /** - * Callback for when leaving a nested mode and returning to the outer mode. - * @param myStateAfterNestedMode The outer mode's state that will begin to tokenize - * @param lastNestedModeState The nested mode's last state - */ - onReturningFromNestedMode?: (myStateAfterNestedMode:Modes.IState, lastNestedModeState:Modes.IState)=> void; -} - -function isFunction(something) { - return typeof something === 'function'; -} - -export class TokenizationSupport extends AbstractSupport implements Modes.ITokenizationSupport, IDisposable { - - static MAX_EMBEDDED_LEVELS = 5; - - private customization:ITokenizationCustomization; - private defaults: { - enterNestedMode: boolean; - getNestedMode: boolean; - getNestedModeInitialState: boolean; - getLeavingNestedModeData: boolean; - onReturningFromNestedMode: boolean; - }; - - public shouldGenerateEmbeddedModels:boolean; - public supportsNestedModes:boolean; - - private _embeddedModesListeners: { [modeId:string]: IDisposable; }; - - constructor(mode:Modes.IMode, customization:ITokenizationCustomization, supportsNestedModes:boolean, shouldGenerateEmbeddedModels:boolean) { - super(mode); - this.customization = customization; - this.supportsNestedModes = supportsNestedModes; - this._embeddedModesListeners = {}; - if (this.supportsNestedModes) { - if (!this.mode.registerSupport) { - throw new Error('Cannot be a mode with nested modes unless I can emit a tokenizationSupport changed event!'); - } - } - this.shouldGenerateEmbeddedModels = shouldGenerateEmbeddedModels; - this.defaults = { - enterNestedMode: !isFunction(customization.enterNestedMode), - getNestedMode: !isFunction(customization.getNestedMode), - getNestedModeInitialState: !isFunction(customization.getNestedModeInitialState), - getLeavingNestedModeData: !isFunction(customization.getLeavingNestedModeData), - onReturningFromNestedMode: !isFunction(customization.onReturningFromNestedMode) - }; - } - - public dispose() : void { - for (var listener in this._embeddedModesListeners) { - this._embeddedModesListeners[listener].dispose(); - delete this._embeddedModesListeners[listener]; - } - } - - public getInitialState(): Modes.IState { - return this.customization.getInitialState(); - } - - public tokenize(line:string, state:Modes.IState, deltaOffset:number = 0, stopAtOffset:number = deltaOffset + line.length):Modes.ILineTokens { - if (state.getMode() !== this.mode) { - return this._nestedTokenize(line, state, deltaOffset, stopAtOffset, [], []); - } else { - return this._myTokenize(line, state, deltaOffset, stopAtOffset, [], []); - } - } - - /** - * Precondition is: nestedModeState.getMode() !== this - * This means we are in a nested mode when parsing starts on this line. - */ - private _nestedTokenize(buffer:string, nestedModeState:Modes.IState, deltaOffset:number, stopAtOffset:number, prependTokens:Modes.IToken[], prependModeTransitions:Modes.IModeTransition[]):Modes.ILineTokens { - var myStateBeforeNestedMode = nestedModeState.getStateData(); - var leavingNestedModeData = this.getLeavingNestedModeData(buffer, myStateBeforeNestedMode); - - // Be sure to give every embedded mode the - // opportunity to leave nested mode. - // i.e. Don't go straight to the most nested mode - var stepOnceNestedState = nestedModeState; - while (stepOnceNestedState.getStateData() && stepOnceNestedState.getStateData().getMode() !== this.mode) { - stepOnceNestedState = stepOnceNestedState.getStateData(); - } - var nestedMode = stepOnceNestedState.getMode(); - - if (!leavingNestedModeData) { - // tokenization will not leave nested mode - var result:Modes.ILineTokens; - if (nestedMode.tokenizationSupport) { - result = nestedMode.tokenizationSupport.tokenize(buffer, nestedModeState, deltaOffset, stopAtOffset); - } else { - // The nested mode doesn't have tokenization support, - // unfortunatelly this means we have to fake it - result = nullTokenize(nestedMode, buffer, nestedModeState, deltaOffset); - } - result.tokens = prependTokens.concat(result.tokens); - result.modeTransitions = prependModeTransitions.concat(result.modeTransitions); - return result; - } - - var nestedModeBuffer = leavingNestedModeData.nestedModeBuffer; - if (nestedModeBuffer.length > 0) { - // Tokenize with the nested mode - var nestedModeLineTokens:Modes.ILineTokens; - if (nestedMode.tokenizationSupport) { - nestedModeLineTokens = nestedMode.tokenizationSupport.tokenize(nestedModeBuffer, nestedModeState, deltaOffset, stopAtOffset); - } else { - // The nested mode doesn't have tokenization support, - // unfortunatelly this means we have to fake it - nestedModeLineTokens = nullTokenize(nestedMode, nestedModeBuffer, nestedModeState, deltaOffset); - } - - // Save last state of nested mode - nestedModeState = nestedModeLineTokens.endState; - - // Prepend nested mode's result to our result - prependTokens = prependTokens.concat(nestedModeLineTokens.tokens); - prependModeTransitions = prependModeTransitions.concat(nestedModeLineTokens.modeTransitions); - } - - var bufferAfterNestedMode = leavingNestedModeData.bufferAfterNestedMode; - var myStateAfterNestedMode = leavingNestedModeData.stateAfterNestedMode; - myStateAfterNestedMode.setStateData(myStateBeforeNestedMode.getStateData()); - this.onReturningFromNestedMode(myStateAfterNestedMode, nestedModeState); - - return this._myTokenize(bufferAfterNestedMode, myStateAfterNestedMode, deltaOffset + nestedModeBuffer.length, stopAtOffset, prependTokens, prependModeTransitions); - } - - /** - * Precondition is: state.getMode() === this - * This means we are in the current mode when parsing starts on this line. - */ - private _myTokenize(buffer:string, myState:Modes.IState, deltaOffset:number, stopAtOffset:number, prependTokens:Modes.IToken[], prependModeTransitions:Modes.IModeTransition[]):Modes.ILineTokens { - var lineStream = new LineStream(buffer); - var tokenResult:Modes.ITokenizationResult, beforeTokenizeStreamPos:number; - var previousType:string = null; - var retokenize:TPromise = null; - - myState = myState.clone(); - if (prependModeTransitions.length <= 0 || prependModeTransitions[prependModeTransitions.length-1].mode !== this.mode) { - // Avoid transitioning to the same mode (this can happen in case of empty embedded modes) - prependModeTransitions.push({ - startIndex: deltaOffset, - mode: this.mode - }); - } - - var maxPos = Math.min(stopAtOffset - deltaOffset, buffer.length); - var noneBracket = Modes.Bracket.None; - while (lineStream.pos() < maxPos) { - beforeTokenizeStreamPos = lineStream.pos(); - - do { - tokenResult = myState.tokenize(lineStream); - if (tokenResult === null || tokenResult === undefined || - ((tokenResult.type === undefined || tokenResult.type === null) && - (tokenResult.nextState === undefined || tokenResult.nextState === null))) { - throw new Error('Tokenizer must return a valid state'); - } - - if (tokenResult.nextState) { - tokenResult.nextState.setStateData(myState.getStateData()); - myState = tokenResult.nextState; - } - if (lineStream.pos() <= beforeTokenizeStreamPos) { - throw new Error('Stream did not advance while tokenizing. Mode id is ' + this.mode.getId() + ' (stuck at token type: "' + tokenResult.type + '", prepend tokens: "' + (prependTokens.map(t => t.type).join(',')) + '").'); - } - } while (!tokenResult.type && tokenResult.type !== ''); - - if (previousType !== tokenResult.type || tokenResult.bracket || previousType === null) { - prependTokens.push(new Token(beforeTokenizeStreamPos + deltaOffset, tokenResult.type, tokenResult.bracket || noneBracket)); - } - - previousType = tokenResult.type; - - if (this.supportsNestedModes && this.enterNestedMode(myState)) { - var currentEmbeddedLevels = this._getEmbeddedLevel(myState); - if (currentEmbeddedLevels < TokenizationSupport.MAX_EMBEDDED_LEVELS) { - var nestedModeState = this.getNestedModeInitialState(myState); - - // Re-emit tokenizationSupport change events from all modes that I ever embedded - var embeddedMode = nestedModeState.state.getMode(); - if (typeof embeddedMode.addSupportChangedListener === 'function' && !this._embeddedModesListeners.hasOwnProperty(embeddedMode.getId())) { - var emitting = false; - this._embeddedModesListeners[embeddedMode.getId()] = embeddedMode.addSupportChangedListener((e) => { - if (emitting) { - return; - } - if (e.tokenizationSupport) { - emitting = true; - this.mode.registerSupport('tokenizationSupport', (mode) => { - return mode.tokenizationSupport; - }); - emitting = false; - } - }); - } - - - if (!lineStream.eos()) { - // There is content from the embedded mode - var restOfBuffer = buffer.substr(lineStream.pos()); - var result = this._nestedTokenize(restOfBuffer, nestedModeState.state, deltaOffset + lineStream.pos(), stopAtOffset, prependTokens, prependModeTransitions); - result.retokenize = result.retokenize || nestedModeState.missingModePromise; - return result; - } else { - // Transition to the nested mode state - myState = nestedModeState.state; - retokenize = nestedModeState.missingModePromise; - } - } - } - } - - return { - tokens: prependTokens, - actualStopOffset: lineStream.pos() + deltaOffset, - modeTransitions: prependModeTransitions, - endState: myState, - retokenize: retokenize - }; - } - - private _getEmbeddedLevel(state:Modes.IState): number { - var result = -1; - while(state) { - result++; - state = state.getStateData(); - } - return result; - } - - private enterNestedMode(state:Modes.IState): boolean { - if (this.defaults.enterNestedMode) { - return false; - } - return this.customization.enterNestedMode(state); - - } - - private getNestedMode(state:Modes.IState): IEnteringNestedModeData { - if (this.defaults.getNestedMode) { - return null; - } - return this.customization.getNestedMode(state); - } - - private static _validatedNestedMode(input:IEnteringNestedModeData): IEnteringNestedModeData { - var mode: Modes.IMode = new NullMode(), - missingModePromise: TPromise = null; - - if (input && input.mode) { - mode = input.mode; - } - if (input && input.missingModePromise) { - missingModePromise = input.missingModePromise; - } - - return { - mode: mode, - missingModePromise: missingModePromise - }; - } - - private getNestedModeInitialState(state:Modes.IState): { state:Modes.IState; missingModePromise:TPromise; } { - if (this.defaults.getNestedModeInitialState) { - var nestedMode = TokenizationSupport._validatedNestedMode(this.getNestedMode(state)); - var missingModePromise = nestedMode.missingModePromise; - var nestedModeState: Modes.IState; - - if (nestedMode.mode.tokenizationSupport) { - nestedModeState = nestedMode.mode.tokenizationSupport.getInitialState(); - } else { - nestedModeState = new NullState(nestedMode.mode, null); - } - - nestedModeState.setStateData(state); - - return { - state: nestedModeState, - missingModePromise: missingModePromise - }; - } - return this.customization.getNestedModeInitialState(state); - } - - private getLeavingNestedModeData(line:string, state:Modes.IState): ILeavingNestedModeData { - if (this.defaults.getLeavingNestedModeData) { - return null; - } - return this.customization.getLeavingNestedModeData(line, state); - } - - private onReturningFromNestedMode(myStateAfterNestedMode:Modes.IState, lastNestedModeState:Modes.IState): void { - if (this.defaults.onReturningFromNestedMode) { - return null; - } - return this.customization.onReturningFromNestedMode(myStateAfterNestedMode, lastNestedModeState); - } -} - -export interface IBracketElectricCharacterContribution { - brackets: Modes.IBracketPair[]; - regexBrackets?: Modes.IRegexBracketPair[]; - docComment?: Modes.IDocComment; - caseInsensitive?: boolean; - embeddedElectricCharacters?: string[]; -} -export class BracketElectricCharacterSupport extends AbstractSupport implements Modes.IElectricCharacterSupport { - - private contribution: IBracketElectricCharacterContribution; - private brackets: Brackets; - - constructor(mode:Modes.IMode, contribution: IBracketElectricCharacterContribution) { - super(mode); - this.contribution = contribution; - this.brackets = new Brackets(contribution.brackets, contribution.regexBrackets, - contribution.docComment, contribution.caseInsensitive); - } - - public getElectricCharacters(): string[]{ - if (Array.isArray(this.contribution.embeddedElectricCharacters)) { - return this.contribution.embeddedElectricCharacters.concat(this.brackets.getElectricCharacters()); - } - return this.brackets.getElectricCharacters(); - } - - public onElectricCharacter(context:Modes.ILineContext, offset:number): Modes.IElectricAction { - return handleEvent(context, offset, (nestedMode:Modes.IMode, context:Modes.ILineContext, offset:number) => { - if (this.mode === nestedMode) { - return this.brackets.onElectricCharacter(context, offset); - } else if (nestedMode.electricCharacterSupport) { - return nestedMode.electricCharacterSupport.onElectricCharacter(context, offset); - } else { - return null; - } - }); - } - - public onEnter(context: Modes.ILineContext, offset: number): Modes.IEnterAction { - return handleEvent(context, offset, (nestedMode:Modes.IMode, context:Modes.ILineContext, offset:number) => { - if (this.mode === nestedMode) { - return this.brackets.onEnter(context, offset); - } else if (nestedMode.electricCharacterSupport) { - return nestedMode.electricCharacterSupport.onEnter(context, offset); - } else { - return null; - } - }); - } -} - - - -export interface IDeclarationContribution { - tokens?: string[]; - findDeclaration: (resource: URI, position: EditorCommon.IPosition) => TPromise; -} -export class DeclarationSupport extends AbstractSupport implements Modes.IDeclarationSupport { - - private contribution: IDeclarationContribution; - - /** - * Provide the token type postfixes for the tokens where a declaration can be found in the 'tokens' argument. - */ - constructor(mode: Modes.IMode, contribution: IDeclarationContribution) { - super(mode); - this.contribution = contribution; - } - - public canFindDeclaration(context: Modes.ILineContext, offset:number):boolean { - return handleEvent(context, offset, (nestedMode:Modes.IMode, context:Modes.ILineContext, offset:number) => { - if (this.mode === nestedMode) { - return (!Array.isArray(this.contribution.tokens) || - this.contribution.tokens.length < 1 || - isLineToken(context, offset, this.contribution.tokens)); - } else if (nestedMode.declarationSupport) { - return nestedMode.declarationSupport.canFindDeclaration(context, offset); - } else { - return false; - } - }); - } - - public findDeclaration(resource: URI, position: EditorCommon.IPosition): TPromise{ - return this.contribution.findDeclaration(resource, position); - } -} - -export interface ITypeDeclarationContribution { - tokens: string[]; - findTypeDeclaration: (resource: URI, position: EditorCommon.IPosition) => TPromise; -} -export class TypeDeclarationSupport extends AbstractSupport implements Modes.ITypeDeclarationSupport { - - private contribution: ITypeDeclarationContribution; - - /** - * Provide the token type postfixes for the tokens where a declaration can be found in the 'tokens' argument. - */ - constructor(mode: Modes.IMode, contribution: ITypeDeclarationContribution) { - super(mode); - this.contribution = contribution; - } - - public canFindTypeDeclaration(context: Modes.ILineContext, offset:number):boolean { - return handleEvent(context, offset, (nestedMode:Modes.IMode, context:Modes.ILineContext, offset:number) => { - if (this.mode === nestedMode) { - return (!Array.isArray(this.contribution.tokens) || - this.contribution.tokens.length < 1 || - isLineToken(context, offset, this.contribution.tokens)); - } else if (nestedMode.typeDeclarationSupport) { - return nestedMode.typeDeclarationSupport.canFindTypeDeclaration(context, offset); - } else { - return false; - } - }); - } - - public findTypeDeclaration(resource: URI, position: EditorCommon.IPosition): TPromise { - return this.contribution.findTypeDeclaration(resource, position); - } -} - -export interface IReferenceContribution { - tokens: string[]; - findReferences: (resource: URI, position: EditorCommon.IPosition, includeDeclaration: boolean) => TPromise; -} - -export class ReferenceSupport extends AbstractSupport implements Modes.IReferenceSupport { - - private contribution: IReferenceContribution; - - /** - * Provide the token type postfixes for the tokens where a reference can be found in the 'tokens' argument. - */ - constructor(mode: Modes.IMode, contribution: IReferenceContribution) { - super(mode); - this.contribution = contribution; - } - - public canFindReferences(context: Modes.ILineContext, offset:number):boolean { - return handleEvent(context, offset, (nestedMode:Modes.IMode, context:Modes.ILineContext, offset:number) => { - if (this.mode === nestedMode) { - return (!Array.isArray(this.contribution.tokens) || - this.contribution.tokens.length < 1 || - isLineToken(context, offset, this.contribution.tokens)); - } else if (nestedMode.referenceSupport) { - return nestedMode.referenceSupport.canFindReferences(context, offset); - } else { - return false; - } - }); - } - - public findReferences(resource: URI, position: EditorCommon.IPosition, includeDeclaration: boolean): TPromise { - return this.contribution.findReferences(resource, position, includeDeclaration); - } -} - -export class ParameterHintsSupport extends AbstractSupport implements Modes.IParameterHintsSupport { - - private contribution: Modes.IParameterHintsContribution; - - constructor(mode: Modes.IMode, contribution: Modes.IParameterHintsContribution) { - super(mode); - this.contribution = contribution; - } - - public getParameterHintsTriggerCharacters(): string[] - { - return this.contribution.triggerCharacters; - } - - public shouldTriggerParameterHints(context: Modes.ILineContext, offset: number): boolean - { - return handleEvent(context, offset, (nestedMode:Modes.IMode, context:Modes.ILineContext, offset:number) => { - if (this.mode === nestedMode) { - if (!Array.isArray(this.contribution.excludeTokens)) { - return true; - } - if (this.contribution.excludeTokens.length === 1 && this.contribution.excludeTokens[0] === '*') { - return false; - } - return !isLineToken(context, offset-1, this.contribution.excludeTokens); - } else if (nestedMode.parameterHintsSupport) { - return nestedMode.parameterHintsSupport.shouldTriggerParameterHints(context, offset); - } else { - return false; - } - }); - } - public getParameterHints(resource: URI, position: EditorCommon.IPosition): TPromise { - return this.contribution.getParameterHints(resource, position); - } -} - -export interface ISuggestContribution { - triggerCharacters: string[]; - disableAutoTrigger?: boolean; - excludeTokens: string[]; - suggest: (resource: URI, position: EditorCommon.IPosition) => TPromise; - getSuggestionDetails? : (resource:URI, position:EditorCommon.IPosition, suggestion:Modes.ISuggestion) => TPromise; -} - -export class SuggestSupport extends AbstractSupport implements Modes.ISuggestSupport { - - private contribution: ISuggestContribution; - - public suggest : (resource:URI, position:EditorCommon.IPosition) => TPromise; - public getSuggestionDetails : (resource:URI, position:EditorCommon.IPosition, suggestion:Modes.ISuggestion) => TPromise; - - constructor(mode: Modes.IMode, contribution : ISuggestContribution){ - super(mode); - this.contribution = contribution; - this.suggest = (resource, position) => contribution.suggest(resource, position); - - if (typeof contribution.getSuggestionDetails === 'function') { - this.getSuggestionDetails = (resource, position, suggestion) => contribution.getSuggestionDetails(resource, position, suggestion); - } - } - - shouldAutotriggerSuggest(context: Modes.ILineContext, offset: number, triggeredByCharacter: string): boolean { - return handleEvent(context, offset, (nestedMode:Modes.IMode, context:Modes.ILineContext, offset:number) => { - if (this.mode === nestedMode) { - if (this.contribution.disableAutoTrigger) { - return false; - } - if (!Array.isArray(this.contribution.excludeTokens)) { - return true; - } - if (this.contribution.excludeTokens.length === 1 && this.contribution.excludeTokens[0] === '*') { - return false; - } - return !isLineToken(context, offset-1, this.contribution.excludeTokens, true); - } else if (nestedMode.suggestSupport) { - return nestedMode.suggestSupport.shouldAutotriggerSuggest(context, offset, triggeredByCharacter); - } else { - return false; - } - }); - } - - public getFilter(): Modes.ISuggestionFilter { - return DefaultFilter; - } - - public getTriggerCharacters(): string[] { - return this.contribution.triggerCharacters; - } - - public shouldShowEmptySuggestionList(): boolean { - return true; - } -} - -export interface IComposableSuggestContribution extends ISuggestContribution { - composeSuggest(resource:URI, position:EditorCommon.IPosition, superSuggestions:Modes.ISuggestResult[]): TPromise; -} - -export class ComposableSuggestSupport extends SuggestSupport { - - constructor(mode: Modes.IMode, contribution: IComposableSuggestContribution) { - super(mode, contribution); - - this.suggest = (resource, position) => { - return ( - contribution.suggest(resource, position) - .then(superSuggestions => contribution.composeSuggest(resource, position, superSuggestions)) - ); - }; - } - -} - -export class CharacterPairSupport extends AbstractSupport implements Modes.ICharacterPairSupport { - - private _autoClosingPairs: Modes.IAutoClosingPairConditional[]; - private _surroundingPairs: Modes.IAutoClosingPair[]; - - constructor(mode: Modes.IMode, contribution: Modes.ICharacterPairContribution) { - - super(mode); - this._autoClosingPairs = contribution.autoClosingPairs; - this._surroundingPairs = Array.isArray(contribution.surroundingPairs) ? contribution.surroundingPairs : contribution.autoClosingPairs; - } - - public getAutoClosingPairs(): Modes.IAutoClosingPair[] { - return this._autoClosingPairs; - } - - public shouldAutoClosePair(character:string, context:Modes.ILineContext, offset:number): boolean { - return handleEvent(context, offset, (nestedMode:Modes.IMode, context:Modes.ILineContext, offset:number) => { - if (this.mode === nestedMode) { - - // Always complete on empty line - if (context.getTokenCount() === 0) { - return true; - } - - var tokenIndex = context.findIndexOfOffset(offset - 1); - var tokenType = context.getTokenType(tokenIndex); - - for (var i = 0; i < this._autoClosingPairs.length; ++i) { - if (this._autoClosingPairs[i].open === character) { - if (this._autoClosingPairs[i].notIn) { - for (var notInIndex = 0; notInIndex < this._autoClosingPairs[i].notIn.length; ++notInIndex) { - if (tokenType.indexOf(this._autoClosingPairs[i].notIn[notInIndex]) > -1) { - return false; - } - } - } - break; - } - } - - return true; - } else if (nestedMode.characterPairSupport) { - return nestedMode.characterPairSupport.shouldAutoClosePair(character, context, offset); - } else { - return null; - } - }); - } - - public getSurroundingPairs(): Modes.IAutoClosingPair[]{ - return this._surroundingPairs; - } -} - -export interface IReplaceSupportHelper { - valueSetReplace(valueSet: string[], value: string, up: boolean): string; - valueSetsReplace(valueSets: string[][], value: string, up: boolean): string; -} -class ReplaceSupportHelperImpl implements IReplaceSupportHelper { - - public valueSetsReplace(valueSets:string[][], value:string, up:boolean):string { - var result:string = null; - for (let i = 0, len = valueSets.length; result === null && i < len; i++) { - result = this.valueSetReplace(valueSets[i], value, up); - } - return result; - } - - public valueSetReplace(valueSet:string[], value:string, up:boolean):string { - var idx = valueSet.indexOf(value); - if(idx >= 0) { - idx += up ? +1 : -1; - if(idx < 0) { - idx = valueSet.length - 1; - } else { - idx %= valueSet.length; - } - return valueSet[idx]; - } - return null; - } - -} - -export var ReplaceSupport: IReplaceSupportHelper = new ReplaceSupportHelperImpl(); - -export interface IInplaceReplaceSupportCustomization { - textReplace?: (value: string, up: boolean) => string; - navigateValueSetFallback?: (resource: URI, range: EditorCommon.IRange, up: boolean) => TPromise; -} - -export class AbstractInplaceReplaceSupport implements Modes.IInplaceReplaceSupport { - - private defaults: { - textReplace: boolean; - navigateValueSetFallback: boolean; - }; - private customization:IInplaceReplaceSupportCustomization; - - constructor(customization: IInplaceReplaceSupportCustomization = null) { - this.defaults = { - textReplace: !customization || !isFunction(customization.textReplace), - navigateValueSetFallback: !customization || !isFunction(customization.navigateValueSetFallback) - }; - this.customization = customization; - } - - public navigateValueSet(resource:URI, range:EditorCommon.IRange, up:boolean):TPromise { - var result = this.doNavigateValueSet(resource, range, up, true); - if (result && result.value && result.range) { - return TPromise.as(result); - } - if (this.defaults.navigateValueSetFallback) { - return TPromise.as(null); - } - return this.customization.navigateValueSetFallback(resource, range, up); - } - - private doNavigateValueSet(resource:URI, range:EditorCommon.IRange, up:boolean, selection:boolean):Modes.IInplaceReplaceSupportResult { - - var model = this.getModel(resource), - result:Modes.IInplaceReplaceSupportResult = { range:null, value: null }, - text:string; - - if(selection) { - // Replace selection - if(range.startColumn === range.endColumn) { - range.endColumn += 1; - } - text = model.getValueInRange(range); - result.range = range; - } else { - // Replace word - var position = { lineNumber: range.startLineNumber, column: range.startColumn }; - var wordPos = model.getWordAtPosition(position); - - if(!wordPos || wordPos.startColumn === -1) { - return null; - } - text = wordPos.word; - result.range = { startLineNumber : range.startLineNumber, endLineNumber: range.endLineNumber, startColumn: wordPos.startColumn, endColumn: wordPos.endColumn }; - } - - // Try to replace numbers or text - var numberResult = this.numberReplace(text, up); - if(numberResult !== null) { - result.value = numberResult; - } else { - var textResult = this.textReplace(text, up); - if(textResult !== null) { - result.value = textResult; - } else if(selection) { - return this.doNavigateValueSet(resource, range, up, false); - } - } - return result; - } - - private numberReplace(value:string, up:boolean):string { - var precision = Math.pow(10, value.length - (value.lastIndexOf('.') + 1)), - n1 = Number(value), - n2 = parseFloat(value); - - if(!isNaN(n1) && !isNaN(n2) && n1 === n2) { - - if(n1 === 0 && !up) { - return null; // don't do negative -// } else if(n1 === 9 && up) { -// return null; // don't insert 10 into a number - } else { - n1 = Math.floor(n1 * precision); - n1 += up ? precision : -precision; - return String(n1 / precision); - } - } - - return null; - } - - private _defaultValueSet: string[][] = [ - ['true', 'false'], - ['True', 'False'], - ['Private', 'Public', 'Friend', 'ReadOnly', 'Partial', 'Protected', 'WriteOnly'], - ['public', 'protected', 'private'], - ]; - - private textReplace(value:string, up:boolean):string { - if (this.defaults.textReplace) { - return ReplaceSupport.valueSetsReplace(this._defaultValueSet, value, up); - } - return this.customization.textReplace(value, up) - || ReplaceSupport.valueSetsReplace(this._defaultValueSet, value, up); - } - - protected getModel(resource:URI): EditorCommon.ITokenizedModel { - throw new Error('Not implemented'); - } -} - -export class WorkerInplaceReplaceSupport extends AbstractInplaceReplaceSupport { - - private resourceService: IResourceService; - - constructor(resourceService: IResourceService, customization: IInplaceReplaceSupportCustomization = null) { - super(customization); - this.resourceService = resourceService; - } - - protected getModel(resource:URI): EditorCommon.ITokenizedModel { - return this.resourceService.get(resource); - } -} - -export class MainInplaceReplaceSupport extends AbstractInplaceReplaceSupport { - private modelService: IModelService; - - constructor(modelService: IModelService, customization: IInplaceReplaceSupportCustomization = null) { - super(customization); - this.modelService = modelService; - } - - protected getModel(resource:URI): EditorCommon.ITokenizedModel { - return this.modelService.getModel(resource); - } -} - -export interface ICommentsSupportContribution { - commentsConfiguration: Modes.ICommentsConfiguration; -} - -export class CommentsSupport implements Modes.ICommentsSupport { - - private _contribution: ICommentsSupportContribution; - - constructor(contribution:ICommentsSupportContribution) { - this._contribution = contribution; - } - - public getCommentsConfiguration(): Modes.ICommentsConfiguration { - return this._contribution.commentsConfiguration; - } - -} - -export interface ITokenTypeClassificationSupportContribution { - wordDefinition?: RegExp; -} - -export class TokenTypeClassificationSupport implements Modes.ITokenTypeClassificationSupport { - - private _contribution: ITokenTypeClassificationSupportContribution; - - constructor(contribution: ITokenTypeClassificationSupportContribution) { - this._contribution = contribution; - } - - public getWordDefinition(): RegExp { - if (typeof this._contribution.wordDefinition === 'undefined') { - return NullMode.DEFAULT_WORD_REGEXP; - } - return this._contribution.wordDefinition; - } -} \ No newline at end of file diff --git a/src/vs/editor/common/modes/supports/characterPair.ts b/src/vs/editor/common/modes/supports/characterPair.ts new file mode 100644 index 00000000000..f90d629d56f --- /dev/null +++ b/src/vs/editor/common/modes/supports/characterPair.ts @@ -0,0 +1,68 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +import {handleEvent} from 'vs/editor/common/modes/supports'; +import * as Modes from 'vs/editor/common/modes'; + +export interface ICharacterPairContribution { + autoClosingPairs: Modes.IAutoClosingPairConditional[]; + surroundingPairs?: Modes.IAutoClosingPair[]; +} + +export class CharacterPairSupport implements Modes.IRichEditCharacterPair { + + private _modeId: string; + private _autoClosingPairs: Modes.IAutoClosingPairConditional[]; + private _surroundingPairs: Modes.IAutoClosingPair[]; + + constructor(modeId: string, contribution: ICharacterPairContribution) { + this._modeId = modeId; + this._autoClosingPairs = contribution.autoClosingPairs; + this._surroundingPairs = Array.isArray(contribution.surroundingPairs) ? contribution.surroundingPairs : contribution.autoClosingPairs; + } + + public getAutoClosingPairs(): Modes.IAutoClosingPair[] { + return this._autoClosingPairs; + } + + public shouldAutoClosePair(character:string, context:Modes.ILineContext, offset:number): boolean { + return handleEvent(context, offset, (nestedMode:Modes.IMode, context:Modes.ILineContext, offset:number) => { + if (this._modeId === nestedMode.getId()) { + + // Always complete on empty line + if (context.getTokenCount() === 0) { + return true; + } + + var tokenIndex = context.findIndexOfOffset(offset - 1); + var tokenType = context.getTokenType(tokenIndex); + + for (var i = 0; i < this._autoClosingPairs.length; ++i) { + if (this._autoClosingPairs[i].open === character) { + if (this._autoClosingPairs[i].notIn) { + for (var notInIndex = 0; notInIndex < this._autoClosingPairs[i].notIn.length; ++notInIndex) { + if (tokenType.indexOf(this._autoClosingPairs[i].notIn[notInIndex]) > -1) { + return false; + } + } + } + break; + } + } + + return true; + } else if (nestedMode.richEditSupport && nestedMode.richEditSupport.characterPair) { + return nestedMode.richEditSupport.characterPair.shouldAutoClosePair(character, context, offset); + } else { + return null; + } + }); + } + + public getSurroundingPairs(): Modes.IAutoClosingPair[]{ + return this._surroundingPairs; + } +} diff --git a/src/vs/editor/common/modes/supports/declarationSupport.ts b/src/vs/editor/common/modes/supports/declarationSupport.ts new file mode 100644 index 00000000000..acdc9a55ba0 --- /dev/null +++ b/src/vs/editor/common/modes/supports/declarationSupport.ts @@ -0,0 +1,47 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +import {TPromise} from 'vs/base/common/winjs.base'; +import {IReference, IDeclarationSupport, ILineContext, IMode} from 'vs/editor/common/modes'; +import {IPosition} from 'vs/editor/common/editorCommon'; +import URI from 'vs/base/common/uri'; +import {handleEvent, isLineToken} from 'vs/editor/common/modes/supports'; + +export interface IDeclarationContribution { + tokens?: string[]; + findDeclaration: (resource: URI, position: IPosition) => TPromise; +} +export class DeclarationSupport implements IDeclarationSupport { + + private _modeId: string; + private contribution: IDeclarationContribution; + + /** + * Provide the token type postfixes for the tokens where a declaration can be found in the 'tokens' argument. + */ + constructor(modeId: string, contribution: IDeclarationContribution) { + this._modeId = modeId; + this.contribution = contribution; + } + + public canFindDeclaration(context: ILineContext, offset:number):boolean { + return handleEvent(context, offset, (nestedMode:IMode, context:ILineContext, offset:number) => { + if (this._modeId === nestedMode.getId()) { + return (!Array.isArray(this.contribution.tokens) || + this.contribution.tokens.length < 1 || + isLineToken(context, offset, this.contribution.tokens)); + } else if (nestedMode.declarationSupport) { + return nestedMode.declarationSupport.canFindDeclaration(context, offset); + } else { + return false; + } + }); + } + + public findDeclaration(resource: URI, position: IPosition): TPromise{ + return this.contribution.findDeclaration(resource, position); + } +} diff --git a/src/vs/editor/common/modes/supports/electricCharacter.ts b/src/vs/editor/common/modes/supports/electricCharacter.ts new file mode 100644 index 00000000000..1afe4c25ab5 --- /dev/null +++ b/src/vs/editor/common/modes/supports/electricCharacter.ts @@ -0,0 +1,341 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +import * as Modes from 'vs/editor/common/modes'; +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 {IRichEditBracket} from 'vs/editor/common/editorCommon'; +import {Arrays} from 'vs/editor/common/core/arrays'; + +/** + * Definition of documentation comments (e.g. Javadoc/JSdoc) + */ +export interface IDocComment { + scope: string; // What tokens should be used to detect a doc comment (e.g. 'comment.documentation'). + open: string; // The string that starts a doc comment (e.g. '/**') + lineStart: string; // The string that appears at the start of each line, except the first and last (e.g. ' * '). + close?: string; // The string that appears on the last line and closes the doc comment (e.g. ' */'). +} + +export interface IBracketElectricCharacterContribution { + brackets: Modes.IBracketPair[]; + docComment?: IDocComment; + caseInsensitive?: boolean; + embeddedElectricCharacters?: string[]; +} + +export class BracketElectricCharacterSupport implements Modes.IRichEditElectricCharacter { + + private _modeId: string; + private contribution: IBracketElectricCharacterContribution; + private brackets: Brackets; + + constructor(modeId: string, contribution: IBracketElectricCharacterContribution) { + this._modeId = modeId; + this.contribution = contribution; + this.brackets = new Brackets(modeId, contribution.brackets, contribution.docComment, contribution.caseInsensitive); + } + + public getElectricCharacters(): string[]{ + if (Array.isArray(this.contribution.embeddedElectricCharacters)) { + return this.contribution.embeddedElectricCharacters.concat(this.brackets.getElectricCharacters()); + } + return this.brackets.getElectricCharacters(); + } + + public onElectricCharacter(context:Modes.ILineContext, offset:number): Modes.IElectricAction { + return handleEvent(context, offset, (nestedMode:Modes.IMode, context:Modes.ILineContext, offset:number) => { + if (this._modeId === nestedMode.getId()) { + return this.brackets.onElectricCharacter(context, offset); + } else if (nestedMode.richEditSupport && nestedMode.richEditSupport.electricCharacter) { + return nestedMode.richEditSupport.electricCharacter.onElectricCharacter(context, offset); + } else { + return null; + } + }); + } + + public getRichEditBrackets(): Modes.IRichEditBrackets { + return this.brackets.getRichEditBrackets(); + } +} + +interface ISimpleInternalBracket { + open: string; + close: string; +} + +export class Brackets { + + private _modeId: string; + private _brackets: IRichEditBracket[]; + private _bracketsForwardRegex: RegExp; + private _bracketsReversedRegex: RegExp; + private _maxBracketLength: number; + private _textIsBracket: {[text:string]:IRichEditBracket;}; + private _textIsOpenBracket: {[text:string]:boolean;}; + private _docComment: IDocComment; + + constructor(modeId: string, brackets: Modes.IBracketPair[], docComment: IDocComment = null, caseInsensitive: boolean = false) { + 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 }), + reversedRegex: getReversedRegexForBracketPair({ open: b.open, close: b.close }) + }; + }); + this._bracketsForwardRegex = getRegexForBrackets(this._brackets); + this._bracketsReversedRegex = getReversedRegexForBrackets(this._brackets); + + this._textIsBracket = {}; + this._textIsOpenBracket = {}; + this._maxBracketLength = 0; + this._brackets.forEach((b) => { + this._textIsBracket[b.open] = b; + this._textIsBracket[b.close] = b; + this._textIsOpenBracket[b.open] = true; + this._textIsOpenBracket[b.close] = false; + this._maxBracketLength = Math.max(this._maxBracketLength, b.open.length); + this._maxBracketLength = Math.max(this._maxBracketLength, b.close.length); + }); + this._docComment = docComment ? docComment : null; + } + + public getRichEditBrackets(): Modes.IRichEditBrackets { + if (this._brackets.length === 0) { + return null; + } + return { + maxBracketLength: this._maxBracketLength, + forwardRegex: this._bracketsForwardRegex, + reversedRegex: this._bracketsReversedRegex, + brackets: this._brackets, + textIsBracket: this._textIsBracket, + textIsOpenBracket: this._textIsOpenBracket + }; + } + + public getElectricCharacters():string[] { + var result: string[] = []; + + for (let i = 0, len = this._brackets.length; i < len; i++) { + let bracketPair = this._brackets[i]; + let lastChar = bracketPair.close.charAt(bracketPair.close.length - 1); + result.push(lastChar); + } + + // Doc comments + if (this._docComment){ + result.push(this._docComment.open.charAt(this._docComment.open.length - 1)); + } + + // Filter duplicate entries + result = result.filter((item, pos, array) => { + return array.indexOf(item) === pos; + }); + + return result; + } + + public onElectricCharacter(context: Modes.ILineContext, offset: number): Modes.IElectricAction { + if (context.getTokenCount() === 0) { + return null; + } + + return (this._onElectricCharacterDocComment(context, offset) || + this._onElectricCharacterStandardBrackets(context, offset)); + } + + private containsTokenTypes(fullTokenSpec: string, tokensToLookFor: string): boolean { + var array = tokensToLookFor.split('.'); + for (var i = 0; i < array.length; ++i) { + if (fullTokenSpec.indexOf(array[i]) < 0) { + return false; + } + } + return true; + } + + private _onElectricCharacterStandardBrackets(context: Modes.ILineContext, offset: number): Modes.IElectricAction { + + if (this._brackets.length === 0) { + return null; + } + + let reversedBracketRegex = this._bracketsReversedRegex; + + let lineText = context.getLineContent(); + let tokenIndex = context.findIndexOfOffset(offset); + let tokenStart = context.getTokenStartIndex(tokenIndex); + let tokenEnd = offset + 1; + + var firstNonWhitespaceIndex = Strings.firstNonWhitespaceIndex(context.getLineContent()); + if (firstNonWhitespaceIndex !== -1 && firstNonWhitespaceIndex < tokenStart) { + return null; + } + + if (!ignoreBracketsInToken(context.getTokenType(tokenIndex))) { + let r = BracketsUtils.findPrevBracketInToken(reversedBracketRegex, 1, lineText, tokenStart, tokenEnd); + if (r) { + let text = lineText.substring(r.startColumn - 1, r.endColumn - 1); + let isOpen = this._textIsOpenBracket[text]; + if (!isOpen) { + return { + matchOpenBracket: text + }; + } + } + } + + return null; + } + + private _onElectricCharacterDocComment(context: Modes.ILineContext, offset: number): Modes.IElectricAction { + // We only auto-close, so do nothing if there is no closing part. + if (!this._docComment || !this._docComment.close) { + return null; + } + + var line = context.getLineContent(); + var char: string = line[offset]; + + // See if the right electric character was pressed + if (char !== this._docComment.open.charAt(this._docComment.open.length - 1)) { + return null; + } + + // If this line already contains the closing tag, do nothing. + if (line.indexOf(this._docComment.close, offset) >= 0) { + return null; + } + + // If we're not in a documentation comment, do nothing. + var lastTokenIndex = context.findIndexOfOffset(offset); + if (! this.containsTokenTypes(context.getTokenType(lastTokenIndex), this._docComment.scope)) { + return null; + } + + if (line.substring(context.getTokenStartIndex(lastTokenIndex), offset+1/* include electric char*/) !== this._docComment.open) { + return null; + } + + return { appendText: this._docComment.close}; + } +} + +function once(keyFn:(input:T)=>string, computeFn:(input:T)=>R):(input:T)=>R { + let cache: {[key:string]:R;} = {}; + return (input:T):R => { + let key = keyFn(input); + if (!cache.hasOwnProperty(key)) { + cache[key] = computeFn(input); + } + return cache[key]; + } +} + +var getRegexForBracketPair = once( + (input) => `${input.open};${input.close}`, + (input) => { + return createOrRegex([input.open, input.close]); + } +); + +var getReversedRegexForBracketPair = once( + (input) => `${input.open};${input.close}`, + (input) => { + return createOrRegex([toReversedString(input.open), toReversedString(input.close)]); + } +); + +var getRegexForBrackets = once( + (input) => input.map(b => `${b.open};${b.close}`).join(';'), + (input) => { + let pieces: string[] = []; + input.forEach((b) => { + pieces.push(b.open); + pieces.push(b.close); + }); + return createOrRegex(pieces); + } +); + +var getReversedRegexForBrackets = once( + (input) => input.map(b => `${b.open};${b.close}`).join(';'), + (input) => { + let pieces: string[] = []; + input.forEach((b) => { + pieces.push(toReversedString(b.open)); + pieces.push(toReversedString(b.close)); + }); + return createOrRegex(pieces); + } +); + +function createOrRegex(pieces:string[]): RegExp { + let regexStr = `(${pieces.map(Strings.escapeRegExpCharacters).join(')|(')})`; + return Strings.createRegExp(regexStr, true, false, false, false); +} + +function toReversedString(str:string): string { + let reversedStr = ''; + for (let i = str.length - 1; i >= 0; i--) { + reversedStr += str.charAt(i); + } + return reversedStr; +} + +export class BracketsUtils { + + private static _findPrevBracketInText(reversedBracketRegex:RegExp, lineNumber:number, reversedText:string, offset:number): Range { + let m = reversedText.match(reversedBracketRegex); + + if (!m) { + return null; + } + + 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); + } + + public 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); + } + + public static findNextBracketInText(bracketRegex:RegExp, lineNumber:number, text:string, offset:number): Range { + let m = text.match(bracketRegex); + + if (!m) { + return null; + } + + let matchOffset = m.index; + let matchLength = m[0].length; + let absoluteMatchOffset = offset + matchOffset; + + return new Range(lineNumber, absoluteMatchOffset + 1, lineNumber, absoluteMatchOffset + 1 + matchLength); + } + + public static findNextBracketInToken(bracketRegex:RegExp, lineNumber:number, lineText:string, currentTokenStart:number, currentTokenEnd:number): Range { + let currentTokenText = lineText.substring(currentTokenStart, currentTokenEnd); + + return this.findNextBracketInText(bracketRegex, lineNumber, currentTokenText, currentTokenStart); + } + +} diff --git a/src/vs/editor/common/modes/supports/inplaceReplaceSupport.ts b/src/vs/editor/common/modes/supports/inplaceReplaceSupport.ts new file mode 100644 index 00000000000..8fd8a2b4f45 --- /dev/null +++ b/src/vs/editor/common/modes/supports/inplaceReplaceSupport.ts @@ -0,0 +1,174 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +import {TPromise} from 'vs/base/common/winjs.base'; +import {IInplaceReplaceSupport, IInplaceReplaceSupportResult} from 'vs/editor/common/modes'; +import {ITokenizedModel, IRange} from 'vs/editor/common/editorCommon'; +import {IResourceService} from 'vs/editor/common/services/resourceService'; +import URI from 'vs/base/common/uri'; + +export interface IReplaceSupportHelper { + valueSetReplace(valueSet: string[], value: string, up: boolean): string; + valueSetsReplace(valueSets: string[][], value: string, up: boolean): string; +} +class ReplaceSupportHelperImpl implements IReplaceSupportHelper { + + public valueSetsReplace(valueSets:string[][], value:string, up:boolean):string { + var result:string = null; + for (let i = 0, len = valueSets.length; result === null && i < len; i++) { + result = this.valueSetReplace(valueSets[i], value, up); + } + return result; + } + + public valueSetReplace(valueSet:string[], value:string, up:boolean):string { + var idx = valueSet.indexOf(value); + if(idx >= 0) { + idx += up ? +1 : -1; + if(idx < 0) { + idx = valueSet.length - 1; + } else { + idx %= valueSet.length; + } + return valueSet[idx]; + } + return null; + } + +} + +export var ReplaceSupport: IReplaceSupportHelper = new ReplaceSupportHelperImpl(); + +function isFunction(something) { + return typeof something === 'function'; +} + +export interface IInplaceReplaceSupportCustomization { + textReplace?: (value: string, up: boolean) => string; + navigateValueSetFallback?: (resource: URI, range: IRange, up: boolean) => TPromise; +} + +export class AbstractInplaceReplaceSupport implements IInplaceReplaceSupport { + + private defaults: { + textReplace: boolean; + navigateValueSetFallback: boolean; + }; + private customization:IInplaceReplaceSupportCustomization; + + constructor(customization: IInplaceReplaceSupportCustomization = null) { + this.defaults = { + textReplace: !customization || !isFunction(customization.textReplace), + navigateValueSetFallback: !customization || !isFunction(customization.navigateValueSetFallback) + }; + this.customization = customization; + } + + public navigateValueSet(resource:URI, range:IRange, up:boolean):TPromise { + var result = this.doNavigateValueSet(resource, range, up, true); + if (result && result.value && result.range) { + return TPromise.as(result); + } + if (this.defaults.navigateValueSetFallback) { + return TPromise.as(null); + } + return this.customization.navigateValueSetFallback(resource, range, up); + } + + private doNavigateValueSet(resource:URI, range:IRange, up:boolean, selection:boolean):IInplaceReplaceSupportResult { + + var model = this.getModel(resource), + result:IInplaceReplaceSupportResult = { range:null, value: null }, + text:string; + + if(selection) { + // Replace selection + if(range.startColumn === range.endColumn) { + range.endColumn += 1; + } + text = model.getValueInRange(range); + result.range = range; + } else { + // Replace word + var position = { lineNumber: range.startLineNumber, column: range.startColumn }; + var wordPos = model.getWordAtPosition(position); + + if(!wordPos || wordPos.startColumn === -1) { + return null; + } + text = wordPos.word; + result.range = { startLineNumber : range.startLineNumber, endLineNumber: range.endLineNumber, startColumn: wordPos.startColumn, endColumn: wordPos.endColumn }; + } + + // Try to replace numbers or text + var numberResult = this.numberReplace(text, up); + if(numberResult !== null) { + result.value = numberResult; + } else { + var textResult = this.textReplace(text, up); + if(textResult !== null) { + result.value = textResult; + } else if(selection) { + return this.doNavigateValueSet(resource, range, up, false); + } + } + return result; + } + + private numberReplace(value:string, up:boolean):string { + var precision = Math.pow(10, value.length - (value.lastIndexOf('.') + 1)), + n1 = Number(value), + n2 = parseFloat(value); + + if(!isNaN(n1) && !isNaN(n2) && n1 === n2) { + + if(n1 === 0 && !up) { + return null; // don't do negative +// } else if(n1 === 9 && up) { +// return null; // don't insert 10 into a number + } else { + n1 = Math.floor(n1 * precision); + n1 += up ? precision : -precision; + return String(n1 / precision); + } + } + + return null; + } + + private _defaultValueSet: string[][] = [ + ['true', 'false'], + ['True', 'False'], + ['Private', 'Public', 'Friend', 'ReadOnly', 'Partial', 'Protected', 'WriteOnly'], + ['public', 'protected', 'private'], + ]; + + private textReplace(value:string, up:boolean):string { + if (this.defaults.textReplace) { + return ReplaceSupport.valueSetsReplace(this._defaultValueSet, value, up); + } + return this.customization.textReplace(value, up) + || ReplaceSupport.valueSetsReplace(this._defaultValueSet, value, up); + } + + protected getModel(resource:URI): ITokenizedModel { + throw new Error('Not implemented'); + } +} + +export class WorkerInplaceReplaceSupport extends AbstractInplaceReplaceSupport { + + private resourceService: IResourceService; + + constructor(resourceService: IResourceService, customization: IInplaceReplaceSupportCustomization = null) { + super(customization); + this.resourceService = resourceService; + } + + protected getModel(resource:URI): ITokenizedModel { + return this.resourceService.get(resource); + } +} diff --git a/src/vs/editor/common/modes/supports/onEnter.ts b/src/vs/editor/common/modes/supports/onEnter.ts index b4cab652153..f49ae64d146 100644 --- a/src/vs/editor/common/modes/supports/onEnter.ts +++ b/src/vs/editor/common/modes/supports/onEnter.ts @@ -5,9 +5,9 @@ 'use strict'; import {handleEvent} from 'vs/editor/common/modes/supports'; -import {IEnterAction, IndentAction, IOnEnterSupport, ILineContext, IMode} from 'vs/editor/common/modes'; -import EditorCommon = require('vs/editor/common/editorCommon'); -import Errors = require('vs/base/common/errors'); +import {IEnterAction, IndentAction, IRichEditOnEnter, ILineContext, IMode} from 'vs/editor/common/modes'; +import {ITokenizedModel, ITextModel, IPosition} from 'vs/editor/common/editorCommon'; +import {onUnexpectedError} from 'vs/base/common/errors'; import Strings = require('vs/base/common/strings'); import {Position} from 'vs/editor/common/core/position'; @@ -40,7 +40,7 @@ interface IProcessedBracketPair extends IBracketPair { closeRegExp: RegExp; } -export class OnEnterSupport implements IOnEnterSupport { +export class OnEnterSupport implements IRichEditOnEnter { private static _INDENT: IEnterAction = { indentAction: IndentAction.Indent }; private static _INDENT_OUTDENT: IEnterAction = { indentAction: IndentAction.IndentOutdent }; @@ -72,21 +72,21 @@ export class OnEnterSupport implements IOnEnterSupport { this._indentationRules = opts.indentationRules; } - public onEnter(model:EditorCommon.ITokenizedModel, position: EditorCommon.IPosition): IEnterAction { + public onEnter(model:ITokenizedModel, position: IPosition): IEnterAction { var context = model.getLineContext(position.lineNumber); return handleEvent(context, position.column - 1, (nestedMode:IMode, context:ILineContext, offset:number) => { if (this._modeId === nestedMode.getId()) { return this._onEnter(model, position); - } else if (nestedMode.onEnterSupport) { - return nestedMode.onEnterSupport.onEnter(model, position); + } else if (nestedMode.richEditSupport && nestedMode.richEditSupport.onEnter) { + return nestedMode.richEditSupport.onEnter.onEnter(model, position); } else { return null; } }); } - private _onEnter(model:EditorCommon.ITextModel, position: EditorCommon.IPosition): IEnterAction { + private _onEnter(model:ITextModel, position: IPosition): IEnterAction { let lineText = model.getLineContent(position.lineNumber); let beforeEnterText = lineText.substr(0, position.column - 1); let afterEnterText = lineText.substr(position.column - 1); @@ -175,40 +175,28 @@ export class OnEnterSupport implements IOnEnterSupport { try { return new RegExp(def); } catch(err) { - Errors.onUnexpectedError(err); + onUnexpectedError(err); return null; } } } -export function getRawEnterActionAtPosition(model:EditorCommon.ITokenizedModel, lineNumber:number, column:number): IEnterAction { - let enterAction:IEnterAction; +export function getRawEnterActionAtPosition(model:ITokenizedModel, lineNumber:number, column:number): IEnterAction { + let result:IEnterAction; + let richEditSupport = model.getMode().richEditSupport; - if (model.getMode().onEnterSupport) { + if (richEditSupport && richEditSupport.onEnter) { try { - enterAction = model.getMode().onEnterSupport.onEnter(model, new Position(lineNumber, column)); + result = richEditSupport.onEnter.onEnter(model, new Position(lineNumber, column)); } catch (e) { - Errors.onUnexpectedError(e); + onUnexpectedError(e); } } - if (!enterAction) { - if (model.getMode().electricCharacterSupport) { - let lineContext = model.getLineContext(lineNumber); - try { - enterAction = model.getMode().electricCharacterSupport.onEnter(lineContext, column - 1); - } catch(e) { - Errors.onUnexpectedError(e); - } - } - } else { - // console.log('USING NEW INDENTATION LOGIC!'); - } - - return enterAction; + return result; } -export function getEnterActionAtPosition(model:EditorCommon.ITokenizedModel, lineNumber:number, column:number): { enterAction: IEnterAction; indentation: string; } { +export function getEnterActionAtPosition(model:ITokenizedModel, lineNumber:number, column:number): { enterAction: IEnterAction; indentation: string; } { let lineText = model.getLineContent(lineNumber); let indentation = Strings.getLeadingWhitespace(lineText); if (indentation.length > column - 1) { diff --git a/src/vs/editor/common/modes/supports/parameterHintsSupport.ts b/src/vs/editor/common/modes/supports/parameterHintsSupport.ts new file mode 100644 index 00000000000..fb36837f6e9 --- /dev/null +++ b/src/vs/editor/common/modes/supports/parameterHintsSupport.ts @@ -0,0 +1,55 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +import {TPromise} from 'vs/base/common/winjs.base'; +import {IParameterHints, IParameterHintsSupport, ILineContext, IMode} from 'vs/editor/common/modes'; +import {IPosition} from 'vs/editor/common/editorCommon'; +import URI from 'vs/base/common/uri'; +import {handleEvent, isLineToken} from 'vs/editor/common/modes/supports'; + +export interface IParameterHintsContribution { + triggerCharacters: string[]; + excludeTokens: string[]; + getParameterHints: (resource: URI, position: IPosition) => TPromise; +} + +export class ParameterHintsSupport implements IParameterHintsSupport { + + private _modeId: string; + private contribution: IParameterHintsContribution; + + constructor(modeId: string, contribution: IParameterHintsContribution) { + this._modeId = modeId; + this.contribution = contribution; + } + + public getParameterHintsTriggerCharacters(): string[] + { + return this.contribution.triggerCharacters; + } + + public shouldTriggerParameterHints(context: ILineContext, offset: number): boolean + { + return handleEvent(context, offset, (nestedMode:IMode, context:ILineContext, offset:number) => { + if (this._modeId === nestedMode.getId()) { + if (!Array.isArray(this.contribution.excludeTokens)) { + return true; + } + if (this.contribution.excludeTokens.length === 1 && this.contribution.excludeTokens[0] === '*') { + return false; + } + return !isLineToken(context, offset-1, this.contribution.excludeTokens); + } else if (nestedMode.parameterHintsSupport) { + return nestedMode.parameterHintsSupport.shouldTriggerParameterHints(context, offset); + } else { + return false; + } + }); + } + public getParameterHints(resource: URI, position: IPosition): TPromise { + return this.contribution.getParameterHints(resource, position); + } +} diff --git a/src/vs/editor/common/modes/supports/referenceSupport.ts b/src/vs/editor/common/modes/supports/referenceSupport.ts new file mode 100644 index 00000000000..b27b8381671 --- /dev/null +++ b/src/vs/editor/common/modes/supports/referenceSupport.ts @@ -0,0 +1,48 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +import {TPromise} from 'vs/base/common/winjs.base'; +import {IReference, IReferenceSupport, ILineContext, IMode} from 'vs/editor/common/modes'; +import {IPosition} from 'vs/editor/common/editorCommon'; +import URI from 'vs/base/common/uri'; +import {handleEvent, isLineToken} from 'vs/editor/common/modes/supports'; + +export interface IReferenceContribution { + tokens: string[]; + findReferences: (resource: URI, position: IPosition, includeDeclaration: boolean) => TPromise; +} + +export class ReferenceSupport implements IReferenceSupport { + + private _modeId: string; + private contribution: IReferenceContribution; + + /** + * Provide the token type postfixes for the tokens where a reference can be found in the 'tokens' argument. + */ + constructor(modeId: string, contribution: IReferenceContribution) { + this._modeId = modeId; + this.contribution = contribution; + } + + public canFindReferences(context: ILineContext, offset:number):boolean { + return handleEvent(context, offset, (nestedMode:IMode, context:ILineContext, offset:number) => { + if (this._modeId === nestedMode.getId()) { + return (!Array.isArray(this.contribution.tokens) || + this.contribution.tokens.length < 1 || + isLineToken(context, offset, this.contribution.tokens)); + } else if (nestedMode.referenceSupport) { + return nestedMode.referenceSupport.canFindReferences(context, offset); + } else { + return false; + } + }); + } + + public findReferences(resource: URI, position: IPosition, includeDeclaration: boolean): TPromise { + return this.contribution.findReferences(resource, position, includeDeclaration); + } +} diff --git a/src/vs/editor/common/modes/supports/richEditSupport.ts b/src/vs/editor/common/modes/supports/richEditSupport.ts new file mode 100644 index 00000000000..3a0dd5096de --- /dev/null +++ b/src/vs/editor/common/modes/supports/richEditSupport.ts @@ -0,0 +1,103 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +import * as Modes from 'vs/editor/common/modes'; +import {OnEnterSupport, IOnEnterSupportOptions, IIndentationRules, IOnEnterRegExpRules} from 'vs/editor/common/modes/supports/onEnter'; +import {CharacterPairSupport} from 'vs/editor/common/modes/supports/characterPair'; +import {BracketElectricCharacterSupport, IBracketElectricCharacterContribution} from 'vs/editor/common/modes/supports/electricCharacter'; +import {ICharacterPairContribution} from 'vs/editor/common/modes/supports/characterPair'; +import {NullMode} from 'vs/editor/common/modes/nullMode'; + +export type CharacterPair = [string, string]; + +export interface CommentRule { + lineComment?: string; + blockComment?: CharacterPair; +} + +export interface IRichEditConfiguration { + comments?: CommentRule; + brackets?: CharacterPair[]; + wordPattern?: RegExp; + indentationRules?: IIndentationRules; + onEnterRules?: IOnEnterRegExpRules[]; + __electricCharacterSupport?: IBracketElectricCharacterContribution; + __characterPairSupport?: ICharacterPairContribution; +} + +export class RichEditSupport implements Modes.IRichEditSupport { + + public electricCharacter: BracketElectricCharacterSupport; + public comments: Modes.ICommentsConfiguration; + public characterPair: Modes.IRichEditCharacterPair; + public wordDefinition: RegExp; + public onEnter: Modes.IRichEditOnEnter; + public brackets: Modes.IRichEditBrackets; + + constructor(modeId:string, conf:IRichEditConfiguration) { + + this._handleOnEnter(modeId, conf); + + this._handleComments(modeId, conf); + + if (conf.__characterPairSupport) { + this.characterPair = new CharacterPairSupport(modeId, conf.__characterPairSupport); + } + + if (conf.__electricCharacterSupport) { + this.electricCharacter = new BracketElectricCharacterSupport(modeId, conf.__electricCharacterSupport); + this.brackets = this.electricCharacter.getRichEditBrackets(); + } + + this.wordDefinition = conf.wordPattern || NullMode.DEFAULT_WORD_REGEXP; + } + + private _handleOnEnter(modeId:string, conf:IRichEditConfiguration): void { + // on enter + let onEnter: IOnEnterSupportOptions = {}; + let empty = true; + let {brackets, indentationRules, onEnterRules} = conf; + + if (brackets) { + empty = false; + onEnter.brackets = brackets.map(pair => { + let [open, close] = pair; + return { open, close }; + }); + } + if (indentationRules) { + empty = false; + onEnter.indentationRules = indentationRules; + } + if (onEnterRules) { + empty = false; + onEnter.regExpRules = onEnterRules; + } + + if (!empty) { + this.onEnter = new OnEnterSupport(modeId, onEnter); + } + } + + private _handleComments(modeId:string, conf:IRichEditConfiguration): void { + let commentRule = conf.comments; + + // comment configuration + if (commentRule) { + this.comments = {}; + + if (commentRule.lineComment) { + this.comments.lineCommentToken = commentRule.lineComment; + } + if (commentRule.blockComment) { + let [blockStart, blockEnd] = commentRule.blockComment; + this.comments.blockCommentStartToken = blockStart; + this.comments.blockCommentEndToken = blockEnd; + } + } + } + +} diff --git a/src/vs/editor/common/modes/supports/suggestSupport.ts b/src/vs/editor/common/modes/supports/suggestSupport.ts new file mode 100644 index 00000000000..482cd89109a --- /dev/null +++ b/src/vs/editor/common/modes/supports/suggestSupport.ts @@ -0,0 +1,91 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +import {TPromise} from 'vs/base/common/winjs.base'; +import {DefaultFilter} from 'vs/editor/common/modes/modesFilters'; +import {ISuggestResult, ISuggestSupport, ISuggestion, ILineContext, IMode, ISuggestionFilter} from 'vs/editor/common/modes'; +import {IPosition} from 'vs/editor/common/editorCommon'; +import URI from 'vs/base/common/uri'; +import {handleEvent, isLineToken} from 'vs/editor/common/modes/supports'; + +export interface ISuggestContribution { + triggerCharacters: string[]; + disableAutoTrigger?: boolean; + excludeTokens: string[]; + suggest: (resource: URI, position: IPosition) => TPromise; + getSuggestionDetails? : (resource:URI, position:IPosition, suggestion:ISuggestion) => TPromise; +} + +export class SuggestSupport implements ISuggestSupport { + + private _modeId: string; + private contribution: ISuggestContribution; + + public suggest : (resource:URI, position:IPosition) => TPromise; + public getSuggestionDetails : (resource:URI, position:IPosition, suggestion:ISuggestion) => TPromise; + + constructor(modeId: string, contribution : ISuggestContribution){ + this._modeId = modeId; + this.contribution = contribution; + this.suggest = (resource, position) => contribution.suggest(resource, position); + + if (typeof contribution.getSuggestionDetails === 'function') { + this.getSuggestionDetails = (resource, position, suggestion) => contribution.getSuggestionDetails(resource, position, suggestion); + } + } + + shouldAutotriggerSuggest(context: ILineContext, offset: number, triggeredByCharacter: string): boolean { + return handleEvent(context, offset, (nestedMode:IMode, context:ILineContext, offset:number) => { + if (this._modeId === nestedMode.getId()) { + if (this.contribution.disableAutoTrigger) { + return false; + } + if (!Array.isArray(this.contribution.excludeTokens)) { + return true; + } + if (this.contribution.excludeTokens.length === 1 && this.contribution.excludeTokens[0] === '*') { + return false; + } + return !isLineToken(context, offset-1, this.contribution.excludeTokens, true); + } else if (nestedMode.suggestSupport) { + return nestedMode.suggestSupport.shouldAutotriggerSuggest(context, offset, triggeredByCharacter); + } else { + return false; + } + }); + } + + public getFilter(): ISuggestionFilter { + return DefaultFilter; + } + + public getTriggerCharacters(): string[] { + return this.contribution.triggerCharacters; + } + + public shouldShowEmptySuggestionList(): boolean { + return true; + } +} + +export interface IComposableSuggestContribution extends ISuggestContribution { + composeSuggest(resource:URI, position:IPosition, superSuggestions:ISuggestResult[]): TPromise; +} + +export class ComposableSuggestSupport extends SuggestSupport { + + constructor(modeId: string, contribution: IComposableSuggestContribution) { + super(modeId, contribution); + + this.suggest = (resource, position) => { + return ( + contribution.suggest(resource, position) + .then(superSuggestions => contribution.composeSuggest(resource, position, superSuggestions)) + ); + }; + } + +} diff --git a/src/vs/editor/common/modes/supports/tokenizationSupport.ts b/src/vs/editor/common/modes/supports/tokenizationSupport.ts new file mode 100644 index 00000000000..ad38d3e8797 --- /dev/null +++ b/src/vs/editor/common/modes/supports/tokenizationSupport.ts @@ -0,0 +1,351 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +import {TPromise} from 'vs/base/common/winjs.base'; +import * as Modes from 'vs/editor/common/modes'; +import {IDisposable} from 'vs/base/common/lifecycle'; +import {NullMode, NullState, nullTokenize} from 'vs/editor/common/modes/nullMode'; +import {LineStream} from 'vs/editor/common/modes/lineStream'; +import {Token} from 'vs/editor/common/modes/supports'; + +export interface ILeavingNestedModeData { + /** + * The part of the line that will be tokenized by the nested mode + */ + nestedModeBuffer: string; + + /** + * The part of the line that will be tokenized by the parent mode when it continues after the nested mode + */ + bufferAfterNestedMode: string; + + /** + * The state that will be used for continuing tokenization by the parent mode after the nested mode + */ + stateAfterNestedMode: Modes.IState; +} + +export interface IEnteringNestedModeData { + mode:Modes.IMode; + missingModePromise:TPromise; +} + +export interface ITokenizationCustomization { + + getInitialState():Modes.IState; + + enterNestedMode?: (state:Modes.IState) => boolean; + + getNestedMode?: (state:Modes.IState) => IEnteringNestedModeData; + + getNestedModeInitialState?: (myState:Modes.IState) => { state:Modes.IState; missingModePromise:TPromise; }; + + /** + * Return null if the line does not leave the nested mode + */ + getLeavingNestedModeData?: (line:string, state:Modes.IState) => ILeavingNestedModeData; + + /** + * Callback for when leaving a nested mode and returning to the outer mode. + * @param myStateAfterNestedMode The outer mode's state that will begin to tokenize + * @param lastNestedModeState The nested mode's last state + */ + onReturningFromNestedMode?: (myStateAfterNestedMode:Modes.IState, lastNestedModeState:Modes.IState)=> void; +} + +function isFunction(something) { + return typeof something === 'function'; +} + +export class TokenizationSupport implements Modes.ITokenizationSupport, IDisposable { + + static MAX_EMBEDDED_LEVELS = 5; + + private customization:ITokenizationCustomization; + private defaults: { + enterNestedMode: boolean; + getNestedMode: boolean; + getNestedModeInitialState: boolean; + getLeavingNestedModeData: boolean; + onReturningFromNestedMode: boolean; + }; + + public shouldGenerateEmbeddedModels:boolean; + public supportsNestedModes:boolean; + + private _mode:Modes.IMode; + private _embeddedModesListeners: { [modeId:string]: IDisposable; }; + + constructor(mode:Modes.IMode, customization:ITokenizationCustomization, supportsNestedModes:boolean, shouldGenerateEmbeddedModels:boolean) { + this._mode = mode; + this.customization = customization; + this.supportsNestedModes = supportsNestedModes; + this._embeddedModesListeners = {}; + if (this.supportsNestedModes) { + if (!this._mode.registerSupport) { + throw new Error('Cannot be a mode with nested modes unless I can emit a tokenizationSupport changed event!'); + } + } + this.shouldGenerateEmbeddedModels = shouldGenerateEmbeddedModels; + this.defaults = { + enterNestedMode: !isFunction(customization.enterNestedMode), + getNestedMode: !isFunction(customization.getNestedMode), + getNestedModeInitialState: !isFunction(customization.getNestedModeInitialState), + getLeavingNestedModeData: !isFunction(customization.getLeavingNestedModeData), + onReturningFromNestedMode: !isFunction(customization.onReturningFromNestedMode) + }; + } + + public dispose() : void { + for (var listener in this._embeddedModesListeners) { + this._embeddedModesListeners[listener].dispose(); + delete this._embeddedModesListeners[listener]; + } + } + + public getInitialState(): Modes.IState { + return this.customization.getInitialState(); + } + + public tokenize(line:string, state:Modes.IState, deltaOffset:number = 0, stopAtOffset:number = deltaOffset + line.length):Modes.ILineTokens { + if (state.getMode() !== this._mode) { + return this._nestedTokenize(line, state, deltaOffset, stopAtOffset, [], []); + } else { + return this._myTokenize(line, state, deltaOffset, stopAtOffset, [], []); + } + } + + /** + * Precondition is: nestedModeState.getMode() !== this + * This means we are in a nested mode when parsing starts on this line. + */ + private _nestedTokenize(buffer:string, nestedModeState:Modes.IState, deltaOffset:number, stopAtOffset:number, prependTokens:Modes.IToken[], prependModeTransitions:Modes.IModeTransition[]):Modes.ILineTokens { + var myStateBeforeNestedMode = nestedModeState.getStateData(); + var leavingNestedModeData = this.getLeavingNestedModeData(buffer, myStateBeforeNestedMode); + + // Be sure to give every embedded mode the + // opportunity to leave nested mode. + // i.e. Don't go straight to the most nested mode + var stepOnceNestedState = nestedModeState; + while (stepOnceNestedState.getStateData() && stepOnceNestedState.getStateData().getMode() !== this._mode) { + stepOnceNestedState = stepOnceNestedState.getStateData(); + } + var nestedMode = stepOnceNestedState.getMode(); + + if (!leavingNestedModeData) { + // tokenization will not leave nested mode + var result:Modes.ILineTokens; + if (nestedMode.tokenizationSupport) { + result = nestedMode.tokenizationSupport.tokenize(buffer, nestedModeState, deltaOffset, stopAtOffset); + } else { + // The nested mode doesn't have tokenization support, + // unfortunatelly this means we have to fake it + result = nullTokenize(nestedMode, buffer, nestedModeState, deltaOffset); + } + result.tokens = prependTokens.concat(result.tokens); + result.modeTransitions = prependModeTransitions.concat(result.modeTransitions); + return result; + } + + var nestedModeBuffer = leavingNestedModeData.nestedModeBuffer; + if (nestedModeBuffer.length > 0) { + // Tokenize with the nested mode + var nestedModeLineTokens:Modes.ILineTokens; + if (nestedMode.tokenizationSupport) { + nestedModeLineTokens = nestedMode.tokenizationSupport.tokenize(nestedModeBuffer, nestedModeState, deltaOffset, stopAtOffset); + } else { + // The nested mode doesn't have tokenization support, + // unfortunatelly this means we have to fake it + nestedModeLineTokens = nullTokenize(nestedMode, nestedModeBuffer, nestedModeState, deltaOffset); + } + + // Save last state of nested mode + nestedModeState = nestedModeLineTokens.endState; + + // Prepend nested mode's result to our result + prependTokens = prependTokens.concat(nestedModeLineTokens.tokens); + prependModeTransitions = prependModeTransitions.concat(nestedModeLineTokens.modeTransitions); + } + + var bufferAfterNestedMode = leavingNestedModeData.bufferAfterNestedMode; + var myStateAfterNestedMode = leavingNestedModeData.stateAfterNestedMode; + myStateAfterNestedMode.setStateData(myStateBeforeNestedMode.getStateData()); + this.onReturningFromNestedMode(myStateAfterNestedMode, nestedModeState); + + return this._myTokenize(bufferAfterNestedMode, myStateAfterNestedMode, deltaOffset + nestedModeBuffer.length, stopAtOffset, prependTokens, prependModeTransitions); + } + + /** + * Precondition is: state.getMode() === this + * This means we are in the current mode when parsing starts on this line. + */ + private _myTokenize(buffer:string, myState:Modes.IState, deltaOffset:number, stopAtOffset:number, prependTokens:Modes.IToken[], prependModeTransitions:Modes.IModeTransition[]):Modes.ILineTokens { + var lineStream = new LineStream(buffer); + var tokenResult:Modes.ITokenizationResult, beforeTokenizeStreamPos:number; + var previousType:string = null; + var retokenize:TPromise = null; + + myState = myState.clone(); + if (prependModeTransitions.length <= 0 || prependModeTransitions[prependModeTransitions.length-1].mode !== this._mode) { + // Avoid transitioning to the same mode (this can happen in case of empty embedded modes) + prependModeTransitions.push({ + startIndex: deltaOffset, + mode: this._mode + }); + } + + var maxPos = Math.min(stopAtOffset - deltaOffset, buffer.length); + while (lineStream.pos() < maxPos) { + beforeTokenizeStreamPos = lineStream.pos(); + + do { + tokenResult = myState.tokenize(lineStream); + if (tokenResult === null || tokenResult === undefined || + ((tokenResult.type === undefined || tokenResult.type === null) && + (tokenResult.nextState === undefined || tokenResult.nextState === null))) { + throw new Error('Tokenizer must return a valid state'); + } + + if (tokenResult.nextState) { + tokenResult.nextState.setStateData(myState.getStateData()); + myState = tokenResult.nextState; + } + if (lineStream.pos() <= beforeTokenizeStreamPos) { + throw new Error('Stream did not advance while tokenizing. Mode id is ' + this._mode.getId() + ' (stuck at token type: "' + tokenResult.type + '", prepend tokens: "' + (prependTokens.map(t => t.type).join(',')) + '").'); + } + } while (!tokenResult.type && tokenResult.type !== ''); + + if (previousType !== tokenResult.type || tokenResult.bracket || previousType === null) { + prependTokens.push(new Token(beforeTokenizeStreamPos + deltaOffset, tokenResult.type)); + } + + previousType = tokenResult.type; + + if (this.supportsNestedModes && this.enterNestedMode(myState)) { + var currentEmbeddedLevels = this._getEmbeddedLevel(myState); + if (currentEmbeddedLevels < TokenizationSupport.MAX_EMBEDDED_LEVELS) { + var nestedModeState = this.getNestedModeInitialState(myState); + + // Re-emit tokenizationSupport change events from all modes that I ever embedded + var embeddedMode = nestedModeState.state.getMode(); + if (typeof embeddedMode.addSupportChangedListener === 'function' && !this._embeddedModesListeners.hasOwnProperty(embeddedMode.getId())) { + var emitting = false; + this._embeddedModesListeners[embeddedMode.getId()] = embeddedMode.addSupportChangedListener((e) => { + if (emitting) { + return; + } + if (e.tokenizationSupport) { + emitting = true; + this._mode.registerSupport('tokenizationSupport', (mode) => { + return mode.tokenizationSupport; + }); + emitting = false; + } + }); + } + + + if (!lineStream.eos()) { + // There is content from the embedded mode + var restOfBuffer = buffer.substr(lineStream.pos()); + var result = this._nestedTokenize(restOfBuffer, nestedModeState.state, deltaOffset + lineStream.pos(), stopAtOffset, prependTokens, prependModeTransitions); + result.retokenize = result.retokenize || nestedModeState.missingModePromise; + return result; + } else { + // Transition to the nested mode state + myState = nestedModeState.state; + retokenize = nestedModeState.missingModePromise; + } + } + } + } + + return { + tokens: prependTokens, + actualStopOffset: lineStream.pos() + deltaOffset, + modeTransitions: prependModeTransitions, + endState: myState, + retokenize: retokenize + }; + } + + private _getEmbeddedLevel(state:Modes.IState): number { + var result = -1; + while(state) { + result++; + state = state.getStateData(); + } + return result; + } + + private enterNestedMode(state:Modes.IState): boolean { + if (this.defaults.enterNestedMode) { + return false; + } + return this.customization.enterNestedMode(state); + + } + + private getNestedMode(state:Modes.IState): IEnteringNestedModeData { + if (this.defaults.getNestedMode) { + return null; + } + return this.customization.getNestedMode(state); + } + + private static _validatedNestedMode(input:IEnteringNestedModeData): IEnteringNestedModeData { + var mode: Modes.IMode = new NullMode(), + missingModePromise: TPromise = null; + + if (input && input.mode) { + mode = input.mode; + } + if (input && input.missingModePromise) { + missingModePromise = input.missingModePromise; + } + + return { + mode: mode, + missingModePromise: missingModePromise + }; + } + + private getNestedModeInitialState(state:Modes.IState): { state:Modes.IState; missingModePromise:TPromise; } { + if (this.defaults.getNestedModeInitialState) { + var nestedMode = TokenizationSupport._validatedNestedMode(this.getNestedMode(state)); + var missingModePromise = nestedMode.missingModePromise; + var nestedModeState: Modes.IState; + + if (nestedMode.mode.tokenizationSupport) { + nestedModeState = nestedMode.mode.tokenizationSupport.getInitialState(); + } else { + nestedModeState = new NullState(nestedMode.mode, null); + } + + nestedModeState.setStateData(state); + + return { + state: nestedModeState, + missingModePromise: missingModePromise + }; + } + return this.customization.getNestedModeInitialState(state); + } + + private getLeavingNestedModeData(line:string, state:Modes.IState): ILeavingNestedModeData { + if (this.defaults.getLeavingNestedModeData) { + return null; + } + return this.customization.getLeavingNestedModeData(line, state); + } + + private onReturningFromNestedMode(myStateAfterNestedMode:Modes.IState, lastNestedModeState:Modes.IState): void { + if (this.defaults.onReturningFromNestedMode) { + return null; + } + return this.customization.onReturningFromNestedMode(myStateAfterNestedMode, lastNestedModeState); + } +} diff --git a/src/vs/editor/common/services/modeService.ts b/src/vs/editor/common/services/modeService.ts index 517e88b999b..69942567e6f 100644 --- a/src/vs/editor/common/services/modeService.ts +++ b/src/vs/editor/common/services/modeService.ts @@ -11,6 +11,11 @@ import Supports = require ('vs/editor/common/modes/supports'); import MonarchTypes = require('vs/editor/common/modes/monarch/monarchTypes'); import {IOnEnterSupportOptions} from 'vs/editor/common/modes/supports/onEnter'; import {IDisposable} from 'vs/base/common/lifecycle'; +import {IRichEditConfiguration} from 'vs/editor/common/modes/supports/richEditSupport'; +import {IDeclarationContribution} from 'vs/editor/common/modes/supports/declarationSupport'; +import {IReferenceContribution} from 'vs/editor/common/modes/supports/referenceSupport'; +import {IParameterHintsContribution} from 'vs/editor/common/modes/supports/parameterHintsSupport'; +import {ISuggestContribution} from 'vs/editor/common/modes/supports/suggestSupport'; export var IModeService = createDecorator('modeService'); @@ -44,24 +49,20 @@ export interface IModeService { getOrCreateModeByLanguageName(languageName: string): TPromise; getOrCreateModeByFilenameOrFirstLine(filename: string, firstLine?:string): TPromise; - registerDeclarativeCharacterPairSupport(modeId: string, support: Modes.ICharacterPairContribution): IDisposable; registerCodeLensSupport(modeId: string, support: Modes.ICodeLensSupport): IDisposable; - registerDeclarativeCommentsSupport(modeId: string, support: Supports.ICommentsSupportContribution): IDisposable; - registerDeclarativeDeclarationSupport(modeId: string, contribution: Supports.IDeclarationContribution): IDisposable; - registerDeclarativeElectricCharacterSupport(modeId: string, support: Supports.IBracketElectricCharacterContribution): IDisposable; + registerDeclarativeDeclarationSupport(modeId: string, contribution: IDeclarationContribution): IDisposable; registerExtraInfoSupport(modeId: string, support: Modes.IExtraInfoSupport): IDisposable; registerFormattingSupport(modeId: string, support: Modes.IFormattingSupport): IDisposable; registerInplaceReplaceSupport(modeId: string, support: Modes.IInplaceReplaceSupport): IDisposable; registerOccurrencesSupport(modeId: string, support: Modes.IOccurrencesSupport): IDisposable; registerOutlineSupport(modeId: string, support: Modes.IOutlineSupport): IDisposable; - registerDeclarativeParameterHintsSupport(modeId: string, support: Modes.IParameterHintsContribution): IDisposable; + registerDeclarativeParameterHintsSupport(modeId: string, support: IParameterHintsContribution): IDisposable; registerQuickFixSupport(modeId: string, support: Modes.IQuickFixSupport): IDisposable; - registerDeclarativeReferenceSupport(modeId: string, contribution: Supports.IReferenceContribution): IDisposable; + registerDeclarativeReferenceSupport(modeId: string, contribution: IReferenceContribution): IDisposable; registerRenameSupport(modeId: string, support: Modes.IRenameSupport): IDisposable; - registerDeclarativeSuggestSupport(modeId: string, declaration: Supports.ISuggestContribution): IDisposable; + registerDeclarativeSuggestSupport(modeId: string, declaration: ISuggestContribution): IDisposable; registerTokenizationSupport(modeId: string, callback: (mode: Modes.IMode) => Modes.ITokenizationSupport): IDisposable; - registerDeclarativeTokenTypeClassificationSupport(modeId: string, support: Supports.ITokenTypeClassificationSupportContribution): IDisposable; - registerDeclarativeOnEnterSupport(modeId: string, support: IOnEnterSupportOptions): IDisposable; + registerRichEditSupport(modeId: string, support: IRichEditConfiguration): IDisposable; registerMonarchDefinition(modeId:string, language:MonarchTypes.ILanguage): IDisposable; } diff --git a/src/vs/editor/common/services/modeServiceImpl.ts b/src/vs/editor/common/services/modeServiceImpl.ts index 74bcebb7c4d..29e7cac8840 100644 --- a/src/vs/editor/common/services/modeServiceImpl.ts +++ b/src/vs/editor/common/services/modeServiceImpl.ts @@ -25,6 +25,11 @@ import MonarchCommonTypes = require('vs/editor/common/modes/monarch/monarchCommo import {OnEnterSupport, IOnEnterSupportOptions} from 'vs/editor/common/modes/supports/onEnter'; import {IDisposable, combinedDispose, empty as EmptyDisposable} from 'vs/base/common/lifecycle'; import {createAsyncDescriptor0, createAsyncDescriptor1} from 'vs/platform/instantiation/common/descriptors'; +import {RichEditSupport, IRichEditConfiguration} from 'vs/editor/common/modes/supports/richEditSupport'; +import {DeclarationSupport, IDeclarationContribution} from 'vs/editor/common/modes/supports/declarationSupport'; +import {ReferenceSupport, IReferenceContribution} from 'vs/editor/common/modes/supports/referenceSupport'; +import {ParameterHintsSupport, IParameterHintsContribution} from 'vs/editor/common/modes/supports/parameterHintsSupport'; +import {SuggestSupport, ComposableSuggestSupport, ISuggestContribution} from 'vs/editor/common/modes/supports/suggestSupport'; interface IModeConfigurationMap { [modeId: string]: any; } @@ -290,15 +295,7 @@ export class ModeServiceImpl implements IModeService { return createTokenizationSupport(this, mode, lexer); }), - this.registerDeclarativeCommentsSupport(modeId, MonarchDefinition.createCommentsSupport(lexer)), - - this.registerDeclarativeElectricCharacterSupport(modeId, MonarchDefinition.createBracketElectricCharacterContribution(lexer)), - - this.registerDeclarativeTokenTypeClassificationSupport(modeId, MonarchDefinition.createTokenTypeClassificationSupportContribution(lexer)), - - this.registerDeclarativeCharacterPairSupport(modeId, MonarchDefinition.createCharacterPairContribution(lexer)), - - this.registerDeclarativeOnEnterSupport(modeId, MonarchDefinition.createOnEnterSupportOptions(lexer)) + this.registerRichEditSupport(modeId, MonarchDefinition.createRichEditSupport(lexer)) ); } @@ -307,24 +304,16 @@ export class ModeServiceImpl implements IModeService { return this.doRegisterMonarchDefinition(modeId, lexer); } - public registerDeclarativeCharacterPairSupport(modeId: string, support: Modes.ICharacterPairContribution): IDisposable { - return this.registerModeSupport(modeId, 'characterPairSupport', (mode) => new Supports.CharacterPairSupport(mode, support)); - } - public registerCodeLensSupport(modeId: string, support: Modes.ICodeLensSupport): IDisposable { return this.registerModeSupport(modeId, 'codeLensSupport', (mode) => support); } - public registerDeclarativeCommentsSupport(modeId: string, support: Supports.ICommentsSupportContribution): IDisposable { - return this.registerModeSupport(modeId, 'commentsSupport', (mode) => new Supports.CommentsSupport(support)); + public registerRichEditSupport(modeId: string, support: IRichEditConfiguration): IDisposable { + return this.registerModeSupport(modeId, 'richEditSupport', (mode) => new RichEditSupport(modeId, support)); } - public registerDeclarativeDeclarationSupport(modeId: string, contribution: Supports.IDeclarationContribution): IDisposable { - return this.registerModeSupport(modeId, 'declarationSupport', (mode) => new Supports.DeclarationSupport(mode, contribution)); - } - - public registerDeclarativeElectricCharacterSupport(modeId: string, support: Supports.IBracketElectricCharacterContribution): IDisposable { - return this.registerModeSupport(modeId, 'electricCharacterSupport', (mode) => new Supports.BracketElectricCharacterSupport(mode, support)); + public registerDeclarativeDeclarationSupport(modeId: string, contribution: IDeclarationContribution): IDisposable { + return this.registerModeSupport(modeId, 'declarationSupport', (mode) => new DeclarationSupport(modeId, contribution)); } public registerExtraInfoSupport(modeId: string, support: Modes.IExtraInfoSupport): IDisposable { @@ -347,37 +336,29 @@ export class ModeServiceImpl implements IModeService { return this.registerModeSupport(modeId, 'outlineSupport', (mode) => support); } - public registerDeclarativeParameterHintsSupport(modeId: string, support: Modes.IParameterHintsContribution): IDisposable { - return this.registerModeSupport(modeId, 'parameterHintsSupport', (mode) => new Supports.ParameterHintsSupport(mode, support)); + public registerDeclarativeParameterHintsSupport(modeId: string, support: IParameterHintsContribution): IDisposable { + return this.registerModeSupport(modeId, 'parameterHintsSupport', (mode) => new ParameterHintsSupport(modeId, support)); } public registerQuickFixSupport(modeId: string, support: Modes.IQuickFixSupport): IDisposable { return this.registerModeSupport(modeId, 'quickFixSupport', (mode) => support); } - public registerDeclarativeReferenceSupport(modeId: string, contribution: Supports.IReferenceContribution): IDisposable { - return this.registerModeSupport(modeId, 'referenceSupport', (mode) => new Supports.ReferenceSupport(mode, contribution)); + public registerDeclarativeReferenceSupport(modeId: string, contribution: IReferenceContribution): IDisposable { + return this.registerModeSupport(modeId, 'referenceSupport', (mode) => new ReferenceSupport(modeId, contribution)); } public registerRenameSupport(modeId: string, support: Modes.IRenameSupport): IDisposable { return this.registerModeSupport(modeId, 'renameSupport', (mode) => support); } - public registerDeclarativeSuggestSupport(modeId: string, declaration: Supports.ISuggestContribution): IDisposable { - return this.registerModeSupport(modeId, 'suggestSupport', (mode) => new Supports.SuggestSupport(mode, declaration)); + public registerDeclarativeSuggestSupport(modeId: string, declaration: ISuggestContribution): IDisposable { + return this.registerModeSupport(modeId, 'suggestSupport', (mode) => new SuggestSupport(modeId, declaration)); } public registerTokenizationSupport(modeId: string, callback: (mode: Modes.IMode) => Modes.ITokenizationSupport): IDisposable { return this.registerModeSupport(modeId, 'tokenizationSupport', callback); } - - public registerDeclarativeTokenTypeClassificationSupport(modeId: string, support: Supports.ITokenTypeClassificationSupportContribution): IDisposable { - return this.registerModeSupport(modeId, 'tokenTypeClassificationSupport', (mode) => new Supports.TokenTypeClassificationSupport(support)); - } - - public registerDeclarativeOnEnterSupport(modeId: string, opts: IOnEnterSupportOptions): IDisposable { - return this.registerModeSupport(modeId, 'onEnterSupport', (mode) => new OnEnterSupport(modeId, opts)); - } } export class MainThreadModeServiceImpl extends ModeServiceImpl { @@ -424,7 +405,7 @@ export class MainThreadModeServiceImpl extends ModeServiceImpl { super.doRegisterMonarchDefinition(modeId, lexer), this.registerModeSupport(modeId, 'suggestSupport', (mode) => { - return new Supports.ComposableSuggestSupport(mode, MonarchDefinition.createSuggestSupport(this._modelService, mode, lexer)); + return new ComposableSuggestSupport(modeId, MonarchDefinition.createSuggestSupport(this._modelService, mode, lexer)); }) ); } diff --git a/src/vs/editor/contrib/comment/common/blockCommentCommand.ts b/src/vs/editor/contrib/comment/common/blockCommentCommand.ts index a8829b05abb..9811d51470b 100644 --- a/src/vs/editor/contrib/comment/common/blockCommentCommand.ts +++ b/src/vs/editor/contrib/comment/common/blockCommentCommand.ts @@ -121,13 +121,8 @@ export class BlockCommentCommand implements EditorCommon.ICommand { var endLineNumber = this._selection.endLineNumber; var endColumn = this._selection.endColumn; - var commentsSupport = model.getModeAtPosition(startLineNumber, startColumn).commentsSupport; - if (!commentsSupport) { - // Mode does not support comments - return; - } - - var config = commentsSupport.getCommentsConfiguration(); + let richEditSupport = model.getModeAtPosition(startLineNumber, startColumn).richEditSupport; + let config = richEditSupport ? richEditSupport.comments : null; if (!config || !config.blockCommentStartToken || !config.blockCommentEndToken) { // Mode does not support block comments return; diff --git a/src/vs/editor/contrib/comment/common/lineCommentCommand.ts b/src/vs/editor/contrib/comment/common/lineCommentCommand.ts index ba4d57a657f..e090516f561 100644 --- a/src/vs/editor/contrib/comment/common/lineCommentCommand.ts +++ b/src/vs/editor/contrib/comment/common/lineCommentCommand.ts @@ -81,9 +81,9 @@ export class LineCommentCommand implements EditorCommon.ICommand { if (seenModes[modeId]) { commentStr = seenModes[modeId]; } else { - config = (mode.commentsSupport ? mode.commentsSupport.getCommentsConfiguration() : null); - commentStr = (config && config.lineCommentTokens && config.lineCommentTokens.length > 0 ? config.lineCommentTokens[0] : null); - if (commentStr === null || commentStr.length === 0) { + config = (mode.richEditSupport ? mode.richEditSupport.comments : null); + commentStr = (config ? config.lineCommentToken : null); + if (!commentStr) { // Mode does not support line comments return null; } @@ -273,13 +273,8 @@ export class LineCommentCommand implements EditorCommon.ICommand { * Given an unsuccessful analysis, delegate to the block comment command */ private _executeBlockComment(model:EditorCommon.ITokenizedModel, builder:EditorCommon.IEditOperationBuilder, s:EditorCommon.IEditorSelection): void { - var commentsSupport = model.getModeAtPosition(s.startLineNumber, s.startColumn).commentsSupport; - if (!commentsSupport) { - // Mode does not support comments - return; - } - - var config = commentsSupport.getCommentsConfiguration(); + let richEditSupport = model.getModeAtPosition(s.startLineNumber, s.startColumn).richEditSupport; + let config = richEditSupport ? richEditSupport.comments : null; if (!config || !config.blockCommentStartToken || !config.blockCommentEndToken) { // Mode does not support block comments return; diff --git a/src/vs/editor/contrib/comment/test/common/blockCommentCommand.test.ts b/src/vs/editor/contrib/comment/test/common/blockCommentCommand.test.ts index 9d0b8f5048a..f78af72c748 100644 --- a/src/vs/editor/contrib/comment/test/common/blockCommentCommand.test.ts +++ b/src/vs/editor/contrib/comment/test/common/blockCommentCommand.test.ts @@ -11,7 +11,7 @@ import {BlockCommentCommand} from 'vs/editor/contrib/comment/common/blockComment import {Selection} from 'vs/editor/common/core/selection'; function testBlockCommentCommand(lines: string[], selection: Selection, expectedLines: string[], expectedSelection: Selection): void { - var mode = new CommentMode({ lineCommentTokens: ['!@#'], blockCommentStartToken: '<0', blockCommentEndToken: '0>' }); + var mode = new CommentMode({ lineCommentToken: '!@#', blockCommentStartToken: '<0', blockCommentEndToken: '0>' }); testCommand(lines, mode, selection, (sel) => new BlockCommentCommand(sel), expectedLines, expectedSelection); } diff --git a/src/vs/editor/contrib/comment/test/common/lineCommentCommand.test.ts b/src/vs/editor/contrib/comment/test/common/lineCommentCommand.test.ts index 0831e8a25fd..2800487f5a4 100644 --- a/src/vs/editor/contrib/comment/test/common/lineCommentCommand.test.ts +++ b/src/vs/editor/contrib/comment/test/common/lineCommentCommand.test.ts @@ -15,7 +15,7 @@ import {Selection} from 'vs/editor/common/core/selection'; suite('Editor Contrib - Line Comment Command', () => { function testLineCommentCommand(lines: string[], selection: Selection, expectedLines: string[], expectedSelection: Selection): void { - var mode = new ModelModes.CommentMode({ lineCommentTokens: ['!@#'], blockCommentStartToken: '' }); + var mode = new ModelModes.CommentMode({ lineCommentToken: '!@#', blockCommentStartToken: '' }); TU.testCommand(lines, mode, selection, (sel) => new LineCommentCommand.LineCommentCommand(sel, 4, LineCommentCommand.Type.Toggle), expectedLines, expectedSelection); } @@ -490,7 +490,7 @@ suite('Editor Contrib - Line Comment Command', () => { suite('Editor Contrib - Line Comment As Block Comment', () => { function testLineCommentCommand(lines: string[], selection: Selection, expectedLines: string[], expectedSelection: Selection): void { - var mode = new ModelModes.CommentMode({ lineCommentTokens: [''], blockCommentStartToken: '(', blockCommentEndToken: ')' }); + var mode = new ModelModes.CommentMode({ lineCommentToken: '', blockCommentStartToken: '(', blockCommentEndToken: ')' }); TU.testCommand(lines, mode, selection, (sel) => new LineCommentCommand.LineCommentCommand(sel, 4, LineCommentCommand.Type.Toggle), expectedLines, expectedSelection); } @@ -600,7 +600,7 @@ suite('Editor Contrib - Line Comment As Block Comment', () => { suite('Editor Contrib - Line Comment As Block Comment 2', () => { function testLineCommentCommand(lines: string[], selection: Selection, expectedLines: string[], expectedSelection: Selection): void { - var mode = new ModelModes.CommentMode({ lineCommentTokens: [], blockCommentStartToken: '' }); + var mode = new ModelModes.CommentMode({ lineCommentToken: null, blockCommentStartToken: '' }); TU.testCommand(lines, mode, selection, (sel) => new LineCommentCommand.LineCommentCommand(sel, 4, LineCommentCommand.Type.Toggle), expectedLines, expectedSelection); } diff --git a/src/vs/editor/contrib/find/common/findModel.ts b/src/vs/editor/contrib/find/common/findModel.ts index b2cf9ea6d3c..ca66f8d1ba2 100644 --- a/src/vs/editor/contrib/find/common/findModel.ts +++ b/src/vs/editor/contrib/find/common/findModel.ts @@ -286,7 +286,7 @@ export class FindModelBoundToEditorModel { if (!this._state.isRegex) { return this._state.replaceString; } - let regexp = Strings.createRegExp(this._state.searchString, this._state.isRegex, this._state.matchCase, this._state.wholeWord); + let regexp = Strings.createRegExp(this._state.searchString, this._state.isRegex, this._state.matchCase, this._state.wholeWord, true); // Parse the replace string to support that \t or \n mean the right thing let parsedReplaceString = parseReplaceString(this._state.replaceString); return matchedString.replace(regexp, parsedReplaceString); diff --git a/src/vs/editor/contrib/smartSelect/common/tokenSelectionSupport.ts b/src/vs/editor/contrib/smartSelect/common/tokenSelectionSupport.ts index 77a09320dfc..88ca133fd13 100644 --- a/src/vs/editor/contrib/smartSelect/common/tokenSelectionSupport.ts +++ b/src/vs/editor/contrib/smartSelect/common/tokenSelectionSupport.ts @@ -21,6 +21,10 @@ class TokenSelectionSupport implements Modes.ILogicalSelectionSupport { } public getRangesToPosition(resource: URI, position: EditorCommon.IPosition): TPromise { + return TPromise.as(this.getRangesToPositionSync(resource, position)); + } + + public getRangesToPositionSync(resource: URI, position: EditorCommon.IPosition): Modes.ILogicalSelectionEntry[] { var model = this._modelService.getModel(resource), entries: Modes.ILogicalSelectionEntry[] = []; @@ -33,7 +37,7 @@ class TokenSelectionSupport implements Modes.ILogicalSelectionSupport { }); } - return TPromise.as(entries); + return entries; } private _doGetRangesToPosition(model: EditorCommon.IModel, position: EditorCommon.IPosition): EditorCommon.IRange[] { diff --git a/src/vs/editor/contrib/smartSelect/common/tokenTree.ts b/src/vs/editor/contrib/smartSelect/common/tokenTree.ts index 67e4c10b522..f637bd2a0e3 100644 --- a/src/vs/editor/contrib/smartSelect/common/tokenTree.ts +++ b/src/vs/editor/contrib/smartSelect/common/tokenTree.ts @@ -8,6 +8,8 @@ import EditorCommon = require('vs/editor/common/editorCommon'); import Modes = require('vs/editor/common/modes'); import {Range} from 'vs/editor/common/core/range'; import {Position} from 'vs/editor/common/core/position'; +import {ignoreBracketsInToken} from 'vs/editor/common/modes/supports'; +import {BracketsUtils} from 'vs/editor/common/modes/supports/electricCharacter'; export class Node { @@ -107,7 +109,13 @@ class TokenScanner { private _versionId: number; private _currentLineNumber: number; private _currentTokenIndex: number; + private _currentTokenStart: number; private _currentLineTokens: EditorCommon.ILineTokens; + private _currentLineModeTransitions: Modes.IModeTransition[]; + private _currentModeIndex: number; + private _nextModeStart: number; + private _currentModeBrackets: Modes.IRichEditBrackets; + private _currentLineText: string; constructor(model: EditorCommon.IModel) { this._model = model; @@ -127,7 +135,12 @@ class TokenScanner { if (!this._currentLineTokens) { // no tokens for this line this._currentLineTokens = this._model.getLineTokens(this._currentLineNumber); + this._currentLineText = this._model.getLineContent(this._currentLineNumber); + this._currentLineModeTransitions = this._model._getLineModeTransitions(this._currentLineNumber); this._currentTokenIndex = 0; + this._currentTokenStart = 0; + this._currentModeIndex = -1; + this._nextModeStart = 0; } if (this._currentTokenIndex >= this._currentLineTokens.getTokenCount()) { // last token of line visited @@ -135,18 +148,74 @@ class TokenScanner { this._currentLineTokens = null; return this.next(); } - var token: Token = { - type: this._currentLineTokens.getTokenType(this._currentTokenIndex), - bracket: this._currentLineTokens.getTokenBracket(this._currentTokenIndex), + + if (this._currentTokenStart >= this._nextModeStart) { + this._currentModeIndex++; + this._nextModeStart = (this._currentModeIndex + 1 < this._currentLineModeTransitions.length ? this._currentLineModeTransitions[this._currentModeIndex + 1].startIndex : this._currentLineText.length + 1); + let mode = (this._currentModeIndex < this._currentLineModeTransitions.length ? this._currentLineModeTransitions[this._currentModeIndex] : null); + this._currentModeBrackets = (mode && mode.mode.richEditSupport ? mode.mode.richEditSupport.brackets : null); + } + + let tokenType = this._currentLineTokens.getTokenType(this._currentTokenIndex); + let tokenEndIndex = this._currentLineTokens.getTokenEndIndex(this._currentTokenIndex, this._currentLineText.length); + + let nextBracket: Range = null; + if (this._currentModeBrackets && !ignoreBracketsInToken(tokenType)) { + nextBracket = BracketsUtils.findNextBracketInToken(this._currentModeBrackets.forwardRegex, this._currentLineNumber, this._currentLineText, this._currentTokenStart, tokenEndIndex); + } + + if (nextBracket && this._currentTokenStart < nextBracket.startColumn - 1) { + // found a bracket, but it is not at the beginning of the token + tokenEndIndex = nextBracket.startColumn - 1; + nextBracket = null; + } + + let bracketData: EditorCommon.IRichEditBracket = null; + let bracketIsOpen: boolean = false; + if (nextBracket) { + let bracketText = this._currentLineText.substring(nextBracket.startColumn - 1, nextBracket.endColumn - 1); + bracketData = this._currentModeBrackets.textIsBracket[bracketText]; + bracketIsOpen = this._currentModeBrackets.textIsOpenBracket[bracketText]; + } + + if (!bracketData) { + let token: Token = { + type: tokenType, + bracket: Modes.Bracket.None, + range: { + startLineNumber: this._currentLineNumber, + startColumn: 1 + this._currentTokenStart, + endLineNumber: this._currentLineNumber, + endColumn: 1 + tokenEndIndex + } + }; + // console.log('TOKEN: <<' + this._currentLineText.substring(this._currentTokenStart, tokenEndIndex) + '>>'); + + this._currentTokenIndex += 1; + this._currentTokenStart = (this._currentTokenIndex < this._currentLineTokens.getTokenCount() ? this._currentLineTokens.getTokenStartIndex(this._currentTokenIndex) : 0); + return token; + } + + let type = `${bracketData.modeId};${bracketData.open};${bracketData.close}`; + let token: Token = { + type: type, + bracket: bracketIsOpen ? Modes.Bracket.Open : Modes.Bracket.Close, range: { startLineNumber: this._currentLineNumber, - startColumn: 1 + this._currentLineTokens.getTokenStartIndex(this._currentTokenIndex), + startColumn: 1 + this._currentTokenStart, endLineNumber: this._currentLineNumber, - endColumn: 1 + this._currentLineTokens.getTokenEndIndex(this._currentTokenIndex, this._model.getLineMaxColumn(this._currentLineNumber)) + endColumn: nextBracket.endColumn } }; - // token.__debugContent = this._model.getValueInRange(token.range); - this._currentTokenIndex += 1; + // console.log('BRACKET: <<' + this._currentLineText.substring(this._currentTokenStart, nextBracket.endColumn - 1) + '>>'); + + if (nextBracket.endColumn - 1 < tokenEndIndex) { + // found a bracket, but it is not at the end of the token + this._currentTokenStart = nextBracket.endColumn - 1; + } else { + this._currentTokenIndex += 1; + this._currentTokenStart = (this._currentTokenIndex < this._currentLineTokens.getTokenCount() ? this._currentLineTokens.getTokenStartIndex(this._currentTokenIndex) : 0); + } return token; } } diff --git a/src/vs/editor/contrib/smartSelect/test/common/tokenSelectionSupport.test.ts b/src/vs/editor/contrib/smartSelect/test/common/tokenSelectionSupport.test.ts new file mode 100644 index 00000000000..7c7615bcbe7 --- /dev/null +++ b/src/vs/editor/contrib/smartSelect/test/common/tokenSelectionSupport.test.ts @@ -0,0 +1,68 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +import 'vs/languages/javascript/common/javascript.contribution'; +import assert = require('assert'); +import Modes = require('vs/editor/common/modes'); +import modesUtil = require('vs/editor/test/common/modesUtil'); +import {createMockModelService} from 'vs/editor/test/common/servicesTestUtils'; +import URI from 'vs/base/common/uri'; +import TokenSelectionSupport = require('vs/editor/contrib/smartSelect/common/tokenSelectionSupport'); +import {Range} from 'vs/editor/common/core/range'; + +suite('TokenSelectionSupport', () => { + + let modelService = createMockModelService(); + let tokenSelectionSupport = new TokenSelectionSupport(modelService); + let _mode: Modes.IMode; + + suiteSetup((done) => { + modesUtil.load('javascript').then(mode => { + _mode = mode; + done(); + }); + }); + + function assertGetRangesToPosition(text:string[], lineNumber:number, column:number, ranges:Range[]): void { + let uri = URI.file('test.js'); + let model = modelService.createModel(text.join('\n'), _mode, uri); + + let actual = tokenSelectionSupport.getRangesToPositionSync(uri, { + lineNumber: lineNumber, + column: column + }); + + let actualStr = actual.map(r => new Range(r.range.startLineNumber, r.range.startColumn, r.range.endLineNumber, r.range.endColumn).toString()); + let desiredStr = ranges.map(r => String(r)); + + assert.deepEqual(actualStr, desiredStr); + + modelService.destroyModel(uri); + } + + test('getRangesToPosition #1', () => { + + assertGetRangesToPosition([ + 'function a(bar, foo){', + '\tif (bar) {', + '\t\treturn (bar + (2 * foo))', + '\t}', + '}' + ], 3, 20, [ + new Range(1, 1, 5, 2), + new Range(1, 21, 5, 2), + new Range(2, 1, 4, 3), + new Range(2, 11, 4, 3), + new Range(3, 1, 4, 2), + new Range(3, 1, 3, 27), + new Range(3, 10, 3, 27), + new Range(3, 11, 3, 26), + new Range(3, 17, 3, 26), + new Range(3, 18, 3, 25), + new Range(3, 19, 3, 20) + ]); + }); +}); \ No newline at end of file diff --git a/src/vs/editor/node/languageConfiguration.ts b/src/vs/editor/node/languageConfiguration.ts index 0ce88b2c185..af2b0356bdf 100644 --- a/src/vs/editor/node/languageConfiguration.ts +++ b/src/vs/editor/node/languageConfiguration.ts @@ -11,7 +11,8 @@ import pfs = require('vs/base/node/pfs'); import Supports = require ('vs/editor/common/modes/supports'); import {IOnEnterSupportOptions} from 'vs/editor/common/modes/supports/onEnter'; import json = require('vs/base/common/json'); -import {ICharacterPairContribution} from 'vs/editor/common/modes'; +import {ICharacterPairContribution} from 'vs/editor/common/modes/supports/characterPair'; +import {IRichEditConfiguration} from 'vs/editor/common/modes/supports/richEditSupport'; type CharacterPair = [string, string]; @@ -62,47 +63,33 @@ export class LanguageConfigurationFileHandler { } private _handleConfig(modeId:string, configuration:ILanguageConfiguration): void { + + let richEditConfig:IRichEditConfiguration = {}; + if (configuration.comments) { - let comments = configuration.comments; - let contrib: Supports.ICommentsSupportContribution = { commentsConfiguration: {} }; - - if (comments.lineComment) { - contrib.commentsConfiguration.lineCommentTokens = [comments.lineComment]; - } - if (comments.blockComment) { - contrib.commentsConfiguration.blockCommentStartToken = comments.blockComment[0]; - contrib.commentsConfiguration.blockCommentEndToken = comments.blockComment[1]; - } - - this._modeService.registerDeclarativeCommentsSupport(modeId, contrib); + richEditConfig.comments = configuration.comments; } if (configuration.brackets) { - let brackets = configuration.brackets; + richEditConfig.brackets = configuration.brackets; - let onEnterContrib: IOnEnterSupportOptions = {}; - onEnterContrib.brackets = brackets.map(pair => { - let [open, close] = pair; - return { open: open, close: close }; - }); - this._modeService.registerDeclarativeOnEnterSupport(modeId, onEnterContrib); - - let characterPairContrib: ICharacterPairContribution = { - autoClosingPairs: brackets.map(pair => { + richEditConfig.__characterPairSupport = { + autoClosingPairs: configuration.brackets.map(pair => { let [open, close] = pair; return { open: open, close: close }; }) - }; - this._modeService.registerDeclarativeCharacterPairSupport(modeId, characterPairContrib); + } } // TMSyntax hard-codes these and tokenizes them as brackets - this._modeService.registerDeclarativeElectricCharacterSupport(modeId, { + richEditConfig.__electricCharacterSupport = { brackets: [ { tokenType:'delimiter.curly.' + modeId, open: '{', close: '}', isElectric: true }, { tokenType:'delimiter.square.' + modeId, open: '[', close: ']', isElectric: true }, { tokenType:'delimiter.paren.' + modeId, open: '(', close: ')', isElectric: true } ] - }); + }; + + this._modeService.registerRichEditSupport(modeId, richEditConfig); } } diff --git a/src/vs/editor/node/textMate/TMSyntax.ts b/src/vs/editor/node/textMate/TMSyntax.ts index 5d734f3a278..9a518817db7 100644 --- a/src/vs/editor/node/textMate/TMSyntax.ts +++ b/src/vs/editor/node/textMate/TMSyntax.ts @@ -161,10 +161,6 @@ class Tokenizer { modeTransitions: [{ startIndex: offsetDelta, mode: freshState.getMode() }], }; - let noBracket = Modes.Bracket.None, - openBracket = Modes.Bracket.Open, - closeBracket = Modes.Bracket.Close; - for (let tokenIndex = 0, len = textMateResult.tokens.length; tokenIndex < len; tokenIndex++) { let token = textMateResult.tokens[tokenIndex]; let tokenStartIndex = token.startIndex; @@ -174,7 +170,7 @@ class Tokenizer { if (t.isOpaqueToken) { // Should not do any smartness to detect brackets on this token - ret.tokens.push(new supports.Token(tokenStartIndex + offsetDelta, t.tokenType, noBracket)); + ret.tokens.push(new supports.Token(tokenStartIndex + offsetDelta, t.tokenType)); continue; } @@ -186,51 +182,44 @@ class Tokenizer { for (i = tokenStartIndex; i < tokenEndIndex; i++) { charCode = line.charCodeAt(i); isBracket = null; - bracketType = noBracket; switch (charCode) { case _openParen: // ( isBracket = 'delimiter.paren'; - bracketType = openBracket; break; case _closeParen: // ) isBracket = 'delimiter.paren'; - bracketType = closeBracket; break; case _openCurly: // { isBracket = 'delimiter.curly'; - bracketType = openBracket; break; case _closeCurly: // } isBracket = 'delimiter.curly'; - bracketType = closeBracket; break; case _openSquare: // [ isBracket = 'delimiter.square'; - bracketType = openBracket; break; case _closeSquare: // ] isBracket = 'delimiter.square'; - bracketType = closeBracket; break; } if (isBracket) { if (tokenStartIndex < i) { // push a token before character `i` - ret.tokens.push(new supports.Token(tokenStartIndex + offsetDelta, t.tokenType, noBracket)); + ret.tokens.push(new supports.Token(tokenStartIndex + offsetDelta, t.tokenType)); tokenStartIndex = i; } // push character `i` as a token - ret.tokens.push(new supports.Token(tokenStartIndex + offsetDelta, isBracket + '.' + t.modeToken, bracketType)); + ret.tokens.push(new supports.Token(tokenStartIndex + offsetDelta, isBracket + '.' + t.modeToken)); tokenStartIndex = i + 1; } } if (tokenStartIndex < tokenEndIndex) { // push the remaining text as a token - ret.tokens.push(new supports.Token(tokenStartIndex + offsetDelta, t.tokenType, noBracket)); + ret.tokens.push(new supports.Token(tokenStartIndex + offsetDelta, t.tokenType)); } } diff --git a/src/vs/editor/standalone-languages/bat.ts b/src/vs/editor/standalone-languages/bat.ts index 99316ae324a..f4467fc2c9a 100644 --- a/src/vs/editor/standalone-languages/bat.ts +++ b/src/vs/editor/standalone-languages/bat.ts @@ -23,17 +23,17 @@ export var language = { { token: 'punctuation.square', open: '[', close: ']' } ], - enhancedBrackets: [ - { - openTrigger: 'l', - open: /setlocal$/i, - closeComplete: 'endlocal', - matchCase: true, - closeTrigger: 'l', - close: /endlocal$/i, - tokenType: 'keyword.tag-setlocal' - } - ], + // enhancedBrackets: [ + // { + // openTrigger: 'l', + // open: /setlocal$/i, + // closeComplete: 'endlocal', + // matchCase: true, + // closeTrigger: 'l', + // close: /endlocal$/i, + // tokenType: 'keyword.tag-setlocal' + // } + // ], keywords: /call|defined|echo|errorlevel|exist|for|goto|if|pause|set|shift|start|title|not|pushd|popd/, diff --git a/src/vs/editor/standalone-languages/coffee.ts b/src/vs/editor/standalone-languages/coffee.ts index 890669fb5fa..fa4e845f713 100644 --- a/src/vs/editor/standalone-languages/coffee.ts +++ b/src/vs/editor/standalone-languages/coffee.ts @@ -23,11 +23,11 @@ export var language = { { open:'(', close:')', token:'delimiter.parenthesis'} ], - enhancedBrackets: [ - { open: /for$/ }, { open: /while$/ }, { open: /loop$/ }, { open: /if$/ }, { open: /unless$/ }, - { open: /else$/ }, { open: /switch$/ }, { open: /try$/ }, { open: /catch$/ }, { open: /finally$/ }, - { open: /class$/ }, { open: /->$/ } - ], + // enhancedBrackets: [ + // { open: /for$/ }, { open: /while$/ }, { open: /loop$/ }, { open: /if$/ }, { open: /unless$/ }, + // { open: /else$/ }, { open: /switch$/ }, { open: /try$/ }, { open: /catch$/ }, { open: /finally$/ }, + // { open: /class$/ }, { open: /->$/ } + // ], // the default separators wordDefinition: /(-?\d*\.\d\w*)|([^\`\~\!\@\#%\^\&\*\(\)\=\$\-\+\[\{\]\}\\\|\;\:\'\"\,\.\<\>\/\?\s]+)/g, diff --git a/src/vs/editor/standalone-languages/powershell.ts b/src/vs/editor/standalone-languages/powershell.ts index 5b6519e967d..1921efa51e7 100644 --- a/src/vs/editor/standalone-languages/powershell.ts +++ b/src/vs/editor/standalone-languages/powershell.ts @@ -25,12 +25,12 @@ export var language = { { token: 'delimiter.square', open: '[', close: ']' }, { token: 'delimiter.parenthesis', open: '(', close: ')' }], - enhancedBrackets: [ - { tokenType:'string', openTrigger: '"', open: /@"$/, closeComplete: '"@' }, - { tokenType:'string', openTrigger: '\'', open: /@'$/, closeComplete: '\'@' }, - { tokenType:'string', openTrigger: '"', open: /"$/, closeComplete: '"' }, - { tokenType: 'string', openTrigger: '\'', open: /'$/, closeComplete: '\'' } - ], + // enhancedBrackets: [ + // { tokenType:'string', openTrigger: '"', open: /@"$/, closeComplete: '"@' }, + // { tokenType:'string', openTrigger: '\'', open: /@'$/, closeComplete: '\'@' }, + // { tokenType:'string', openTrigger: '"', open: /"$/, closeComplete: '"' }, + // { tokenType: 'string', openTrigger: '\'', open: /'$/, closeComplete: '\'' } + // ], autoClosingPairs: [['{', '}'], ['[', ']'], ['(', ')']], // Defined explicitly, to suppress the // default auto-closing of ' and " which is diff --git a/src/vs/editor/standalone-languages/python.ts b/src/vs/editor/standalone-languages/python.ts index 9da3cd81c99..0f77fcda89f 100644 --- a/src/vs/editor/standalone-languages/python.ts +++ b/src/vs/editor/standalone-languages/python.ts @@ -156,7 +156,7 @@ export var language = { ], // Cause an automatic indent to occur after lines ending in :. - enhancedBrackets: [ { open: /.*:\s*$/, closeComplete: 'else:' } ], + // enhancedBrackets: [ { open: /.*:\s*$/, closeComplete: 'else:' } ], tokenizer: { root: [ diff --git a/src/vs/editor/standalone-languages/sql.ts b/src/vs/editor/standalone-languages/sql.ts index 11947ddf745..d661e8db80b 100644 --- a/src/vs/editor/standalone-languages/sql.ts +++ b/src/vs/editor/standalone-languages/sql.ts @@ -16,11 +16,11 @@ export var language = { { open: '[', close: ']', token: 'delimiter.square' }, { open: '(', close: ')', token: 'delimiter.parenthesis' } ], - enhancedBrackets:[ - { openTrigger: 'n', open: /begin$/i, closeComplete: 'end', matchCase: true }, - { openTrigger: 'e', open: /case$/i, closeComplete: 'end', matchCase: true }, - { openTrigger: 'n', open: /when$/i, closeComplete: 'then', matchCase: true } - ], + // enhancedBrackets:[ + // { openTrigger: 'n', open: /begin$/i, closeComplete: 'end', matchCase: true }, + // { openTrigger: 'e', open: /case$/i, closeComplete: 'end', matchCase: true }, + // { openTrigger: 'n', open: /when$/i, closeComplete: 'then', matchCase: true } + // ], noindentBrackets: '()', editorOptions: { tabSize: 4, insertSpaces: true }, lineComment: '--', diff --git a/src/vs/editor/standalone-languages/test/testUtil.ts b/src/vs/editor/standalone-languages/test/testUtil.ts index ee0ca7c0647..e68ae122711 100644 --- a/src/vs/editor/standalone-languages/test/testUtil.ts +++ b/src/vs/editor/standalone-languages/test/testUtil.ts @@ -11,6 +11,7 @@ import modesUtil = require('vs/editor/test/common/modesUtil'); import monarchCompile = require('vs/editor/common/modes/monarch/monarchCompile'); import MonarchDefinition = require('vs/editor/common/modes/monarch/monarchDefinition'); import {OnEnterSupport} from 'vs/editor/common/modes/supports/onEnter'; +import {RichEditSupport} from 'vs/editor/common/modes/supports/richEditSupport'; export enum Bracket { None = 0, @@ -47,8 +48,8 @@ export function testOnEnter(name:string, language: types.ILanguage, callback:(as suite(language.displayName || name, () => { test('onEnter', () => { var lexer = monarchCompile.compile(language); - var onEnterSupport = new OnEnterSupport('test', MonarchDefinition.createOnEnterSupportOptions(lexer)); - callback(modesUtil.createOnEnterAsserter('test', onEnterSupport)); + var richEditSupport = new RichEditSupport('test', MonarchDefinition.createRichEditSupport(lexer)); + callback(modesUtil.createOnEnterAsserter('test', richEditSupport)); }); }); -} \ No newline at end of file +} diff --git a/src/vs/editor/standalone-languages/types.ts b/src/vs/editor/standalone-languages/types.ts index cd2853862cb..0039d16eb4c 100644 --- a/src/vs/editor/standalone-languages/types.ts +++ b/src/vs/editor/standalone-languages/types.ts @@ -35,7 +35,7 @@ export interface ILanguage { autoClosingPairs?: string[][]; // for example [['"','"']] wordDefinition?: RegExp; // word definition regular expression outdentTriggers?: string; // characters that could potentially cause outdentation - enhancedBrackets?: IRegexBracketPair[]; // Advanced auto completion, auto indenting, and bracket matching + // enhancedBrackets?: IRegexBracketPair[]; // Advanced auto completion, auto indenting, and bracket matching } /** @@ -47,33 +47,33 @@ export interface ILanguageBracket { token: string; // token class } -export interface ILanguageAutoComplete { - triggers: string; // characters that trigger auto completion rules - match: string|RegExp; // autocomplete if this matches - complete: string; // complete with this string -} +// export interface ILanguageAutoComplete { +// triggers: string; // characters that trigger auto completion rules +// match: string|RegExp; // autocomplete if this matches +// complete: string; // complete with this string +// } -export interface ILanguageAutoIndent { - match: string|RegExp; // auto indent if this matches on enter - matchAfter: string|RegExp; // and auto-outdent if this matches on the next line -} +// export interface ILanguageAutoIndent { +// match: string|RegExp; // auto indent if this matches on enter +// matchAfter: string|RegExp; // and auto-outdent if this matches on the next line +// } -/** - * Regular expression based brackets. These are always electric. - */ -export interface IRegexBracketPair { - openTrigger?: string; // The character that will trigger the evaluation of 'open'. - open: RegExp; // The definition of when an opening brace is detected. This regex is matched against the entire line upto, and including the last typed character (the trigger character). - closeComplete?: string; // How to complete a matching open brace. Matches from 'open' will be expanded, e.g. '' - matchCase?: boolean; // If set to true, the case of the string captured in 'open' will be detected an applied also to 'closeComplete'. - // This is useful for cases like BEGIN/END or begin/end where the opening and closing phrases are unrelated. - // For identical phrases, use the $1 replacement syntax above directly in closeComplete, as it will - // include the proper casing from the captured string in 'open'. - // Upper/Lower/Camel cases are detected. Camel case dection uses only the first two characters and assumes - // that 'closeComplete' contains wors separated by spaces (e.g. 'End Loop') +// /** +// * Regular expression based brackets. These are always electric. +// */ +// export interface IRegexBracketPair { +// // openTrigger?: string; // The character that will trigger the evaluation of 'open'. +// open: RegExp; // The definition of when an opening brace is detected. This regex is matched against the entire line upto, and including the last typed character (the trigger character). +// closeComplete?: string; // How to complete a matching open brace. Matches from 'open' will be expanded, e.g. '' +// matchCase?: boolean; // If set to true, the case of the string captured in 'open' will be detected an applied also to 'closeComplete'. +// // This is useful for cases like BEGIN/END or begin/end where the opening and closing phrases are unrelated. +// // For identical phrases, use the $1 replacement syntax above directly in closeComplete, as it will +// // include the proper casing from the captured string in 'open'. +// // Upper/Lower/Camel cases are detected. Camel case dection uses only the first two characters and assumes +// // that 'closeComplete' contains wors separated by spaces (e.g. 'End Loop') - closeTrigger?: string; // The character that will trigger the evaluation of 'close'. - close?: RegExp; // The definition of when a closing brace is detected. This regex is matched against the entire line upto, and including the last typed character (the trigger character). - tokenType?: string; // The type of the token. Matches from 'open' or 'close' will be expanded, e.g. 'keyword.$1'. - // Only used to auto-(un)indent a closing bracket. -} +// // closeTrigger?: string; // The character that will trigger the evaluation of 'close'. +// close?: RegExp; // The definition of when a closing brace is detected. This regex is matched against the entire line upto, and including the last typed character (the trigger character). +// tokenType?: string; // The type of the token. Matches from 'open' or 'close' will be expanded, e.g. 'keyword.$1'. +// // Only used to auto-(un)indent a closing bracket. +// } diff --git a/src/vs/editor/standalone-languages/xml.ts b/src/vs/editor/standalone-languages/xml.ts index 0d1439fe430..ad7f64e70da 100644 --- a/src/vs/editor/standalone-languages/xml.ts +++ b/src/vs/editor/standalone-languages/xml.ts @@ -22,14 +22,14 @@ export var language = { // Useful regular expressions qualifiedName: /(?:[\w\.\-]+:)?[\w\.\-]+/, - enhancedBrackets: [{ - tokenType: 'tag.tag-$1.xml', - openTrigger: '>', - open: /<(\w[\w\d]*)([^\/>]*(?!\/)>)[^<>]*$/i, - closeComplete: '', - closeTrigger: '>', - close: /<\/(\w[\w\d]*)\s*>$/i - }], + // enhancedBrackets: [{ + // tokenType: 'tag.tag-$1.xml', + // openTrigger: '>', + // open: /<(\w[\w\d]*)([^\/>]*(?!\/)>)[^<>]*$/i, + // closeComplete: '', + // closeTrigger: '>', + // close: /<\/(\w[\w\d]*)\s*>$/i + // }], autoClosingPairs: [['\'', '\''], ['"', '"'] ], diff --git a/src/vs/editor/test/common/commands/shiftCommand.test.ts b/src/vs/editor/test/common/commands/shiftCommand.test.ts index b55658da809..7a2e1035144 100644 --- a/src/vs/editor/test/common/commands/shiftCommand.test.ts +++ b/src/vs/editor/test/common/commands/shiftCommand.test.ts @@ -13,6 +13,7 @@ import {Selection} from 'vs/editor/common/core/selection'; import {Cursor} from 'vs/editor/common/controller/cursor'; import * as Modes from 'vs/editor/common/modes'; import {OnEnterSupport} from 'vs/editor/common/modes/supports/onEnter'; +import {RichEditSupport} from 'vs/editor/common/modes/supports/richEditSupport'; function testShiftCommand(lines: string[], selection: Selection, expectedLines: string[], expectedSelection: Selection): void { TU.testCommand(lines, null, selection, (sel) => new ShiftCommand(sel, { @@ -32,16 +33,17 @@ function testUnshiftCommand(lines: string[], selection: Selection, expectedLines class DocBlockCommentMode implements Modes.IMode { - public onEnterSupport: Modes.IOnEnterSupport; + public richEditSupport: Modes.IRichEditSupport; constructor() { - this.onEnterSupport = new OnEnterSupport(this.getId(), { + this.richEditSupport = new RichEditSupport(this.getId(), { brackets: [ - { open: '(', close: ')' }, - { open: '{', close: '}' }, - { open: '[', close: ']' } + ['(', ')'], + ['{', '}'], + ['[', ']'] ], - regExpRules: [ + + onEnterRules: [ { // e.g. /** | */ beforeText: /^\s*\/\*\*(?!\/)([^\*]|\*(?!\/))*$/, diff --git a/src/vs/editor/test/common/controller/cursor.test.ts b/src/vs/editor/test/common/controller/cursor.test.ts index e0715e8ff74..bebe2da4f11 100644 --- a/src/vs/editor/test/common/controller/cursor.test.ts +++ b/src/vs/editor/test/common/controller/cursor.test.ts @@ -15,7 +15,7 @@ import {Handler, EventType, IPosition, ISelection, EndOfLinePreference} from 'vs import {MockConfiguration} from 'vs/editor/test/common/mocks/mockConfiguration'; import {EditOperation} from 'vs/editor/common/core/editOperation'; import {AbstractState} from 'vs/editor/common/modes/abstractState'; -import {CharacterPairSupport} from 'vs/editor/common/modes/supports'; +import {RichEditSupport} from 'vs/editor/common/modes/supports/richEditSupport'; let H = Handler; @@ -768,28 +768,30 @@ class TestMode { } class SurroundingMode extends TestMode { - public characterPairSupport: Modes.ICharacterPairSupport; + public richEditSupport: Modes.IRichEditSupport; constructor() { super(); - this.characterPairSupport = new CharacterPairSupport(this, { - autoClosingPairs: [{ open: '(', close: ')' }] + this.richEditSupport = new RichEditSupport(this.getId(), { + __characterPairSupport: { + autoClosingPairs: [{ open: '(', close: ')' }] + } }); } } class OnEnterMode extends TestMode { - public electricCharacterSupport: Modes.IElectricCharacterSupport; + public richEditSupport: Modes.IRichEditSupport; constructor(indentAction: Modes.IndentAction) { super(); - this.electricCharacterSupport = { - getElectricCharacters: ():string[] => null, - onElectricCharacter: (context:Modes.ILineContext, offset:number): Modes.IElectricAction => null, - onEnter: (context:Modes.ILineContext, offset:number): Modes.IEnterAction => { - return { - indentAction: indentAction - }; + this.richEditSupport = { + onEnter: { + onEnter: (model, position) => { + return { + indentAction: indentAction + }; + } } }; } diff --git a/src/vs/editor/test/common/model/model.brackets.test.ts b/src/vs/editor/test/common/model/model.brackets.test.ts new file mode 100644 index 00000000000..339a7698d45 --- /dev/null +++ b/src/vs/editor/test/common/model/model.brackets.test.ts @@ -0,0 +1,131 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +import assert = require('assert'); +import {TextModel} from 'vs/editor/common/model/textModel'; +import {TextModelWithTokens} from 'vs/editor/common/model/textModelWithTokens'; +import {IFoundBracket} from 'vs/editor/common/editorCommon'; +import {Position} from 'vs/editor/common/core/position'; +import {Range} from 'vs/editor/common/core/range'; + +suite('TextModelWithTokens', () => { + + function toRelaxedFoundBracket(a:IFoundBracket) { + if (!a) { + return null; + } + return { + range: a.range.toString(), + open: a.open, + close: a.close, + isOpen: a.isOpen + }; + } + + function testBrackets(contents: string[], brackets:string[][]): void { + let charIsBracket: {[char:string]:boolean} = {}; + let charIsOpenBracket: {[char:string]:boolean} = {}; + let openForChar: {[char:string]:string} = {}; + let closeForChar: {[char:string]:string} = {}; + brackets.forEach((b) => { + charIsBracket[b[0]] = true; + charIsBracket[b[1]] = true; + + charIsOpenBracket[b[0]] = true; + charIsOpenBracket[b[1]] = false; + + openForChar[b[0]] = b[0]; + closeForChar[b[0]] = b[1]; + + openForChar[b[1]] = b[0]; + closeForChar[b[1]] = b[1]; + }); + + let expectedBrackets:IFoundBracket[] = []; + for (let lineIndex = 0; lineIndex < contents.length; lineIndex++) { + let lineText = contents[lineIndex]; + + for (let charIndex = 0; charIndex < lineText.length; charIndex++) { + let ch = lineText.charAt(charIndex); + if (charIsBracket[ch]) { + expectedBrackets.push({ + open: openForChar[ch], + close: closeForChar[ch], + isOpen: charIsOpenBracket[ch], + range: new Range(lineIndex + 1, charIndex + 1, lineIndex + 1, charIndex + 2) + }); + } + } + } + + let model = new TextModelWithTokens([], TextModel.toRawText(contents.join('\n')), false, null); + + // findPrevBracket + { + let expectedBracketIndex = expectedBrackets.length - 1; + let currentExpectedBracket = expectedBracketIndex >= 0 ? expectedBrackets[expectedBracketIndex] : null; + for (let lineNumber = contents.length; lineNumber >= 1; lineNumber--) { + let lineText = contents[lineNumber - 1]; + + for (let column = lineText.length + 1; column >= 1; column--) { + + if (currentExpectedBracket) { + if (lineNumber === currentExpectedBracket.range.startLineNumber && column < currentExpectedBracket.range.endColumn) { + expectedBracketIndex--; + currentExpectedBracket = expectedBracketIndex >= 0 ? expectedBrackets[expectedBracketIndex] : null; + } + } + + let actual = model.findPrevBracket({ + lineNumber: lineNumber, + column: column + }); + + assert.deepEqual(toRelaxedFoundBracket(actual), toRelaxedFoundBracket(currentExpectedBracket), 'findPrevBracket of ' + lineNumber + ', ' + column); + } + } + } + + // findNextBracket + { + let expectedBracketIndex = 0; + let currentExpectedBracket = expectedBracketIndex < expectedBrackets.length ? expectedBrackets[expectedBracketIndex] : null; + for (let lineNumber = 1; lineNumber <= contents.length; lineNumber++) { + let lineText = contents[lineNumber - 1]; + + for (let column = 1; column <= lineText.length + 1; column++) { + + if (currentExpectedBracket) { + if (lineNumber === currentExpectedBracket.range.startLineNumber && column > currentExpectedBracket.range.startColumn) { + expectedBracketIndex++; + currentExpectedBracket = expectedBracketIndex < expectedBrackets.length ? expectedBrackets[expectedBracketIndex] : null; + } + } + + let actual = model.findNextBracket({ + lineNumber: lineNumber, + column: column + }); + + assert.deepEqual(toRelaxedFoundBracket(actual), toRelaxedFoundBracket(currentExpectedBracket), 'findNextBracket of ' + lineNumber + ', ' + column); + } + } + } + + model.dispose(); + } + + test('brackets', () => { + testBrackets([ + 'if (a == 3) { return (7 * (a + 5)); }' + ], [ + ['{', '}'], + ['[', ']'], + ['(', ')'] + ]) + }); + +}); diff --git a/src/vs/editor/test/common/model/model.line.test.ts b/src/vs/editor/test/common/model/model.line.test.ts index df3d189def3..bc172822d91 100644 --- a/src/vs/editor/test/common/model/model.line.test.ts +++ b/src/vs/editor/test/common/model/model.line.test.ts @@ -248,7 +248,7 @@ suite('Editor Model - ModelLine.applyEdits text & tokens', () => { test('insertion on empty line', () => { var line = new ModelLine.ModelLine(1, 'some text'); var map = new TextModelWithTokens.TokensInflatorMap(); - line.setTokens(map, [{startIndex: 0, type:'bar', bracket:0}], null, []); + line.setTokens(map, [{startIndex: 0, type:'bar'}], null, []); line.applyEdits({}, [{startColumn:1, endColumn:10, text:'', forceMoveMarkers: false}]); line.setTokens(map, [], null, []); @@ -256,8 +256,7 @@ suite('Editor Model - ModelLine.applyEdits text & tokens', () => { line.applyEdits({}, [{startColumn:1, endColumn:1, text:'a', forceMoveMarkers: false}]); assertLineTokens(line.getTokens(), [{ startIndex: 0, - type:'', - bracket:0 + type:'' }]); }) @@ -265,9 +264,9 @@ suite('Editor Model - ModelLine.applyEdits text & tokens', () => { testLineEditTokens( 'abcd efgh', [ - { startIndex: 0, type: '1', bracket: 0 }, - { startIndex: 4, type: '2', bracket: 1 }, - { startIndex: 5, type: '3', bracket: -1 } + { startIndex: 0, type: '1' }, + { startIndex: 4, type: '2' }, + { startIndex: 5, type: '3' } ], [{ startColumn: 1, @@ -277,9 +276,9 @@ suite('Editor Model - ModelLine.applyEdits text & tokens', () => { }], 'aabcd efgh', [ - { startIndex: 0, type: '1', bracket: 0 }, - { startIndex: 5, type: '2', bracket: 1 }, - { startIndex: 6, type: '3', bracket: -1 } + { startIndex: 0, type: '1' }, + { startIndex: 5, type: '2' }, + { startIndex: 6, type: '3' } ] ); }); @@ -288,9 +287,9 @@ suite('Editor Model - ModelLine.applyEdits text & tokens', () => { testLineEditTokens( 'aabcd efgh', [ - { startIndex: 0, type: '1', bracket: 0 }, - { startIndex: 5, type: '2', bracket: 1 }, - { startIndex: 6, type: '3', bracket: -1 } + { startIndex: 0, type: '1' }, + { startIndex: 5, type: '2' }, + { startIndex: 6, type: '3' } ], [{ startColumn: 2, @@ -300,9 +299,9 @@ suite('Editor Model - ModelLine.applyEdits text & tokens', () => { }], 'axabcd efgh', [ - { startIndex: 0, type: '1', bracket: 0 }, - { startIndex: 6, type: '2', bracket: 1 }, - { startIndex: 7, type: '3', bracket: -1 } + { startIndex: 0, type: '1' }, + { startIndex: 6, type: '2' }, + { startIndex: 7, type: '3' } ] ); }); @@ -311,9 +310,9 @@ suite('Editor Model - ModelLine.applyEdits text & tokens', () => { testLineEditTokens( 'axabcd efgh', [ - { startIndex: 0, type: '1', bracket: 0 }, - { startIndex: 6, type: '2', bracket: 1 }, - { startIndex: 7, type: '3', bracket: -1 } + { startIndex: 0, type: '1' }, + { startIndex: 6, type: '2' }, + { startIndex: 7, type: '3' } ], [{ startColumn: 3, @@ -323,9 +322,9 @@ suite('Editor Model - ModelLine.applyEdits text & tokens', () => { }], 'axstuabcd efgh', [ - { startIndex: 0, type: '1', bracket: 0 }, - { startIndex: 9, type: '2', bracket: 1 }, - { startIndex: 10, type: '3', bracket: -1 } + { startIndex: 0, type: '1' }, + { startIndex: 9, type: '2' }, + { startIndex: 10, type: '3' } ] ); }); @@ -334,9 +333,9 @@ suite('Editor Model - ModelLine.applyEdits text & tokens', () => { testLineEditTokens( 'axstuabcd efgh', [ - { startIndex: 0, type: '1', bracket: 0 }, - { startIndex: 9, type: '2', bracket: 1 }, - { startIndex: 10, type: '3', bracket: -1 } + { startIndex: 0, type: '1' }, + { startIndex: 9, type: '2' }, + { startIndex: 10, type: '3' } ], [{ startColumn: 10, @@ -346,9 +345,9 @@ suite('Editor Model - ModelLine.applyEdits text & tokens', () => { }], 'axstuabcd\t efgh', [ - { startIndex: 0, type: '1', bracket: 0 }, - { startIndex: 10, type: '2', bracket: 1 }, - { startIndex: 11, type: '3', bracket: -1 } + { startIndex: 0, type: '1' }, + { startIndex: 10, type: '2' }, + { startIndex: 11, type: '3' } ] ); }); @@ -357,9 +356,9 @@ suite('Editor Model - ModelLine.applyEdits text & tokens', () => { testLineEditTokens( 'axstuabcd\t efgh', [ - { startIndex: 0, type: '1', bracket: 0 }, - { startIndex: 10, type: '2', bracket: 1 }, - { startIndex: 11, type: '3', bracket: -1 } + { startIndex: 0, type: '1' }, + { startIndex: 10, type: '2' }, + { startIndex: 11, type: '3' } ], [{ startColumn: 12, @@ -369,9 +368,9 @@ suite('Editor Model - ModelLine.applyEdits text & tokens', () => { }], 'axstuabcd\t ddefgh', [ - { startIndex: 0, type: '1', bracket: 0 }, - { startIndex: 10, type: '2', bracket: 1 }, - { startIndex: 13, type: '3', bracket: -1 } + { startIndex: 0, type: '1' }, + { startIndex: 10, type: '2' }, + { startIndex: 13, type: '3' } ] ); }); @@ -380,9 +379,9 @@ suite('Editor Model - ModelLine.applyEdits text & tokens', () => { testLineEditTokens( 'axstuabcd\t ddefgh', [ - { startIndex: 0, type: '1', bracket: 0 }, - { startIndex: 10, type: '2', bracket: 1 }, - { startIndex: 13, type: '3', bracket: -1 } + { startIndex: 0, type: '1' }, + { startIndex: 10, type: '2' }, + { startIndex: 13, type: '3' } ], [{ startColumn: 18, @@ -392,9 +391,9 @@ suite('Editor Model - ModelLine.applyEdits text & tokens', () => { }], 'axstuabcd\t ddefghxyz', [ - { startIndex: 0, type: '1', bracket: 0 }, - { startIndex: 10, type: '2', bracket: 1 }, - { startIndex: 13, type: '3', bracket: -1 } + { startIndex: 0, type: '1' }, + { startIndex: 10, type: '2' }, + { startIndex: 13, type: '3' } ] ); }); @@ -403,9 +402,9 @@ suite('Editor Model - ModelLine.applyEdits text & tokens', () => { testLineEditTokens( 'axstuabcd\t ddefghxyz', [ - { startIndex: 0, type: '1', bracket: 0 }, - { startIndex: 10, type: '2', bracket: 1 }, - { startIndex: 13, type: '3', bracket: -1 } + { startIndex: 0, type: '1' }, + { startIndex: 10, type: '2' }, + { startIndex: 13, type: '3' } ], [{ startColumn: 1, @@ -415,9 +414,9 @@ suite('Editor Model - ModelLine.applyEdits text & tokens', () => { }], 'xaxstuabcd\t ddefghxyz', [ - { startIndex: 0, type: '1', bracket: 0 }, - { startIndex: 11, type: '2', bracket: 1 }, - { startIndex: 14, type: '3', bracket: -1 } + { startIndex: 0, type: '1' }, + { startIndex: 11, type: '2' }, + { startIndex: 14, type: '3' } ] ); }); @@ -426,9 +425,9 @@ suite('Editor Model - ModelLine.applyEdits text & tokens', () => { testLineEditTokens( 'xaxstuabcd\t ddefghxyz', [ - { startIndex: 0, type: '1', bracket: 0 }, - { startIndex: 11, type: '2', bracket: 1 }, - { startIndex: 14, type: '3', bracket: -1 } + { startIndex: 0, type: '1' }, + { startIndex: 11, type: '2' }, + { startIndex: 14, type: '3' } ], [{ startColumn: 22, @@ -438,9 +437,9 @@ suite('Editor Model - ModelLine.applyEdits text & tokens', () => { }], 'xaxstuabcd\t ddefghxyzx', [ - { startIndex: 0, type: '1', bracket: 0 }, - { startIndex: 11, type: '2', bracket: 1 }, - { startIndex: 14, type: '3', bracket: -1 } + { startIndex: 0, type: '1' }, + { startIndex: 11, type: '2' }, + { startIndex: 14, type: '3' } ] ); }); @@ -449,9 +448,9 @@ suite('Editor Model - ModelLine.applyEdits text & tokens', () => { testLineEditTokens( 'xaxstuabcd\t ddefghxyzx', [ - { startIndex: 0, type: '1', bracket: 0 }, - { startIndex: 11, type: '2', bracket: 1 }, - { startIndex: 14, type: '3', bracket: -1 } + { startIndex: 0, type: '1' }, + { startIndex: 11, type: '2' }, + { startIndex: 14, type: '3' } ], [{ startColumn: 2, @@ -461,9 +460,9 @@ suite('Editor Model - ModelLine.applyEdits text & tokens', () => { }], 'xaxstuabcd\t ddefghxyzx', [ - { startIndex: 0, type: '1', bracket: 0 }, - { startIndex: 11, type: '2', bracket: 1 }, - { startIndex: 14, type: '3', bracket: -1 } + { startIndex: 0, type: '1' }, + { startIndex: 11, type: '2' }, + { startIndex: 14, type: '3' } ] ); }); @@ -480,7 +479,7 @@ suite('Editor Model - ModelLine.applyEdits text & tokens', () => { }], 'a', [ - { startIndex: 0, type: '', bracket: 0 } + { startIndex: 0, type: '' } ] ); }); @@ -489,9 +488,9 @@ suite('Editor Model - ModelLine.applyEdits text & tokens', () => { testLineEditTokens( 'abcdefghij', [ - { startIndex: 0, type: '1', bracket: 0 }, - { startIndex: 3, type: '2', bracket: 1 }, - { startIndex: 6, type: '3', bracket: -1 } + { startIndex: 0, type: '1' }, + { startIndex: 3, type: '2' }, + { startIndex: 6, type: '3' } ], [{ startColumn: 4, @@ -501,8 +500,8 @@ suite('Editor Model - ModelLine.applyEdits text & tokens', () => { }], 'abcghij', [ - { startIndex: 0, type: '1', bracket: 0 }, - { startIndex: 3, type: '3', bracket: -1 } + { startIndex: 0, type: '1' }, + { startIndex: 3, type: '3' } ] ); }); @@ -511,9 +510,9 @@ suite('Editor Model - ModelLine.applyEdits text & tokens', () => { testLineEditTokens( 'abcdefghij', [ - { startIndex: 0, type: '1', bracket: 0 }, - { startIndex: 3, type: '2', bracket: 1 }, - { startIndex: 6, type: '3', bracket: -1 } + { startIndex: 0, type: '1' }, + { startIndex: 3, type: '2' }, + { startIndex: 6, type: '3' } ], [{ startColumn: 4, @@ -523,9 +522,9 @@ suite('Editor Model - ModelLine.applyEdits text & tokens', () => { }], 'abchellodefghij', [ - { startIndex: 0, type: '1', bracket: 0 }, - { startIndex: 8, type: '2', bracket: 1 }, - { startIndex: 11, type: '3', bracket: -1 } + { startIndex: 0, type: '1' }, + { startIndex: 8, type: '2' }, + { startIndex: 11, type: '3' } ] ); }); @@ -534,9 +533,9 @@ suite('Editor Model - ModelLine.applyEdits text & tokens', () => { testLineEditTokens( 'abcd efgh', [ - { startIndex: 0, type: '1', bracket: 0 }, - { startIndex: 4, type: '2', bracket: 1 }, - { startIndex: 5, type: '3', bracket: -1 } + { startIndex: 0, type: '1' }, + { startIndex: 4, type: '2' }, + { startIndex: 5, type: '3' } ], [{ startColumn: 1, @@ -546,9 +545,9 @@ suite('Editor Model - ModelLine.applyEdits text & tokens', () => { }], 'bcd efgh', [ - { startIndex: 0, type: '1', bracket: 0 }, - { startIndex: 3, type: '2', bracket: 1 }, - { startIndex: 4, type: '3', bracket: -1 } + { startIndex: 0, type: '1' }, + { startIndex: 3, type: '2' }, + { startIndex: 4, type: '3' } ] ); }); @@ -557,9 +556,9 @@ suite('Editor Model - ModelLine.applyEdits text & tokens', () => { testLineEditTokens( 'abcd efgh', [ - { startIndex: 0, type: '1', bracket: 0 }, - { startIndex: 4, type: '2', bracket: 1 }, - { startIndex: 5, type: '3', bracket: -1 } + { startIndex: 0, type: '1' }, + { startIndex: 4, type: '2' }, + { startIndex: 5, type: '3' } ], [{ startColumn: 2, @@ -569,9 +568,9 @@ suite('Editor Model - ModelLine.applyEdits text & tokens', () => { }], 'ad efgh', [ - { startIndex: 0, type: '1', bracket: 0 }, - { startIndex: 2, type: '2', bracket: 1 }, - { startIndex: 3, type: '3', bracket: -1 } + { startIndex: 0, type: '1' }, + { startIndex: 2, type: '2' }, + { startIndex: 3, type: '3' } ] ); }); @@ -580,9 +579,9 @@ suite('Editor Model - ModelLine.applyEdits text & tokens', () => { testLineEditTokens( 'abcd efgh', [ - { startIndex: 0, type: '1', bracket: 0 }, - { startIndex: 4, type: '2', bracket: 1 }, - { startIndex: 5, type: '3', bracket: -1 } + { startIndex: 0, type: '1' }, + { startIndex: 4, type: '2' }, + { startIndex: 5, type: '3' } ], [{ startColumn: 1, @@ -592,8 +591,8 @@ suite('Editor Model - ModelLine.applyEdits text & tokens', () => { }], ' efgh', [ - { startIndex: 0, type: '2', bracket: 1 }, - { startIndex: 1, type: '3', bracket: -1 } + { startIndex: 0, type: '2' }, + { startIndex: 1, type: '3' } ] ); }); @@ -602,9 +601,9 @@ suite('Editor Model - ModelLine.applyEdits text & tokens', () => { testLineEditTokens( 'abcd efgh', [ - { startIndex: 0, type: '1', bracket: 0 }, - { startIndex: 4, type: '2', bracket: 1 }, - { startIndex: 5, type: '3', bracket: -1 } + { startIndex: 0, type: '1' }, + { startIndex: 4, type: '2' }, + { startIndex: 5, type: '3' } ], [{ startColumn: 5, @@ -614,8 +613,8 @@ suite('Editor Model - ModelLine.applyEdits text & tokens', () => { }], 'abcdefgh', [ - { startIndex: 0, type: '1', bracket: 0 }, - { startIndex: 4, type: '3', bracket: -1 } + { startIndex: 0, type: '1' }, + { startIndex: 4, type: '3' } ] ); }); @@ -624,9 +623,9 @@ suite('Editor Model - ModelLine.applyEdits text & tokens', () => { testLineEditTokens( 'abcd efgh', [ - { startIndex: 0, type: '1', bracket: 0 }, - { startIndex: 4, type: '2', bracket: 1 }, - { startIndex: 5, type: '3', bracket: -1 } + { startIndex: 0, type: '1' }, + { startIndex: 4, type: '2' }, + { startIndex: 5, type: '3' } ], [{ startColumn: 5, @@ -636,8 +635,8 @@ suite('Editor Model - ModelLine.applyEdits text & tokens', () => { }], 'abcdfgh', [ - { startIndex: 0, type: '1', bracket: 0 }, - { startIndex: 4, type: '3', bracket: -1 } + { startIndex: 0, type: '1' }, + { startIndex: 4, type: '3' } ] ); }); @@ -646,9 +645,9 @@ suite('Editor Model - ModelLine.applyEdits text & tokens', () => { testLineEditTokens( 'abcd efgh', [ - { startIndex: 0, type: '1', bracket: 0 }, - { startIndex: 4, type: '2', bracket: 1 }, - { startIndex: 5, type: '3', bracket: -1 } + { startIndex: 0, type: '1' }, + { startIndex: 4, type: '2' }, + { startIndex: 5, type: '3' } ], [{ startColumn: 5, @@ -658,7 +657,7 @@ suite('Editor Model - ModelLine.applyEdits text & tokens', () => { }], 'abcd', [ - { startIndex: 0, type: '1', bracket: 0 } + { startIndex: 0, type: '1' } ] ); }); @@ -667,9 +666,9 @@ suite('Editor Model - ModelLine.applyEdits text & tokens', () => { testLineEditTokens( 'abcd efgh', [ - { startIndex: 0, type: '1', bracket: 0 }, - { startIndex: 4, type: '2', bracket: 1 }, - { startIndex: 5, type: '3', bracket: -1 } + { startIndex: 0, type: '1' }, + { startIndex: 4, type: '2' }, + { startIndex: 5, type: '3' } ], [{ startColumn: 1, @@ -686,9 +685,9 @@ suite('Editor Model - ModelLine.applyEdits text & tokens', () => { testLineEditTokens( 'abcd efgh', [ - { startIndex: 0, type: '1', bracket: 0 }, - { startIndex: 4, type: '2', bracket: 1 }, - { startIndex: 5, type: '3', bracket: -1 } + { startIndex: 0, type: '1' }, + { startIndex: 4, type: '2' }, + { startIndex: 5, type: '3' } ], [{ startColumn: 1, @@ -698,9 +697,9 @@ suite('Editor Model - ModelLine.applyEdits text & tokens', () => { }], 'abcd efgh', [ - { startIndex: 0, type: '1', bracket: 0 }, - { startIndex: 4, type: '2', bracket: 1 }, - { startIndex: 5, type: '3', bracket: -1 } + { startIndex: 0, type: '1' }, + { startIndex: 4, type: '2' }, + { startIndex: 5, type: '3' } ] ); }); @@ -709,9 +708,9 @@ suite('Editor Model - ModelLine.applyEdits text & tokens', () => { testLineEditTokens( 'abcd efgh', [ - { startIndex: 0, type: '1', bracket: 0 }, - { startIndex: 4, type: '2', bracket: 1 }, - { startIndex: 5, type: '3', bracket: -1 } + { startIndex: 0, type: '1' }, + { startIndex: 4, type: '2' }, + { startIndex: 5, type: '3' } ], [{ startColumn: 1, @@ -721,9 +720,9 @@ suite('Editor Model - ModelLine.applyEdits text & tokens', () => { }], 'cd efgh', [ - { startIndex: 0, type: '1', bracket: 0 }, - { startIndex: 2, type: '2', bracket: 1 }, - { startIndex: 3, type: '3', bracket: -1 } + { startIndex: 0, type: '1' }, + { startIndex: 2, type: '2' }, + { startIndex: 3, type: '3' } ] ); }); @@ -732,9 +731,9 @@ suite('Editor Model - ModelLine.applyEdits text & tokens', () => { testLineEditTokens( 'abcd efgh', [ - { startIndex: 0, type: '1', bracket: 0 }, - { startIndex: 4, type: '2', bracket: 1 }, - { startIndex: 5, type: '3', bracket: -1 } + { startIndex: 0, type: '1' }, + { startIndex: 4, type: '2' }, + { startIndex: 5, type: '3' } ], [{ startColumn: 5, @@ -744,7 +743,7 @@ suite('Editor Model - ModelLine.applyEdits text & tokens', () => { }], 'abcd', [ - { startIndex: 0, type: '1', bracket: 0 } + { startIndex: 0, type: '1' } ] ); }); @@ -753,11 +752,11 @@ suite('Editor Model - ModelLine.applyEdits text & tokens', () => { testLineEditTokens( 'Hello world, ciao', [ - { startIndex: 0, type: 'hello', bracket: 0 }, - { startIndex: 5, type: '', bracket: 0 }, - { startIndex: 6, type: 'world', bracket: 0 }, - { startIndex: 11, type: '', bracket: 0 }, - { startIndex: 13, type: '', bracket: 0 }, + { startIndex: 0, type: 'hello' }, + { startIndex: 5, type: '' }, + { startIndex: 6, type: 'world' }, + { startIndex: 11, type: '' }, + { startIndex: 13, type: '' }, ], [{ startColumn: 1, @@ -767,11 +766,11 @@ suite('Editor Model - ModelLine.applyEdits text & tokens', () => { }], 'Hi world, ciao', [ - { startIndex: 0, type: 'hello', bracket: 0 }, - { startIndex: 2, type: '', bracket: 0 }, - { startIndex: 3, type: 'world', bracket: 0 }, - { startIndex: 8, type: '', bracket: 0 }, - { startIndex: 10, type: '', bracket: 0 }, + { startIndex: 0, type: 'hello' }, + { startIndex: 2, type: '' }, + { startIndex: 3, type: 'world' }, + { startIndex: 8, type: '' }, + { startIndex: 10, type: '' }, ] ); }); @@ -780,11 +779,11 @@ suite('Editor Model - ModelLine.applyEdits text & tokens', () => { testLineEditTokens( 'Hello world, ciao', [ - { startIndex: 0, type: 'hello', bracket: 0 }, - { startIndex: 5, type: '', bracket: 0 }, - { startIndex: 6, type: 'world', bracket: 0 }, - { startIndex: 11, type: '', bracket: 0 }, - { startIndex: 13, type: '', bracket: 0 }, + { startIndex: 0, type: 'hello' }, + { startIndex: 5, type: '' }, + { startIndex: 6, type: 'world' }, + { startIndex: 11, type: '' }, + { startIndex: 13, type: '' }, ], [{ startColumn: 1, @@ -799,11 +798,11 @@ suite('Editor Model - ModelLine.applyEdits text & tokens', () => { }], 'Hi wmy friends, ciao', [ - { startIndex: 0, type: 'hello', bracket: 0 }, - { startIndex: 2, type: '', bracket: 0 }, - { startIndex: 3, type: 'world', bracket: 0 }, - { startIndex: 14, type: '', bracket: 0 }, - { startIndex: 16, type: '', bracket: 0 }, + { startIndex: 0, type: 'hello' }, + { startIndex: 2, type: '' }, + { startIndex: 3, type: 'world' }, + { startIndex: 14, type: '' }, + { startIndex: 16, type: '' }, ] ); }); @@ -825,9 +824,9 @@ suite('Editor Model - ModelLine.split text & tokens', () => { testLineSplitTokens( 'abcd efgh', [ - { startIndex: 0, type: '1', bracket: 0 }, - { startIndex: 4, type: '2', bracket: 1 }, - { startIndex: 5, type: '3', bracket: -1 } + { startIndex: 0, type: '1' }, + { startIndex: 4, type: '2' }, + { startIndex: 5, type: '3' } ], 1, '', @@ -840,17 +839,17 @@ suite('Editor Model - ModelLine.split text & tokens', () => { testLineSplitTokens( 'abcd efgh', [ - { startIndex: 0, type: '1', bracket: 0 }, - { startIndex: 4, type: '2', bracket: 1 }, - { startIndex: 5, type: '3', bracket: -1 } + { startIndex: 0, type: '1' }, + { startIndex: 4, type: '2' }, + { startIndex: 5, type: '3' } ], 10, 'abcd efgh', '', [ - { startIndex: 0, type: '1', bracket: 0 }, - { startIndex: 4, type: '2', bracket: 1 }, - { startIndex: 5, type: '3', bracket: -1 } + { startIndex: 0, type: '1' }, + { startIndex: 4, type: '2' }, + { startIndex: 5, type: '3' } ] ); }); @@ -859,15 +858,15 @@ suite('Editor Model - ModelLine.split text & tokens', () => { testLineSplitTokens( 'abcd efgh', [ - { startIndex: 0, type: '1', bracket: 0 }, - { startIndex: 4, type: '2', bracket: 1 }, - { startIndex: 5, type: '3', bracket: -1 } + { startIndex: 0, type: '1' }, + { startIndex: 4, type: '2' }, + { startIndex: 5, type: '3' } ], 5, 'abcd', ' efgh', [ - { startIndex: 0, type: '1', bracket: 0 } + { startIndex: 0, type: '1' } ] ); }); @@ -876,16 +875,16 @@ suite('Editor Model - ModelLine.split text & tokens', () => { testLineSplitTokens( 'abcd efgh', [ - { startIndex: 0, type: '1', bracket: 0 }, - { startIndex: 4, type: '2', bracket: 1 }, - { startIndex: 5, type: '3', bracket: -1 } + { startIndex: 0, type: '1' }, + { startIndex: 4, type: '2' }, + { startIndex: 5, type: '3' } ], 6, 'abcd ', 'efgh', [ - { startIndex: 0, type: '1', bracket: 0 }, - { startIndex: 4, type: '2', bracket: 1 } + { startIndex: 0, type: '1' }, + { startIndex: 4, type: '2' } ] ); }); @@ -911,17 +910,17 @@ suite('Editor Model - ModelLine.append text & tokens', () => { testLineAppendTokens( 'abcd efgh', [ - { startIndex: 0, type: '1', bracket: 0 }, - { startIndex: 4, type: '2', bracket: 1 }, - { startIndex: 5, type: '3', bracket: -1 } + { startIndex: 0, type: '1' }, + { startIndex: 4, type: '2' }, + { startIndex: 5, type: '3' } ], '', [], 'abcd efgh', [ - { startIndex: 0, type: '1', bracket: 0 }, - { startIndex: 4, type: '2', bracket: 1 }, - { startIndex: 5, type: '3', bracket: -1 } + { startIndex: 0, type: '1' }, + { startIndex: 4, type: '2' }, + { startIndex: 5, type: '3' } ] ); }); @@ -932,15 +931,15 @@ suite('Editor Model - ModelLine.append text & tokens', () => { [], 'abcd efgh', [ - { startIndex: 0, type: '1', bracket: 0 }, - { startIndex: 4, type: '2', bracket: 1 }, - { startIndex: 5, type: '3', bracket: -1 } + { startIndex: 0, type: '1' }, + { startIndex: 4, type: '2' }, + { startIndex: 5, type: '3' } ], 'abcd efgh', [ - { startIndex: 0, type: '1', bracket: 0 }, - { startIndex: 4, type: '2', bracket: 1 }, - { startIndex: 5, type: '3', bracket: -1 } + { startIndex: 0, type: '1' }, + { startIndex: 4, type: '2' }, + { startIndex: 5, type: '3' } ] ); }); @@ -949,24 +948,24 @@ suite('Editor Model - ModelLine.append text & tokens', () => { testLineAppendTokens( 'abcd efgh', [ - { startIndex: 0, type: '1', bracket: 0 }, - { startIndex: 4, type: '2', bracket: 1 }, - { startIndex: 5, type: '3', bracket: -1 } + { startIndex: 0, type: '1' }, + { startIndex: 4, type: '2' }, + { startIndex: 5, type: '3' } ], 'abcd efgh', [ - { startIndex: 0, type: '4', bracket: 0 }, - { startIndex: 4, type: '5', bracket: 1 }, - { startIndex: 5, type: '6', bracket: -1 } + { startIndex: 0, type: '4' }, + { startIndex: 4, type: '5' }, + { startIndex: 5, type: '6' } ], 'abcd efghabcd efgh', [ - { startIndex: 0, type: '1', bracket: 0 }, - { startIndex: 4, type: '2', bracket: 1 }, - { startIndex: 5, type: '3', bracket: -1 }, - { startIndex: 9, type: '4', bracket: 0 }, - { startIndex: 13, type: '5', bracket: 1 }, - { startIndex: 14, type: '6', bracket: -1 } + { startIndex: 0, type: '1' }, + { startIndex: 4, type: '2' }, + { startIndex: 5, type: '3' }, + { startIndex: 9, type: '4' }, + { startIndex: 13, type: '5' }, + { startIndex: 14, type: '6' } ] ); }); @@ -975,18 +974,18 @@ suite('Editor Model - ModelLine.append text & tokens', () => { testLineAppendTokens( 'abcd ', [ - { startIndex: 0, type: '1', bracket: 0 }, - { startIndex: 4, type: '2', bracket: 1 } + { startIndex: 0, type: '1' }, + { startIndex: 4, type: '2' } ], 'efgh', [ - { startIndex: 0, type: '3', bracket: -1 } + { startIndex: 0, type: '3' } ], 'abcd efgh', [ - { startIndex: 0, type: '1', bracket: 0 }, - { startIndex: 4, type: '2', bracket: 1 }, - { startIndex: 5, type: '3', bracket: -1 } + { startIndex: 0, type: '1' }, + { startIndex: 4, type: '2' }, + { startIndex: 5, type: '3' } ] ); }); @@ -995,18 +994,18 @@ suite('Editor Model - ModelLine.append text & tokens', () => { testLineAppendTokens( 'abcd', [ - { startIndex: 0, type: '1', bracket: 0 }, + { startIndex: 0, type: '1' }, ], ' efgh', [ - { startIndex: 0, type: '2', bracket: 1 }, - { startIndex: 1, type: '3', bracket: -1 } + { startIndex: 0, type: '2' }, + { startIndex: 1, type: '3' } ], 'abcd efgh', [ - { startIndex: 0, type: '1', bracket: 0 }, - { startIndex: 4, type: '2', bracket: 1 }, - { startIndex: 5, type: '3', bracket: -1 } + { startIndex: 0, type: '1' }, + { startIndex: 4, type: '2' }, + { startIndex: 5, type: '3' } ] ); }); diff --git a/src/vs/editor/test/common/model/model.test.ts b/src/vs/editor/test/common/model/model.test.ts index 1742d7a5bba..67af0482873 100644 --- a/src/vs/editor/test/common/model/model.test.ts +++ b/src/vs/editor/test/common/model/model.test.ts @@ -16,8 +16,8 @@ import {EditOperation} from 'vs/editor/common/core/editOperation'; function isNotABracket(model, lineNumber, column) { var match = model.matchBracket(new Position(lineNumber, column)); - assert.equal(match.isAccurate, true); - assert.equal(match.brackets, null); + assert.equal(match.isAccurate, true, 'is not matching brackets at ' + lineNumber + ', ' + column); + assert.equal(match.brackets, null, 'is not matching brackets at ' + lineNumber + ', ' + column); } function isBracket(model, lineNumber1, column11, column12, lineNumber2, column21, column22) { @@ -28,7 +28,7 @@ function isBracket(model, lineNumber1, column11, column12, lineNumber2, column21 new Range(lineNumber2, column21, lineNumber2, column22) ], isAccurate: true - }); + }, 'is matching brackets at ' + lineNumber1 + ', ' + column11); } diff --git a/src/vs/editor/test/common/modes/autoIndentation.test.ts b/src/vs/editor/test/common/modes/autoIndentation.test.ts index e507f333e09..cff5adcec81 100644 --- a/src/vs/editor/test/common/modes/autoIndentation.test.ts +++ b/src/vs/editor/test/common/modes/autoIndentation.test.ts @@ -5,98 +5,13 @@ 'use strict'; import assert = require('assert'); -import autoIndentation = require('vs/editor/common/modes/autoIndentation'); +import autoIndentation = require('vs/editor/common/modes/supports/electricCharacter'); import modes = require('vs/editor/common/modes'); import modesUtil = require('vs/editor/test/common/modesTestUtils'); suite('Editor Modes - Auto Indentation', () => { - - test('Bracket Pairs', () => { - var brackets = new autoIndentation.Brackets([ - { tokenType:'b', open: '{', close: '}', isElectric: false }, - { tokenType:'a', open: '[', close: ']', isElectric: true }, - { tokenType:'p', open: '(', close: ')', isElectric: false } - ]); - - assert.equal(brackets.stringIsBracket(''), false); - assert.equal(brackets.stringIsBracket('<'), false); - assert.equal(brackets.stringIsBracket('{'), true); - assert.equal(brackets.stringIsBracket('}'), true); - assert.equal(brackets.stringIsBracket('['), true); - assert.equal(brackets.stringIsBracket(']'), true); - assert.equal(brackets.stringIsBracket('('), true); - assert.equal(brackets.stringIsBracket(')'), true); - }); - - test('Case insensitive regular expressions', () => { - var brackets = new autoIndentation.Brackets([], - [ - { tokenType: 'tag-$1', - openTrigger: '>', - open: /<(\w[\w\d]*)(\s+.*[^\/]>|\s*>)[^<]*$/i, - closeComplete: '', - closeTrigger: '>', - close: /<\/(\w[\w\d]*)\s*>$/i } - ], null, true); - - assert.equal(brackets.onEnter(modesUtil.createLineContextFromTokenText([ - { text: '', type: '' } - ]), 0), null); - assert.equal(brackets.onEnter(modesUtil.createLineContextFromTokenText([ - { text: '<', type: 'delim' }, - { text: 'tag', type: 'tag', bracket: modes.Bracket.Open }, - { text: '>', type: 'delim' }, - ]), 5).indentAction, modes.IndentAction.Indent); - assert.equal(brackets.onEnter(modesUtil.createLineContextFromTokenText([ - { text: '<', type: 'delim' }, - { text: 'tag', type: 'tag', bracket: modes.Bracket.Open }, - { text: '>', type: 'delim' }, - { text: '', type: 'delim' }, - ]), 5).indentAction, modes.IndentAction.IndentOutdent); - - assert.equal(brackets.onElectricCharacter(modesUtil.createLineContextFromTokenText([ - { text: '<', type: 'delim' }, - { text: 'tag', type: 'tag', bracket: modes.Bracket.Open }, - { text: '>', type: 'delim' } - ]), 4).appendText, ''); - - assert.equal(brackets.onElectricCharacter(modesUtil.createLineContextFromTokenText([ - { text: '', type: 'delim' }, - ]), 5).matchBracketType, 'tag-tag'); - }); - - - test('Case insensitive regular expressions and case matching auto completion', () => { - var brackets = new autoIndentation.Brackets([], - [ - { tokenType: 'sub', - openTrigger: 'b', - open: /^(|.*\s)sub/i, - closeComplete: ' end sub', - matchCase: true - } - ], null, true); - - assert.equal(brackets.onElectricCharacter(modesUtil.createLineContextFromTokenText([ - { text: 'sub', type: '' } - ]), 2).appendText, ' end sub'); - - assert.equal(brackets.onElectricCharacter(modesUtil.createLineContextFromTokenText([ - { text: 'Sub', type: '' } - ]), 2).appendText, ' End Sub'); - - assert.equal(brackets.onElectricCharacter(modesUtil.createLineContextFromTokenText([ - { text: 'SUB', type: '' } - ]), 2).appendText, ' END SUB'); - }); - - 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([ diff --git a/src/vs/editor/test/common/modes/textToHtmlTokenizer.test.ts b/src/vs/editor/test/common/modes/textToHtmlTokenizer.test.ts index 7451b1737cc..6f35eef8ea0 100644 --- a/src/vs/editor/test/common/modes/textToHtmlTokenizer.test.ts +++ b/src/vs/editor/test/common/modes/textToHtmlTokenizer.test.ts @@ -9,6 +9,7 @@ import {tokenizeToHtmlContent} from 'vs/editor/common/modes/textToHtmlTokenizer' import {AbstractState} from 'vs/editor/common/modes/abstractState'; import modes = require('vs/editor/common/modes'); import supports = require('vs/editor/common/modes/supports'); +import {TokenizationSupport} from 'vs/editor/common/modes/supports/tokenizationSupport'; suite('Editor Modes - textToHtmlTokenizer', () => { test('TextToHtmlTokenizer', () => { @@ -76,7 +77,7 @@ class Mode implements modes.IMode { public tokenizationSupport: modes.ITokenizationSupport; constructor() { - this.tokenizationSupport = new supports.TokenizationSupport(this, { + this.tokenizationSupport = new TokenizationSupport(this, { getInitialState: () => new State(this) }, false, false); } diff --git a/src/vs/editor/test/common/modes/tokenization.test.ts b/src/vs/editor/test/common/modes/tokenization.test.ts index 3854a550cef..24b04e9dccb 100644 --- a/src/vs/editor/test/common/modes/tokenization.test.ts +++ b/src/vs/editor/test/common/modes/tokenization.test.ts @@ -15,6 +15,7 @@ import {tokenizeToHtmlContent} from 'vs/editor/common/modes/textToHtmlTokenizer' import {createLineContext} from 'vs/editor/test/common/modesTestUtils'; import EditorCommon = require('vs/editor/common/editorCommon'); import {IDisposable, empty as EmptyDisposable} from 'vs/base/common/lifecycle'; +import {TokenizationSupport, IEnteringNestedModeData, ILeavingNestedModeData} from 'vs/editor/common/modes/supports/tokenizationSupport'; export class State extends AbstractState { @@ -36,7 +37,7 @@ export class Mode implements modes.IMode { public tokenizationSupport: modes.ITokenizationSupport; constructor() { - this.tokenizationSupport = new supports.TokenizationSupport(this, { + this.tokenizationSupport = new TokenizationSupport(this, { getInitialState: () => new State(this) }, false, false); } @@ -111,7 +112,7 @@ export class SwitchingMode implements modes.IMode { constructor(id:string, descriptor:IModeSwitchingDescriptor) { this._id = id; this._switchingModeDescriptor = descriptor; - this.tokenizationSupport = new supports.TokenizationSupport(this, this, true, false); + this.tokenizationSupport = new TokenizationSupport(this, this, true, false); } public getId():string { @@ -144,7 +145,7 @@ export class SwitchingMode implements modes.IMode { } } - public getNestedMode(state:modes.IState): supports.IEnteringNestedModeData { + public getNestedMode(state:modes.IState): IEnteringNestedModeData { var s = state; return { mode: this._switchingModeDescriptor[s.lastWord].mode, @@ -152,7 +153,7 @@ export class SwitchingMode implements modes.IMode { }; } - public getLeavingNestedModeData(line:string, state:modes.IState):supports.ILeavingNestedModeData { + public getLeavingNestedModeData(line:string, state:modes.IState): ILeavingNestedModeData { var s = state; var endChar = this._switchingModeDescriptor[s.lastWord].endCharacter; var endCharPosition = line.indexOf(endChar); @@ -177,7 +178,6 @@ function assertTokens(actual:modes.IToken[], expected:ITestToken[], message?:str for (var i = 0; i < expected.length; i++) { assert.equal(actual[i].startIndex, expected[i].startIndex, 'startIndex mismatch'); assert.equal(actual[i].type, expected[i].type, 'type mismatch'); - assert.equal(actual[i].bracket, expected[i].bracket ? expected[i].bracket : modes.Bracket.None, 'bracket mismatch'); } }; diff --git a/src/vs/editor/test/common/modesTestUtils.ts b/src/vs/editor/test/common/modesTestUtils.ts index cdb3a85aa35..28ca0fbafa5 100644 --- a/src/vs/editor/test/common/modesTestUtils.ts +++ b/src/vs/editor/test/common/modesTestUtils.ts @@ -4,51 +4,49 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import modes = require('vs/editor/common/modes'); +import Modes = require('vs/editor/common/modes'); import {Arrays} from 'vs/editor/common/core/arrays'; +import {RichEditSupport} from 'vs/editor/common/modes/supports/richEditSupport'; -class SimpleTokenTypeClassificationMode implements modes.IMode { +class SimpleTokenTypeClassificationMode implements Modes.IMode { private _id:string; - public tokenTypeClassificationSupport: modes.ITokenTypeClassificationSupport; - constructor(id:string, tokenTypeClassificationSupport: modes.ITokenTypeClassificationSupport) { + public richEditSupport: Modes.IRichEditSupport; + + constructor(id:string, wordRegExp:RegExp) { this._id = id; - this.tokenTypeClassificationSupport = tokenTypeClassificationSupport; + this.richEditSupport = new RichEditSupport(this._id, { + wordPattern: wordRegExp + }); } public getId(): string { return this._id; } - public toSimplifiedMode(): modes.IMode { + public toSimplifiedMode(): Modes.IMode { return this; } } -export function createMockMode(id:string, wordRegExp:RegExp = null):modes.IMode { - var tokenTypeClassificationSupport: modes.ITokenTypeClassificationSupport; - if (wordRegExp) { - tokenTypeClassificationSupport = { - getWordDefinition: () => wordRegExp - }; - } - return new SimpleTokenTypeClassificationMode(id, tokenTypeClassificationSupport); +export function createMockMode(id:string, wordRegExp:RegExp = null):Modes.IMode { + return new SimpleTokenTypeClassificationMode(id, wordRegExp); } export interface TokenText { text: string; type: string; - bracket?: modes.Bracket; + bracket?: Modes.Bracket; } -export function createLineContextFromTokenText(tokens: TokenText[]): modes.ILineContext { +export function createLineContextFromTokenText(tokens: TokenText[]): Modes.ILineContext { var line = ''; - var processedTokens: modes.IToken[] = []; + var processedTokens: Modes.IToken[] = []; var indexSoFar = 0; for (var i = 0; i < tokens.length; ++i){ - processedTokens.push({ startIndex: indexSoFar, type: tokens[i].type, bracket: (tokens[i].bracket ? tokens[i].bracket : modes.Bracket.None) }); + processedTokens.push({ startIndex: indexSoFar, type: tokens[i].type }); line += tokens[i].text; indexSoFar += tokens[i].text.length; } @@ -56,17 +54,17 @@ export function createLineContextFromTokenText(tokens: TokenText[]): modes.ILine return new TestLineContext(line, processedTokens, null); } -export function createLineContext(line:string, tokens:modes.ILineTokens): modes.ILineContext { +export function createLineContext(line:string, tokens:Modes.ILineTokens): Modes.ILineContext { return new TestLineContext(line, tokens.tokens, tokens.modeTransitions); } -class TestLineContext implements modes.ILineContext { +class TestLineContext implements Modes.ILineContext { - public modeTransitions: modes.IModeTransition[]; + public modeTransitions: Modes.IModeTransition[]; private _line:string; - private _tokens: modes.IToken[]; + private _tokens: Modes.IToken[]; - constructor(line:string, tokens: modes.IToken[], modeTransitions:modes.IModeTransition[]) { + constructor(line:string, tokens: Modes.IToken[], modeTransitions:Modes.IModeTransition[]) { this.modeTransitions = modeTransitions; this._line = line; this._tokens = tokens; @@ -95,10 +93,6 @@ class TestLineContext implements modes.ILineContext { return this._tokens[tokenIndex].type; } - public getTokenBracket(tokenIndex:number): modes.Bracket { - return this._tokens[tokenIndex].bracket; - } - public findIndexOfOffset(offset:number): number { return Arrays.findIndexInSegmentsArray(this._tokens, offset); } diff --git a/src/vs/editor/test/common/modesUtil.ts b/src/vs/editor/test/common/modesUtil.ts index 1d67d1e4f18..bb9eecd0189 100644 --- a/src/vs/editor/test/common/modesUtil.ts +++ b/src/vs/editor/test/common/modesUtil.ts @@ -25,35 +25,10 @@ export interface ITestItem { tokens: IRelaxedToken[]; } - -export interface IOnEnterFunc { - (line:string, offset:number, state?:modes.IState): modes.IEnterAction; -} - -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.electricCharacterSupport.onElectricCharacter(createLineContext(line, lineTokens), offset); - }; -} - export function assertWords(actual:string[], expected:string[], message?:string): void { assert.deepEqual(actual, expected, message); } -export function createOnEnter(mode:modes.IMode): IOnEnterFunc { - return function onEnter(line:string, offset:number, state?:modes.IState): modes.IEnterAction { - state = state || mode.tokenizationSupport.getInitialState(); - var lineTokens = mode.tokenizationSupport.tokenize(line, state); - return mode.electricCharacterSupport.onEnter(createLineContext(line, lineTokens), offset); - }; -} - export function load(modeId: string, preloadModes: string[] = [] ): TPromise { var toLoad:string[] = [].concat(preloadModes).concat([modeId]); @@ -72,7 +47,7 @@ export function assertTokenization(tokenizationSupport: modes.ITokenizationSuppo assert.ok(true, tests[i].line); var result = tokenizationSupport.tokenize(tests[i].line, state); if (tests[i].tokens) { - assert.deepEqual(generateRelaxedTokens(result.tokens, tests[i].tokens), tests[i].tokens, JSON.stringify(result.tokens, null, '\t')); + assert.deepEqual(toRelaxedTokens(result.tokens), toRelaxedTokens(tests[i].tokens), JSON.stringify(result.tokens, null, '\t')); } state = result.endState; @@ -103,13 +78,13 @@ class SimpleMode implements modes.IMode { } } -export function createOnEnterAsserter(modeId:string, onEnterSupport: modes.IOnEnterSupport): IOnEnterAsserter { +export function createOnEnterAsserter(modeId:string, richEditSupport: modes.IRichEditSupport): IOnEnterAsserter { var assertOne = (oneLineAboveText:string, beforeText:string, afterText:string, expected: modes.IndentAction) => { var model = new Model( [ oneLineAboveText, beforeText + afterText ].join('\n'), new SimpleMode(modeId) ); - var actual = onEnterSupport.onEnter(model, { lineNumber: 2, column: beforeText.length + 1 }); + var actual = richEditSupport.onEnter.onEnter(model, { lineNumber: 2, column: beforeText.length + 1 }); if (expected === modes.IndentAction.None) { assert.equal(actual, null, oneLineAboveText + '\\n' + beforeText + '|' + afterText); } else { @@ -151,24 +126,13 @@ export function executeMonarchTokenizationTests(name:string, language:monarchTyp executeTests(tokenizationSupport, tests); } -function generateRelaxedTokens(actualTokens: modes.IToken[], expectedTokens: IRelaxedToken[]): IRelaxedToken[] { - var r = actualTokens.map((token, index) => { - // Remove bracket if it's missing in expectedTokens too - if (expectedTokens[index] && typeof expectedTokens[index].bracket !== 'undefined') { - return { - startIndex: token.startIndex, - type: token.type, - bracket: token.bracket - }; - } else { - return { - startIndex: token.startIndex, - type: token.type - }; - } +function toRelaxedTokens(tokens: modes.IToken[]): IRelaxedToken[] { + return tokens.map((t) => { + return { + startIndex: t.startIndex, + type: t.type + }; }); - - return r; } function executeTest(tokenizationSupport: modes.ITokenizationSupport, tests:ITestItem[]): void { @@ -187,5 +151,5 @@ function executeTest(tokenizationSupport: modes.ITokenizationSupport, tests:ITes } function assertTokens(actual:modes.IToken[], expected:IRelaxedToken[], message?:string): void { - assert.deepEqual(generateRelaxedTokens(actual, expected), expected, message + ': ' + JSON.stringify(actual, null, '\t')); + assert.deepEqual(toRelaxedTokens(actual), toRelaxedTokens(expected), message + ': ' + JSON.stringify(actual, null, '\t')); } \ No newline at end of file diff --git a/src/vs/editor/test/common/testModes.ts b/src/vs/editor/test/common/testModes.ts index 5ab4205a356..57607805b6c 100644 --- a/src/vs/editor/test/common/testModes.ts +++ b/src/vs/editor/test/common/testModes.ts @@ -9,6 +9,8 @@ import supports = require('vs/editor/common/modes/supports'); import {AbstractMode} from 'vs/editor/common/modes/abstractMode'; import {AbstractState} from 'vs/editor/common/modes/abstractState'; import {AbstractModeWorker} from 'vs/editor/common/modes/abstractModeWorker'; +import {RichEditSupport} from 'vs/editor/common/modes/supports/richEditSupport'; +import {TokenizationSupport} from 'vs/editor/common/modes/supports/tokenizationSupport'; export class CommentState extends AbstractState { @@ -32,21 +34,19 @@ export class CommentState extends AbstractState { export class CommentMode extends AbstractMode { - private commentsConfig:modes.ICommentsConfiguration; - public tokenizationSupport: modes.ITokenizationSupport; + public richEditSupport: modes.IRichEditSupport; constructor(commentsConfig:modes.ICommentsConfiguration) { super({ id: 'tests.commentMode', workerParticipants: [] }, null, null); - this.commentsConfig = commentsConfig; - this.tokenizationSupport = new supports.TokenizationSupport(this, { + this.tokenizationSupport = new TokenizationSupport(this, { getInitialState: () => new CommentState(this, 0) }, false, false); - } - public getCommentsConfiguration():modes.ICommentsConfiguration { - return this.commentsConfig; + this.richEditSupport = { + comments:commentsConfig + }; } } @@ -105,7 +105,7 @@ export class ModelMode1 extends TestingMode { constructor() { super(); this.calledFor = []; - this.tokenizationSupport = new supports.TokenizationSupport(this, { + this.tokenizationSupport = new TokenizationSupport(this, { getInitialState: () => new ModelState1(this) }, false, false); } @@ -146,7 +146,7 @@ export class ModelMode2 extends TestingMode { constructor() { super(); this.calledFor = null; - this.tokenizationSupport = new supports.TokenizationSupport(this, { + this.tokenizationSupport = new TokenizationSupport(this, { getInitialState: () => new ModelState2(this, '') }, false, false); } @@ -220,12 +220,22 @@ export class BracketState extends AbstractState { export class BracketMode extends TestingMode { public tokenizationSupport: modes.ITokenizationSupport; + public richEditSupport: modes.IRichEditSupport; constructor() { super(); - this.tokenizationSupport = new supports.TokenizationSupport(this, { + this.tokenizationSupport = new TokenizationSupport(this, { getInitialState: () => new BracketState(this) }, false, false); + this.richEditSupport = new RichEditSupport(this.getId(), { + __electricCharacterSupport: { + brackets: [ + { tokenType: 'asd', open: '{', close: '}', isElectric: true }, + { tokenType: 'qwe', open: '[', close: ']', isElectric: true }, + { tokenType: 'zxc', open: '(', close: ')', isElectric: true } + ] + } + }); } } @@ -268,7 +278,7 @@ export class NMode extends TestingMode { constructor(n:number) { this.n = n; super(); - this.tokenizationSupport = new supports.TokenizationSupport(this, { + this.tokenizationSupport = new TokenizationSupport(this, { getInitialState: () => new NState(this, this.n) }, false, false); } diff --git a/src/vs/languages/css/common/css.ts b/src/vs/languages/css/common/css.ts index cc7d1047f58..c7d0db9a37b 100644 --- a/src/vs/languages/css/common/css.ts +++ b/src/vs/languages/css/common/css.ts @@ -20,6 +20,11 @@ import {IMarker} from 'vs/platform/markers/common/markers'; import {OnEnterSupport} from 'vs/editor/common/modes/supports/onEnter'; import {IInstantiationService} from 'vs/platform/instantiation/common/instantiation'; import {IThreadService} from 'vs/platform/thread/common/thread'; +import {RichEditSupport} from 'vs/editor/common/modes/supports/richEditSupport'; +import {TokenizationSupport} from 'vs/editor/common/modes/supports/tokenizationSupport'; +import {DeclarationSupport} from 'vs/editor/common/modes/supports/declarationSupport'; +import {ReferenceSupport} from 'vs/editor/common/modes/supports/referenceSupport'; +import {SuggestSupport} from 'vs/editor/common/modes/supports/suggestSupport'; export enum States { Selector, @@ -279,8 +284,7 @@ export class State extends AbstractState { export class CSSMode extends AbstractMode { public tokenizationSupport: Modes.ITokenizationSupport; - public electricCharacterSupport: Modes.IElectricCharacterSupport; - public characterPairSupport: Modes.ICharacterPairSupport; + public richEditSupport: Modes.IRichEditSupport; public referenceSupport: Modes.IReferenceSupport; public logicalSelectionSupport: Modes.ILogicalSelectionSupport; @@ -290,7 +294,6 @@ export class CSSMode extends AbstractMode { public declarationSupport: Modes.IDeclarationSupport; public suggestSupport: Modes.ISuggestSupport; public quickFixSupport: Modes.IQuickFixSupport; - public onEnterSupport: Modes.IOnEnterSupport; constructor( descriptor:Modes.IModeDescriptor, @@ -299,45 +302,57 @@ export class CSSMode extends AbstractMode { ) { super(descriptor, instantiationService, threadService); - this.tokenizationSupport = new supports.TokenizationSupport(this, { + this.tokenizationSupport = new TokenizationSupport(this, { getInitialState: () => new State(this, States.Selector, false, null, false, 0) }, false, false); - this.electricCharacterSupport = new supports.BracketElectricCharacterSupport(this, { brackets: [ - { tokenType: 'punctuation.bracket.css', open: '{', close: '}', isElectric: true } - ] }); - this.occurrencesSupport = this; - this.extraInfoSupport = this; - this.referenceSupport = new supports.ReferenceSupport(this, { - tokens: [cssTokenTypes.TOKEN_PROPERTY + '.css', cssTokenTypes.TOKEN_VALUE + '.css', cssTokenTypes.TOKEN_SELECTOR_TAG + '.css'], - findReferences: (resource, position, /*unused*/includeDeclaration) => this.findReferences(resource, position)}); - this.logicalSelectionSupport = this; - this.outlineSupport = this; - this.declarationSupport = new supports.DeclarationSupport(this, { - tokens: [cssTokenTypes.TOKEN_VALUE + '.css'], - findDeclaration: (resource, position) => this.findDeclaration(resource, position)}); + this.richEditSupport = new RichEditSupport(this.getId(), { + // TODO@Martin: This definition does not work with umlauts for example + wordPattern: /(#?-?\d*\.\d\w*%?)|((::|[@#.!:])?[\w-?]+%?)|::|[@#.!:]/g, - this.characterPairSupport = new supports.CharacterPairSupport(this, { - autoClosingPairs: - [ { open: '{', close: '}' }, + comments: { + blockComment: ['/*', '*/'] + }, + + brackets: [ + ['{', '}'], + ['[', ']'], + ['(', ')'] + ], + + __electricCharacterSupport: { + brackets: [ + { tokenType: 'punctuation.bracket.css', open: '{', close: '}', isElectric: true } + ] + }, + + __characterPairSupport: { + autoClosingPairs: [ + { open: '{', close: '}' }, { open: '[', close: ']' }, { open: '(', close: ')' }, { open: '"', close: '"', notIn: ['string'] }, { open: '\'', close: '\'', notIn: ['string'] } - ]}); + ] + } + }); - this.suggestSupport = new supports.SuggestSupport(this, { + this.occurrencesSupport = this; + this.extraInfoSupport = this; + this.referenceSupport = new ReferenceSupport(this.getId(), { + tokens: [cssTokenTypes.TOKEN_PROPERTY + '.css', cssTokenTypes.TOKEN_VALUE + '.css', cssTokenTypes.TOKEN_SELECTOR_TAG + '.css'], + findReferences: (resource, position, /*unused*/includeDeclaration) => this.findReferences(resource, position)}); + this.logicalSelectionSupport = this; + this.outlineSupport = this; + this.declarationSupport = new DeclarationSupport(this.getId(), { + tokens: [cssTokenTypes.TOKEN_VALUE + '.css'], + findDeclaration: (resource, position) => this.findDeclaration(resource, position)}); + + this.suggestSupport = new SuggestSupport(this.getId(), { triggerCharacters: [' ', ':'], excludeTokens: ['comment.css', 'string.css'], suggest: (resource, position) => this.suggest(resource, position)}); - this.onEnterSupport = new OnEnterSupport(this.getId(), { - brackets: [ - { open: '(', close: ')' }, - { open: '{', close: '}' }, - { open: '[', close: ']' } - ] - }); this.quickFixSupport = this; } @@ -376,15 +391,6 @@ export class CSSMode extends AbstractMode { return this._worker((w) => w.getOutline(resource)); } - public getCommentsConfiguration():Modes.ICommentsConfiguration { - return { blockCommentStartToken: '/*', blockCommentEndToken: '*/' }; - } - - // TODO@Martin: This definition does not work with umlauts for example - public getWordDefinition():RegExp { - return /(#?-?\d*\.\d\w*%?)|((::|[@#.!:])?[\w-?]+%?)|::|[@#.!:]/g; - } - static $findColorDeclarations = OneWorkerAttr(CSSMode, CSSMode.prototype.findColorDeclarations); public findColorDeclarations(resource:URI):WinJS.TPromise<{range:EditorCommon.IRange; value:string; }[]> { return this._worker((w) => w.findColorDeclarations(resource)); diff --git a/src/vs/languages/css/common/cssWorker.ts b/src/vs/languages/css/common/cssWorker.ts index fcf0f5cec35..41b99fb7114 100644 --- a/src/vs/languages/css/common/cssWorker.ts +++ b/src/vs/languages/css/common/cssWorker.ts @@ -26,6 +26,7 @@ import supports = require('vs/editor/common/modes/supports'); import {IMarker, IMarkerData} from 'vs/platform/markers/common/markers'; import {IMarkerService} from 'vs/platform/markers/common/markers'; import {IResourceService} from 'vs/editor/common/services/resourceService'; +import {WorkerInplaceReplaceSupport} from 'vs/editor/common/modes/supports/inplaceReplaceSupport'; export class CSSWorker extends AbstractModeWorker { @@ -44,7 +45,7 @@ export class CSSWorker extends AbstractModeWorker { } protected _createInPlaceReplaceSupport(): Modes.IInplaceReplaceSupport { - return new supports.WorkerInplaceReplaceSupport(this.resourceService, this); + return new WorkerInplaceReplaceSupport(this.resourceService, this); } public createLanguageService(resourceService:IResourceService, modeId:string): languageService.CSSLanguageService { diff --git a/src/vs/languages/css/test/common/css.test.ts b/src/vs/languages/css/test/common/css.test.ts index 9d737d39f0b..886b3d2c738 100644 --- a/src/vs/languages/css/test/common/css.test.ts +++ b/src/vs/languages/css/test/common/css.test.ts @@ -21,8 +21,8 @@ suite('CSS Colorizing', () => { suiteSetup((done) => { modesUtil.load('css').then(mode => { tokenizationSupport = mode.tokenizationSupport; - assertOnEnter = modesUtil.createOnEnterAsserter(mode.getId(), mode.onEnterSupport); - wordDefinition = mode.tokenTypeClassificationSupport.getWordDefinition(); + assertOnEnter = modesUtil.createOnEnterAsserter(mode.getId(), mode.richEditSupport); + wordDefinition = mode.richEditSupport.wordDefinition; done(); }); }); diff --git a/src/vs/languages/handlebars/common/handlebars.ts b/src/vs/languages/handlebars/common/handlebars.ts index 585e03dd6e6..96e6ff8e43d 100644 --- a/src/vs/languages/handlebars/common/handlebars.ts +++ b/src/vs/languages/handlebars/common/handlebars.ts @@ -14,6 +14,9 @@ import htmlWorker = require('vs/languages/html/common/htmlWorker'); import {IInstantiationService} from 'vs/platform/instantiation/common/instantiation'; import {IThreadService} from 'vs/platform/thread/common/thread'; import {IModeService} from 'vs/editor/common/services/modeService'; +import {RichEditSupport} from 'vs/editor/common/modes/supports/richEditSupport'; +import {createWordRegExp} from 'vs/editor/common/modes/abstractMode'; +import {ILeavingNestedModeData} from 'vs/editor/common/modes/supports/tokenizationSupport'; export enum States { HTML, @@ -114,28 +117,51 @@ export class HandlebarsMode extends htmlMode.HTMLMode { ) { super(descriptor, instantiationService, threadService, modeService); - this.onEnterSupport = new OnEnterSupport(this.getId(), { - brackets: [ - { open: '' }, - { open: '{{', close: '}}' }, - ] - }); + this.formattingSupport = null; } - public asyncCtor(): winjs.Promise { - return super.asyncCtor().then(() => { - var pairs = this.characterPairSupport.getAutoClosingPairs().slice(0).concat([ - { open: '{', close: '}'} - ]); + protected _createRichEditSupport(embeddedAutoClosingPairs: Modes.IAutoClosingPair[]): Modes.IRichEditSupport { + return new RichEditSupport(this.getId(), { - this.characterPairSupport = new supports.CharacterPairSupport(this, { - autoClosingPairs: pairs.slice(0), + wordPattern: createWordRegExp('#-?%'), + + comments: { + blockComment: [''] + }, + + brackets: [ + [''], + ['{{', '}}'] + ], + + __electricCharacterSupport: { + brackets: [], + caseInsensitive: true, + embeddedElectricCharacters: ['*', '}', ']', ')'] + }, + + __characterPairSupport: { + autoClosingPairs: embeddedAutoClosingPairs.slice(0).concat([ + { open: '{', close: '}'} + ]), surroundingPairs: [ { open: '<', close: '>' }, { open: '"', close: '"' }, { open: '\'', close: '\'' } ] - }); + }, + + onEnterRules: [ + { + beforeText: new RegExp(`<(?!(?:${htmlMode.EMPTY_ELEMENTS.join("|")}))(\\w[\\w\\d]*)([^/>]*(?!/)>)[^<]*$`, 'i'), + afterText: /^<\/(\w[\w\d]*)\s*>$/i, + action: { indentAction: Modes.IndentAction.IndentOutdent } + }, + { + beforeText: new RegExp(`<(?!(?:${htmlMode.EMPTY_ELEMENTS.join("|")}))(\\w[\\w\\d]*)([^/>]*(?!/)>)[^<]*$`, 'i'), + action: { indentAction: Modes.IndentAction.Indent } + } + ], }); } @@ -143,7 +169,7 @@ export class HandlebarsMode extends htmlMode.HTMLMode { return new HandlebarsState(this, htmlMode.States.Content, States.HTML, '', '', '', '', ''); } - public getLeavingNestedModeData(line:string, state:Modes.IState):supports.ILeavingNestedModeData { + public getLeavingNestedModeData(line:string, state:Modes.IState):ILeavingNestedModeData { var leavingNestedModeData = super.getLeavingNestedModeData(line, state); if (leavingNestedModeData) { leavingNestedModeData.stateAfterNestedMode = new HandlebarsState(this, htmlMode.States.Content, States.HTML, '', '', '', '', ''); diff --git a/src/vs/languages/html/common/html.ts b/src/vs/languages/html/common/html.ts index b6da9284631..c549aca3ba1 100644 --- a/src/vs/languages/html/common/html.ts +++ b/src/vs/languages/html/common/html.ts @@ -20,8 +20,15 @@ import {IInstantiationService} from 'vs/platform/instantiation/common/instantiat import {IThreadService } from 'vs/platform/thread/common/thread'; import * as htmlTokenTypes from 'vs/languages/html/common/htmlTokenTypes'; import {EMPTY_ELEMENTS} from 'vs/languages/html/common/htmlEmptyTagsShared'; +import {RichEditSupport} from 'vs/editor/common/modes/supports/richEditSupport'; +import {TokenizationSupport, IEnteringNestedModeData, ILeavingNestedModeData, ITokenizationCustomization} from 'vs/editor/common/modes/supports/tokenizationSupport'; +// import {DeclarationSupport} from 'vs/editor/common/modes/supports/declarationSupport'; +import {ReferenceSupport} from 'vs/editor/common/modes/supports/referenceSupport'; +import {ParameterHintsSupport} from 'vs/editor/common/modes/supports/parameterHintsSupport'; +import {SuggestSupport} from 'vs/editor/common/modes/supports/suggestSupport'; export { htmlTokenTypes }; // export to be used by Razor. We are the main module, so Razor should get ot from use. +export { EMPTY_ELEMENTS }; // export to be used by Razor. We are the main module, so Razor should get ot from use. export enum States { Content, @@ -265,11 +272,10 @@ export class State extends AbstractState { } } -export class HTMLMode extends AbstractMode implements supports.ITokenizationCustomization { +export class HTMLMode extends AbstractMode implements ITokenizationCustomization { public tokenizationSupport: Modes.ITokenizationSupport; - public electricCharacterSupport: Modes.IElectricCharacterSupport; - public characterPairSupport: Modes.ICharacterPairSupport; + public richEditSupport: Modes.IRichEditSupport; public extraInfoSupport:Modes.IExtraInfoSupport; public occurrencesSupport:Modes.IOccurrencesSupport; @@ -278,7 +284,6 @@ export class HTMLMode extends AbstractMode i public formattingSupport: Modes.IFormattingSupport; public parameterHintsSupport: Modes.IParameterHintsSupport; public suggestSupport: Modes.ISuggestSupport; - public onEnterSupport: Modes.IOnEnterSupport; private modeService:IModeService; @@ -292,46 +297,29 @@ export class HTMLMode extends AbstractMode i this.modeService = modeService; - this.tokenizationSupport = new supports.TokenizationSupport(this, this, true, true); - this.electricCharacterSupport = new supports.BracketElectricCharacterSupport(this, - { - brackets: [], - regexBrackets:[ - { tokenType: htmlTokenTypes.getTag('$1'), - open: new RegExp(`<(?!(?:${EMPTY_ELEMENTS.join("|")}))(\\w[\\w\\d]*)([^/>]*(?!/)>)[^<]*$`, 'i'), - closeComplete: '', - close: /<\/(\w[\w\d]*)\s*>$/i }], - caseInsensitive:true, - embeddedElectricCharacters: ['*', '}', ']', ')'] - }); + this.tokenizationSupport = new TokenizationSupport(this, this, true, true); this.formattingSupport = this; this.extraInfoSupport = this; this.occurrencesSupport = this; - this.referenceSupport = new supports.ReferenceSupport(this, { + this.referenceSupport = new ReferenceSupport(this.getId(), { tokens: ['invalid'], findReferences: (resource, position, includeDeclaration) => this.findReferences(resource, position, includeDeclaration)}); this.logicalSelectionSupport = this; - this.parameterHintsSupport = new supports.ParameterHintsSupport(this, { + this.parameterHintsSupport = new ParameterHintsSupport(this.getId(), { triggerCharacters: ['(', ','], excludeTokens: ['*'], getParameterHints: (resource, position) => this.getParameterHints(resource, position)}); // TODO@Alex TODO@Joh: there is something off about declaration support of embedded JS in HTML - // this.declarationSupport = new supports.DeclarationSupport(this, { + // this.declarationSupport = new DeclarationSupport(this, { // tokens: ['invalid'], // findDeclaration: (resource, position) => this.findDeclaration(resource, position)}); - this.suggestSupport = new supports.SuggestSupport(this, { + this.suggestSupport = new SuggestSupport(this.getId(), { triggerCharacters: ['.', ':', '<', '"', '=', '/'], excludeTokens: ['comment'], suggest: (resource, position) => this.suggest(resource, position)}); - - this.onEnterSupport = new OnEnterSupport(this.getId(), { - brackets: [ - { open: '' } - ] - }); } public asyncCtor(): winjs.Promise { @@ -340,13 +328,52 @@ export class HTMLMode extends AbstractMode i this.modeService.getOrCreateMode('text/css') ]).then((embeddableModes) => { var autoClosingPairs = this._getAutoClosingPairs(embeddableModes); + this.richEditSupport = this._createRichEditSupport(autoClosingPairs); + }); + } - this.characterPairSupport = new supports.CharacterPairSupport(this, { - autoClosingPairs: autoClosingPairs.slice(0), + protected _createRichEditSupport(embeddedAutoClosingPairs: Modes.IAutoClosingPair[]): Modes.IRichEditSupport { + return new RichEditSupport(this.getId(), { + + wordPattern: createWordRegExp('#-?%'), + + comments: { + blockComment: [''] + }, + + brackets: [ + [''], + ['<', '>'], + ], + + __electricCharacterSupport: { + brackets: [ + { tokenType: 'bla', open: '', isElectric: true }, + { tokenType: 'bla', open: '<', close: '>', isElectric: true } + ], + caseInsensitive: true, + embeddedElectricCharacters: ['*', '}', ']', ')'] + }, + + __characterPairSupport: { + autoClosingPairs: embeddedAutoClosingPairs.slice(0), surroundingPairs: [ - { open: '"', close: '"' }, - { open: '\'', close: '\'' } - ]}); + { open: '"', close: '"' }, + { open: '\'', close: '\'' } + ] + }, + + onEnterRules: [ + { + beforeText: new RegExp(`<(?!(?:${EMPTY_ELEMENTS.join("|")}))(\\w[\\w\\d]*)([^/>]*(?!/)>)[^<]*$`, 'i'), + afterText: /^<\/(\w[\w\d]*)\s*>$/i, + action: { indentAction: Modes.IndentAction.IndentOutdent } + }, + { + beforeText: new RegExp(`<(?!(?:${EMPTY_ELEMENTS.join("|")}))(\\w[\\w\\d]*)([^/>]*(?!/)>)[^<]*$`, 'i'), + action: { indentAction: Modes.IndentAction.Indent } + } + ], }); } @@ -372,12 +399,13 @@ export class HTMLMode extends AbstractMode i } private _collectAutoClosingPairs(result:{[key:string]:string;}, mode:Modes.IMode): void { - if (mode && mode.characterPairSupport) { - var acp = mode.characterPairSupport.getAutoClosingPairs(); - if (acp !== null) { - for(var i = 0; i < acp.length; i++) { - result[acp[i].open] = acp[i].close; - } + if (!mode || !mode.richEditSupport || !mode.richEditSupport.characterPair) { + return; + } + var acp = mode.richEditSupport.characterPair.getAutoClosingPairs(); + if (acp !== null) { + for(var i = 0; i < acp.length; i++) { + result[acp[i].open] = acp[i].close; } } } @@ -392,7 +420,7 @@ export class HTMLMode extends AbstractMode i return state instanceof State && (state).kind === States.WithinEmbeddedContent; } - public getNestedMode(state:Modes.IState): supports.IEnteringNestedModeData { + public getNestedMode(state:Modes.IState): IEnteringNestedModeData { var result:Modes.IMode = null; var htmlState:State = state; var missingModePromise: winjs.Promise = null; @@ -424,7 +452,7 @@ export class HTMLMode extends AbstractMode i }; } - public getLeavingNestedModeData(line:string, state:Modes.IState):supports.ILeavingNestedModeData { + public getLeavingNestedModeData(line:string, state:Modes.IState):ILeavingNestedModeData { var tagName = (state).lastTagName; var regexp = new RegExp('<\\/' + tagName + '\\s*>', 'i'); var match:any = regexp.exec(line); @@ -438,15 +466,6 @@ export class HTMLMode extends AbstractMode i return null; } - public static WORD_DEFINITION = createWordRegExp('#-?%'); - public getWordDefinition():RegExp { - return HTMLMode.WORD_DEFINITION; - } - - public getCommentsConfiguration():Modes.ICommentsConfiguration { - return { blockCommentStartToken: '' }; - } - protected _getWorkerDescriptor(): AsyncDescriptor2 { return createAsyncDescriptor2('vs/languages/html/common/htmlWorker', 'HTMLWorker'); } diff --git a/src/vs/languages/html/test/common/html.test.ts b/src/vs/languages/html/test/common/html.test.ts index ed1d87225ff..243fe1375fd 100644 --- a/src/vs/languages/html/test/common/html.test.ts +++ b/src/vs/languages/html/test/common/html.test.ts @@ -14,18 +14,19 @@ import modesUtil = require('vs/editor/test/common/modesUtil'); import {Model} from 'vs/editor/common/model/model'; import Supports = require('vs/editor/common/modes/supports'); import {getTag, DELIM_END, DELIM_START, DELIM_ASSIGN, ATTRIB_NAME, ATTRIB_VALUE, COMMENT, DELIM_COMMENT, DELIM_DOCTYPE, DOCTYPE} from 'vs/languages/html/common/htmlTokenTypes'; - +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', () => { - var onEnter: modesUtil.IOnEnterFunc; var tokenizationSupport: Modes.ITokenizationSupport; var _mode: Modes.IMode; suiteSetup((done) => { modesUtil.load('html').then(mode => { tokenizationSupport = mode.tokenizationSupport; - onEnter = modesUtil.createOnEnter(mode); _mode = mode; done(); }); @@ -609,7 +610,7 @@ suite('Colorizing - HTML', () => { test('onEnter', function() { var model = new Model('a[aa[aa[aa[aa[aa[aa]aa]aa]aa]aa]aa]a