/*
* 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() + "]";
}
}