blob: 525c4ddfd760e8bca85b80593cb496afd2e8b280 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.netbeans.modules.javascript2.editor;
import java.util.concurrent.atomic.AtomicReference;
import javax.swing.text.BadLocationException;
import javax.swing.text.Caret;
import org.netbeans.api.editor.mimelookup.MimeRegistration;
import org.netbeans.api.lexer.Language;
import org.netbeans.api.lexer.Token;
import org.netbeans.api.lexer.TokenHierarchy;
import org.netbeans.api.lexer.TokenId;
import org.netbeans.api.lexer.TokenSequence;
import org.netbeans.editor.BaseDocument;
import org.netbeans.editor.Utilities;
import org.netbeans.modules.csl.api.EditorOptions;
import org.netbeans.modules.csl.api.OffsetRange;
import org.netbeans.modules.csl.spi.GsfUtilities;
import org.netbeans.modules.javascript2.lexer.api.JsTokenId;
import org.netbeans.modules.javascript2.lexer.api.LexUtilities;
import org.netbeans.modules.javascript2.editor.options.OptionsUtils;
import org.netbeans.spi.editor.typinghooks.TypedTextInterceptor;
/**
*
* @author Petr Hejl
*/
public class JsTypedTextInterceptor implements TypedTextInterceptor {
/** Tokens which indicate that we're within a regexp string */
// XXX What about JsTokenId.REGEXP_BEGIN?
private static final TokenId[] REGEXP_TOKENS = { JsTokenId.REGEXP, JsTokenId.REGEXP_END };
/** Tokens which indicate that we're within a literal string */
private final static TokenId[] STRING_TOKENS = { JsTokenId.STRING, JsTokenId.STRING_END };
/** Tokens which indicate that we're within a template string */
private final static TokenId[] TEMPLATE_TOKENS = { JsTokenId.TEMPLATE, JsTokenId.TEMPLATE_END };
private final Language<JsTokenId> language;
private final boolean singleQuote;
/** When != -1, this indicates that we previously adjusted the indentation of the
* line to the given offset, and if it turns out that the user changes that token,
* we revert to the original indentation
*/
private int previousAdjustmentOffset = -1;
/**
* The indentation to revert to when previousAdjustmentOffset is set and the token
* changed
*/
private int previousAdjustmentIndent;
public JsTypedTextInterceptor(Language<JsTokenId> language, boolean singleQuote) {
this.language = language;
this.singleQuote = singleQuote;
}
private boolean isInsertMatchingEnabled() {
EditorOptions options = EditorOptions.get(language.mimeType());
if (options != null) {
return options.getMatchBrackets();
}
return true;
}
private boolean isSmartQuotingEnabled() {
return OptionsUtils.forLanguage(language).autoCompletionSmartQuotes();
}
@Override
public void afterInsert(final Context context) throws BadLocationException {
final BaseDocument doc = (BaseDocument) context.getDocument();
final AtomicReference<BadLocationException> ex = new AtomicReference<BadLocationException>();
doc.runAtomicAsUser(new Runnable() {
@Override
public void run() {
int dotPos = context.getOffset();
Caret caret = context.getComponent().getCaret();
char ch = context.getText().charAt(0);
try {
// See if our automatic adjustment of indentation when typing (for example) "end" was
// premature - if you were typing a longer word beginning with one of my adjustment
// prefixes, such as "endian", then put the indentation back.
if (previousAdjustmentOffset != -1) {
if (dotPos == previousAdjustmentOffset) {
// Revert indentation iff the character at the insert position does
// not start a new token (e.g. the previous token that we reindented
// was not complete)
TokenSequence<? extends JsTokenId> ts = LexUtilities.getTokenSequence(
doc, dotPos, language);
if (ts != null) {
ts.move(dotPos);
if (ts.moveNext() && (ts.offset() < dotPos)) {
GsfUtilities.setLineIndentation(doc, dotPos, previousAdjustmentIndent);
}
}
}
previousAdjustmentOffset = -1;
}
switch (ch) {
case '{':
case '(':
case '[':
if (!isInsertMatchingEnabled()) {
break;
}
case '}':
case ')':
case ']':
Token<? extends JsTokenId> token = LexUtilities.getToken(doc, dotPos, language);
if (token == null) {
return;
}
TokenId id = token.id();
if (((id == JsTokenId.IDENTIFIER) && (token.length() == 1))
|| (id == JsTokenId.BRACKET_LEFT_BRACKET) || (id == JsTokenId.BRACKET_RIGHT_BRACKET)
|| (id == JsTokenId.BRACKET_LEFT_CURLY) || (id == JsTokenId.BRACKET_RIGHT_CURLY)
|| (id == JsTokenId.BRACKET_LEFT_PAREN) || (id == JsTokenId.BRACKET_RIGHT_PAREN)) {
if (ch == ']') {
skipClosingBracket(doc, caret, ch, JsTokenId.BRACKET_RIGHT_BRACKET);
} else if (ch == ')') {
skipClosingBracket(doc, caret, ch, JsTokenId.BRACKET_RIGHT_PAREN);
} else if (ch == '}') {
skipClosingBracket(doc, caret, ch, JsTokenId.BRACKET_RIGHT_CURLY);
// the curly is not completed intentionally see #189443
// java and php don't do that as well
} else if ((ch == '[') || (ch == '(')) {
completeOpeningBracket(doc, dotPos, caret, ch);
}
}
// Reindent blocks (won't do anything if } is not at the beginning of a line
if (ch == '}') {
reindent(doc, dotPos, JsTokenId.BRACKET_RIGHT_CURLY, caret);
} else if (ch == ']') {
reindent(doc, dotPos, JsTokenId.BRACKET_RIGHT_BRACKET, caret);
}
break;
default:
break;
}
} catch (BadLocationException blex) {
ex.set(blex);
}
}
});
BadLocationException blex = ex.get();
if (blex != null) {
throw blex;
}
}
@Override
public boolean beforeInsert(Context context) throws BadLocationException {
return false;
}
@Override
public void insert(MutableContext context) throws BadLocationException {
int caretOffset = context.getOffset();
char ch = context.getText().charAt(0);
BaseDocument doc = (BaseDocument) context.getDocument();
String selection = context.getReplacedText();
boolean isTemplate = GsfUtilities.isCodeTemplateEditing(doc);
if (selection != null && selection.length() > 0) {
if (!isTemplate && (((ch == '"' || ch == '\'' || ch == '`') && isSmartQuotingEnabled())
|| ((ch == '(' || ch == '{' || ch == '[')) && isInsertMatchingEnabled())) {
// Bracket the selection
char firstChar = selection.charAt(0);
if (firstChar != ch) {
TokenSequence<? extends JsTokenId> ts = LexUtilities.getPositionedSequence(
doc, caretOffset, language);
if (ts != null
&& ts.token().id() != JsTokenId.LINE_COMMENT
&& ts.token().id() != JsTokenId.DOC_COMMENT
&& ts.token().id() != JsTokenId.BLOCK_COMMENT // not inside comments
&& ts.token().id() != JsTokenId.STRING) { // not inside strings!
int lastChar = selection.charAt(selection.length()-1);
// Replace the surround-with chars?
if (selection.length() > 1 &&
((firstChar == '"' || firstChar == '\'' || firstChar == '`' || firstChar == '(' ||
firstChar == '{' || firstChar == '[' || firstChar == '/') &&
lastChar == matching(firstChar))) {
String innerText = selection.substring(1, selection.length() - 1);
String text = Character.toString(ch) + innerText + Character.toString(matching(ch));
context.setText(text, text.length());
} else {
// No, insert around
String text = ch + selection + matching(ch);
context.setText(text, text.length());
}
return;
}
}
}
}
TokenSequence<? extends JsTokenId> ts = LexUtilities.getTokenSequence(
doc, caretOffset, language);
if (ts == null) {
return;
}
ts.move(caretOffset);
if (!ts.moveNext() && !ts.movePrevious()) {
return;
}
Token<? extends JsTokenId> token = ts.token();
JsTokenId id = token.id();
TokenId[] stringTokens = null;
TokenId beginTokenId = null;
// "/" is handled AFTER the character has been inserted since we need the lexer's help
if (ch == '\"' || (ch == '\'' && singleQuote)) {
stringTokens = STRING_TOKENS;
beginTokenId = JsTokenId.STRING_BEGIN;
} else if (ch == '`' && singleQuote) {
stringTokens = TEMPLATE_TOKENS;
beginTokenId = JsTokenId.TEMPLATE_BEGIN;
} else if (id.isError()) {
ts.movePrevious();
TokenId prevId = ts.token().id();
if (isCompletableStringBoundary(ts.token(), singleQuote, false)) {
stringTokens = STRING_TOKENS;
beginTokenId = prevId;
} else if (isCompletableTemplateBoundary(ts.token(), singleQuote, false)) {
stringTokens = TEMPLATE_TOKENS;
beginTokenId = prevId;
} else if (prevId == JsTokenId.REGEXP_BEGIN) {
stringTokens = REGEXP_TOKENS;
beginTokenId = JsTokenId.REGEXP_BEGIN;
}
} else if (isCompletableStringBoundary(token, singleQuote, false) &&
(caretOffset == (ts.offset() + 1))) {
if (!Character.isLetter(ch)) { // %q, %x, etc. Only %[], %!!, %<space> etc. is allowed
stringTokens = STRING_TOKENS;
beginTokenId = id;
}
} else if (isCompletableTemplateBoundary(token, singleQuote, false) &&
(caretOffset == (ts.offset() + 1))) {
if (!Character.isLetter(ch)) { // %q, %x, etc. Only %[], %!!, %<space> etc. is allowed
stringTokens = TEMPLATE_TOKENS;
beginTokenId = id;
}
} else if ((isCompletableStringBoundary(token, singleQuote, false) && (caretOffset == (ts.offset() + 2))) ||
isCompletableStringBoundary(token, singleQuote, true)) {
stringTokens = STRING_TOKENS;
beginTokenId = JsTokenId.STRING_BEGIN;
} else if ((isCompletableTemplateBoundary(token, singleQuote, false) && (caretOffset == (ts.offset() + 2))) ||
isCompletableTemplateBoundary(token, singleQuote, true)) {
stringTokens = TEMPLATE_TOKENS;
beginTokenId = JsTokenId.TEMPLATE_BEGIN;
} else if (((id == JsTokenId.REGEXP_BEGIN) && (caretOffset == (ts.offset() + 2))) ||
(id == JsTokenId.REGEXP_END)) {
stringTokens = REGEXP_TOKENS;
beginTokenId = JsTokenId.REGEXP_BEGIN;
}
if (stringTokens != null && isSmartQuotingEnabled()) {
completeQuote(context, ch, stringTokens, beginTokenId, isTemplate);
}
}
@Override
public void cancelled(Context context) {
}
private void reindent(BaseDocument doc, int offset, TokenId id, Caret caret)
throws BadLocationException {
TokenSequence<? extends JsTokenId> ts = LexUtilities.getTokenSequence(
doc, offset, language);
if (ts != null) {
ts.move(offset);
if (!ts.moveNext() && !ts.movePrevious()) {
return;
}
Token<? extends JsTokenId> token = ts.token();
if ((token.id() == id)) {
final int rowFirstNonWhite = Utilities.getRowFirstNonWhite(doc, offset);
// Ensure that this token is at the beginning of the line
if (ts.offset() > rowFirstNonWhite) {
// if (RubyUtils.isRhtmlDocument(doc)) {
// // Allow "<%[whitespace]*" to preceed
// String s = doc.getText(rowFirstNonWhite, ts.offset()-rowFirstNonWhite);
// if (!s.matches("<%\\s*")) {
// return;
// }
// } else {
return;
// }
}
OffsetRange begin = OffsetRange.NONE;
if (id == JsTokenId.BRACKET_RIGHT_CURLY) {
begin = LexUtilities.findBwd(doc, ts, JsTokenId.BRACKET_LEFT_CURLY, JsTokenId.BRACKET_RIGHT_CURLY);
} else if (id == JsTokenId.BRACKET_RIGHT_BRACKET) {
begin = LexUtilities.findBwd(doc, ts, JsTokenId.BRACKET_LEFT_BRACKET, JsTokenId.BRACKET_RIGHT_BRACKET);
}
if (begin != OffsetRange.NONE) {
int beginOffset = begin.getStart();
int indent = GsfUtilities.getLineIndent(doc, beginOffset);
previousAdjustmentIndent = GsfUtilities.getLineIndent(doc, offset);
GsfUtilities.setLineIndentation(doc, offset, indent);
previousAdjustmentOffset = caret.getDot();
}
}
}
}
/**
* Check for various conditions and possibly add a pairing bracket
* to the already inserted.
* @param doc the document
* @param dotPos position of the opening bracket (already in the doc)
* @param caret caret
* @param bracket the bracket that was inserted
*/
private void completeOpeningBracket(BaseDocument doc, int dotPos, Caret caret, char bracket)
throws BadLocationException {
if (isCompletablePosition(doc, dotPos + 1)) {
String matchingBracket = "" + matching(bracket);
doc.insertString(dotPos + 1, matchingBracket, null);
caret.setDot(dotPos + 1);
}
}
/**
* Check for conditions and possibly complete an already inserted
* quote .
* @param doc the document
* @param dotPos position of the opening bracket (already in the doc)
* @param caret caret
* @param bracket the character that was inserted
*/
private void completeQuote(MutableContext context, char bracket,
TokenId[] stringTokens, TokenId beginToken, boolean isTemplate) throws BadLocationException {
if (isTemplate) {
if (bracket == '"' || bracket == '\'' || bracket == '`' || bracket == '(' || bracket == '{' || bracket == '[') {
String text = context.getText() + matching(bracket);
context.setText(text, text.length() - 1);
}
return;
}
int dotPos = context.getOffset();
BaseDocument doc = (BaseDocument) context.getDocument();
if (isEscapeSequence(doc, dotPos)) { // \" or \' typed
return;
}
// Examine token at the caret offset
if (doc.getLength() < dotPos) {
return;
}
TokenSequence<? extends JsTokenId> ts = LexUtilities.getTokenSequence(
doc, dotPos, language);
if (ts == null) {
return;
}
ts.move(dotPos);
if (!ts.moveNext() && !ts.movePrevious()) {
return;
}
Token<? extends JsTokenId> token = ts.token();
Token<? extends JsTokenId> previousToken = null;
if (ts.movePrevious()) {
previousToken = ts.token();
}
int lastNonWhite = Utilities.getRowLastNonWhite(doc, dotPos);
// eol - true if the caret is at the end of line (ignoring whitespaces)
boolean eol = lastNonWhite < dotPos;
if ((token.id() == JsTokenId.BLOCK_COMMENT)
|| (token.id() == JsTokenId.DOC_COMMENT)
|| (token.id() == JsTokenId.LINE_COMMENT)
|| (previousToken != null && previousToken.id() == JsTokenId.LINE_COMMENT && token.id() == JsTokenId.EOL)) {
return;
} else if ((token.id() == JsTokenId.WHITESPACE) && eol && ((dotPos - 1) > 0)) {
// check if the caret is at the very end of the line comment
token = LexUtilities.getToken(doc, dotPos - 1, language);
if (token.id() == JsTokenId.LINE_COMMENT) {
return;
}
}
boolean completablePosition = isQuoteCompletablePosition(doc, dotPos);
boolean insideString = false;
JsTokenId id = token.id();
for (TokenId currId : stringTokens) {
if (id == currId) {
insideString = true;
break;
}
}
if (id.isError() && (previousToken != null)
&& (previousToken.id() == beginToken)) {
insideString = true;
}
if (id == JsTokenId.EOL && previousToken != null) {
if (previousToken.id() == beginToken) {
insideString = true;
} else if (previousToken.id().isError()) {
if (ts.movePrevious()) {
if (ts.token().id() == beginToken) {
insideString = true;
}
}
}
}
if (!insideString) {
// check if the caret is at the very end of the line and there
// is an unterminated string literal
if ((token.id() == JsTokenId.WHITESPACE) && eol) {
if ((dotPos - 1) > 0) {
token = LexUtilities.getToken(doc, dotPos - 1, language);
// XXX TODO use language embedding to handle this
insideString = (token.id() == JsTokenId.STRING || token.id() == JsTokenId.TEMPLATE);
}
}
}
if (insideString) {
if (eol) {
return; // do not complete
} else {
//#69524
char chr = doc.getChars(dotPos, 1)[0];
if (chr == bracket) {
context.setText(Character.toString(bracket), 1);
doc.remove(dotPos, 1);
return;
}
}
}
if ((completablePosition && !insideString) || eol) {
String text = Character.toString(bracket) + matching(bracket);
context.setText(text, text.length() - 1);
}
}
/**
* Checks whether dotPos is a position at which bracket and quote
* completion is performed. Brackets and quotes are not completed
* everywhere but just at suitable places .
* @param doc the document
* @param dotPos position to be tested
*/
private boolean isCompletablePosition(BaseDocument doc, int dotPos)
throws BadLocationException {
if (dotPos == doc.getLength()) { // there's no other character to test
return true;
} else {
// test that we are in front of ) , " or '
char chr = doc.getChars(dotPos, 1)[0];
return ((chr == ')') || (chr == ',') || (chr == '\"') || (chr == '\'') || (chr == '`') || (chr == ' ') ||
(chr == ']') || (chr == '}') || (chr == '\n') || (chr == '\t') || (chr == ';'));
}
}
private boolean isQuoteCompletablePosition(BaseDocument doc, int dotPos)
throws BadLocationException {
if (dotPos == doc.getLength()) { // there's no other character to test
return true;
} else {
// test that we are in front of ) , " or ' ... etc.
int eol = Utilities.getRowEnd(doc, dotPos);
if ((dotPos == eol) || (eol == -1)) {
return false;
}
int firstNonWhiteFwd = Utilities.getFirstNonWhiteFwd(doc, dotPos, eol);
if (firstNonWhiteFwd == -1) {
return false;
}
char chr = doc.getChars(firstNonWhiteFwd, 1)[0];
return ((chr == ')') || (chr == ',') || (chr == '+') || (chr == '}') || (chr == ';') ||
(chr == ']'));
}
}
/**
* A hook to be called after closing bracket ) or ] was inserted into
* the document. The method checks if the bracket should stay there
* or be removed and some exisitng bracket just skipped.
*
* @param doc the document
* @param dotPos position of the inserted bracket
* @param caret caret
* @param bracket the bracket character ']' or ')'
*/
private void skipClosingBracket(BaseDocument doc, Caret caret, char bracket, TokenId bracketId)
throws BadLocationException {
int caretOffset = caret.getDot();
if (isSkipClosingBracket(doc, caretOffset, bracketId)) {
doc.remove(caretOffset - 1, 1);
caret.setDot(caretOffset); // skip closing bracket
}
}
/**
* Check whether the typed bracket should stay in the document
* or be removed.
* <br>
* This method is called by <code>skipClosingBracket()</code>.
*
* @param doc document into which typing was done.
* @param caretOffset
*/
private boolean isSkipClosingBracket(BaseDocument doc, int caretOffset, TokenId bracketId)
throws BadLocationException {
// First check whether the caret is not after the last char in the document
// because no bracket would follow then so it could not be skipped.
if (caretOffset == doc.getLength()) {
return false; // no skip in this case
}
boolean skipClosingBracket = false; // by default do not remove
TokenSequence<? extends JsTokenId> ts = LexUtilities.getTokenSequence(
doc, caretOffset, language);
if (ts == null) {
return false;
}
// XXX BEGIN TOR MODIFICATIONS
//ts.move(caretOffset+1);
ts.move(caretOffset);
if (!ts.moveNext()) {
return false;
}
Token<? extends JsTokenId> token = ts.token();
// Check whether character follows the bracket is the same bracket
if ((token != null) && (token.id() == bracketId)) {
int bracketIntId = bracketId.ordinal();
int leftBracketIntId =
(bracketIntId == JsTokenId.BRACKET_RIGHT_PAREN.ordinal()) ? JsTokenId.BRACKET_LEFT_PAREN.ordinal()
: JsTokenId.BRACKET_LEFT_BRACKET.ordinal();
// Skip all the brackets of the same type that follow the last one
ts.moveNext();
Token<? extends JsTokenId> nextToken = ts.token();
boolean endOfJs = false;
while ((nextToken != null) && (nextToken.id() == bracketId)) {
token = nextToken;
if (!ts.moveNext()) {
endOfJs = true;
break;
}
nextToken = ts.token();
}
// token var points to the last bracket in a group of two or more right brackets
// Attempt to find the left matching bracket for it
// Search would stop on an extra opening left brace if found
int braceBalance = 0; // balance of '{' and '}'
int bracketBalance = 0; // balance of the brackets or parenthesis
Token<? extends JsTokenId> lastRBracket = token;
if (!endOfJs) {
// move on the las bracket || parent
ts.movePrevious();
}
token = ts.token();
boolean finished = false;
while (!finished && (token != null)) {
int tokenIntId = token.id().ordinal();
if ((token.id() == JsTokenId.BRACKET_LEFT_PAREN) || (token.id() == JsTokenId.BRACKET_LEFT_BRACKET)) {
if (tokenIntId == leftBracketIntId) {
bracketBalance++;
if (bracketBalance == 0) {
if (braceBalance != 0) {
// Here the bracket is matched but it is located
// inside an unclosed brace block
// e.g. ... ->( } a()|)
// which is in fact illegal but it's a question
// of what's best to do in this case.
// We chose to leave the typed bracket
// by setting bracketBalance to 1.
// It can be revised in the future.
bracketBalance = 1;
}
finished = true;
}
}
} else if ((token.id() == JsTokenId.BRACKET_RIGHT_PAREN) ||
(token.id() == JsTokenId.BRACKET_RIGHT_BRACKET)) {
if (tokenIntId == bracketIntId) {
bracketBalance--;
}
} else if (token.id() == JsTokenId.BRACKET_LEFT_CURLY) {
braceBalance++;
if (braceBalance > 0) { // stop on extra left brace
finished = true;
}
} else if (token.id() == JsTokenId.BRACKET_RIGHT_CURLY) {
braceBalance--;
}
if (!ts.movePrevious()) {
break;
}
token = ts.token();
}
if (bracketBalance != 0
|| (bracketId == JsTokenId.BRACKET_RIGHT_CURLY && braceBalance < 0)) { // not found matching bracket
// Remove the typed bracket as it's unmatched
skipClosingBracket = true;
} else { // the bracket is matched
// Now check whether the bracket would be matched
// when the closing bracket would be removed
// i.e. starting from the original lastRBracket token
// and search for the same bracket to the right in the text
// The search would stop on an extra right brace if found
braceBalance = 0;
bracketBalance = 0;
//token = lastRBracket.getNext();
TokenHierarchy<BaseDocument> th = TokenHierarchy.get(doc);
int ofs = lastRBracket.offset(th);
ts.move(ofs);
ts.moveNext();
token = ts.token();
finished = false;
while (!finished && (token != null)) {
if ((token.id() == JsTokenId.BRACKET_LEFT_PAREN) || (token.id() == JsTokenId.BRACKET_LEFT_BRACKET)) {
if (token.id().ordinal() == leftBracketIntId) {
bracketBalance++;
}
} else if ((token.id() == JsTokenId.BRACKET_RIGHT_PAREN) ||
(token.id() == JsTokenId.BRACKET_RIGHT_BRACKET)) {
if (token.id().ordinal() == bracketIntId) {
bracketBalance--;
if (bracketBalance == 0) {
if (braceBalance != 0) {
// Here the bracket is matched but it is located
// inside an unclosed brace block
// which is in fact illegal but it's a question
// of what's best to do in this case.
// We chose to leave the typed bracket
// by setting bracketBalance to -1.
// It can be revised in the future.
bracketBalance = -1;
}
finished = true;
}
}
} else if (token.id() == JsTokenId.BRACKET_LEFT_CURLY) {
braceBalance++;
} else if (token.id() == JsTokenId.BRACKET_RIGHT_CURLY) {
braceBalance--;
}
if (!ts.movePrevious()) {
break;
}
token = ts.token();
}
skipClosingBracket = ((braceBalance == 0) && (bracketId == JsTokenId.BRACKET_RIGHT_CURLY))
|| ((bracketBalance > 0) && (bracketId == JsTokenId.BRACKET_RIGHT_BRACKET || bracketId == JsTokenId.BRACKET_RIGHT_PAREN));
}
}
return skipClosingBracket;
}
// XXX TODO Use embedded string sequence here and see if it
// really is escaped. I know where those are!
// TODO Adjust for JavaScript
private static boolean isEscapeSequence(BaseDocument doc, int dotPos)
throws BadLocationException {
if (dotPos <= 0) {
return false;
}
char previousChar = doc.getChars(dotPos - 1, 1)[0];
return previousChar == '\\';
}
/**
* Returns for an opening bracket or quote the appropriate closing
* character.
*/
private static char matching(char bracket) {
switch (bracket) {
case '(':
return ')';
case '[':
return ']';
case '\"':
return '\"'; // NOI18N
case '\'':
return '\'';
case '`':
return '`';
case '{':
return '}';
case '}':
return '{';
default:
return bracket;
}
}
private static boolean isCompletableStringBoundary(Token<? extends JsTokenId> token,
boolean singleQuote, boolean end) {
if ((!end && token.id() == JsTokenId.STRING_BEGIN)
|| (end && token.id() == JsTokenId.STRING_END)) {
if (singleQuote || "\"".equals(token.text().toString())) {
return true;
}
}
return false;
}
private static boolean isCompletableTemplateBoundary(Token<? extends JsTokenId> token,
boolean singleQuote, boolean end) {
if ((!end && token.id() == JsTokenId.TEMPLATE_BEGIN)
|| (end && token.id() == JsTokenId.TEMPLATE_END)) {
return singleQuote;
}
return false;
}
@MimeRegistration(mimeType = JsTokenId.JAVASCRIPT_MIME_TYPE, service = TypedTextInterceptor.Factory.class)
public static class JsFactory implements TypedTextInterceptor.Factory {
@Override
public TypedTextInterceptor createTypedTextInterceptor(org.netbeans.api.editor.mimelookup.MimePath mimePath) {
return new JsTypedTextInterceptor(JsTokenId.javascriptLanguage(), true);
}
}
@MimeRegistration(mimeType = JsTokenId.JSON_MIME_TYPE, service = TypedTextInterceptor.Factory.class)
public static class JsonFactory implements TypedTextInterceptor.Factory {
@Override
public TypedTextInterceptor createTypedTextInterceptor(org.netbeans.api.editor.mimelookup.MimePath mimePath) {
return new JsTypedTextInterceptor(JsTokenId.jsonLanguage(), false);
}
}
}