* Copyright (C) 2013 Robert Futrell
* robert_futrell at users.sourceforge.net
* http://fifesoft.com/rsyntaxtextarea
* This library is distributed under a modified BSD license. See the included
* RSTALanguageSupport.License.txt file for details.
package org.fife.rsta.ac;
import java.awt.event.ActionEvent;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.KeyStroke;
import javax.swing.text.Caret;
import javax.swing.text.Element;
import javax.swing.text.TextAction;
import org.fife.ui.rsyntaxtextarea.RSyntaxDocument;
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
import org.fife.ui.rsyntaxtextarea.RSyntaxUtilities;
import org.fife.ui.rsyntaxtextarea.Token;
* Base class for language supports for markup languages, such as HTML, PHP,
* and JSP. This class facilitates support for automatically adding a closing
* tag (e.g. "<code></foo></code>") when the user types an opening tag
* (e.g. "<code><foo attr='val'></code>").
public abstract class AbstractMarkupLanguageSupport
extends AbstractLanguageSupport {
protected static final String INSERT_CLOSING_TAG_ACTION =
"HtmlLanguageSupport.InsertClosingTag";
* Whether closing tags are automatically added when the user types opening
* tags. For HTML, this will only occur for tags that are allowed to have
* closing tags, based on the doctype. For XML, this will always occur if
* this property is set to <code>true</code>.
private boolean autoAddClosingTags;
protected AbstractMarkupLanguageSupport() {
setAutoAddClosingTags(true);
* Returns whether closing tags should be automatically added when the user
* types a (non-self-closing) start tag. This will only be done for tags
* where closing tags are accepted and valid, based on the doctype.
* @return Whether to automatically add closing tags.
* @see #setAutoAddClosingTags(boolean)
public boolean getAutoAddClosingTags() {
return autoAddClosingTags;
* Installs extra keyboard shortcuts supported by this language support.
* The default implementation maps an action to automatically add closing
* tags when '>' is pressed; subclasses can override and add additional
* shortcuts if desired.<p>
* Subclasses should call this method in their
* {@link #install(RSyntaxTextArea)} methods.
* @param textArea The text area to install the shortcuts into.
* @see #uninstallKeyboardShortcuts(RSyntaxTextArea)
protected void installKeyboardShortcuts(RSyntaxTextArea textArea) {
InputMap im = textArea.getInputMap();
ActionMap am = textArea.getActionMap();
im.put(KeyStroke.getKeyStroke('>'), INSERT_CLOSING_TAG_ACTION);
am.put(INSERT_CLOSING_TAG_ACTION, new InsertClosingTagAction());
* Subclasses should override this method to return whether a specified
* tag should have its closing tag auto-inserted.
* @param tag The name of the tag to check.
* @return Whether the tag should have its closing tag auto-inserted.
protected abstract boolean shouldAutoCloseTag(String tag);
* Sets whether closing tags should be automatically added when the user
* types a (non-self-closing) start tag. This will only be done for tags
* where closing tags are accepted and valid, based on the doctype.
* @param autoAdd Whether to automatically add closing tags.
* @see #getAutoAddClosingTags()
public void setAutoAddClosingTags(boolean autoAdd) {
autoAddClosingTags = autoAdd;
* Uninstalls any keyboard shortcuts specific to this language support.<p>
* Subclasses should call this method in their
* {@link #uninstall(RSyntaxTextArea)} methods.
* @param textArea The text area to uninstall the actions from.
* @see #installKeyboardShortcuts(RSyntaxTextArea)
protected void uninstallKeyboardShortcuts(RSyntaxTextArea textArea) {
InputMap im = textArea.getInputMap();
ActionMap am = textArea.getActionMap();
im.remove(KeyStroke.getKeyStroke('>'));
am.remove(INSERT_CLOSING_TAG_ACTION);
* Action that checks whether a closing tag should be auto-inserted into
* the document. Subclasses should map this action to the '>'
private class InsertClosingTagAction extends TextAction {
InsertClosingTagAction() {
super(INSERT_CLOSING_TAG_ACTION);
public void actionPerformed(ActionEvent e) {
RSyntaxTextArea textArea = (RSyntaxTextArea)getTextComponent(e);
RSyntaxDocument doc = (RSyntaxDocument)textArea.getDocument();
Caret c = textArea.getCaret();
int dot = c.getDot(); // Get before "<" insertion
boolean selection = dot!=c.getMark(); // Me too
textArea.replaceSelection(">");
// Don't automatically complete a tag if there was a selection
if (!selection && getAutoAddClosingTags()) {
Token t = doc.getTokenListForLine(textArea.getCaretLineNumber());
t = RSyntaxUtilities.getTokenAtOffset(t, dot);
if (t!=null && t.isSingleChar(Token.MARKUP_TAG_DELIMITER, '>')) {
String tagName = discoverTagName(doc, dot);
textArea.replaceSelection("</" + tagName + ">");
textArea.setCaretPosition(dot+1);
* Discovers the name of the tag just opened, if any. Assumes standard
* SGML-style markup tags.
* @param doc The document to parse.
* @param dot The location of the caret. This should be right at a
* "<code>></code>" character closing an HTML tag.
* @return The name of the tag to close, or <code>null</code> if it
* could not be determined.
private String discoverTagName(RSyntaxDocument doc, int dot) {
Element root = doc.getDefaultRootElement();
int curLine = root.getElementIndex(dot);
// For now, we only check for tags on the current line, for
// simplicity. Tags spanning multiple lines aren't common anyway.
Token t = doc.getTokenListForLine(curLine);
while (t!=null && t.isPaintable()) {
if (t.getType()==Token.MARKUP_TAG_DELIMITER) {
if (t.isSingleChar('<')) {
if (t!=null && t.isPaintable()) {
candidate = t.getLexeme();
else if (t.isSingleChar('>')) {
if (t.getOffset()==dot) {
shouldAutoCloseTag(candidate)) {
else if (t.is(Token.MARKUP_TAG_DELIMITER, "</")) {
return null; // No match found