org.fife.ui.rsyntaxtextarea.RSyntaxTextAreaEditorKit Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of rsyntaxtextarea Show documentation
Show all versions of rsyntaxtextarea Show documentation
RSyntaxTextArea is the syntax highlighting text editor for Swing applications. Features include syntax highlighting for 40+ languages, code folding, code completion, regex find and replace, macros, code templates, undo/redo, line numbering and bracket matching.
/* * 08/29/2004 * * RSyntaxTextAreaEditorKit.java - The editor kit used by RSyntaxTextArea. * * This library is distributed under a modified BSD license. See the included * LICENSE file for details. */ package org.fife.ui.rsyntaxtextarea; import java.awt.Component; import java.awt.Font; import java.awt.Point; import java.awt.event.ActionEvent; import java.text.CharacterIterator; import java.util.ResourceBundle; import java.util.Stack; import javax.swing.Action; import javax.swing.Icon; import javax.swing.JScrollPane; import javax.swing.KeyStroke; import javax.swing.UIManager; import javax.swing.text.BadLocationException; import javax.swing.text.Caret; import javax.swing.text.Document; import javax.swing.text.Element; import javax.swing.text.Segment; import javax.swing.text.TextAction; 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.IconRowHeader; import org.fife.ui.rtextarea.RTextArea; import org.fife.ui.rtextarea.RTextAreaEditorKit; import org.fife.ui.rtextarea.RecordableTextAction; /** * An extension of
that * is NOT a whitespace char, orRTextAreaEditorKit
that adds functionality for * programming-specific stuff. There are currently subclasses to handle: * **
* * @author Robert Futrell * @version 0.5 */ @SuppressWarnings({ "checkstyle:constantname" }) 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 rstaCopyAsStyledTextAction = "RSTA.CopyAsStyledTextAction"; 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- 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.
*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 CopyAsStyledTextAction(), //new DecreaseFontSizeAction(), new DecreaseIndentAction(), new DeletePrevWordAction(), new DumbCompleteWordAction(), 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 byRSyntaxTextArea
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 int languageIndex = 0; // TODO if (doc.isIdentifierChar(languageIndex, ch)) { if (offs!=end && !doc.isIdentifierChar(languageIndex, nextCh)) { return offs; } do { ch = seg.previous(); } while (doc.isIdentifierChar(languageIndex, ch) && ch != CharacterIterator.DONE); } // 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); } RSyntaxUtilities.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-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, ornull
if it * could not be determined. */ private String discoverTagName(RSyntaxDocument doc, int dot) { Stackstack = new Stack<>(); 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==null ? null : 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()); RSyntaxUtilities.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()); RSyntaxUtilities.possiblyRepaintGutter(textArea); } else { UIManager.getLookAndFeel().provideErrorFeedback(rsta); } } @Override public final String getMacroID() { return rstaCollapseAllFoldsAction; } } /** * Action for copying text as styled text. */ public static class CopyAsStyledTextAction extends RecordableTextAction { private Theme theme; private static final long serialVersionUID = 1L; public CopyAsStyledTextAction() { super(rstaCopyAsStyledTextAction); } public CopyAsStyledTextAction(String themeName, Theme theme) { super(rstaCopyAsStyledTextAction + "_" + themeName); this.theme = theme; } public CopyAsStyledTextAction(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).copyAsStyledText(theme); 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 =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; textArea.beginAtomicEdit(); try { for (line1=dot; line1i) { // 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 start) { char ch = doc.charAt(offs); if (isIdentifierChar(ch)) { offs--; } } else { // offs == start => previous word is on previous line if (line == 0) { return -1; } elem = root.getElement(--line); offs = elem.getEndOffset() - 1; } int prevWordStart = getPreviousWordStartInLine(doc, elem, offs); while (prevWordStart == -1 && line > 0) { line--; elem = root.getElement(line); prevWordStart = getPreviousWordStartInLine(doc, elem, elem.getEndOffset()); } return prevWordStart; } private int getPreviousWordStartInLine(RSyntaxDocument doc, Element elem, int offs) throws BadLocationException { int start = elem.getStartOffset(); int cur = offs; // Skip any whitespace or non-word chars while (cur >= start) { char ch = doc.charAt(cur); if (isIdentifierChar(ch)) { break; } cur--; } if (cur < start) { // Empty line or nothing but whitespace/non-word chars return -1; } return getWordStartImpl(doc, elem, cur); } @Override protected int getWordEnd(RTextArea textArea, int offs) throws BadLocationException { RSyntaxDocument doc = (RSyntaxDocument)textArea.getDocument(); Element root = doc.getDefaultRootElement(); int line = root.getElementIndex(offs); Element elem = root.getElement(line); int end = elem.getEndOffset() - 1; int wordEnd = offs; while (wordEnd <= end) { if (!isIdentifierChar(doc.charAt(wordEnd))) { break; } wordEnd++; } return wordEnd; } @Override protected int getWordStart(RTextArea textArea, int offs) throws BadLocationException { RSyntaxDocument doc = (RSyntaxDocument)textArea.getDocument(); Element root = doc.getDefaultRootElement(); int line = root.getElementIndex(offs); Element elem = root.getElement(line); return getWordStartImpl(doc, elem, offs); } private static int getWordStartImpl(RSyntaxDocument doc, Element elem, int offs) throws BadLocationException { int start = elem.getStartOffset(); int wordStart = offs; while (wordStart >= start) { char ch = doc.charAt(wordStart); // Ignore newlines so we work when caret is at end of line if (!isIdentifierChar(ch) && ch != '\n') { break; } wordStart--; } return wordStart==offs ? offs : wordStart + 1; } /** * Overridden to not suggest word completions if the text right before * the caret contains non-word characters, such as '/' or '%'. * * @param prefix The prefix characters before the caret. * @return Whether the prefix could be part of a "word" in the context * of the text area's current content. */ @Override protected boolean isAcceptablePrefix(String prefix) { return prefix.length() > 0 && isIdentifierChar(prefix.charAt(prefix.length()-1)); } /** * Returns whether the specified character should be considered part * of an identifier. * * @param ch The character. * @return Whether the character is part of an identifier. */ private static boolean isIdentifierChar(char ch) { //return doc.isIdentifierChar(languageIndex, ch); return Character.isLetterOrDigit(ch) || ch == '_' || ch == '$'; } } /** * 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 int languageIndex = 0; // TODO if (doc.isIdentifierChar(languageIndex, ch)) { do { ch = seg.next(); } while (doc.isIdentifierChar(languageIndex, ch) && ch != CharacterIterator.DONE); } // 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 -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 pos -1
if only * whitespace chars followpos
(or it is the end * position in the string). */ private static int atEndOfLine(int pos, String s, int sLen) { for (int i=pos; i0) { 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 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 int languageIndex = 0; if (doc.isIdentifierChar(languageIndex, ch)) { do { ch = seg.previous(); } while (doc.isIdentifierChar(languageIndex, ch) && ch != CharacterIterator.DONE); } // Skip groups of "anything else" (operators, etc.). else if (!Character.isWhitespace(ch)) { do { ch = seg.previous(); } while (ch!=Segment.DONE && !(doc.isIdentifierChar(languageIndex, 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(); 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); Token t = doc.getTokenListForLine(start); int languageIndex = t!=null ? t.getLanguageIndex() : 0; String[] startEnd = doc.getLineCommentStartAndEnd(languageIndex); if (startEnd==null) { UIManager.getLookAndFeel().provideErrorFeedback(textArea); return; } // 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) { if (startEnd[1]!=null) { doc.insertString(end, startEnd[1], null); } doc.insertString(start, startEnd[0], null); } else { if (startEnd[1]!=null) { int temp = startEnd[1].length(); doc.remove(end-temp, temp); } doc.remove(start, startEnd[0].length()); } } @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(); } RSyntaxUtilities.possiblyRepaintGutter(textArea); } else { UIManager.getLookAndFeel().provideErrorFeedback(rsta); } } @Override public final String getMacroID() { return rstaToggleCurrentFoldAction; } } }