blob: e92d0917f457ddcfb8de21d0645fb979fa2f923a [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.List;
import javax.swing.text.AbstractDocument;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
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.TokenSequence;
import org.netbeans.modules.javascript2.lexer.api.JsTokenId;
import org.netbeans.modules.javascript2.lexer.api.LexUtilities;
import org.netbeans.spi.editor.bracesmatching.BraceContext;
import org.netbeans.spi.editor.bracesmatching.BracesMatcher;
import org.netbeans.spi.editor.bracesmatching.BracesMatcherFactory;
import org.netbeans.spi.editor.bracesmatching.MatcherContext;
import org.netbeans.spi.editor.bracesmatching.support.BracesMatcherSupport;
import org.openide.util.Exceptions;
/**
*
* @author Petr Hejl
*/
// major portion copied from java
public class JsBracesMatcher implements BracesMatcher {
private static final char [] PAIRS = new char [] { '(', ')', '[', ']', '{', '}' }; //NOI18N
private static final JsTokenId [] PAIR_TOKEN_IDS = new JsTokenId [] {
JsTokenId.BRACKET_LEFT_PAREN, JsTokenId.BRACKET_RIGHT_PAREN,
JsTokenId.BRACKET_LEFT_BRACKET, JsTokenId.BRACKET_RIGHT_BRACKET,
JsTokenId.BRACKET_LEFT_CURLY, JsTokenId.BRACKET_RIGHT_CURLY
};
private final MatcherContext context;
private final Language<JsTokenId> language;
private int originOffset;
private char originChar;
private char matchingChar;
private boolean backward;
private List<TokenSequence<?>> sequences;
private boolean templateExp;
public JsBracesMatcher(MatcherContext context, Language<JsTokenId> language) {
this.context = context;
this.language = language;
}
@Override
public int [] findOrigin() throws InterruptedException, BadLocationException {
((AbstractDocument) context.getDocument()).readLock();
try {
templateExp = false;
int endOffset = -1;
TokenSequence<? extends JsTokenId> testSeq = LexUtilities.getJsPositionedSequence(
context.getDocument(), context.getSearchOffset());
if (testSeq != null) {
if (testSeq.token().id() != JsTokenId.TEMPLATE_EXP_BEGIN && context.isSearchingBackward()) {
if (!testSeq.movePrevious() && context.getSearchOffset() - 1 >= context.getLimitOffset()) {
testSeq = LexUtilities.getJsPositionedSequence(
context.getDocument(), context.getSearchOffset() - 1);
}
}
}
if (testSeq != null && testSeq.token().id() == JsTokenId.TEMPLATE_EXP_BEGIN) {
originOffset = testSeq.offset();
endOffset = originOffset + testSeq.token().length();
originChar = '{'; // NOI18N
matchingChar = '}'; // NOI18N
backward = false;
} else {
int[] origin = BracesMatcherSupport.findChar(
context.getDocument(),
context.getSearchOffset(),
context.getLimitOffset(),
PAIRS
);
if (origin != null) {
originOffset = origin[0];
endOffset = originOffset + 1;
originChar = PAIRS[origin[1]];
matchingChar = PAIRS[origin[1] + origin[2]];
backward = origin[2] < 0;
}
}
if (endOffset > 0) {
TokenHierarchy<Document> th = TokenHierarchy.get(context.getDocument());
// to get it work, there should not be checked previous ts. it can be different. see issue #250521
sequences = getEmbeddedTokenSequences(th, originOffset, false, language);
if (!sequences.isEmpty()) {
// Check special tokens
TokenSequence<?> seq = sequences.get(sequences.size() - 1);
seq.move(originOffset);
if (seq.moveNext()) {
if (seq.token().id() == JsTokenId.BLOCK_COMMENT
|| seq.token().id() == JsTokenId.DOC_COMMENT
|| seq.token().id() == JsTokenId.LINE_COMMENT
// remove once we have a lagueage
|| seq.token().id() == JsTokenId.REGEXP
|| seq.token().id() == JsTokenId.STRING) {
return null;
}
if (seq.token().id() == JsTokenId.TEMPLATE_EXP_BEGIN
|| seq.token().id() == JsTokenId.TEMPLATE_EXP_END) {
templateExp = true;
}
}
}
return new int [] { originOffset, endOffset };
} else {
return null;
}
} finally {
((AbstractDocument) context.getDocument()).readUnlock();
}
}
@Override
public int [] findMatches() throws InterruptedException, BadLocationException {
((AbstractDocument) context.getDocument()).readLock();
try {
if (sequences != null && !sequences.isEmpty()) {
TokenSequence<?> seq = sequences.get(sequences.size() - 1);
TokenHierarchy<Document> th = TokenHierarchy.get(context.getDocument());
List<TokenSequence<?>> list;
if (backward) {
list = th.tokenSequenceList(seq.languagePath(), 0, originOffset);
} else {
int offset = originOffset + 1;
if (templateExp) {
offset++;
}
list = th.tokenSequenceList(seq.languagePath(), offset, context.getDocument().getLength());
}
JsTokenId originId = getTokenId(originChar);
JsTokenId lookingForId = getTokenId(matchingChar);
if (templateExp) {
if (originChar == '}') { // NOI18N
originId = JsTokenId.TEMPLATE_EXP_END;
lookingForId = JsTokenId.TEMPLATE_EXP_BEGIN;
} else {
originId = JsTokenId.TEMPLATE_EXP_BEGIN;
lookingForId = JsTokenId.TEMPLATE_EXP_END;
}
}
int counter = 0;
for(TokenSequenceIterator tsi = new TokenSequenceIterator(list, backward); tsi.hasMore(); ) {
TokenSequence<?> sq = tsi.getSequence();
if (originId == sq.token().id()) {
counter++;
} else if (lookingForId == sq.token().id()) {
if (counter == 0) {
return new int [] { sq.offset(), sq.offset() + sq.token().length() };
} else {
counter--;
}
}
}
}
return null;
} finally {
((AbstractDocument) context.getDocument()).readUnlock();
}
}
public static List<TokenSequence<?>> getEmbeddedTokenSequences(
TokenHierarchy<?> th, int offset, boolean backwardBias, Language<?> language) {
List<TokenSequence<?>> sequences = th.embeddedTokenSequences(offset, backwardBias);
for (int i = sequences.size() - 1; i >= 0; i--) {
TokenSequence<?> seq = sequences.get(i);
if (seq.language() == language) {
break;
} else {
sequences.remove(i);
}
}
return sequences;
}
private JsTokenId getTokenId(char ch) {
for(int i = 0; i < PAIRS.length; i++) {
if (PAIRS[i] == ch) {
return PAIR_TOKEN_IDS[i];
}
}
return null;
}
@MimeRegistration(mimeType = JsTokenId.JAVASCRIPT_MIME_TYPE, service = BracesMatcherFactory.class, position=0)
public static class JsBracesMatcherFactory implements BracesMatcherFactory {
@Override
public BracesMatcher createMatcher(MatcherContext context) {
return new JsBracesMatcher(context, JsTokenId.javascriptLanguage());
}
}
@MimeRegistration(mimeType = JsTokenId.JSON_MIME_TYPE, service = BracesMatcherFactory.class, position=0)
public static class JsonBracesMatcherFactory implements BracesMatcherFactory {
@Override
public BracesMatcher createMatcher(MatcherContext context) {
return new JsBracesMatcher(context, JsTokenId.jsonLanguage());
}
}
}