text:
/*
 * 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 org.fife.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 org.fife.ui.rsyntaxtextarea.folding.Fold;
import org.fife.ui.rsyntaxtextarea.folding.FoldCollapser;
import org.fife.ui.rsyntaxtextarea.folding.FoldManager;
import org.fife.ui.rsyntaxtextarea.templates.CodeTemplate;
import org.fife.ui.rtextarea.Gutter;
import org.fife.ui.rtextarea.IconRowHeader;
import org.fife.ui.rtextarea.RecordableTextAction;
import org.fife.ui.rtextarea.RTextArea;
import org.fife.ui.rtextarea.RTextAreaEditorKit;

/**
 * An extension of RTextAreaEditorKit that adds functionality for
 * programming-specific stuff.  There are currently subclasses to handle:
 *
 *

 *
Toggling code folds.

 *
Aligning "closing" curly braces with their matches, if the current
 *       programming language uses curly braces to identify code blocks.

 *
Copying the current selection as RTF.

 *
Block indentation (increasing the indent of one or multiple lines)

 *
Block un-indentation (decreasing the indent of one or multiple lines)
 *

 *
Inserting a "code template" when a configurable key (e.g. a space) is
 *       pressed

 *
Decreasing the point size of all fonts in the text area

 *
Increasing the point size of all fonts in the text area

 *
Moving the caret to the "matching bracket" of the one at the current
 *       caret position

 *
Toggling whether the currently selected lines are commented out.

 *
Better selection of "words" on mouse double-clicks for programming
 *       languages.

 *
Better keyboard navigation via Ctrl+arrow keys for programming
 *       languages.

 *

 *
 * @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	= "org.fife.ui.rsyntaxtextarea.RSyntaxTextArea";
	private static final ResourceBundle msg = ResourceBundle.getBundle(MSG);

	/**
	 * The actions that RSyntaxTextAreaEditorKit adds to those of
	 * RTextAreaEditorKit.
	 */
	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 RSyntaxTextAreas.
	 *
	 * @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();

			int languageIndex = 0;
			int dot = textArea.getCaretPosition();
			if (dot>0) {
				Token t = RSyntaxUtilities.getTokenAtOffset(rsta, dot-1);
				languageIndex = t==null ? 0 : t.getLanguageIndex();
			}
			boolean alignCurlyBraces = rsta.isAutoIndentEnabled() &&
							doc.getCurlyBracesDenoteCodeBlocks(languageIndex);

			if (alignCurlyBraces) {
				textArea.beginAtomicEdit();
			}

			try {

				textArea.replaceSelection("}");

				// If the user wants to align curly braces...
				if (alignCurlyBraces) {

					Element root = doc.getDefaultRootElement();
					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. "</"
		 *        or "[" in the case of BBCode).
		 * @return The name of the tag to close, or null 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;

		}

	}

	/**
	 * 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 {

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

		}

		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 pos that
		 *         is NOT a whitespace char, or -1 if only
		 *         whitespace chars follow pos (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 languageIndex) {
			int openCount = 0;
			for (Token t : doc) {
				if (t.getType()==Token.SEPARATOR && t.length()==1 &&
						t.getLanguageIndex()==languageIndex) {
					char ch = t.charAt(0);
					if (ch=='{') {
						openCount++;
					}
					else if (ch=='}') {
						openCount--;
					}
				}
			}
			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);
				Element line = map.getElement(lineNum);
				int start = line.getStartOffset();
				int end = line.getEndOffset()-1; // Why always "-1"?
				int len = end-start;
				String s = doc.getText(start, len);

				// endWS is the end of the leading whitespace of the
				// current line.
				String leadingWS = RSyntaxUtilities.getLeadingWhitespace(s);
				StringBuilder sb = new StringBuilder("\n");
				sb.append(leadingWS);

				// If there is only whitespace between the caret and
				// the EOL, pressing Enter auto-indents the new line to
				// the same place as the previous line.
				int nonWhitespacePos = atEndOfLine(caretPos-start, s, len);
				if (nonWhitespacePos==-1) {
					if (leadingWS.length()==len &&
							sta.isClearWhitespaceLinesEnabled()) {
						// If the line was nothing but whitespace, select it
						// so its contents get removed.
						sta.setSelectionStart(start);
						sta.setSelectionEnd(end);
					}
					sta.replaceSelection(sb.toString());
				}

				// If there is non-whitespace between the caret and the
				// EOL, pressing Enter takes that text to the next line
				// and auto-indents it to the same place as the last
				// line.
				else {
					sb.append(s.substring(nonWhitespacePos));
					sta.replaceRange(sb.toString(), caretPos, end);
					sta.setCaretPosition(caretPos + leadingWS.length()+1);
				}

				// Must do it after everything else, as the "smart indent"
				// calculation depends on the previous line's state
				// AFTER the Enter press (stuff may have been moved down).
				if (sta.getShouldIndentNextLine(lineNum)) {
					sta.replaceSelection("\t");
				}

				possiblyCloseCurlyBrace(sta, leadingWS);

			} catch (BadLocationException ble) { // Never happens
				sta.replaceSelection("\n");
				ble.printStackTrace();
			}

		}

		private void possiblyCloseCurlyBrace(RSyntaxTextArea textArea,
											String leadingWS) {

			RSyntaxDocument doc = (RSyntaxDocument)textArea.getDocument();

			if (textArea.getCloseCurlyBraces()) {

				int line = textArea.getCaretLineNumber();
				Token t = doc.getTokenListForLine(line-1);
				t = t.getLastNonCommentNonWhitespaceToken();

				if (t!=null && t.isLeftCurly()) {

					int languageIndex = t.getLanguageIndex();
					if (doc.getCurlyBracesDenoteCodeBlocks(languageIndex) &&
							getOpenBraceCount(doc, languageIndex)>0) {
						StringBuilder sb = new StringBuilder();
						if (line==textArea.getLineCount()-1) {
							sb.append('\n');
						}
						if (leadingWS!=null) {
							sb.append(leadingWS);
						}
						sb.append("}\n");
						int dot = textArea.getCaretPosition();
						int end = textArea.getLineEndOffsetOfCurrentLine();
						// Insert at end of line, not at dot: they may have
						// pressed Enter in the middle of the line and brought
						// some text (though it must be whitespace and/or
						// comments) down onto the new line.
						textArea.insert(sb.toString(), end);
						textArea.setCaretPosition(dot); // Caret may have moved
					}

				}

			}

		}

	}

	/**
	 * Action for inserting tabs.  This is extended to "block indent" a
	 * group of contiguous lines if they are selected.
	 */
	public static class InsertTabAction extends RecordableTextAction {

		private static final long serialVersionUID = 1L;

		public InsertTabAction() {
			super(insertTabAction);
		}

		public InsertTabAction(String name) {
			super(name);
		}

		@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 dotLine = map.getElementIndex(dot);
			int markLine = map.getElementIndex(mark);

			// If there is a multi-line selection, indent all lines in
			// the selection.
			if (dotLine!=markLine) {
				int first = Math.min(dotLine, markLine);
				int last = Math.max(dotLine, markLine);
				Element elem; int start;

				// Since we're using Document.insertString(), we must mimic the
				// soft tab behavior provided by RTextArea.replaceSelection().
				String replacement = "\t";
				if (textArea.getTabsEmulated()) {
					StringBuilder sb = new StringBuilder();
					int temp = textArea.getTabSize();
					for (int i=0; i<temp; i++) {
						sb.append(' ');
					}
					replacement = sb.toString();
				}

				textArea.beginAtomicEdit();
				try {
					for (int i=first; i<last; i++) {
						elem = map.getElement(i);
						start = elem.getStartOffset();
						document.insertString(start, replacement, null);
					}
					// 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(last);
					start = elem.getStartOffset();
					if (Math.max(c.getDot(), c.getMark())!=start) {
						document.insertString(start, replacement, null);
					}
				} catch (BadLocationException ble) { // Never happens.
					ble.printStackTrace();
					UIManager.getLookAndFeel().
									provideErrorFeedback(textArea);
				} finally {
					textArea.endAtomicEdit();
				}
			}
			else {
				textArea.replaceSelection("\t");
			}

		}

		@Override
		public final String getMacroID() {
			return insertTabAction;
		}

	}

	/**
	 * Action to move the selection and/or caret. Constructor indicates
	 * direction to use.  This class overrides the behavior defined in
	 * {@link RTextAreaEditorKit} to better skip "words" in source code.
	 */
	public static class NextWordAction
						extends RTextAreaEditorKit.NextWordAction {

		private Segment seg;

		public NextWordAction(String nm, boolean select) {
			super(nm, select);
			seg = new Segment();
		}

		/**
		 * Overridden to do better with skipping "words" in code.
		 */
		@Override
		protected int getNextWord(RTextArea textArea, int offs)
									throws BadLocationException {

			RSyntaxDocument doc = (RSyntaxDocument)textArea.getDocument();
			if (offs==doc.getLength()) {
				return offs;
			}

			Element root = doc.getDefaultRootElement();
			int line = root.getElementIndex(offs);
			int end = root.getElement(line).getEndOffset() - 1;
			if (offs==end) {// If we're already at the end of the line...
				RSyntaxTextArea rsta = (RSyntaxTextArea)textArea;
				if (rsta.isCodeFoldingEnabled()) { // Start of next visible line
					FoldManager fm = rsta.getFoldManager();
					int lineCount = root.getElementCount();
					while (++line<lineCount && fm.isLineHidden(line));
					if (line<lineCount) { // Found a lower visible line
						offs = root.getElement(line).getStartOffset();
					}
					// No lower visible line - we're already at last visible offset
					return offs;
				}
				else {
					return offs+1; // Start of next line.
				}
			}
			doc.getText(offs, end-offs, seg);

			// Determine the "type" of char at offs - letter/digit,
			// whitespace or other
			char ch = seg.first();

			// Skip the group of letters and/or digits
			if (Character.isLetterOrDigit(ch)) {
				do {
					ch = seg.next();
				} while (Character.isLetterOrDigit(ch));
			}

			// Skip groups of "anything else" (operators, etc.).
			else if (!Character.isWhitespace(ch)) {
				do {
					ch = seg.next();
				} while (ch!=Segment.DONE &&
						!(Character.isLetterOrDigit(ch) ||
								Character.isWhitespace(ch)));
			}

			// Skip any trailing whitespace
			while (Character.isWhitespace(ch)) {
				ch = seg.next();
			}

			offs += seg.getIndex() - seg.getBeginIndex();
			return offs;

		}

	}

	/**
	 * Action for when the user tries to insert a template (that is,
	 * they've typed a template ID and pressed the trigger character
	 * (a space) in an attempt to do the substitution).
	 */
	public static class PossiblyInsertTemplateAction extends RecordableTextAction {

		private static final long serialVersionUID = 1L;

		public PossiblyInsertTemplateAction() {
			super(rstaPossiblyInsertTemplateAction);
		}

		@Override
		public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {

			if (!textArea.isEditable() || !textArea.isEnabled())
				return;

			RSyntaxTextArea rsta = (RSyntaxTextArea)textArea;

			if (RSyntaxTextArea.getTemplatesEnabled()) {

				Document doc = textArea.getDocument();
				if (doc != null) {

					try {

						CodeTemplateManager manager = RSyntaxTextArea.
											getCodeTemplateManager();
						CodeTemplate template =  manager==null ? null :
													manager.getTemplate(rsta);

						// A non-null template means modify the text to insert!
						if (template!=null) {
							template.invoke(rsta);
						}

						// No template - insert default text.  This is
						// exactly what DefaultKeyTypedAction does.
						else {
							doDefaultInsert(rsta);
						}

					} catch (BadLocationException ble) {
						UIManager.getLookAndFeel().
								provideErrorFeedback(textArea);
					}

				} // End of if (doc!=null).

			} // End of if (textArea.getTemplatesEnabled()).

			// If templates aren't enabled, just insert the text as usual.
			else {
				doDefaultInsert(rsta);
			}

		}

		private final void doDefaultInsert(RTextArea textArea) {
			// FIXME:  We need a way to get the "trigger string" (i.e.,
			// the text that was just typed); however, the text area's
			// template manager might be null (if templates are disabled).
			// Also, the manager's trigger string doesn't yet match up with
			// that defined in RSyntaxTextAreaEditorKit.java (which is
			// hardcoded as a space)...
			//String str = manager.getInsertTriggerString();
			//int mod = manager.getInsertTrigger().getModifiers();
			//if (str!=null && str.length()>0 &&
			//	((mod&ActionEvent.ALT_MASK)==(mod&ActionEvent.CTRL_MASK))) {
			//	char ch = str.charAt(0);
			//	if (ch>=0x20 && ch!=0x7F)
			//		textArea.replaceSelection(str);
			//}
			textArea.replaceSelection(" ");
		}

		@Override
		public final String getMacroID() {
			return rstaPossiblyInsertTemplateAction;
		}

	}

	/**
	 * Action to move the selection and/or caret. Constructor indicates
	 * direction to use.  This class overrides the behavior defined in
	 * {@link RTextAreaEditorKit} to better skip "words" in source code.
	 */
	public static class PreviousWordAction
						extends RTextAreaEditorKit.PreviousWordAction {

		private Segment seg;

		public PreviousWordAction(String nm, boolean select) {
			super(nm, select);
			seg = new Segment();
		}

		/**
		 * Overridden to do better with skipping "words" in code.
		 */
		@Override
		protected int getPreviousWord(RTextArea textArea, int offs)
										throws BadLocationException {

			if (offs==0) {
				return offs;
			}

			RSyntaxDocument doc = (RSyntaxDocument)textArea.getDocument();
			Element root = doc.getDefaultRootElement();
			int line = root.getElementIndex(offs);
			int start = root.getElement(line).getStartOffset();
			if (offs==start) {// If we're already at the start of the line...
				RSyntaxTextArea rsta = (RSyntaxTextArea)textArea;
				if (rsta.isCodeFoldingEnabled()) { // End of next visible line
					FoldManager fm = rsta.getFoldManager();
					while (--line>=0 && fm.isLineHidden(line));
					if (line>=0) { // Found an earlier visible line
						offs = root.getElement(line).getEndOffset() - 1;
					}
					// No earlier visible line - we must be at offs==0...
					return offs;
				}
				else {
					return start-1; // End of previous line.
				}
			}
			doc.getText(start, offs-start, seg);

			// Determine the "type" of char at offs - lower case, upper case,
			// whitespace or other
			char ch = seg.last();

			// Skip any "leading" whitespace
			while (Character.isWhitespace(ch)) {
				ch = seg.previous();
			}

			// Skip the group of letters and/or digits
			if (Character.isLetterOrDigit(ch)) {
				do {
					ch = seg.previous();
				} while (Character.isLetterOrDigit(ch));
			}

			// Skip groups of "anything else" (operators, etc.).
			else if (!Character.isWhitespace(ch)) {
				do {
					ch = seg.previous();
				} while (ch!=Segment.DONE &&
						!(Character.isLetterOrDigit(ch) ||
								Character.isWhitespace(ch)));
			}

			offs -= seg.getEndIndex() - seg.getIndex();
			if (ch!=Segment.DONE) {
				offs++;
			}

			return offs;

		}

	}

	/**
	 * Selects the word around the caret.  This class is here to better
	 * handle selecting "words" in programming languages.
	 */
	public static class SelectWordAction
					extends RTextAreaEditorKit.SelectWordAction {

		@Override
		protected void createActions() {
			start = new BeginWordAction("pigdog", false);
			end = new EndWordAction("pigdog", true);
		}

	}

	/**
	 * Action that toggles whether the currently selected lines are
	 * commented.
	 */
	public static class ToggleCommentAction extends RecordableTextAction {

		public ToggleCommentAction() {
			super(rstaToggleCommentAction);
		}

		@Override
		public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {

			if (!textArea.isEditable() || !textArea.isEnabled()) {
				UIManager.getLookAndFeel().provideErrorFeedback(textArea);
				return;
			}

			RSyntaxDocument doc = (RSyntaxDocument)textArea.getDocument();
			String[] startEnd = doc.getLineCommentStartAndEnd();

			if (startEnd==null) {
				UIManager.getLookAndFeel().provideErrorFeedback(textArea);
				return;
			}

			Element map = doc.getDefaultRootElement();
			Caret c = textArea.getCaret();
			int dot = c.getDot();
			int mark = c.getMark();
			int line1 = map.getElementIndex(dot);
			int line2 = map.getElementIndex(mark);
			int start = Math.min(line1, line2);
			int end   = Math.max(line1, line2);

			// Don't toggle comment on last line if there is no
			// text selected on it.
			if (start!=end) {
				Element elem = map.getElement(end);
				if (Math.max(dot, mark)==elem.getStartOffset()) {
					end--;
				}
			}

			textArea.beginAtomicEdit();
			try {
				boolean add = getDoAdd(doc,map, start,end, startEnd);
				for (line1=start; line1<=end; line1++) {
					Element elem = map.getElement(line1);
					handleToggleComment(elem, doc, startEnd, add);
				}
			} catch (BadLocationException ble) {
				ble.printStackTrace();
				UIManager.getLookAndFeel().provideErrorFeedback(textArea);
			} finally {
				textArea.endAtomicEdit();
			}

		}

		private boolean getDoAdd(Document doc, Element map, int startLine,
							int endLine, String[] startEnd)
								throws BadLocationException {
			boolean doAdd = false;
			for (int i=startLine; i<=endLine; i++) {
				Element elem = map.getElement(i);
				int start = elem.getStartOffset();
				String t = doc.getText(start, elem.getEndOffset()-start-1);
				if (!t.startsWith(startEnd[0]) ||
						(startEnd[1]!=null && !t.endsWith(startEnd[1]))) {
					doAdd = true;
					break;
				}
			}
			return doAdd;
		}

		private void handleToggleComment(Element elem, Document doc,
			String[] startEnd, boolean add) throws BadLocationException {
			int start = elem.getStartOffset();
			int end = elem.getEndOffset() - 1;
			if (add) {
				doc.insertString(start, startEnd[0], null);
				if (startEnd[1]!=null) {
					doc.insertString(end+startEnd[0].length(), startEnd[1],
									null);
				}
			}
			else {
				doc.remove(start, startEnd[0].length());
				if (startEnd[1]!=null) {
					int temp = startEnd[1].length();
					doc.remove(end-startEnd[0].length()-temp, temp);
				}
			}
		}

		@Override
		public final String getMacroID() {
			return rstaToggleCommentAction;
		}

	}

	/**
	 * Toggles the fold at the current caret position or line.
	 */
	public static class ToggleCurrentFoldAction extends FoldRelatedAction {

		private static final long serialVersionUID = 1L;

		public ToggleCurrentFoldAction() {
			super(rstaToggleCurrentFoldAction);
			setProperties(msg, "Action.ToggleCurrentFold");
		}

		public ToggleCurrentFoldAction(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.toggleCollapsedState();
				}
				possiblyRepaintGutter(textArea);
			}
			else {
				UIManager.getLookAndFeel().provideErrorFeedback(rsta);
			}
		}

		@Override
		public final String getMacroID() {
			return rstaToggleCurrentFoldAction;
		}

	}

}


