/* * 01/25/2009 * * AbstractJFlexCTokenMaker.java - Base class for token makers that use curly * braces to denote code blocks, such as C, C++, Java, Perl, etc. * Copyright (C) 2009 Robert Futrell * robert_futrell at users.sourceforge.net * http://fifesoft.com/rsyntaxtextarea * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. */ package org.fife.ui.rsyntaxtextarea; import java.awt.event.ActionEvent; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.swing.Action; import javax.swing.UIManager; import javax.swing.text.BadLocationException; import org.fife.ui.rtextarea.RTextArea; /** * Base class for JFlex-based token makers using C-style syntax. This class knows how to auto-indent after opening * braces and parens. * * @author Robert Futrell * @version 1.0 */ public abstract class AbstractJFlexCTokenMaker extends AbstractJFlexTokenMaker { protected static final Action INSERT_BREAK_ACTION = new InsertBreakAction(); /** * Returns <code>true</code> always as C-style languages use curly braces to denote code blocks. * * @return <code>true</code> always. */ public boolean getCurlyBracesDenoteCodeBlocks() { return true; } /** * Returns an action to handle "insert break" key presses (i.e. Enter). An action is returned that handles newlines * differently in multi-line comments. * * @return The action. */ public Action getInsertBreakAction() { return INSERT_BREAK_ACTION; } /** * {@inheritDoc} */ public boolean getShouldIndentNextLineAfter(Token t) { if (t != null && t.textCount == 1) { char ch = t.text[t.textOffset]; return ch == '{' || ch == '('; } return false; } /** * Action that knows how to special-case inserting a newline in a multi-line comment for languages like C and Java. */ private static class InsertBreakAction extends RSyntaxTextAreaEditorKit.InsertBreakAction { private static final Pattern p = Pattern.compile("([ \\t]*)(/?[\\*]+)([ \\t]*)"); public void actionPerformedImpl(ActionEvent e, RTextArea textArea) { if (!textArea.isEditable() || !textArea.isEnabled()) { UIManager.getLookAndFeel().provideErrorFeedback(textArea); return; } RSyntaxTextArea rsta = (RSyntaxTextArea) getTextComponent(e); RSyntaxDocument doc = (RSyntaxDocument) rsta.getDocument(); int line = textArea.getCaretLineNumber(); int type = doc.getLastTokenTypeOnLine(line); // Only in MLC's should we try this if (type == Token.COMMENT_DOCUMENTATION || type == Token.COMMENT_MULTILINE) { insertBreakInMLC(e, rsta, line); } else { handleInsertBreak(rsta, true); } } /** * Returns whether the MLC token containing <code>offs</code> appears to have a "nested" comment (i.e., contains * "<code>/*</code>" somewhere inside of it). This implies that it is likely a "new" MLC and needs to be closed. * While not foolproof, this is usually good enough of a sign. * * @param textArea * @param line * @param offs * @return Whether a comment appears to be nested inside this one. */ private boolean appearsNested(RSyntaxTextArea textArea, int line, int offs) { final int firstLine = line; // Remember the line we start at. while (line < textArea.getLineCount()) { Token t = textArea.getTokenListForLine(line); int i = 0; // If examining the first line, start at offs. if (line++ == firstLine) { t = RSyntaxUtilities.getTokenAtOffset(t, offs); if (t == null) { // offs was at end of the line continue; } i = t.documentToToken(offs); } else { i = t.textOffset; } while (i < t.textOffset + t.textCount - 1) { if (t.text[i] == '/' && t.text[i + 1] == '*') { return true; } i++; } // If tokens come after this one on this line, our MLC ended. if (t.getNextToken() != null) { return false; } } return true; // No match - MLC goes to the end of the file } private void insertBreakInMLC(ActionEvent e, RSyntaxTextArea textArea, int line) { Matcher m = null; int start = -1; int end = -1; try { start = textArea.getLineStartOffset(line); end = textArea.getLineEndOffset(line); String text = textArea.getText(start, end - start); m = p.matcher(text); } catch (BadLocationException ble) { // Never happens UIManager.getLookAndFeel().provideErrorFeedback(textArea); ble.printStackTrace(); return; } if (m.lookingAt()) { String leadingWS = m.group(1); String mlcMarker = m.group(2); // If the caret is "inside" any leading whitespace or MLC // marker, move it to the end of the line. int dot = textArea.getCaretPosition(); if (dot >= start && dot < start + leadingWS.length() + mlcMarker.length()) { // If we're in the whitespace before the very start of the // MLC though, just insert a normal newline if (mlcMarker.charAt(0) == '/') { handleInsertBreak(textArea, true); return; } textArea.setCaretPosition(end - 1); } boolean firstMlcLine = mlcMarker.charAt(0) == '/'; boolean nested = appearsNested(textArea, line, start + leadingWS.length() + 2); String header = leadingWS + (firstMlcLine ? " * " : "*") + m.group(3); textArea.replaceSelection("\n" + header); if (nested) { dot = textArea.getCaretPosition(); // Has changed textArea.insert("\n" + leadingWS + " */", dot); textArea.setCaretPosition(dot); } } else { handleInsertBreak(textArea, true); } } } }