gt: 				Element line = map.getElement(lineNum);
context: /*
   * 08/29/2004
   *
   * RSyntaxTextAreaEditorKit.java - The editor kit used by RSyntaxTextArea.
   * 
   * This library is distributed under a modified BSD license.  See the included
   * RSyntaxTextArea.License.txt file for details.
   */
  package com.fr.design.gui.syntax.ui.rsyntaxtextarea;
  
  import java.awt.*;
  import java.awt.event.*;
  import java.util.ResourceBundle;
  import java.util.Stack;
  import javax.swing.*;
  import javax.swing.text.*;
  
  import com.fr.design.gui.syntax.ui.rsyntaxtextarea.folding.Fold;
  import com.fr.design.gui.syntax.ui.rsyntaxtextarea.folding.FoldCollapser;
  import com.fr.design.gui.syntax.ui.rsyntaxtextarea.folding.FoldManager;
  import com.fr.design.gui.syntax.ui.rsyntaxtextarea.templates.CodeTemplate;
  import com.fr.design.gui.syntax.ui.rtextarea.Gutter;
  import com.fr.design.gui.syntax.ui.rtextarea.IconRowHeader;
  import com.fr.design.gui.syntax.ui.rtextarea.RecordableTextAction;
  import com.fr.design.gui.syntax.ui.rtextarea.RTextArea;
  import com.fr.design.gui.syntax.ui.rtextarea.RTextAreaEditorKit;
  
  
  /**
   * An extension of <code>RTextAreaEditorKit</code> that adds functionality for
   * programming-specific stuff.  There are currently subclasses to handle:
   *
   * <ul>
   *   <li>Toggling code folds.</li>
   *   <li>Aligning "closing" curly braces with their matches, if the current
   *       programming language uses curly braces to identify code blocks.</li>
   *   <li>Copying the current selection as RTF.</li>
   *   <li>Block indentation (increasing the indent of one or multiple lines)</li>
   *   <li>Block un-indentation (decreasing the indent of one or multiple lines)
   *       </li>
   *   <li>Inserting a "code template" when a configurable key (e.g. a space) is
   *       pressed</li>
   *   <li>Decreasing the point size of all fonts in the text area</li>
   *   <li>Increasing the point size of all fonts in the text area</li>
   *   <li>Moving the caret to the "matching bracket" of the one at the current
   *       caret position</li>
   *   <li>Toggling whether the currently selected lines are commented out.</li>
   *   <li>Better selection of "words" on mouse double-clicks for programming
   *       languages.</li>
   *   <li>Better keyboard navigation via Ctrl+arrow keys for programming
   *       languages.</li>
   * </ul>
   *
   * @author Robert Futrell
   * @version 0.5
   */
  public class RSyntaxTextAreaEditorKit extends RTextAreaEditorKit {
  
  	private static final long serialVersionUID = 1L;
  
  	public static final String rstaCloseCurlyBraceAction	= "RSTA.CloseCurlyBraceAction";
  	public static final String rstaCloseMarkupTagAction		= "RSTA.CloseMarkupTagAction";
  	public static final String rstaCollapseAllFoldsAction	= "RSTA.CollapseAllFoldsAction";
  	public static final String rstaCollapseAllCommentFoldsAction = "RSTA.CollapseAllCommentFoldsAction";
  	public static final String rstaCollapseFoldAction		= "RSTA.CollapseFoldAction";
  	public static final String rstaCopyAsRtfAction			= "RSTA.CopyAsRtfAction";
  	public static final String rstaDecreaseIndentAction		= "RSTA.DecreaseIndentAction";
  	public static final String rstaExpandAllFoldsAction		= "RSTA.ExpandAllFoldsAction";
  	public static final String rstaExpandFoldAction			= "RSTA.ExpandFoldAction";
  	public static final String rstaGoToMatchingBracketAction	= "RSTA.GoToMatchingBracketAction";
  	public static final String rstaPossiblyInsertTemplateAction = "RSTA.TemplateAction";
  	public static final String rstaToggleCommentAction 		= "RSTA.ToggleCommentAction";
  	public static final String rstaToggleCurrentFoldAction	= "RSTA.ToggleCurrentFoldAction";
  
  	private static final String MSG	= "com.fr.design.gui.syntax.ui.rsyntaxtextarea.RSyntaxTextArea";
  	private static final ResourceBundle msg = ResourceBundle.getBundle(MSG);
  
  
  	/**
  	 * The actions that <code>RSyntaxTextAreaEditorKit</code> adds to those of
  	 * <code>RTextAreaEditorKit</code>.
  	 */
  	private static final Action[] defaultActions = {
  		new CloseCurlyBraceAction(),
  		new CloseMarkupTagAction(),
  		new BeginWordAction(beginWordAction, false),
  		new BeginWordAction(selectionBeginWordAction, true),
  		new ChangeFoldStateAction(rstaCollapseFoldAction, true),
  		new ChangeFoldStateAction(rstaExpandFoldAction, false),
  		new CollapseAllFoldsAction(),
  		new CopyAsRtfAction(),
  		//new DecreaseFontSizeAction(),
  		new DecreaseIndentAction(),
  		new DeletePrevWordAction(),
  		new EndAction(endAction, false),
  		new EndAction(selectionEndAction, true),
  		new EndWordAction(endWordAction, false),
  		new EndWordAction(endWordAction, true),
  		new ExpandAllFoldsAction(),
  		new GoToMatchingBracketAction(),
  		new InsertBreakAction(),
  		//new IncreaseFontSizeAction(),
  		new InsertTabAction(),
  		new NextWordAction(nextWordAction, false),
  		new NextWordAction(selectionNextWordAction, true),
  		new PossiblyInsertTemplateAction(),
  		new PreviousWordAction(previousWordAction, false),  
  		new PreviousWordAction(selectionPreviousWordAction, true),
  		new SelectWordAction(),
  		new ToggleCommentAction(),
  	};
  
  
  	/**
  	 * Constructor.
  	 */
  	public RSyntaxTextAreaEditorKit() {
  	}
  
  
  	/**
  	 * Returns the default document used by <code>RSyntaxTextArea</code>s.
  	 *
  	 * @return The document.
  	 */
  	@Override
  	public Document createDefaultDocument() {
  		return new RSyntaxDocument(SyntaxConstants.SYNTAX_STYLE_NONE);
  	}
  
  
  	/**
  	 * Overridden to return a row header that is aware of folding.
  	 *
  	 * @param textArea The text area.
  	 * @return The icon row header.
  	 */
  	@Override
  	public IconRowHeader createIconRowHeader(RTextArea textArea) {
  		return new FoldingAwareIconRowHeader((RSyntaxTextArea)textArea);
  	}
  
  
  	/**
  	 * Fetches the set of commands that can be used
  	 * on a text component that is using a model and
  	 * view produced by this kit.
  	 *
  	 * @return the command list
  	 */ 
  	@Override
  	public Action[] getActions() {
  		return TextAction.augmentList(super.getActions(),
  							RSyntaxTextAreaEditorKit.defaultActions);
  	}
  
  
  	/**
  	 * Returns localized text for an action.  There's definitely a better place
  	 * for this functionality.
  	 *
  	 * @param key The key into the action resource bundle.
  	 * @return The localized text.
  	 */
  	public static String getString(String key) {
  		return msg.getString(key);
  	}
  
  
  	/**
  	 * Positions the caret at the beginning of the word.  This class is here
  	 * to better handle finding the "beginning of the word" for programming
  	 * languages.
  	 */
  	protected static class BeginWordAction
  							extends RTextAreaEditorKit.BeginWordAction {
  
  		private Segment seg;
  
  		protected BeginWordAction(String name, boolean select) {
  			super(name, select);
  			seg = new Segment();
  		}
  
  		@Override
  		protected int getWordStart(RTextArea textArea, int offs)
  										throws BadLocationException {
  
  			if (offs==0) {
  				return offs;
  			}
  
  			RSyntaxDocument doc = (RSyntaxDocument)textArea.getDocument();
  			int line = textArea.getLineOfOffset(offs);
  			int start = textArea.getLineStartOffset(line);
  			if (offs==start) {
  				return start;
  			}
  			int end = textArea.getLineEndOffset(line);
  			if (line!=textArea.getLineCount()-1) {
  				end--;
  			}
  			doc.getText(start, end-start, seg);
  
  			// Determine the "type" of char at offs - lower case, upper case,
  			// whitespace or other.  We take special care here as we're starting
  			// in the middle of the Segment to check whether we're already at
  			// the "beginning" of a word.
  			int firstIndex = seg.getBeginIndex() + (offs-start) - 1;
  			seg.setIndex(firstIndex);
  			char ch = seg.current();
  			char nextCh = offs==end ? 0 : seg.array[seg.getIndex() + 1];
  
  			// The "word" is a group of letters and/or digits
  			if (Character.isLetterOrDigit(ch)) {
  				if (offs!=end && !Character.isLetterOrDigit(nextCh)) {
  					return offs;
  				}
  				do {
  					ch = seg.previous();
  				} while (Character.isLetterOrDigit(ch));
  			}
  
  			// The "word" is whitespace
  			else if (Character.isWhitespace(ch)) {
  				if (offs!=end && !Character.isWhitespace(nextCh)) {
  					return offs;
  				}
  				do {
  					ch = seg.previous();
  				} while (Character.isWhitespace(ch));
  			}
  
  			// Otherwise, the "word" a single "something else" char (operator,
  			// etc.).
  
  			offs -= firstIndex - seg.getIndex() + 1;//seg.getEndIndex() - seg.getIndex();
  			if (ch!=Segment.DONE && nextCh!='\n') {
  				offs++;
  			}
  
  			return offs;
  
  		}
  
  	}
  
  
  	/**
  	 * Expands or collapses the nearest fold.
  	 */
  	public static class ChangeFoldStateAction extends FoldRelatedAction {
  
  		private boolean collapse;
  
  		public ChangeFoldStateAction(String name, boolean collapse) {
  			super(name);
  			this.collapse = collapse;
  		}
  
  		public ChangeFoldStateAction(String name, Icon icon,
  				String desc, Integer mnemonic, KeyStroke accelerator) {
  			super(name, icon, desc, mnemonic, accelerator);
  		}
  
  		@Override
  		public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
  			RSyntaxTextArea rsta = (RSyntaxTextArea)textArea;
  			if (rsta.isCodeFoldingEnabled()) {
  				Fold fold = getClosestFold(rsta);
  				if (fold!=null) {
  					fold.setCollapsed(collapse);
  				}
  				possiblyRepaintGutter(textArea);
  			}
  			else {
  				UIManager.getLookAndFeel().provideErrorFeedback(rsta);
  			}
  		}
  
  		@Override
  		public final String getMacroID() {
  			return getName();
  		}
  
  	}
  
  
  	/**
  	 * Action that (optionally) aligns a closing curly brace with the line
  	 * containing its matching opening curly brace.
  	 */
  	public static class CloseCurlyBraceAction extends RecordableTextAction {
  		
  		private static final long serialVersionUID = 1L;
  
  		private Point bracketInfo;
  		private Segment seg;
  
  		public CloseCurlyBraceAction() {
  			super(rstaCloseCurlyBraceAction);
  			seg = new Segment();
  		}
  
  		@Override
  		public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
  
  			RSyntaxTextArea rsta = (RSyntaxTextArea)textArea;
  			RSyntaxDocument doc = (RSyntaxDocument)rsta.getDocument();
  			boolean alignCurlyBraces = rsta.isAutoIndentEnabled() &&
  										doc.getCurlyBracesDenoteCodeBlocks();
  
  			if (alignCurlyBraces) {
  				textArea.beginAtomicEdit();
  			}
  
  			try {
  
  				textArea.replaceSelection("}");
  
  				// If the user wants to align curly braces...
  				if (alignCurlyBraces) {
  
  					Element root = doc.getDefaultRootElement();
  					int dot = rsta.getCaretPosition() - 1; // Start before '{'
  					int line = root.getElementIndex(dot);
  					Element elem = root.getElement(line);
  					int start = elem.getStartOffset();
  
  					// Get the current line's text up to the '}' entered.
  					try {
  						doc.getText(start, dot-start, seg);
  					} catch (BadLocationException ble) { // Never happens
  						ble.printStackTrace();
  						return;
  					}
  
  					// Only attempt to align if there's only whitespace up to
  					// the '}' entered.
  					for (int i=0; i<seg.count; i++) {
  						char ch = seg.array[seg.offset+i];
  						if (!Character.isWhitespace(ch)) {
  							return;
  						}
  					}
  
  					// Locate the matching '{' bracket, and replace the leading
  					// whitespace for the '}' to match that of the '{' char's line.
  					bracketInfo = RSyntaxUtilities.getMatchingBracketPosition(
  							rsta, bracketInfo);
  					if (bracketInfo.y>-1) {
  						try {
  							String ws = RSyntaxUtilities.getLeadingWhitespace(
  									doc, bracketInfo.y);
  							rsta.replaceRange(ws, start, dot);
  						} catch (BadLocationException ble) {
  							ble.printStackTrace();
  							return;
  						}
  					}
  
  				}
  
  			} finally {
  				if (alignCurlyBraces) {
  					textArea.endAtomicEdit();
  				}
  			}
  
  		}
  
  		@Override
  		public final String getMacroID() {
  			return rstaCloseCurlyBraceAction;
  		}
  
  	}
  
  
  	/**
  	 * (Optionally) completes a closing markup tag.
  	 */
  	public static class CloseMarkupTagAction extends RecordableTextAction {
  
  		private static final long serialVersionUID = 1L;
  
  		public CloseMarkupTagAction() {
  			super(rstaCloseMarkupTagAction);
  		}
  
  		@Override
  		public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
  
  			if (!textArea.isEditable() || !textArea.isEnabled()) {
  				UIManager.getLookAndFeel().provideErrorFeedback(textArea);
  				return;
  			}
  
  			RSyntaxTextArea rsta = (RSyntaxTextArea)textArea;
  			RSyntaxDocument doc = (RSyntaxDocument)rsta.getDocument();
  
  			Caret c = rsta.getCaret();
  			boolean selection = c.getDot()!=c.getMark();
  			rsta.replaceSelection("/");
  
  			// Don't automatically complete a tag if there was a selection
  			int dot = c.getDot();
  
  			if (doc.getLanguageIsMarkup() && 
  					doc.getCompleteMarkupCloseTags() &&
  					!selection && rsta.getCloseMarkupTags() && dot>1) {
  
  				try {
  
  					// Check actual char before token type, since it's quicker
  					char ch = doc.charAt(dot-2);
  					if (ch=='<' || ch=='[') {
  
  						Token t = doc.getTokenListForLine(
  											rsta.getCaretLineNumber());
  						t = RSyntaxUtilities.getTokenAtOffset(t, dot-1);
  						if (t!=null && t.getType()==Token.MARKUP_TAG_DELIMITER) {
  							//System.out.println("Huzzah - closing tag!");
  							String tagName = discoverTagName(doc, dot);
  							if (tagName!=null) {
  								rsta.replaceSelection(tagName + (char)(ch+2));
  							}
  						}
  
  					}
  
  				} catch (BadLocationException ble) { // Never happens
  					UIManager.getLookAndFeel().provideErrorFeedback(rsta);
  					ble.printStackTrace();
  				}
  
  			}
  
  		}
  
  		/**
  		 * Discovers the name of the tag being closed.  Assumes standard
  		 * SGML-style markup tags.
  		 *
  		 * @param doc The document to parse.
  		 * @param dot The location of the caret.  This should be right after
  		 *        the start of a closing tag token (e.g. "<code>&lt;/</code>"
  		 *        or "<code>[</code>" in the case of BBCode).
  		 * @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) {
  
  			Stack<String> stack = new Stack<String>();
  
  			Element root = doc.getDefaultRootElement();
  			int curLine = root.getElementIndex(dot);
  
  			for (int i=0; i<=curLine; i++) {
  
  				Token t = doc.getTokenListForLine(i);
  				while (t!=null && t.isPaintable()) {
  
  					if (t.getType()==Token.MARKUP_TAG_DELIMITER) {
  						if (t.isSingleChar('<') || t.isSingleChar('[')) {
  							t = t.getNextToken();
  							while (t!=null && t.isPaintable()) {
  								if (t.getType()==Token.MARKUP_TAG_NAME ||
  										// Being lenient here and also checking
  										// for attributes, in case they
  										// (incorrectly) have whitespace between
  										// the '<' char and the element name.
  										t.getType()==Token.MARKUP_TAG_ATTRIBUTE) {
  									stack.push(t.getLexeme());
  									break;
  								}
  								t = t.getNextToken();
  							}
  						}
  						else if (t.length()==2 && t.charAt(0)=='/' &&
  								(t.charAt(1)=='>' ||
  										t.charAt(1)==']')) {
  							if (!stack.isEmpty()) { // Always true for valid XML
  								stack.pop();
  							}
  						}
  						else if (t.length()==2 && 
  								(t.charAt(0)=='<' || t.charAt(0)=='[') &&
  								t.charAt(1)=='/') {
  							String tagName = null;
  							if (!stack.isEmpty()) { // Always true for valid XML
  								tagName = stack.pop();
  							}
  							if (t.getEndOffset()>=dot) {
  								return tagName;
  							}
  						}
  					}
  
  					t = t.getNextToken();
  
  				}
  
  			}
  
  			return null; // Should never happen
  
  		}
  
  		@Override
  		public String getMacroID() {
  			return getName();
  		}
  
  	}
  
  
  	/**
  	 * Collapses all comment folds.
  	 */
  	public static class CollapseAllCommentFoldsAction extends FoldRelatedAction{
  
  		private static final long serialVersionUID = 1L;
  
  		public CollapseAllCommentFoldsAction() {
  			super(rstaCollapseAllCommentFoldsAction);
  			setProperties(msg, "Action.CollapseCommentFolds");
  		}
  
  		public CollapseAllCommentFoldsAction(String name, Icon icon,
  				String desc, Integer mnemonic, KeyStroke accelerator) {
  			super(name, icon, desc, mnemonic, accelerator);
  		}
  
  		@Override
  		public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
  			RSyntaxTextArea rsta = (RSyntaxTextArea)textArea;
  			if (rsta.isCodeFoldingEnabled()) {
  				FoldCollapser collapser = new FoldCollapser();
  				collapser.collapseFolds(rsta.getFoldManager());
  				possiblyRepaintGutter(textArea);
  			}
  			else {
  				UIManager.getLookAndFeel().provideErrorFeedback(rsta);
  			}
  		}
  
  		@Override
  		public final String getMacroID() {
  			return rstaCollapseAllCommentFoldsAction;
  		}
  
  	}
  
  
  	/**
  	 * Collapses all folds.
  	 */
  	public static class CollapseAllFoldsAction extends FoldRelatedAction {
  
  		private static final long serialVersionUID = 1L;
  
  		public CollapseAllFoldsAction() {
  			this(false);
  		}
  
  		public CollapseAllFoldsAction(boolean localizedName) {
  			super(rstaCollapseAllFoldsAction);
  			if (localizedName) {
  				setProperties(msg, "Action.CollapseAllFolds");
  			}
  		}
  
  		public CollapseAllFoldsAction(String name, Icon icon,
  				String desc, Integer mnemonic, KeyStroke accelerator) {
  			super(name, icon, desc, mnemonic, accelerator);
  		}
  
  		@Override
  		public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
  			RSyntaxTextArea rsta = (RSyntaxTextArea)textArea;
  			if (rsta.isCodeFoldingEnabled()) {
  				FoldCollapser collapser = new FoldCollapser() {
  					@Override
  					public boolean getShouldCollapse(Fold fold) {
  						return true;
  					}
  				};
  				collapser.collapseFolds(rsta.getFoldManager());
  				possiblyRepaintGutter(textArea);
  			}
  			else {
  				UIManager.getLookAndFeel().provideErrorFeedback(rsta);
  			}
  		}
  
  		@Override
  		public final String getMacroID() {
  			return rstaCollapseAllFoldsAction;
  		}
  
  	}
  
  
  	/**
  	 * Action for copying text as RTF.
  	 */
  	public static class CopyAsRtfAction extends RecordableTextAction {
  
  		private static final long serialVersionUID = 1L;
  
  		public CopyAsRtfAction() {
  			super(rstaCopyAsRtfAction);
  		}
  
  		public CopyAsRtfAction(String name, Icon icon, String desc,
  					Integer mnemonic, KeyStroke accelerator) {
  			super(name, icon, desc, mnemonic, accelerator);
  		}
  
  		@Override
  		public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
  			((RSyntaxTextArea)textArea).copyAsRtf();
  			textArea.requestFocusInWindow();
  		}
  
  		@Override
  		public final String getMacroID() {
  			return getName();
  		}
  
  	}
  
  
  	/**
  	 * Action for decreasing the font size of all fonts in the text area.
  	 */
  	public static class DecreaseFontSizeAction
  					extends RTextAreaEditorKit.DecreaseFontSizeAction {
  
  		private static final long serialVersionUID = 1L;
  
  		public DecreaseFontSizeAction() {
  			super();
  		}
  
  		public DecreaseFontSizeAction(String name, Icon icon, String desc,
  							Integer mnemonic, KeyStroke accelerator) {
  			super(name, icon, desc, mnemonic, accelerator);
  		}
  
  		@Override
  		public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
  
  			RSyntaxTextArea rsta = (RSyntaxTextArea)textArea;
  			SyntaxScheme scheme = rsta.getSyntaxScheme();
  
  			// All we need to do is update all of the fonts in syntax
  			// schemes, then call setSyntaxHighlightingColorScheme with the
  			// same scheme already being used.  This relies on the fact that
  			// that method does not check whether the new scheme is different
  			// from the old scheme before updating.
  
  			boolean changed = false;
  			int count = scheme.getStyleCount();
  			for (int i=0; i<count; i++) {
  				Style ss = scheme.getStyle(i);
  				if (ss!=null) {
  					Font font = ss.font;
  					if (font!=null) {
  						float oldSize = font.getSize2D();
  						float newSize = oldSize - decreaseAmount;
  						if (newSize>=MINIMUM_SIZE) {
  							// Shrink by decreaseAmount.
  							ss.font = font.deriveFont(newSize);
  							changed = true;
  						}
  						else if (oldSize>MINIMUM_SIZE) {
  							// Can't shrink by full decreaseAmount, but
  							// can shrink a little bit.
  							ss.font = font.deriveFont(MINIMUM_SIZE);
  							changed = true;
  						}
  					}
  				}
  			}
  
  			// Do the text area's font also.
  			Font font = rsta.getFont();
  			float oldSize = font.getSize2D();
  			float newSize = oldSize - decreaseAmount;
  			if (newSize>=MINIMUM_SIZE) {
  				// Shrink by decreaseAmount.
  				rsta.setFont(font.deriveFont(newSize));
  				changed = true;
  			}
  			else if (oldSize>MINIMUM_SIZE) {
  				// Can't shrink by full decreaseAmount, but
  				// can shrink a little bit.
  				rsta.setFont(font.deriveFont(MINIMUM_SIZE));
  				changed = true;
  			}
  
  			// If we updated at least one font, update the screen.  If
  			// all of the fonts were already the minimum size, beep.
  			if (changed) {
  				rsta.setSyntaxScheme(scheme);
  				// NOTE:  This is a hack to get an encompassing
  				// RTextScrollPane to repaint its line numbers to account
  				// for a change in line height due to a font change.  I'm
  				// not sure why we need to do this here but not when we
  				// change the syntax highlighting color scheme via the
  				// Options dialog... setSyntaxHighlightingColorScheme()
  				// calls revalidate() which won't repaint the scroll pane
  				// if scrollbars don't change, which is why we need this.
  				Component parent = rsta.getParent();
  				if (parent instanceof javax.swing.JViewport) {
  					parent = parent.getParent();
  					if (parent instanceof JScrollPane) {
  						parent.repaint();
  					}
  				}
  			}
  			else
  				UIManager.getLookAndFeel().provideErrorFeedback(rsta);
  
  		}
  
  	}
  
  
  	/**
  	 * Action for when un-indenting lines (either the current line if there is
  	 * selection, or all selected lines if there is one).
  	 */
  	public static class DecreaseIndentAction extends RecordableTextAction {
  
  		private static final long serialVersionUID = 1L;
  	
  		private Segment s;
  
  		public DecreaseIndentAction() {
  			this(rstaDecreaseIndentAction);
  		}
  
  		public DecreaseIndentAction(String name) {
  			super(name);
  			s = new Segment();
  		}
  
  		@Override
  		public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
  
  			if (!textArea.isEditable() || !textArea.isEnabled()) {
  				UIManager.getLookAndFeel().provideErrorFeedback(textArea);
  				return;
  			}
  
  			Document document = textArea.getDocument();
  			Element map = document.getDefaultRootElement();
  			Caret c = textArea.getCaret();
  			int dot = c.getDot();
  			int mark = c.getMark();
  			int line1 = map.getElementIndex(dot);
  			int tabSize = textArea.getTabSize();
  
  			// If there is a selection, indent all lines in the selection.
  			// Otherwise, indent the line the caret is on.
  			if (dot!=mark) {
  				// Note that we cheaply reuse variables here, so don't
  				// take their names to mean what they are.
  				int line2 = map.getElementIndex(mark);
  				dot = Math.min(line1, line2);
  				mark = Math.max(line1, line2);
  				Element elem;
  				try {
  					for (line1=dot; line1<mark; line1++) {
  						elem = map.getElement(line1);
  						handleDecreaseIndent(elem, document, tabSize);
  					}
  					// Don't do the last line if the caret is at its
  					// beginning.  We must call getDot() again and not just
  					// use 'dot' as the caret's position may have changed
  					// due to the insertion of the tabs above.
  					elem = map.getElement(mark);
  					int start = elem.getStartOffset();
  					if (Math.max(c.getDot(),c.getMark())!=start) {
  						handleDecreaseIndent(elem, document, tabSize);
  					}
  				} catch (BadLocationException ble) {
  					ble.printStackTrace();
  					UIManager.getLookAndFeel().
  								provideErrorFeedback(textArea);
  				}
  			}
  			else {
  				Element elem = map.getElement(line1);
  				try {
  					handleDecreaseIndent(elem, document, tabSize);
  				} catch (BadLocationException ble) {
  					ble.printStackTrace();
  					UIManager.getLookAndFeel().
  								provideErrorFeedback(textArea);
  				}
  			}
  
  		}
  
  		@Override
  		public final String getMacroID() {
  			return rstaDecreaseIndentAction;
  		}
  
  		/**
  		 * Actually does the "de-indentation."  This method finds where the
  		 * given element's leading whitespace ends, then, if there is indeed
  		 * leading whitespace, removes either the last char in it (if it is a
  		 * tab), or removes up to the number of spaces equal to a tab in the
  		 * specified document (i.e., if the tab size was 5 and there were 3
  		 * spaces at the end of the leading whitespace, the three will be
  		 * removed; if there were 8 spaces, only the first 5 would be
  		 * removed).
  		 *
  		 * @param elem The element to "de-indent."
  		 * @param doc The document containing the specified element.
  		 * @param tabSize The size of a tab, in spaces.
  		 */
  		private final void handleDecreaseIndent(Element elem, Document doc,
  									int tabSize)
  									throws BadLocationException {
  			int start = elem.getStartOffset();
  			int end = elem.getEndOffset() - 1; // Why always true??
  			doc.getText(start,end-start, s);
  			int i = s.offset;
  			end = i+s.count;
  			if (end>i) {
  				// If the first character is a tab, remove it.
  				if (s.array[i]=='\t') {
  					doc.remove(start, 1);
  				}
  				// Otherwise, see if the first character is a space.  If it
  				// is, remove all contiguous whitespaces at the beginning of
  				// this line, up to the tab size.
  				else if (s.array[i]==' ') {
  					i++;
  					int toRemove = 1;
  					while (i<end && s.array[i]==' ' && toRemove<tabSize) {
  						i++;
  						toRemove++;
  					}
  					doc.remove(start, toRemove);
  				}
  			}
  		}
  
  	}
  
  
  	/**
  	 * Deletes the previous word, but differentiates symbols from "words" to
  	 * match the behavior of code editors.
  	 */
  	public static class DeletePrevWordAction
  			extends RTextAreaEditorKit.DeletePrevWordAction {
  
  		private Segment seg = new Segment();
  
  		@Override
  		protected int getPreviousWordStart(RTextArea textArea, int offs)
  				throws BadLocationException {
  
  			if (offs==0) {
  				return offs;
  			}
  
  			RSyntaxDocument doc = (RSyntaxDocument)textArea.getDocument();
  			int line = textArea.getLineOfOffset(offs);
  			int start = textArea.getLineStartOffset(line);
  			if (offs==start) {
  				return start-1; // Just delete the newline
  			}
  			int end = textArea.getLineEndOffset(line);
  			if (line!=textArea.getLineCount()-1) {
  				end--;
  			}
  			doc.getText(start, end-start, seg);
  
  			// Determine the "type" of char at offs - lower case, upper case,
  			// whitespace or other.  We take special care here as we're starting
  			// in the middle of the Segment to check whether we're already at
  			// the "beginning" of a word.
  			int firstIndex = seg.getBeginIndex() + (offs-start) - 1;
  			seg.setIndex(firstIndex);
  			char ch = seg.current();
  
  			// Always strip off whitespace first
  			if (Character.isWhitespace(ch)) {
  				do {
  					ch = seg.previous();
  				} while (Character.isWhitespace(ch));
  			}
  
  			// The "word" is a group of letters and/or digits
  			if (Character.isLetterOrDigit(ch)) {
  				do {
  					ch = seg.previous();
  				} while (Character.isLetterOrDigit(ch));
  			}
  
  			// The "word" is a series of symbols.
  			else {
  				while (!Character.isWhitespace(ch) &&
  						!Character.isLetterOrDigit(ch)
  						&& ch!=Segment.DONE) {
  					ch = seg.previous();
  				}
  			}
  
  			if (ch==Segment.DONE) {
  				return start; // Removed last "token" of the line
  			}
  			offs -= firstIndex - seg.getIndex();
  			return offs;
  
  		}
  
  	}
  
  
  	/**
  	 * Moves the caret to the end of the document, taking into account code
  	 * folding.
  	 */
  	public static class EndAction extends RTextAreaEditorKit.EndAction {
  
  		public EndAction(String name, boolean select) {
  			super(name, select);
  		}
  
  		@Override
  		protected int getVisibleEnd(RTextArea textArea) {
  			RSyntaxTextArea rsta = (RSyntaxTextArea)textArea;
  			return rsta.getLastVisibleOffset();
  		}
  
  	}
  
  
  	/**
  	 * Positions the caret at the end of the word.  This class is here to
  	 * better handle finding the "end of the word" in programming languages.
  	 */
  	protected static class EndWordAction
  							extends RTextAreaEditorKit.EndWordAction {
  
   		private Segment seg;
  
  		protected EndWordAction(String name, boolean select) {
  			super(name, select);
  			seg = new Segment();
  		}
  
  		@Override
  		protected int getWordEnd(RTextArea textArea, int offs)
  									throws BadLocationException {
  
  			RSyntaxDocument doc = (RSyntaxDocument)textArea.getDocument();
  			if (offs==doc.getLength()) {
  				return offs;
  			}
  
  			int line = textArea.getLineOfOffset(offs);
  			int end = textArea.getLineEndOffset(line);
  			if (line!=textArea.getLineCount()-1) {
  				end--; // Hide newline
  			}
  			if (offs==end) {
  				return end;
  			}
  			doc.getText(offs, end-offs, seg);
  
  			// Determine the "type" of char at offs - letter/digit,
  			// whitespace or other
  			char ch = seg.first();
  
  			// The "word" is a group of letters and/or digits
  			if (Character.isLetterOrDigit(ch)) {
  				do {
  					ch = seg.next();
  				} while (Character.isLetterOrDigit(ch));
  			}
  
  			// The "word" is whitespace.
  			else if (Character.isWhitespace(ch)) {
  
  				do {
  					ch = seg.next();
  				} while (Character.isWhitespace(ch));
  			}
  
  			// Otherwise, the "word" is a single character of some other type
  			// (operator, etc.).
  
  			offs += seg.getIndex() - seg.getBeginIndex();
  			return offs;
  
  		}
  
  	}
  
  
  	/**
  	 * Expands all folds.
  	 */
  	public static class ExpandAllFoldsAction extends FoldRelatedAction {
  
  		private static final long serialVersionUID = 1L;
  
  		public ExpandAllFoldsAction() {
  			this(false);
  		}
  
  		public ExpandAllFoldsAction(boolean localizedName) {
  			super(rstaExpandAllFoldsAction);
  			if (localizedName) {
  				setProperties(msg, "Action.ExpandAllFolds");
  			}
  		}
  
  		public ExpandAllFoldsAction(String name, Icon icon,
  				String desc, Integer mnemonic, KeyStroke accelerator) {
  			super(name, icon, desc, mnemonic, accelerator);
  		}
  
  		@Override
  		public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
  			RSyntaxTextArea rsta = (RSyntaxTextArea)textArea;
  			if (rsta.isCodeFoldingEnabled()) {
  				FoldManager fm = rsta.getFoldManager();
  				for (int i=0; i<fm.getFoldCount(); i++) {
  					expand(fm.getFold(i));
  				}
  				possiblyRepaintGutter(rsta);
  			}
  			else {
  				UIManager.getLookAndFeel().provideErrorFeedback(rsta);
  			}
  		}
  
  		private void expand(Fold fold) {
  			fold.setCollapsed(false);
  			for (int i=0; i<fold.getChildCount(); i++) {
  				expand(fold.getChild(i));
  			}
  		}
  
  		@Override
  		public final String getMacroID() {
  			return rstaExpandAllFoldsAction;
  		}
  
  	}
  
  
  	/**
  	 * Base class for folding-related actions.
  	 */
  	static abstract class FoldRelatedAction extends RecordableTextAction {
  
  		public FoldRelatedAction(String name) {
  			super(name);
  		}
  
  		public FoldRelatedAction(String name, Icon icon,
  				String desc, Integer mnemonic, KeyStroke accelerator) {
  			super(name, icon, desc, mnemonic, accelerator);
  		}
  
  		protected Fold getClosestFold(RSyntaxTextArea textArea) {
  			int offs = textArea.getCaretPosition();
  			int line = textArea.getCaretLineNumber();
  			FoldManager fm = textArea.getFoldManager();
  			Fold fold = fm.getFoldForLine(line);
  			if (fold==null) {
  				fold = fm.getDeepestOpenFoldContaining(offs);
  			}
  			return fold;
  		}
  
  		/**
  		 * Repaints the gutter in a text area's scroll pane, if necessary.
  		 *
  		 * @param textArea The text area.
  		 */
  		protected void possiblyRepaintGutter(RTextArea textArea) {
  			Gutter gutter = RSyntaxUtilities.getGutter(textArea);
  			if (gutter!=null) {
  				gutter.repaint();
  			}
  		}
  
  	}
  
  
  	/**
  	 * Action for moving the caret to the "matching bracket" of the bracket
  	 * at the caret position (either before or after).
  	 */
  	public static class GoToMatchingBracketAction
  									extends RecordableTextAction {
  
  		private static final long serialVersionUID = 1L;
  
  		private Point bracketInfo;
  
  		public GoToMatchingBracketAction() {
  			super(rstaGoToMatchingBracketAction);
  		}
  
  		public GoToMatchingBracketAction(String name, Icon icon, String desc,
  							Integer mnemonic, KeyStroke accelerator) {
  			super(name, icon, desc, mnemonic, accelerator);
  		}
  
  		@Override
  		public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
  			RSyntaxTextArea rsta = (RSyntaxTextArea)textArea;
  			bracketInfo = RSyntaxUtilities.getMatchingBracketPosition(rsta,
  					bracketInfo);
  			if (bracketInfo.y>-1) {
  				// Go to the position AFTER the bracket so the previous
  				// bracket (which we were just on) is highlighted.
  				rsta.setCaretPosition(bracketInfo.y+1);
  			}
  			else {
  				UIManager.getLookAndFeel().provideErrorFeedback(rsta);
  			}
  		}
  
  		@Override
  		public final String getMacroID() {
  			return rstaGoToMatchingBracketAction;
  		}
  
  	}
  
  
  	/**
  	 * Action for increasing the font size of all fonts in the text area.
  	 */
  	public static class IncreaseFontSizeAction
  					extends RTextAreaEditorKit.IncreaseFontSizeAction {
  
  		private static final long serialVersionUID = 1L;
  
  		public IncreaseFontSizeAction() {
  			super();
  		}
  
  		public IncreaseFontSizeAction(String name, Icon icon, String desc,
  							Integer mnemonic, KeyStroke accelerator) {
  			super(name, icon, desc, mnemonic, accelerator);
  		}
  
  		@Override
  		public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
  
  			RSyntaxTextArea rsta = (RSyntaxTextArea)textArea;
  			SyntaxScheme scheme = rsta.getSyntaxScheme();
  
  			// All we need to do is update all of the fonts in syntax
  			// schemes, then call setSyntaxHighlightingColorScheme with the
  			// same scheme already being used.  This relies on the fact that
  			// that method does not check whether the new scheme is different
  			// from the old scheme before updating.
  
  			boolean changed = false;
  			int count = scheme.getStyleCount();
  			for (int i=0; i<count; i++) {
  				Style ss = scheme.getStyle(i);
  				if (ss!=null) {
  					Font font = ss.font;
  					if (font!=null) {
  						float oldSize = font.getSize2D();
  						float newSize = oldSize + increaseAmount;
  						if (newSize<=MAXIMUM_SIZE) {
  							// Grow by increaseAmount.
  							ss.font = font.deriveFont(newSize);
  							changed = true;
  						}
  						else if (oldSize<MAXIMUM_SIZE) {
  							// Can't grow by full increaseAmount, but
  							// can grow a little bit.
  							ss.font = font.deriveFont(MAXIMUM_SIZE);
  							changed = true;
  						}
  					}
  				}
  			}
  
  			// Do the text area's font also.
  			Font font = rsta.getFont();
  			float oldSize = font.getSize2D();
  			float newSize = oldSize + increaseAmount;
  			if (newSize<=MAXIMUM_SIZE) {
  				// Grow by increaseAmount.
  				rsta.setFont(font.deriveFont(newSize));
  				changed = true;
  			}
  			else if (oldSize<MAXIMUM_SIZE) {
  				// Can't grow by full increaseAmount, but
  				// can grow a little bit.
  				rsta.setFont(font.deriveFont(MAXIMUM_SIZE));
  				changed = true;
  			}
  
  			// If we updated at least one font, update the screen.  If
  			// all of the fonts were already the minimum size, beep.
  			if (changed) {
  				rsta.setSyntaxScheme(scheme);
  				// NOTE:  This is a hack to get an encompassing
  				// RTextScrollPane to repaint its line numbers to account
  				// for a change in line height due to a font change.  I'm
  				// not sure why we need to do this here but not when we
  				// change the syntax highlighting color scheme via the
  				// Options dialog... setSyntaxHighlightingColorScheme()
  				// calls revalidate() which won't repaint the scroll pane
  				// if scrollbars don't change, which is why we need this.
  				Component parent = rsta.getParent();
  				if (parent instanceof javax.swing.JViewport) {
  					parent = parent.getParent();
  					if (parent instanceof JScrollPane) {
  						parent.repaint();
  					}
  				}
  			}
  			else
  				UIManager.getLookAndFeel().provideErrorFeedback(rsta);
  
  		}
  
  	}
  
  
  	/**
  	 * Action for when the user presses the Enter key.  This is here so we can
  	 * be smart and "auto-indent" for programming languages.
  	 */
  	public static class InsertBreakAction
  							extends RTextAreaEditorKit.InsertBreakAction {
  
  		private static final long serialVersionUID = 1L;
  
  		@Override
  		public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
  
  			if (!textArea.isEditable() || !textArea.isEnabled()) {
  				UIManager.getLookAndFeel().provideErrorFeedback(textArea);
  				return;
  			}
  
  			RSyntaxTextArea sta = (RSyntaxTextArea)textArea;
  			boolean noSelection= sta.getSelectionStart()==sta.getSelectionEnd();
  
  			// First, see if this language wants to handle inserting newlines
  			// itself.
  			boolean handled = false;
  			if (noSelection) {
  				RSyntaxDocument doc = (RSyntaxDocument)sta.getDocument();
  				handled = doc.insertBreakSpecialHandling(e);
  			}
  
  			// If not...
  			if (!handled) {
  				handleInsertBreak(sta, noSelection);
  			}
  
  		}
  
  		/**
  		 * @return The first location in the string past <code>pos</code> that
  		 *         is NOT a whitespace char, or <code>-1</code> if only
  		 *         whitespace chars follow <code>pos</code> (or it is the end
  		 *         position in the string).
  		 */
  		private static final int atEndOfLine(int pos, String s, int sLen) {
  			for (int i=pos; i<sLen; i++) {
  				if (!RSyntaxUtilities.isWhitespace(s.charAt(i)))
  					return i;
  			}
  			return -1;
  		}
  
  		private static final int getOpenBraceCount(RSyntaxDocument doc) {
  			int openCount = 0;
  			Element root = doc.getDefaultRootElement();
  			int lineCount = root.getElementCount();
  			for (int i=0; i<lineCount; i++) {
  				Token t = doc.getTokenListForLine(i);
  				while (t!=null && t.isPaintable()) {
  					if (t.getType()==Token.SEPARATOR && t.length()==1) {
  						char ch = t.charAt(0);
  						if (ch=='{') {
  							openCount++;
  						}
  						else if (ch=='}') {
  							openCount--;
  						}
  					}
  					t = t.getNextToken();
  				}
  			}
  			return openCount;
  		}
  
  		/**
  		 * Actually inserts the newline into the document, and auto-indents
  		 * if appropriate.  This method can be called by token makers who
  		 * implement a custom action for inserting newlines.
  		 *
  		 * @param textArea
  		 * @param noSelection Whether there is no selection.
  		 */
  		protected void handleInsertBreak(RSyntaxTextArea textArea,
  										boolean noSelection) {
  			// If we're auto-indenting...
  			if (noSelection && textArea.isAutoIndentEnabled()) {
  				insertNewlineWithAutoIndent(textArea);
  			}
  			else {
  				textArea.replaceSelection("\n");
  				if (noSelection) {
  					possiblyCloseCurlyBrace(textArea, null);
  				}
  			}
  		}
  
  		private void insertNewlineWithAutoIndent(RSyntaxTextArea sta) {
  
  			try {
  
  				int caretPos = sta.getCaretPosition();
  				Document doc = sta.getDocument();
  				Element map = doc.getDefaultRootElement();
  				int lineNum = map.getElementIndex(caretPos);
  