text:
var handlebars = require('handlebars');
var requireDir = require('require-dir');

requireDir('./handlebars');

module.exports = handlebars;

redpajama_set_name: RedPajamaGithub


text:
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const path_1 = require("path");
const fs_1 = require("fs");
// This import lacks of type definitions.
const resolveBinSync = require('resolve-bin').sync;
/** Finds the path to the TSLint CLI binary. */
function findTslintBinaryPath() {
    const defaultPath = path_1.resolve(__dirname, '..', 'node_modules', 'tslint', 'bin', 'tslint');
    if (fs_1.existsSync(defaultPath)) {
        return defaultPath;
    }
    else {
        return resolveBinSync('tslint', 'tslint');
    }
}
exports.findTslintBinaryPath = findTslintBinaryPath;
//# sourceMappingURL=find-tslint-binary.js.map


text:
import sys

args = sys.argv[1:]

if args == ['-s', 'show server']:
    print('Status Ok')
elif args == ['-s', 'show dimm']:
    print('Status Ok')
elif args == ['ctrl', 'all', 'show', 'config']:
    print('logicaldrive OK')
else:
    sys.exit('fake_hp_monitoring.py has received the following '
             'unexpected arguments - "%s".' % str(args))


text:
ACCEPTED

#### According to
Index Fungorum

