All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.fife.ui.rsyntaxtextarea.RSyntaxTextAreaEditorKit Maven / Gradle / Ivy

Go to download

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.

There is a newer version: 3.5.1
Show newest version
/*
 * 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 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 */ @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 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 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 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, or null if it * could not be determined. */ private String discoverTagName(RSyntaxDocument doc, int dot) { Stack stack = 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; ipos
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 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; i0 && // ((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; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy