/** * 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.keypress; import com.github.drrb.rust.netbeans.RustLanguage; import com.github.drrb.rust.netbeans.parsing.RustLexUtils; import com.github.drrb.rust.netbeans.parsing.RustTokenId; import static com.github.drrb.rust.netbeans.parsing.RustTokenId.*; import java.util.concurrent.atomic.AtomicBoolean; import javax.swing.text.BadLocationException; import org.netbeans.api.editor.mimelookup.MimePath; import org.netbeans.api.editor.mimelookup.MimeRegistration; import org.netbeans.api.lexer.TokenId; import org.netbeans.api.lexer.TokenSequence; import org.netbeans.editor.Utilities; import org.netbeans.modules.editor.indent.api.IndentUtils; import org.netbeans.spi.editor.typinghooks.TypedBreakInterceptor; /** * */ public class RustBreakInterceptor implements TypedBreakInterceptor { private final AtomicBoolean cancelled = new AtomicBoolean(false); @Override public boolean beforeInsert(Context context) throws BadLocationException { cancelled.set(false); return false; } @Override public void insert(MutableContext ctx) throws BadLocationException { if (cancelled.get()) return; ContextHolder context = new ContextHolder(ctx); if (context.previousTokenKind() != OPEN_BRACE) { return; // Only insert a close brace after an open brace } else if (context.nextRowIndent() > context.currentRowIndent()) { return; // There's already stuff in this block } else if (context.nextTokenKind() == CLOSE_BRACE && context.currentRowIndent() == context.nextRowIndent()) { return; // There's already a closing brace } ctx.setText("\n\n" + context.currentRowIndentString() + "}", 0, 1); } @Override public void afterInsert(Context context) throws BadLocationException { } @Override public void cancelled(Context context) { cancelled.set(true); } private static class ContextHolder { private final MutableContext context; private final TokenSequence<RustTokenId> tokenSequence; private Integer currentRowIndent; private Integer nextRowIndent; private ContextHolder(MutableContext context) { this.context = context; this.tokenSequence = new RustLexUtils().getRustTokenSequence(context.getDocument(), context.getCaretOffset()); tokenSequence.move(context.getCaretOffset()); } private RustTokenId previousTokenKind() { return findNonWhitespaceToken(Direction.BACKWARD); } private RustTokenId nextTokenKind() { return findNonWhitespaceToken(Direction.FORWARD); } private String currentRowIndentString() throws BadLocationException { return IndentUtils.createIndentString(context.getDocument(), currentRowIndent()); } private int currentRowIndent() throws BadLocationException { if (currentRowIndent == null) { int currentRowStart = IndentUtils.lineStartOffset(context.getDocument(), context.getCaretOffset()); currentRowIndent = IndentUtils.lineIndent(context.getDocument(), currentRowStart); } return currentRowIndent; } private int nextRowIndent() throws BadLocationException { if (nextRowIndent == null) { int currentRowEnd = Utilities.getRowEnd(context.getComponent(), context.getCaretOffset()); int nextRowStart = currentRowEnd + 1; nextRowIndent = IndentUtils.lineIndent(context.getDocument(), nextRowStart); } return nextRowIndent; } private RustTokenId findNonWhitespaceToken(Direction direction) { while (direction.move(tokenSequence)) { RustTokenId nextTokenKind = tokenSequence.token().id(); if (nextTokenKind != WHITESPACE) { return nextTokenKind; } } return null; } } private enum Direction { FORWARD { @Override public boolean move(TokenSequence<? extends TokenId> tokenSequence) { return tokenSequence.moveNext(); } }, BACKWARD { @Override public boolean move(TokenSequence<? extends TokenId> tokenSequence) { return tokenSequence.movePrevious(); } }; public abstract boolean move(TokenSequence<? extends TokenId> tokenSequence); } @MimeRegistration(mimeType = RustLanguage.MIME_TYPE, service = TypedBreakInterceptor.Factory.class) public static class Factory implements TypedBreakInterceptor.Factory { @Override public TypedBreakInterceptor createTypedBreakInterceptor(MimePath mimePath) { if (RustLanguage.MIME_TYPE.equals(mimePath.getPath())) { return new RustBreakInterceptor(); } else { return null; } } } }