#### Published in
null

#### Original name
Lecidea aucklandica var. planata Zahlbr.

### Remarks
null


text:
ACCEPTED

#### According to
The Catalogue of Life, 3rd January 2011

#### Published in
Scott. crypt. fl. (Edinburgh) 5: tab. 253 (1827)

#### Original name
Ceuthospora phacidioides Grev., 1827

### Remarks
null


text:
void import_2_D_int()
{
	NumpyEigenConverter<Eigen::Matrix< int, 2, Eigen::Dynamic > >::register_converter();
}


text:
package io.swagger.api;

import io.swagger.jaxrs.config.SwaggerContextService;
import io.swagger.models.*;

import javax.servlet.http.HttpServlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;

public class Bootstrap extends HttpServlet {
  /**
	 *
	 */
	private static final long serialVersionUID = -3027164743593330062L;

@Override
  public void init(ServletConfig config) throws ServletException {
    Info info = new Info()
      .title("Swagger Server")
      .description("This is an api for \"RSS Feed Aggregator\".  [View Subject](https://intra.epitech.eu/module/2016/M-EAP-650/PAR-9-1/acti-235029/project/file/RSS-feed-aggregator.pdf) or [Messenger group](https://www.messenger.com/t/552069568251252)  A successfull login or signup generate a key usable to authenticate request  This key is owned by one account, a request providing an apiKey should get result for the user owning this key. ")
      .termsOfService("")
      .contact(new Contact()
        .email(""))
      .license(new License()
        .name("")
        .url("http://unlicense.org"));

    config.getServletContext();
    Swagger swagger = new Swagger().info(info);

    new SwaggerContextService().withServletConfig(config).updateSwagger(swagger);
  }
}


