提交 c968accf 编写于 作者: A Alex Dima

Inline autoIndentation to electricCharacter

上级 45ad5412
/*---------------------------------------------------------------------------------------------
* 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
......@@ -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');
......
......@@ -4,9 +4,9 @@
*--------------------------------------------------------------------------------------------*/
'use strict';
import {Brackets} from 'vs/editor/common/modes/autoIndentation';
import * as Modes from 'vs/editor/common/modes';
import {handleEvent} from 'vs/editor/common/modes/supports';
import Strings = require('vs/base/common/strings');
export interface IBracketElectricCharacterContribution {
brackets: Modes.IBracketPair[];
......@@ -60,3 +60,286 @@ export class BracketElectricCharacterSupport implements Modes.IRichEditElectricC
});
}
}
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;
}
}
}
......@@ -5,7 +5,7 @@
'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');
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册