/*
* 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>
* <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>Delphi
* <li>DTD
* <li>Fortran
* <li>Groovy
* <li>HTML
* <li>htaccess
* <li>Java
* <li>JavaScript
* <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>Python
* <li>Ruby
* <li>SAS
* <li>Scala
* <li>SQL
* <li>Tcl
* <li>UNIX shell scripts
* <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.3
* @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);
}
/**
* 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 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];
}
/**
* 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);
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();
}
}
/**
* 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!");
super.setDocument(document);
}
/**
* 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) {
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.
}
}
}
/**
* 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();
}
}
}
}
}