/**
* Copyright (C) 2015 drrb
*
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation, either version 3 of the License, or (at your option) any later
* version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.github.drrb.rust.netbeans.highlighting;
import com.github.drrb.rust.netbeans.RustLanguage;
import com.github.drrb.rust.netbeans.parsing.RustLexUtils;
import com.github.drrb.rust.netbeans.parsing.RustTokenId;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.text.AbstractDocument;
import javax.swing.text.BadLocationException;
import org.netbeans.api.editor.mimelookup.MimeRegistration;
import org.netbeans.api.lexer.Token;
import org.netbeans.api.lexer.TokenSequence;
import org.netbeans.spi.editor.bracesmatching.BracesMatcher;
import org.netbeans.spi.editor.bracesmatching.BracesMatcherFactory;
import org.netbeans.spi.editor.bracesmatching.MatcherContext;
/**
*
*/
public class RustBracesMatcher implements BracesMatcher {
private static final Logger LOGGER = Logger.getLogger(RustBracesMatcher.class.getName());
@MimeRegistration(mimeType = RustLanguage.MIME_TYPE, service = BracesMatcherFactory.class)
public static class Factory implements BracesMatcherFactory {
@Override
public BracesMatcher createMatcher(MatcherContext context) {
return new RustBracesMatcher(context, new RustLexUtils());
// TODO: is our implementation better than just doing this?:
//return BracesMatcherSupport.defaultMatcher(context, -1, -1);
// Probably, because it's dealing with tokens instead of characters
// but let's keep the option open in case it does stuff ours doesn't
}
}
private final MatcherContext context;
private final RustLexUtils rustLexUtils;
public RustBracesMatcher(MatcherContext context, RustLexUtils rustLexUtils) {
this.context = context;
this.rustLexUtils = rustLexUtils;
}
@Override
public int[] findOrigin() throws InterruptedException, BadLocationException {
AbstractDocument document = (AbstractDocument) context.getDocument();
document.readLock();
try {
int offset = context.getSearchOffset();
TokenSequence<RustTokenId> tokenSequence = rustLexUtils.getRustTokenSequence(document, offset);
if (tokenSequence == null) {
LOGGER.warning("Couldn't get Rust token sequence for braces matching");
return null;
} else {
return getBraceAtOffset(tokenSequence, offset).ends();
}
} finally {
document.readUnlock();
}
}
private OffsetRange getBraceAtOffset(TokenSequence<RustTokenId> tokenSequence, int offset) {
tokenSequence.move(offset);
if (tokenSequence.moveNext()) {
Token<RustTokenId> token = tokenSequence.token();
for (BracePair bracePair : BracePair.values()) {
if (token.id() == bracePair.open || token.id() == bracePair.close) {
return OffsetRange.ofCurrentToken(tokenSequence);
}
}
} else {
LOGGER.log(Level.WARNING, "No token at offset {0}", offset);
}
return OffsetRange.NONE;
}
@Override
public int[] findMatches() throws InterruptedException, BadLocationException {
AbstractDocument document = (AbstractDocument) context.getDocument();
document.readLock();
try {
int offset = context.getSearchOffset();
TokenSequence<RustTokenId> tokenSequence = rustLexUtils.getRustTokenSequence(document, offset);
if (tokenSequence == null) {
LOGGER.warning("Couldn't get Rust token sequence for braces matching");
return null;
} else {
return getBraceMatchingTheOneAtOffset(tokenSequence, offset).ends();
}
} finally {
document.readUnlock();
}
}
private OffsetRange getBraceMatchingTheOneAtOffset(TokenSequence<RustTokenId> tokenSequence, int offset) {
tokenSequence.move(offset);
if (tokenSequence.moveNext()) {
Token<RustTokenId> token = tokenSequence.token();
for (BracePair bracePair : BracePair.values()) {
if (token.id() == bracePair.open) {
return findCloseBraceForward(tokenSequence, bracePair);
} else if (token.id() == bracePair.close) {
return findOpenBraceBackward(tokenSequence, bracePair);
}
}
} else {
LOGGER.log(Level.WARNING, "No token at offset {0}", offset);
}
return OffsetRange.NONE;
}
private static OffsetRange findCloseBraceForward(TokenSequence<? extends RustTokenId> tokenSequence, BracePair bracePair) {
int balance = 0;
while (tokenSequence.moveNext()) {
Token<? extends RustTokenId> token = tokenSequence.token();
if (token.id() == bracePair.open) {
balance++;
} else if (token.id() == bracePair.close) {
if (balance == 0) {
return OffsetRange.ofCurrentToken(tokenSequence);
}
balance--;
}
}
return OffsetRange.NONE;
}
private static OffsetRange findOpenBraceBackward(TokenSequence<? extends RustTokenId> tokenSequence, BracePair bracePair) {
int balance = 0;
while (tokenSequence.movePrevious()) {
Token<? extends RustTokenId> token = tokenSequence.token();
if (token.id() == bracePair.open) {
if (balance == 0) {
return OffsetRange.ofCurrentToken(tokenSequence);
}
balance++;
} else if (token.id() == bracePair.close) {
balance--;
}
}
return OffsetRange.NONE;
}
private enum BracePair {
PARENS(RustTokenId.OPEN_PAREN, RustTokenId.CLOSE_PAREN),
BRACES(RustTokenId.OPEN_BRACE, RustTokenId.CLOSE_BRACE),
BRACKETS(RustTokenId.OPEN_BRACKET, RustTokenId.CLOSE_BRACKET),
ANGLES(RustTokenId.LT, RustTokenId.GT);
final RustTokenId open;
final RustTokenId close;
private BracePair(RustTokenId open, RustTokenId close) {
this.open = open;
this.close = close;
}
}
private static class OffsetRange {
static final OffsetRange NONE = new OffsetRange(-1, -1);
private final int start;
private final int end;
OffsetRange(int start, int end) {
this.start = start;
this.end = end;
}
int[] ends() {
if (this == NONE) {
return null;
} else {
return new int[]{start, end};
}
}
static OffsetRange ofCurrentToken(TokenSequence<?> tokenSequence) {
Token<?> token = tokenSequence.token();
return new OffsetRange(tokenSequence.offset(), tokenSequence.offset() + token.length());
}
}
}