text:
require_relative '../spec_helper.rb'

describe Ginatra::Repository do
  context "initialize" do
    context "with valid information" do
      before(:each) do
        @repo= get_repo('repo_1')
        @repos_dir = GinatraDummy::REPOS_DIR
      end

      it "should have the correct id" do
        expect(@repo.id).to eq "repo_1"
      end

      it "should have the correct path" do
        expect(@repo.path).to eq "#{@repos_dir}/repo_1"
      end

      it "should have the correct name" do
        expect(@repo.name).to eq "First Repository"
      end

      it "should have the correct color" do
        expect(@repo.color).to eq "#ce0000"
      end
    end

    context "with invalid info:" do
      before(:each) do
        # Use repository 1 as default
        @repo = get_repo("repo_1")
        @params = {"id" => @repo.id,
                   "path" => @repo.path,
                   "name" => @repo.name}
      end

      context "missing repo name" do
        before { @params['name'] = nil }
        it "should return MissingName error" do
          expect { Ginatra::Repository.new @params }.to raise_error("repository's name is missing for #{@repo.id}. Check config.yml file, make sure your data is correct.")
        end
      end

      context "missing repo path" do
        before { @params['path'] = nil }
        it "should return MissingPath error" do
          expect { Ginatra::Repository.new @params }.to raise_error("repository's path is missing for #{@repo.id}. Check config.yml file, make sure your data is correct.")
        end
      end

      context "missing repo id" do
        before { @params['id'] = nil }
        it "should return MissingId error" do
          expect { Ginatra::Repository.new @params }.to raise_error("repository's id is missing. Check config.yml file, make sure your repository data is correct.")
        end
      end

      context "invalid path" do
        before { @params['path'] = "/ginatra/random/path" }
        it "should return InvalidPath error" do
          expect { Ginatra::Repository.new @params }.to raise_error("repository's path is invalid for #{@repo.id}. Check config.yml file, make sure your data is correct.")
        end
      end
    end
  end

  describe "commits data behaviours" do
    before(:each) do
      @repo = get_repo('repo_1')
    end

    it "should have the correct commits count" do
      # We are only checking for commit size at the moment
      # Need a better fool proof test for this?
      expect(@repo.commits.size).to eq 61
    end

    context "when no data file exists" do
      before { remove_data_file('repo_1') }

      it "should retrieve commits and create new data file" do
        expect(@repo.commits.size).to eq 61
        expect(File.exists?(repo_data_path(@repo.id))).to be true
      end
    end

    context "on refresh" do
      before do
        undo_commits(@repo.id, 5)
        remove_data_file(@repo.id)
      end

      describe "after" do
        before { @repo.refresh_data }
        it "should have the new commits count" do
          expect(@repo.commits.size).to eq 61
        end
      end
    end
  end

  describe "authors" do
    let(:repo) { get_repo('repo_1') }
    let(:authors_hash) { repo.authors }
    let(:authors) { authors_hash.map { |v| v['name'] } }
    let(:expected_authors) { ["Joe Hudson", "Nathan Dao", "bultas",
                              "David Ascher", "aaronhayes", "Steven Dickinson",
                              "Toni Cárdenas", "Neo Alienson", "jefffriesen",
                              "Peter Halliday", "Eric Tse"] }

    it "should return the correct authors" do
      expect(authors & expected_authors).to eq authors
    end

    let(:expected_author_data) { [{"name"=>"Joe Hudson", "commits"=>49, "additions"=>1167, "deletions"=>881},
                                  {"name"=>"Nathan Dao", "commits"=>1, "additions"=>5, "deletions"=>1},
                                  {"name"=>"bultas", "commits"=>1, "additions"=>1, "deletions"=>1},
                                  {"name"=>"David Ascher", "commits"=>1, "additions"=>1, "deletions"=>1},
                                  {"name"=>"aaronhayes", "commits"=>3, "additions"=>19, "deletions"=>3},
                                  {"name"=>"Steven Dickinson", "commits"=>1, "additions"=>295, "deletions"=>1},
                                  {"name"=>"Toni Cárdenas", "commits"=>1, "additions"=>6, "deletions"=>5},
                                  {"name"=>"Neo Alienson", "commits"=>1, "additions"=>1, "deletions"=>1},
                                  {"name"=>"jefffriesen", "commits"=>1, "additions"=>35, "deletions"=>3},
                                  {"name"=>"Peter Halliday", "commits"=>1, "additions"=>27, "deletions"=>27},
                                  {"name"=>"Eric Tse", "commits"=>1, "additions"=>4, "deletions"=>4}] }

    it "should return the correct author data" do
      authors_hash.each do |author|
        expected_author = expected_author_data.select { |v|
          v['name'] == author['name']
        }.first

        author.each do |key, val|
          expect(val).to eq expected_author[key]
        end
      end
    end
  end
