electricCharacter.ts 6.3 KB
Newer Older
1 2 3 4 5 6
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/
'use strict';

A
Alex Dima 已提交
7 8 9
import * as strings from 'vs/base/common/strings';
import * as modes from 'vs/editor/common/modes';
import {handleEvent, ignoreBracketsInToken} from 'vs/editor/common/modes/supports';
A
Alex Dima 已提交
10
import {BracketsUtils} from 'vs/editor/common/modes/supports/richEditBrackets';
11
import {LanguageConfigurationRegistryImpl} from 'vs/editor/common/modes/languageConfigurationRegistry';
12

A
Alex Dima 已提交
13 14 15 16 17 18 19 20 21 22
/**
 * 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. ' */').
}

23
export interface IBracketElectricCharacterContribution {
A
Alex Dima 已提交
24
	docComment?: IDocComment;
25 26 27
	embeddedElectricCharacters?: string[];
}

A
Alex Dima 已提交
28
export class BracketElectricCharacterSupport implements modes.IRichEditElectricCharacter {
29

30
	private _registry: LanguageConfigurationRegistryImpl;
31 32 33 34
	private _modeId: string;
	private contribution: IBracketElectricCharacterContribution;
	private brackets: Brackets;

35 36
	constructor(registry:LanguageConfigurationRegistryImpl, modeId: string, brackets: modes.IRichEditBrackets, contribution: IBracketElectricCharacterContribution) {
		this._registry = registry;
37
		this._modeId = modeId;
A
Alex Dima 已提交
38
		this.contribution = contribution || {};
39
		this.brackets = new Brackets(modeId, brackets, this.contribution.docComment);
40 41 42 43 44 45 46 47 48
	}

	public getElectricCharacters(): string[]{
		if (Array.isArray(this.contribution.embeddedElectricCharacters)) {
			return this.contribution.embeddedElectricCharacters.concat(this.brackets.getElectricCharacters());
		}
		return this.brackets.getElectricCharacters();
	}

A
Alex Dima 已提交
49 50
	public onElectricCharacter(context:modes.ILineContext, offset:number): modes.IElectricAction {
		return handleEvent(context, offset, (nestedMode:modes.IMode, context:modes.ILineContext, offset:number) => {
51 52 53
			if (this._modeId === nestedMode.getId()) {
				return this.brackets.onElectricCharacter(context, offset);
			}
54 55 56 57 58
			let electricCharacterSupport = this._registry.getElectricCharacterSupport(nestedMode);
			if (electricCharacterSupport) {
				return electricCharacterSupport.onElectricCharacter(context, offset);
			}
			return null;
59 60
		});
	}
A
Alex Dima 已提交
61 62
}

A
Alex Dima 已提交
63

64 65 66

export class Brackets {

67
	private _modeId: string;
A
Alex Dima 已提交
68
	private _richEditBrackets: modes.IRichEditBrackets;
A
Alex Dima 已提交
69 70
	private _docComment: IDocComment;

71
	constructor(modeId: string, richEditBrackets: modes.IRichEditBrackets, docComment: IDocComment = null) {
72
		this._modeId = modeId;
A
Alex Dima 已提交
73
		this._richEditBrackets = richEditBrackets;
A
Alex Dima 已提交
74
		this._docComment = docComment ? docComment : null;
75 76 77 78 79
	}

	public getElectricCharacters():string[] {
		var result: string[] = [];

A
Alex Dima 已提交
80 81 82 83 84 85
		if (this._richEditBrackets) {
			for (let i = 0, len = this._richEditBrackets.brackets.length; i < len; i++) {
				let bracketPair = this._richEditBrackets.brackets[i];
				let lastChar = bracketPair.close.charAt(bracketPair.close.length - 1);
				result.push(lastChar);
			}
86 87 88
		}

		// Doc comments
A
Alex Dima 已提交
89 90
		if (this._docComment){
			result.push(this._docComment.open.charAt(this._docComment.open.length - 1));
91 92 93 94 95 96 97 98 99 100
		}

		// Filter duplicate entries
		result = result.filter((item, pos, array) => {
			return array.indexOf(item) === pos;
		});

		return result;
	}

A
Alex Dima 已提交
101
	public onElectricCharacter(context: modes.ILineContext, offset: number): modes.IElectricAction {
102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119
		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;
	}

A
Alex Dima 已提交
120
	private _onElectricCharacterStandardBrackets(context: modes.ILineContext, offset: number): modes.IElectricAction {
121

A
Alex Dima 已提交
122
		if (!this._richEditBrackets || this._richEditBrackets.brackets.length === 0) {
123 124 125
			return null;
		}

A
Alex Dima 已提交
126
		let reversedBracketRegex = this._richEditBrackets.reversedRegex;
127

128 129 130 131 132
		let lineText = context.getLineContent();
		let tokenIndex = context.findIndexOfOffset(offset);
		let tokenStart = context.getTokenStartIndex(tokenIndex);
		let tokenEnd = offset + 1;

A
Alex Dima 已提交
133
		var firstNonWhitespaceIndex = strings.firstNonWhitespaceIndex(context.getLineContent());
134
		if (firstNonWhitespaceIndex !== -1 && firstNonWhitespaceIndex < tokenStart) {
135 136 137
			return null;
		}

138
		if (!ignoreBracketsInToken(context.getTokenType(tokenIndex))) {
A
Alex Dima 已提交
139
			let r = BracketsUtils.findPrevBracketInToken(reversedBracketRegex, 1, lineText, tokenStart, tokenEnd);
140 141
			if (r) {
				let text = lineText.substring(r.startColumn - 1, r.endColumn - 1);
A
Alex Dima 已提交
142
				let isOpen = this._richEditBrackets.textIsOpenBracket[text];
143
				if (!isOpen) {
144
					return {
145 146
						matchOpenBracket: text
					};
147 148 149 150 151
				}
			}
		}

		return null;
152 153
	}

A
Alex Dima 已提交
154
	private _onElectricCharacterDocComment(context: modes.ILineContext, offset: number): modes.IElectricAction {
155
		// We only auto-close, so do nothing if there is no closing part.
A
Alex Dima 已提交
156
		if (!this._docComment || !this._docComment.close) {
157 158 159 160 161 162 163
			return null;
		}

		var line = context.getLineContent();
		var char: string = line[offset];

		// See if the right electric character was pressed
A
Alex Dima 已提交
164
		if (char !== this._docComment.open.charAt(this._docComment.open.length - 1)) {
165 166 167 168
			return null;
		}

		// If this line already contains the closing tag, do nothing.
A
Alex Dima 已提交
169
		if (line.indexOf(this._docComment.close, offset) >= 0) {
170 171 172 173 174
			return null;
		}

		// If we're not in a documentation comment, do nothing.
		var lastTokenIndex = context.findIndexOfOffset(offset);
A
Alex Dima 已提交
175
		if (! this.containsTokenTypes(context.getTokenType(lastTokenIndex), this._docComment.scope)) {
176 177 178
			return null;
		}

A
Alex Dima 已提交
179
		if (line.substring(context.getTokenStartIndex(lastTokenIndex), offset+1/* include electric char*/) !== this._docComment.open) {
180 181 182
			return null;
		}

A
Alex Dima 已提交
183
		return { appendText: this._docComment.close};
184
	}
A
Alex Dima 已提交
185
}