/* * 02/21/2005 * * CodeTemplate.java - A "template" (macro) for commonly-typed code. * Copyright (C) 2005 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.templates; import java.io.IOException; import java.io.ObjectInputStream; import javax.swing.text.BadLocationException; import javax.swing.text.Caret; import javax.swing.text.Element; import org.fife.ui.rsyntaxtextarea.RSyntaxDocument; import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea; import org.fife.ui.rsyntaxtextarea.RSyntaxUtilities; /** * A code template that inserts static text before and after the caret. * <p> * * For example, you can associate the identifier <code>forb</code> (short for "for-block") with the following code: * <p> * * <pre> * for (<caret>) { * * } * </pre> * * Then, whenever you type <code>forb</code> followed by a trigger (e.g., a space) into a text area with this * <code>CodeTemplate</code>, the code snippet is added in place of <code>forb</code>. Further, the caret is placed at * the position denoted by <code><caret></code>. * * @author Robert Futrell * @version 0.1 * @see CodeTemplate */ public class StaticCodeTemplate extends AbstractCodeTemplate { private static final long serialVersionUID = 1; /** * The code inserted before the caret position. */ private String beforeCaret; /** * The code inserted after the caret position. */ private String afterCaret; /** * Cached value representing whether <code>beforeCaret</code> contains one or more newlines. */ private transient int firstBeforeNewline; /** * Cached value representing whether <code>afterCaret</code> contains one or more newlines. */ private transient int firstAfterNewline; private static final String EMPTY_STRING = ""; /** * Constructor. This constructor only exists to support persistance through serialization. */ public StaticCodeTemplate() { } /** * Constructor. * * @param id * The ID of this code template. * @param beforeCaret * The text to place before the caret. * @param afterCaret * The text to place after the caret. */ public StaticCodeTemplate(String id, String beforeCaret, String afterCaret) { super(id); setBeforeCaretText(beforeCaret); setAfterCaretText(afterCaret); } /** * Returns the text that will be placed after the caret. * * @return The text. * @see #setAfterCaretText */ public String getAfterCaretText() { return afterCaret; } /** * Returns the text that will be placed before the caret. * * @return The text. * @see #setBeforeCaretText */ public String getBeforeCaretText() { return beforeCaret; } /** * Returns the "after caret" text, with each new line indented by the specified amount. * * @param indent * The amount to indent. * @return The "after caret" text. */ private String getAfterTextIndented(String indent) { return getTextIndented(getAfterCaretText(), firstAfterNewline, indent); } /** * Returns the "before caret" text, with each new line indented by the specified amount. * * @param indent * The amount to indent. * @return The "before caret" text. */ private String getBeforeTextIndented(String indent) { return getTextIndented(getBeforeCaretText(), firstBeforeNewline, indent); } /** * Returns text with newlines indented by the specifed amount. * * @param text * The original text. * @param firstNewline * The index of the first '\n' character. * @param indent * The amount to indent. * @return The indented text. */ private String getTextIndented(String text, int firstNewline, String indent) { if (firstNewline == -1) { return text; } int pos = 0; int old = firstNewline + 1; StringBuffer sb = new StringBuffer(text.substring(0, old)); sb.append(indent); while ((pos = text.indexOf('\n', old)) > -1) { sb.append(text.substring(old, pos + 1)); sb.append(indent); old = pos + 1; } if (old < text.length()) { sb.append(text.substring(old)); } return sb.toString(); } /** * Invokes this code template. The changes are made to the given text area. * * @param textArea * The text area to operate on. * @throws BadLocationException * If something bad happens. */ public void invoke(RSyntaxTextArea textArea) throws BadLocationException { Caret c = textArea.getCaret(); int dot = c.getDot(); int mark = c.getMark(); int p0 = Math.min(dot, mark); int p1 = Math.max(dot, mark); RSyntaxDocument doc = (RSyntaxDocument) textArea.getDocument(); Element map = doc.getDefaultRootElement(); int lineNum = map.getElementIndex(dot); Element line = map.getElement(lineNum); int start = line.getStartOffset(); int end = line.getEndOffset() - 1; // Why always "-1"? String s = textArea.getText(start, end - start); int len = s.length(); // endWS is the end of the leading whitespace // of the current line. int endWS = 0; while (endWS < len && RSyntaxUtilities.isWhitespace(s.charAt(endWS))) { endWS++; } s = s.substring(0, endWS); p0 -= getID().length(); String beforeText = getBeforeTextIndented(s); String afterText = getAfterTextIndented(s); doc.replace(p0, p1 - p0, beforeText + afterText, null); textArea.setCaretPosition(p0 + beforeText.length()); } /** * Called when reading a serialized version of this document. This is overridden to initialize the transient members * of this class. * * @param in * The input stream to read from. * @throws ClassNotFoundException * Never. * @throws IOException * If an IO error occurs. */ private void readObject(ObjectInputStream in) throws ClassNotFoundException, IOException { in.defaultReadObject(); // "Resetting" before and after text to the same values will replace // nulls with empty srings, and set transient "first*Newline" values. setBeforeCaretText(this.beforeCaret); setAfterCaretText(this.afterCaret); } /** * Sets the text to place after the caret. * * @param afterCaret * The text. * @see #getAfterCaretText() */ public void setAfterCaretText(String afterCaret) { this.afterCaret = afterCaret == null ? EMPTY_STRING : afterCaret; firstAfterNewline = this.afterCaret.indexOf('\n'); } /** * Sets the text to place before the caret. * * @param beforeCaret * The text. * @see #getBeforeCaretText() */ public void setBeforeCaretText(String beforeCaret) { this.beforeCaret = beforeCaret == null ? EMPTY_STRING : beforeCaret; firstBeforeNewline = this.beforeCaret.indexOf('\n'); } /** * Returns a string representation of this template for debugging purposes. * * @return A string representation of this template. */ public String toString() { return "[StaticCodeTemplate: id=" + getID() + ", text=" + getBeforeCaretText() + "|" + getAfterCaretText() + "]"; } }