end


text:
<!DOCTYPE html>

        <title>Rocky S | About Me</title>
        <link href="css/normalize.css" rel="stylesheet" type="text/css" media="screen" title="normalize" charset="utf-8">
        <link href='https://fonts.googleapis.com/css?family=Open+Sans:400,700' rel='stylesheet' type='text/css'>
        <link href="css/style.css" rel="stylesheet" type="text/css" media="screen" title="normalize" charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">

Home

About Me

Contact

Hi, I'm Rocky!

I am

About

I have a background in web development, support engineering, and sales. My purpose is to transcend this role and start a career as a full stack developer.

When I am not working or studying you can find me at the gym lifting weights or preparing for the next long distance run.
                I enjoy a good cup of coffee, ping pong, camping, and a proper steak.

Let's talk

I am currently taking on new opportunities and can be reached by email at rokas.simkonis@gmail.com.

© 2015 Rocky Simkonis


text:
module App.Services {
    export class DashboardService {
        constructor(private $http: ng.IHttpService,
            private $q: ng.IQService) {
        }

        public getData(): ng.IPromise<DashboardResponse>  {
            var defer = this.$q.defer<DashboardResponse>();

            this.$http.get('api/dashboard')
                .success(defer.resolve)
                .error(defer.reject);

            return defer.promise;
        }
    }
}


