/* * 01/27/2004 * * RSyntaxTextArea.java - An extension of RTextArea that adds * the ability to syntax highlight certain programming languages. * * 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.Color; import java.awt.Cursor; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Insets; import java.awt.Point; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.Window; import java.awt.datatransfer.Clipboard; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.InputEvent; import java.awt.event.MouseEvent; import java.awt.font.FontRenderContext; import java.io.File; import java.lang.reflect.Method; import java.net.MalformedURLException; import java.net.URL; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.ResourceBundle; import javax.swing.JLabel; import javax.swing.JMenu; import javax.swing.JPopupMenu; import javax.swing.SwingUtilities; import javax.swing.Timer; import javax.swing.UIManager; import javax.swing.event.CaretEvent; import javax.swing.event.EventListenerList; import javax.swing.event.HyperlinkEvent; import javax.swing.event.HyperlinkListener; import javax.swing.text.BadLocationException; import javax.swing.text.Document; import javax.swing.text.Element; import javax.swing.text.Highlighter; import org.fife.ui.rsyntaxtextarea.focusabletip.FocusableTip; import org.fife.ui.rsyntaxtextarea.folding.Fold; import org.fife.ui.rsyntaxtextarea.folding.FoldManager; import org.fife.ui.rsyntaxtextarea.parser.Parser; import org.fife.ui.rsyntaxtextarea.parser.ParserNotice; import org.fife.ui.rsyntaxtextarea.parser.ToolTipInfo; import org.fife.ui.rtextarea.Gutter; import org.fife.ui.rtextarea.RTextArea; import org.fife.ui.rtextarea.RTextAreaUI; import org.fife.ui.rtextarea.RTextScrollPane; import org.fife.ui.rtextarea.RecordableTextAction; /** * An extension of <code>RTextArea</code> that adds syntax highlighting * of certain programming languages to its list of features. Languages * currently supported include: * * <table summary=""> * <tr> * <td style="vertical-align: top"> * <ul> * <li>ActionScript * <li>Assembler (X86) * <li>BBCode * <li>C * <li>C++ * <li>CSS * <li>C# * <li>Clojure * <li>Dart * <li>Delphi * <li>DTD * <li>Fortran * <li>Groovy * <li>HTML * <li>htaccess * <li>Java * <li>JavaScript * <li>.jshintrc * <li>JSP * </ul> * </td> * <td style="vertical-align: top"> * <ul> * <li>LaTeX * <li>Lisp * <li>Lua * <li>Make * <li>MXML * <li>NSIS * <li>Perl * <li>PHP * <li>Properties files * <li>Python * <li>Ruby * <li>SAS * <li>Scala * <li>SQL * <li>Tcl * <li>UNIX shell scripts * <li>Visual Basic * <li>Windows batch * <li>XML files * </ul> * </td> * </tr> * </table> * * Other added features include: * <ul style="columns: 2 12em; column-gap: 1em"> * <li>Code folding * <li>Bracket matching * <li>Auto-indentation * <li>Copy as RTF * <li>Clickable hyperlinks (if the language scanner being used supports it) * <li>A pluggable "parser" system that can be used to implement syntax * validation, spell checking, etc. * </ul> * * It is recommended that you use an instance of * {@link org.fife.ui.rtextarea.RTextScrollPane} instead of a regular * <code>JScrollPane</code> as this class allows you to add line numbers and * bookmarks easily to your text area. * * @author Robert Futrell * @version 2.5.8 * @see TextEditorPane */ public class RSyntaxTextArea extends RTextArea implements SyntaxConstants { public static final String ANIMATE_BRACKET_MATCHING_PROPERTY = "RSTA.animateBracketMatching"; public static final String ANTIALIAS_PROPERTY = "RSTA.antiAlias"; public static final String AUTO_INDENT_PROPERTY = "RSTA.autoIndent"; public static final String BRACKET_MATCHING_PROPERTY = "RSTA.bracketMatching"; public static final String CLEAR_WHITESPACE_LINES_PROPERTY = "RSTA.clearWhitespaceLines"; public static final String CLOSE_CURLY_BRACES_PROPERTY = "RSTA.closeCurlyBraces"; public static final String CLOSE_MARKUP_TAGS_PROPERTY = "RSTA.closeMarkupTags"; public static final String CODE_FOLDING_PROPERTY = "RSTA.codeFolding"; public static final String EOL_VISIBLE_PROPERTY = "RSTA.eolMarkersVisible"; public static final String FOCUSABLE_TIPS_PROPERTY = "RSTA.focusableTips"; public static final String FRACTIONAL_FONTMETRICS_PROPERTY = "RSTA.fractionalFontMetrics"; public static final String HIGHLIGHT_SECONDARY_LANGUAGES_PROPERTY = "RSTA.highlightSecondaryLanguages"; public static final String HYPERLINKS_ENABLED_PROPERTY = "RSTA.hyperlinksEnabled"; public static final String MARK_OCCURRENCES_PROPERTY = "RSTA.markOccurrences"; public static final String MARKED_OCCURRENCES_CHANGED_PROPERTY = "RSTA.markedOccurrencesChanged"; public static final String PAINT_MATCHED_BRACKET_PAIR_PROPERTY = "RSTA.paintMatchedBracketPair"; public static final String PARSER_NOTICES_PROPERTY = "RSTA.parserNotices"; public static final String SYNTAX_SCHEME_PROPERTY = "RSTA.syntaxScheme"; public static final String SYNTAX_STYLE_PROPERTY = "RSTA.syntaxStyle"; public static final String TAB_LINE_COLOR_PROPERTY = "RSTA.tabLineColor"; public static final String TAB_LINES_PROPERTY = "RSTA.tabLines"; public static final String USE_SELECTED_TEXT_COLOR_PROPERTY = "RSTA.useSelectedTextColor"; public static final String VISIBLE_WHITESPACE_PROPERTY = "RSTA.visibleWhitespace"; private static final Color DEFAULT_BRACKET_MATCH_BG_COLOR = new Color(234,234,255); private static final Color DEFAULT_BRACKET_MATCH_BORDER_COLOR = new Color(0,0,128); private static final Color DEFAULT_SELECTION_COLOR = new Color(200,200,255); private static final String MSG = "org.fife.ui.rsyntaxtextarea.RSyntaxTextArea"; private JMenu foldingMenu; private static RecordableTextAction toggleCurrentFoldAction; private static RecordableTextAction collapseAllCommentFoldsAction; private static RecordableTextAction collapseAllFoldsAction; private static RecordableTextAction expandAllFoldsAction; /** The key for the syntax style to be highlighting. */ private String syntaxStyleKey; /** The colors used for syntax highlighting. */ private SyntaxScheme syntaxScheme; /** Handles code templates. */ private static CodeTemplateManager codeTemplateManager; /** Whether or not templates are enabled. */ private static boolean templatesEnabled; /** * The rectangle surrounding the "matched bracket" if bracket matching * is enabled. */ private Rectangle match; /** * The rectangle surrounding the current offset if both bracket matching and * "match both brackets" are enabled. */ private Rectangle dotRect; /** * Used to store the location of the bracket at the caret position (either * just before or just after it) and the location of its match. */ private Point bracketInfo; /** * Colors used for the "matched bracket" if bracket matching is enabled. */ private Color matchedBracketBGColor; private Color matchedBracketBorderColor; /** The location of the last matched bracket. */ private int lastBracketMatchPos; /** Whether or not bracket matching is enabled. */ private boolean bracketMatchingEnabled; /** Whether or not bracket matching is animated. */ private boolean animateBracketMatching; /** Whether <b>both</b> brackets are highlighted when bracket matching. */ private boolean paintMatchedBracketPair; private BracketMatchingTimer bracketRepaintTimer; private boolean metricsNeverRefreshed; /** * Whether or not auto-indent is on. */ private boolean autoIndentEnabled; /** * Whether curly braces should be closed on Enter key presses, (if the * current language supports it). */ private boolean closeCurlyBraces; /** * Whether closing markup tags should be automatically completed when * "<code></</code>" is typed (if the current language is a markup * language). */ private boolean closeMarkupTags; /** * Whether or not lines with nothing but whitespace are "made empty." */ private boolean clearWhitespaceLines; /** Whether we are displaying visible whitespace (spaces and tabs). */ private boolean whitespaceVisible; /** Whether EOL markers should be visible at the end of each line. */ private boolean eolMarkersVisible; /** Whether tab lines are enabled. */ private boolean paintTabLines; /** The color to use when painting tab lines. */ private Color tabLineColor; /** * Whether hyperlinks are enabled (must be supported by the syntax * scheme being used). */ private boolean hyperlinksEnabled; /** The color to use when painting hyperlinks. */ private Color hyperlinkFG; /** * Mask used to determine if the correct key is being held down to scan * for hyperlinks (ctrl, meta, etc.). */ private int linkScanningMask; /** Whether secondary languages have their backgrounds colored. */ private boolean highlightSecondaryLanguages; /** Whether the "selected text" color should be used with selected text. */ private boolean useSelectedTextColor; /** Handles "mark occurrences" support. */ private MarkOccurrencesSupport markOccurrencesSupport; /** The color used to render "marked occurrences." */ private Color markOccurrencesColor; /** Whether a border should be painted around marked occurrences. */ private boolean paintMarkOccurrencesBorder; /** Metrics of the text area's font. */ private FontMetrics defaultFontMetrics; /** Manages running the parser. */ private ParserManager parserManager; private String cachedTip; /** Used to work around an issue with Apple JVMs. */ private Point cachedTipLoc; /** * Whether the editor is currently scanning for hyperlinks on mouse * movement. */ private boolean isScanningForLinks; private int hoveredOverLinkOffset; private LinkGenerator linkGenerator; private LinkGeneratorResult linkGeneratorResult; private int rhsCorrection; private FoldManager foldManager; /** Whether "focusable" tool tips are used instead of standard ones. */ private boolean useFocusableTips; /** The last focusable tip displayed. */ private FocusableTip focusableTip; /** Cached desktop anti-aliasing hints, if anti-aliasing is enabled. */ private Map<?,?> aaHints; /** Renders tokens. */ private TokenPainter tokenPainter; private int lineHeight; // Height of a line of text; same for default, bold & italic. private int maxAscent; private boolean fractionalFontMetricsEnabled; private Color[] secondaryLanguageBackgrounds; /** * Constructor. */ public RSyntaxTextArea() { } /** * Constructor. * * @param doc The document for the editor. */ public RSyntaxTextArea(RSyntaxDocument doc) { super(doc); setSyntaxEditingStyle(doc.getSyntaxStyle()); } /** * Constructor. * * @param text The initial text to display. */ public RSyntaxTextArea(String text) { super(text); } /** * Constructor. * * @param rows The number of rows to display. * @param cols The number of columns to display. * @throws IllegalArgumentException If either <code>rows</code> or * <code>cols</code> is negative. */ public RSyntaxTextArea(int rows, int cols) { super(rows, cols); } /** * Constructor. * * @param text The initial text to display. * @param rows The number of rows to display. * @param cols The number of columns to display. * @throws IllegalArgumentException If either <code>rows</code> or * <code>cols</code> is negative. */ public RSyntaxTextArea(String text, int rows, int cols) { super(text, rows, cols); } /** * Constructor. * * @param doc The document for the editor. * @param text The initial text to display. * @param rows The number of rows to display. * @param cols The number of columns to display. * @throws IllegalArgumentException If either <code>rows</code> or * <code>cols</code> is negative. */ public RSyntaxTextArea(RSyntaxDocument doc, String text,int rows,int cols) { super(doc, text, rows, cols); } /** * Creates a new <code>RSyntaxTextArea</code>. * * @param textMode Either <code>INSERT_MODE</code> or * <code>OVERWRITE_MODE</code>. */ public RSyntaxTextArea(int textMode) { super(textMode); } /** * Adds an "active line range" listener to this text area. * * @param l The listener to add. * @see #removeActiveLineRangeListener(ActiveLineRangeListener) */ public void addActiveLineRangeListener(ActiveLineRangeListener l) { listenerList.add(ActiveLineRangeListener.class, l); } /** * Adds a hyperlink listener to this text area. * * @param l The listener to add. * @see #removeHyperlinkListener(HyperlinkListener) */ public void addHyperlinkListener(HyperlinkListener l) { listenerList.add(HyperlinkListener.class, l); } /** * Updates the font metrics the first time we're displayed. */ @Override public void addNotify() { super.addNotify(); // Some LookAndFeels (e.g. WebLaF) for some reason have a 0x0 parent // window initially (perhaps something to do with them fading in?), // which will cause an exception from getGraphics(), so we must be // careful here. if (metricsNeverRefreshed) { Window parent = SwingUtilities.getWindowAncestor(this); if (parent!=null && parent.getWidth()>0 && parent.getHeight()>0) { refreshFontMetrics(getGraphics2D(getGraphics())); metricsNeverRefreshed = false; } } // Re-start parsing if we were removed from one container and added // to another if (parserManager!=null) { parserManager.restartParsing(); } } /** * Adds the parser to "validate" the source code in this text area. This * can be anything from a spell checker to a "compiler" that verifies * source code. * * @param parser The new parser. A value of <code>null</code> will * do nothing. * @see #getParser(int) * @see #getParserCount() * @see #removeParser(Parser) */ public void addParser(Parser parser) { if (parserManager==null) { parserManager = new ParserManager(this); } parserManager.addParser(parser); } /** * Appends a submenu with code folding options to this text component's * popup menu. * * @param popup The popup menu to append to. * @see #createPopupMenu() */ protected void appendFoldingMenu(JPopupMenu popup) { popup.addSeparator(); ResourceBundle bundle = ResourceBundle.getBundle(MSG); foldingMenu = new JMenu(bundle.getString("ContextMenu.Folding")); foldingMenu.add(createPopupMenuItem(toggleCurrentFoldAction)); foldingMenu.add(createPopupMenuItem(collapseAllCommentFoldsAction)); foldingMenu.add(createPopupMenuItem(collapseAllFoldsAction)); foldingMenu.add(createPopupMenuItem(expandAllFoldsAction)); popup.add(foldingMenu); } /** * Recalculates the height of a line in this text area and the * maximum ascent of all fonts displayed. */ private void calculateLineHeight() { lineHeight = maxAscent = 0; // Each token style. for (int i=0; i<syntaxScheme.getStyleCount(); i++) { Style ss = syntaxScheme.getStyle(i); if (ss!=null && ss.font!=null) { FontMetrics fm = getFontMetrics(ss.font); int height = fm.getHeight(); if (height>lineHeight) lineHeight = height; int ascent = fm.getMaxAscent(); if (ascent>maxAscent) maxAscent = ascent; } } // The text area's (default) font). Font temp = getFont(); FontMetrics fm = getFontMetrics(temp); int height = fm.getHeight(); if (height>lineHeight) { lineHeight = height; } int ascent = fm.getMaxAscent(); if (ascent>maxAscent) { maxAscent = ascent; } } /** * Removes all parsers from this text area. * * @see #removeParser(Parser) */ public void clearParsers() { if (parserManager!=null) { parserManager.clearParsers(); } } /** * Clones a token list. This is necessary as tokens are reused in * {@link RSyntaxDocument}, so we can't simply use the ones we * are handed from it. * * @param t The token list to clone. * @return The clone of the token list. */ private TokenImpl cloneTokenList(Token t) { if (t==null) { return null; } TokenImpl clone = new TokenImpl(t); TokenImpl cloneEnd = clone; while ((t=t.getNextToken())!=null) { TokenImpl temp = new TokenImpl(t); cloneEnd.setNextToken(temp); cloneEnd = temp; } return clone; } /** * Overridden to toggle the enabled state of various * RSyntaxTextArea-specific menu items. * * If you set the popup menu via {@link #setPopupMenu(JPopupMenu)}, you * will want to override this method, especially if you removed any of the * menu items in the default popup menu. * * @param popupMenu The popup menu. This will never be <code>null</code>. * @see #createPopupMenu() * @see #setPopupMenu(JPopupMenu) */ @Override protected void configurePopupMenu(JPopupMenu popupMenu) { super.configurePopupMenu(popupMenu); // They may have overridden createPopupMenu()... if (popupMenu!=null && popupMenu.getComponentCount()>0 && foldingMenu!=null) { foldingMenu.setEnabled(foldManager. isCodeFoldingSupportedAndEnabled()); } } /** * Copies the currently selected text to the system clipboard, with * any necessary style information (font, foreground color and background * color). Does nothing for <code>null</code> selections. */ public void copyAsRtf() { int selStart = getSelectionStart(); int selEnd = getSelectionEnd(); if (selStart==selEnd) { return; } // Make sure there is a system clipboard, and that we can write // to it. SecurityManager sm = System.getSecurityManager(); if (sm!=null) { try { sm.checkSystemClipboardAccess(); } catch (SecurityException se) { UIManager.getLookAndFeel().provideErrorFeedback(null); return; } } Clipboard cb = getToolkit().getSystemClipboard(); // Create the RTF selection. RtfGenerator gen = new RtfGenerator(); Token tokenList = getTokenListFor(selStart, selEnd); for (Token t=tokenList; t!=null; t=t.getNextToken()) { if (t.isPaintable()) { if (t.length()==1 && t.charAt(0)=='\n') { gen.appendNewline(); } else { Font font = getFontForTokenType(t.getType()); Color bg = getBackgroundForToken(t); boolean underline = getUnderlineForToken(t); // Small optimization - don't print fg color if this // is a whitespace color. Saves on RTF size. if (t.isWhitespace()) { gen.appendToDocNoFG(t.getLexeme(), font, bg, underline); } else { Color fg = getForegroundForToken(t); gen.appendToDoc(t.getLexeme(), font, fg, bg, underline); } } } } // Set the system clipboard contents to the RTF selection. RtfTransferable contents = new RtfTransferable(gen.getRtf().getBytes()); //System.out.println("*** " + new String(gen.getRtf().getBytes())); try { cb.setContents(contents, null); } catch (IllegalStateException ise) { UIManager.getLookAndFeel().provideErrorFeedback(null); return; } } /** * Returns the document to use for an <code>RSyntaxTextArea</code> * * @return The document. */ @Override protected Document createDefaultModel() { return new RSyntaxDocument(SYNTAX_STYLE_NONE); } /** * Returns the caret event/mouse listener for <code>RTextArea</code>s. * * @return The caret event/mouse listener. */ @Override protected RTAMouseListener createMouseListener() { return new RSyntaxTextAreaMutableCaretEvent(this); } /** * Overridden to add menu items related to cold folding. * * @return The popup menu. * @see #appendFoldingMenu(JPopupMenu) */ @Override protected JPopupMenu createPopupMenu() { JPopupMenu popup = super.createPopupMenu(); appendFoldingMenu(popup); return popup; } /** * See createPopupMenuActions() in RTextArea. * TODO: Remove these horrible hacks and move localizing of actions into * the editor kits, where it should be! The context menu should contain * actions from the editor kits. */ private static void createRstaPopupMenuActions() { ResourceBundle msg = ResourceBundle.getBundle(MSG); toggleCurrentFoldAction = new RSyntaxTextAreaEditorKit. ToggleCurrentFoldAction(); toggleCurrentFoldAction.setProperties(msg, "Action.ToggleCurrentFold"); collapseAllCommentFoldsAction = new RSyntaxTextAreaEditorKit. CollapseAllCommentFoldsAction(); collapseAllCommentFoldsAction.setProperties(msg, "Action.CollapseCommentFolds"); collapseAllFoldsAction = new RSyntaxTextAreaEditorKit.CollapseAllFoldsAction(true); expandAllFoldsAction = new RSyntaxTextAreaEditorKit.ExpandAllFoldsAction(true); } /** * Returns the a real UI to install on this text area. * * @return The UI. */ @Override protected RTextAreaUI createRTextAreaUI() { return new RSyntaxTextAreaUI(this); } /** * If the caret is on a bracket, this method finds the matching bracket, * and if it exists, highlights it. */ protected final void doBracketMatching() { // We always need to repaint the "matched bracket" highlight if it // exists. if (match!=null) { repaint(match); if (dotRect!=null) { repaint(dotRect); } } // If a matching bracket is found, get its bounds and paint it! int lastCaretBracketPos = bracketInfo==null ? -1 : bracketInfo.x; bracketInfo = RSyntaxUtilities.getMatchingBracketPosition(this, bracketInfo); if (bracketInfo.y>-1 && (bracketInfo.y!=lastBracketMatchPos || bracketInfo.x!=lastCaretBracketPos)) { try { match = modelToView(bracketInfo.y); if (match!=null) { // Happens if we're not yet visible if (getPaintMatchedBracketPair()) { dotRect = modelToView(bracketInfo.x); } else { dotRect = null; } if (getAnimateBracketMatching()) { bracketRepaintTimer.restart(); } repaint(match); if (dotRect!=null) { repaint(dotRect); } } } catch (BadLocationException ble) { ble.printStackTrace(); // Shouldn't happen. } } else if (bracketInfo.y==-1) { // Set match to null so the old value isn't still repainted. match = null; dotRect = null; bracketRepaintTimer.stop(); } lastBracketMatchPos = bracketInfo.y; } /** * Notifies all listeners that a caret change has occurred. * * @param e The caret event. */ @Override protected void fireCaretUpdate(CaretEvent e) { super.fireCaretUpdate(e); if (isBracketMatchingEnabled()) { doBracketMatching(); } } /** * Notifies all listeners that the active line range has changed. * * @param min The minimum "active" line, or <code>-1</code>. * @param max The maximum "active" line, or <code>-1</code>. */ private void fireActiveLineRangeEvent(int min, int max) { ActiveLineRangeEvent e = null; // Lazily created // Guaranteed to return a non-null array Object[] listeners = listenerList.getListenerList(); // Process the listeners last to first, notifying // those that are interested in this event for (int i = listeners.length-2; i>=0; i-=2) { if (listeners[i]==ActiveLineRangeListener.class) { if (e==null) { e = new ActiveLineRangeEvent(this, min, max); } ((ActiveLineRangeListener)listeners[i+1]).activeLineRangeChanged(e); } } } /** * Notifies all listeners that have registered interest for notification * on this event type. The listener list is processed last to first. * * @param e The event to fire. * @see EventListenerList */ private void fireHyperlinkUpdate(HyperlinkEvent e) { // Guaranteed to return a non-null array Object[] listeners = listenerList.getListenerList(); // Process the listeners last to first, notifying // those that are interested in this event for (int i = listeners.length-2; i>=0; i-=2) { if (listeners[i]==HyperlinkListener.class) { ((HyperlinkListener)listeners[i+1]).hyperlinkUpdate(e); } } } /** * Notifies listeners that the marked occurrences for this text area * have changed. */ void fireMarkedOccurrencesChanged() { firePropertyChange(RSyntaxTextArea.MARKED_OCCURRENCES_CHANGED_PROPERTY, null, null); } /** * Fires a notification that the parser notices for this text area have * changed. */ void fireParserNoticesChange() { firePropertyChange(PARSER_NOTICES_PROPERTY, null, null); } /** * Called whenever a fold is collapsed or expanded. This causes the * text editor to revalidate. This method is here because of poor design * and should be removed. * * @param fold The fold that was collapsed or expanded. */ public void foldToggled(Fold fold) { match = null; // TODO: Update the bracket rect rather than hide it dotRect = null; if (getLineWrap()) { // NOTE: Without doing this later, the caret position is out of // sync with the Element structure when word wrap is enabled, and // causes BadLocationExceptions when an entire folded region is // deleted (see GitHub issue #22: // https://github.com/bobbylight/RSyntaxTextArea/issues/22) SwingUtilities.invokeLater(new Runnable() { public void run() { possiblyUpdateCurrentLineHighlightLocation(); } }); } else { possiblyUpdateCurrentLineHighlightLocation(); } revalidate(); repaint(); } /** * Forces the given {@link Parser} to re-parse the content of this text * area.<p> * * This method can be useful when a <code>Parser</code> can be configured * as to what notices it returns. For example, if a Java language parser * can be configured to set whether no serialVersionUID is a warning, * error, or ignored, this method can be called after changing the expected * notice type to have the document re-parsed. * * @param parser The index of the <code>Parser</code> to re-run. * @see #getParser(int) */ public void forceReparsing(int parser) { parserManager.forceReparsing(parser); } /** * Forces re-parsing with a specific parser. Note that if this parser is * not installed on this text area, nothing will happen. * * @param parser The parser that should re-parse this text area's contents. * This should be installed on this text area. * @return Whether the parser was installed on this text area. * @see #forceReparsing(int) */ public boolean forceReparsing(Parser parser) { for (int i=0; i<getParserCount(); i++) { if (getParser(i)==parser) { forceReparsing(i); return true; } } return false; } /** * Returns whether bracket matching should be animated. * * @return Whether bracket matching should be animated. * @see #setAnimateBracketMatching(boolean) */ public boolean getAnimateBracketMatching() { return animateBracketMatching; } /** * Returns whether anti-aliasing is enabled in this editor. * * @return Whether anti-aliasing is enabled in this editor. * @see #setAntiAliasingEnabled(boolean) * @see #getFractionalFontMetricsEnabled() */ public boolean getAntiAliasingEnabled() { return aaHints!=null; } /** * Returns the background color for a token. * * @param token The token. * @return The background color to use for that token. If this value is * is <code>null</code> then this token has no special background * color. * @see #getForegroundForToken(Token) */ public Color getBackgroundForToken(Token token) { Color c = null; if (getHighlightSecondaryLanguages()) { // 1-indexed, since 0 == main language. int languageIndex = token.getLanguageIndex() - 1; if (languageIndex>=0 && languageIndex<secondaryLanguageBackgrounds.length) { c = secondaryLanguageBackgrounds[languageIndex]; } } if (c==null) { c = syntaxScheme.getStyle(token.getType()).background; } // Don't default to this.getBackground(), as Tokens simply don't // paint a background if they get a null Color. return c; } /** * Returns whether curly braces should be automatically closed when a * newline is entered after an opening curly brace. Note that this * property is only honored for languages that use curly braces to denote * code blocks. * * @return Whether curly braces should be automatically closed. * @see #setCloseCurlyBraces(boolean) */ public boolean getCloseCurlyBraces() { return closeCurlyBraces; } /** * Returns whether closing markup tags should be automatically completed * when "<code></</code>" is typed. Note that this property is only * honored for markup languages, such as HTML, XML and PHP. * * @return Whether closing markup tags should be automatically completed. * @see #setCloseMarkupTags(boolean) */ public boolean getCloseMarkupTags() { return closeMarkupTags; } /** * Returns the code template manager for all instances of * <code>RSyntaxTextArea</code>. The manager is lazily created. * * @return The code template manager. * @see #setTemplatesEnabled(boolean) */ public static synchronized CodeTemplateManager getCodeTemplateManager() { if (codeTemplateManager==null) { codeTemplateManager = new CodeTemplateManager(); } return codeTemplateManager; } /** * Returns the default bracket-match background color. * * @return The color. * @see #getDefaultBracketMatchBorderColor */ public static final Color getDefaultBracketMatchBGColor() { return DEFAULT_BRACKET_MATCH_BG_COLOR; } /** * Returns the default bracket-match border color. * * @return The color. * @see #getDefaultBracketMatchBGColor */ public static final Color getDefaultBracketMatchBorderColor() { return DEFAULT_BRACKET_MATCH_BORDER_COLOR; } /** * Returns the default selection color for this text area. This * color was chosen because it's light and <code>RSyntaxTextArea</code> * does not change text color between selected/unselected text for * contrast like regular <code>JTextArea</code>s do. * * @return The default selection color. */ public static Color getDefaultSelectionColor() { return DEFAULT_SELECTION_COLOR; } /** * Returns the "default" syntax highlighting color scheme. The colors * used are somewhat standard among syntax highlighting text editors. * * @return The default syntax highlighting color scheme. * @see #restoreDefaultSyntaxScheme() * @see #getSyntaxScheme() * @see #setSyntaxScheme(SyntaxScheme) */ public SyntaxScheme getDefaultSyntaxScheme() { return new SyntaxScheme(getFont()); } /** * Returns whether an EOL marker should be drawn at the end of each line. * * @return Whether EOL markers should be visible. * @see #setEOLMarkersVisible(boolean) * @see #isWhitespaceVisible() */ public boolean getEOLMarkersVisible() { return eolMarkersVisible; } /** * Returns the fold manager for this text area. * * @return The fold manager. */ public FoldManager getFoldManager() { return foldManager; } /** * Returns the font for tokens of the specified type. * * @param type The type of token. * @return The font to use for that token type. * @see #getFontMetricsForTokenType(int) */ public Font getFontForTokenType(int type) { Font f = syntaxScheme.getStyle(type).font; return f!=null ? f : getFont(); } /** * Returns the font metrics for tokens of the specified type. * * @param type The type of token. * @return The font metrics to use for that token type. * @see #getFontForTokenType(int) */ public FontMetrics getFontMetricsForTokenType(int type) { FontMetrics fm = syntaxScheme.getStyle(type).fontMetrics; return fm!=null ? fm : defaultFontMetrics; } /** * Returns the foreground color to use when painting a token. * * @param t The token. * @return The foreground color to use for that token. This * value is never <code>null</code>. * @see #getBackgroundForToken(Token) */ public Color getForegroundForToken(Token t) { if (getHyperlinksEnabled() && hoveredOverLinkOffset==t.getOffset() && (t.isHyperlink() || linkGeneratorResult!=null)) { return hyperlinkFG; } return getForegroundForTokenType(t.getType()); } /** * Returns the foreground color to use when painting a token. This does * not take into account whether the token is a hyperlink. * * @param type The token type. * @return The foreground color to use for that token. This * value is never <code>null</code>. * @see #getForegroundForToken(Token) */ public Color getForegroundForTokenType(int type) { Color fg = syntaxScheme.getStyle(type).foreground; return fg!=null ? fg : getForeground(); } /** * Returns whether fractional font metrics are enabled for this text area. * * @return Whether fractional font metrics are enabled. * @see #setFractionalFontMetricsEnabled * @see #getAntiAliasingEnabled() */ public boolean getFractionalFontMetricsEnabled() { return fractionalFontMetricsEnabled; } /** * Returns a <code>Graphics2D</code> version of the specified graphics * that has been initialized with the proper rendering hints. * * @param g The graphics context for which to get a * <code>Graphics2D</code>. * @return The <code>Graphics2D</code>. */ private final Graphics2D getGraphics2D(Graphics g) { Graphics2D g2d = (Graphics2D)g; if (aaHints!=null) { g2d.addRenderingHints(aaHints); } if (fractionalFontMetricsEnabled) { g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON); } return g2d; } /** * Returns whether "secondary" languages should have their backgrounds * colored differently to visually differentiate them. This feature * imposes a fair performance penalty. * * @return Whether secondary languages have their backgrounds colored * differently. * @see #setHighlightSecondaryLanguages(boolean) * @see #getSecondaryLanguageBackground(int) * @see #getSecondaryLanguageCount() * @see #setSecondaryLanguageBackground(int, Color) */ public boolean getHighlightSecondaryLanguages() { return highlightSecondaryLanguages; } /** * Returns the color to use when painting hyperlinks. * * @return The color to use when painting hyperlinks. * @see #setHyperlinkForeground(Color) * @see #getHyperlinksEnabled() */ public Color getHyperlinkForeground() { return hyperlinkFG; } /** * Returns whether hyperlinks are enabled for this text area. * * @return Whether hyperlinks are enabled for this text area. * @see #setHyperlinksEnabled(boolean) */ public boolean getHyperlinksEnabled() { return hyperlinksEnabled; } /** * Returns the last visible offset in this text area. This may not be the * length of the document if code folding is enabled. * * @return The last visible offset in this text area. */ public int getLastVisibleOffset() { if (isCodeFoldingEnabled()) { int lastVisibleLine = foldManager.getLastVisibleLine(); if (lastVisibleLine<getLineCount()-1) { // Not the last line try { return getLineEndOffset(lastVisibleLine) - 1; } catch (BadLocationException ble) { // Never happens ble.printStackTrace(); } } } return getDocument().getLength(); } /** * Returns the height to use for a line of text in this text area. * * @return The height of a line of text in this text area. */ @Override public int getLineHeight() { //System.err.println("... getLineHeight() returning " + lineHeight); return lineHeight; } public LinkGenerator getLinkGenerator() { return linkGenerator; } /** * Returns a list of "mark all" highlights in the text area. If there are * no such highlights, this will be an empty list. * * @return The list of "mark all" highlight ranges. */ public List<DocumentRange> getMarkAllHighlightRanges() { return ((RSyntaxTextAreaHighlighter)getHighlighter()). getMarkAllHighlightRanges(); } /** * Returns a list of "marked occurrences" in the text area. If there are * no marked occurrences, this will be an empty list. * * @return The list of marked occurrences. */ public List<DocumentRange> getMarkedOccurrences() { return ((RSyntaxTextAreaHighlighter)getHighlighter()). getMarkedOccurrences(); } /** * Returns whether "Mark Occurrences" is enabled. * * @return Whether "Mark Occurrences" is enabled. * @see #setMarkOccurrences(boolean) */ public boolean getMarkOccurrences() { return markOccurrencesSupport!=null; } /** * Returns the color used to "mark occurrences." * * @return The mark occurrences color. * @see #setMarkOccurrencesColor(Color) */ public Color getMarkOccurrencesColor() { return markOccurrencesColor; } /** * Returns whether tokens of the specified type should have "mark * occurrences" enabled for the current programming language. * * @param type The token type. * @return Whether tokens of this type should have "mark occurrences" * enabled. */ boolean getMarkOccurrencesOfTokenType(int type) { RSyntaxDocument doc = (RSyntaxDocument)getDocument(); return doc.getMarkOccurrencesOfTokenType(type); } /** * Gets the color used as the background for a matched bracket. * * @return The color used. If this is <code>null</code>, no special * background is painted behind a matched bracket. * @see #setMatchedBracketBGColor * @see #getMatchedBracketBorderColor */ public Color getMatchedBracketBGColor() { return matchedBracketBGColor; } /** * Gets the color used as the border for a matched bracket. * * @return The color used. * @see #setMatchedBracketBorderColor * @see #getMatchedBracketBGColor */ public Color getMatchedBracketBorderColor() { return matchedBracketBorderColor; } /** * Returns the caret's offset's rectangle, or <code>null</code> if there * is currently no matched bracket, bracket matching is disabled, or "paint * both matched brackets" is disabled. This should never be called by the * programmer directly. * * @return The rectangle surrounding the matched bracket. * @see #getMatchRectangle() */ Rectangle getDotRectangle() { return dotRect; } /** * Returns the matched bracket's rectangle, or <code>null</code> if there * is currently no matched bracket. This should never be called by the * programmer directly. * * @return The rectangle surrounding the matched bracket. * @see #getDotRectangle() */ Rectangle getMatchRectangle() { return match; } /** * Overridden to return the max ascent for any font used in the editor. * * @return The max ascent value. */ @Override public int getMaxAscent() { return maxAscent; } /** * Returns whether the bracket at the caret position is painted as a * "match" when a matched bracket is found. Note that this property does * nothing if {@link #isBracketMatchingEnabled()} returns * <code>false</code>. * * @return Whether both brackets in a bracket pair are highlighted when * bracket matching is enabled. * @see #setPaintMatchedBracketPair(boolean) * @see #isBracketMatchingEnabled() * @see #setBracketMatchingEnabled(boolean) */ public boolean getPaintMatchedBracketPair() { return paintMatchedBracketPair; } /** * Returns whether tab lines are painted. * * @return Whether tab lines are painted. * @see #setPaintTabLines(boolean) * @see #getTabLineColor() */ public boolean getPaintTabLines() { return paintTabLines; } /** * Returns whether to paint the backgrounds of tokens on the specified * line (assuming they are not obstructed by e.g. selection). * * @param line The line number. * @param y The y-offset of the line. This is used when line wrap is * enabled, since each logical line can be rendered as several * physical lines. * @return Whether to paint the token backgrounds on this line. */ boolean getPaintTokenBackgrounds(int line, float y) { //System.out.println(y + ", " + getCurrentCaretY() + "-" + (getCurrentCaretY() + getLineHeight())); int iy = (int)y; int curCaretY = getCurrentCaretY(); return iy<curCaretY || iy>=curCaretY+getLineHeight() || !getHighlightCurrentLine(); } /** * Returns the specified parser. * * @param index The {@link Parser} to retrieve. * @return The <code>Parser</code>. * @see #getParserCount() * @see #addParser(Parser) */ public Parser getParser(int index) { return parserManager.getParser(index); } /** * Returns the number of parsers operating on this text area. * * @return The parser count. * @see #addParser(Parser) */ public int getParserCount() { return parserManager==null ? 0 : parserManager.getParserCount(); } /** * Returns the currently set parser delay. This is the delay that must * occur between edits for any registered {@link Parser}s to run. * * @return The currently set parser delay, in milliseconds. * @see #setParserDelay(int) */ public int getParserDelay() { return parserManager.getDelay(); } /** * Returns a list of the current parser notices for this text area. * This method (like most Swing methods) should only be called on the * EDT. * * @return The list of notices. This will be an empty list if there are * none. */ public List<ParserNotice> getParserNotices() { if (parserManager==null) { return Collections.emptyList(); } return parserManager.getParserNotices(); } /** * Workaround for JTextComponents allowing the caret to be rendered * entirely off-screen if the entire "previous" character fit entirely. * * @return The amount of space to add to the x-axis preferred span. * @see #setRightHandSideCorrection(int) */ public int getRightHandSideCorrection() { return rhsCorrection; } /** * If auto-indent is enabled, this method returns whether a new line after * this one should be indented (based on the standard indentation rules for * the current programming language). For example, in Java, for a line * containing: * * <pre> * for (int i=0; i<10; i++) { * </pre> * * the following line should be indented. * * @param line The line to check. * @return Whether a line inserted after this one should be auto-indented. * If auto-indentation is disabled, this will always return * <code>false</code>. * @see #isAutoIndentEnabled() */ public boolean getShouldIndentNextLine(int line) { if (isAutoIndentEnabled()) { RSyntaxDocument doc = (RSyntaxDocument)getDocument(); return doc.getShouldIndentNextLine(line); } return false; } /** * Returns what type of syntax highlighting this editor is doing. * * @return The style being used, such as * {@link SyntaxConstants#SYNTAX_STYLE_JAVA}. * @see #setSyntaxEditingStyle(String) * @see SyntaxConstants */ public String getSyntaxEditingStyle() { return syntaxStyleKey; } /** * Returns all of the colors currently being used in syntax highlighting * by this text component. * * @return An instance of <code>SyntaxScheme</code> that represents * the colors currently being used for syntax highlighting. * @see #setSyntaxScheme(SyntaxScheme) */ public SyntaxScheme getSyntaxScheme() { return syntaxScheme; } /** * Returns the color used to paint tab lines. * * @return The color used to paint tab lines. * @see #setTabLineColor(Color) * @see #getPaintTabLines() * @see #setPaintTabLines(boolean) */ public Color getTabLineColor() { return tabLineColor; } /** * Returns whether a border is painted around marked occurrences. * * @return Whether a border is painted. * @see #setPaintMarkOccurrencesBorder(boolean) * @see #getMarkOccurrencesColor() * @see #getMarkOccurrences() */ public boolean getPaintMarkOccurrencesBorder() { return paintMarkOccurrencesBorder; } /** * Returns the background color for the specified secondary language. * * @param index The language index. Note that these are 1-based, not * 0-based, and should be in the range * <code>1-getSecondaryLanguageCount()</code>, inclusive. * @return The color, or <code>null</code> if none. * @see #getSecondaryLanguageCount() * @see #setSecondaryLanguageBackground(int, Color) * @see #getHighlightSecondaryLanguages() */ public Color getSecondaryLanguageBackground(int index) { return secondaryLanguageBackgrounds[index - 1]; } /** * Returns the number of secondary language backgrounds. * * @return The number of secondary language backgrounds. * @see #getSecondaryLanguageBackground(int) * @see #setSecondaryLanguageBackground(int, Color) * @see #getHighlightSecondaryLanguages() */ public int getSecondaryLanguageCount() { return secondaryLanguageBackgrounds.length; } /** * Returns whether or not templates are enabled for all instances * of <code>RSyntaxTextArea</code>.<p> * * For more flexible boilerplate code insertion, consider using the * <a href="http://javadoc.fifesoft.com/autocomplete/org/fife/ui/autocomplete/TemplateCompletion.html">TemplateCompletion * class</a> in the * <a href="https://github.com/bobbylight/AutoComplete">AutoComplete * add-on library</a>. * * @return Whether templates are enabled. * @see #saveTemplates() * @see #setTemplateDirectory(String) * @see #setTemplatesEnabled(boolean) */ public static synchronized boolean getTemplatesEnabled() { return templatesEnabled; } /** * Returns a token list for the given range in the document. * * @param startOffs The starting offset in the document. * @param endOffs The end offset in the document. * @return The first token in the token list. */ private Token getTokenListFor(int startOffs, int endOffs) { TokenImpl tokenList = null; TokenImpl lastToken = null; Element map = getDocument().getDefaultRootElement(); int startLine = map.getElementIndex(startOffs); int endLine = map.getElementIndex(endOffs); for (int line=startLine; line<=endLine; line++) { TokenImpl t = (TokenImpl)getTokenListForLine(line); t = cloneTokenList(t); if (tokenList==null) { tokenList = t; lastToken = tokenList; } else { lastToken.setNextToken(t); } while (lastToken.getNextToken()!=null && lastToken.getNextToken().isPaintable()) { lastToken = (TokenImpl)lastToken.getNextToken(); } if (line<endLine) { // Document offset MUST be correct to prevent exceptions // in getTokenListFor() int docOffs = map.getElement(line).getEndOffset()-1; t = new TokenImpl(new char[] { '\n' }, 0,0, docOffs, Token.WHITESPACE, 0); lastToken.setNextToken(t); lastToken = t; } } // Trim the beginning and end of the token list so that it starts // at startOffs and ends at endOffs. // Be careful and check that startOffs is actually in the list. // startOffs can be < the token list's start if the end "newline" // character of a line is the first character selected (the token // list returned for that line will be null, so the first token in // the final token list will be from the next line and have a // starting offset > startOffs?). if (startOffs>=tokenList.getOffset()) { while (!tokenList.containsPosition(startOffs)) { tokenList = (TokenImpl)tokenList.getNextToken(); } tokenList.makeStartAt(startOffs); } TokenImpl temp = tokenList; // Be careful to check temp for null here. It is possible that no // token contains endOffs, if endOffs is at the end of a line. while (temp!=null && !temp.containsPosition(endOffs)) { temp = (TokenImpl)temp.getNextToken(); } if (temp!=null) { temp.textCount = endOffs - temp.getOffset(); temp.setNextToken(null); } return tokenList; } /** * Returns a list of tokens representing the given line. * * @param line The line number to get tokens for. * @return A linked list of tokens representing the line's text. */ public Token getTokenListForLine(int line) { return ((RSyntaxDocument)getDocument()).getTokenListForLine(line); } /** * Returns the painter to use for rendering tokens. * * @return The painter to use for rendering tokens. */ TokenPainter getTokenPainter() { return tokenPainter; } /** * Returns the tool tip to display for a mouse event at the given * location. This method is overridden to give a registered parser a * chance to display a tool tip (such as an error description when the * mouse is over an error highlight). * * @param e The mouse event. */ @Override public String getToolTipText(MouseEvent e) { // Apple JVMS (Java 6 and prior) have their ToolTipManager events // repeat for some reason, so this method gets called every 1 second // or so. We short-circuit that since some ToolTipManagers may do // expensive calculations (e.g. language supports). if (RSyntaxUtilities.getOS()==RSyntaxUtilities.OS_MAC_OSX) { Point newLoc = e.getPoint(); if (newLoc!=null && newLoc.equals(cachedTipLoc)) { return cachedTip; } cachedTipLoc = newLoc; } return cachedTip = getToolTipTextImpl(e); } /** * Does the dirty work of getting the tool tip text. * * @param e The mouse event. * @return The tool tip text. */ protected String getToolTipTextImpl(MouseEvent e) { // Check parsers for tool tips first. String text = null; URL imageBase = null; if (parserManager!=null) { ToolTipInfo info = parserManager.getToolTipText(e); if (info!=null) { // Should always be true text = info.getToolTipText(); // May be null imageBase = info.getImageBase(); // May be null } } if (text==null) { text = super.getToolTipText(e); } // Do we want to use "focusable" tips? if (getUseFocusableTips()) { if (text!=null) { if (focusableTip==null) { focusableTip = new FocusableTip(this, parserManager); } focusableTip.setImageBase(imageBase); focusableTip.toolTipRequested(e, text); } // No tool tip text at new location - hide tip window if one is // currently visible else if (focusableTip!=null) { focusableTip.possiblyDisposeOfTipWindow(); } return null; } return text; // Standard tool tips } /** * Returns whether the specified token should be underlined. * A token is underlined if its syntax style includes underlining, * or if it is a hyperlink and hyperlinks are enabled. * * @param t The token. * @return Whether the specified token should be underlined. */ public boolean getUnderlineForToken(Token t) { return (getHyperlinksEnabled() && (t.isHyperlink() || (linkGeneratorResult!=null && linkGeneratorResult.getSourceOffset()==t.getOffset()))) || syntaxScheme.getStyle(t.getType()).underline; } /** * Returns whether "focusable" tool tips are used instead of standard * ones. Focusable tool tips are tool tips that the user can click on, * resize, copy from, and click links in. * * @return Whether to use focusable tool tips. * @see #setUseFocusableTips(boolean) * @see FocusableTip */ public boolean getUseFocusableTips() { return useFocusableTips; } /** * Returns whether selected text should use the "selected text color" * property set via {@link #setSelectedTextColor(Color)}. This is the * typical behavior of text components. By default, RSyntaxTextArea does * not do this, so that token styles are visible even in selected regions * of text. * * @return Whether the "selected text" color is used when painting text * in selected regions. * @see #setUseSelectedTextColor(boolean) */ public boolean getUseSelectedTextColor() { return useSelectedTextColor; } /** * Called by constructors to initialize common properties of the text * editor. */ @Override protected void init() { super.init(); metricsNeverRefreshed = true; tokenPainter = new DefaultTokenPainter(); // NOTE: Our actions are created here instead of in a static block // so they are only created when the first RTextArea is instantiated, // not before. There have been reports of users calling static getters // (e.g. RSyntaxTextArea.getDefaultBracketMatchBGColor()) which would // cause these actions to be created and (possibly) incorrectly // localized, if they were in a static block. if (toggleCurrentFoldAction==null) { createRstaPopupMenuActions(); } // Set some RSyntaxTextArea default values. syntaxStyleKey = SYNTAX_STYLE_NONE; setMatchedBracketBGColor(getDefaultBracketMatchBGColor()); setMatchedBracketBorderColor(getDefaultBracketMatchBorderColor()); setBracketMatchingEnabled(true); setAnimateBracketMatching(true); lastBracketMatchPos = -1; setSelectionColor(getDefaultSelectionColor()); setTabLineColor(null); setMarkOccurrencesColor(MarkOccurrencesSupport.DEFAULT_COLOR); foldManager = new FoldManager(this); // Set auto-indent related stuff. setAutoIndentEnabled(true); setCloseCurlyBraces(true); setCloseMarkupTags(true); setClearWhitespaceLinesEnabled(true); setHyperlinksEnabled(true); setLinkScanningMask(InputEvent.CTRL_DOWN_MASK); setHyperlinkForeground(Color.BLUE); isScanningForLinks = false; setUseFocusableTips(true); //setAntiAliasingEnabled(true); setDefaultAntiAliasingState(); restoreDefaultSyntaxScheme(); setHighlightSecondaryLanguages(true); secondaryLanguageBackgrounds = new Color[3]; secondaryLanguageBackgrounds[0] = new Color(0xfff0cc); secondaryLanguageBackgrounds[1] = new Color(0xdafeda); secondaryLanguageBackgrounds[2] = new Color(0xffe0f0); setRightHandSideCorrection(0); } /** * Returns whether or not auto-indent is enabled. * * @return Whether or not auto-indent is enabled. * @see #setAutoIndentEnabled(boolean) */ public boolean isAutoIndentEnabled() { return autoIndentEnabled; } /** * Returns whether or not bracket matching is enabled. * * @return <code>true</code> iff bracket matching is enabled. * @see #setBracketMatchingEnabled */ public final boolean isBracketMatchingEnabled() { return bracketMatchingEnabled; } /** * Returns whether or not lines containing nothing but whitespace are made * into blank lines when Enter is pressed in them. * * @return Whether or not whitespace-only lines are cleared when * the user presses Enter on them. * @see #setClearWhitespaceLinesEnabled(boolean) */ public boolean isClearWhitespaceLinesEnabled() { return clearWhitespaceLines; } /** * Returns whether code folding is enabled. Note that only certain * languages support code folding; those that do not will ignore this * property. * * @return Whether code folding is enabled. * @see #setCodeFoldingEnabled(boolean) */ public boolean isCodeFoldingEnabled() { return foldManager.isCodeFoldingEnabled(); } /** * Returns whether whitespace (spaces and tabs) is visible. * * @return Whether whitespace is visible. * @see #setWhitespaceVisible(boolean) * @see #getEOLMarkersVisible() */ public boolean isWhitespaceVisible() { return whitespaceVisible; } /** * Returns the token at the specified position in the model. * * @param offs The position in the model. * @return The token, or <code>null</code> if no token is at that * position. * @see #viewToToken(Point) */ public Token modelToToken(int offs) { if (offs>=0) { try { int line = getLineOfOffset(offs); Token t = getTokenListForLine(line); return RSyntaxUtilities.getTokenAtOffset(t, offs); } catch (BadLocationException ble) { ble.printStackTrace(); // Never happens } } return null; } /** * The <code>paintComponent</code> method is overridden so we * apply any necessary rendering hints to the Graphics object. */ @Override protected void paintComponent(Graphics g) { // A call to refreshFontMetrics() used to be in addNotify(), but // unfortunately we cannot always get the graphics context there. If // the parent frame/dialog is LAF-decorated, there is a chance that the // window's width and/or height is still == 0 at addNotify() (e.g. // WebLaF). So unfortunately it's safest to do this here, with a flag // to only allow it to happen once. if (metricsNeverRefreshed) { refreshFontMetrics(getGraphics2D(getGraphics())); metricsNeverRefreshed = false; } super.paintComponent(getGraphics2D(g)); } private void refreshFontMetrics(Graphics2D g2d) { // It is assumed that any rendering hints are already applied to g2d. defaultFontMetrics = g2d.getFontMetrics(getFont()); syntaxScheme.refreshFontMetrics(g2d); if (getLineWrap()==false) { // HORRIBLE HACK! The un-wrapped view needs to refresh its cached // longest line information. SyntaxView sv = (SyntaxView)getUI().getRootView(this).getView(0); sv.calculateLongestLine(); } } /** * {@inheritDoc} */ @Override public void redoLastAction() { super.redoLastAction(); // Occasionally marked occurrences' Positions are in invalid states // due to how javax.swing.text.AbstractDocument tracks the start and // end offsets. This is usually not needed, but can be when the last // token in the Document is a marked occurrence, and an undo or redo // occurs which clears most of the document text. In that case it is // possible for the end Position to be reset to something small, but // the start offset to be its prior valid (start > end). ((RSyntaxTextAreaHighlighter)getHighlighter()). clearMarkOccurrencesHighlights(); } /** * Removes an "active line range" listener from this text area. * * @param l The listener to remove. * @see #removeActiveLineRangeListener(ActiveLineRangeListener) */ public void removeActiveLineRangeListener(ActiveLineRangeListener l) { listenerList.remove(ActiveLineRangeListener.class, l); } /** * Removes a hyperlink listener from this text area. * * @param l The listener to remove. * @see #addHyperlinkListener(HyperlinkListener) */ public void removeHyperlinkListener(HyperlinkListener l) { listenerList.remove(HyperlinkListener.class, l); } /** * Overridden so we stop this text area's parsers, if any. */ @Override public void removeNotify() { if (parserManager!=null) { parserManager.stopParsing(); } super.removeNotify(); } /** * Removes a parser from this text area. * * @param parser The {@link Parser} to remove. * @return Whether the parser was found and removed. * @see #clearParsers() * @see #addParser(Parser) * @see #getParser(int) */ public boolean removeParser(Parser parser) { boolean removed = false; if (parserManager!=null) { removed = parserManager.removeParser(parser); } return removed; } /** * Sets the colors used for syntax highlighting to their defaults. * * @see #setSyntaxScheme(SyntaxScheme) * @see #getSyntaxScheme() * @see #getDefaultSyntaxScheme() */ public void restoreDefaultSyntaxScheme() { setSyntaxScheme(getDefaultSyntaxScheme()); } /** * Attempts to save all currently-known templates to the current template * directory, as set by <code>setTemplateDirectory</code>. Templates * will be saved as XML files with names equal to their abbreviations; for * example, a template that expands on the word "forb" will be saved as * <code>forb.xml</code>. * * @return Whether or not the save was successful. The save will * be unsuccessful if the template directory does not exist or * if it has not been set (i.e., you have not yet called * <code>setTemplateDirectory</code>). * @see #getTemplatesEnabled * @see #setTemplateDirectory * @see #setTemplatesEnabled */ public static synchronized boolean saveTemplates() { if (!getTemplatesEnabled()) { return false; } return getCodeTemplateManager().saveTemplates(); } /** * Sets the "active line range." Note that this * <code>RSyntaxTextArea</code> itself does nothing with this information, * but if it is contained inside an {@link RTextScrollPane}, the active * line range may be displayed in the icon area of the {@link Gutter}.<p> * * Note that basic users of <code>RSyntaxTextArea</code> will not call this * method directly; rather, it is usually called by instances of * <code>LanguageSupport</code> in the <code>RSTALangaugeSupport</code> * library. See <a href="http://fifesoft.com">http://fifesoft.com</a> * for more information about this library. * * @param min The "minimum" line in the active line range, or * <code>-1</code> if the range is being cleared. * @param max The "maximum" line in the active line range, or * <code>-1</code> if the range is being cleared. * @see #addActiveLineRangeListener(ActiveLineRangeListener) */ public void setActiveLineRange(int min, int max) { if (min==-1) { max = -1; // Force max to be -1 if min is. } fireActiveLineRangeEvent(min, max); } /** * Sets whether bracket matching should be animated. This fires a property * change event of type {@link #ANIMATE_BRACKET_MATCHING_PROPERTY}. * * @param animate Whether to animate bracket matching. * @see #getAnimateBracketMatching() */ public void setAnimateBracketMatching(boolean animate) { if (animate!=animateBracketMatching) { animateBracketMatching = animate; if (animate && bracketRepaintTimer==null) { bracketRepaintTimer = new BracketMatchingTimer(); } firePropertyChange(ANIMATE_BRACKET_MATCHING_PROPERTY, !animate, animate); } } /** * Sets whether anti-aliasing is enabled in this editor. This method * fires a property change event of type {@link #ANTIALIAS_PROPERTY}. * * @param enabled Whether anti-aliasing is enabled. * @see #getAntiAliasingEnabled() */ public void setAntiAliasingEnabled(boolean enabled) { boolean currentlyEnabled = aaHints!=null; if (enabled!=currentlyEnabled) { if (enabled) { aaHints = RSyntaxUtilities.getDesktopAntiAliasHints(); // If the desktop query method comes up empty, use the standard // Java2D greyscale method. Note this will likely NOT be as // nice as what would be used if the getDesktopAntiAliasHints() // call worked. if (aaHints==null) { Map<RenderingHints.Key, Object> temp = new HashMap<RenderingHints.Key, Object>(); temp.put(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); aaHints = temp; } } else { aaHints = null; } // We must be connected to a screen resource for our graphics // to be non-null. if (isDisplayable()) { refreshFontMetrics(getGraphics2D(getGraphics())); } firePropertyChange(ANTIALIAS_PROPERTY, !enabled, enabled); repaint(); } } /** * Sets whether or not auto-indent is enabled. This fires a property * change event of type {@link #AUTO_INDENT_PROPERTY}. * * @param enabled Whether or not auto-indent is enabled. * @see #isAutoIndentEnabled() */ public void setAutoIndentEnabled(boolean enabled) { if (autoIndentEnabled!=enabled) { autoIndentEnabled = enabled; firePropertyChange(AUTO_INDENT_PROPERTY, !enabled, enabled); } } /** * Sets whether bracket matching is enabled. This fires a property change * event of type {@link #BRACKET_MATCHING_PROPERTY}. * * @param enabled Whether or not bracket matching should be enabled. * @see #isBracketMatchingEnabled() */ public void setBracketMatchingEnabled(boolean enabled) { if (enabled!=bracketMatchingEnabled) { bracketMatchingEnabled = enabled; repaint(); firePropertyChange(BRACKET_MATCHING_PROPERTY, !enabled, enabled); } } /** * Sets whether or not lines containing nothing but whitespace are made * into blank lines when Enter is pressed in them. This method fires * a property change event of type {@link #CLEAR_WHITESPACE_LINES_PROPERTY}. * * @param enabled Whether or not whitespace-only lines are cleared when * the user presses Enter on them. * @see #isClearWhitespaceLinesEnabled() */ public void setClearWhitespaceLinesEnabled(boolean enabled) { if (enabled!=clearWhitespaceLines) { clearWhitespaceLines = enabled; firePropertyChange(CLEAR_WHITESPACE_LINES_PROPERTY, !enabled, enabled); } } /** * Toggles whether curly braces should be automatically closed when a * newline is entered after an opening curly brace. Note that this * property is only honored for languages that use curly braces to denote * code blocks.<p> * * This method fires a property change event of type * {@link #CLOSE_CURLY_BRACES_PROPERTY}. * * @param close Whether curly braces should be automatically closed. * @see #getCloseCurlyBraces() */ public void setCloseCurlyBraces(boolean close) { if (close!=closeCurlyBraces) { closeCurlyBraces = close; firePropertyChange(CLOSE_CURLY_BRACES_PROPERTY, !close, close); } } /** * Sets whether closing markup tags should be automatically completed * when "<code></</code>" is typed. Note that this property is only * honored for markup languages, such as HTML, XML and PHP.<p> * * This method fires a property change event of type * {@link #CLOSE_MARKUP_TAGS_PROPERTY}. * * @param close Whether closing markup tags should be automatically * completed. * @see #getCloseMarkupTags() */ public void setCloseMarkupTags(boolean close) { if (close!=closeMarkupTags) { closeMarkupTags = close; firePropertyChange(CLOSE_MARKUP_TAGS_PROPERTY, !close, close); } } /** * Sets whether code folding is enabled. Note that only certain * languages will support code folding out of the box. Those languages * which do not support folding will ignore this property.<p> * This method fires a property change event of type * {@link #CODE_FOLDING_PROPERTY}. * * @param enabled Whether code folding should be enabled. * @see #isCodeFoldingEnabled() */ public void setCodeFoldingEnabled(boolean enabled) { if (enabled!=foldManager.isCodeFoldingEnabled()) { foldManager.setCodeFoldingEnabled(enabled); firePropertyChange(CODE_FOLDING_PROPERTY, !enabled, enabled); } } /** * Sets anti-aliasing to whatever the user's desktop value is. * * @see #getAntiAliasingEnabled() */ private final void setDefaultAntiAliasingState() { // Most accurate technique, but not available on all OSes. aaHints = RSyntaxUtilities.getDesktopAntiAliasHints(); if (aaHints==null) { Map<RenderingHints.Key, Object> temp = new HashMap<RenderingHints.Key, Object>(); // In Java 6+, you can figure out what text AA hint Swing uses for // JComponents... JLabel label = new JLabel(); FontMetrics fm = label.getFontMetrics(label.getFont()); Object hint = null; //FontRenderContext frc = fm.getFontRenderContext(); //hint = fm.getAntiAliasingHint(); try { Method m = FontMetrics.class.getMethod("getFontRenderContext"); FontRenderContext frc = (FontRenderContext)m.invoke(fm); m = FontRenderContext.class.getMethod("getAntiAliasingHint"); hint = m.invoke(frc); } catch (RuntimeException re) { throw re; // FindBugs } catch (Exception e) { // Swallow, either Java 1.5, or running in an applet } // If not running Java 6+, default to AA enabled on Windows where // the software AA is pretty fast, and default (e.g. disabled) on // non-Windows. Note that OS X always uses AA no matter what // rendering hints you give it, so this is a moot point there. //System.out.println("Rendering hint: " + hint); if (hint==null) { String os = System.getProperty("os.name").toLowerCase(); if (os.contains("windows")) { hint = RenderingHints.VALUE_TEXT_ANTIALIAS_ON; } else { hint = RenderingHints.VALUE_TEXT_ANTIALIAS_DEFAULT; } } temp.put(RenderingHints.KEY_TEXT_ANTIALIASING, hint); aaHints = temp; } // We must be connected to a screen resource for our graphics // to be non-null. if (isDisplayable()) { refreshFontMetrics(getGraphics2D(getGraphics())); } repaint(); } /** * Sets the document used by this text area. This is overridden so that * only instances of {@link RSyntaxDocument} are accepted; for all * others, an exception will be thrown. * * @param document The new document for this text area. * @throws IllegalArgumentException If the document is not an * <code>RSyntaxDocument</code>. */ @Override public void setDocument(Document document) { if (!(document instanceof RSyntaxDocument)) throw new IllegalArgumentException("Documents for " + "RSyntaxTextArea must be instances of " + "RSyntaxDocument!"); if (markOccurrencesSupport != null) { markOccurrencesSupport.clear(); } super.setDocument(document); setSyntaxEditingStyle(((RSyntaxDocument)document).getSyntaxStyle()); if (markOccurrencesSupport != null) { markOccurrencesSupport.doMarkOccurrences(); } } /** * Sets whether EOL markers are visible at the end of each line. This * method fires a property change of type {@link #EOL_VISIBLE_PROPERTY}. * * @param visible Whether EOL markers are visible. * @see #getEOLMarkersVisible() * @see #setWhitespaceVisible(boolean) */ public void setEOLMarkersVisible(boolean visible) { if (visible!=eolMarkersVisible) { eolMarkersVisible = visible; repaint(); firePropertyChange(EOL_VISIBLE_PROPERTY, !visible, visible); } } /** * Sets the font used by this text area. Note that if some token styles * are using a different font, they will not be changed by calling this * method. To set different fonts on individual token types, use the * text area's <code>SyntaxScheme</code>. * * @param font The font. * @see SyntaxScheme#getStyle(int) */ @Override public void setFont(Font font) { Font old = super.getFont(); super.setFont(font); // Do this first. // Usually programmers keep a single font for all token types, but // may use bold or italic for styling some. SyntaxScheme scheme = getSyntaxScheme(); if (scheme!=null && old!=null) { scheme.changeBaseFont(old, font); calculateLineHeight(); } // We must be connected to a screen resource for our // graphics to be non-null. if (isDisplayable()) { refreshFontMetrics(getGraphics2D(getGraphics())); // Updates the margin line. updateMarginLineX(); // Force the current line highlight to be repainted, even // though the caret's location hasn't changed. forceCurrentLineHighlightRepaint(); // Get line number border in text area to repaint again // since line heights have updated. firePropertyChange("font", old, font); // So parent JScrollPane will have its scrollbars updated. revalidate(); } } /** * Sets whether fractional font metrics are enabled. This method fires * a property change event of type {@link #FRACTIONAL_FONTMETRICS_PROPERTY}. * * @param enabled Whether fractional font metrics are enabled. * @see #getFractionalFontMetricsEnabled() */ public void setFractionalFontMetricsEnabled(boolean enabled) { if (fractionalFontMetricsEnabled!=enabled) { fractionalFontMetricsEnabled = enabled; // We must be connected to a screen resource for our graphics to be // non-null. if (isDisplayable()) { refreshFontMetrics(getGraphics2D(getGraphics())); } firePropertyChange(FRACTIONAL_FONTMETRICS_PROPERTY, !enabled, enabled); } } /** * Sets the highlighter used by this text area. * * @param h The highlighter. * @throws IllegalArgumentException If <code>h</code> is not an instance * of {@link RSyntaxTextAreaHighlighter}. */ @Override public void setHighlighter(Highlighter h) { if (!(h instanceof RSyntaxTextAreaHighlighter)) { throw new IllegalArgumentException("RSyntaxTextArea requires " + "an RSyntaxTextAreaHighlighter for its Highlighter"); } super.setHighlighter(h); } /** * Sets whether "secondary" languages should have their backgrounds * colored differently to visually differentiate them. This feature * imposes a fair performance penalty. This method fires a property change * event of type {@link #HIGHLIGHT_SECONDARY_LANGUAGES_PROPERTY}. * * @see #getHighlightSecondaryLanguages() * @see #setSecondaryLanguageBackground(int, Color) * @see #getSecondaryLanguageCount() */ public void setHighlightSecondaryLanguages(boolean highlight) { if (this.highlightSecondaryLanguages!=highlight) { highlightSecondaryLanguages = highlight; repaint(); firePropertyChange(HIGHLIGHT_SECONDARY_LANGUAGES_PROPERTY, !highlight, highlight); } } /** * Sets the color to use when painting hyperlinks. * * @param fg The color to use when painting hyperlinks. * @throws NullPointerException If <code>fg</code> is <code>null</code>. * @see #getHyperlinkForeground() * @see #setHyperlinksEnabled(boolean) */ public void setHyperlinkForeground(Color fg) { if (fg==null) { throw new NullPointerException("fg cannot be null"); } hyperlinkFG = fg; } /** * Sets whether hyperlinks are enabled for this text area. This method * fires a property change event of type * {@link #HYPERLINKS_ENABLED_PROPERTY}. * * @param enabled Whether hyperlinks are enabled. * @see #getHyperlinksEnabled() */ public void setHyperlinksEnabled(boolean enabled) { if (this.hyperlinksEnabled!=enabled) { this.hyperlinksEnabled = enabled; repaint(); firePropertyChange(HYPERLINKS_ENABLED_PROPERTY, !enabled, enabled); } } public void setLinkGenerator(LinkGenerator generator) { this.linkGenerator = generator; } /** * Sets the mask for the key used to toggle whether we are scanning for * hyperlinks with mouse hovering. The default value is * <code>CTRL_DOWN_MASK</code>. * * @param mask The mask to use. This should be some bitwise combination of * {@link InputEvent#CTRL_DOWN_MASK}, * {@link InputEvent#ALT_DOWN_MASK}, * {@link InputEvent#SHIFT_DOWN_MASK} or * {@link InputEvent#META_DOWN_MASK}. * For invalid values, behavior is undefined. * @see InputEvent */ public void setLinkScanningMask(int mask) { mask &= (InputEvent.CTRL_DOWN_MASK|InputEvent.META_DOWN_MASK| InputEvent.ALT_DOWN_MASK|InputEvent.SHIFT_DOWN_MASK); if (mask==0) { throw new IllegalArgumentException("mask argument should be " + "some combination of InputEvent.*_DOWN_MASK fields"); } linkScanningMask = mask; } /** * Toggles whether "mark occurrences" is enabled. This method fires a * property change event of type {@link #MARK_OCCURRENCES_PROPERTY}. * * @param markOccurrences Whether "Mark Occurrences" should be enabled. * @see #getMarkOccurrences() * @see #setMarkOccurrencesColor(Color) */ public void setMarkOccurrences(boolean markOccurrences) { if (markOccurrences) { if (markOccurrencesSupport==null) { markOccurrencesSupport = new MarkOccurrencesSupport(); markOccurrencesSupport.install(this); firePropertyChange(MARK_OCCURRENCES_PROPERTY, false, true); } } else { if (markOccurrencesSupport!=null) { markOccurrencesSupport.uninstall(); markOccurrencesSupport = null; firePropertyChange(MARK_OCCURRENCES_PROPERTY, true, false); } } } /** * Sets the "mark occurrences" color. * * @param color The new color. This cannot be <code>null</code>. * @see #getMarkOccurrencesColor() * @see #setMarkOccurrences(boolean) */ public void setMarkOccurrencesColor(Color color) { markOccurrencesColor = color; if (markOccurrencesSupport!=null) { markOccurrencesSupport.setColor(color); } } /** * Sets the color used as the background for a matched bracket. * * @param color The color to use. If this is <code>null</code>, then no * special background is painted behind a matched bracket. * @see #getMatchedBracketBGColor * @see #setMatchedBracketBorderColor * @see #setPaintMarkOccurrencesBorder(boolean) */ public void setMatchedBracketBGColor(Color color) { matchedBracketBGColor = color; if (match!=null) { repaint(); } } /** * Sets the color used as the border for a matched bracket. * * @param color The color to use. * @see #getMatchedBracketBorderColor * @see #setMatchedBracketBGColor */ public void setMatchedBracketBorderColor(Color color) { matchedBracketBorderColor = color; if (match!=null) { repaint(); } } /** * Toggles whether a border should be painted around marked occurrences. * * @param paintBorder Whether to paint a border. * @see #getPaintMarkOccurrencesBorder() * @see #setMarkOccurrencesColor(Color) * @see #setMarkOccurrences(boolean) */ public void setPaintMarkOccurrencesBorder(boolean paintBorder) { paintMarkOccurrencesBorder = paintBorder; if (markOccurrencesSupport!=null) { markOccurrencesSupport.setPaintBorder(paintBorder); } } /** * Sets whether the bracket at the caret position is painted as a "match" * when a matched bracket is found. Note that this property does nothing * if {@link #isBracketMatchingEnabled()} returns <code>false</code>.<p> * * This method fires a property change event of type * {@link #PAINT_MATCHED_BRACKET_PAIR_PROPERTY}. * * @param paintPair Whether both brackets in a bracket pair should be * highlighted when bracket matching is enabled. * @see #getPaintMatchedBracketPair() * @see #isBracketMatchingEnabled() * @see #setBracketMatchingEnabled(boolean) */ public void setPaintMatchedBracketPair(boolean paintPair) { if (paintPair!=paintMatchedBracketPair) { paintMatchedBracketPair = paintPair; doBracketMatching(); repaint(); firePropertyChange(PAINT_MATCHED_BRACKET_PAIR_PROPERTY, !paintMatchedBracketPair, paintMatchedBracketPair); } } /** * Toggles whether tab lines are painted. This method fires a property * change event of type {@link #TAB_LINES_PROPERTY}. * * @param paint Whether tab lines are painted. * @see #getPaintTabLines() * @see #setTabLineColor(Color) */ public void setPaintTabLines(boolean paint) { if (paint!=paintTabLines) { paintTabLines = paint; repaint(); firePropertyChange(TAB_LINES_PROPERTY, !paint, paint); } } /** * Sets the parser delay. This is the delay that must occur between edits * for any registered {@link Parser}s to run. * * @param millis The new parser delay, in milliseconds. This must be * greater than zero. * @see #getParserDelay() */ public void setParserDelay(int millis) { if (parserManager==null) { parserManager = new ParserManager(this); } parserManager.setDelay(millis); } /** * Applications typically have no need to modify this value.<p> * * Workaround for JTextComponents allowing the caret to be rendered * entirely off-screen if the entire "previous" character fit entirely. * * @param rhsCorrection The amount of space to add to the x-axis preferred * span. This should be non-negative. * @see #getRightHandSideCorrection() */ public void setRightHandSideCorrection(int rhsCorrection) { if (rhsCorrection<0) { throw new IllegalArgumentException("correction should be > 0"); } if (rhsCorrection!=this.rhsCorrection) { this.rhsCorrection = rhsCorrection; revalidate(); repaint(); } } /** * Sets the background color to use for a secondary language. * * @param index The language index. Note that these are 1-based, not * 0-based, and should be in the range * <code>1-getSecondaryLanguageCount()</code>, inclusive. * @param color The new color, or <code>null</code> for none. * @see #getSecondaryLanguageBackground(int) * @see #getSecondaryLanguageCount() */ public void setSecondaryLanguageBackground(int index, Color color) { index--; Color old = secondaryLanguageBackgrounds[index]; if ((color==null && old!=null) || (color!=null && !color.equals(old))) { secondaryLanguageBackgrounds[index] = color; if (getHighlightSecondaryLanguages()) { repaint(); } } } /** * Sets what type of syntax highlighting this editor is doing. This method * fires a property change of type {@link #SYNTAX_STYLE_PROPERTY}. * * @param styleKey The syntax editing style to use, for example, * {@link SyntaxConstants#SYNTAX_STYLE_NONE} or * {@link SyntaxConstants#SYNTAX_STYLE_JAVA}. * @see #getSyntaxEditingStyle() * @see SyntaxConstants */ public void setSyntaxEditingStyle(String styleKey) { if (styleKey==null) { styleKey = SYNTAX_STYLE_NONE; } if (!styleKey.equals(syntaxStyleKey)) { String oldStyle = syntaxStyleKey; syntaxStyleKey = styleKey; ((RSyntaxDocument)getDocument()).setSyntaxStyle(styleKey); firePropertyChange(SYNTAX_STYLE_PROPERTY, oldStyle, styleKey); setActiveLineRange(-1, -1); } } /** * Sets all of the colors used in syntax highlighting to the colors * specified. This uses a shallow copy of the color scheme so that * multiple text areas can share the same color scheme and have their * properties changed simultaneously.<p> * * This method fires a property change event of type * {@link #SYNTAX_SCHEME_PROPERTY}. * * @param scheme The instance of <code>SyntaxScheme</code> to use. * @see #getSyntaxScheme() */ public void setSyntaxScheme(SyntaxScheme scheme) { // NOTE: We don't check whether colorScheme is the same as the // current scheme because DecreaseFontSizeAction and // IncreaseFontSizeAction need it this way. // FIXME: Find a way around this. SyntaxScheme old = this.syntaxScheme; this.syntaxScheme = scheme; // Recalculate the line height. We do this here instead of in // refreshFontMetrics() as this method is called less often and we // don't need the rendering hints to get the font's height. calculateLineHeight(); if (isDisplayable()) { refreshFontMetrics(getGraphics2D(getGraphics())); } // Updates the margin line and "matched bracket" highlight updateMarginLineX(); lastBracketMatchPos = -1; doBracketMatching(); // Force the current line highlight to be repainted, even though // the caret's location hasn't changed. forceCurrentLineHighlightRepaint(); // So encompassing JScrollPane will have its scrollbars updated. revalidate(); firePropertyChange(SYNTAX_SCHEME_PROPERTY, old, this.syntaxScheme); } /** * If templates are enabled, all currently-known templates are forgotten * and all templates are loaded from all files in the specified directory * ending in "*.xml". If templates aren't enabled, nothing happens. * * @param dir The directory containing files ending in extension * <code>.xml</code> that contain templates to load. * @return <code>true</code> if the load was successful; * <code>false</code> if either templates aren't currently * enabled or the load failed somehow (most likely, the * directory doesn't exist). * @see #getTemplatesEnabled * @see #setTemplatesEnabled * @see #saveTemplates */ public static synchronized boolean setTemplateDirectory(String dir) { if (getTemplatesEnabled() && dir!=null) { File directory = new File(dir); if (directory.isDirectory()) { return getCodeTemplateManager(). setTemplateDirectory(directory)>-1; } boolean created = directory.mkdir(); if (created) { return getCodeTemplateManager(). setTemplateDirectory(directory)>-1; } } return false; } /** * Enables or disables templates.<p> * * Templates are a set of "shorthand identifiers" that you can configure * so that you only have to type a short identifier (such as "forb") to * insert a larger amount of code into the document (such as:<p> * * <pre> * for (<caret>) { * * } * </pre> * * Templates are a shared resource among all instances of * <code>RSyntaxTextArea</code>; that is, templates can only be * enabled/disabled for all text areas globally, not individually, and * all text areas have access of the same templates. This should not * be an issue; rather, it should be beneficial as it promotes * uniformity among all text areas in an application.<p> * * For more flexible boilerplate code insertion, consider using the * <a href="http://javadoc.fifesoft.com/autocomplete/org/fife/ui/autocomplete/TemplateCompletion.html">TemplateCompletion * class</a> in the * <a href="https://github.com/bobbylight/AutoComplete">AutoComplete * add-on library</a>. * * @param enabled Whether or not templates should be enabled. * @see #getTemplatesEnabled() */ public static synchronized void setTemplatesEnabled(boolean enabled) { templatesEnabled = enabled; } /** * Sets the color use to paint tab lines. This method fires a property * change event of type {@link #TAB_LINE_COLOR_PROPERTY}. * * @param c The color. If this value is <code>null</code>, the default * (gray) is used. * @see #getTabLineColor() * @see #setPaintTabLines(boolean) * @see #getPaintTabLines() */ public void setTabLineColor(Color c) { if (c==null) { c = Color.gray; } if (!c.equals(tabLineColor)) { Color old = tabLineColor; tabLineColor = c; if (getPaintTabLines()) { repaint(); } firePropertyChange(TAB_LINE_COLOR_PROPERTY, old, tabLineColor); } } /** * Sets whether "focusable" tool tips are used instead of standard ones. * Focusable tool tips are tool tips that the user can click on, * resize, copy from, and clink links in. This method fires a property * change event of type {@link #FOCUSABLE_TIPS_PROPERTY}. * * @param use Whether to use focusable tool tips. * @see #getUseFocusableTips() * @see FocusableTip */ public void setUseFocusableTips(boolean use) { if (use!=useFocusableTips) { useFocusableTips = use; firePropertyChange(FOCUSABLE_TIPS_PROPERTY, !use, use); } } /** * Sets whether selected text should use the "selected text color" property * (set via {@link #setSelectedTextColor(Color)}). This is the typical * behavior of text components. By default, RSyntaxTextArea does not do * this, so that token styles are visible even in selected regions of text. * This method fires a property change event of type * {@link #USE_SELECTED_TEXT_COLOR_PROPERTY}. * * @param use Whether to use the "selected text" color when painting text * in selected regions. * @see #getUseSelectedTextColor() */ public void setUseSelectedTextColor(boolean use) { if (use!=useSelectedTextColor) { useSelectedTextColor = use; firePropertyChange(USE_SELECTED_TEXT_COLOR_PROPERTY, !use, use); } } /** * Sets whether whitespace is visible. This method fires a property change * of type {@link #VISIBLE_WHITESPACE_PROPERTY}. * * @param visible Whether whitespace should be visible. * @see #isWhitespaceVisible() */ public void setWhitespaceVisible(boolean visible) { if (whitespaceVisible!=visible) { this.whitespaceVisible = visible; tokenPainter = visible ? new VisibleWhitespaceTokenPainter() : (TokenPainter)new DefaultTokenPainter(); repaint(); firePropertyChange(VISIBLE_WHITESPACE_PROPERTY, !visible, visible); } } /** * Resets the editor state after the user clicks on a hyperlink or releases * the hyperlink modifier. */ private void stopScanningForLinks() { if (isScanningForLinks) { Cursor c = getCursor(); isScanningForLinks = false; linkGeneratorResult = null; hoveredOverLinkOffset = -1; if (c!=null && c.getType()==Cursor.HAND_CURSOR) { setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR)); repaint(); // TODO: Repaint just the affected line. } } } /** * {@inheritDoc} */ @Override public void undoLastAction() { super.undoLastAction(); // Occasionally marked occurrences' Positions are in invalid states // due to how javax.swing.text.AbstractDocument tracks the start and // end offsets. This is usually not needed, but can be when the last // token in the Document is a marked occurrence, and an undo or redo // occurs which clears most of the document text. In that case it is // possible for the end Position to be reset to something small, but // the start offset to be its prior valid (start > end). ((RSyntaxTextAreaHighlighter)getHighlighter()). clearMarkOccurrencesHighlights(); } /** * Returns the token at the specified position in the view. * * @param p The position in the view. * @return The token, or <code>null</code> if no token is at that * position. * @see #modelToToken(int) */ /* * TODO: This is a little inefficient. This should convert view * coordinates to the underlying token (if any). The way things currently * are, we're calling getTokenListForLine() twice (once in viewToModel() * and once here). */ public Token viewToToken(Point p) { return modelToToken(viewToModel(p)); } /** * A timer that animates the "bracket matching" animation. */ private class BracketMatchingTimer extends Timer implements ActionListener { private int pulseCount; public BracketMatchingTimer() { super(20, null); addActionListener(this); setCoalesce(false); } public void actionPerformed(ActionEvent e) { if (isBracketMatchingEnabled()) { if (match!=null) { updateAndInvalidate(match); } if (dotRect!=null && getPaintMatchedBracketPair()) { updateAndInvalidate(dotRect); } if (++pulseCount==8) { pulseCount = 0; stop(); } } } private void init(Rectangle r) { r.x += 3; r.y += 3; r.width -= 6; r.height -= 6; // So animation can "grow" match } @Override public void start() { init(match); if (dotRect!=null && getPaintMatchedBracketPair()) { init(dotRect); } pulseCount = 0; super.start(); } private void updateAndInvalidate(Rectangle r) { if (pulseCount<5) { r.x--; r.y--; r.width += 2; r.height += 2; repaint(r.x,r.y, r.width,r.height); } else if (pulseCount<7) { r.x++; r.y++; r.width -= 2; r.height -= 2; repaint(r.x-2,r.y-2, r.width+5,r.height+5); } } } /** * Handles hyperlinks. */ private class RSyntaxTextAreaMutableCaretEvent extends RTextAreaMutableCaretEvent { private Insets insets; protected RSyntaxTextAreaMutableCaretEvent(RTextArea textArea) { super(textArea); insets = new Insets(0, 0, 0, 0); } private HyperlinkEvent createHyperlinkEvent() { HyperlinkEvent he = null; if (linkGeneratorResult!=null) { he = linkGeneratorResult.execute(); linkGeneratorResult = null; } else { Token t = modelToToken(hoveredOverLinkOffset); URL url = null; String desc = null; try { String temp = t.getLexeme(); // URI's need "http://" prefix for web URL's to work. if (temp.startsWith("www.")) { temp = "http://" + temp; } url = new URL(temp); } catch (MalformedURLException mue) { desc = mue.getMessage(); } he = new HyperlinkEvent(RSyntaxTextArea.this, HyperlinkEvent.EventType.ACTIVATED, url, desc); } return he; } private final boolean equal(LinkGeneratorResult e1, LinkGeneratorResult e2) { return e1.getSourceOffset()==e2.getSourceOffset(); } @Override public void mouseClicked(MouseEvent e) { if (getHyperlinksEnabled() && isScanningForLinks && hoveredOverLinkOffset>-1) { HyperlinkEvent he = createHyperlinkEvent(); if (he!=null) { fireHyperlinkUpdate(he); } stopScanningForLinks(); } } @Override public void mouseMoved(MouseEvent e) { super.mouseMoved(e); if (!getHyperlinksEnabled()) { return; } // If our link scanning mask is pressed... if ((e.getModifiersEx()&linkScanningMask)==linkScanningMask) { // GitHub issue #25 - links identified at "edges" of editor // should not be activated if mouse is in margin insets. insets = getInsets(insets); if (insets!=null) { int x = e.getX(); int y = e.getY(); if (x<=insets.left || y<insets.top) { if (isScanningForLinks) { stopScanningForLinks(); } return; } } isScanningForLinks = true; Token t = viewToToken(e.getPoint()); if (t!=null) { // Copy token, viewToModel() unfortunately modifies Token t = new TokenImpl(t); } Cursor c2 = null; if (t!=null && t.isHyperlink()) { if (hoveredOverLinkOffset==-1 || hoveredOverLinkOffset!=t.getOffset()) { hoveredOverLinkOffset = t.getOffset(); repaint(); } c2 = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR); } else if (t!=null && linkGenerator!=null) { int offs = viewToModel(e.getPoint()); LinkGeneratorResult newResult = linkGenerator. isLinkAtOffset(RSyntaxTextArea.this, offs); if (newResult!=null) { // Repaint if we're at a new link now. if (linkGeneratorResult==null || !equal(newResult, linkGeneratorResult)) { repaint(); } linkGeneratorResult = newResult; hoveredOverLinkOffset = t.getOffset(); c2 = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR); } else { // Repaint if we've moved off of a link. if (linkGeneratorResult!=null) { repaint(); } c2 = Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR); hoveredOverLinkOffset = -1; linkGeneratorResult = null; } } else { c2 = Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR); hoveredOverLinkOffset = -1; linkGeneratorResult = null; } if (getCursor()!=c2) { setCursor(c2); // TODO: Repaint just the affected line(s). repaint(); // Link either left or went into. } } else { if (isScanningForLinks) { stopScanningForLinks(); } } } } }