diff --git a/src/vs/editor/common/core/lineTokens.ts b/src/vs/editor/common/core/lineTokens.ts index e4d2e4fb7a60a5921408de8fdecc6d185f7f6713..ff7f049978bcdc9b8312cc340956ec413d25b2b4 100644 --- a/src/vs/editor/common/core/lineTokens.ts +++ b/src/vs/editor/common/core/lineTokens.ts @@ -9,13 +9,13 @@ import { ModeTransition } from 'vs/editor/common/core/modeTransition'; import { ViewLineToken } from 'vs/editor/common/core/viewLineToken'; /** - * A standard token type. + * A standard token type. Values are 2^x such that a bit mask can be used. */ export const enum StandardTokenType { Other = 0, Comment = 1, String = 2, - RegEx = 3 + RegEx = 4 } const STANDARD_TOKEN_TYPE_REGEXP = /\b(comment|string|regex)\b/; diff --git a/src/vs/editor/common/modes/languageConfiguration.ts b/src/vs/editor/common/modes/languageConfiguration.ts index 3591973bb53b094e26cfca2e37343929073fadcc..a0afe5f5a0f33b94838c014c6a626256798db51d 100644 --- a/src/vs/editor/common/modes/languageConfiguration.ts +++ b/src/vs/editor/common/modes/languageConfiguration.ts @@ -4,6 +4,8 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; +import { StandardTokenType } from 'vs/editor/common/core/lineTokens'; + /** * Describes how comments for a language work. */ @@ -181,3 +183,43 @@ export interface EnterAction { */ removeText?: number; } + +/** + * @internal + */ +export class StandardAutoClosingPairConditional { + _standardAutoClosingPairConditionalBrand: void; + + readonly open: string; + readonly close: string; + private readonly _standardTokenMask: number; + + constructor(source: IAutoClosingPairConditional) { + this.open = source.open; + this.close = source.close; + + // initially allowed in all tokens + this._standardTokenMask = 0; + + if (Array.isArray(source.notIn)) { + for (let i = 0, len = source.notIn.length; i < len; i++) { + let notIn = source.notIn[i]; + switch (notIn) { + case 'string': + this._standardTokenMask |= StandardTokenType.String; + break; + case 'comment': + this._standardTokenMask |= StandardTokenType.Comment; + break; + case 'regex': + this._standardTokenMask |= StandardTokenType.RegEx; + break; + } + } + } + } + + public isOK(standardToken: StandardTokenType): boolean { + return (this._standardTokenMask & standardToken) === 0; + } +} diff --git a/src/vs/editor/common/modes/languageConfigurationRegistry.ts b/src/vs/editor/common/modes/languageConfigurationRegistry.ts index df4b4308067e77be7f98cba5e9bafdeb4514a75b..37c78d0f801564c1b5447702de6bc7f8587fc26d 100644 --- a/src/vs/editor/common/modes/languageConfigurationRegistry.ts +++ b/src/vs/editor/common/modes/languageConfigurationRegistry.ts @@ -16,7 +16,7 @@ import { IDisposable } from 'vs/base/common/lifecycle'; import { DEFAULT_WORD_REGEXP, ensureValidWordDefinition } from 'vs/editor/common/model/wordHelper'; import { createScopedLineTokens } from 'vs/editor/common/modes/supports'; import { LineTokens } from 'vs/editor/common/core/lineTokens'; -import { IndentAction, EnterAction, IAutoClosingPair, IAutoClosingPairConditional, LanguageConfiguration } from 'vs/editor/common/modes/languageConfiguration'; +import { IndentAction, EnterAction, IAutoClosingPair, LanguageConfiguration } from 'vs/editor/common/modes/languageConfiguration'; /** * Interface used to support insertion of mode specific comments. @@ -191,7 +191,7 @@ export class LanguageConfigurationRegistryImpl { return value.characterPair || null; } - public getAutoClosingPairs(modeId: string): IAutoClosingPairConditional[] { + public getAutoClosingPairs(modeId: string): IAutoClosingPair[] { let characterPairSupport = this._getCharacterPairSupport(modeId); if (!characterPairSupport) { return []; diff --git a/src/vs/editor/common/modes/supports/characterPair.ts b/src/vs/editor/common/modes/supports/characterPair.ts index 1e1a209d1f1efdd192cf37e258793a76a4469d1e..d4f72526ae83870a87ba679cf7ea1705dfafd524 100644 --- a/src/vs/editor/common/modes/supports/characterPair.ts +++ b/src/vs/editor/common/modes/supports/characterPair.ts @@ -5,18 +5,22 @@ 'use strict'; import { ScopedLineTokens } from 'vs/editor/common/modes/supports'; -import { CharacterPair, IAutoClosingPair, IAutoClosingPairConditional } from 'vs/editor/common/modes/languageConfiguration'; +import { CharacterPair, IAutoClosingPair, IAutoClosingPairConditional, StandardAutoClosingPairConditional } from 'vs/editor/common/modes/languageConfiguration'; export class CharacterPairSupport { - private readonly _autoClosingPairs: IAutoClosingPairConditional[]; + private readonly _autoClosingPairs: StandardAutoClosingPairConditional[]; private readonly _surroundingPairs: IAutoClosingPair[]; constructor(config: { brackets?: CharacterPair[]; autoClosingPairs?: IAutoClosingPairConditional[], surroundingPairs?: IAutoClosingPair[] }) { - this._autoClosingPairs = config.autoClosingPairs; - if (!this._autoClosingPairs) { - this._autoClosingPairs = config.brackets ? config.brackets.map(b => ({ open: b[0], close: b[1] })) : []; + if (config.autoClosingPairs) { + this._autoClosingPairs = config.autoClosingPairs.map(el => new StandardAutoClosingPairConditional(el)); + } else if (config.brackets) { + this._autoClosingPairs = config.brackets.map(b => new StandardAutoClosingPairConditional({ open: b[0], close: b[1] })); + } else { + this._autoClosingPairs = []; } + this._surroundingPairs = config.surroundingPairs || this._autoClosingPairs; } @@ -30,19 +34,14 @@ export class CharacterPairSupport { return true; } - var tokenIndex = context.findTokenIndexAtOffset(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; + let tokenIndex = context.findTokenIndexAtOffset(offset - 1); + let standardTokenType = context.getStandardTokenType(tokenIndex); + + for (let i = 0; i < this._autoClosingPairs.length; ++i) { + let autoClosingPair = this._autoClosingPairs[i]; + + if (autoClosingPair.open === character) { + return autoClosingPair.isOK(standardTokenType); } } diff --git a/src/vs/editor/test/common/modes/languageConfiguration.test.ts b/src/vs/editor/test/common/modes/languageConfiguration.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..45bb72ca8ab8a04899ba8eb78b6d46a16167634b --- /dev/null +++ b/src/vs/editor/test/common/modes/languageConfiguration.test.ts @@ -0,0 +1,93 @@ +/*--------------------------------------------------------------------------------------------- + * 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 assert from 'assert'; +import { StandardAutoClosingPairConditional } from 'vs/editor/common/modes/languageConfiguration'; +import { StandardTokenType } from 'vs/editor/common/core/lineTokens'; + +suite('StandardAutoClosingPairConditional', () => { + + test('Missing notIn', () => { + let v = new StandardAutoClosingPairConditional({ open: '{', close: '}' }); + assert.equal(v.isOK(StandardTokenType.Other), true); + assert.equal(v.isOK(StandardTokenType.Comment), true); + assert.equal(v.isOK(StandardTokenType.String), true); + assert.equal(v.isOK(StandardTokenType.RegEx), true); + }); + + test('Empty notIn', () => { + let v = new StandardAutoClosingPairConditional({ open: '{', close: '}', notIn: [] }); + assert.equal(v.isOK(StandardTokenType.Other), true); + assert.equal(v.isOK(StandardTokenType.Comment), true); + assert.equal(v.isOK(StandardTokenType.String), true); + assert.equal(v.isOK(StandardTokenType.RegEx), true); + }); + + test('Invalid notIn', () => { + let v = new StandardAutoClosingPairConditional({ open: '{', close: '}', notIn: [ 'bla' ] }); + assert.equal(v.isOK(StandardTokenType.Other), true); + assert.equal(v.isOK(StandardTokenType.Comment), true); + assert.equal(v.isOK(StandardTokenType.String), true); + assert.equal(v.isOK(StandardTokenType.RegEx), true); + }); + + test('notIn in strings', () => { + let v = new StandardAutoClosingPairConditional({ open: '{', close: '}', notIn: [ 'string' ] }); + assert.equal(v.isOK(StandardTokenType.Other), true); + assert.equal(v.isOK(StandardTokenType.Comment), true); + assert.equal(v.isOK(StandardTokenType.String), false); + assert.equal(v.isOK(StandardTokenType.RegEx), true); + }); + + test('notIn in comments', () => { + let v = new StandardAutoClosingPairConditional({ open: '{', close: '}', notIn: [ 'comment' ] }); + assert.equal(v.isOK(StandardTokenType.Other), true); + assert.equal(v.isOK(StandardTokenType.Comment), false); + assert.equal(v.isOK(StandardTokenType.String), true); + assert.equal(v.isOK(StandardTokenType.RegEx), true); + }); + + test('notIn in regex', () => { + let v = new StandardAutoClosingPairConditional({ open: '{', close: '}', notIn: [ 'regex' ] }); + assert.equal(v.isOK(StandardTokenType.Other), true); + assert.equal(v.isOK(StandardTokenType.Comment), true); + assert.equal(v.isOK(StandardTokenType.String), true); + assert.equal(v.isOK(StandardTokenType.RegEx), false); + }); + + test('notIn in strings nor comments', () => { + let v = new StandardAutoClosingPairConditional({ open: '{', close: '}', notIn: [ 'string', 'comment' ] }); + assert.equal(v.isOK(StandardTokenType.Other), true); + assert.equal(v.isOK(StandardTokenType.Comment), false); + assert.equal(v.isOK(StandardTokenType.String), false); + assert.equal(v.isOK(StandardTokenType.RegEx), true); + }); + + test('notIn in strings nor regex', () => { + let v = new StandardAutoClosingPairConditional({ open: '{', close: '}', notIn: [ 'string', 'regex' ] }); + assert.equal(v.isOK(StandardTokenType.Other), true); + assert.equal(v.isOK(StandardTokenType.Comment), true); + assert.equal(v.isOK(StandardTokenType.String), false); + assert.equal(v.isOK(StandardTokenType.RegEx), false); + }); + + test('notIn in comments nor regex', () => { + let v = new StandardAutoClosingPairConditional({ open: '{', close: '}', notIn: [ 'comment', 'regex' ] }); + assert.equal(v.isOK(StandardTokenType.Other), true); + assert.equal(v.isOK(StandardTokenType.Comment), false); + assert.equal(v.isOK(StandardTokenType.String), true); + assert.equal(v.isOK(StandardTokenType.RegEx), false); + }); + + test('notIn in strings, comments nor regex', () => { + let v = new StandardAutoClosingPairConditional({ open: '{', close: '}', notIn: [ 'string', 'comment', 'regex' ] }); + assert.equal(v.isOK(StandardTokenType.Other), true); + assert.equal(v.isOK(StandardTokenType.Comment), false); + assert.equal(v.isOK(StandardTokenType.String), false); + assert.equal(v.isOK(StandardTokenType.RegEx), false); + }); +}); diff --git a/src/vs/editor/test/common/modes/supports/characterPair.test.ts b/src/vs/editor/test/common/modes/supports/characterPair.test.ts index 039f2a0dd3c1b86ce5a9bb297e17a8f6595d824d..85ebdaecd3707ceeaa1b728d78d83dd3c6aaffac 100644 --- a/src/vs/editor/test/common/modes/supports/characterPair.test.ts +++ b/src/vs/editor/test/common/modes/supports/characterPair.test.ts @@ -5,8 +5,6 @@ 'use strict'; import * as assert from 'assert'; -import { CharacterPair, IndentAction } from 'vs/editor/common/modes/languageConfiguration'; -import { OnEnterSupport } from 'vs/editor/common/modes/supports/onEnter'; import { CharacterPairSupport } from 'vs/editor/common/modes/supports/characterPair'; import { TokenText, createFakeScopedLineTokens } from 'vs/editor/test/common/modesTestUtils'; @@ -14,8 +12,8 @@ suite('CharacterPairSupport', () => { test('only autoClosingPairs', () => { let characaterPairSupport = new CharacterPairSupport({ autoClosingPairs: [{ open: 'a', close: 'b' }] }); - assert.deepEqual(characaterPairSupport.getAutoClosingPairs(), [{ open: 'a', close: 'b' }]); - assert.deepEqual(characaterPairSupport.getSurroundingPairs(), [{ open: 'a', close: 'b' }]); + assert.deepEqual(characaterPairSupport.getAutoClosingPairs(), [{ open: 'a', close: 'b', _standardTokenMask: 0 }]); + assert.deepEqual(characaterPairSupport.getSurroundingPairs(), [{ open: 'a', close: 'b', _standardTokenMask: 0 }]); }); test('only empty autoClosingPairs', () => { @@ -26,8 +24,8 @@ suite('CharacterPairSupport', () => { test('only brackets', () => { let characaterPairSupport = new CharacterPairSupport({ brackets: [['a', 'b']] }); - assert.deepEqual(characaterPairSupport.getAutoClosingPairs(), [{ open: 'a', close: 'b' }]); - assert.deepEqual(characaterPairSupport.getSurroundingPairs(), [{ open: 'a', close: 'b' }]); + assert.deepEqual(characaterPairSupport.getAutoClosingPairs(), [{ open: 'a', close: 'b', _standardTokenMask: 0 }]); + assert.deepEqual(characaterPairSupport.getSurroundingPairs(), [{ open: 'a', close: 'b', _standardTokenMask: 0 }]); }); test('only empty brackets', () => { @@ -43,7 +41,7 @@ suite('CharacterPairSupport', () => { }); test('only empty surroundingPairs', () => { - let characaterPairSupport = new CharacterPairSupport({ brackets: [] }); + let characaterPairSupport = new CharacterPairSupport({ surroundingPairs: [] }); assert.deepEqual(characaterPairSupport.getAutoClosingPairs(), []); assert.deepEqual(characaterPairSupport.getSurroundingPairs(), []); }); @@ -71,7 +69,7 @@ suite('CharacterPairSupport', () => { }); test('shouldAutoClosePair in not interesting line 2', () => { - let sup = new CharacterPairSupport({ autoClosingPairs: [{ open: '{', close: '}'}] }); + let sup = new CharacterPairSupport({ autoClosingPairs: [{ open: '{', close: '}' }] }); assert.equal(testShouldAutoClose(sup, [{ text: 'do', type: 'string' }], '{', 2), true); assert.equal(testShouldAutoClose(sup, [{ text: 'do', type: 'string' }], 'a', 2), true); }); @@ -121,149 +119,3 @@ suite('CharacterPairSupport', () => { }); }); - -// suite('OnEnter', () => { - -// test('uses indentationRules', () => { -// var support = new OnEnterSupport({ -// indentationRules: { -// decreaseIndentPattern: /^\s*((?!\S.*\/[*]).*[*]\/\s*)?[})\]]|^\s*(case\b.*|default):\s*(\/\/.*|\/[*].*[*]\/\s*)?$/, -// increaseIndentPattern: /(\{[^}"']*|\([^)"']*|\[[^\]"']*|^\s*(\{\}|\(\)|\[\]|(case\b.*|default):))\s*(\/\/.*|\/[*].*[*]\/\s*)?$/, -// indentNextLinePattern: /^\s*(for|while|if|else)\b(?!.*[;{}]\s*(\/\/.*|\/[*].*[*]\/\s*)?$)/, -// unIndentedLinePattern: /^(?!.*([;{}]|\S:)\s*(\/\/.*|\/[*].*[*]\/\s*)?$)(?!.*(\{[^}"']*|\([^)"']*|\[[^\]"']*|^\s*(\{\}|\(\)|\[\]|(case\b.*|default):))\s*(\/\/.*|\/[*].*[*]\/\s*)?$)(?!^\s*((?!\S.*\/[*]).*[*]\/\s*)?[})\]]|^\s*(case\b.*|default):\s*(\/\/.*|\/[*].*[*]\/\s*)?$)(?!^\s*(for|while|if|else)\b(?!.*[;{}]\s*(\/\/.*|\/[*].*[*]\/\s*)?$))/ -// } -// }); - -// var testIndentAction = (oneLineAboveText: string, beforeText: string, afterText: string, expected: IndentAction) => { -// var actual = support.onEnter(oneLineAboveText, beforeText, afterText); -// if (expected === IndentAction.None) { -// assert.equal(actual, null); -// } else { -// assert.equal(actual.indentAction, expected); -// } -// }; - -// testIndentAction('', 'case', '', IndentAction.None); -// testIndentAction('', 'case:', '', IndentAction.Indent); -// testIndentAction('', 'if (true) {', '', IndentAction.Indent); -// testIndentAction('', 'if (true)', '', IndentAction.Indent); -// testIndentAction('', ' ', '}', IndentAction.Outdent); -// testIndentAction('if(true)', '\treturn false', '', IndentAction.Outdent); -// }); - -// test('uses brackets', () => { -// var brackets: CharacterPair[] = [ -// ['(', ')'], -// ['begin', 'end'] -// ]; -// var support = new OnEnterSupport({ -// brackets: brackets -// }); -// var testIndentAction = (beforeText: string, afterText: string, expected: IndentAction) => { -// var actual = support.onEnter('', beforeText, afterText); -// if (expected === IndentAction.None) { -// assert.equal(actual, null); -// } else { -// assert.equal(actual.indentAction, expected); -// } -// }; - -// testIndentAction('a', '', IndentAction.None); -// testIndentAction('', 'b', IndentAction.None); -// testIndentAction('(', 'b', IndentAction.Indent); -// testIndentAction('a', ')', IndentAction.None); -// testIndentAction('begin', 'ending', IndentAction.Indent); -// testIndentAction('abegin', 'end', IndentAction.None); -// testIndentAction('begin', ')', IndentAction.Indent); -// testIndentAction('begin', 'end', IndentAction.IndentOutdent); -// testIndentAction('begin ', ' end', IndentAction.IndentOutdent); -// testIndentAction(' begin', 'end//as', IndentAction.IndentOutdent); -// testIndentAction('(', ')', IndentAction.IndentOutdent); -// testIndentAction('( ', ')', IndentAction.IndentOutdent); -// testIndentAction('a(', ')b', IndentAction.IndentOutdent); - -// testIndentAction('(', '', IndentAction.Indent); -// testIndentAction('(', 'foo', IndentAction.Indent); -// testIndentAction('begin', 'foo', IndentAction.Indent); -// testIndentAction('begin', '', IndentAction.Indent); -// }); - -// test('uses regExpRules', () => { -// var support = new OnEnterSupport({ -// regExpRules: [ -// { -// beforeText: /^\s*\/\*\*(?!\/)([^\*]|\*(?!\/))*$/, -// afterText: /^\s*\*\/$/, -// action: { indentAction: IndentAction.IndentOutdent, appendText: ' * ' } -// }, -// { -// beforeText: /^\s*\/\*\*(?!\/)([^\*]|\*(?!\/))*$/, -// action: { indentAction: IndentAction.None, appendText: ' * ' } -// }, -// { -// beforeText: /^(\t|(\ \ ))*\ \*(\ ([^\*]|\*(?!\/))*)?$/, -// action: { indentAction: IndentAction.None, appendText: '* ' } -// }, -// { -// beforeText: /^(\t|(\ \ ))*\ \*\/\s*$/, -// action: { indentAction: IndentAction.None, removeText: 1 } -// }, -// { -// beforeText: /^(\t|(\ \ ))*\ \*[^/]*\*\/\s*$/, -// action: { indentAction: IndentAction.None, removeText: 1 } -// } -// ] -// }); -// var testIndentAction = (beforeText: string, afterText: string, expectedIndentAction: IndentAction, expectedAppendText: string, removeText: number = 0) => { -// var actual = support.onEnter('', beforeText, afterText); -// if (expectedIndentAction === null) { -// assert.equal(actual, null, 'isNull:' + beforeText); -// } else { -// assert.equal(actual !== null, true, 'isNotNull:' + beforeText); -// assert.equal(actual.indentAction, expectedIndentAction, 'indentAction:' + beforeText); -// if (expectedAppendText !== null) { -// assert.equal(actual.appendText, expectedAppendText, 'appendText:' + beforeText); -// } -// if (removeText !== 0) { -// assert.equal(actual.removeText, removeText, 'removeText:' + beforeText); -// } -// } -// }; - -// testIndentAction('\t/**', ' */', IndentAction.IndentOutdent, ' * '); -// testIndentAction('\t/**', '', IndentAction.None, ' * '); -// testIndentAction('\t/** * / * / * /', '', IndentAction.None, ' * '); -// testIndentAction('\t/** /*', '', IndentAction.None, ' * '); -// testIndentAction('/**', '', IndentAction.None, ' * '); -// testIndentAction('\t/**/', '', null, null); -// testIndentAction('\t/***/', '', null, null); -// testIndentAction('\t/*******/', '', null, null); -// testIndentAction('\t/** * * * * */', '', null, null); -// testIndentAction('\t/** */', '', null, null); -// testIndentAction('\t/** asdfg */', '', null, null); -// testIndentAction('\t/* asdfg */', '', null, null); -// testIndentAction('\t/* asdfg */', '', null, null); -// testIndentAction('\t/** asdfg */', '', null, null); -// testIndentAction('*/', '', null, null); -// testIndentAction('\t/*', '', null, null); -// testIndentAction('\t*', '', null, null); -// testIndentAction('\t *', '', IndentAction.None, '* '); -// testIndentAction('\t */', '', IndentAction.None, null, 1); -// testIndentAction('\t * */', '', IndentAction.None, null, 1); -// testIndentAction('\t * * / * / * / */', '', null, null); -// testIndentAction('\t * ', '', IndentAction.None, '* '); -// testIndentAction(' * ', '', IndentAction.None, '* '); -// testIndentAction(' * asdfsfagadfg', '', IndentAction.None, '* '); -// testIndentAction(' * asdfsfagadfg * * * ', '', IndentAction.None, '* '); -// testIndentAction(' * /*', '', IndentAction.None, '* '); -// testIndentAction(' * asdfsfagadfg * / * / * /', '', IndentAction.None, '* '); -// testIndentAction(' * asdfsfagadfg * / * / * /*', '', IndentAction.None, '* '); -// testIndentAction(' */', '', IndentAction.None, null, 1); -// testIndentAction('\t */', '', IndentAction.None, null, 1); -// testIndentAction('\t\t */', '', IndentAction.None, null, 1); -// testIndentAction(' */', '', IndentAction.None, null, 1); -// testIndentAction(' */', '', IndentAction.None, null, 1); -// testIndentAction('\t */', '', IndentAction.None, null, 1); -// testIndentAction(' *--------------------------------------------------------------------------------------------*/', '', IndentAction.None, null, 1); -// }); -// }); \ No newline at end of file diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index ea0f731b4c29edc1a795681fb791856f064cf23d..2e5f97c1a6a572d1ec96b4684c4fc6b8eced26b4 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -4067,6 +4067,7 @@ declare module monaco.languages { */ resolveCompletionItem?(item: CompletionItem, token: CancellationToken): CompletionItem | Thenable; } + /** * Describes how comments for a language work. */