text:
package com.hubspot.singularity.helpers;

import java.util.Date;
import java.util.concurrent.TimeUnit;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

public class RFC5545ScheduleTest {

  @Test
  public void testEveryWeekdayRFC5545Schdule() throws Exception {
    String schedule = "FREQ=DAILY;BYDAY=MO,TU,WE,TH,FR";
    Date nextValidTime = new RFC5545Schedule(schedule).getNextValidTime();
    Assertions.assertTrue(nextValidTime.after(new Date()));
    Assertions.assertTrue(
      nextValidTime.before(
        new Date(System.currentTimeMillis() + TimeUnit.DAYS.toMillis(3))
      )
    );
  }

  @Test
  public void testMaxIterations() throws Exception {
    String schedule = "DTSTART=19970902T090000\nRRULE:FREQ=HOURLY;COUNT=10001";
    Assertions.assertEquals(
      new RFC5545Schedule(schedule).getStartDateTime(),
      new org.joda.time.DateTime(1997, 9, 2, 9, 0, 0)
    );
    Assertions.assertEquals(new RFC5545Schedule(schedule).getNextValidTime(), null);
  }

  @Test
  public void testRecurInPastDoesNotGiveNext() throws Exception {
    String schedule =
      "FREQ=YEARLY;INTERVAL=4;BYMONTH=11;BYDAY=TU;BYMONTHDAY=2,3,4,5,6,7,8;COUNT=1";
    Assertions.assertEquals(new RFC5545Schedule(schedule).getNextValidTime(), null);
  }
}


text:
using System.Collections.Generic;
using System.Collections.Immutable;
using Abp.Collections.Extensions;
using Abp.Extensions;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Razor;

namespace poolprobase.Web.Resources
{
    public class WebResourceManager : IWebResourceManager
    {
        private readonly IHostingEnvironment _environment;
        private readonly List<string> _scriptUrls;

        public WebResourceManager(IHostingEnvironment environment)
        {
            _environment = environment;
            _scriptUrls = new List<string>();
        }

        public void AddScript(string url, bool addMinifiedOnProd = true)
        {
            _scriptUrls.AddIfNotContains(NormalizeUrl(url, "js"));
        }

        public IReadOnlyList<string> GetScripts()
        {
            return _scriptUrls.ToImmutableList();
        }

        public HelperResult RenderScripts()
        {
            return new HelperResult(async writer =>
            {
                foreach (var scriptUrl in _scriptUrls)
                {
                    await writer.WriteAsync($"<script src=\"{scriptUrl}\" asp-append-version=\"true\"></script>");
                }
            });
        }

        private string NormalizeUrl(string url, string ext)
        {
            if (_environment.IsDevelopment())
            {
                return url;
            }

            if (url.EndsWith(".min." + ext))
            {
                return url;
            }

            return url.Left(url.Length - ext.Length) + "min." + ext;
        }
    }
}