mirror of
https://github.com/SuspiciousActivity/ByteEdit.git
synced 2026-08-13 17:56:17 +00:00
Added source for ByteEdit
This commit is contained in:
1 parent
32dacb30ff
commit
d29e5765af
352 files changed
+199016
No files matched your search
@@ -0,0 +1,288 @@
|
||||
/*
|
||||
* 01/25/2009
|
||||
*
|
||||
* AbstractJFlexCTokenMaker.java - Base class for token makers that use curly
|
||||
* braces to denote code blocks, such as C, C++, Java, Perl, etc.
|
||||
*
|
||||
* 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.event.ActionEvent;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import javax.swing.Action;
|
||||
import javax.swing.UIManager;
|
||||
import javax.swing.text.BadLocationException;
|
||||
|
||||
import org.fife.ui.rtextarea.RTextArea;
|
||||
|
||||
|
||||
/**
|
||||
* Base class for JFlex-based token makers using C-style syntax. This class
|
||||
* knows how to:
|
||||
*
|
||||
* <ul>
|
||||
* <li>Auto-indent after opening braces and parens
|
||||
* <li>Automatically close multi-line and documentation comments
|
||||
* </ul>
|
||||
*
|
||||
* @author Robert Futrell
|
||||
* @version 1.0
|
||||
*/
|
||||
public abstract class AbstractJFlexCTokenMaker extends AbstractJFlexTokenMaker {
|
||||
|
||||
private final Action INSERT_BREAK_ACTION;
|
||||
|
||||
private static final Pattern MLC_PATTERN =
|
||||
Pattern.compile("([ \\t]*)(/?[\\*]+)([ \\t]*)");
|
||||
|
||||
|
||||
protected AbstractJFlexCTokenMaker() {
|
||||
INSERT_BREAK_ACTION = createInsertBreakAction();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates and returns the action to use when the user inserts a newline.
|
||||
* The default implementation intelligently closes multi-line comments.
|
||||
* Subclasses can override.
|
||||
*
|
||||
* @return The action.
|
||||
* @see #getInsertBreakAction()
|
||||
*/
|
||||
protected Action createInsertBreakAction() {
|
||||
return new CStyleInsertBreakAction();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns <code>true</code> always as C-style languages use curly braces
|
||||
* to denote code blocks.
|
||||
*
|
||||
* @return <code>true</code> always.
|
||||
*/
|
||||
@Override
|
||||
public boolean getCurlyBracesDenoteCodeBlocks(int languageIndex) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns an action to handle "insert break" key presses (i.e. Enter).
|
||||
* An action is returned that handles newlines differently in multi-line
|
||||
* comments.
|
||||
*
|
||||
* @return The action.
|
||||
*/
|
||||
@Override
|
||||
public Action getInsertBreakAction() {
|
||||
return INSERT_BREAK_ACTION;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public boolean getMarkOccurrencesOfTokenType(int type) {
|
||||
return type==Token.IDENTIFIER || type==Token.FUNCTION;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public boolean getShouldIndentNextLineAfter(Token t) {
|
||||
if (t!=null && t.length()==1) {
|
||||
char ch = t.charAt(0);
|
||||
return ch=='{' || ch=='(';
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether a given token is an internal token type that represents
|
||||
* an MLC or documentation comment continuing on to the next line. This is
|
||||
* done by languages such as JavaScript that are a little more verbose
|
||||
* than necessary so that their code can be copy-and-pasted into other
|
||||
* <code>TokenMaker</code>s that use them as nested languages (such as
|
||||
* HTML, JSP, etc.).
|
||||
*
|
||||
* @param t The token to check. This cannot be <code>null</code>.
|
||||
* @return Whether the token is an internal token representing the end of
|
||||
* a line for an MLC/doc comment continuing on to the next line.
|
||||
*/
|
||||
private boolean isInternalEolTokenForMLCs(Token t) {
|
||||
int type = t.getType();
|
||||
if (type<0) {
|
||||
type = getClosestStandardTokenTypeForInternalType(type);
|
||||
return type==TokenTypes.COMMENT_MULTILINE ||
|
||||
type==TokenTypes.COMMENT_DOCUMENTATION;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Action that knows how to special-case inserting a newline in a
|
||||
* multi-line comment for languages like C and Java.
|
||||
*/
|
||||
protected class CStyleInsertBreakAction extends
|
||||
RSyntaxTextAreaEditorKit.InsertBreakAction {
|
||||
|
||||
@Override
|
||||
public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
|
||||
|
||||
if (!textArea.isEditable() || !textArea.isEnabled()) {
|
||||
UIManager.getLookAndFeel().provideErrorFeedback(textArea);
|
||||
return;
|
||||
}
|
||||
|
||||
RSyntaxTextArea rsta = (RSyntaxTextArea)getTextComponent(e);
|
||||
RSyntaxDocument doc = (RSyntaxDocument)rsta.getDocument();
|
||||
|
||||
int line = textArea.getCaretLineNumber();
|
||||
int type = doc.getLastTokenTypeOnLine(line);
|
||||
if (type<0) {
|
||||
type = doc.getClosestStandardTokenTypeForInternalType(type);
|
||||
}
|
||||
|
||||
// Only in MLC's should we try this
|
||||
if (type==Token.COMMENT_DOCUMENTATION ||
|
||||
type==Token.COMMENT_MULTILINE) {
|
||||
insertBreakInMLC(e, rsta, line);
|
||||
}
|
||||
else {
|
||||
handleInsertBreak(rsta, true);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether the MLC token containing <code>offs</code> appears
|
||||
* to have a "nested" comment (i.e., contains "<code>/*</code>"
|
||||
* somewhere inside of it). This implies that it is likely a "new" MLC
|
||||
* and needs to be closed. While not foolproof, this is usually good
|
||||
* enough of a sign.
|
||||
*
|
||||
* @param textArea
|
||||
* @param line
|
||||
* @param offs
|
||||
* @return Whether a comment appears to be nested inside this one.
|
||||
*/
|
||||
private boolean appearsNested(RSyntaxTextArea textArea,
|
||||
int line, int offs) {
|
||||
|
||||
final int firstLine = line; // Remember the line we start at.
|
||||
|
||||
while (line<textArea.getLineCount()) {
|
||||
Token t = textArea.getTokenListForLine(line);
|
||||
int i = 0;
|
||||
// If examining the first line, start at offs.
|
||||
if (line++==firstLine) {
|
||||
t = RSyntaxUtilities.getTokenAtOffset(t, offs);
|
||||
if (t==null) { // offs was at end of the line
|
||||
continue;
|
||||
}
|
||||
i = t.documentToToken(offs);
|
||||
}
|
||||
else {
|
||||
i = t.getTextOffset();
|
||||
}
|
||||
int textOffset = t.getTextOffset();
|
||||
while (i<textOffset+t.length()-1) {
|
||||
if (t.charAt(i-textOffset)=='/' && t.charAt(i-textOffset+1)=='*') {
|
||||
return true;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
// If tokens come after this one on this line, our MLC ended.
|
||||
if ((t=t.getNextToken())!=null && !isInternalEolTokenForMLCs(t)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true; // No match - MLC goes to the end of the file
|
||||
|
||||
}
|
||||
|
||||
private void insertBreakInMLC(ActionEvent e, RSyntaxTextArea textArea,
|
||||
int line) {
|
||||
|
||||
Matcher m = null;
|
||||
int start = -1;
|
||||
int end = -1;
|
||||
String text = null;
|
||||
try {
|
||||
start = textArea.getLineStartOffset(line);
|
||||
end = textArea.getLineEndOffset(line);
|
||||
text = textArea.getText(start, end-start);
|
||||
m = MLC_PATTERN.matcher(text);
|
||||
} catch (BadLocationException ble) { // Never happens
|
||||
UIManager.getLookAndFeel().provideErrorFeedback(textArea);
|
||||
ble.printStackTrace();
|
||||
return;
|
||||
}
|
||||
|
||||
if (m.lookingAt()) {
|
||||
|
||||
String leadingWS = m.group(1);
|
||||
String mlcMarker = m.group(2);
|
||||
|
||||
// If the caret is "inside" any leading whitespace or MLC
|
||||
// marker, move it to the end of the line.
|
||||
int dot = textArea.getCaretPosition();
|
||||
if (dot>=start &&
|
||||
dot<start+leadingWS.length()+mlcMarker.length()) {
|
||||
// If we're in the whitespace before the very start of the
|
||||
// MLC though, just insert a normal newline
|
||||
if (mlcMarker.charAt(0)=='/') {
|
||||
handleInsertBreak(textArea, true);
|
||||
return;
|
||||
}
|
||||
textArea.setCaretPosition(end-1);
|
||||
}
|
||||
else {
|
||||
// Ensure caret is at the "end" of any whitespace
|
||||
// immediately after the '*', but before any possible
|
||||
// non-whitespace chars.
|
||||
boolean moved = false;
|
||||
while (dot<end-1 &&
|
||||
Character.isWhitespace(text.charAt(dot-start))) {
|
||||
moved = true;
|
||||
dot++;
|
||||
}
|
||||
if (moved) {
|
||||
textArea.setCaretPosition(dot);
|
||||
}
|
||||
}
|
||||
|
||||
boolean firstMlcLine = mlcMarker.charAt(0)=='/';
|
||||
boolean nested = appearsNested(textArea, line,
|
||||
start+leadingWS.length()+2);
|
||||
String header = leadingWS +
|
||||
(firstMlcLine ? " * " : "*") +
|
||||
m.group(3);
|
||||
textArea.replaceSelection("\n" + header);
|
||||
if (nested) {
|
||||
dot = textArea.getCaretPosition(); // Has changed
|
||||
textArea.insert("\n" + leadingWS + " */", dot);
|
||||
textArea.setCaretPosition(dot);
|
||||
}
|
||||
|
||||
}
|
||||
else {
|
||||
handleInsertBreak(textArea, true);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* 03/23/2005
|
||||
*
|
||||
* AbstractJFlexTokenMaker.java - Base class for token makers generated from
|
||||
* programs such as JFlex.
|
||||
*
|
||||
* This library is distributed under a modified BSD license. See the included
|
||||
* RSyntaxTextArea.License.txt file for details.
|
||||
*/
|
||||
package org.fife.ui.rsyntaxtextarea;
|
||||
|
||||
import javax.swing.text.Segment;
|
||||
|
||||
|
||||
/**
|
||||
* Base class for JFlex-generated token makers. This class attempts to factor
|
||||
* out all common code from these classes. Many methods <em>almost</em> could
|
||||
* be factored out into this class, but cannot because they reference JFlex
|
||||
* variables that we cannot access from this class.
|
||||
*
|
||||
* @author Robert Futrell
|
||||
* @version 0.1
|
||||
*/
|
||||
public abstract class AbstractJFlexTokenMaker extends TokenMakerBase {
|
||||
|
||||
protected Segment s;
|
||||
|
||||
protected int start; // Just for states.
|
||||
protected int offsetShift; // As parser always starts at 0, but our line doesn't.
|
||||
|
||||
|
||||
/**
|
||||
* Declared here so we can define overloads that refer to this method.
|
||||
*
|
||||
* @param newState The new JFlex state to enter.
|
||||
*/
|
||||
public abstract void yybegin(int newState);
|
||||
|
||||
|
||||
/**
|
||||
* Starts a new JFlex state and changes the current language index.
|
||||
*
|
||||
* @param state The new JFlex state to enter.
|
||||
* @param languageIndex The new language index.
|
||||
*/
|
||||
protected void yybegin(int state, int languageIndex) {
|
||||
yybegin(state);
|
||||
setLanguageIndex(languageIndex);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* 11/07/2004
|
||||
*
|
||||
* AbstractTokenMaker.java - An abstract implementation of TokenMaker.
|
||||
*
|
||||
* This library is distributed under a modified BSD license. See the included
|
||||
* RSyntaxTextArea.License.txt file for details.
|
||||
*/
|
||||
package org.fife.ui.rsyntaxtextarea;
|
||||
|
||||
|
||||
/**
|
||||
* An abstract implementation of the
|
||||
* {@link org.fife.ui.rsyntaxtextarea.TokenMaker} interface. It should
|
||||
* be overridden for every language for which you want to provide
|
||||
* syntax highlighting.<p>
|
||||
*
|
||||
* @see Token
|
||||
*
|
||||
* @author Robert Futrell
|
||||
* @version 0.2
|
||||
*/
|
||||
public abstract class AbstractTokenMaker extends TokenMakerBase {
|
||||
|
||||
/**
|
||||
* Hash table of words to highlight and what token type they are.
|
||||
* The keys are the words to highlight, and their values are the
|
||||
* token types, for example, <code>Token.RESERVED_WORD</code> or
|
||||
* <code>Token.FUNCTION</code>.
|
||||
*/
|
||||
protected TokenMap wordsToHighlight;
|
||||
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public AbstractTokenMaker() {
|
||||
wordsToHighlight = getWordsToHighlight();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the words to highlight for this programming language.
|
||||
*
|
||||
* @return A <code>TokenMap</code> containing the words to highlight for
|
||||
* this programming language.
|
||||
*/
|
||||
public abstract TokenMap getWordsToHighlight();
|
||||
|
||||
|
||||
/**
|
||||
* Removes the token last added from the linked list of tokens. The
|
||||
* programmer should never have to call this directly; it can be called
|
||||
* by subclasses of <code>TokenMaker</code> if necessary.
|
||||
*/
|
||||
public void removeLastToken() {
|
||||
if (previousToken==null) {
|
||||
firstToken = currentToken = null;
|
||||
}
|
||||
else {
|
||||
currentToken = previousToken;
|
||||
currentToken.setNextToken(null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
* 12/14/08
|
||||
*
|
||||
* AbstractTokenMakerFactory.java - Base class for TokenMaker implementations.
|
||||
*
|
||||
* 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.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
|
||||
/**
|
||||
* Base class for {@link TokenMakerFactory} implementations. A mapping from
|
||||
* language keys to the names of {@link TokenMaker} classes is stored.
|
||||
*
|
||||
* @author Robert Futrell
|
||||
* @version 1.0
|
||||
*/
|
||||
public abstract class AbstractTokenMakerFactory extends TokenMakerFactory {
|
||||
|
||||
/**
|
||||
* A mapping from keys to the names of {@link TokenMaker} implementation
|
||||
* class names. When {@link #getTokenMaker(String)} is called with a key
|
||||
* defined in this map, a <code>TokenMaker</code> of the corresponding type
|
||||
* is returned.
|
||||
*/
|
||||
private Map<String, Object> tokenMakerMap;
|
||||
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
protected AbstractTokenMakerFactory() {
|
||||
tokenMakerMap = new HashMap<String, Object>();
|
||||
initTokenMakerMap();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a {@link TokenMaker} for the specified key.
|
||||
*
|
||||
* @param key The key.
|
||||
* @return The corresponding <code>TokenMaker</code>, or <code>null</code>
|
||||
* if none matches the specified key.
|
||||
*/
|
||||
@Override
|
||||
protected TokenMaker getTokenMakerImpl(String key) {
|
||||
TokenMakerCreator tmc = (TokenMakerCreator)tokenMakerMap.get(key);
|
||||
if (tmc!=null) {
|
||||
try {
|
||||
return tmc.create();
|
||||
} catch (RuntimeException re) { // FindBugs
|
||||
throw re;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Populates the mapping from keys to instances of
|
||||
* <code>TokenMakerCreator</code>s. Subclasses should override this method
|
||||
* and call one of the <code>putMapping</code> overloads to register
|
||||
* {@link TokenMaker}s for syntax constants.
|
||||
*
|
||||
* @see #putMapping(String, String)
|
||||
* @see #putMapping(String, String, ClassLoader)
|
||||
*/
|
||||
protected abstract void initTokenMakerMap();
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public Set<String> keySet() {
|
||||
return tokenMakerMap.keySet();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds a mapping from a key to a <code>TokenMaker</code> implementation
|
||||
* class name.
|
||||
*
|
||||
* @param key The key.
|
||||
* @param className The <code>TokenMaker</code> class name.
|
||||
* @see #putMapping(String, String, ClassLoader)
|
||||
*/
|
||||
public void putMapping(String key, String className) {
|
||||
putMapping(key, className, null);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds a mapping from a key to a <code>TokenMaker</code> implementation
|
||||
* class name.
|
||||
*
|
||||
* @param key The key.
|
||||
* @param className The <code>TokenMaker</code> class name.
|
||||
* @param cl The class loader to use when loading the class.
|
||||
* @see #putMapping(String, String)
|
||||
*/
|
||||
public void putMapping(String key, String className, ClassLoader cl) {
|
||||
tokenMakerMap.put(key, new TokenMakerCreator(className, cl));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Wrapper that handles the creation of TokenMaker instances.
|
||||
*/
|
||||
private static class TokenMakerCreator {
|
||||
|
||||
private String className;
|
||||
private ClassLoader cl;
|
||||
|
||||
public TokenMakerCreator(String className, ClassLoader cl) {
|
||||
this.className = className;
|
||||
this.cl = cl!=null ? cl : getClass().getClassLoader();
|
||||
}
|
||||
|
||||
public TokenMaker create() throws Exception {
|
||||
return (TokenMaker)Class.forName(className, true, cl).newInstance();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* 02/06/2011
|
||||
*
|
||||
* ActiveLineRangeEvent.java - Notifies listeners of an "active line range"
|
||||
* change in an RSyntaxTextArea.
|
||||
*
|
||||
* This library is distributed under a modified BSD license. See the included
|
||||
* RSyntaxTextArea.License.txt file for details.
|
||||
*/
|
||||
package org.fife.ui.rsyntaxtextarea;
|
||||
|
||||
import java.util.EventObject;
|
||||
|
||||
|
||||
/**
|
||||
* The event fired by {@link RSyntaxTextArea}s when the active line range
|
||||
* changes.
|
||||
*
|
||||
* @author Robert Futrell
|
||||
* @version 1.0
|
||||
*/
|
||||
public class ActiveLineRangeEvent extends EventObject {
|
||||
|
||||
private int min;
|
||||
private int max;
|
||||
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param source The text area.
|
||||
* @param min The first line in the active line range, or
|
||||
* <code>-1</code> if the line range is being cleared.
|
||||
* @param max The last line in the active line range, or
|
||||
* <code>-1</code> if the line range is being cleared.
|
||||
*/
|
||||
public ActiveLineRangeEvent(RSyntaxTextArea source, int min, int max) {
|
||||
super(source);
|
||||
this.min = min;
|
||||
this.max = max;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the last line in the active line range.
|
||||
*
|
||||
* @return The last line, or <code>-1</code> if the range is being
|
||||
* cleared.
|
||||
* @see #getMin()
|
||||
*/
|
||||
public int getMax() {
|
||||
return max;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the first line in the active line range.
|
||||
*
|
||||
* @return The first line, or <code>-1</code> if the range is being
|
||||
* cleared.
|
||||
* @see #getMax()
|
||||
*/
|
||||
public int getMin() {
|
||||
return min;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* 02/06/2011
|
||||
*
|
||||
* ActiveLineRangeListener.java - Listens for "active line range" changes
|
||||
* in an RSyntaxTextArea.
|
||||
*
|
||||
* This library is distributed under a modified BSD license. See the included
|
||||
* RSyntaxTextArea.License.txt file for details.
|
||||
*/
|
||||
package org.fife.ui.rsyntaxtextarea;
|
||||
|
||||
import java.util.EventListener;
|
||||
|
||||
|
||||
/**
|
||||
* Listens for "active line range" events from an <code>RSyntaxTextArea</code>.
|
||||
* If a text area contains some semantic knowledge of the programming language
|
||||
* being edited, it may broadcast {@link ActiveLineRangeEvent}s whenever the
|
||||
* caret moves into a new "block" of code. Listeners can listen for these
|
||||
* events and respond accordingly.<p>
|
||||
*
|
||||
* See the <code>RSTALanguageSupport</code> project at
|
||||
* <a href="http://fifesoft.com">http://fifesoft.com</a> for some
|
||||
* <code>LanguageSupport</code> implementations that may broadcast these
|
||||
* events. Note that if an RSTA/LanguageSupport does not support broadcasting
|
||||
* these events, the listener will simply never receive any notifications.
|
||||
*
|
||||
* @author Robert Futrell
|
||||
* @version 1.0
|
||||
*/
|
||||
public interface ActiveLineRangeListener extends EventListener {
|
||||
|
||||
|
||||
/**
|
||||
* Called whenever the "active line range" changes.
|
||||
*
|
||||
* @param e Information about the line range change. If there is no longer
|
||||
* an "active line range," the "minimum" and "maximum" line values
|
||||
* should both be <code>-1</code>.
|
||||
*/
|
||||
void activeLineRangeChanged(ActiveLineRangeEvent e);
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,401 @@
|
||||
/*
|
||||
* 02/21/2005
|
||||
*
|
||||
* CodeTemplateManager.java - manages code templates.
|
||||
*
|
||||
* 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.beans.XMLDecoder;
|
||||
import java.beans.XMLEncoder;
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileFilter;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import javax.swing.text.BadLocationException;
|
||||
import javax.swing.text.Document;
|
||||
import javax.swing.text.Segment;
|
||||
|
||||
import org.fife.ui.rsyntaxtextarea.templates.CodeTemplate;
|
||||
|
||||
|
||||
/**
|
||||
* Manages "code templates."<p>
|
||||
*
|
||||
* All methods in this class are synchronized for thread safety, but as a
|
||||
* best practice, you should probably only modify the templates known to a
|
||||
* <code>CodeTemplateManager</code> on the EDT. Modifying a
|
||||
* <code>CodeTemplate</code> retrieved from a <code>CodeTemplateManager</code>
|
||||
* while <em>not</em> on the EDT could cause problems.<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>.
|
||||
*
|
||||
* @author Robert Futrell
|
||||
* @version 1.0
|
||||
*/
|
||||
public class CodeTemplateManager {
|
||||
|
||||
private int maxTemplateIDLength;
|
||||
private List<CodeTemplate> templates;
|
||||
|
||||
private Segment s;
|
||||
private TemplateComparator comparator;
|
||||
private File directory;
|
||||
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public CodeTemplateManager() {
|
||||
s = new Segment();
|
||||
comparator = new TemplateComparator();
|
||||
templates = new ArrayList<CodeTemplate>();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Registers the specified template with this template manager.
|
||||
*
|
||||
* @param template The template to register.
|
||||
* @throws IllegalArgumentException If <code>template</code> is
|
||||
* <code>null</code>.
|
||||
* @see #removeTemplate(CodeTemplate)
|
||||
* @see #removeTemplate(String)
|
||||
*/
|
||||
public synchronized void addTemplate(CodeTemplate template) {
|
||||
if (template==null) {
|
||||
throw new IllegalArgumentException("template cannot be null");
|
||||
}
|
||||
templates.add(template);
|
||||
sortTemplates();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the template that should be inserted at the current caret
|
||||
* position, assuming the trigger character was pressed.
|
||||
*
|
||||
* @param textArea The text area that's getting text inserted into it.
|
||||
* @return A template that should be inserted, if appropriate, or
|
||||
* <code>null</code> if no template should be inserted.
|
||||
*/
|
||||
public synchronized CodeTemplate getTemplate(RSyntaxTextArea textArea) {
|
||||
int caretPos = textArea.getCaretPosition();
|
||||
int charsToGet = Math.min(caretPos, maxTemplateIDLength);
|
||||
try {
|
||||
Document doc = textArea.getDocument();
|
||||
doc.getText(caretPos-charsToGet, charsToGet, s);
|
||||
@SuppressWarnings("unchecked")
|
||||
int index = Collections.binarySearch(templates, s, comparator);
|
||||
return index>=0 ? (CodeTemplate)templates.get(index) : null;
|
||||
} catch (BadLocationException ble) {
|
||||
ble.printStackTrace();
|
||||
throw new InternalError("Error in CodeTemplateManager");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the number of templates this manager knows about.
|
||||
*
|
||||
* @return The template count.
|
||||
*/
|
||||
public synchronized int getTemplateCount() {
|
||||
return templates.size();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the templates currently available.
|
||||
*
|
||||
* @return The templates available.
|
||||
*/
|
||||
public synchronized CodeTemplate[] getTemplates() {
|
||||
CodeTemplate[] temp = new CodeTemplate[templates.size()];
|
||||
return templates.toArray(temp);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether the specified character is a valid character for a
|
||||
* <code>CodeTemplate</code> id.
|
||||
*
|
||||
* @param ch The character to check.
|
||||
* @return Whether the character is a valid template character.
|
||||
*/
|
||||
public static final boolean isValidChar(char ch) {
|
||||
return RSyntaxUtilities.isLetterOrDigit(ch) || ch=='_';
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the specified code template.
|
||||
*
|
||||
* @param template The template to remove.
|
||||
* @return <code>true</code> if the template was removed, <code>false</code>
|
||||
* if the template was not in this template manager.
|
||||
* @throws IllegalArgumentException If <code>template</code> is
|
||||
* <code>null</code>.
|
||||
* @see #removeTemplate(String)
|
||||
* @see #addTemplate(CodeTemplate)
|
||||
*/
|
||||
public synchronized boolean removeTemplate(CodeTemplate template) {
|
||||
|
||||
if (template==null) {
|
||||
throw new IllegalArgumentException("template cannot be null");
|
||||
}
|
||||
|
||||
// TODO: Do a binary search
|
||||
return templates.remove(template);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the code template with the specified id.
|
||||
*
|
||||
* @param id The id to check for.
|
||||
* @return The code template that was removed, or <code>null</code> if
|
||||
* there was no template with the specified ID.
|
||||
* @throws IllegalArgumentException If <code>id</code> is <code>null</code>.
|
||||
* @see #removeTemplate(CodeTemplate)
|
||||
* @see #addTemplate(CodeTemplate)
|
||||
*/
|
||||
public synchronized CodeTemplate removeTemplate(String id) {
|
||||
|
||||
if (id==null) {
|
||||
throw new IllegalArgumentException("id cannot be null");
|
||||
}
|
||||
|
||||
// TODO: Do a binary search
|
||||
for (Iterator<CodeTemplate> i=templates.iterator(); i.hasNext();) {
|
||||
CodeTemplate template = i.next();
|
||||
if (id.equals(template.getID())) {
|
||||
i.remove();
|
||||
return template;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Replaces the current set of available templates with the ones
|
||||
* specified.
|
||||
*
|
||||
* @param newTemplates The new set of templates. Note that we will
|
||||
* be taking a shallow copy of these and sorting them.
|
||||
*/
|
||||
public synchronized void replaceTemplates(CodeTemplate[] newTemplates) {
|
||||
templates.clear();
|
||||
if (newTemplates!=null) {
|
||||
for (int i=0; i<newTemplates.length; i++) {
|
||||
templates.add(newTemplates[i]);
|
||||
}
|
||||
}
|
||||
sortTemplates(); // Also recomputes maxTemplateIDLength.
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Saves all templates as XML files in the current template directory.
|
||||
*
|
||||
* @return Whether or not the save was successful.
|
||||
*/
|
||||
public synchronized boolean saveTemplates() {
|
||||
|
||||
if (templates==null) {
|
||||
return true;
|
||||
}
|
||||
if (directory==null || !directory.isDirectory()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Blow away all old XML files to start anew, as some might be from
|
||||
// templates we're removed from the template manager.
|
||||
File[] oldXMLFiles = directory.listFiles(new XMLFileFilter());
|
||||
if (oldXMLFiles==null) {
|
||||
return false; // Either an IOException or it isn't a directory.
|
||||
}
|
||||
int count = oldXMLFiles.length;
|
||||
for (int i=0; i<count; i++) {
|
||||
/*boolean deleted = */oldXMLFiles[i].delete();
|
||||
}
|
||||
|
||||
// Save all current templates as XML.
|
||||
boolean wasSuccessful = true;
|
||||
for (CodeTemplate template : templates) {
|
||||
File xmlFile = new File(directory, template.getID() + ".xml");
|
||||
try {
|
||||
XMLEncoder e = new XMLEncoder(new BufferedOutputStream(
|
||||
new FileOutputStream(xmlFile)));
|
||||
e.writeObject(template);
|
||||
e.close();
|
||||
} catch (IOException ioe) {
|
||||
ioe.printStackTrace();
|
||||
wasSuccessful = false;
|
||||
}
|
||||
}
|
||||
|
||||
return wasSuccessful;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the directory in which to look for templates. Calling this
|
||||
* method adds any new templates found in the specified directory to
|
||||
* the templates already registered.
|
||||
*
|
||||
* @param dir The new directory in which to look for templates.
|
||||
* @return The new number of templates in this template manager, or
|
||||
* <code>-1</code> if the specified directory does not exist.
|
||||
*/
|
||||
public synchronized int setTemplateDirectory(File dir) {
|
||||
|
||||
if (dir!=null && dir.isDirectory()) {
|
||||
|
||||
this.directory = dir;
|
||||
|
||||
File[] files = dir.listFiles(new XMLFileFilter());
|
||||
int newCount = files==null ? 0 : files.length;
|
||||
int oldCount = templates.size();
|
||||
|
||||
List<CodeTemplate> temp =
|
||||
new ArrayList<CodeTemplate>(oldCount+newCount);
|
||||
temp.addAll(templates);
|
||||
|
||||
for (int i=0; i<newCount; i++) {
|
||||
try {
|
||||
XMLDecoder d = new XMLDecoder(new BufferedInputStream(
|
||||
new FileInputStream(files[i])));
|
||||
Object obj = d.readObject();
|
||||
if (!(obj instanceof CodeTemplate)) {
|
||||
d.close();
|
||||
throw new IOException("Not a CodeTemplate: " +
|
||||
files[i].getAbsolutePath());
|
||||
}
|
||||
temp.add((CodeTemplate)obj);
|
||||
d.close();
|
||||
} catch (/*IO, NoSuchElement*/Exception e) {
|
||||
// NoSuchElementException can be thrown when reading
|
||||
// an XML file not in the format expected by XMLDecoder.
|
||||
// (e.g. CodeTemplates in an old format).
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
templates = temp;
|
||||
sortTemplates();
|
||||
|
||||
return getTemplateCount();
|
||||
|
||||
}
|
||||
|
||||
return -1;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Removes any null entries in the current set of templates (if
|
||||
* any), sorts the remaining templates, and computes the new
|
||||
* maximum template ID length.
|
||||
*/
|
||||
private synchronized void sortTemplates() {
|
||||
|
||||
// Get the maximum length of a template ID.
|
||||
maxTemplateIDLength = 0;
|
||||
|
||||
// Remove any null entries (should only happen because of
|
||||
// IOExceptions, etc. when loading from files), and sort
|
||||
// the remaining list.
|
||||
for (Iterator<CodeTemplate> i=templates.iterator(); i.hasNext();) {
|
||||
CodeTemplate temp = i.next();
|
||||
if (temp==null || temp.getID()==null) {
|
||||
i.remove();
|
||||
}
|
||||
else {
|
||||
maxTemplateIDLength = Math.max(maxTemplateIDLength,
|
||||
temp.getID().length());
|
||||
}
|
||||
}
|
||||
|
||||
Collections.sort(templates);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* A comparator that takes a <code>CodeTemplate</code> as its first
|
||||
* parameter and a <code>Segment</code> as its second, and knows
|
||||
* to compare the template's ID to the segment's text.
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
private static class TemplateComparator implements Comparator, Serializable{
|
||||
|
||||
@Override
|
||||
public int compare(Object template, Object segment) {
|
||||
|
||||
// Get template start index (0) and length.
|
||||
CodeTemplate t = (CodeTemplate)template;
|
||||
final char[] templateArray = t.getID().toCharArray();
|
||||
int i = 0;
|
||||
int len1 = templateArray.length;
|
||||
|
||||
// Find "token" part of segment and get its offset and length.
|
||||
Segment s = (Segment)segment;
|
||||
char[] segArray = s.array;
|
||||
int len2 = s.count;
|
||||
int j = s.offset + len2 - 1;
|
||||
while (j>=s.offset && isValidChar(segArray[j])) {
|
||||
j--;
|
||||
}
|
||||
j++;
|
||||
int segShift = j - s.offset;
|
||||
len2 -= segShift;
|
||||
|
||||
int n = Math.min(len1, len2);
|
||||
while (n-- != 0) {
|
||||
char c1 = templateArray[i++];
|
||||
char c2 = segArray[j++];
|
||||
if (c1 != c2) {
|
||||
return c1 - c2;
|
||||
}
|
||||
}
|
||||
return len1 - len2;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* A file filter that accepts only XML files.
|
||||
*/
|
||||
private static class XMLFileFilter implements FileFilter {
|
||||
@Override
|
||||
public boolean accept(File f) {
|
||||
return f.getName().toLowerCase().endsWith(".xml");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* 03/09/2013
|
||||
*
|
||||
* DefaultOccurrenceMarker - Marks occurrences of the current token for most
|
||||
* 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 javax.swing.text.BadLocationException;
|
||||
import javax.swing.text.Caret;
|
||||
|
||||
import org.fife.ui.rtextarea.SmartHighlightPainter;
|
||||
|
||||
|
||||
/**
|
||||
* The default implementation of {@link OccurrenceMarker}. It goes through
|
||||
* the document and marks all instances of the specified token.
|
||||
*
|
||||
* @author Robert Futrell
|
||||
* @version 1.0
|
||||
*/
|
||||
class DefaultOccurrenceMarker implements OccurrenceMarker {
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public Token getTokenToMark(RSyntaxTextArea textArea) {
|
||||
|
||||
// Get the token at the caret position.
|
||||
int line = textArea.getCaretLineNumber();
|
||||
Token tokenList = textArea.getTokenListForLine(line);
|
||||
Caret c = textArea.getCaret();
|
||||
int dot = c.getDot();
|
||||
|
||||
Token t = RSyntaxUtilities.getTokenAtOffset(tokenList, dot);
|
||||
if (t==null /* EOL */ || !isValidType(textArea, t) ||
|
||||
RSyntaxUtilities.isNonWordChar(t)) {
|
||||
// Try to the "left" of the caret.
|
||||
dot--;
|
||||
try {
|
||||
if (dot>=textArea.getLineStartOffset(line)) {
|
||||
t = RSyntaxUtilities.getTokenAtOffset(tokenList, dot);
|
||||
}
|
||||
} catch (BadLocationException ble) {
|
||||
ble.printStackTrace(); // Never happens
|
||||
}
|
||||
}
|
||||
|
||||
return t;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public boolean isValidType(RSyntaxTextArea textArea, Token t) {
|
||||
return textArea.getMarkOccurrencesOfTokenType(t.getType());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void markOccurrences(RSyntaxDocument doc, Token t,
|
||||
RSyntaxTextAreaHighlighter h, SmartHighlightPainter p) {
|
||||
markOccurrencesOfToken(doc, t, h, p);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Highlights all instances of tokens identical to <code>t</code> in the
|
||||
* specified document.
|
||||
*
|
||||
* @param doc The document.
|
||||
* @param t The document whose relevant occurrences should be marked.
|
||||
* @param h The highlighter to add the highlights to.
|
||||
* @param p The painter for the highlights.
|
||||
*/
|
||||
public static final void markOccurrencesOfToken(RSyntaxDocument doc,
|
||||
Token t, RSyntaxTextAreaHighlighter h, SmartHighlightPainter p) {
|
||||
|
||||
char[] lexeme = t.getLexeme().toCharArray();
|
||||
int type = t.getType();
|
||||
int lineCount = doc.getDefaultRootElement().getElementCount();
|
||||
|
||||
for (int i=0; i<lineCount; i++) {
|
||||
Token temp = doc.getTokenListForLine(i);
|
||||
while (temp!=null && temp.isPaintable()) {
|
||||
if (temp.is(type, lexeme)) {
|
||||
try {
|
||||
int end = temp.getEndOffset();
|
||||
h.addMarkedOccurrenceHighlight(temp.getOffset(),end,p);
|
||||
} catch (BadLocationException ble) {
|
||||
ble.printStackTrace(); // Never happens
|
||||
}
|
||||
}
|
||||
temp = temp.getNextToken();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
* 10/28/2004
|
||||
*
|
||||
* DefaultTokenFactory.java - Default token factory.
|
||||
*
|
||||
* This library is distributed under a modified BSD license. See the included
|
||||
* RSyntaxTextArea.License.txt file for details.
|
||||
*/
|
||||
package org.fife.ui.rsyntaxtextarea;
|
||||
|
||||
import javax.swing.text.Segment;
|
||||
|
||||
|
||||
/**
|
||||
* This class generates tokens for a {@link TokenMaker}. This class is here
|
||||
* because it reuses tokens when they aren't needed anymore to prevent
|
||||
* This class doesn't actually create new tokens every time
|
||||
* <code>createToken</code> is called. Instead, it internally keeps a stack of
|
||||
* available already-created tokens. When more tokens are needed to properly
|
||||
* display a line, more tokens are added to the available stack. This saves
|
||||
* from needless repetitive memory allocation. However, it makes it IMPERATIVE
|
||||
* that users call <code>resetTokenList</code> when creating a new token list so
|
||||
* that the token maker can keep an accurate list of available tokens.<p>
|
||||
*
|
||||
* NOTE: This class should only be used by {@link TokenMaker}; nobody else
|
||||
* needs it!
|
||||
*
|
||||
* @author Robert Futrell
|
||||
* @version 0.1
|
||||
*/
|
||||
class DefaultTokenFactory implements TokenFactory {
|
||||
|
||||
private int size;
|
||||
private int increment;
|
||||
private TokenImpl[] tokenList;
|
||||
private int currentFreeToken;
|
||||
|
||||
protected static final int DEFAULT_START_SIZE = 30;
|
||||
protected static final int DEFAULT_INCREMENT = 10;
|
||||
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
DefaultTokenFactory() {
|
||||
this(DEFAULT_START_SIZE, DEFAULT_INCREMENT);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param size The initial number of tokens in this factory.
|
||||
* @param increment How many tokens to increment by when the stack gets
|
||||
* empty.
|
||||
*/
|
||||
DefaultTokenFactory(int size, int increment) {
|
||||
|
||||
this.size = size;
|
||||
this.increment = increment;
|
||||
this.currentFreeToken = 0;
|
||||
|
||||
// Give us some tokens to initially work with.
|
||||
tokenList = new TokenImpl[size];
|
||||
for (int i=0; i<size; i++) {
|
||||
tokenList[i] = new TokenImpl();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds tokens to the internal token list. This is called whenever a
|
||||
* request is made and no more tokens are available.
|
||||
*/
|
||||
private void augmentTokenList() {
|
||||
TokenImpl[] temp = new TokenImpl[size + increment];
|
||||
System.arraycopy(tokenList,0, temp,0, size);
|
||||
size += increment;
|
||||
tokenList = temp;
|
||||
for (int i=0; i<increment; i++) {
|
||||
tokenList[size-i-1] = new TokenImpl();
|
||||
}
|
||||
//System.err.println("... size up to: " + size);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public TokenImpl createToken() {
|
||||
TokenImpl token = tokenList[currentFreeToken];
|
||||
token.text = null;
|
||||
token.setType(Token.NULL);
|
||||
token.setOffset(-1);
|
||||
token.setNextToken(null);
|
||||
currentFreeToken++;
|
||||
if (currentFreeToken==size) {
|
||||
augmentTokenList();
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public TokenImpl createToken(final Segment line, final int beg,
|
||||
final int end, final int startOffset, final int type) {
|
||||
return createToken(line.array, beg,end, startOffset, type);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public TokenImpl createToken(final char[] line, final int beg,
|
||||
final int end, final int startOffset, final int type) {
|
||||
TokenImpl token = tokenList[currentFreeToken];
|
||||
token.set(line, beg,end, startOffset, type);
|
||||
currentFreeToken++;
|
||||
if (currentFreeToken==size) {
|
||||
augmentTokenList();
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Resets the state of this token maker. This method should be called
|
||||
* by the <code>TokenMaker</code> every time a token list is generated for
|
||||
* a new line so the tokens can be reused.
|
||||
*/
|
||||
@Override
|
||||
public void resetAllTokens() {
|
||||
currentFreeToken = 0;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* 12/14/2008
|
||||
*
|
||||
* DefaultTokenMakerFactory.java - The default TokenMaker factory.
|
||||
*
|
||||
* This library is distributed under a modified BSD license. See the included
|
||||
* RSyntaxTextArea.License.txt file for details.
|
||||
*/
|
||||
package org.fife.ui.rsyntaxtextarea;
|
||||
|
||||
|
||||
/**
|
||||
* The default implementation of <code>TokenMakerFactory</code>. This factory
|
||||
* can create {@link TokenMaker}s for all languages known to
|
||||
* {@link RSyntaxTextArea}.
|
||||
*
|
||||
* @author Robert Futrell
|
||||
* @version 1.0
|
||||
*/
|
||||
class DefaultTokenMakerFactory extends AbstractTokenMakerFactory
|
||||
implements SyntaxConstants {
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
protected void initTokenMakerMap() {
|
||||
|
||||
String pkg = "org.fife.ui.rsyntaxtextarea.modes.";
|
||||
|
||||
putMapping(SYNTAX_STYLE_NONE, pkg + "PlainTextTokenMaker");
|
||||
putMapping(SYNTAX_STYLE_ACTIONSCRIPT, pkg + "ActionScriptTokenMaker");
|
||||
putMapping(SYNTAX_STYLE_ASSEMBLER_X86, pkg + "AssemblerX86TokenMaker");
|
||||
putMapping(SYNTAX_STYLE_BBCODE, pkg + "BBCodeTokenMaker");
|
||||
putMapping(SYNTAX_STYLE_C, pkg + "CTokenMaker");
|
||||
putMapping(SYNTAX_STYLE_CLOJURE, pkg + "ClojureTokenMaker");
|
||||
putMapping(SYNTAX_STYLE_CPLUSPLUS, pkg + "CPlusPlusTokenMaker");
|
||||
putMapping(SYNTAX_STYLE_CSHARP, pkg + "CSharpTokenMaker");
|
||||
putMapping(SYNTAX_STYLE_CSS, pkg + "CSSTokenMaker");
|
||||
putMapping(SYNTAX_STYLE_D, pkg + "DTokenMaker");
|
||||
putMapping(SYNTAX_STYLE_DART, pkg + "DartTokenMaker");
|
||||
putMapping(SYNTAX_STYLE_DELPHI, pkg + "DelphiTokenMaker");
|
||||
putMapping(SYNTAX_STYLE_DOCKERFILE, pkg + "DockerTokenMaker");
|
||||
putMapping(SYNTAX_STYLE_DTD, pkg + "DtdTokenMaker");
|
||||
putMapping(SYNTAX_STYLE_FORTRAN, pkg + "FortranTokenMaker");
|
||||
putMapping(SYNTAX_STYLE_GROOVY, pkg + "GroovyTokenMaker");
|
||||
putMapping(SYNTAX_STYLE_HOSTS, pkg + "HostsTokenMaker");
|
||||
putMapping(SYNTAX_STYLE_HTACCESS, pkg + "HtaccessTokenMaker");
|
||||
putMapping(SYNTAX_STYLE_HTML, pkg + "HTMLTokenMaker");
|
||||
putMapping(SYNTAX_STYLE_INI, pkg + "IniTokenMaker");
|
||||
putMapping(SYNTAX_STYLE_JAVA, pkg + "JavaTokenMaker");
|
||||
putMapping(SYNTAX_STYLE_JAVASCRIPT, pkg + "JavaScriptTokenMaker");
|
||||
putMapping(SYNTAX_STYLE_JSON_WITH_COMMENTS, pkg + "JshintrcTokenMaker");
|
||||
putMapping(SYNTAX_STYLE_JSON, pkg + "JsonTokenMaker");
|
||||
putMapping(SYNTAX_STYLE_JSP, pkg + "JSPTokenMaker");
|
||||
putMapping(SYNTAX_STYLE_LATEX, pkg + "LatexTokenMaker");
|
||||
putMapping(SYNTAX_STYLE_LESS, pkg + "LessTokenMaker");
|
||||
putMapping(SYNTAX_STYLE_LISP, pkg + "LispTokenMaker");
|
||||
putMapping(SYNTAX_STYLE_LUA, pkg + "LuaTokenMaker");
|
||||
putMapping(SYNTAX_STYLE_MAKEFILE, pkg + "MakefileTokenMaker");
|
||||
putMapping(SYNTAX_STYLE_MXML, pkg + "MxmlTokenMaker");
|
||||
putMapping(SYNTAX_STYLE_NSIS, pkg + "NSISTokenMaker");
|
||||
putMapping(SYNTAX_STYLE_PERL, pkg + "PerlTokenMaker");
|
||||
putMapping(SYNTAX_STYLE_PHP, pkg + "PHPTokenMaker");
|
||||
putMapping(SYNTAX_STYLE_PROPERTIES_FILE,pkg + "PropertiesFileTokenMaker");
|
||||
putMapping(SYNTAX_STYLE_PYTHON, pkg + "PythonTokenMaker");
|
||||
putMapping(SYNTAX_STYLE_RUBY, pkg + "RubyTokenMaker");
|
||||
putMapping(SYNTAX_STYLE_SAS, pkg + "SASTokenMaker");
|
||||
putMapping(SYNTAX_STYLE_SCALA, pkg + "ScalaTokenMaker");
|
||||
putMapping(SYNTAX_STYLE_SQL, pkg + "SQLTokenMaker");
|
||||
putMapping(SYNTAX_STYLE_TCL, pkg + "TclTokenMaker");
|
||||
putMapping(SYNTAX_STYLE_TYPESCRIPT, pkg + "TypeScriptTokenMaker");
|
||||
putMapping(SYNTAX_STYLE_UNIX_SHELL, pkg + "UnixShellTokenMaker");
|
||||
putMapping(SYNTAX_STYLE_VISUAL_BASIC, pkg + "VisualBasicTokenMaker");
|
||||
putMapping(SYNTAX_STYLE_WINDOWS_BATCH, pkg + "WindowsBatchTokenMaker");
|
||||
putMapping(SYNTAX_STYLE_XML, pkg + "XMLTokenMaker");
|
||||
putMapping(SYNTAX_STYLE_YAML, pkg + "YamlTokenMaker");
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,279 @@
|
||||
/*
|
||||
* 03/16/2013
|
||||
*
|
||||
* DefaultTokenPainter - Standard implementation of a token painter.
|
||||
*
|
||||
* 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.FontMetrics;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
|
||||
import javax.swing.text.TabExpander;
|
||||
|
||||
|
||||
/**
|
||||
* Standard implementation of a token painter.
|
||||
*
|
||||
* @author Robert Futrell
|
||||
* @version 1.0
|
||||
*/
|
||||
class DefaultTokenPainter implements TokenPainter {
|
||||
|
||||
/**
|
||||
* Rectangle used for filling token backgrounds.
|
||||
*/
|
||||
private Rectangle2D.Float bgRect;
|
||||
|
||||
/**
|
||||
* Micro-optimization; buffer used to compute tab width. If the width is
|
||||
* correct it's not re-allocated, to prevent lots of very small garbage.
|
||||
* Only used when painting tab lines.
|
||||
*/
|
||||
private static char[] tabBuf;
|
||||
|
||||
|
||||
DefaultTokenPainter() {
|
||||
bgRect = new Rectangle2D.Float();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public final float paint(Token token, Graphics2D g, float x, float y,
|
||||
RSyntaxTextArea host, TabExpander e) {
|
||||
return paint(token, g, x,y, host, e, 0);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public float paint(Token token, Graphics2D g, float x, float y,
|
||||
RSyntaxTextArea host, TabExpander e, float clipStart) {
|
||||
return paintImpl(token, g, x, y, host, e, clipStart, false, false);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public float paint(Token token, Graphics2D g, float x, float y,
|
||||
RSyntaxTextArea host, TabExpander e, float clipStart,
|
||||
boolean paintBG) {
|
||||
return paintImpl(token, g, x, y, host, e, clipStart, !paintBG, false);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Paints the background of a token.
|
||||
*
|
||||
* @param x The x-coordinate of the token.
|
||||
* @param y The y-coordinate of the token.
|
||||
* @param width The width of the token (actually, the width of the part of
|
||||
* the token to paint).
|
||||
* @param height The height of the token.
|
||||
* @param g The graphics context with which to paint.
|
||||
* @param fontAscent The ascent of the token's font.
|
||||
* @param host The text area.
|
||||
* @param color The color with which to paint.
|
||||
*/
|
||||
protected void paintBackground(float x, float y, float width, float height,
|
||||
Graphics2D g, int fontAscent, RSyntaxTextArea host,
|
||||
Color color) {
|
||||
g.setColor(color);
|
||||
bgRect.setRect(x,y-fontAscent, width,height);
|
||||
//g.fill(bgRect);
|
||||
g.fillRect((int)x, (int)(y-fontAscent), (int)width, (int)height);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Does the dirty-work of actually painting the token.
|
||||
*/
|
||||
protected float paintImpl(Token token, Graphics2D g, float x, float y,
|
||||
RSyntaxTextArea host, TabExpander e, float clipStart,
|
||||
boolean selected, boolean useSTC) {
|
||||
|
||||
int origX = (int)x;
|
||||
int textOffs = token.getTextOffset();
|
||||
char[] text = token.getTextArray();
|
||||
int end = textOffs + token.length();
|
||||
float nextX = x;
|
||||
int flushLen = 0;
|
||||
int flushIndex = textOffs;
|
||||
Color fg = useSTC ? host.getSelectedTextColor() :
|
||||
host.getForegroundForToken(token);
|
||||
Color bg = selected ? null : host.getBackgroundForToken(token);
|
||||
g.setFont(host.getFontForTokenType(token.getType()));
|
||||
FontMetrics fm = host.getFontMetricsForTokenType(token.getType());
|
||||
|
||||
for (int i=textOffs; i<end; i++) {
|
||||
switch (text[i]) {
|
||||
case '\t':
|
||||
nextX = e.nextTabStop(
|
||||
x + fm.charsWidth(text, flushIndex,flushLen), 0);
|
||||
if (bg!=null) {
|
||||
paintBackground(x,y, nextX-x,fm.getHeight(),
|
||||
g, fm.getAscent(), host, bg);
|
||||
}
|
||||
if (flushLen > 0) {
|
||||
g.setColor(fg);
|
||||
g.drawChars(text, flushIndex, flushLen, (int)x,(int)y);
|
||||
flushLen = 0;
|
||||
}
|
||||
flushIndex = i + 1;
|
||||
x = nextX;
|
||||
break;
|
||||
default:
|
||||
flushLen += 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
nextX = x+fm.charsWidth(text, flushIndex,flushLen);
|
||||
java.awt.Rectangle r = host.getMatchRectangle();
|
||||
|
||||
if (flushLen>0 && nextX>=clipStart) {
|
||||
if (bg!=null) {
|
||||
paintBackground(x,y, nextX-x,fm.getHeight(),
|
||||
g, fm.getAscent(), host, bg);
|
||||
if (token.length()==1 && r!=null && r.x==x) {
|
||||
((RSyntaxTextAreaUI)host.getUI()).paintMatchedBracketImpl(
|
||||
g, host, r);
|
||||
}
|
||||
}
|
||||
g.setColor(fg);
|
||||
g.drawChars(text, flushIndex, flushLen, (int)x,(int)y);
|
||||
}
|
||||
|
||||
if (host.getUnderlineForToken(token)) {
|
||||
g.setColor(fg);
|
||||
int y2 = (int)(y+1);
|
||||
g.drawLine(origX,y2, (int)nextX,y2);
|
||||
}
|
||||
|
||||
// Don't check if it's whitespace - some TokenMakers may return types
|
||||
// other than Token.WHITESPACE for spaces (such as Token.IDENTIFIER).
|
||||
// This also allows us to paint tab lines for MLC's.
|
||||
if (host.getPaintTabLines() && origX==host.getMargin().left) {// && isWhitespace()) {
|
||||
paintTabLines(token, origX, (int)y, (int)nextX, g, e, host);
|
||||
}
|
||||
|
||||
return nextX;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public float paintSelected(Token token, Graphics2D g, float x, float y,
|
||||
RSyntaxTextArea host, TabExpander e, boolean useSTC) {
|
||||
return paintSelected(token, g, x, y, host, e, 0, useSTC);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public float paintSelected(Token token, Graphics2D g, float x, float y,
|
||||
RSyntaxTextArea host, TabExpander e, float clipStart,
|
||||
boolean useSTC) {
|
||||
return paintImpl(token, g, x, y, host, e, clipStart, true, useSTC);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Paints dotted "tab" lines; that is, lines that show where your caret
|
||||
* would go to on the line if you hit "tab". This visual effect is usually
|
||||
* done in the leading whitespace token(s) of lines.
|
||||
*
|
||||
* @param token The token to render.
|
||||
* @param x The starting x-offset of this token. It is assumed that this
|
||||
* is the left margin of the text area (may be non-zero due to
|
||||
* insets), since tab lines are only painted for leading whitespace.
|
||||
* @param y The baseline where this token was painted.
|
||||
* @param endX The ending x-offset of this token.
|
||||
* @param g The graphics context.
|
||||
* @param e Used to expand tabs.
|
||||
* @param host The text area.
|
||||
*/
|
||||
protected void paintTabLines(Token token, int x, int y, int endX,
|
||||
Graphics2D g, TabExpander e, RSyntaxTextArea host) {
|
||||
|
||||
// We allow tab lines to be painted in more than just Token.WHITESPACE,
|
||||
// i.e. for MLC's and Token.IDENTIFIERS (for TokenMakers that return
|
||||
// whitespace as identifiers for performance). But we only paint tab
|
||||
// lines for the leading whitespace in the token. So, if this isn't a
|
||||
// WHITESPACE token, figure out the leading whitespace's length.
|
||||
if (token.getType()!=Token.WHITESPACE) {
|
||||
int offs = 0;
|
||||
for (; offs<token.length(); offs++) {
|
||||
if (!RSyntaxUtilities.isWhitespace(token.charAt(offs))) {
|
||||
break; // MLC text, etc.
|
||||
}
|
||||
}
|
||||
if (offs<2) { // Must be at least two spaces to see tab line
|
||||
return;
|
||||
}
|
||||
//endX = x + (int)getWidthUpTo(offs, host, e, x);
|
||||
endX = (int)token.getWidthUpTo(offs, host, e, 0);
|
||||
}
|
||||
|
||||
// Get the length of a tab.
|
||||
FontMetrics fm = host.getFontMetricsForTokenType(token.getType());
|
||||
int tabSize = host.getTabSize();
|
||||
if (tabBuf==null || tabBuf.length<tabSize) {
|
||||
tabBuf = new char[tabSize];
|
||||
for (int i=0; i<tabSize; i++) {
|
||||
tabBuf[i] = ' ';
|
||||
}
|
||||
}
|
||||
// Note different token types (MLC's, whitespace) could possibly be
|
||||
// using different fonts, which means we can't cache the actual width
|
||||
// of a tab as it may be different per-token-type. We could keep a
|
||||
// per-token-type cache, but we'd have to clear it whenever they
|
||||
// modified token styles.
|
||||
int tabW = fm.charsWidth(tabBuf, 0, tabSize);
|
||||
|
||||
// Draw any tab lines. Here we're assuming that "x" is the left
|
||||
// margin of the editor.
|
||||
g.setColor(host.getTabLineColor());
|
||||
int x0 = x + tabW;
|
||||
int y0 = y - fm.getAscent();
|
||||
if ((y0&1)>0) {
|
||||
// Only paint on even y-pixels to prevent doubling up between lines
|
||||
y0++;
|
||||
}
|
||||
|
||||
// TODO: Go to endX (inclusive) if this token is last token in the line
|
||||
Token next = token.getNextToken();
|
||||
if (next==null || !next.isPaintable()) {
|
||||
endX++;
|
||||
}
|
||||
while (x0<endX) {
|
||||
int y1 = y0;
|
||||
int y2 = y0 + host.getLineHeight();
|
||||
while (y1<y2) {
|
||||
g.drawLine(x0, y1, x0, y1);
|
||||
y1 += 2;
|
||||
}
|
||||
//g.drawLine(x0,y0, x0,y0+host.getLineHeight());
|
||||
x0 += tabW;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
/*
|
||||
* 08/11/2009
|
||||
*
|
||||
* DocumentRange.java - A range of text in a document.
|
||||
*
|
||||
* This library is distributed under a modified BSD license. See the included
|
||||
* RSyntaxTextArea.License.txt file for details.
|
||||
*/
|
||||
package org.fife.ui.rsyntaxtextarea;
|
||||
|
||||
|
||||
/**
|
||||
* A range of text in a document.
|
||||
*
|
||||
* @author Robert Futrell
|
||||
* @version 1.0
|
||||
*/
|
||||
public class DocumentRange implements Comparable<DocumentRange> {
|
||||
|
||||
private int startOffs;
|
||||
private int endOffs;
|
||||
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param startOffs The starting offset in the document, inclusive.
|
||||
* @param endOffs The ending offset in the document, exclusive.
|
||||
* @throws IllegalArgumentException If <code>endOffs</code> is less than
|
||||
* <code>startOffs</code>, or either argument is less than zero.
|
||||
*/
|
||||
public DocumentRange(int startOffs, int endOffs) {
|
||||
set(startOffs, endOffs);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Compares this document range to another.
|
||||
*
|
||||
* @param other Another document range.
|
||||
* @return How the two should be sorted relative to each other.
|
||||
*/
|
||||
@Override
|
||||
public int compareTo(DocumentRange other) {
|
||||
if (other==null) {
|
||||
return 1;
|
||||
}
|
||||
int diff = startOffs - other.startOffs;
|
||||
if (diff!=0) {
|
||||
return diff;
|
||||
}
|
||||
return endOffs - other.endOffs;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether this document range is equal to another one.
|
||||
*
|
||||
* @param other Another object, presumably a document range.
|
||||
* @return Whether <code>other</code> is also a document range, and equal
|
||||
* to this one.
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (other==this) {
|
||||
return true;
|
||||
}
|
||||
if (other instanceof DocumentRange) {
|
||||
return this.compareTo((DocumentRange)other)==0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets the end offset of the range.
|
||||
*
|
||||
* @return The end offset.
|
||||
* @see #getStartOffset()
|
||||
*/
|
||||
public int getEndOffset() {
|
||||
return endOffs;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets the starting offset of the range.
|
||||
*
|
||||
* @return The starting offset.
|
||||
* @see #getEndOffset()
|
||||
*/
|
||||
public int getStartOffset() {
|
||||
return startOffs;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Overridden simply as a best practice, since {@link #equals(Object)} is
|
||||
* overridden.
|
||||
*
|
||||
* @return The hash code for this object.
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return startOffs + endOffs;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether this document range has zero length. This can happen,
|
||||
* for example, with regex searches of forms like
|
||||
* <code>"foo|"</code>, where the right-hand sub-expression matches empty
|
||||
* strings.
|
||||
*
|
||||
* @return Whether this document range has zero length.
|
||||
*/
|
||||
public boolean isZeroLength() {
|
||||
return startOffs == endOffs;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the document range.
|
||||
*
|
||||
* @param start The new start value, inclusive.
|
||||
* @param end The new end value, exclusive.
|
||||
* @throws IllegalArgumentException If <code>end</code> is less than
|
||||
* <code>start</code>, or either argument is less than zero.
|
||||
*/
|
||||
public void set(int start, int end) {
|
||||
if (start<0 || end<0) {
|
||||
throw new IllegalArgumentException(
|
||||
"start and end must be >= 0 (" + start + "-" + end + ")");
|
||||
}
|
||||
if (end<start) {
|
||||
throw new IllegalArgumentException(
|
||||
"'end' cannot be less than 'start' (" +
|
||||
start + "-" + end + ")");
|
||||
}
|
||||
this.startOffs = start;
|
||||
this.endOffs = end;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a string representation of this object.
|
||||
*
|
||||
* @return A string representation of this object.
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return "[DocumentRange: " + startOffs + "-" + endOffs + "]";
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Translates this document range by a given amount.
|
||||
*
|
||||
* @param amount The amount to translate this range by.
|
||||
* @return This (modified) range.
|
||||
*/
|
||||
public DocumentRange translate(int amount) {
|
||||
startOffs += amount;
|
||||
endOffs += amount;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,924 @@
|
||||
/*
|
||||
* 08/10/2009
|
||||
*
|
||||
* ErrorStrip.java - A component that can visually show Parser messages (syntax
|
||||
* errors, etc.) in an RSyntaxTextArea.
|
||||
*
|
||||
* This library is distributed under a modified BSD license. See the included
|
||||
* RSyntaxTextArea.License.txt file for details.
|
||||
*/
|
||||
package org.fife.ui.rsyntaxtextarea;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Component;
|
||||
import java.awt.Cursor;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.beans.PropertyChangeEvent;
|
||||
import java.beans.PropertyChangeListener;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
import javax.swing.JComponent;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.ToolTipManager;
|
||||
import javax.swing.UIManager;
|
||||
import javax.swing.event.CaretEvent;
|
||||
import javax.swing.event.CaretListener;
|
||||
import javax.swing.text.BadLocationException;
|
||||
|
||||
import org.fife.ui.rsyntaxtextarea.parser.Parser;
|
||||
import org.fife.ui.rsyntaxtextarea.parser.ParserNotice;
|
||||
import org.fife.ui.rsyntaxtextarea.parser.TaskTagParser.TaskNotice;
|
||||
import org.fife.ui.rtextarea.RTextArea;
|
||||
|
||||
|
||||
/**
|
||||
* A component to sit alongside an {@link RSyntaxTextArea} that displays
|
||||
* colored markers for locations of interest (parser errors, marked
|
||||
* occurrences, etc.).<p>
|
||||
*
|
||||
* <code>ErrorStrip</code>s display <code>ParserNotice</code>s from
|
||||
* {@link Parser}s. Currently, the only way to get lines flagged in this
|
||||
* component is to register a <code>Parser</code> on an RSyntaxTextArea and
|
||||
* return <code>ParserNotice</code>s for each line to display an icon for.
|
||||
* The severity of each notice must be at least the threshold set by
|
||||
* {@link #setLevelThreshold(org.fife.ui.rsyntaxtextarea.parser.ParserNotice.Level)}
|
||||
* to be displayed in this error strip. The default threshold is
|
||||
* {@link org.fife.ui.rsyntaxtextarea.parser.ParserNotice.Level#WARNING}.<p>
|
||||
*
|
||||
* An <code>ErrorStrip</code> can be added to a UI like so:
|
||||
* <pre>
|
||||
* textArea = createTextArea();
|
||||
* textArea.addParser(new MyParser(textArea)); // Identifies lines to display
|
||||
* scrollPane = new RTextScrollPane(textArea, true);
|
||||
* ErrorStrip es = new ErrorStrip(textArea);
|
||||
* JPanel temp = new JPanel(new BorderLayout());
|
||||
* temp.add(scrollPane);
|
||||
* temp.add(es, BorderLayout.LINE_END);
|
||||
* </pre>
|
||||
*
|
||||
* @author Robert Futrell
|
||||
* @version 0.5
|
||||
*/
|
||||
// Possible improvements:
|
||||
// 1. Handle marked occurrence changes & "mark all" changes separately from
|
||||
// parser changes. For each property change, call a method that removes
|
||||
// the notices being reloaded from the Markers (removing any Markers that
|
||||
// are now "empty").
|
||||
//
|
||||
public class ErrorStrip extends JPanel {
|
||||
|
||||
/**
|
||||
* The text area.
|
||||
*/
|
||||
private RSyntaxTextArea textArea;
|
||||
|
||||
/**
|
||||
* Listens for events in this component.
|
||||
*/
|
||||
private transient Listener listener;
|
||||
|
||||
/**
|
||||
* Whether "marked occurrences" in the text area should be shown in this
|
||||
* error strip.
|
||||
*/
|
||||
private boolean showMarkedOccurrences;
|
||||
|
||||
/**
|
||||
* Whether markers for "mark all" highlights should be shown in this
|
||||
* error strip.
|
||||
*/
|
||||
private boolean showMarkAll;
|
||||
|
||||
/**
|
||||
* Mapping of colors to brighter colors. This is kept to prevent
|
||||
* unnecessary creation of the same Colors over and over.
|
||||
*/
|
||||
private Map<Color, Color> brighterColors;
|
||||
|
||||
/**
|
||||
* Only notices of this severity (or worse) will be displayed in this
|
||||
* error strip.
|
||||
*/
|
||||
private ParserNotice.Level levelThreshold;
|
||||
|
||||
/**
|
||||
* Whether the caret marker's location should be rendered.
|
||||
*/
|
||||
private boolean followCaret;
|
||||
|
||||
/**
|
||||
* The color to use for the caret marker.
|
||||
*/
|
||||
private Color caretMarkerColor;
|
||||
|
||||
/**
|
||||
* Where we paint the caret marker.
|
||||
*/
|
||||
private int caretLineY;
|
||||
|
||||
/**
|
||||
* The last location of the caret marker.
|
||||
*/
|
||||
private int lastLineY;
|
||||
|
||||
/**
|
||||
* Generates the tool tips for markers in this error strip.
|
||||
*/
|
||||
private transient ErrorStripMarkerToolTipProvider markerToolTipProvider;
|
||||
|
||||
/**
|
||||
* The preferred width of this component.
|
||||
*/
|
||||
private static final int PREFERRED_WIDTH = 14;
|
||||
|
||||
private static final ResourceBundle MSG = ResourceBundle.getBundle(
|
||||
"org.fife.ui.rsyntaxtextarea.ErrorStrip");
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param textArea The text area we are examining.
|
||||
*/
|
||||
public ErrorStrip(RSyntaxTextArea textArea) {
|
||||
this.textArea = textArea;
|
||||
listener = new Listener();
|
||||
ToolTipManager.sharedInstance().registerComponent(this);
|
||||
setLayout(null); // Manually layout Markers as they can overlap
|
||||
addMouseListener(listener);
|
||||
setShowMarkedOccurrences(true);
|
||||
setShowMarkAll(true);
|
||||
setLevelThreshold(ParserNotice.Level.WARNING);
|
||||
setFollowCaret(true);
|
||||
setCaretMarkerColor(Color.BLACK);
|
||||
setMarkerToolTipProvider(null); // Install default
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Overridden so we only start listening for parser notices when this
|
||||
* component (and presumably the text area) are visible.
|
||||
*/
|
||||
@Override
|
||||
public void addNotify() {
|
||||
super.addNotify();
|
||||
textArea.addCaretListener(listener);
|
||||
textArea.addPropertyChangeListener(
|
||||
RSyntaxTextArea.PARSER_NOTICES_PROPERTY, listener);
|
||||
textArea.addPropertyChangeListener(
|
||||
RSyntaxTextArea.MARK_OCCURRENCES_PROPERTY, listener);
|
||||
textArea.addPropertyChangeListener(
|
||||
RSyntaxTextArea.MARKED_OCCURRENCES_CHANGED_PROPERTY, listener);
|
||||
textArea.addPropertyChangeListener(
|
||||
RSyntaxTextArea.MARK_ALL_OCCURRENCES_CHANGED_PROPERTY, listener);
|
||||
refreshMarkers();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Manually manages layout since this component uses no layout manager.
|
||||
*/
|
||||
@Override
|
||||
public void doLayout() {
|
||||
for (int i=0; i<getComponentCount(); i++) {
|
||||
Marker m = (Marker)getComponent(i);
|
||||
m.updateLocation();
|
||||
}
|
||||
listener.caretUpdate(null); // Force recalculation of caret line pos
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a "brighter" color.
|
||||
*
|
||||
* @param c The color.
|
||||
* @return A brighter color.
|
||||
*/
|
||||
private Color getBrighterColor(Color c) {
|
||||
if (brighterColors==null) {
|
||||
brighterColors = new HashMap<Color, Color>(5); // Usually small
|
||||
}
|
||||
Color brighter = brighterColors.get(c);
|
||||
if (brighter==null) {
|
||||
// Don't use c.brighter() as it doesn't work well for blue, and
|
||||
// also doesn't return something brighter "enough."
|
||||
int r = possiblyBrighter(c.getRed());
|
||||
int g = possiblyBrighter(c.getGreen());
|
||||
int b = possiblyBrighter(c.getBlue());
|
||||
brighter = new Color(r, g, b);
|
||||
brighterColors.put(c, brighter);
|
||||
}
|
||||
return brighter;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* returns the color to use when painting the caret marker.
|
||||
*
|
||||
* @return The caret marker color.
|
||||
* @see #setCaretMarkerColor(Color)
|
||||
*/
|
||||
public Color getCaretMarkerColor() {
|
||||
return caretMarkerColor;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether the caret's position should be drawn.
|
||||
*
|
||||
* @return Whether the caret's position should be drawn.
|
||||
* @see #setFollowCaret(boolean)
|
||||
*/
|
||||
public boolean getFollowCaret() {
|
||||
return followCaret;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public Dimension getPreferredSize() {
|
||||
int height = textArea.getPreferredScrollableViewportSize().height;
|
||||
return new Dimension(PREFERRED_WIDTH, height);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the minimum severity a parser notice must be for it to be
|
||||
* displayed in this error strip. This will be one of the constants
|
||||
* defined in the <code>ParserNotice</code> class.
|
||||
*
|
||||
* @return The minimum severity.
|
||||
* @see #setLevelThreshold(org.fife.ui.rsyntaxtextarea.parser.ParserNotice.Level)
|
||||
*/
|
||||
public ParserNotice.Level getLevelThreshold() {
|
||||
return levelThreshold;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether "mark all" highlights are shown in this error strip.
|
||||
*
|
||||
* @return Whether markers are shown for "mark all" highlights.
|
||||
* @see #setShowMarkAll(boolean)
|
||||
*/
|
||||
public boolean getShowMarkAll() {
|
||||
return showMarkAll;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether marked occurrences are shown in this error strip.
|
||||
*
|
||||
* @return Whether marked occurrences are shown.
|
||||
* @see #setShowMarkedOccurrences(boolean)
|
||||
*/
|
||||
public boolean getShowMarkedOccurrences() {
|
||||
return showMarkedOccurrences;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public String getToolTipText(MouseEvent e) {
|
||||
String text = null;
|
||||
int line = yToLine(e.getY());
|
||||
if (line>-1) {
|
||||
text = MSG.getString("Line");
|
||||
text = MessageFormat.format(text, Integer.valueOf(line+1));
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the y-offset in this component corresponding to a line in the
|
||||
* text component.
|
||||
*
|
||||
* @param line The line.
|
||||
* @return The y-offset.
|
||||
* @see #yToLine(int)
|
||||
*/
|
||||
private int lineToY(int line) {
|
||||
int h = textArea.getVisibleRect().height;
|
||||
float lineCount = textArea.getLineCount();
|
||||
return (int)(((line-1)/(lineCount-1)) * (h-2));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Overridden to (possibly) draw the caret's position.
|
||||
*
|
||||
* @param g The graphics context.
|
||||
*/
|
||||
@Override
|
||||
protected void paintComponent(Graphics g) {
|
||||
super.paintComponent(g);
|
||||
if (caretLineY>-1) {
|
||||
g.setColor(getCaretMarkerColor());
|
||||
g.fillRect(0, caretLineY, getWidth(), 2);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a possibly brighter component for a color.
|
||||
*
|
||||
* @param i An RGB component for a color (0-255).
|
||||
* @return A possibly brighter value for the component.
|
||||
*/
|
||||
private static int possiblyBrighter(int i) {
|
||||
if (i<255) {
|
||||
i += (int)((255-i)*0.8f);
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Refreshes the markers displayed in this error strip.
|
||||
*/
|
||||
private void refreshMarkers() {
|
||||
|
||||
removeAll(); // listener is removed in Marker.removeNotify()
|
||||
Map<Integer, Marker> markerMap = new HashMap<Integer, Marker>();
|
||||
|
||||
List<ParserNotice> notices = textArea.getParserNotices();
|
||||
for (ParserNotice notice : notices) {
|
||||
if (notice.getLevel().isEqualToOrWorseThan(levelThreshold) ||
|
||||
(notice instanceof TaskNotice)) {
|
||||
Integer key = Integer.valueOf(notice.getLine());
|
||||
Marker m = markerMap.get(key);
|
||||
if (m==null) {
|
||||
m = new Marker(notice);
|
||||
m.addMouseListener(listener);
|
||||
markerMap.put(key, m);
|
||||
add(m);
|
||||
}
|
||||
else {
|
||||
m.addNotice(notice);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (getShowMarkedOccurrences() && textArea.getMarkOccurrences()) {
|
||||
List<DocumentRange> occurrences = textArea.getMarkedOccurrences();
|
||||
addMarkersForRanges(occurrences, markerMap, textArea.getMarkOccurrencesColor());
|
||||
}
|
||||
|
||||
if (getShowMarkAll() /*&& textArea.getMarkAll()*/) {
|
||||
Color markAllColor = textArea.getMarkAllHighlightColor();
|
||||
List<DocumentRange> ranges = textArea.getMarkAllHighlightRanges();
|
||||
addMarkersForRanges(ranges, markerMap, markAllColor);
|
||||
}
|
||||
|
||||
revalidate();
|
||||
repaint();
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds markers for a list of ranges in the document.
|
||||
*
|
||||
* @param ranges The list of ranges in the document.
|
||||
* @param markerMap A mapping from line number to <code>Marker</code>.
|
||||
* @param color The color to use for the markers.
|
||||
*/
|
||||
private void addMarkersForRanges(List<DocumentRange> ranges,
|
||||
Map<Integer, Marker> markerMap, Color color) {
|
||||
for (DocumentRange range : ranges) {
|
||||
int line = 0;
|
||||
try {
|
||||
line = textArea.getLineOfOffset(range.getStartOffset());
|
||||
} catch (BadLocationException ble) { // Never happens
|
||||
continue;
|
||||
}
|
||||
ParserNotice notice = new MarkedOccurrenceNotice(range, color);
|
||||
Integer key = Integer.valueOf(line);
|
||||
Marker m = markerMap.get(key);
|
||||
if (m==null) {
|
||||
m = new Marker(notice);
|
||||
m.addMouseListener(listener);
|
||||
markerMap.put(key, m);
|
||||
add(m);
|
||||
}
|
||||
else {
|
||||
if (!m.containsMarkedOccurence()) {
|
||||
m.addNotice(notice);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void removeNotify() {
|
||||
super.removeNotify();
|
||||
textArea.removeCaretListener(listener);
|
||||
textArea.removePropertyChangeListener(
|
||||
RSyntaxTextArea.PARSER_NOTICES_PROPERTY, listener);
|
||||
textArea.removePropertyChangeListener(
|
||||
RSyntaxTextArea.MARK_OCCURRENCES_PROPERTY, listener);
|
||||
textArea.removePropertyChangeListener(
|
||||
RSyntaxTextArea.MARKED_OCCURRENCES_CHANGED_PROPERTY, listener);
|
||||
textArea.removePropertyChangeListener(
|
||||
RSyntaxTextArea.MARK_ALL_OCCURRENCES_CHANGED_PROPERTY, listener);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the color to use when painting the caret marker.
|
||||
*
|
||||
* @param color The new caret marker color.
|
||||
* @see #getCaretMarkerColor()
|
||||
*/
|
||||
public void setCaretMarkerColor(Color color) {
|
||||
if (color!=null) {
|
||||
caretMarkerColor = color;
|
||||
listener.caretUpdate(null); // Force repaint
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Toggles whether the caret's current location should be drawn.
|
||||
*
|
||||
* @param follow Whether the caret's current location should be followed.
|
||||
* @see #getFollowCaret()
|
||||
*/
|
||||
public void setFollowCaret(boolean follow) {
|
||||
if (followCaret!=follow) {
|
||||
if (followCaret) {
|
||||
repaint(0,caretLineY, getWidth(),2); // Erase
|
||||
}
|
||||
caretLineY = -1;
|
||||
lastLineY = -1;
|
||||
followCaret = follow;
|
||||
listener.caretUpdate(null); // Possibly repaint
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the minimum severity a parser notice must be for it to be displayed
|
||||
* in this error strip. This should be one of the constants defined in
|
||||
* the <code>ParserNotice</code> class. The default value is
|
||||
* {@link org.fife.ui.rsyntaxtextarea.parser.ParserNotice.Level#WARNING}.
|
||||
*
|
||||
* @param level The new severity threshold.
|
||||
* @see #getLevelThreshold()
|
||||
* @see ParserNotice
|
||||
*/
|
||||
public void setLevelThreshold(ParserNotice.Level level) {
|
||||
levelThreshold = level;
|
||||
if (isDisplayable()) {
|
||||
refreshMarkers();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the provider of tool tips for markers in this error strip.
|
||||
* Applications can use this method to control the content and format of
|
||||
* the tool tip descriptions of line markers.
|
||||
*
|
||||
* @param provider The provider. If this is <code>null</code>, a default
|
||||
* implementation will be used.
|
||||
*/
|
||||
public void setMarkerToolTipProvider(ErrorStripMarkerToolTipProvider provider) {
|
||||
markerToolTipProvider = provider != null ? provider :
|
||||
new DefaultErrorStripMarkerToolTipProvider();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets whether "mark all" highlights are shown in this error strip.
|
||||
*
|
||||
* @param show Whether to show markers for "mark all" highlights.
|
||||
* @see #getShowMarkAll()
|
||||
*/
|
||||
public void setShowMarkAll(boolean show) {
|
||||
if (show!=showMarkAll) {
|
||||
showMarkAll = show;
|
||||
if (isDisplayable()) { // Skip this when we're first created
|
||||
refreshMarkers();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets whether marked occurrences are shown in this error strip.
|
||||
*
|
||||
* @param show Whether to show marked occurrences.
|
||||
* @see #getShowMarkedOccurrences()
|
||||
*/
|
||||
public void setShowMarkedOccurrences(boolean show) {
|
||||
if (show!=showMarkedOccurrences) {
|
||||
showMarkedOccurrences = show;
|
||||
if (isDisplayable()) { // Skip this when we're first created
|
||||
refreshMarkers();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the line in the text area corresponding to a y-offset in this
|
||||
* component.
|
||||
*
|
||||
* @param y The y-offset.
|
||||
* @return The line.
|
||||
* @see #lineToY(int)
|
||||
*/
|
||||
private int yToLine(int y) {
|
||||
int line = -1;
|
||||
int h = textArea.getVisibleRect().height;
|
||||
if (y<h) {
|
||||
float at = y/(float)h;
|
||||
line = Math.round((textArea.getLineCount()-1)*at);
|
||||
}
|
||||
return line;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The default implementation of the provider of tool tips for markers in
|
||||
* an error strip.
|
||||
*
|
||||
* @author predi
|
||||
*/
|
||||
private static class DefaultErrorStripMarkerToolTipProvider
|
||||
implements ErrorStripMarkerToolTipProvider {
|
||||
|
||||
@Override
|
||||
public String getToolTipText(List<ParserNotice> notices) {
|
||||
|
||||
String text = null;
|
||||
|
||||
if (notices.size()==1) {
|
||||
text = notices.get(0).getMessage();
|
||||
}
|
||||
else { // > 1
|
||||
StringBuilder sb = new StringBuilder("<html>");
|
||||
sb.append(MSG.getString("MultipleMarkers"));
|
||||
sb.append("<br>");
|
||||
for (int i=0; i<notices.size(); i++) {
|
||||
ParserNotice pn = notices.get(i);
|
||||
sb.append(" - ");
|
||||
sb.append(pn.getMessage());
|
||||
sb.append("<br>");
|
||||
}
|
||||
text = sb.toString();
|
||||
}
|
||||
|
||||
return text;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns tool tip text for the markers in an {@link ErrorStrip} that
|
||||
* denote one or more parser notices.
|
||||
*
|
||||
* @author predi
|
||||
*/
|
||||
public interface ErrorStripMarkerToolTipProvider {
|
||||
|
||||
/**
|
||||
* Returns the tool tip text for a marker in an <code>ErrorStrip</code>
|
||||
* that denotes a given list of parser notices.
|
||||
*
|
||||
* @param notices The list of parser notices.
|
||||
* @return The tool tip text. This may be HTML. Returning
|
||||
* <code>null</code> will result in no tool tip being displayed.
|
||||
*/
|
||||
String getToolTipText(List<ParserNotice> notices);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Listens for events in the error strip and its markers.
|
||||
*/
|
||||
private class Listener extends MouseAdapter
|
||||
implements PropertyChangeListener, CaretListener {
|
||||
|
||||
private Rectangle visibleRect = new Rectangle();
|
||||
|
||||
@Override
|
||||
public void caretUpdate(CaretEvent e) {
|
||||
if (getFollowCaret()) {
|
||||
int line = textArea.getCaretLineNumber();
|
||||
float percent = line / (float)(textArea.getLineCount()-1);
|
||||
textArea.computeVisibleRect(visibleRect);
|
||||
caretLineY = (int)(visibleRect.height*percent);
|
||||
if (caretLineY!=lastLineY) {
|
||||
repaint(0,lastLineY, getWidth(), 2); // Erase old position
|
||||
repaint(0,caretLineY, getWidth(), 2);
|
||||
lastLineY = caretLineY;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
|
||||
Component source = (Component)e.getSource();
|
||||
if (source instanceof Marker) {
|
||||
((Marker)source).mouseClicked(e);
|
||||
return;
|
||||
}
|
||||
|
||||
int line = yToLine(e.getY());
|
||||
if (line>-1) {
|
||||
try {
|
||||
int offs = textArea.getLineStartOffset(line);
|
||||
textArea.setCaretPosition(offs);
|
||||
} catch (BadLocationException ble) { // Never happens
|
||||
UIManager.getLookAndFeel().provideErrorFeedback(textArea);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void propertyChange(PropertyChangeEvent e) {
|
||||
|
||||
String propName = e.getPropertyName();
|
||||
|
||||
// If they change whether marked occurrences are visible in editor
|
||||
if (RSyntaxTextArea.MARK_OCCURRENCES_PROPERTY.equals(propName)) {
|
||||
if (getShowMarkedOccurrences()) {
|
||||
refreshMarkers();
|
||||
}
|
||||
}
|
||||
|
||||
// If parser notices changed.
|
||||
// TODO: Don't update "mark all/occurrences" markers.
|
||||
else if (RSyntaxTextArea.PARSER_NOTICES_PROPERTY.equals(propName)) {
|
||||
refreshMarkers();
|
||||
}
|
||||
|
||||
// If marked occurrences changed.
|
||||
// TODO: Only update "mark occurrences" markers, not all of them.
|
||||
else if (RSyntaxTextArea.MARKED_OCCURRENCES_CHANGED_PROPERTY.
|
||||
equals(propName)) {
|
||||
if (getShowMarkedOccurrences()) {
|
||||
refreshMarkers();
|
||||
}
|
||||
}
|
||||
|
||||
// If "mark all" occurrences changed.
|
||||
// TODO: Only update "mark all" markers, not all of them.
|
||||
else if (RTextArea.MARK_ALL_OCCURRENCES_CHANGED_PROPERTY.
|
||||
equals(propName)) {
|
||||
if (getShowMarkAll()) {
|
||||
refreshMarkers();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* A notice that wraps a "marked occurrence" instance.
|
||||
*/
|
||||
private class MarkedOccurrenceNotice implements ParserNotice {
|
||||
|
||||
private DocumentRange range;
|
||||
private Color color;
|
||||
|
||||
MarkedOccurrenceNotice(DocumentRange range, Color color) {
|
||||
this.range = range;
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(ParserNotice other) {
|
||||
return 0; // Value doesn't matter
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsPosition(int pos) {
|
||||
return pos>=range.getStartOffset() && pos<range.getEndOffset();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
// FindBugs - Define equals() when defining compareTo()
|
||||
if (!(o instanceof ParserNotice)) {
|
||||
return false;
|
||||
}
|
||||
return compareTo((ParserNotice)o)==0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Color getColor() {
|
||||
return color;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public boolean getKnowsOffsetAndLength() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLength() {
|
||||
return range.getEndOffset() - range.getStartOffset();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Level getLevel() {
|
||||
return Level.INFO; // Won't matter
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLine() {
|
||||
try {
|
||||
return textArea.getLineOfOffset(range.getStartOffset())+1;
|
||||
} catch (BadLocationException ble) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
String text = null;
|
||||
try {
|
||||
String word = textArea.getText(range.getStartOffset(),
|
||||
getLength());
|
||||
text = MSG.getString("OccurrenceOf");
|
||||
text = MessageFormat.format(text, word);
|
||||
} catch (BadLocationException ble) {
|
||||
UIManager.getLookAndFeel().provideErrorFeedback(textArea);
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOffset() {
|
||||
return range.getStartOffset();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Parser getParser() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getShowInEditor() {
|
||||
return false; // Value doesn't matter
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getToolTipText() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() { // FindBugs, since we override equals()
|
||||
return 0; // Value doesn't matter for us.
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* A "marker" in this error strip, representing one or more notices.
|
||||
*/
|
||||
private class Marker extends JComponent {
|
||||
|
||||
private List<ParserNotice> notices;
|
||||
|
||||
Marker(ParserNotice notice) {
|
||||
notices = new ArrayList<ParserNotice>(1); // Usually just 1
|
||||
addNotice(notice);
|
||||
setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
|
||||
setSize(getPreferredSize());
|
||||
ToolTipManager.sharedInstance().registerComponent(this);
|
||||
}
|
||||
|
||||
public void addNotice(ParserNotice notice) {
|
||||
notices.add(notice);
|
||||
}
|
||||
|
||||
public boolean containsMarkedOccurence() {
|
||||
boolean result = false;
|
||||
for (int i=0; i<notices.size(); i++) {
|
||||
if (notices.get(i) instanceof MarkedOccurrenceNotice) {
|
||||
result = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public Color getColor() {
|
||||
// Return the color for the highest-level parser.
|
||||
Color c = null;
|
||||
int lowestLevel = Integer.MAX_VALUE; // ERROR is 0
|
||||
for (ParserNotice notice : notices) {
|
||||
if (notice.getLevel().getNumericValue()<lowestLevel) {
|
||||
lowestLevel = notice.getLevel().getNumericValue();
|
||||
c = notice.getColor();
|
||||
}
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dimension getPreferredSize() {
|
||||
int w = PREFERRED_WIDTH - 4; // 2-pixel empty border
|
||||
return new Dimension(w, 5);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getToolTipText() {
|
||||
return markerToolTipProvider.getToolTipText(Collections.
|
||||
unmodifiableList(notices));
|
||||
}
|
||||
|
||||
protected void mouseClicked(MouseEvent e) {
|
||||
ParserNotice pn = notices.get(0);
|
||||
int offs = pn.getOffset();
|
||||
int len = pn.getLength();
|
||||
if (offs>-1 && len>-1) { // These values are optional
|
||||
DocumentRange range = new DocumentRange(offs, offs + len);
|
||||
RSyntaxUtilities.selectAndPossiblyCenter(textArea, range, true);
|
||||
}
|
||||
else {
|
||||
int line = pn.getLine();
|
||||
try {
|
||||
offs = textArea.getLineStartOffset(line);
|
||||
textArea.getFoldManager().ensureOffsetNotInClosedFold(offs);
|
||||
textArea.setCaretPosition(offs);
|
||||
} catch (BadLocationException ble) { // Never happens
|
||||
UIManager.getLookAndFeel().provideErrorFeedback(textArea);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void paintComponent(Graphics g) {
|
||||
|
||||
// TODO: Give "priorities" and always pick color of a notice with
|
||||
// highest priority (e.g. parsing errors will usually be red).
|
||||
|
||||
Color borderColor = getColor();
|
||||
if (borderColor==null) {
|
||||
borderColor = Color.DARK_GRAY;
|
||||
}
|
||||
Color fillColor = getBrighterColor(borderColor);
|
||||
|
||||
int w = getWidth();
|
||||
int h = getHeight();
|
||||
|
||||
g.setColor(fillColor);
|
||||
g.fillRect(0,0, w,h);
|
||||
|
||||
g.setColor(borderColor);
|
||||
g.drawRect(0,0, w-1,h-1);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeNotify() {
|
||||
super.removeNotify();
|
||||
ToolTipManager.sharedInstance().unregisterComponent(this);
|
||||
removeMouseListener(listener);
|
||||
}
|
||||
|
||||
public void updateLocation() {
|
||||
int line = notices.get(0).getLine();
|
||||
int y = lineToY(line);
|
||||
setLocation(2, y);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
Line=Line: {0}
|
||||
MultipleMarkers=Multiple markers at this line:
|
||||
OccurrenceOf=Occurrence of "{0}"
|
||||
@@ -0,0 +1,3 @@
|
||||
Line=Line: {0}
|
||||
MultipleMarkers=\u0647\u0646\u0627\u0643 \u0639\u062f\u0629 \u062a\u062d\u062f\u064a\u062f\u0627\u062a \u0641\u064a \u0647\u0630\u0627 \u0627\u0644\u0633\u0637\u0631:
|
||||
OccurrenceOf=\u0639\u062f\u062f \u0645\u0631\u0627\u062a \u062d\u062f\u0648\u062b"{0}"
|
||||
@@ -0,0 +1,3 @@
|
||||
Line=Zeile: {0}
|
||||
MultipleMarkers=Mehrfache Markierungen in dieser Zeile:
|
||||
OccurrenceOf=Auftreten von "{0}"
|
||||
@@ -0,0 +1,3 @@
|
||||
Line=Linea: {0}
|
||||
MultipleMarkers=M\u00faltiples marcadores en esta linea:
|
||||
OccurrenceOf=Ocurrencia de "{0}"
|
||||
@@ -0,0 +1,3 @@
|
||||
Line=Ligne: {0}
|
||||
MultipleMarkers=Plusieurs rep\u00e8res sur cette ligne:
|
||||
OccurrenceOf=Pr\u00e9sence de "{0}"
|
||||
@@ -0,0 +1,3 @@
|
||||
Line=Sor: {0}
|
||||
MultipleMarkers=T\u00f6bb jel\u00f6l\u0151 ebben a sorban:
|
||||
OccurrenceOf=A(z) "{0}" el\u0151fordul\u00e1sa
|
||||
@@ -0,0 +1,3 @@
|
||||
Line=Line: {0}
|
||||
MultipleMarkers=Multiple markers at this line:
|
||||
OccurrenceOf=Occurrence of "{0}"
|
||||
@@ -0,0 +1,3 @@
|
||||
Line=Linea: {0}
|
||||
MultipleMarkers=Multipli punti a questa linea:
|
||||
OccurrenceOf=Occorrenze di "{0}"
|
||||
@@ -0,0 +1,3 @@
|
||||
Line=\u884c: {0}
|
||||
MultipleMarkers=\u3053\u306e\u884c\u306e\u4e2d\u306b\u3044\u304f\u3064\u306e\u30de\u30fc\u30ab\u30fc\u306f\u3042\u308a\u307e\u3059:
|
||||
OccurrenceOf=\u51fa\u73fe\u56de\u6570 "{0}"
|
||||
@@ -0,0 +1,3 @@
|
||||
Line=\ud589: {0}
|
||||
MultipleMarkers=\uc774 \ud589\uc5d0 \ubcf5\uc218\uc758 \ub9c8\ucee4 \uc874\uc7ac:
|
||||
OccurrenceOf="{0}" \ubc1c\uc0dd
|
||||
@@ -0,0 +1,3 @@
|
||||
Line=Line: {0}
|
||||
MultipleMarkers=Multiple markers at this line:
|
||||
OccurrenceOf=Occurrence of "{0}"
|
||||
@@ -0,0 +1,3 @@
|
||||
Line=Linia: {0}
|
||||
MultipleMarkers=Wiele znacznik w tej linii:
|
||||
OccurrenceOf=Wyst\u0119powanie "{0}"
|
||||
@@ -0,0 +1,3 @@
|
||||
Line=Linha: {0}
|
||||
MultipleMarkers=M\u00faltiplos marcadores nesta linha:
|
||||
OccurrenceOf=Ocorr\u00eancia de: "{0}"
|
||||
@@ -0,0 +1,3 @@
|
||||
Line=\u0421\u0442\u0440\u043e\u043a\u0430: {0}
|
||||
MultipleMarkers=\u041d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043c\u0430\u0440\u043a\u0435\u0440\u043e\u0432 \u0432 \u044d\u0442\u043e\u0439 \u0441\u0442\u0440\u043e\u043a\u0435:
|
||||
OccurrenceOf=\u0412\u043e\u0437\u043d\u0438\u043a\u043d\u043e\u0432\u0435\u043d\u0438\u0435 "{0}"
|
||||
@@ -0,0 +1,3 @@
|
||||
Line=Sat\u0131r: {0}
|
||||
MultipleMarkers=Bu sat\u0131rda birden fazla i\u015faret\u00e7i:
|
||||
OccurrenceOf="{0}"\u0131n bulunmas\u0131
|
||||
@@ -0,0 +1,3 @@
|
||||
Line=\u884c: {0}
|
||||
MultipleMarkers=\u6b64\u884c\u6709\u591a\u6807\u8bb0
|
||||
OccurrenceOf=\u6709"{0}"\u51fa\u73b0
|
||||
@@ -0,0 +1,3 @@
|
||||
Line=Line: {0}
|
||||
MultipleMarkers=Multiple markers at this line:
|
||||
OccurrenceOf=Occurrence of "{0}"
|
||||
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
* 11/13/2008
|
||||
*
|
||||
* FileFileLocation.java - The location of a local file.
|
||||
*
|
||||
* 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.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
|
||||
/**
|
||||
* The location of a local file.
|
||||
*
|
||||
* @author Robert Futrell
|
||||
* @version 1.0
|
||||
*/
|
||||
class FileFileLocation extends FileLocation {
|
||||
|
||||
/**
|
||||
* The file. This may or may not actually exist.
|
||||
*/
|
||||
private File file;
|
||||
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param file The local file.
|
||||
*/
|
||||
FileFileLocation(File file) {
|
||||
try {
|
||||
// Useful on Windows and OS X.
|
||||
this.file = file.getCanonicalFile();
|
||||
} catch (IOException ioe) {
|
||||
this.file = file;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
protected long getActualLastModified() {
|
||||
return file.lastModified();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the full path to the file.
|
||||
*
|
||||
* @return The full path to the file.
|
||||
* @see #getFileName()
|
||||
*/
|
||||
@Override
|
||||
public String getFileFullPath() {
|
||||
return file.getAbsolutePath();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public String getFileName() {
|
||||
return file.getName();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
protected InputStream getInputStream() throws IOException {
|
||||
return new FileInputStream(file);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
protected OutputStream getOutputStream() throws IOException {
|
||||
return new FileOutputStream(file);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Always returns <code>true</code>.
|
||||
*
|
||||
* @return <code>true</code> always.
|
||||
* @see #isLocalAndExists()
|
||||
*/
|
||||
@Override
|
||||
public boolean isLocal() {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Since file locations of this type are guaranteed to be local, this
|
||||
* method returns whether the file exists.
|
||||
*
|
||||
* @return Whether this local file actually exists.
|
||||
* @see #isLocal()
|
||||
*/
|
||||
@Override
|
||||
public boolean isLocalAndExists() {
|
||||
return file.exists();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
* 11/13/2008
|
||||
*
|
||||
* FileLocation.java - Holds the location of a local or remote file.
|
||||
*
|
||||
* 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.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
|
||||
|
||||
/**
|
||||
* Holds the location of a local or remote file. This provides a common way
|
||||
* to read, write, and check properties of both local and remote files.
|
||||
*
|
||||
* @author Robert Futrell
|
||||
* @version 1.0
|
||||
*/
|
||||
public abstract class FileLocation {
|
||||
|
||||
|
||||
/**
|
||||
* Creates a {@link FileLocation} instance for the specified local file.
|
||||
*
|
||||
* @param fileFullPath The full path to a local file.
|
||||
* @return The file's location.
|
||||
*/
|
||||
public static FileLocation create(String fileFullPath) {
|
||||
if (fileFullPath.startsWith("http://") ||
|
||||
fileFullPath.startsWith("https://") ||
|
||||
fileFullPath.startsWith("ftp://")) {
|
||||
try {
|
||||
return new URLFileLocation(new URL(fileFullPath));
|
||||
} catch (MalformedURLException mue) {
|
||||
throw new IllegalArgumentException(
|
||||
"Not a valid URL: " + fileFullPath, mue);
|
||||
}
|
||||
}
|
||||
return new FileFileLocation(new File(fileFullPath));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a {@link FileLocation} instance for the specified local file.
|
||||
*
|
||||
* @param file A local file.
|
||||
* @return The file's location.
|
||||
*/
|
||||
public static FileLocation create(File file) {
|
||||
return new FileFileLocation(file);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a {@link FileLocation} instance for the specified file.
|
||||
*
|
||||
* @param url The URL of a file.
|
||||
* @return The file's location.
|
||||
*/
|
||||
public static FileLocation create(URL url) {
|
||||
if ("file".equalsIgnoreCase(url.getProtocol())) {
|
||||
return new FileFileLocation(new File(url.getPath()));
|
||||
}
|
||||
return new URLFileLocation(url);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the last time this file was modified, or
|
||||
* {@link TextEditorPane#LAST_MODIFIED_UNKNOWN} if this value cannot be
|
||||
* computed (such as for a remote file).
|
||||
*
|
||||
* @return The last time this file was modified.
|
||||
*/
|
||||
protected abstract long getActualLastModified();
|
||||
|
||||
|
||||
/**
|
||||
* Returns the full path to the file. This will be stripped of
|
||||
* sensitive information such as passwords for remote files.
|
||||
*
|
||||
* @return The full path to the file.
|
||||
* @see #getFileName()
|
||||
*/
|
||||
public abstract String getFileFullPath();
|
||||
|
||||
|
||||
/**
|
||||
* Returns the name of the file.
|
||||
*
|
||||
* @return The name of the file.
|
||||
* @see #getFileFullPath()
|
||||
*/
|
||||
public abstract String getFileName();
|
||||
|
||||
|
||||
/**
|
||||
* Opens an input stream for reading from this file.
|
||||
*
|
||||
* @return The input stream.
|
||||
* @throws IOException If the file does not exist, or some other IO error
|
||||
* occurs.
|
||||
*/
|
||||
protected abstract InputStream getInputStream() throws IOException;
|
||||
|
||||
|
||||
/**
|
||||
* Opens an output stream for writing this file.
|
||||
*
|
||||
* @return An output stream.
|
||||
* @throws IOException If an IO error occurs.
|
||||
*/
|
||||
protected abstract OutputStream getOutputStream() throws IOException;
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether this file location is a local file.
|
||||
*
|
||||
* @return Whether this is a local file.
|
||||
* @see #isLocalAndExists()
|
||||
*/
|
||||
public abstract boolean isLocal();
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether this file location is a local file that already
|
||||
* exists.
|
||||
*
|
||||
* @return Whether this file is local and actually exists.
|
||||
* @see #isLocal()
|
||||
*/
|
||||
public abstract boolean isLocalAndExists();
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether this file location is a remote location.
|
||||
*
|
||||
* @return Whether this is a remote file location.
|
||||
*/
|
||||
public boolean isRemote() {
|
||||
return !isLocal();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,251 @@
|
||||
/*
|
||||
* 03/07/2012
|
||||
*
|
||||
* FoldingAwareIconRowHeader - Icon row header that paints itself correctly
|
||||
* even when code folding is enabled.
|
||||
*
|
||||
* 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.Graphics;
|
||||
import java.awt.Point;
|
||||
import javax.swing.Icon;
|
||||
import javax.swing.text.BadLocationException;
|
||||
import javax.swing.text.Document;
|
||||
import javax.swing.text.Element;
|
||||
|
||||
import org.fife.ui.rsyntaxtextarea.folding.FoldManager;
|
||||
import org.fife.ui.rtextarea.GutterIconInfo;
|
||||
import org.fife.ui.rtextarea.IconRowHeader;
|
||||
|
||||
|
||||
/**
|
||||
* A row header component that takes code folding into account when painting
|
||||
* itself.
|
||||
*
|
||||
* @author Robert Futrell
|
||||
* @version 1.0
|
||||
*/
|
||||
public class FoldingAwareIconRowHeader extends IconRowHeader {
|
||||
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param textArea The parent text area.
|
||||
*/
|
||||
public FoldingAwareIconRowHeader(RSyntaxTextArea textArea) {
|
||||
super(textArea);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
protected void paintComponent(Graphics g) {
|
||||
|
||||
// When line wrap is not enabled, take the faster code path.
|
||||
if (textArea==null) {
|
||||
return;
|
||||
}
|
||||
RSyntaxTextArea rsta = (RSyntaxTextArea)textArea;
|
||||
FoldManager fm = rsta.getFoldManager();
|
||||
if (!fm.isCodeFoldingSupportedAndEnabled()) {
|
||||
super.paintComponent(g);
|
||||
return;
|
||||
}
|
||||
|
||||
visibleRect = g.getClipBounds(visibleRect);
|
||||
if (visibleRect==null) { // ???
|
||||
visibleRect = getVisibleRect();
|
||||
}
|
||||
//System.out.println("IconRowHeader repainting: " + visibleRect);
|
||||
if (visibleRect==null) {
|
||||
return;
|
||||
}
|
||||
paintBackgroundImpl(g, visibleRect);
|
||||
|
||||
if (textArea.getLineWrap()) {
|
||||
paintComponentWrapped(g);
|
||||
return;
|
||||
}
|
||||
|
||||
Document doc = textArea.getDocument();
|
||||
Element root = doc.getDefaultRootElement();
|
||||
textAreaInsets = textArea.getInsets(textAreaInsets);
|
||||
if (visibleRect.y<textAreaInsets.top) {
|
||||
visibleRect.height -= (textAreaInsets.top - visibleRect.y);
|
||||
visibleRect.y = textAreaInsets.top;
|
||||
}
|
||||
|
||||
// Get the first line to paint.
|
||||
int cellHeight = textArea.getLineHeight();
|
||||
int topLine = (visibleRect.y-textAreaInsets.top)/cellHeight;
|
||||
|
||||
// Get where to start painting (top of the row).
|
||||
// We need to be "scrolled up" up just enough for the missing part of
|
||||
// the first line.
|
||||
int y = topLine*cellHeight + textAreaInsets.top;
|
||||
|
||||
// AFTER calculating visual offset to paint at, account for folding.
|
||||
topLine += fm.getHiddenLineCountAbove(topLine, true);
|
||||
|
||||
// Paint the active line range.
|
||||
if (activeLineRangeStart>-1 && activeLineRangeEnd>-1) {
|
||||
Color activeLineRangeColor = getActiveLineRangeColor();
|
||||
g.setColor(activeLineRangeColor);
|
||||
try {
|
||||
|
||||
int realY1 = rsta.yForLine(activeLineRangeStart);
|
||||
if (realY1>-1) { // Not in a collapsed fold...
|
||||
|
||||
int y1 = realY1;//Math.max(y, realY1);
|
||||
|
||||
int y2 = rsta.yForLine(activeLineRangeEnd);
|
||||
if (y2==-1) { // In a collapsed fold
|
||||
y2 = y1;
|
||||
}
|
||||
y2 += cellHeight - 1;
|
||||
|
||||
if (y2<visibleRect.y || y1>visibleRect.y+visibleRect.height) {
|
||||
//System.out.println("... nothing to paint, bailing...");
|
||||
return;
|
||||
}
|
||||
y1 = Math.max(y, realY1);
|
||||
y2 = Math.min(y2, visibleRect.y+visibleRect.height);
|
||||
//System.out.println(y1 + "... " + y2 + "; " + realY1 + ", " + visibleRect);
|
||||
|
||||
int j = y1;
|
||||
while (j<=y2) {
|
||||
int yEnd = Math.min(y2, j+getWidth());
|
||||
int xEnd = yEnd-j;
|
||||
g.drawLine(0,j, xEnd,yEnd);
|
||||
j += 2;
|
||||
}
|
||||
|
||||
int i = 2;
|
||||
while (i<getWidth()) {
|
||||
int yEnd = y1 + getWidth() - i;
|
||||
g.drawLine(i,y1, getWidth(),yEnd);
|
||||
i += 2;
|
||||
}
|
||||
|
||||
if (realY1>=y && realY1<visibleRect.y+visibleRect.height) {
|
||||
g.drawLine(0,realY1, getWidth(),realY1);
|
||||
}
|
||||
if (y2>=y && y2<visibleRect.y+visibleRect.height) {
|
||||
g.drawLine(0,y2, getWidth(),y2);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} catch (BadLocationException ble) {
|
||||
ble.printStackTrace(); // Never happens
|
||||
}
|
||||
}
|
||||
|
||||
// Paint icons
|
||||
if (trackingIcons!=null) {
|
||||
int lastLine = textArea.getLineCount() - 1;
|
||||
for (int i=trackingIcons.size()-1; i>=0; i--) { // Last to first
|
||||
GutterIconInfo ti = getTrackingIcon(i);
|
||||
int offs = ti.getMarkedOffset();
|
||||
if (offs>=0 && offs<=doc.getLength()) {
|
||||
int line = root.getElementIndex(offs);
|
||||
if (line<=lastLine && line>=topLine) {
|
||||
try {
|
||||
Icon icon = ti.getIcon();
|
||||
if (icon!=null) {
|
||||
int lineY = rsta.yForLine(line);
|
||||
if (lineY>=y && lineY<=visibleRect.y+visibleRect.height) {
|
||||
int y2 = lineY + (cellHeight-icon.getIconHeight())/2;
|
||||
icon.paintIcon(this, g, 0, y2);
|
||||
lastLine = line-1; // Paint only 1 icon per line
|
||||
}
|
||||
}
|
||||
} catch (BadLocationException ble) {
|
||||
ble.printStackTrace(); // Never happens
|
||||
}
|
||||
}
|
||||
else if (line<topLine) {
|
||||
break; // All other lines are above us, so quit now
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Paints icons when line wrapping is enabled. Note that this does not
|
||||
* override the parent class's implementation to avoid this version being
|
||||
* called when line wrapping is disabled.
|
||||
*/
|
||||
private void paintComponentWrapped(Graphics g) {
|
||||
|
||||
// The variables we use are as follows:
|
||||
// - visibleRect is the "visible" area of the text area; e.g.
|
||||
// [0,100, 300,100+(lineCount*cellHeight)-1].
|
||||
// actualTop.y is the topmost-pixel in the first logical line we
|
||||
// paint. Note that we may well not paint this part of the logical
|
||||
// line, as it may be broken into many physical lines, with the first
|
||||
// few physical lines scrolled past. Note also that this is NOT the
|
||||
// visible rect of this line number list; this line number list has
|
||||
// visible rect == [0,0, insets.left-1,visibleRect.height-1].
|
||||
|
||||
// We avoid using modelToView/viewToModel where possible, as these
|
||||
// methods trigger a parsing of the line into syntax tokens, which is
|
||||
// costly. It's cheaper to just grab the child views' bounds.
|
||||
|
||||
RSyntaxTextArea rsta = (RSyntaxTextArea)textArea;
|
||||
// boolean currentLineHighlighted = textArea.getHighlightCurrentLine();
|
||||
Document doc = textArea.getDocument();
|
||||
Element root = doc.getDefaultRootElement();
|
||||
int topPosition = textArea.viewToModel(
|
||||
new Point(visibleRect.x,visibleRect.y));
|
||||
int topLine = root.getElementIndex(topPosition);
|
||||
|
||||
int topY = visibleRect.y;
|
||||
int bottomY = visibleRect.y + visibleRect.height;
|
||||
int cellHeight = textArea.getLineHeight();
|
||||
|
||||
// Paint icons
|
||||
if (trackingIcons!=null) {
|
||||
int lastLine = textArea.getLineCount() - 1;
|
||||
for (int i=trackingIcons.size()-1; i>=0; i--) { // Last to first
|
||||
GutterIconInfo ti = getTrackingIcon(i);
|
||||
Icon icon = ti.getIcon();
|
||||
if (icon!=null) {
|
||||
int iconH = icon.getIconHeight();
|
||||
int offs = ti.getMarkedOffset();
|
||||
if (offs>=0 && offs<=doc.getLength()) {
|
||||
int line = root.getElementIndex(offs);
|
||||
if (line<=lastLine && line>=topLine) {
|
||||
try {
|
||||
int lineY = rsta.yForLine(line);
|
||||
if (lineY<=bottomY && (lineY+iconH>=topY)) {
|
||||
int y2 = lineY + (cellHeight-iconH)/2;
|
||||
ti.getIcon().paintIcon(this, g, 0, y2);
|
||||
lastLine = line-1; // Paint only 1 icon per line
|
||||
}
|
||||
} catch (BadLocationException ble) {
|
||||
ble.printStackTrace(); // Never happens
|
||||
}
|
||||
}
|
||||
else if (line<topLine) {
|
||||
break; // All other lines are above us, so quit now
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,411 @@
|
||||
/*
|
||||
* 12/01/2014
|
||||
*
|
||||
* Copyright (C) 2013 Robert Futrell
|
||||
* robert_futrell at users.sourceforge.net
|
||||
* http://fifesoft.com/rsyntaxtextarea
|
||||
*
|
||||
* This library is distributed under a modified BSD license. See the included
|
||||
* RSyntaxTextArea.License.txt file for details.
|
||||
*/
|
||||
package org.fife.ui.rsyntaxtextarea;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.swing.text.BadLocationException;
|
||||
import javax.swing.text.Element;
|
||||
|
||||
import org.fife.ui.rtextarea.SmartHighlightPainter;
|
||||
|
||||
|
||||
/**
|
||||
* Marks occurrences of the current token for HTML. Tags that require a
|
||||
* closing tag have their "opposite" tag closed.
|
||||
*
|
||||
* @author Robert Futrell
|
||||
* @version 1.0
|
||||
*/
|
||||
public class HtmlOccurrenceMarker implements OccurrenceMarker {
|
||||
|
||||
private static final char[] CLOSE_TAG_START = { '<', '/' };
|
||||
private static final char[] TAG_SELF_CLOSE = { '/', '>' };
|
||||
|
||||
private static final Set<String> TAGS_REQUIRING_CLOSING =
|
||||
getRequiredClosingTags();
|
||||
|
||||
public static final Set<String> getRequiredClosingTags() {
|
||||
final String[] tags = {
|
||||
"html",
|
||||
"head",
|
||||
"title",
|
||||
"style",
|
||||
"script",
|
||||
"noscript",
|
||||
"body",
|
||||
"section",
|
||||
"nav",
|
||||
"article",
|
||||
"aside",
|
||||
"h1",
|
||||
"h2",
|
||||
"h3",
|
||||
"h4",
|
||||
"h5",
|
||||
"h6",
|
||||
"header",
|
||||
"footer",
|
||||
"address",
|
||||
"pre",
|
||||
"dialog",
|
||||
"blockquote",
|
||||
"ol",
|
||||
"ul",
|
||||
"dl",
|
||||
"a",
|
||||
"q",
|
||||
"cite",
|
||||
"em",
|
||||
"strong",
|
||||
"small",
|
||||
"mark",
|
||||
"dfn",
|
||||
"abbr",
|
||||
"time",
|
||||
"progress",
|
||||
"meter",
|
||||
"code",
|
||||
"var",
|
||||
"samp",
|
||||
"kbd",
|
||||
"sub",
|
||||
"sup",
|
||||
"span",
|
||||
"i",
|
||||
"b",
|
||||
"bdo",
|
||||
"ruby",
|
||||
"rt",
|
||||
"rp",
|
||||
"ins",
|
||||
"del",
|
||||
"figure",
|
||||
"iframe",
|
||||
"object",
|
||||
"video",
|
||||
"audio",
|
||||
"canvas",
|
||||
"map",
|
||||
"table",
|
||||
"caption",
|
||||
"form",
|
||||
"fieldset",
|
||||
"label",
|
||||
"button",
|
||||
"select",
|
||||
"datalist",
|
||||
"textarea",
|
||||
"output",
|
||||
"details",
|
||||
"bb",
|
||||
"menu",
|
||||
"legend",
|
||||
"div",
|
||||
// Obsolete elements
|
||||
"acronym",
|
||||
"applet",
|
||||
"big",
|
||||
"blink",
|
||||
"center",
|
||||
"dir",
|
||||
"font",
|
||||
"frame",
|
||||
"frameset",
|
||||
"isindex",
|
||||
"listing",
|
||||
"marquee",
|
||||
"nobr",
|
||||
"noembed",
|
||||
"noframes",
|
||||
"plaintext",
|
||||
"s",
|
||||
"spacer",
|
||||
"strike",
|
||||
"tt",
|
||||
"u",
|
||||
"xmp",
|
||||
};
|
||||
return new HashSet<String>(Arrays.asList(tags));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* If the caret is inside of a tag, this method returns the token
|
||||
* representing the tag name; otherwise, <code>null</code> is returned.<p>
|
||||
*
|
||||
* Currently, this method only checks for tag names on the same line as
|
||||
* the caret, for simplicity. In the future it could check prior lines
|
||||
* until the tag name is found.
|
||||
*
|
||||
* @param textArea The text area.
|
||||
* @param occurrenceMarker The occurrence marker.
|
||||
* @return The token to mark occurrences of. Note that, if the
|
||||
* specified occurrence marker identifies tokens other than
|
||||
* tag names, these other element types may be returned.
|
||||
*/
|
||||
public static final Token getTagNameTokenForCaretOffset(
|
||||
RSyntaxTextArea textArea, OccurrenceMarker occurrenceMarker) {
|
||||
|
||||
// Get the tag name token.
|
||||
// For now, we only check for tags on the current line, for simplicity.
|
||||
|
||||
int dot = textArea.getCaretPosition();
|
||||
Token t = textArea.getTokenListForLine(textArea.getCaretLineNumber());
|
||||
Token toMark = null;
|
||||
|
||||
while (t!=null && t.isPaintable()) {
|
||||
if (t.getType()==Token.MARKUP_TAG_NAME) {
|
||||
toMark = t;
|
||||
}
|
||||
// Check for the token containing the caret before checking
|
||||
// if it's the close token.
|
||||
if (t.getEndOffset()==dot || t.containsPosition(dot)) {
|
||||
// Some languages, like PHP, mark functions/variables (PHP,
|
||||
// JavaScirpt) as well as HTML tags.
|
||||
if (occurrenceMarker.isValidType(textArea, t) &&
|
||||
t.getType()!=Token.MARKUP_TAG_NAME) {
|
||||
return t;
|
||||
}
|
||||
if (t.containsPosition(dot)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (t.getType()==Token.MARKUP_TAG_DELIMITER) {
|
||||
if (t.isSingleChar('>') || t.is(TAG_SELF_CLOSE)) {
|
||||
toMark = null;
|
||||
}
|
||||
}
|
||||
t = t.getNextToken();
|
||||
}
|
||||
|
||||
return toMark;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public Token getTokenToMark(RSyntaxTextArea textArea) {
|
||||
return getTagNameTokenForCaretOffset(textArea, this);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public boolean isValidType(RSyntaxTextArea textArea, Token t) {
|
||||
return textArea.getMarkOccurrencesOfTokenType(t.getType());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void markOccurrences(RSyntaxDocument doc, Token t,
|
||||
RSyntaxTextAreaHighlighter h, SmartHighlightPainter p) {
|
||||
|
||||
if (t.getType()!=Token.MARKUP_TAG_NAME) {
|
||||
DefaultOccurrenceMarker.markOccurrencesOfToken(doc, t, h, p);
|
||||
return;
|
||||
}
|
||||
|
||||
String lexemeStr = t.getLexeme();
|
||||
char[] lexeme = lexemeStr.toCharArray();
|
||||
lexemeStr = lexemeStr.toLowerCase();
|
||||
int tokenOffs = t.getOffset();
|
||||
Element root = doc.getDefaultRootElement();
|
||||
int lineCount = root.getElementCount();
|
||||
int curLine = root.getElementIndex(t.getOffset());
|
||||
int depth = 0;
|
||||
|
||||
// For now, we only check for tags on the current line, for
|
||||
// simplicity. Tags spanning multiple lines aren't common anyway.
|
||||
boolean found = false;
|
||||
boolean forward = true;
|
||||
t = doc.getTokenListForLine(curLine);
|
||||
while (t!=null && t.isPaintable()) {
|
||||
if (t.getType()==Token.MARKUP_TAG_DELIMITER) {
|
||||
if (t.isSingleChar('<') && t.getOffset()+1==tokenOffs) {
|
||||
// Don't try to match a tag that is optionally closed (or
|
||||
// closing is forbidden entirely).
|
||||
if (TAGS_REQUIRING_CLOSING.contains(lexemeStr)) {
|
||||
found = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
else if (t.is(CLOSE_TAG_START) && t.getOffset()+2==tokenOffs) {
|
||||
// Searching backward, we assume we can find the opening
|
||||
// tag. Don't really care if it's valid or not.
|
||||
found = true;
|
||||
forward = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
t = t.getNextToken();
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (forward) {
|
||||
|
||||
t = t.getNextToken().getNextToken();
|
||||
|
||||
do {
|
||||
|
||||
while (t!=null && t.isPaintable()) {
|
||||
if (t.getType()==Token.MARKUP_TAG_DELIMITER) {
|
||||
if (t.is(CLOSE_TAG_START)) {
|
||||
Token match = t.getNextToken();
|
||||
if (match!=null && match.is(lexeme)) {
|
||||
if (depth>0) {
|
||||
depth--;
|
||||
}
|
||||
else {
|
||||
try {
|
||||
int end = match.getOffset() + match.length();
|
||||
h.addMarkedOccurrenceHighlight(match.getOffset(), end, p);
|
||||
end = tokenOffs + match.length();
|
||||
h.addMarkedOccurrenceHighlight(tokenOffs, end, p);
|
||||
} catch (BadLocationException ble) {
|
||||
ble.printStackTrace(); // Never happens
|
||||
}
|
||||
return; // We're done!
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (t.isSingleChar('<')) {
|
||||
t = t.getNextToken();
|
||||
if (t!=null && t.is(lexeme)) {
|
||||
depth++;
|
||||
}
|
||||
}
|
||||
}
|
||||
t = t==null ? null : t.getNextToken();
|
||||
}
|
||||
|
||||
if (++curLine<lineCount) {
|
||||
t = doc.getTokenListForLine(curLine);
|
||||
}
|
||||
|
||||
} while (curLine<lineCount);
|
||||
|
||||
|
||||
}
|
||||
|
||||
else { // !forward
|
||||
|
||||
// Idea: Get all opening and closing tags of the relevant type on
|
||||
// the current line. Find the opening tag paired to the closing
|
||||
// tag we found originally; if it's not on this line, keep going
|
||||
// to the previous line.
|
||||
|
||||
List<Entry> openCloses = new ArrayList<Entry>();
|
||||
boolean inPossibleMatch = false;
|
||||
t = doc.getTokenListForLine(curLine);
|
||||
final int endBefore = tokenOffs - 2; // Stop before "</".
|
||||
|
||||
do {
|
||||
|
||||
while (t!=null && t.getOffset()<endBefore && t.isPaintable()) {
|
||||
if (t.getType()==Token.MARKUP_TAG_DELIMITER) {
|
||||
if (t.isSingleChar('<')) {
|
||||
Token next = t.getNextToken();
|
||||
if (next!=null) {
|
||||
if (next.is(lexeme)) {
|
||||
openCloses.add(new Entry(true, next));
|
||||
inPossibleMatch = true;
|
||||
}
|
||||
else {
|
||||
inPossibleMatch = false;
|
||||
}
|
||||
t = next;
|
||||
}
|
||||
}
|
||||
else if (t.isSingleChar('>')) {
|
||||
inPossibleMatch = false;
|
||||
}
|
||||
else if (inPossibleMatch && t.is(TAG_SELF_CLOSE)) {
|
||||
openCloses.remove(openCloses.size()-1);
|
||||
inPossibleMatch = false;
|
||||
}
|
||||
else if (t.is(CLOSE_TAG_START)) {
|
||||
Token next = t.getNextToken();
|
||||
if (next!=null) {
|
||||
// Invalid XML might not have a match
|
||||
if (next.is(lexeme)) {
|
||||
openCloses.add(new Entry(false, next));
|
||||
}
|
||||
t = next;
|
||||
}
|
||||
}
|
||||
}
|
||||
t = t.getNextToken();
|
||||
}
|
||||
|
||||
for (int i=openCloses.size()-1; i>=0; i--) {
|
||||
Entry entry = openCloses.get(i);
|
||||
depth += entry.open ? -1 : 1;
|
||||
if (depth==-1) {
|
||||
try {
|
||||
Token match = entry.t;
|
||||
int end = match.getOffset() + match.length();
|
||||
h.addMarkedOccurrenceHighlight(match.getOffset(), end, p);
|
||||
end = tokenOffs + match.length();
|
||||
h.addMarkedOccurrenceHighlight(tokenOffs, end, p);
|
||||
} catch (BadLocationException ble) {
|
||||
ble.printStackTrace(); // Never happens
|
||||
}
|
||||
openCloses.clear();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
openCloses.clear();
|
||||
if (--curLine>=0) {
|
||||
t = doc.getTokenListForLine(curLine);
|
||||
}
|
||||
|
||||
} while (curLine>=0);
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Used internally when searching backward for a matching "open" tag.
|
||||
*/
|
||||
private static class Entry {
|
||||
|
||||
private boolean open;
|
||||
private Token t;
|
||||
|
||||
Entry(boolean open, Token t) {
|
||||
this.open = open;
|
||||
this.t = t;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 02/16/2012
|
||||
*
|
||||
* Copyright (C) 2013 Robert Futrell
|
||||
* robert_futrell at users.sourceforge.net
|
||||
* http://fifesoft.com/rsyntaxtextarea
|
||||
*
|
||||
* This library is distributed under a modified BSD license. See the included
|
||||
* RSyntaxTextArea.License.txt file for details.
|
||||
*/
|
||||
package org.fife.ui.rsyntaxtextarea;
|
||||
|
||||
|
||||
/**
|
||||
* Generates hyperlinks in a document. If one of these is installed on an
|
||||
* <code>RSyntaxTextArea</code> it is queried when the mouse is moved and
|
||||
* hyperlinks are enabled. If the user is not hovering over a "real" hyperlink
|
||||
* (e.g. "http://www.google.com"), the link generator is asked if a text region
|
||||
* at the mouse position should be considered a hyperlink. If so, a result
|
||||
* object is returned, describing exactly what region of text is the link, and
|
||||
* where it goes to.<p>
|
||||
*
|
||||
* This interface is typically used by applications providing advanced support
|
||||
* for programming languages, such as IDEs. For example, an implementation of
|
||||
* this class could identify the token under the mouse position as a "variable,"
|
||||
* and the hyperlink returned would select the variable's declaration in the
|
||||
* document.
|
||||
*
|
||||
* @author Robert Futrell
|
||||
* @version 1.0
|
||||
*/
|
||||
public interface LinkGenerator {
|
||||
|
||||
|
||||
/**
|
||||
* If a region of text under the mouse position should be considered a
|
||||
* hyperlink, a result object is returned. This object describes what
|
||||
* region of text is the link, and what action to perform if the link is
|
||||
* clicked.
|
||||
*
|
||||
* @param textArea The text component.
|
||||
* @param offs The offset in the document under the mouse position.
|
||||
* @return The link information, or <code>null</code> if no link is at the
|
||||
* specified offset.
|
||||
*/
|
||||
LinkGeneratorResult isLinkAtOffset(RSyntaxTextArea textArea, int offs);
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* 02/16/2012
|
||||
*
|
||||
* Copyright (C) 2013 Robert Futrell
|
||||
* robert_futrell at users.sourceforge.net
|
||||
* http://fifesoft.com/rsyntaxtextarea
|
||||
*
|
||||
* This library is distributed under a modified BSD license. See the included
|
||||
* RSyntaxTextArea.License.txt file for details.
|
||||
*/
|
||||
package org.fife.ui.rsyntaxtextarea;
|
||||
|
||||
import javax.swing.event.HyperlinkEvent;
|
||||
|
||||
|
||||
/**
|
||||
* A result object from a {@link LinkGenerator}. Implementations of this class
|
||||
* specify what action to execute when the user clicks on the "link" specified
|
||||
* by the <code>LinkGenerator</code>. Typically, this will do something like
|
||||
* select another region of text in the document (the declaration of the
|
||||
* variable at the mouse position), or open another file in the parent
|
||||
* application, etc.
|
||||
*
|
||||
* @author Robert Futrell
|
||||
* @version 1.0
|
||||
* @see SelectRegionLinkGeneratorResult
|
||||
*/
|
||||
public interface LinkGeneratorResult {
|
||||
|
||||
|
||||
/**
|
||||
* Executes the action associated with this object. If the result is a
|
||||
* URL to open, a standard hyperlink event can be returned. Alternatively,
|
||||
* <code>null</code> can be returned and the action performed in this
|
||||
* method itself.
|
||||
*
|
||||
* @return The hyperlink event to broadcast from the text area, or
|
||||
* <code>null</code> if the action's behavior occurs in this method
|
||||
* directly.
|
||||
*/
|
||||
HyperlinkEvent execute();
|
||||
|
||||
|
||||
/**
|
||||
* Returns the starting offset of the link specified by the parent
|
||||
* <code>LinkGenerator</code>.
|
||||
*
|
||||
* @return The offset.
|
||||
*/
|
||||
int getSourceOffset();
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,289 @@
|
||||
/*
|
||||
* 01/06/2009
|
||||
*
|
||||
* MarkOccurrencesSupport.java - Handles marking all occurrences of the
|
||||
* currently selected identifier in a text area.
|
||||
*
|
||||
* 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.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
|
||||
import javax.swing.Timer;
|
||||
import javax.swing.event.CaretEvent;
|
||||
import javax.swing.event.CaretListener;
|
||||
import javax.swing.text.Caret;
|
||||
|
||||
import org.fife.ui.rtextarea.SmartHighlightPainter;
|
||||
|
||||
|
||||
/**
|
||||
* Marks all occurrences of the token at the current caret position, if it is
|
||||
* an identifier.
|
||||
*
|
||||
* @author Robert Futrell
|
||||
* @version 1.0
|
||||
* @see OccurrenceMarker
|
||||
*/
|
||||
class MarkOccurrencesSupport implements CaretListener, ActionListener {
|
||||
|
||||
private RSyntaxTextArea textArea;
|
||||
private Timer timer;
|
||||
private SmartHighlightPainter p;
|
||||
|
||||
/**
|
||||
* The default color used to mark occurrences.
|
||||
*/
|
||||
static final Color DEFAULT_COLOR = new Color(224, 224, 224);
|
||||
|
||||
/**
|
||||
* The default delay.
|
||||
*/
|
||||
static final int DEFAULT_DELAY_MS = 1000;
|
||||
|
||||
|
||||
/**
|
||||
* Constructor. Creates a listener with a 1 second delay.
|
||||
*/
|
||||
MarkOccurrencesSupport() {
|
||||
this(DEFAULT_DELAY_MS);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param delay The delay between when the caret last moves and when the
|
||||
* text should be scanned for matching occurrences. This should
|
||||
* be in milliseconds.
|
||||
*/
|
||||
MarkOccurrencesSupport(int delay) {
|
||||
this(delay, DEFAULT_COLOR);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param delay The delay between when the caret last moves and when the
|
||||
* text should be scanned for matching occurrences. This should
|
||||
* be in milliseconds.
|
||||
* @param color The color to use to mark the occurrences. This cannot be
|
||||
* <code>null</code>.
|
||||
*/
|
||||
MarkOccurrencesSupport(int delay, Color color) {
|
||||
timer = new Timer(delay, this);
|
||||
timer.setRepeats(false);
|
||||
p = new SmartHighlightPainter();
|
||||
setColor(color);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Called after the caret has been moved and a fixed time delay has
|
||||
* elapsed. This locates and highlights all occurrences of the identifier
|
||||
* at the caret position, if any.<p>
|
||||
*
|
||||
* Callers should not call this method directly, but should rather prefer
|
||||
* {@link #doMarkOccurrences()} to mark occurrences.
|
||||
*
|
||||
* @param e The event.
|
||||
* @see #doMarkOccurrences()
|
||||
*/
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
|
||||
// Don't do anything if they are selecting text.
|
||||
Caret c = textArea.getCaret();
|
||||
if (c.getDot()!=c.getMark()) {
|
||||
return;
|
||||
}
|
||||
|
||||
RSyntaxDocument doc = (RSyntaxDocument)textArea.getDocument();
|
||||
OccurrenceMarker occurrenceMarker = doc.getOccurrenceMarker();
|
||||
boolean occurrencesChanged = false;
|
||||
|
||||
if (occurrenceMarker!=null) {
|
||||
|
||||
doc.readLock();
|
||||
try {
|
||||
|
||||
Token t = occurrenceMarker.getTokenToMark(textArea);
|
||||
|
||||
if (t!=null && occurrenceMarker.isValidType(textArea, t) &&
|
||||
!RSyntaxUtilities.isNonWordChar(t)) {
|
||||
clear();
|
||||
RSyntaxTextAreaHighlighter h = (RSyntaxTextAreaHighlighter)
|
||||
textArea.getHighlighter();
|
||||
occurrenceMarker.markOccurrences(doc, t, h, p);
|
||||
//textArea.repaint();
|
||||
// TODO: Do a textArea.repaint() instead of repainting each
|
||||
// marker as it's added if count is huge
|
||||
occurrencesChanged = true;
|
||||
}
|
||||
|
||||
} finally {
|
||||
doc.readUnlock();
|
||||
//time = System.currentTimeMillis() - time;
|
||||
//System.out.println("MarkOccurrencesSupport took: " + time + " ms");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (occurrencesChanged) {
|
||||
textArea.fireMarkedOccurrencesChanged();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Called when the caret moves in the text area.
|
||||
*
|
||||
* @param e The event.
|
||||
*/
|
||||
@Override
|
||||
public void caretUpdate(CaretEvent e) {
|
||||
timer.restart();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Removes all highlights added to the text area by this listener.
|
||||
*/
|
||||
void clear() {
|
||||
if (textArea!=null) {
|
||||
RSyntaxTextAreaHighlighter h = (RSyntaxTextAreaHighlighter)
|
||||
textArea.getHighlighter();
|
||||
h.clearMarkOccurrencesHighlights();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Immediately marks all occurrences of the token at the current caret
|
||||
* position.
|
||||
*/
|
||||
public void doMarkOccurrences() {
|
||||
timer.stop();
|
||||
actionPerformed(null);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the color being used to mark occurrences.
|
||||
*
|
||||
* @return The color being used.
|
||||
* @see #setColor(Color)
|
||||
*/
|
||||
public Color getColor() {
|
||||
return (Color)p.getPaint();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the delay, in milliseconds.
|
||||
*
|
||||
* @return The delay.
|
||||
* @see #setDelay(int)
|
||||
*/
|
||||
public int getDelay() {
|
||||
return timer.getDelay();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether a border is painted around marked occurrences.
|
||||
*
|
||||
* @return Whether a border is painted.
|
||||
* @see #setPaintBorder(boolean)
|
||||
* @see #getColor()
|
||||
*/
|
||||
public boolean getPaintBorder() {
|
||||
return p.getPaintBorder();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Installs this listener on a text area. If it is already installed on
|
||||
* another text area, it is uninstalled first.
|
||||
*
|
||||
* @param textArea The text area to install on.
|
||||
*/
|
||||
public void install(RSyntaxTextArea textArea) {
|
||||
if (this.textArea!=null) {
|
||||
uninstall();
|
||||
}
|
||||
this.textArea = textArea;
|
||||
textArea.addCaretListener(this);
|
||||
if (textArea.getMarkOccurrencesColor()!=null) {
|
||||
setColor(textArea.getMarkOccurrencesColor());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the color to use when marking occurrences.
|
||||
*
|
||||
* @param color The color to use.
|
||||
* @see #getColor()
|
||||
* @see #setPaintBorder(boolean)
|
||||
*/
|
||||
public void setColor(Color color) {
|
||||
p.setPaint(color);
|
||||
if (textArea!=null) {
|
||||
clear();
|
||||
caretUpdate(null); // Force a highlight repaint.
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the delay between the last caret position change and when the
|
||||
* text is scanned for matching identifiers. A delay is needed to prevent
|
||||
* repeated scanning while the user is typing.
|
||||
*
|
||||
* @param delay The new delay.
|
||||
* @see #getDelay()
|
||||
*/
|
||||
public void setDelay(int delay) {
|
||||
timer.setInitialDelay(delay);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Toggles whether a border is painted around marked highlights.
|
||||
*
|
||||
* @param paint Whether to paint a border.
|
||||
* @see #getPaintBorder()
|
||||
* @see #setColor(Color)
|
||||
*/
|
||||
public void setPaintBorder(boolean paint) {
|
||||
if (paint!=p.getPaintBorder()) {
|
||||
p.setPaintBorder(paint);
|
||||
if (textArea!=null) {
|
||||
textArea.repaint();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Uninstalls this listener from the current text area. Does nothing if
|
||||
* it not currently installed on any text area.
|
||||
*
|
||||
* @see #install(RSyntaxTextArea)
|
||||
*/
|
||||
public void uninstall() {
|
||||
if (textArea!=null) {
|
||||
clear();
|
||||
textArea.removeCaretListener(this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
/*
|
||||
* 07/03/2016
|
||||
*
|
||||
* 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.BorderLayout;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Font;
|
||||
import java.awt.Point;
|
||||
import java.awt.Window;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ComponentEvent;
|
||||
import java.awt.event.ComponentListener;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.awt.event.WindowAdapter;
|
||||
import java.awt.event.WindowEvent;
|
||||
|
||||
import javax.swing.AbstractAction;
|
||||
import javax.swing.ActionMap;
|
||||
import javax.swing.BorderFactory;
|
||||
import javax.swing.InputMap;
|
||||
import javax.swing.JComponent;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JWindow;
|
||||
import javax.swing.KeyStroke;
|
||||
import javax.swing.SwingUtilities;
|
||||
import javax.swing.UIManager;
|
||||
import javax.swing.text.BadLocationException;
|
||||
|
||||
import org.fife.ui.rsyntaxtextarea.focusabletip.TipUtil;
|
||||
|
||||
|
||||
/**
|
||||
* A tool tip-like popup that shows the line of code containing the bracket
|
||||
* matched to that at the caret position, if it is scrolled out of the user's
|
||||
* viewport.
|
||||
*
|
||||
* @author Robert Futrell
|
||||
* @version 1.0
|
||||
*/
|
||||
class MatchedBracketPopup extends JWindow {
|
||||
|
||||
private RSyntaxTextArea textArea;
|
||||
|
||||
private transient Listener listener;
|
||||
|
||||
private static final int LEFT_EMPTY_BORDER = 5;
|
||||
|
||||
|
||||
MatchedBracketPopup(Window parent, RSyntaxTextArea textArea, int
|
||||
offsToRender) {
|
||||
|
||||
super(parent);
|
||||
this.textArea = textArea;
|
||||
JPanel cp = new JPanel(new BorderLayout());
|
||||
cp.setBorder(BorderFactory.createCompoundBorder(
|
||||
TipUtil.getToolTipBorder(),
|
||||
BorderFactory.createEmptyBorder(2, LEFT_EMPTY_BORDER, 5, 5)));
|
||||
cp.setBackground(TipUtil.getToolTipBackground());
|
||||
setContentPane(cp);
|
||||
|
||||
cp.add(new JLabel(getText(offsToRender)));
|
||||
|
||||
installKeyBindings();
|
||||
listener = new Listener();
|
||||
setLocation();
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Overridden to ensure this popup stays in a specific size range.
|
||||
*/
|
||||
@Override
|
||||
public Dimension getPreferredSize() {
|
||||
Dimension size = super.getPreferredSize();
|
||||
if (size!=null) {
|
||||
size.width = Math.min(size.width, 800);
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
|
||||
private String getText(int offsToRender) {
|
||||
|
||||
int line = 0;
|
||||
try {
|
||||
line = textArea.getLineOfOffset(offsToRender);
|
||||
} catch (BadLocationException ble) {
|
||||
ble.printStackTrace(); // Never happens
|
||||
return null;
|
||||
}
|
||||
|
||||
int lastLine = line + 1;
|
||||
|
||||
// Render prior line if the open brace line has no other text on it
|
||||
if (line > 0) {
|
||||
try {
|
||||
int startOffs = textArea.getLineStartOffset(line);
|
||||
int length = textArea.getLineEndOffset(line) - startOffs;
|
||||
String text = textArea.getText(startOffs, length);
|
||||
if (text.trim().length() == 1) {
|
||||
line--;
|
||||
}
|
||||
} catch (BadLocationException ble) {
|
||||
UIManager.getLookAndFeel().provideErrorFeedback(textArea);
|
||||
ble.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
Font font = textArea.getFontForTokenType(TokenTypes.IDENTIFIER);
|
||||
StringBuilder sb = new StringBuilder("<html>");
|
||||
sb.append("<style>body { font-size:\"").append(font.getSize());
|
||||
sb.append("pt\" }</style><nobr>");
|
||||
while (line < lastLine) {
|
||||
Token t = textArea.getTokenListForLine(line);
|
||||
while (t!=null && t.isPaintable()) {
|
||||
t.appendHTMLRepresentation(sb, textArea, true, true);
|
||||
t = t.getNextToken();
|
||||
}
|
||||
sb.append("<br>");
|
||||
line++;
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds key bindings to this popup.
|
||||
*/
|
||||
private void installKeyBindings() {
|
||||
|
||||
InputMap im = getRootPane().getInputMap(
|
||||
JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
|
||||
ActionMap am = getRootPane().getActionMap();
|
||||
|
||||
KeyStroke escapeKS = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0);
|
||||
im.put(escapeKS, "onEscape");
|
||||
am.put("onEscape", new EscapeAction());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Positions this popup to be in the top right-hand corner of the parent
|
||||
* editor.
|
||||
*/
|
||||
private void setLocation() {
|
||||
Point topLeft = textArea.getVisibleRect().getLocation();
|
||||
SwingUtilities.convertPointToScreen(topLeft, textArea);
|
||||
topLeft.y = Math.max(topLeft.y - 24, 0);
|
||||
setLocation(topLeft.x - LEFT_EMPTY_BORDER, topLeft.y);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Action performed when Escape is pressed in this popup.
|
||||
*/
|
||||
private class EscapeAction extends AbstractAction {
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
listener.uninstallAndHide();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Listens for events in this popup.
|
||||
*/
|
||||
private class Listener extends WindowAdapter implements ComponentListener {
|
||||
|
||||
Listener() {
|
||||
|
||||
addWindowFocusListener(this);
|
||||
|
||||
// If anything happens to the "parent" window, hide this popup
|
||||
Window parent = (Window)getParent();
|
||||
parent.addWindowFocusListener(this);
|
||||
parent.addWindowListener(this);
|
||||
parent.addComponentListener(this);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void componentResized(ComponentEvent e) {
|
||||
uninstallAndHide();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void componentMoved(ComponentEvent e) {
|
||||
uninstallAndHide();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void componentShown(ComponentEvent e) {
|
||||
uninstallAndHide();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void componentHidden(ComponentEvent e) {
|
||||
uninstallAndHide();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void windowActivated(WindowEvent e) {
|
||||
checkForParentWindowEvent(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void windowLostFocus(WindowEvent e) {
|
||||
uninstallAndHide();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void windowIconified(WindowEvent e) {
|
||||
checkForParentWindowEvent(e);
|
||||
}
|
||||
|
||||
private boolean checkForParentWindowEvent(WindowEvent e) {
|
||||
if (e.getSource()==getParent()) {
|
||||
uninstallAndHide();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void uninstallAndHide() {
|
||||
Window parent = (Window)getParent();
|
||||
parent.removeWindowFocusListener(this);
|
||||
parent.removeWindowListener(this);
|
||||
parent.removeComponentListener(this);
|
||||
removeWindowFocusListener(this);
|
||||
setVisible(false);
|
||||
dispose();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* 03/09/2013
|
||||
*
|
||||
* OccurrenceMarker - Marks occurrences of the current token.
|
||||
*
|
||||
* This library is distributed under a modified BSD license. See the included
|
||||
* RSyntaxTextArea.License.txt file for details.
|
||||
*/
|
||||
package org.fife.ui.rsyntaxtextarea;
|
||||
|
||||
import org.fife.ui.rtextarea.SmartHighlightPainter;
|
||||
|
||||
|
||||
/**
|
||||
* An <code>OccurrenceMarker</code> is called when the caret stops moving after
|
||||
* a short period. If the current {@link TokenMaker} returns an instance of
|
||||
* this class, it is told to mark all occurrences of the identifier at the
|
||||
* caret position.
|
||||
*
|
||||
* @author Robert Futrell
|
||||
* @version 1.0
|
||||
*/
|
||||
public interface OccurrenceMarker {
|
||||
|
||||
|
||||
/**
|
||||
* Returns the token to mark occurrences, of, provided it matches the
|
||||
* criteria put forth by {@link #isValidType(RSyntaxTextArea, Token)}.
|
||||
* For most languages, this method should return the token at the caret
|
||||
* position.
|
||||
*
|
||||
* @param textArea The text area.
|
||||
* @return The token to (possibly) mark occurrences of, or
|
||||
* <code>null</code> if none.
|
||||
*/
|
||||
Token getTokenToMark(RSyntaxTextArea textArea);
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether the specified token is a type that we can do a
|
||||
* "mark occurrences" of. Typically, this will delegate to
|
||||
* {@link RSyntaxTextArea#getMarkOccurrencesOfTokenType(int)}.
|
||||
*
|
||||
* @param textArea The text area.
|
||||
* @param t The token.
|
||||
* @return Whether we should mark all occurrences of this token.
|
||||
*/
|
||||
boolean isValidType(RSyntaxTextArea textArea, Token t);
|
||||
|
||||
|
||||
/**
|
||||
* Called when occurrences of a token should be marked.
|
||||
*
|
||||
* @param doc The document.
|
||||
* @param t The document whose relevant occurrences should be marked.
|
||||
* @param h The highlighter to add the highlights to.
|
||||
* @param p The painter for the highlights.
|
||||
*/
|
||||
void markOccurrences(RSyntaxDocument doc, Token t,
|
||||
RSyntaxTextAreaHighlighter h, SmartHighlightPainter p);
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,829 @@
|
||||
/*
|
||||
* 09/26/2005
|
||||
*
|
||||
* ParserManager.java - Manages the parsing of an RSyntaxTextArea's document,
|
||||
* if necessary.
|
||||
*
|
||||
* 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.Point;
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.beans.PropertyChangeEvent;
|
||||
import java.beans.PropertyChangeListener;
|
||||
import java.net.URL;
|
||||
import java.security.AccessControlException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import javax.swing.Timer;
|
||||
import javax.swing.ToolTipManager;
|
||||
import javax.swing.event.DocumentEvent;
|
||||
import javax.swing.event.DocumentListener;
|
||||
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.Position;
|
||||
|
||||
import org.fife.ui.rsyntaxtextarea.parser.ParseResult;
|
||||
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.RDocument;
|
||||
import org.fife.ui.rtextarea.RTextAreaHighlighter.HighlightInfo;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Manages running a parser object for an <code>RSyntaxTextArea</code>.
|
||||
*
|
||||
* @author Robert Futrell
|
||||
* @version 0.9
|
||||
*/
|
||||
class ParserManager implements DocumentListener, ActionListener,
|
||||
HyperlinkListener, PropertyChangeListener {
|
||||
|
||||
private RSyntaxTextArea textArea;
|
||||
private List<Parser> parsers;
|
||||
private Timer timer;
|
||||
private boolean running;
|
||||
private Parser parserForTip;
|
||||
private Position firstOffsetModded;
|
||||
private Position lastOffsetModded;
|
||||
|
||||
/**
|
||||
* Mapping of notices to their highlights in the editor. Can't use a Map
|
||||
* since parsers could return two <code>ParserNotice</code>s that compare
|
||||
* equally via <code>equals()</code>. Real-world example: The Perl
|
||||
* compiler will return 2+ identical error messages if the same error is
|
||||
* committed in a single line more than once.
|
||||
*/
|
||||
private List<NoticeHighlightPair> noticeHighlightPairs;
|
||||
|
||||
/**
|
||||
* Painter used to underline errors.
|
||||
*/
|
||||
private SquiggleUnderlineHighlightPainter parserErrorHighlightPainter =
|
||||
new SquiggleUnderlineHighlightPainter(Color.RED);
|
||||
|
||||
/**
|
||||
* If this system property is set to <code>true</code>, debug messages
|
||||
* will be printed to stdout to help diagnose parsing issues.
|
||||
*/
|
||||
private static final String PROPERTY_DEBUG_PARSING = "rsta.debugParsing";
|
||||
|
||||
/**
|
||||
* Whether to print debug messages while running parsers.
|
||||
*/
|
||||
private static final boolean DEBUG_PARSING;
|
||||
|
||||
/**
|
||||
* The default delay between the last key press and when the document
|
||||
* is parsed, in milliseconds.
|
||||
*/
|
||||
private static final int DEFAULT_DELAY_MS = 1250;
|
||||
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param textArea The text area whose document the parser will be
|
||||
* parsing.
|
||||
*/
|
||||
ParserManager(RSyntaxTextArea textArea) {
|
||||
this(DEFAULT_DELAY_MS, textArea);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param delay The delay between the last key press and when the document
|
||||
* is parsed.
|
||||
* @param textArea The text area whose document the parser will be
|
||||
* parsing.
|
||||
*/
|
||||
ParserManager(int delay, RSyntaxTextArea textArea) {
|
||||
this.textArea = textArea;
|
||||
textArea.getDocument().addDocumentListener(this);
|
||||
textArea.addPropertyChangeListener("document", this);
|
||||
parsers = new ArrayList<Parser>(1); // Usually small
|
||||
timer = new Timer(delay, this);
|
||||
timer.setRepeats(false);
|
||||
running = true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Called when the timer fires (e.g. it's time to parse the document).
|
||||
*
|
||||
* @param e The event.
|
||||
*/
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
|
||||
// Sanity check - should have >1 parser if event is fired.
|
||||
int parserCount = getParserCount();
|
||||
if (parserCount==0) {
|
||||
return;
|
||||
}
|
||||
|
||||
long begin = 0;
|
||||
if (DEBUG_PARSING) {
|
||||
begin = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
RSyntaxDocument doc = (RSyntaxDocument)textArea.getDocument();
|
||||
|
||||
Element root = doc.getDefaultRootElement();
|
||||
int firstLine = firstOffsetModded==null ? 0 :
|
||||
root.getElementIndex(firstOffsetModded.getOffset());
|
||||
int lastLine = lastOffsetModded==null ? root.getElementCount()-1 :
|
||||
root.getElementIndex(lastOffsetModded.getOffset());
|
||||
firstOffsetModded = lastOffsetModded = null;
|
||||
if (DEBUG_PARSING) {
|
||||
System.out.println("[DEBUG]: Minimum lines to parse: " + firstLine + "-" + lastLine);
|
||||
}
|
||||
|
||||
String style = textArea.getSyntaxEditingStyle();
|
||||
doc.readLock();
|
||||
try {
|
||||
for (int i=0; i<parserCount; i++) {
|
||||
Parser parser = getParser(i);
|
||||
if (parser.isEnabled()) {
|
||||
ParseResult res = parser.parse(doc, style);
|
||||
addParserNoticeHighlights(res);
|
||||
}
|
||||
else {
|
||||
clearParserNoticeHighlights(parser);
|
||||
}
|
||||
}
|
||||
textArea.fireParserNoticesChange();
|
||||
} finally {
|
||||
doc.readUnlock();
|
||||
}
|
||||
|
||||
if (DEBUG_PARSING) {
|
||||
float time = (System.currentTimeMillis()-begin)/1000f;
|
||||
System.out.println("Total parsing time: " + time + " seconds");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds a parser for the text area.
|
||||
*
|
||||
* @param parser The new parser. If this is <code>null</code>, nothing
|
||||
* happens.
|
||||
* @see #getParser(int)
|
||||
* @see #removeParser(Parser)
|
||||
*/
|
||||
public void addParser(Parser parser) {
|
||||
if (parser!=null && !parsers.contains(parser)) {
|
||||
if (running) {
|
||||
timer.stop();
|
||||
}
|
||||
parsers.add(parser);
|
||||
if (parsers.size()==1) {
|
||||
// Okay to call more than once.
|
||||
ToolTipManager.sharedInstance().registerComponent(textArea);
|
||||
}
|
||||
if (running) {
|
||||
timer.restart();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds highlights for a list of parser notices. Any current notices
|
||||
* from the same Parser, in the same parsed range, are removed.
|
||||
*
|
||||
* @param res The result of a parsing.
|
||||
* @see #clearParserNoticeHighlights()
|
||||
*/
|
||||
private void addParserNoticeHighlights(ParseResult res) {
|
||||
|
||||
// Parsers are supposed to return at least empty ParseResults, but
|
||||
// we'll be defensive here.
|
||||
if (res==null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (DEBUG_PARSING) {
|
||||
System.out.println("[DEBUG]: Adding parser notices from " +
|
||||
res.getParser());
|
||||
}
|
||||
|
||||
if (noticeHighlightPairs==null) {
|
||||
noticeHighlightPairs = new ArrayList<NoticeHighlightPair>();
|
||||
}
|
||||
|
||||
removeParserNotices(res);
|
||||
|
||||
List<ParserNotice> notices = res.getNotices();
|
||||
if (notices.size()>0) { // Guaranteed non-null
|
||||
|
||||
RSyntaxTextAreaHighlighter h = (RSyntaxTextAreaHighlighter)
|
||||
textArea.getHighlighter();
|
||||
|
||||
for (ParserNotice notice : notices) {
|
||||
if (DEBUG_PARSING) {
|
||||
System.out.println("[DEBUG]: ... adding: " + notice);
|
||||
}
|
||||
try {
|
||||
HighlightInfo highlight = null;
|
||||
if (notice.getShowInEditor()) {
|
||||
highlight = h.addParserHighlight(notice,
|
||||
parserErrorHighlightPainter);
|
||||
}
|
||||
noticeHighlightPairs.add(new NoticeHighlightPair(notice, highlight));
|
||||
} catch (BadLocationException ble) { // Never happens
|
||||
ble.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (DEBUG_PARSING) {
|
||||
System.out.println("[DEBUG]: Done adding parser notices from " +
|
||||
res.getParser());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Called when the document is modified.
|
||||
*
|
||||
* @param e The document event.
|
||||
*/
|
||||
@Override
|
||||
public void changedUpdate(DocumentEvent e) {
|
||||
}
|
||||
|
||||
|
||||
private void clearParserNoticeHighlights() {
|
||||
RSyntaxTextAreaHighlighter h = (RSyntaxTextAreaHighlighter)
|
||||
textArea.getHighlighter();
|
||||
if (h!=null) {
|
||||
h.clearParserHighlights();
|
||||
}
|
||||
if (noticeHighlightPairs!=null) {
|
||||
noticeHighlightPairs.clear();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Removes all parser notice highlights for a specific parser.
|
||||
*
|
||||
* @param parser The parser whose highlights to remove.
|
||||
*/
|
||||
private void clearParserNoticeHighlights(Parser parser) {
|
||||
RSyntaxTextAreaHighlighter h = (RSyntaxTextAreaHighlighter)
|
||||
textArea.getHighlighter();
|
||||
if (h!=null) {
|
||||
h.clearParserHighlights(parser);
|
||||
}
|
||||
if (noticeHighlightPairs!=null) {
|
||||
Iterator<NoticeHighlightPair> i=noticeHighlightPairs.iterator();
|
||||
while (i.hasNext()) {
|
||||
NoticeHighlightPair pair = i.next();
|
||||
if (pair.notice.getParser()==parser) {
|
||||
i.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Removes all parsers and any highlights they have created.
|
||||
*
|
||||
* @see #addParser(Parser)
|
||||
*/
|
||||
public void clearParsers() {
|
||||
timer.stop();
|
||||
clearParserNoticeHighlights();
|
||||
parsers.clear();
|
||||
textArea.fireParserNoticesChange();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
Parser p = getParser(parser);
|
||||
RSyntaxDocument doc = (RSyntaxDocument)textArea.getDocument();
|
||||
String style = textArea.getSyntaxEditingStyle();
|
||||
doc.readLock();
|
||||
try {
|
||||
if (p.isEnabled()) {
|
||||
ParseResult res = p.parse(doc, style);
|
||||
addParserNoticeHighlights(res);
|
||||
}
|
||||
else {
|
||||
clearParserNoticeHighlights(p);
|
||||
}
|
||||
textArea.fireParserNoticesChange();
|
||||
} finally {
|
||||
doc.readUnlock();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the delay between the last "concurrent" edit and when the
|
||||
* document is re-parsed.
|
||||
*
|
||||
* @return The delay, in milliseconds.
|
||||
* @see #setDelay(int)
|
||||
*/
|
||||
public int getDelay() {
|
||||
return timer.getDelay();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the specified parser.
|
||||
*
|
||||
* @param index The index of the parser.
|
||||
* @return The parser.
|
||||
* @see #getParserCount()
|
||||
* @see #addParser(Parser)
|
||||
* @see #removeParser(Parser)
|
||||
*/
|
||||
public Parser getParser(int index) {
|
||||
return parsers.get(index);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the number of registered parsers.
|
||||
*
|
||||
* @return The number of registered parsers.
|
||||
*/
|
||||
public int getParserCount() {
|
||||
return parsers.size();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
List<ParserNotice> notices = new ArrayList<ParserNotice>();
|
||||
if (noticeHighlightPairs!=null) {
|
||||
for (NoticeHighlightPair pair : noticeHighlightPairs) {
|
||||
notices.add(pair.notice);
|
||||
}
|
||||
}
|
||||
return notices;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @return The tool tip to display, and possibly a hyperlink event handler.
|
||||
*/
|
||||
public ToolTipInfo getToolTipText(MouseEvent e) {
|
||||
|
||||
String tip = null;
|
||||
HyperlinkListener listener = null;
|
||||
parserForTip = null;
|
||||
Point p = e.getPoint();
|
||||
|
||||
// try {
|
||||
int pos = textArea.viewToModel(p);
|
||||
/*
|
||||
Highlighter.Highlight[] highlights = textArea.getHighlighter().
|
||||
getHighlights();
|
||||
for (int i=0; i<highlights.length; i++) {
|
||||
Highlighter.Highlight h = highlights[i];
|
||||
//if (h instanceof ParserNoticeHighlight) {
|
||||
// ParserNoticeHighlight pnh = (ParserNoticeHighlight)h;
|
||||
int start = h.getStartOffset();
|
||||
int end = h.getEndOffset();
|
||||
if (start<=pos && end>=pos) {
|
||||
//return pnh.getMessage();
|
||||
return textArea.getText(start, end-start);
|
||||
}
|
||||
//}
|
||||
}
|
||||
*/
|
||||
if (noticeHighlightPairs!=null) {
|
||||
for (NoticeHighlightPair pair : noticeHighlightPairs) {
|
||||
ParserNotice notice = pair.notice;
|
||||
if (noticeContainsPosition(notice, pos) &&
|
||||
noticeContainsPointInView(notice, p)) {
|
||||
tip = notice.getToolTipText();
|
||||
parserForTip = notice.getParser();
|
||||
if (parserForTip instanceof HyperlinkListener) {
|
||||
listener = (HyperlinkListener)parserForTip;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// } catch (BadLocationException ble) {
|
||||
// ble.printStackTrace(); // Should never happen.
|
||||
// }
|
||||
|
||||
URL imageBase = parserForTip==null ? null : parserForTip.getImageBase();
|
||||
return new ToolTipInfo(tip, listener, imageBase);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Called when the document is modified.
|
||||
*
|
||||
* @param e The document event.
|
||||
*/
|
||||
public void handleDocumentEvent(DocumentEvent e) {
|
||||
if (running && parsers.size()>0) {
|
||||
timer.restart();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Called when the user clicks a hyperlink in a
|
||||
* {@link org.fife.ui.rsyntaxtextarea.focusabletip.FocusableTip}.
|
||||
*
|
||||
* @param e The event.
|
||||
*/
|
||||
@Override
|
||||
public void hyperlinkUpdate(HyperlinkEvent e) {
|
||||
if (parserForTip!=null && parserForTip.getHyperlinkListener()!=null) {
|
||||
parserForTip.getHyperlinkListener().linkClicked(textArea, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Called when the document is modified.
|
||||
*
|
||||
* @param e The document event.
|
||||
*/
|
||||
@Override
|
||||
public void insertUpdate(DocumentEvent e) {
|
||||
|
||||
// Keep track of the first and last offset modified. Some parsers are
|
||||
// smart and will only re-parse this section of the file.
|
||||
try {
|
||||
int offs = e.getOffset();
|
||||
if (firstOffsetModded==null || offs<firstOffsetModded.getOffset()) {
|
||||
firstOffsetModded = e.getDocument().createPosition(offs);
|
||||
}
|
||||
offs = e.getOffset() + e.getLength();
|
||||
if (lastOffsetModded==null || offs>lastOffsetModded.getOffset()) {
|
||||
lastOffsetModded = e.getDocument().createPosition(offs);
|
||||
}
|
||||
} catch (BadLocationException ble) {
|
||||
ble.printStackTrace(); // Shouldn't happen
|
||||
}
|
||||
|
||||
handleDocumentEvent(e);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether a parser notice contains the specified offset.
|
||||
*
|
||||
* @param notice The notice.
|
||||
* @param offs The offset.
|
||||
* @return Whether the notice contains the offset.
|
||||
*/
|
||||
private boolean noticeContainsPosition(ParserNotice notice, int offs){
|
||||
if (notice.getKnowsOffsetAndLength()) {
|
||||
return notice.containsPosition(offs);
|
||||
}
|
||||
Document doc = textArea.getDocument();
|
||||
Element root = doc.getDefaultRootElement();
|
||||
int line = notice.getLine();
|
||||
if (line<0) { // Defensive against possible bad user-defined notices.
|
||||
return false;
|
||||
}
|
||||
Element elem = root.getElement(line);
|
||||
return elem != null && offs>=elem.getStartOffset() && offs<elem.getEndOffset();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Since <code>viewToModel()</code> returns the <em>closest</em> model
|
||||
* position, and the position doesn't <em>necessarily</em> contain the
|
||||
* point passed in as an argument, this method checks whether the point is
|
||||
* indeed contained in the view rectangle for the specified offset.
|
||||
*
|
||||
* @param notice The parser notice.
|
||||
* @param p The point possibly contained in the view range of the
|
||||
* parser notice.
|
||||
* @return Whether the parser notice actually contains the specified point
|
||||
* in the view.
|
||||
*/
|
||||
private boolean noticeContainsPointInView(ParserNotice notice,
|
||||
Point p) {
|
||||
|
||||
try {
|
||||
|
||||
int start, end;
|
||||
if (notice.getKnowsOffsetAndLength()) {
|
||||
start = notice.getOffset();
|
||||
end = start + notice.getLength() - 1;
|
||||
}
|
||||
else {
|
||||
Document doc = textArea.getDocument();
|
||||
Element root = doc.getDefaultRootElement();
|
||||
int line = notice.getLine();
|
||||
// Defend against possible bad user-defined notices.
|
||||
if (line<0) {
|
||||
return false;
|
||||
}
|
||||
Element elem = root.getElement(line);
|
||||
start = elem.getStartOffset();
|
||||
end = elem.getEndOffset() - 1;
|
||||
}
|
||||
|
||||
Rectangle r1 = textArea.modelToView(start);
|
||||
Rectangle r2 = textArea.modelToView(end);
|
||||
if (r1.y!=r2.y) {
|
||||
// If the notice spans multiple lines, give them the benefit
|
||||
// of the doubt. This is only "wrong" if the user is in empty
|
||||
// space "to the right" of the error marker when it ends at the
|
||||
// end of a line anyway.
|
||||
return true;
|
||||
}
|
||||
|
||||
r1.y--; // Be a tiny bit lenient.
|
||||
r1.height += 2; // Ditto
|
||||
return p.x>=r1.x && p.x<(r2.x+r2.width) &&
|
||||
p.y>=r1.y && p.y<(r1.y+r1.height);
|
||||
|
||||
} catch (BadLocationException ble) { // Never occurs
|
||||
// Give them the benefit of the doubt, should 99% of the time be
|
||||
// true anyway
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Called when a property we're interested in changes.
|
||||
*
|
||||
* @param e The property change event.
|
||||
*/
|
||||
@Override
|
||||
public void propertyChange(PropertyChangeEvent e) {
|
||||
|
||||
String name = e.getPropertyName();
|
||||
|
||||
if ("document".equals(name)) {
|
||||
// The document switched out from under us
|
||||
RDocument old = (RDocument)e.getOldValue();
|
||||
if (old != null) {
|
||||
old.removeDocumentListener(this);
|
||||
}
|
||||
RDocument newDoc = (RDocument)e.getNewValue();
|
||||
if (newDoc != null) {
|
||||
newDoc.addDocumentListener(this);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Removes a parser.
|
||||
*
|
||||
* @param parser The parser to remove.
|
||||
* @return Whether the parser was found.
|
||||
* @see #addParser(Parser)
|
||||
* @see #getParser(int)
|
||||
*/
|
||||
public boolean removeParser(Parser parser) {
|
||||
removeParserNotices(parser);
|
||||
boolean removed = parsers.remove(parser);
|
||||
if (removed) {
|
||||
textArea.fireParserNoticesChange();
|
||||
}
|
||||
return removed;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Removes all parser notices (and clears highlights in the editor) from
|
||||
* a particular parser.
|
||||
*
|
||||
* @param parser The parser.
|
||||
*/
|
||||
private void removeParserNotices(Parser parser) {
|
||||
if (noticeHighlightPairs!=null) {
|
||||
RSyntaxTextAreaHighlighter h = (RSyntaxTextAreaHighlighter)
|
||||
textArea.getHighlighter();
|
||||
for (Iterator<NoticeHighlightPair> i=noticeHighlightPairs.iterator(); i.hasNext();) {
|
||||
NoticeHighlightPair pair = i.next();
|
||||
if (pair.notice.getParser()==parser && pair.highlight!=null) {
|
||||
h.removeParserHighlight(pair.highlight);
|
||||
i.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Removes any currently stored notices (and the corresponding highlights
|
||||
* from the editor) from the same Parser, and in the given line range,
|
||||
* as in the results.
|
||||
*
|
||||
* @param res The results.
|
||||
*/
|
||||
private void removeParserNotices(ParseResult res) {
|
||||
if (noticeHighlightPairs!=null) {
|
||||
RSyntaxTextAreaHighlighter h = (RSyntaxTextAreaHighlighter)
|
||||
textArea.getHighlighter();
|
||||
for (Iterator<NoticeHighlightPair> i=noticeHighlightPairs.iterator(); i.hasNext();) {
|
||||
NoticeHighlightPair pair = i.next();
|
||||
boolean removed = false;
|
||||
if (shouldRemoveNotice(pair.notice, res)) {
|
||||
if (pair.highlight!=null) {
|
||||
h.removeParserHighlight(pair.highlight);
|
||||
}
|
||||
i.remove();
|
||||
removed = true;
|
||||
}
|
||||
if (DEBUG_PARSING) {
|
||||
String text = removed ? "[DEBUG]: ... notice removed: " :
|
||||
"[DEBUG]: ... notice not removed: ";
|
||||
System.out.println(text + pair.notice);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Called when the document is modified.
|
||||
*
|
||||
* @param e The document event.
|
||||
*/
|
||||
@Override
|
||||
public void removeUpdate(DocumentEvent e) {
|
||||
|
||||
// Keep track of the first and last offset modified. Some parsers are
|
||||
// smart and will only re-parse this section of the file. Note that
|
||||
// for removals, only the line at the removal start needs to be
|
||||
// re-parsed.
|
||||
try {
|
||||
int offs = e.getOffset();
|
||||
if (firstOffsetModded==null || offs<firstOffsetModded.getOffset()) {
|
||||
firstOffsetModded = e.getDocument().createPosition(offs);
|
||||
}
|
||||
if (lastOffsetModded==null || offs>lastOffsetModded.getOffset()) {
|
||||
lastOffsetModded = e.getDocument().createPosition(offs);
|
||||
}
|
||||
} catch (BadLocationException ble) { // Never happens
|
||||
ble.printStackTrace();
|
||||
}
|
||||
|
||||
handleDocumentEvent(e);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Restarts parsing the document.
|
||||
*
|
||||
* @see #stopParsing()
|
||||
*/
|
||||
public void restartParsing() {
|
||||
timer.restart();
|
||||
running = true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the delay between the last "concurrent" edit and when the document
|
||||
* is re-parsed.
|
||||
*
|
||||
* @param millis The new delay, in milliseconds. This must be greater
|
||||
* than <code>0</code>.
|
||||
* @see #getDelay()
|
||||
*/
|
||||
public void setDelay(int millis) {
|
||||
if (running) {
|
||||
timer.stop();
|
||||
}
|
||||
timer.setInitialDelay(millis);
|
||||
timer.setDelay(millis);
|
||||
if (running) {
|
||||
timer.start();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether a parser notice should be removed, based on a parse
|
||||
* result.
|
||||
*
|
||||
* @param notice The notice in question.
|
||||
* @param res The result.
|
||||
* @return Whether the notice should be removed.
|
||||
*/
|
||||
private boolean shouldRemoveNotice(ParserNotice notice,
|
||||
ParseResult res) {
|
||||
|
||||
if (DEBUG_PARSING) {
|
||||
System.out.println("[DEBUG]: ... ... shouldRemoveNotice " +
|
||||
notice + ": " + (notice.getParser()==res.getParser()));
|
||||
}
|
||||
|
||||
// NOTE: We must currently remove all notices for the parser. Parser
|
||||
// implementors are required to parse the entire document each parsing
|
||||
// request, as RSTA is not yet sophisticated enough to determine the
|
||||
// minimum range of text to parse (and ParserNotices' locations aren't
|
||||
// updated when the Document is mutated, which would be a requirement
|
||||
// for this as well).
|
||||
// return same_parser && (in_reparsed_range || in_deleted_end_of_doc)
|
||||
return notice.getParser()==res.getParser();
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Stops parsing the document.
|
||||
*
|
||||
* @see #restartParsing()
|
||||
*/
|
||||
public void stopParsing() {
|
||||
timer.stop();
|
||||
running = false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Mapping of a parser notice to its highlight in the editor.
|
||||
*/
|
||||
private static class NoticeHighlightPair {
|
||||
|
||||
private ParserNotice notice;
|
||||
private HighlightInfo highlight;
|
||||
|
||||
NoticeHighlightPair(ParserNotice notice, HighlightInfo highlight) {
|
||||
this.notice = notice;
|
||||
this.highlight = highlight;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
static {
|
||||
boolean debugParsing = false;
|
||||
try {
|
||||
debugParsing = Boolean.getBoolean(PROPERTY_DEBUG_PARSING);
|
||||
} catch (AccessControlException ace) {
|
||||
// Likely an applet's security manager.
|
||||
debugParsing = false; // FindBugs
|
||||
}
|
||||
DEBUG_PARSING = debugParsing;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* 01/11/2011
|
||||
*
|
||||
* PopupWindowDecorator.java - Hook allowing hosting applications to decorate
|
||||
* JWindows created by the AutoComplete library.
|
||||
*
|
||||
* This library is distributed under a modified BSD license. See the included
|
||||
* RSyntaxTextArea.License.txt file for details.
|
||||
*/
|
||||
package org.fife.ui.rsyntaxtextarea;
|
||||
|
||||
import javax.swing.JWindow;
|
||||
|
||||
|
||||
/**
|
||||
* A hook allowing hosting applications to decorate JWindows created by the
|
||||
* AutoComplete library. For example, you could use the
|
||||
* <a href="http://jgoodies.com/">JGoodies</a> library to add drop shadows
|
||||
* to the windows.
|
||||
*
|
||||
* @author Robert Futrell
|
||||
* @version 1.0
|
||||
*/
|
||||
public abstract class PopupWindowDecorator {
|
||||
|
||||
/**
|
||||
* The singleton instance of this class.
|
||||
*/
|
||||
private static PopupWindowDecorator decorator;
|
||||
|
||||
|
||||
/**
|
||||
* Callback called whenever an appropriate JWindow is created by the
|
||||
* AutoComplete library. Implementations can decorate the window however
|
||||
* they see fit.
|
||||
*
|
||||
* @param window The newly-created window.
|
||||
*/
|
||||
public abstract void decorate(JWindow window);
|
||||
|
||||
|
||||
/**
|
||||
* Returns the singleton instance of this class. This should only be
|
||||
* called on the EDT.
|
||||
*
|
||||
* @return The singleton instance of this class, or <code>null</code>
|
||||
* for none.
|
||||
* @see #set(PopupWindowDecorator)
|
||||
*/
|
||||
public static PopupWindowDecorator get() {
|
||||
return decorator;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the singleton instance of this class. This should only be called
|
||||
* on the EDT.
|
||||
*
|
||||
* @param decorator The new instance of this class. This may be
|
||||
* <code>null</code>.
|
||||
* @see #get()
|
||||
*/
|
||||
public static void set(PopupWindowDecorator decorator) {
|
||||
PopupWindowDecorator.decorator = decorator;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* 02/10/2009
|
||||
*
|
||||
* RSTAView.java - An <code>RSyntaxTextArea</code> view.
|
||||
*
|
||||
* 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.Rectangle;
|
||||
|
||||
import javax.swing.text.BadLocationException;
|
||||
|
||||
|
||||
/**
|
||||
* Utility methods for RSyntaxTextArea's views.
|
||||
*
|
||||
* @author Robert Futrell
|
||||
* @version 1.0
|
||||
*/
|
||||
interface RSTAView {
|
||||
|
||||
|
||||
/**
|
||||
* Returns the y-coordinate of the specified line.<p>
|
||||
*
|
||||
* This method is quicker than using traditional
|
||||
* <code>modelToView(int)</code> calls, as the entire bounding box isn't
|
||||
* computed.
|
||||
*
|
||||
* @param alloc The area the text area can render into.
|
||||
* @param line The line number.
|
||||
* @return The y-coordinate of the top of the line, or <code>-1</code> if
|
||||
* this text area doesn't yet have a positive size or the line is
|
||||
* hidden (i.e. from folding).
|
||||
* @throws BadLocationException If <code>line</code> isn't a valid line
|
||||
* number for this document.
|
||||
*/
|
||||
int yForLine(Rectangle alloc, int line) throws BadLocationException;
|
||||
|
||||
|
||||
/**
|
||||
* Returns the y-coordinate of the line containing a specified offset.<p>
|
||||
*
|
||||
* This method is quicker than using traditional
|
||||
* <code>modelToView(int)</code> calls, as the entire bounding box isn't
|
||||
* computed.
|
||||
*
|
||||
* @param alloc The area the text area can render into.
|
||||
* @param offs The offset info the document.
|
||||
* @return The y-coordinate of the top of the offset, or <code>-1</code> if
|
||||
* this text area doesn't yet have a positive size or the line is
|
||||
* hidden (i.e. from folding).
|
||||
* @throws BadLocationException If <code>offs</code> isn't a valid offset
|
||||
* into the document.
|
||||
*/
|
||||
int yForLineContaining(Rectangle alloc, int offs)
|
||||
throws BadLocationException;
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,672 @@
|
||||
/*
|
||||
* 10/16/2004
|
||||
*
|
||||
* RSyntaxDocument.java - A document capable of syntax highlighting, used by
|
||||
* RSyntaxTextArea.
|
||||
*
|
||||
* This library is distributed under a modified BSD license. See the included
|
||||
* RSyntaxTextArea.License.txt file for details.
|
||||
*/
|
||||
package org.fife.ui.rsyntaxtextarea;
|
||||
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.util.Iterator;
|
||||
|
||||
import javax.swing.Action;
|
||||
import javax.swing.event.DocumentEvent;
|
||||
import javax.swing.text.BadLocationException;
|
||||
import javax.swing.text.Element;
|
||||
import javax.swing.text.Segment;
|
||||
|
||||
import org.fife.ui.rsyntaxtextarea.modes.AbstractMarkupTokenMaker;
|
||||
import org.fife.ui.rtextarea.RDocument;
|
||||
import org.fife.util.DynamicIntArray;
|
||||
|
||||
import me.ByteEdit.main.JavaAssemblyTokenMaker;
|
||||
|
||||
/**
|
||||
* The document used by {@link org.fife.ui.rsyntaxtextarea.RSyntaxTextArea}.
|
||||
* This document is like <code>javax.swing.text.PlainDocument</code> except that
|
||||
* it also keeps track of syntax highlighting in the document. It has a "style"
|
||||
* attribute associated with it that determines how syntax highlighting is done
|
||||
* (i.e., what language is being highlighted).
|
||||
* <p>
|
||||
*
|
||||
* Instances of <code>RSyntaxTextArea</code> will only accept instances of
|
||||
* <code>RSyntaxDocument</code>, since it is this document that keeps track of
|
||||
* syntax highlighting. All others will cause an exception to be thrown.
|
||||
* <p>
|
||||
*
|
||||
* To change the language being syntax highlighted at any time, you merely have
|
||||
* to call {@link #setSyntaxStyle}. Other than that, this document can be
|
||||
* treated like any other save one caveat: all <code>DocumentEvent</code>s of
|
||||
* type <code>CHANGE</code> use their offset and length values to represent the
|
||||
* first and last lines, respectively, that have had their syntax coloring
|
||||
* change. This is really a hack to increase the speed of the painting code and
|
||||
* should really be corrected, but oh well.
|
||||
*
|
||||
* @author Robert Futrell
|
||||
* @version 1.0
|
||||
*/
|
||||
public class RSyntaxDocument extends RDocument implements Iterable<Token>, SyntaxConstants {
|
||||
|
||||
/**
|
||||
* Creates a {@link TokenMaker} appropriate for a given programming language.
|
||||
*/
|
||||
private transient TokenMakerFactory tokenMakerFactory;
|
||||
|
||||
/**
|
||||
* Splits text into tokens for the current programming language.
|
||||
*/
|
||||
private transient TokenMaker tokenMaker;
|
||||
|
||||
/**
|
||||
* The current syntax style. Only cached to keep this class serializable.
|
||||
*/
|
||||
private String syntaxStyle;
|
||||
|
||||
/**
|
||||
* Array of values representing the "last token type" on each line. This is used
|
||||
* in cases such as multi-line comments: if the previous line ended with an
|
||||
* (unclosed) multi-line comment, we can use this knowledge and start the
|
||||
* current line's syntax highlighting in multi-line comment state.
|
||||
*/
|
||||
protected transient DynamicIntArray lastTokensOnLines;
|
||||
|
||||
private transient int lastLine = -1;
|
||||
private transient Token cachedTokenList;
|
||||
private transient int useCacheCount = 0;
|
||||
private transient int tokenRetrievalCount = 0;
|
||||
|
||||
private transient Segment s;
|
||||
|
||||
/**
|
||||
* If this is set to <code>true</code>, debug information about how much token
|
||||
* caching is helping is printed to stdout.
|
||||
*/
|
||||
private static final boolean DEBUG_TOKEN_CACHING = false;
|
||||
|
||||
/**
|
||||
* Constructs a plain text document. A default root element is created, and the
|
||||
* tab size set to 5.
|
||||
*
|
||||
* @param syntaxStyle
|
||||
* The syntax highlighting scheme to use.
|
||||
*/
|
||||
public RSyntaxDocument(String syntaxStyle) {
|
||||
this(null, syntaxStyle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a plain text document. A default root element is created, and the
|
||||
* tab size set to 5.
|
||||
*
|
||||
* @param tmf
|
||||
* The <code>TokenMakerFactory</code> for this document. If this is
|
||||
* <code>null</code>, a default factory is used.
|
||||
* @param syntaxStyle
|
||||
* The syntax highlighting scheme to use.
|
||||
*/
|
||||
public RSyntaxDocument(TokenMakerFactory tmf, String syntaxStyle) {
|
||||
putProperty(tabSizeAttribute, Integer.valueOf(5));
|
||||
lastTokensOnLines = new DynamicIntArray(400);
|
||||
lastTokensOnLines.add(Token.NULL); // Initial (empty) line.
|
||||
s = new Segment();
|
||||
setTokenMakerFactory(tmf);
|
||||
setSyntaxStyle(syntaxStyle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Alerts all listeners to this document of an insertion. This is overridden so
|
||||
* we can update our syntax highlighting stuff.
|
||||
* <p>
|
||||
* The syntax highlighting stuff has to be here instead of in
|
||||
* <code>insertUpdate</code> because <code>insertUpdate</code> is not called by
|
||||
* the undo/redo actions, but this method is.
|
||||
*
|
||||
* @param e
|
||||
* The change.
|
||||
*/
|
||||
@Override
|
||||
protected void fireInsertUpdate(DocumentEvent e) {
|
||||
|
||||
cachedTokenList = null;
|
||||
|
||||
/*
|
||||
* Now that the text is actually inserted into the content and element
|
||||
* structure, we can update our token elements and "last tokens on lines"
|
||||
* structure.
|
||||
*/
|
||||
|
||||
Element lineMap = getDefaultRootElement();
|
||||
DocumentEvent.ElementChange change = e.getChange(lineMap);
|
||||
Element[] added = change == null ? null : change.getChildrenAdded();
|
||||
|
||||
int numLines = lineMap.getElementCount();
|
||||
int line = lineMap.getElementIndex(e.getOffset());
|
||||
int previousLine = line - 1;
|
||||
int previousTokenType = (previousLine > -1 ? lastTokensOnLines.get(previousLine) : Token.NULL);
|
||||
|
||||
// If entire lines were added...
|
||||
if (added != null && added.length > 0) {
|
||||
|
||||
Element[] removed = change.getChildrenRemoved();
|
||||
int numRemoved = removed != null ? removed.length : 0;
|
||||
|
||||
int endBefore = line + added.length - numRemoved;
|
||||
// System.err.println("... adding lines: " + line + " - " + (endBefore-1));
|
||||
// System.err.println("... ... added: " + added.length + ", removed:" +
|
||||
// numRemoved);
|
||||
for (int i = line; i < endBefore; i++) {
|
||||
|
||||
setSharedSegment(i); // Loads line i's text into s.
|
||||
|
||||
int tokenType = tokenMaker.getLastTokenTypeOnLine(s, previousTokenType);
|
||||
lastTokensOnLines.add(i, tokenType);
|
||||
// System.err.println("--------- lastTokensOnLines.size() == " +
|
||||
// lastTokensOnLines.getSize());
|
||||
|
||||
previousTokenType = tokenType;
|
||||
|
||||
} // End of for (int i=line; i<endBefore; i++).
|
||||
|
||||
// Update last tokens for lines below until they stop changing.
|
||||
updateLastTokensBelow(endBefore, numLines, previousTokenType);
|
||||
|
||||
} // End of if (added!=null && added.length>0).
|
||||
|
||||
// Otherwise, text was inserted on a single line...
|
||||
else {
|
||||
|
||||
// Update last tokens for lines below until they stop changing.
|
||||
updateLastTokensBelow(line, numLines, previousTokenType);
|
||||
|
||||
} // End of else.
|
||||
|
||||
// Let all listeners know about the insertion.
|
||||
super.fireInsertUpdate(e);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called AFTER the content has been inserted into the document
|
||||
* and the element structure has been updated.
|
||||
* <p>
|
||||
* The syntax-highlighting updates need to be done here (as opposed to an
|
||||
* override of <code>postRemoveUpdate</code>) as this method is called in
|
||||
* response to undo/redo events, whereas <code>postRemoveUpdate</code> is not.
|
||||
* <p>
|
||||
* Now that the text is actually inserted into the content and element
|
||||
* structure, we can update our token elements and "last tokens on lines"
|
||||
* structure.
|
||||
*
|
||||
* @param chng
|
||||
* The change that occurred.
|
||||
* @see #removeUpdate
|
||||
*/
|
||||
@Override
|
||||
protected void fireRemoveUpdate(DocumentEvent chng) {
|
||||
|
||||
cachedTokenList = null;
|
||||
Element lineMap = getDefaultRootElement();
|
||||
int numLines = lineMap.getElementCount();
|
||||
|
||||
DocumentEvent.ElementChange change = chng.getChange(lineMap);
|
||||
Element[] removed = change == null ? null : change.getChildrenRemoved();
|
||||
|
||||
// If entire lines were removed...
|
||||
if (removed != null && removed.length > 0) {
|
||||
|
||||
int line = change.getIndex(); // First line entirely removed.
|
||||
int previousLine = line - 1; // Line before that.
|
||||
int previousTokenType = (previousLine > -1 ? lastTokensOnLines.get(previousLine) : Token.NULL);
|
||||
|
||||
Element[] added = change.getChildrenAdded();
|
||||
int numAdded = added == null ? 0 : added.length;
|
||||
|
||||
// Remove the cached last-token values for the removed lines.
|
||||
int endBefore = line + removed.length - numAdded;
|
||||
// System.err.println("... removing lines: " + line + " - " + (endBefore-1));
|
||||
// System.err.println("... added: " + numAdded + ", removed: " +
|
||||
// removed.length);
|
||||
|
||||
lastTokensOnLines.removeRange(line, endBefore); // Removing values for lines [line-(endBefore-1)].
|
||||
// System.err.println("--------- lastTokensOnLines.size() == " +
|
||||
// lastTokensOnLines.getSize());
|
||||
|
||||
// Update last tokens for lines below until they've stopped changing.
|
||||
updateLastTokensBelow(line, numLines, previousTokenType);
|
||||
|
||||
} // End of if (removed!=null && removed.size()>0).
|
||||
|
||||
// Otherwise, text was removed from just one line...
|
||||
else {
|
||||
|
||||
int line = lineMap.getElementIndex(chng.getOffset());
|
||||
if (line >= lastTokensOnLines.getSize()) {
|
||||
return; // If we're editing the last line in a document...
|
||||
}
|
||||
|
||||
int previousLine = line - 1;
|
||||
int previousTokenType = (previousLine > -1 ? lastTokensOnLines.get(previousLine) : Token.NULL);
|
||||
// System.err.println("previousTokenType for line : " + previousLine + " is " +
|
||||
// previousTokenType);
|
||||
// Update last tokens for lines below until they've stopped changing.
|
||||
updateLastTokensBelow(line, numLines, previousTokenType);
|
||||
|
||||
}
|
||||
|
||||
// Let all of our listeners know about the removal.
|
||||
super.fireRemoveUpdate(chng);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the closest {@link TokenTypes "standard" token type} for a given
|
||||
* "internal" token type (e.g. one whose value is <code>< 0</code>).
|
||||
*
|
||||
* @param type
|
||||
* The token type.
|
||||
* @return The closest "standard" token type. If a mapping is not defined for
|
||||
* this language, then <code>type</code> is returned.
|
||||
*/
|
||||
public int getClosestStandardTokenTypeForInternalType(int type) {
|
||||
return tokenMaker.getClosestStandardTokenTypeForInternalType(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether closing markup tags should be automatically completed. This
|
||||
* method only returns <code>true</code> if {@link #getLanguageIsMarkup()} also
|
||||
* returns <code>true</code>.
|
||||
*
|
||||
* @return Whether markup closing tags should be automatically completed.
|
||||
* @see #getLanguageIsMarkup()
|
||||
*/
|
||||
public boolean getCompleteMarkupCloseTags() {
|
||||
// TODO: Remove terrible dependency on AbstractMarkupTokenMaker
|
||||
return getLanguageIsMarkup() && ((AbstractMarkupTokenMaker) tokenMaker).getCompleteCloseTags();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the current programming language uses curly braces
|
||||
* ('<code>{</code>' and '<code>}</code>') to denote code blocks.
|
||||
*
|
||||
* @param languageIndex
|
||||
* The language index at the offset in question. Since some
|
||||
* <code>TokenMaker</code>s effectively have nested languages (such
|
||||
* as JavaScript in HTML), this parameter tells the
|
||||
* <code>TokenMaker</code> what sub-language to look at.
|
||||
* @return Whether curly braces denote code blocks.
|
||||
*/
|
||||
public boolean getCurlyBracesDenoteCodeBlocks(int languageIndex) {
|
||||
return tokenMaker.getCurlyBracesDenoteCodeBlocks(languageIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the current language is a markup language, such as HTML, XML
|
||||
* or PHP.
|
||||
*
|
||||
* @return Whether the current language is a markup language.
|
||||
*/
|
||||
public boolean getLanguageIsMarkup() {
|
||||
return tokenMaker.isMarkupLanguage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the token type of the last token on the given line.
|
||||
*
|
||||
* @param line
|
||||
* The line to inspect.
|
||||
* @return The token type of the last token on the specified line. If the line
|
||||
* is invalid, an exception is thrown.
|
||||
*/
|
||||
public int getLastTokenTypeOnLine(int line) {
|
||||
return lastTokensOnLines.get(line);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the text to place at the beginning and end of a line to "comment" it
|
||||
* in this programming language.
|
||||
*
|
||||
* @return The start and end strings to add to a line to "comment" it out. A
|
||||
* <code>null</code> value for either means there is no string to add
|
||||
* for that part. A value of <code>null</code> for the array means this
|
||||
* language does not support commenting/uncommenting lines.
|
||||
*/
|
||||
public String[] getLineCommentStartAndEnd(int languageIndex) {
|
||||
return tokenMaker.getLineCommentStartAndEnd(languageIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
return tokenMaker.getMarkOccurrencesOfTokenType(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the occurrence marker for the current language.
|
||||
*
|
||||
* @return The occurrence marker.
|
||||
*/
|
||||
OccurrenceMarker getOccurrenceMarker() {
|
||||
return tokenMaker.getOccurrenceMarker();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns whether auto indentation should be done if Enter is
|
||||
* pressed at the end of the specified line.
|
||||
*
|
||||
* @param line
|
||||
* The line to check.
|
||||
* @return Whether an extra indentation should be done.
|
||||
*/
|
||||
public boolean getShouldIndentNextLine(int line) {
|
||||
Token t = getTokenListForLine(line);
|
||||
t = t.getLastNonCommentNonWhitespaceToken();
|
||||
return tokenMaker.getShouldIndentNextLineAfter(t);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the syntax style being used.
|
||||
*
|
||||
* @return The syntax style.
|
||||
* @see #setSyntaxStyle(String)
|
||||
*/
|
||||
public String getSyntaxStyle() {
|
||||
return syntaxStyle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a token list for the specified segment of text representing the
|
||||
* specified line number. This method is basically a wrapper for
|
||||
* <code>tokenMaker.getTokenList</code> that takes into account the last token
|
||||
* on the previous line to assure token accuracy.
|
||||
*
|
||||
* @param line
|
||||
* The line number of <code>text</code> in the document, >= 0.
|
||||
* @return A token list representing the specified line.
|
||||
*/
|
||||
public final Token getTokenListForLine(int line) {
|
||||
|
||||
tokenRetrievalCount++;
|
||||
if (line == lastLine && cachedTokenList != null) {
|
||||
if (DEBUG_TOKEN_CACHING) {
|
||||
useCacheCount++;
|
||||
System.err.println("--- Using cached line; ratio now: " + useCacheCount + "/" + tokenRetrievalCount);
|
||||
}
|
||||
return cachedTokenList;
|
||||
}
|
||||
lastLine = line;
|
||||
|
||||
Element map = getDefaultRootElement();
|
||||
Element elem = map.getElement(line);
|
||||
int startOffset = elem.getStartOffset();
|
||||
// int endOffset = (line==map.getElementCount()-1 ? elem.getEndOffset() - 1:
|
||||
// elem.getEndOffset() - 1);
|
||||
int endOffset = elem.getEndOffset() - 1; // Why always "-1"?
|
||||
try {
|
||||
getText(startOffset, endOffset - startOffset, s);
|
||||
} catch (BadLocationException ble) {
|
||||
ble.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
int initialTokenType = line == 0 ? Token.NULL : getLastTokenTypeOnLine(line - 1);
|
||||
|
||||
// return tokenMaker.getTokenList(s, initialTokenType, startOffset);
|
||||
cachedTokenList = tokenMaker.getTokenList(s, initialTokenType, startOffset);
|
||||
return cachedTokenList;
|
||||
|
||||
}
|
||||
|
||||
boolean insertBreakSpecialHandling(ActionEvent e) {
|
||||
Action a = tokenMaker.getInsertBreakAction();
|
||||
if (a != null) {
|
||||
a.actionPerformed(e);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether a character could be part of an "identifier" token in a
|
||||
* specific language. This is used to identify such things as the bounds of the
|
||||
* "word" to select on double-clicking.
|
||||
*
|
||||
* @param languageIndex
|
||||
* The language index the character was found in.
|
||||
* @param ch
|
||||
* The character.
|
||||
* @return Whether the character could be part of an "identifier" token.
|
||||
*/
|
||||
public boolean isIdentifierChar(int languageIndex, char ch) {
|
||||
return tokenMaker.isIdentifierChar(languageIndex, ch);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an iterator over the paintable tokens in this document. Results are
|
||||
* undefined if this document is modified while the iterator is being iterated
|
||||
* through, so this should only be used on the EDT.
|
||||
* <p>
|
||||
*
|
||||
* The <code>remove()</code> method of the returned iterator will throw an
|
||||
* <code>UnsupportedOperationException</code>.
|
||||
*
|
||||
* @return An iterator.
|
||||
*/
|
||||
@Override
|
||||
public Iterator<Token> iterator() {
|
||||
return new TokenIterator(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserializes a document.
|
||||
*
|
||||
* @param in
|
||||
* The stream to read from.
|
||||
* @throws ClassNotFoundException
|
||||
* @throws IOException
|
||||
*/
|
||||
private void readObject(ObjectInputStream in) throws ClassNotFoundException, IOException {
|
||||
|
||||
in.defaultReadObject();
|
||||
|
||||
// Install default TokenMakerFactory. To support custom TokenMakers,
|
||||
// both JVM's should install default TokenMakerFactories that support
|
||||
// the language they want to use beforehand.
|
||||
setTokenMakerFactory(null);
|
||||
|
||||
// Handle other transient stuff
|
||||
this.s = new Segment();
|
||||
int lineCount = getDefaultRootElement().getElementCount();
|
||||
lastTokensOnLines = new DynamicIntArray(lineCount);
|
||||
setSyntaxStyle(syntaxStyle); // Actually install (transient) TokenMaker
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes our private <code>Segment s</code> point to the text in our document
|
||||
* referenced by the specified element. Note that <code>line</code> MUST be a
|
||||
* valid line number in the document.
|
||||
*
|
||||
* @param line
|
||||
* The line number you want to get.
|
||||
*/
|
||||
private void setSharedSegment(int line) {
|
||||
|
||||
Element map = getDefaultRootElement();
|
||||
// int numLines = map.getElementCount();
|
||||
|
||||
Element element = map.getElement(line);
|
||||
if (element == null) {
|
||||
throw new InternalError("Invalid line number: " + line);
|
||||
}
|
||||
int startOffset = element.getStartOffset();
|
||||
// int endOffset = (line==numLines-1 ?
|
||||
// element.getEndOffset()-1 : element.getEndOffset() - 1);
|
||||
int endOffset = element.getEndOffset() - 1; // Why always "-1"?
|
||||
try {
|
||||
getText(startOffset, endOffset - startOffset, s);
|
||||
} catch (BadLocationException ble) {
|
||||
throw new InternalError("Text range not in document: " + startOffset + "-" + endOffset);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the syntax style being used for syntax highlighting in this document.
|
||||
* What styles are supported by a document is determined by its
|
||||
* {@link TokenMakerFactory}. By default, all <code>RSyntaxDocument</code>s
|
||||
* support all languages built into <code>RSyntaxTextArea</code>.
|
||||
*
|
||||
* @param styleKey
|
||||
* The new style to use, such as
|
||||
* {@link SyntaxConstants#SYNTAX_STYLE_JAVA}. If this style is not
|
||||
* known or supported by this document, then
|
||||
* {@link SyntaxConstants#SYNTAX_STYLE_NONE} is used.
|
||||
* @see #setSyntaxStyle(TokenMaker)
|
||||
* @see #getSyntaxStyle()
|
||||
*/
|
||||
public void setSyntaxStyle(String styleKey) {
|
||||
if (styleKey.equals("text/javaassembly")) {
|
||||
tokenMaker = new JavaAssemblyTokenMaker();
|
||||
} else {
|
||||
tokenMaker = tokenMakerFactory.getTokenMaker(styleKey);
|
||||
}
|
||||
updateSyntaxHighlightingInformation();
|
||||
this.syntaxStyle = styleKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the syntax style being used for syntax highlighting in this document.
|
||||
* You should call this method if you've created a custom token maker for a
|
||||
* language not normally supported by <code>RSyntaxTextArea</code>.
|
||||
*
|
||||
* @param tokenMaker
|
||||
* The new token maker to use.
|
||||
* @see #setSyntaxStyle(String)
|
||||
*/
|
||||
public void setSyntaxStyle(TokenMaker tokenMaker) {
|
||||
this.tokenMaker = tokenMaker;
|
||||
updateSyntaxHighlightingInformation();
|
||||
this.syntaxStyle = "text/unknown"; // TODO: Make me public?
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the token maker factory used by this document.
|
||||
*
|
||||
* @param tmf
|
||||
* The <code>TokenMakerFactory</code> for this document. If this is
|
||||
* <code>null</code>, a default factory is used.
|
||||
*/
|
||||
public void setTokenMakerFactory(TokenMakerFactory tmf) {
|
||||
tokenMakerFactory = tmf != null ? tmf : TokenMakerFactory.getDefaultInstance();
|
||||
}
|
||||
|
||||
/**
|
||||
* Loops through the last-tokens-on-lines array from a specified point onward,
|
||||
* updating last-token values until they stop changing. This should be called
|
||||
* when lines are updated/inserted/removed, as doing so may cause lines below to
|
||||
* change color.
|
||||
*
|
||||
* @param line
|
||||
* The first line to check for a change in last-token value.
|
||||
* @param numLines
|
||||
* The number of lines in the document.
|
||||
* @param previousTokenType
|
||||
* The last-token value of the line just before <code>line</code>.
|
||||
* @return The last line that needs repainting.
|
||||
*/
|
||||
private int updateLastTokensBelow(int line, int numLines, int previousTokenType) {
|
||||
|
||||
int firstLine = line;
|
||||
|
||||
// Loop through all lines past our starting point. Update even the last
|
||||
// line's info, even though there aren't any lines after it that depend
|
||||
// on it changing for them to be changed, as its state may be used
|
||||
// elsewhere in the library.
|
||||
int end = numLines;
|
||||
// System.err.println("--- end==" + end + " (numLines==" + numLines + ")");
|
||||
while (line < end) {
|
||||
|
||||
setSharedSegment(line); // Sets s's text to that of line 'line' in the document.
|
||||
|
||||
int oldTokenType = lastTokensOnLines.get(line);
|
||||
int newTokenType = tokenMaker.getLastTokenTypeOnLine(s, previousTokenType);
|
||||
// System.err.println("---------------- line " + line + "; oldTokenType==" +
|
||||
// oldTokenType + ", newTokenType==" + newTokenType + ", s=='" + s + "'");
|
||||
|
||||
// If this line's end-token value didn't change, stop here. Note
|
||||
// that we're saying this line needs repainting; this is because
|
||||
// the beginning of this line did indeed change color, but the
|
||||
// end didn't.
|
||||
if (oldTokenType == newTokenType) {
|
||||
// System.err.println("... ... ... repainting lines " + firstLine + "-" + line);
|
||||
fireChangedUpdate(new DefaultDocumentEvent(firstLine, line, DocumentEvent.EventType.CHANGE));
|
||||
return line;
|
||||
}
|
||||
|
||||
// If the line's end-token value did change, update it and
|
||||
// keep going.
|
||||
// NOTE: "setUnsafe" is okay here as the bounds checking was
|
||||
// already done in lastTokensOnLines.get(line) above.
|
||||
lastTokensOnLines.setUnsafe(line, newTokenType);
|
||||
previousTokenType = newTokenType;
|
||||
line++;
|
||||
|
||||
} // End of while (line<numLines).
|
||||
|
||||
// If any lines had their token types changed, fire a changed update
|
||||
// for them. The view will repaint the area covered by the lines.
|
||||
// FIXME: We currently cheat and send the line range that needs to be
|
||||
// repainted as the "offset and length" of the change, since this is
|
||||
// what the view needs. We really should send the actual offset and
|
||||
// length.
|
||||
if (line > firstLine) {
|
||||
// System.err.println("... ... ... repainting lines " + firstLine + "-" + line);
|
||||
fireChangedUpdate(new DefaultDocumentEvent(firstLine, line, DocumentEvent.EventType.CHANGE));
|
||||
}
|
||||
|
||||
return line;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates internal state information; e.g. the "last tokens on lines" data.
|
||||
* After this, a changed update is fired to let listeners know that the
|
||||
* document's structure has changed.
|
||||
* <p>
|
||||
*
|
||||
* This is called internally whenever the syntax style changes.
|
||||
*/
|
||||
private void updateSyntaxHighlightingInformation() {
|
||||
|
||||
// Reinitialize the "last token on each line" array. Note that since
|
||||
// the actual text in the document isn't changing, the number of lines
|
||||
// is the same.
|
||||
Element map = getDefaultRootElement();
|
||||
int numLines = map.getElementCount();
|
||||
int lastTokenType = Token.NULL;
|
||||
for (int i = 0; i < numLines; i++) {
|
||||
setSharedSegment(i);
|
||||
lastTokenType = tokenMaker.getLastTokenTypeOnLine(s, lastTokenType);
|
||||
lastTokensOnLines.set(i, lastTokenType);
|
||||
}
|
||||
|
||||
// Clear our token cache to force re-painting
|
||||
lastLine = -1;
|
||||
cachedTokenList = null;
|
||||
|
||||
// Let everybody know that syntax styles have (probably) changed.
|
||||
fireChangedUpdate(new DefaultDocumentEvent(0, numLines - 1, DocumentEvent.EventType.CHANGE));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,14 @@
|
||||
ContextMenu.Folding=Folding
|
||||
|
||||
Action.ToggleCurrentFold.Name=Toggle Current Fold
|
||||
Action.ToggleCurrentFold.Mnemonic=F
|
||||
Action.ToggleCurrentFold.Desc=Toggles the fold at the caret position.
|
||||
Action.CollapseAllFolds.Name=Collapse All Folds
|
||||
Action.CollapseAllFolds.Mnemonic=O
|
||||
Action.CollapseAllFolds.Desc=Collapses all folds.
|
||||
Action.CollapseCommentFolds.Name=Collapse All Comments
|
||||
Action.CollapseCommentFolds.Mnemonic=C
|
||||
Action.CollapseCommentFolds.Desc=Collapses all comment folds.
|
||||
Action.ExpandAllFolds.Name=Expand All Folds
|
||||
Action.ExpandAllFolds.Mnemonic=E
|
||||
Action.ExpandAllFolds.Desc=Expands all folds.
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* 10/27/2004
|
||||
*
|
||||
* RSyntaxTextAreaDefaultInputMap.java - The default input map for
|
||||
* RSyntaxTextAreas.
|
||||
*
|
||||
* 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.event.InputEvent;
|
||||
import java.awt.event.KeyEvent;
|
||||
|
||||
import javax.swing.KeyStroke;
|
||||
|
||||
import org.fife.ui.rtextarea.RTADefaultInputMap;
|
||||
|
||||
|
||||
/**
|
||||
* The default input map for an <code>RSyntaxTextArea</code>.
|
||||
* Currently, the new key bindings include:
|
||||
* <ul>
|
||||
* <li>Shift+Tab indents the current line or currently selected lines
|
||||
* to the left.
|
||||
* </ul>
|
||||
*
|
||||
* @author Robert Futrell
|
||||
* @version 1.0
|
||||
*/
|
||||
@SuppressWarnings({ "checkstyle:linelength" })
|
||||
public class RSyntaxTextAreaDefaultInputMap extends RTADefaultInputMap {
|
||||
|
||||
/**
|
||||
* Constructs the default input map for an <code>RSyntaxTextArea</code>.
|
||||
*/
|
||||
public RSyntaxTextAreaDefaultInputMap() {
|
||||
|
||||
int defaultMod = getDefaultModifier();
|
||||
//int ctrl = InputEvent.CTRL_MASK;
|
||||
int shift = InputEvent.SHIFT_MASK;
|
||||
//int alt = InputEvent.ALT_MASK;
|
||||
int defaultShift = defaultMod|shift;
|
||||
|
||||
put(KeyStroke.getKeyStroke(KeyEvent.VK_TAB, shift), RSyntaxTextAreaEditorKit.rstaDecreaseIndentAction);
|
||||
put(KeyStroke.getKeyStroke('}'), RSyntaxTextAreaEditorKit.rstaCloseCurlyBraceAction);
|
||||
|
||||
put(KeyStroke.getKeyStroke('/'), RSyntaxTextAreaEditorKit.rstaCloseMarkupTagAction);
|
||||
int os = RSyntaxUtilities.getOS();
|
||||
if (os==RSyntaxUtilities.OS_WINDOWS || os==RSyntaxUtilities.OS_MAC_OSX) {
|
||||
// *nix causes trouble with CloseMarkupTagAction and ToggleCommentAction.
|
||||
// It triggers both KEY_PRESSED ctrl+'/' and KEY_TYPED '/' events when the
|
||||
// user presses ctrl+'/', but Windows and OS X do not. If we try to "move"
|
||||
// the KEY_TYPED event for '/' to KEY_PRESSED, it'll work for Linux boxes
|
||||
// with QWERTY keyboard layouts, but non-QUERTY users won't be able to type
|
||||
// a '/' character at all then (!). Rather than try to hack together a
|
||||
// solution by trying to detect the IM locale and do different things for
|
||||
// different OSes & keyboard layouts, we do the simplest thing and
|
||||
// (unfortunately) don't have a ToggleCommentAction for *nix out-of-the-box.
|
||||
// Applications can add one easily enough if they want one.
|
||||
put(KeyStroke.getKeyStroke(KeyEvent.VK_SLASH, defaultMod), RSyntaxTextAreaEditorKit.rstaToggleCommentAction);
|
||||
}
|
||||
|
||||
put(KeyStroke.getKeyStroke(KeyEvent.VK_OPEN_BRACKET, defaultMod), RSyntaxTextAreaEditorKit.rstaGoToMatchingBracketAction);
|
||||
put(KeyStroke.getKeyStroke(KeyEvent.VK_SUBTRACT, defaultMod), RSyntaxTextAreaEditorKit.rstaCollapseFoldAction);
|
||||
put(KeyStroke.getKeyStroke(KeyEvent.VK_ADD, defaultMod), RSyntaxTextAreaEditorKit.rstaExpandFoldAction);
|
||||
put(KeyStroke.getKeyStroke(KeyEvent.VK_DIVIDE, defaultMod), RSyntaxTextAreaEditorKit.rstaCollapseAllFoldsAction);
|
||||
put(KeyStroke.getKeyStroke(KeyEvent.VK_MULTIPLY, defaultMod), RSyntaxTextAreaEditorKit.rstaExpandAllFoldsAction);
|
||||
|
||||
// NOTE: no modifiers => mapped to keyTyped. If we had "0" as a second
|
||||
// second parameter, we'd get the template action (keyPressed) AND the
|
||||
// default space action (keyTyped).
|
||||
//put(KeyStroke.getKeyStroke(' '), RSyntaxTextAreaEditorKit.rstaPossiblyInsertTemplateAction);
|
||||
put(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, defaultShift), RSyntaxTextAreaEditorKit.rstaPossiblyInsertTemplateAction);
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,291 @@
|
||||
/*
|
||||
* 04/23/2009
|
||||
*
|
||||
* RSyntaxTextAreaHighlighter.java - Highlighter for RSyntaxTextAreas.
|
||||
*
|
||||
* 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.Graphics;
|
||||
import java.awt.Shape;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import javax.swing.plaf.TextUI;
|
||||
import javax.swing.text.BadLocationException;
|
||||
import javax.swing.text.Document;
|
||||
import javax.swing.text.Element;
|
||||
import javax.swing.text.JTextComponent;
|
||||
import javax.swing.text.View;
|
||||
|
||||
import org.fife.ui.rsyntaxtextarea.parser.Parser;
|
||||
import org.fife.ui.rsyntaxtextarea.parser.ParserNotice;
|
||||
import org.fife.ui.rtextarea.RTextAreaHighlighter;
|
||||
import org.fife.ui.rtextarea.SmartHighlightPainter;
|
||||
|
||||
|
||||
/**
|
||||
* The highlighter implementation used by {@link RSyntaxTextArea}s. It knows to
|
||||
* always paint "marked occurrences" highlights below selection highlights,
|
||||
* and squiggle underline highlights above all other highlights.<p>
|
||||
*
|
||||
* Most of this code is copied from javax.swing.text.DefaultHighlighter;
|
||||
* unfortunately, we cannot re-use much of it since it is package private.
|
||||
*
|
||||
* @author Robert Futrell
|
||||
* @version 1.0
|
||||
*/
|
||||
public class RSyntaxTextAreaHighlighter extends RTextAreaHighlighter {
|
||||
|
||||
/**
|
||||
* Marked occurrences in the document (to be painted separately from
|
||||
* other highlights).
|
||||
*/
|
||||
private List<SyntaxLayeredHighlightInfoImpl> markedOccurrences;
|
||||
|
||||
/**
|
||||
* Highlights from document parsers. These should be painted "on top of"
|
||||
* all other highlights to ensure they are always above the selection.
|
||||
*/
|
||||
private List<SyntaxLayeredHighlightInfoImpl> parserHighlights;
|
||||
|
||||
/**
|
||||
* The default color used for parser notices when none is specified.
|
||||
*/
|
||||
private static final Color DEFAULT_PARSER_NOTICE_COLOR = Color.RED;
|
||||
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public RSyntaxTextAreaHighlighter() {
|
||||
markedOccurrences = new ArrayList<SyntaxLayeredHighlightInfoImpl>();
|
||||
parserHighlights = new ArrayList<SyntaxLayeredHighlightInfoImpl>(0); // Often unused
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds a special "marked occurrence" highlight.
|
||||
*
|
||||
* @param start
|
||||
* @param end
|
||||
* @param p
|
||||
* @return A tag to reference the highlight later.
|
||||
* @throws BadLocationException
|
||||
* @see #clearMarkOccurrencesHighlights()
|
||||
*/
|
||||
Object addMarkedOccurrenceHighlight(int start, int end,
|
||||
SmartHighlightPainter p) throws BadLocationException {
|
||||
Document doc = textArea.getDocument();
|
||||
TextUI mapper = textArea.getUI();
|
||||
// Always layered highlights for marked occurrences.
|
||||
SyntaxLayeredHighlightInfoImpl i = new SyntaxLayeredHighlightInfoImpl();
|
||||
i.setPainter(p);
|
||||
i.setStartOffset(doc.createPosition(start));
|
||||
// HACK: Use "end-1" to prevent chars the user types at the "end" of
|
||||
// the highlight to be absorbed into the highlight (default Highlight
|
||||
// behavior).
|
||||
i.setEndOffset(doc.createPosition(end-1));
|
||||
markedOccurrences.add(i);
|
||||
mapper.damageRange(textArea, start, end);
|
||||
return i;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds a highlight from a parser.
|
||||
*
|
||||
* @param notice The notice from a {@link Parser}.
|
||||
* @return A tag with which to reference the highlight.
|
||||
* @throws BadLocationException
|
||||
* @see #clearParserHighlights()
|
||||
* @see #clearParserHighlights(Parser)
|
||||
*/
|
||||
HighlightInfo addParserHighlight(ParserNotice notice, HighlightPainter p)
|
||||
throws BadLocationException {
|
||||
|
||||
Document doc = textArea.getDocument();
|
||||
TextUI mapper = textArea.getUI();
|
||||
|
||||
int start = notice.getOffset();
|
||||
int end = 0;
|
||||
if (start==-1) { // Could just define an invalid line number
|
||||
int line = notice.getLine();
|
||||
Element root = doc.getDefaultRootElement();
|
||||
if (line>=0 && line<root.getElementCount()) {
|
||||
Element elem = root.getElement(line);
|
||||
start = elem.getStartOffset();
|
||||
end = elem.getEndOffset();
|
||||
}
|
||||
}
|
||||
else {
|
||||
end = start + notice.getLength();
|
||||
}
|
||||
|
||||
// Always layered highlights for parser highlights.
|
||||
SyntaxLayeredHighlightInfoImpl i = new SyntaxLayeredHighlightInfoImpl();
|
||||
i.setPainter(p);
|
||||
i.setStartOffset(doc.createPosition(start));
|
||||
// HACK: Use "end-1" to prevent chars the user types at the "end" of
|
||||
// the highlight to be absorbed into the highlight (default Highlight
|
||||
// behavior).
|
||||
i.setEndOffset(doc.createPosition(end-1));
|
||||
i.notice = notice;//i.color = notice.getColor();
|
||||
|
||||
parserHighlights.add(i);
|
||||
mapper.damageRange(textArea, start, end);
|
||||
return i;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Removes all "marked occurrences" highlights from the view.
|
||||
*
|
||||
* @see #addMarkedOccurrenceHighlight(int, int, SmartHighlightPainter)
|
||||
*/
|
||||
void clearMarkOccurrencesHighlights() {
|
||||
// Don't remove via an iterator; since our List is an ArrayList, this
|
||||
// implies tons of System.arrayCopy()s
|
||||
for (HighlightInfo info : markedOccurrences) {
|
||||
repaintListHighlight(info);
|
||||
}
|
||||
markedOccurrences.clear();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Removes all parser highlights.
|
||||
*
|
||||
* @see #addParserHighlight(ParserNotice, javax.swing.text.Highlighter.HighlightPainter)
|
||||
*/
|
||||
void clearParserHighlights() {
|
||||
// Don't remove via an iterator; since our List is an ArrayList, this
|
||||
// implies tons of System.arrayCopy()s
|
||||
for (int i=0; i<parserHighlights.size(); i++) {
|
||||
repaintListHighlight(parserHighlights.get(i));
|
||||
}
|
||||
parserHighlights.clear();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Removes all of the highlights for a specific parser.
|
||||
*
|
||||
* @param parser The parser.
|
||||
*/
|
||||
public void clearParserHighlights(Parser parser) {
|
||||
|
||||
Iterator<SyntaxLayeredHighlightInfoImpl> i = parserHighlights.iterator();
|
||||
while (i.hasNext()) {
|
||||
|
||||
SyntaxLayeredHighlightInfoImpl info = i.next();
|
||||
|
||||
if (info.notice.getParser()==parser) {
|
||||
if (info.width > 0 && info.height > 0) {
|
||||
textArea.repaint(info.x, info.y, info.width, info.height);
|
||||
}
|
||||
i.remove();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void deinstall(JTextComponent c) {
|
||||
super.deinstall(c);
|
||||
markedOccurrences.clear();
|
||||
parserHighlights.clear();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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, or an empty list if none. The
|
||||
* contents of this list will be of type {@link DocumentRange}.
|
||||
*/
|
||||
public List<DocumentRange> getMarkedOccurrences() {
|
||||
List<DocumentRange> list = new ArrayList<DocumentRange>(markedOccurrences.size());
|
||||
for (HighlightInfo info : markedOccurrences) {
|
||||
int start = info.getStartOffset();
|
||||
int end = info.getEndOffset() + 1; // HACK
|
||||
if (start <= end) {
|
||||
// Occasionally a Marked Occurrence can have a lost end offset
|
||||
// but not start offset (replacing entire text content with
|
||||
// new content, and a marked occurrence is on the last token
|
||||
// in the document).
|
||||
DocumentRange range = new DocumentRange(start, end);
|
||||
list.add(range);
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void paintLayeredHighlights(Graphics g, int lineStart, int lineEnd,
|
||||
Shape viewBounds, JTextComponent editor, View view) {
|
||||
paintListLayered(g, lineStart,lineEnd, viewBounds, editor, view, markedOccurrences);
|
||||
super.paintLayeredHighlights(g, lineStart, lineEnd, viewBounds, editor, view);
|
||||
paintListLayered(g, lineStart,lineEnd, viewBounds, editor, view, parserHighlights);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Removes a parser highlight from this view.
|
||||
*
|
||||
* @param tag The reference to the highlight.
|
||||
* @see #addParserHighlight(ParserNotice, javax.swing.text.Highlighter.HighlightPainter)
|
||||
*/
|
||||
void removeParserHighlight(HighlightInfo tag) {
|
||||
repaintListHighlight(tag);
|
||||
parserHighlights.remove(tag);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Highlight info implementation used for parser notices and marked
|
||||
* occurrences.
|
||||
*/
|
||||
private static class SyntaxLayeredHighlightInfoImpl extends
|
||||
LayeredHighlightInfoImpl {
|
||||
|
||||
private ParserNotice notice;//Color color; // Used only by Parser highlights.
|
||||
|
||||
@Override
|
||||
public Color getColor() {
|
||||
//return color;
|
||||
Color color = null;
|
||||
if (notice!=null) {
|
||||
color = notice.getColor();
|
||||
if (color==null) {
|
||||
color = DEFAULT_PARSER_NOTICE_COLOR;
|
||||
}
|
||||
}
|
||||
return color;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "[SyntaxLayeredHighlightInfoImpl: " +
|
||||
"startOffs=" + getStartOffset() +
|
||||
", endOffs=" + getEndOffset() +
|
||||
", color=" + getColor() +
|
||||
"]";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,269 @@
|
||||
/*
|
||||
* 02/24/2004
|
||||
*
|
||||
* RSyntaxTextAreaUI.java - UI for an RSyntaxTextArea.
|
||||
*
|
||||
* This library is distributed under a modified BSD license. See the included
|
||||
* RSyntaxTextArea.License.txt file for details.
|
||||
*/
|
||||
package org.fife.ui.rsyntaxtextarea;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Rectangle;
|
||||
import java.beans.PropertyChangeEvent;
|
||||
|
||||
import javax.swing.InputMap;
|
||||
import javax.swing.JComponent;
|
||||
import javax.swing.UIManager;
|
||||
import javax.swing.plaf.ComponentUI;
|
||||
import javax.swing.plaf.InputMapUIResource;
|
||||
import javax.swing.text.BadLocationException;
|
||||
import javax.swing.text.EditorKit;
|
||||
import javax.swing.text.Element;
|
||||
import javax.swing.text.Highlighter;
|
||||
import javax.swing.text.JTextComponent;
|
||||
import javax.swing.text.View;
|
||||
|
||||
import org.fife.ui.rtextarea.RTextArea;
|
||||
import org.fife.ui.rtextarea.RTextAreaUI;
|
||||
|
||||
|
||||
/**
|
||||
* UI used by <code>RSyntaxTextArea</code>. This allows us to implement
|
||||
* syntax highlighting.
|
||||
*
|
||||
* @author Robert Futrell
|
||||
* @version 0.1
|
||||
*/
|
||||
public class RSyntaxTextAreaUI extends RTextAreaUI {
|
||||
|
||||
private static final String SHARED_ACTION_MAP_NAME = "RSyntaxTextAreaUI.actionMap";
|
||||
private static final String SHARED_INPUT_MAP_NAME = "RSyntaxTextAreaUI.inputMap";
|
||||
private static final EditorKit DEFAULT_KIT = new RSyntaxTextAreaEditorKit();
|
||||
|
||||
|
||||
public static ComponentUI createUI(JComponent ta) {
|
||||
return new RSyntaxTextAreaUI(ta);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public RSyntaxTextAreaUI(JComponent rSyntaxTextArea) {
|
||||
super(rSyntaxTextArea);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates the view for an element.
|
||||
*
|
||||
* @param elem The element.
|
||||
* @return The view.
|
||||
*/
|
||||
@Override
|
||||
public View create(Element elem) {
|
||||
RTextArea c = getRTextArea();
|
||||
if (c instanceof RSyntaxTextArea) {
|
||||
RSyntaxTextArea area = (RSyntaxTextArea) c;
|
||||
View v;
|
||||
if (area.getLineWrap()) {
|
||||
v = new WrappedSyntaxView(elem);
|
||||
}
|
||||
else {
|
||||
v = new SyntaxView(elem);
|
||||
}
|
||||
return v;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates the highlighter to use for syntax text areas.
|
||||
*
|
||||
* @return The highlighter.
|
||||
*/
|
||||
@Override
|
||||
protected Highlighter createHighlighter() {
|
||||
return new RSyntaxTextAreaHighlighter();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the name to use to cache/fetch the shared action map. This
|
||||
* should be overridden by subclasses if the subclass has its own custom
|
||||
* editor kit to install, so its actions get picked up.
|
||||
*
|
||||
* @return The name of the cached action map.
|
||||
*/
|
||||
@Override
|
||||
protected String getActionMapName() {
|
||||
return SHARED_ACTION_MAP_NAME;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Fetches the EditorKit for the UI.
|
||||
*
|
||||
* @param tc The text component for which this UI is installed.
|
||||
* @return The editor capabilities.
|
||||
* @see javax.swing.plaf.TextUI#getEditorKit
|
||||
*/
|
||||
@Override
|
||||
public EditorKit getEditorKit(JTextComponent tc) {
|
||||
return DEFAULT_KIT;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the InputMap to use for the UI.<p>
|
||||
*
|
||||
* This method is not named <code>getInputMap()</code> because there is
|
||||
* a package-private method in <code>BasicTextAreaUI</code> with that name.
|
||||
* Thus, creating a new method with that name causes certain compilers to
|
||||
* issue warnings that you are not actually overriding the original method
|
||||
* (since it is package-private).
|
||||
*/
|
||||
@Override
|
||||
protected InputMap getRTextAreaInputMap() {
|
||||
InputMap map = new InputMapUIResource();
|
||||
InputMap shared = (InputMap)UIManager.get(SHARED_INPUT_MAP_NAME);
|
||||
if (shared==null) {
|
||||
shared = new RSyntaxTextAreaDefaultInputMap();
|
||||
UIManager.put(SHARED_INPUT_MAP_NAME, shared);
|
||||
}
|
||||
//KeyStroke[] keys = shared.allKeys();
|
||||
//for (int i=0; i<keys.length; i++)
|
||||
// System.err.println(keys[i] + " -> " + shared.get(keys[i]));
|
||||
map.setParent(shared);
|
||||
return map;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void paintEditorAugmentations(Graphics g) {
|
||||
super.paintEditorAugmentations(g);
|
||||
paintMatchedBracket(g);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Paints the "matched bracket", if any.
|
||||
*
|
||||
* @param g The graphics context.
|
||||
*/
|
||||
protected void paintMatchedBracket(Graphics g) {
|
||||
RSyntaxTextArea rsta = (RSyntaxTextArea)textArea;
|
||||
if (rsta.isBracketMatchingEnabled()) {
|
||||
Rectangle match = rsta.getMatchRectangle();
|
||||
if (match!=null) {
|
||||
paintMatchedBracketImpl(g, rsta, match);
|
||||
}
|
||||
if (rsta.getPaintMatchedBracketPair()) {
|
||||
Rectangle dotRect = rsta.getDotRectangle();
|
||||
if (dotRect!=null) { // should always be true
|
||||
paintMatchedBracketImpl(g, rsta, dotRect);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected void paintMatchedBracketImpl(Graphics g, RSyntaxTextArea rsta,
|
||||
Rectangle r) {
|
||||
// We must add "-1" to the height because otherwise we'll paint below
|
||||
// the region that gets invalidated.
|
||||
if (rsta.getAnimateBracketMatching()) {
|
||||
Color bg = rsta.getMatchedBracketBGColor();
|
||||
final int arcWH = 5;
|
||||
if (bg!=null) {
|
||||
g.setColor(bg);
|
||||
g.fillRoundRect(r.x,r.y, r.width,r.height-1, arcWH, arcWH);
|
||||
}
|
||||
g.setColor(rsta.getMatchedBracketBorderColor());
|
||||
g.drawRoundRect(r.x,r.y, r.width,r.height-1, arcWH, arcWH);
|
||||
}
|
||||
else {
|
||||
Color bg = rsta.getMatchedBracketBGColor();
|
||||
if (bg!=null) {
|
||||
g.setColor(bg);
|
||||
g.fillRect(r.x,r.y, r.width,r.height-1);
|
||||
}
|
||||
g.setColor(rsta.getMatchedBracketBorderColor());
|
||||
g.drawRect(r.x,r.y, r.width,r.height-1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets called whenever a bound property is changed on this UI's
|
||||
* <code>RSyntaxTextArea</code>.
|
||||
*
|
||||
* @param e The property change event.
|
||||
*/
|
||||
@Override
|
||||
protected void propertyChange(PropertyChangeEvent e) {
|
||||
|
||||
String name = e.getPropertyName();
|
||||
|
||||
// If they change the syntax scheme, we must do this so that
|
||||
// WrappedSyntaxView(_TEST) updates its child views properly.
|
||||
if (name.equals(RSyntaxTextArea.SYNTAX_SCHEME_PROPERTY)) {
|
||||
modelChanged();
|
||||
}
|
||||
|
||||
// Everything else is general to all RTextAreas.
|
||||
else {
|
||||
super.propertyChange(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Updates the view. This should be called when the underlying
|
||||
* <code>RSyntaxTextArea</code> changes its syntax editing style.
|
||||
*/
|
||||
public void refreshSyntaxHighlighting() {
|
||||
modelChanged();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the y-coordinate of the specified line.<p>
|
||||
*
|
||||
* This method is quicker than using traditional
|
||||
* <code>modelToView(int)</code> calls, as the entire bounding box isn't
|
||||
* computed.
|
||||
*/
|
||||
@Override
|
||||
public int yForLine(int line) throws BadLocationException {
|
||||
Rectangle alloc = getVisibleEditorRect();
|
||||
if (alloc!=null) {
|
||||
RSTAView view = (RSTAView)getRootView(textArea).getView(0);
|
||||
return view.yForLine(alloc, line);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the y-coordinate of the line containing a specified offset.<p>
|
||||
*
|
||||
* This is faster than calling <code>modelToView(offs).y</code>, so it is
|
||||
* preferred if you do not need the actual bounding box.
|
||||
*/
|
||||
@Override
|
||||
public int yForLineContaining(int offs) throws BadLocationException {
|
||||
Rectangle alloc = getVisibleEditorRect();
|
||||
if (alloc!=null) {
|
||||
RSTAView view = (RSTAView)getRootView(textArea).getView(0);
|
||||
return view.yForLineContaining(alloc, offs);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
ContextMenu.Folding=\u0637\u0648\u064a
|
||||
|
||||
Action.ToggleCurrentFold.Name=\u063a\u064a\u0651\u0631 \u0648\u0636\u0639\u064a\u0629 \u0627\u0644\u0637\u0648\u064a\u0629 \u0627\u0644\u062d\u0627\u0644\u064a\u0629
|
||||
Action.ToggleCurrentFold.Mnemonic=F
|
||||
Action.ToggleCurrentFold.Desc=\u0645\u0646 \u0623\u062c\u0644 \u062a\u063a\u064a\u064a\u0631 \u0648\u0636\u0639\u064a\u0629 \u0627\u0644\u0637\u0648\u064a\u0629 \u0641\u064a \u0645\u0648\u0642\u0639 \u0627\u0644\u0645\u0624\u0634\u0631 \u0627\u0644\u062d\u0627\u0644\u064a
|
||||
Action.CollapseAllFolds.Name=\u0642\u0644\u0635 \u062c\u0645\u064a\u0639 \u0627\u0644\u0637\u064a\u0627\u062a
|
||||
Action.CollapseAllFolds.Mnemonic=O
|
||||
Action.CollapseAllFolds.Desc=\u0645\u0646 \u0623\u062c\u0644 \u062a\u0642\u0644\u064a\u0635 \u062c\u0645\u064a\u0639 \u0627\u0644\u0637\u064a\u0627\u062a
|
||||
Action.CollapseCommentFolds.Name=\u0642\u0644\u0635 \u062c\u0645\u064a\u0639 \u0627\u0644\u062a\u0639\u0644\u064a\u0642\u0627\u062a
|
||||
Action.CollapseCommentFolds.Mnemonic=C
|
||||
Action.CollapseCommentFolds.Desc=\u0645\u0646 \u0623\u062c\u0644 \u062a\u0642\u0644\u064a\u0635 \u062c\u0645\u064a\u0639 \u0637\u064a\u0627\u062a \u0627\u0644\u062a\u0639\u0644\u064a\u0642\u0627\u062a
|
||||
Action.ExpandAllFolds.Name=\u0648\u0633\u0639 \u062c\u0645\u064a\u0639 \u0627\u0644\u0637\u064a\u0627\u062a
|
||||
Action.ExpandAllFolds.Mnemonic=E
|
||||
Action.ExpandAllFolds.Desc=\u0645\u0646 \u0623\u062c\u0644 \u062a\u0648\u0633\u064a\u0639 \u062c\u0645\u064a\u0639 \u0627\u0644\u0637\u064a\u0627\u062a.
|
||||
@@ -0,0 +1,14 @@
|
||||
ContextMenu.Folding=Klappung
|
||||
|
||||
Action.ToggleCurrentFold.Name=Aktuellen Block umschalten
|
||||
Action.ToggleCurrentFold.Mnemonic=F
|
||||
Action.ToggleCurrentFold.Desc=Schaltet den Block an der Position der Schreibmarke um.
|
||||
Action.CollapseAllFolds.Name=Action.CollapseAllFolds.Name=Alle Bl\u00f6cke zuklappen
|
||||
Action.CollapseAllFolds.Mnemonic=O
|
||||
Action.CollapseAllFolds.Desc=Klappt alle Bl\u00f6cke zu.
|
||||
Action.CollapseCommentFolds.Name=Alle Kommentarbl\u00f6cke zuklappen
|
||||
Action.CollapseCommentFolds.Mnemonic=C
|
||||
Action.CollapseCommentFolds.Desc=Klappt alle Kommentarbl\u00f6cke zu.
|
||||
Action.ExpandAllFolds.Name=Alle Bl\u00f6cke aufklappen
|
||||
Action.ExpandAllFolds.Mnemonic=E
|
||||
Action.ExpandAllFolds.Desc=Klappt alle Bl\u00f6cke auf.
|
||||
@@ -0,0 +1,14 @@
|
||||
ContextMenu.Folding=Folding
|
||||
|
||||
Action.ToggleCurrentFold.Name=Ver Fold (solapa) actual
|
||||
Action.ToggleCurrentFold.Mnemonic=F
|
||||
Action.ToggleCurrentFold.Desc=Mostrar el fold en la posici\u00f3n del cursor
|
||||
Action.CollapseAllFolds.Name=Ocultar todos los folds
|
||||
Action.CollapseAllFolds.Mnemonic=O
|
||||
Action.CollapseAllFolds.Desc=Oculta todos los folds
|
||||
Action.CollapseCommentFolds.Name=Ocultar todos los comentarios
|
||||
Action.CollapseCommentFolds.Mnemonic=C
|
||||
Action.CollapseCommentFolds.Desc=Oculta todos los comentarios
|
||||
Action.ExpandAllFolds.Name=Expandir todos las solapas (folds)
|
||||
Action.ExpandAllFolds.Mnemonic=E
|
||||
Action.ExpandAllFolds.Desc=Expande todas las solapas (folds)
|
||||
@@ -0,0 +1,14 @@
|
||||
ContextMenu.Folding=Pliage
|
||||
|
||||
Action.ToggleCurrentFold.Name=Active ou d\u00e9sactive le pli actuel
|
||||
Action.ToggleCurrentFold.Mnemonic=F
|
||||
Action.ToggleCurrentFold.Desc=Active ou d\u00e9sactive le pli \u00e0 la position du curseur.
|
||||
Action.CollapseAllFolds.Name=Comprimer tous les plis
|
||||
Action.CollapseAllFolds.Mnemonic=O
|
||||
Action.CollapseAllFolds.Desc=Comprime tous les plis.
|
||||
Action.CollapseCommentFolds.Name=Comprimer tous les commentaires
|
||||
Action.CollapseCommentFolds.Mnemonic=C
|
||||
Action.CollapseCommentFolds.Desc=Comprime tous les plis commentaires.
|
||||
Action.ExpandAllFolds.Name=D\u00e9comprimer tous les plis
|
||||
Action.ExpandAllFolds.Mnemonic=E
|
||||
Action.ExpandAllFolds.Desc=D\u00e9comprime tous les plis.
|
||||
@@ -0,0 +1,14 @@
|
||||
ContextMenu.Folding=\u00d6sszevon\u00e1s
|
||||
|
||||
Action.ToggleCurrentFold.Name=A jelenlegi \u00f6sszevon\u00e1s megjelen\u00edt\u00e9se/rejt\u00e9se.
|
||||
Action.ToggleCurrentFold.Mnemonic=F
|
||||
Action.ToggleCurrentFold.Desc=Megjelen\u00edti, illetve elt\u00fcnteti az \u00f6sszevon\u00e1st a kurzor poz\u00edci\u00f3j\u00e1ban.
|
||||
Action.CollapseAllFolds.Name=\u00d6sszevon\u00e1sok rejt\u00e9se.
|
||||
Action.CollapseAllFolds.Mnemonic=O
|
||||
Action.CollapseAllFolds.Desc=\u00d6sszevon\u00e1sok rejt\u00e9se.
|
||||
Action.CollapseCommentFolds.Name=Az \u00f6sszes megjegyz\u00e9s elrejt\u00e9se
|
||||
Action.CollapseCommentFolds.Mnemonic=C
|
||||
Action.CollapseCommentFolds.Desc=Megjegyz\u00e9s \u00f6sszevon\u00e1sok rejt\u00e9se.
|
||||
Action.ExpandAllFolds.Name=\u00d6sszevon\u00e1sok kiterjeszt\u00e9se
|
||||
Action.ExpandAllFolds.Mnemonic=E
|
||||
Action.ExpandAllFolds.Desc=\u00d6sszevon\u00e1sok kiterjeszt\u00e9se.
|
||||
@@ -0,0 +1,14 @@
|
||||
ContextMenu.Folding=Folding
|
||||
|
||||
Action.ToggleCurrentFold.Name=Toggle Current Fold
|
||||
Action.ToggleCurrentFold.Mnemonic=F
|
||||
Action.ToggleCurrentFold.Desc=Toggles the fold at the caret position.
|
||||
Action.CollapseAllFolds.Name=Collapse All Folds
|
||||
Action.CollapseAllFolds.Mnemonic=O
|
||||
Action.CollapseAllFolds.Desc=Collapses all folds.
|
||||
Action.CollapseCommentFolds.Name=Collapse All Comments
|
||||
Action.CollapseCommentFolds.Mnemonic=C
|
||||
Action.CollapseCommentFolds.Desc=Collapses all comment folds.
|
||||
Action.ExpandAllFolds.Name=Expand All Folds
|
||||
Action.ExpandAllFolds.Mnemonic=E
|
||||
Action.ExpandAllFolds.Desc=Expands all folds.
|
||||
@@ -0,0 +1,14 @@
|
||||
ContextMenu.Folding=Folding
|
||||
|
||||
Action.ToggleCurrentFold.Name=Imposta il fold corrente
|
||||
Action.ToggleCurrentFold.Mnemonic=F
|
||||
Action.ToggleCurrentFold.Desc=Imposta il fold alla posizione corrente.
|
||||
Action.CollapseAllFolds.Name=Raggruppa tutti i fold
|
||||
Action.CollapseAllFolds.Mnemonic=O
|
||||
Action.CollapseAllFolds.Desc=Raggruppa tutti i fold.
|
||||
Action.CollapseCommentFolds.Name=Raggruppa tutti i commenti
|
||||
Action.CollapseCommentFolds.Mnemonic=C
|
||||
Action.CollapseCommentFolds.Desc=Raggruppa tutti i commenti.
|
||||
Action.ExpandAllFolds.Name=Espandi tutti i fold
|
||||
Action.ExpandAllFolds.Mnemonic=E
|
||||
Action.ExpandAllFolds.Desc=Espandi tutti i fold.
|
||||
@@ -0,0 +1,14 @@
|
||||
ContextMenu.Folding=\u30d5\u30a9\u30eb\u30c7\u30a3\u30f3\u30b0
|
||||
|
||||
Action.ToggleCurrentFold.Name=\u30ad\u30e3\u30ec\u30c3\u30c8\u5074\u306e\u30d5\u30a9\u30eb\u30c9\u3092\u30c8\u30b0\u30eb\u3059\u308b\u3002
|
||||
Action.ToggleCurrentFold.Mnemonic=F
|
||||
Action.ToggleCurrentFold.Desc=\u30ad\u30e3\u30ec\u30c3\u30c8\u5074\u306e\u30d5\u30a9\u30eb\u30c9\u3092\u30c8\u30b0\u30eb\u3059\u308b\u3002
|
||||
Action.CollapseAllFolds.Name=\u5168\u3066\u306e\u30d5\u30a9\u30eb\u30c9\u3092\u6298\u308a\u305f\u305f\u3080
|
||||
Action.CollapseAllFolds.Mnemonic=O
|
||||
Action.CollapseAllFolds.Desc=\u5168\u3066\u306e\u3075\u3049\u308b\u3069\u3092\u6298\u308a\u305f\u305f\u3080
|
||||
Action.CollapseCommentFolds.Name=\u5168\u3066\u306e\u30b3\u30e1\u30f3\u30c8\u306e\u30d5\u30a9\u30eb\u30c9\u3092\u6298\u308a\u305f\u305f\u3080
|
||||
Action.CollapseCommentFolds.Mnemonic=C
|
||||
Action.CollapseCommentFolds.Desc=\u5168\u3066\u306e\u30b3\u30e1\u30f3\u30c8\u306e\u30d5\u30a9\u30eb\u30c9\u3092\u6298\u308a\u305f\u305f\u3080
|
||||
Action.ExpandAllFolds.Name=\u5168\u3066\u306e\u30d5\u30a9\u30eb\u30c9\u3092\u5c55\u958b\u3059\u308b
|
||||
Action.ExpandAllFolds.Mnemonic=E
|
||||
Action.ExpandAllFolds.Desc=\u5168\u3066\u306e\u30d5\u30a9\u30eb\u30c9\u3092\u5c55\u958b\u3059\u308b
|
||||
@@ -0,0 +1,14 @@
|
||||
ContextMenu.Folding=\ucf54\ub4dc \uac10\ucd94\uae30
|
||||
|
||||
Action.ToggleCurrentFold.Name=\ud604\uc7ac \ucf54\ub4dc \uac10\ucd94\uae30 \ud1a0\uae00
|
||||
Action.ToggleCurrentFold.Mnemonic=F
|
||||
Action.ToggleCurrentFold.Desc=\uce90\ub7ff \uc704\uce58\uc5d0\uc11c \ucf54\ub4dc \uac10\ucd94\uae30\ub97c \ud1a0\uae00\ud569\ub2c8\ub2e4.
|
||||
Action.CollapseAllFolds.Name=\ubaa8\ub4e0 \ucf54\ub4dc \uac10\ucd94\uae30 \uc811\uae30
|
||||
Action.CollapseAllFolds.Mnemonic=O
|
||||
Action.CollapseAllFolds.Desc=\ubaa8\ub4e0 \ucf54\ub4dc \uac10\ucd94\uae30\ub97c \uc811\uc2b5\ub2c8\ub2e4.
|
||||
Action.CollapseCommentFolds.Name=\ubaa8\ub4e0 \uc8fc\uc11d \uac10\ucd94\uae30 \uc811\uae30
|
||||
Action.CollapseCommentFolds.Mnemonic=C
|
||||
Action.CollapseCommentFolds.Desc=\ubaa8\ub4e0 \uc8fc\uc11d \uac10\ucd94\uae30\ub97c \uc811\uc2b5\ub2c8\ub2e4.
|
||||
Action.ExpandAllFolds.Name=\ubaa8\ub4e0 \ucf54\ub4dc \uac10\ucd94\uae30 \ud3bc\uce58\uae30
|
||||
Action.ExpandAllFolds.Mnemonic=E
|
||||
Action.ExpandAllFolds.Desc=\ubaa8\ub4e0 \ucf54\ub4dc \uac10\ucd94\uae30\ub97c \ud3bc\uce69\ub2c8\ub2e4.
|
||||
@@ -0,0 +1,14 @@
|
||||
ContextMenu.Folding=Folding
|
||||
|
||||
Action.ToggleCurrentFold.Name=Toggle Current Fold
|
||||
Action.ToggleCurrentFold.Mnemonic=F
|
||||
Action.ToggleCurrentFold.Desc=Toggles the fold at the caret position.
|
||||
Action.CollapseAllFolds.Name=Collapse All Folds
|
||||
Action.CollapseAllFolds.Mnemonic=O
|
||||
Action.CollapseAllFolds.Desc=Collapses all folds.
|
||||
Action.CollapseCommentFolds.Name=Collapse All Comments
|
||||
Action.CollapseCommentFolds.Mnemonic=C
|
||||
Action.CollapseCommentFolds.Desc=Collapses all comment folds.
|
||||
Action.ExpandAllFolds.Name=Expand All Folds
|
||||
Action.ExpandAllFolds.Mnemonic=E
|
||||
Action.ExpandAllFolds.Desc=Expands all folds.
|
||||
@@ -0,0 +1,15 @@
|
||||
ContextMenu.Folding=Zwijanie
|
||||
|
||||
Action.ToggleCurrentFold.Name=Reguluj zwini\u0119cie
|
||||
Action.ToggleCurrentFold.Mnemonic=F
|
||||
Action.ToggleCurrentFold.Desc=Ustawia zwini\u0119cie na pozycji karetki.
|
||||
Action.CollapseAllFolds.Name=Zwi\u0144 wszystkie zwini\u0119cia
|
||||
Action.CollapseAllFolds.Mnemonic=O
|
||||
Action.CollapseAllFolds.Desc=Zwija wszystkie zwini\u0119cia.
|
||||
Action.CollapseCommentFolds.Name=Zwi\u0144 wszystkie komentarze
|
||||
Action.CollapseCommentFolds.Mnemonic=C
|
||||
Action.CollapseCommentFolds.Desc=Zwija wszystkie zwini\u0119cia z komentarzami.
|
||||
Action.ExpandAllFolds.Name=Rozwi\u0144 zwini\u0119cia
|
||||
Action.ExpandAllFolds.Mnemonic=E
|
||||
Action.ExpandAllFolds.Desc=Rozwija wszystkie zwini\u0119cia.
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
ContextMenu.Folding=Dobramento de c\u00f3digo
|
||||
|
||||
Action.ToggleCurrentFold.Name=Alternar o dobramento atual
|
||||
Action.ToggleCurrentFold.Mnemonic=F
|
||||
Action.ToggleCurrentFold.Desc=Alterna o dobramento na posi\u00e7\u00e3o do cursor.
|
||||
Action.CollapseAllFolds.Name=Recolher o dobramento de c\u00f3digo
|
||||
Action.CollapseAllFolds.Mnemonic=O
|
||||
Action.CollapseAllFolds.Desc=Recolher o dobramento de c\u00f3digo.
|
||||
Action.CollapseCommentFolds.Name=Recolher o dobramento de coment\u00e1rios
|
||||
Action.CollapseCommentFolds.Mnemonic=C
|
||||
Action.CollapseCommentFolds.Desc=Recolher o dobramento de coment\u00e1rios.
|
||||
Action.ExpandAllFolds.Name=Expandir o dobramento de c\u00f3digo
|
||||
Action.ExpandAllFolds.Mnemonic=E
|
||||
Action.ExpandAllFolds.Desc=Expandir o dobramento de c\u00f3digo.
|
||||
@@ -0,0 +1,14 @@
|
||||
ContextMenu.Folding=\u0421\u0432\u043e\u0440\u0430\u0447\u0438\u0432\u0430\u043d\u0438\u0435
|
||||
|
||||
Action.ToggleCurrentFold.Name=\u041f\u0435\u0440\u0435\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u0442\u0435\u043a\u0443\u0449\u0438\u0439 \u0431\u043b\u043e\u043a
|
||||
Action.ToggleCurrentFold.Mnemonic=F
|
||||
Action.ToggleCurrentFold.Desc=\u041f\u0435\u0440\u0435\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u0431\u043b\u043e\u043a\u0430 \u043d\u0430 \u043f\u043e\u0437\u0438\u0446\u0438\u044e \u043a\u0443\u0440\u0441\u043e\u0440\u0430
|
||||
Action.CollapseAllFolds.Name=\u0421\u0432\u0435\u0440\u043d\u0443\u0442\u044c \u0432\u0441\u0435 \u0431\u043b\u043e\u043a\u0438
|
||||
Action.CollapseAllFolds.Mnemonic=O
|
||||
Action.CollapseAllFolds.Desc=\u0421\u0432\u043e\u0440\u0430\u0447\u0438\u0432\u0430\u043d\u0438\u0435 \u0432\u0441\u0435\u0445 \u0431\u043b\u043e\u043a\u043e\u0432
|
||||
Action.CollapseCommentFolds.Name=\u0421\u0432\u0435\u0440\u043d\u0443\u0442\u044c \u0432\u0441\u0435 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0438
|
||||
Action.CollapseCommentFolds.Mnemonic=C
|
||||
Action.CollapseCommentFolds.Desc=\u0421\u0432\u043e\u0440\u0430\u0447\u0438\u0432\u0430\u043d\u0438\u0435 \u0432\u0441\u0435\u0445 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0435\u0432 \u0431\u043b\u043e\u043a\u0430
|
||||
Action.ExpandAllFolds.Name=\u0420\u0430\u0437\u0432\u0435\u0440\u043d\u0443\u0442\u044c \u0432\u0441\u0435 \u0431\u043b\u043e\u043a\u0438
|
||||
Action.ExpandAllFolds.Mnemonic=E
|
||||
Action.ExpandAllFolds.Desc=\u0420\u0430\u0437\u0432\u0435\u0440\u0442\u044b\u0432\u0430\u043d\u0438\u0435 \u0432\u0441\u0435\u0445 \u0431\u043b\u043e\u043a\u043e\u0432
|
||||
@@ -0,0 +1,14 @@
|
||||
ContextMenu.Folding=Katlama
|
||||
|
||||
Action.ToggleCurrentFold.Name=\u015eu Anki Kat\u0131 Geni\u015flet/Katla
|
||||
Action.ToggleCurrentFold.Mnemonic=F
|
||||
Action.ToggleCurrentFold.Desc=\u0130\u015faret\u00e7inin bulundu\u011fu kat\u0131 geni\u015fletir/katlar.
|
||||
Action.CollapseAllFolds.Name=T\u00fcm Katlar\u0131 Katla
|
||||
Action.CollapseAllFolds.Mnemonic=O
|
||||
Action.CollapseAllFolds.Desc=T\u00fcm katlar\u0131 katlar.
|
||||
Action.CollapseCommentFolds.Name=T\u00fcm Yorumlar\u0131 Katla
|
||||
Action.CollapseCommentFolds.Mnemonic=C
|
||||
Action.CollapseCommentFolds.Desc=T\u00fcm yorumlar\u0131 katlar.
|
||||
Action.ExpandAllFolds.Name=T\u00fcm Katlar\u0131 Geni\u015flet
|
||||
Action.ExpandAllFolds.Mnemonic=E
|
||||
Action.ExpandAllFolds.Desc=T\u00fcm katlar\u0131 geni\u015fletir.
|
||||
@@ -0,0 +1,14 @@
|
||||
ContextMenu.Folding=\u6298\u53e0
|
||||
|
||||
Action.ToggleCurrentFold.Name=\u6298\u53e0/\u5f00\u542f\u672c\u533a\u6bb5
|
||||
Action.ToggleCurrentFold.Mnemonic=F
|
||||
Action.ToggleCurrentFold.Desc=\u6298\u53e0/\u5f00\u542f\u63d2\u5165\u7b26\u6240\u5728\u4f4d\u7f6e.
|
||||
Action.CollapseAllFolds.Name=\u53d6\u6d88\u6240\u6709\u4ee3\u7801\u6298\u53e0
|
||||
Action.CollapseAllFolds.Mnemonic=O
|
||||
Action.CollapseAllFolds.Desc=\u53d6\u6d88\u6240\u6709\u4ee3\u7801\u6298\u53e0.
|
||||
Action.CollapseCommentFolds.Name=\u53d6\u6d88\u6ce8\u91ca\u4ee3\u7801\u6298\u53e0
|
||||
Action.CollapseCommentFolds.Mnemonic=C
|
||||
Action.CollapseCommentFolds.Desc=\u53d6\u6d88\u6ce8\u91ca\u4ee3\u7801\u6298\u53e0.
|
||||
Action.ExpandAllFolds.Name=\u6253\u5f00\u6240\u6709\u4ee3\u7801\u6298\u53e0
|
||||
Action.ExpandAllFolds.Mnemonic=E
|
||||
Action.ExpandAllFolds.Desc=\u6253\u5f00\u6240\u6709\u4ee3\u7801\u6298\u53e0.
|
||||
@@ -0,0 +1,14 @@
|
||||
ContextMenu.Folding=Folding
|
||||
|
||||
Action.ToggleCurrentFold.Name=Toggle Current Fold
|
||||
Action.ToggleCurrentFold.Mnemonic=F
|
||||
Action.ToggleCurrentFold.Desc=Toggles the fold at the caret position.
|
||||
Action.CollapseAllFolds.Name=Collapse All Folds
|
||||
Action.CollapseAllFolds.Mnemonic=O
|
||||
Action.CollapseAllFolds.Desc=Collapses all folds.
|
||||
Action.CollapseCommentFolds.Name=Collapse All Comments
|
||||
Action.CollapseCommentFolds.Mnemonic=C
|
||||
Action.CollapseCommentFolds.Desc=Collapses all comment folds.
|
||||
Action.ExpandAllFolds.Name=Expand All Folds
|
||||
Action.ExpandAllFolds.Mnemonic=E
|
||||
Action.ExpandAllFolds.Desc=Expands all folds.
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,237 @@
|
||||
/*
|
||||
* 07/28/2008
|
||||
*
|
||||
* RtfToText.java - Returns the plain text version of RTF documents.
|
||||
*
|
||||
* 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.io.BufferedReader;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.io.StringReader;
|
||||
|
||||
|
||||
/**
|
||||
* Gets the plain text version of RTF documents.<p>
|
||||
*
|
||||
* This is used by <code>RtfTransferable</code> to return the plain text
|
||||
* version of the transferable when the receiver does not support RTF.
|
||||
*
|
||||
* @author Robert Futrell
|
||||
* @version 1.0
|
||||
*/
|
||||
final class RtfToText {
|
||||
|
||||
private Reader r;
|
||||
private StringBuilder sb;
|
||||
private StringBuilder controlWord;
|
||||
private int blockCount;
|
||||
private boolean inControlWord;
|
||||
|
||||
|
||||
/**
|
||||
* Private constructor.
|
||||
*
|
||||
* @param r The reader to read RTF text from.
|
||||
*/
|
||||
private RtfToText(Reader r) {
|
||||
this.r = r;
|
||||
sb = new StringBuilder();
|
||||
controlWord = new StringBuilder();
|
||||
blockCount = 0;
|
||||
inControlWord = false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Converts the RTF text read from this converter's <code>Reader</code>
|
||||
* into plain text. It is the caller's responsibility to close the
|
||||
* reader after this method is called.
|
||||
*
|
||||
* @return The plain text.
|
||||
* @throws IOException If an IO error occurs.
|
||||
*/
|
||||
private String convert() throws IOException {
|
||||
|
||||
// Skip over first curly brace as the whole file is in '{' and '}'
|
||||
int i = r.read();
|
||||
if (i!='{') {
|
||||
throw new IOException("Invalid RTF file");
|
||||
}
|
||||
|
||||
while ((i=r.read())!=-1) {
|
||||
|
||||
char ch = (char)i;
|
||||
switch (ch) {
|
||||
case '{':
|
||||
if (inControlWord && controlWord.length()==0) { // "\{"
|
||||
sb.append('{');
|
||||
controlWord.setLength(0);
|
||||
inControlWord = false;
|
||||
}
|
||||
else {
|
||||
blockCount++;
|
||||
}
|
||||
break;
|
||||
case '}':
|
||||
if (inControlWord && controlWord.length()==0) { // "\}"
|
||||
sb.append('}');
|
||||
controlWord.setLength(0);
|
||||
inControlWord = false;
|
||||
}
|
||||
else {
|
||||
blockCount--;
|
||||
}
|
||||
break;
|
||||
case '\\':
|
||||
if (blockCount==0) {
|
||||
if (inControlWord) {
|
||||
if (controlWord.length()==0) { // "\\"
|
||||
sb.append('\\');
|
||||
controlWord.setLength(0);
|
||||
inControlWord = false;
|
||||
}
|
||||
else {
|
||||
endControlWord();
|
||||
inControlWord = true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
inControlWord = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ' ':
|
||||
if (blockCount==0) {
|
||||
if (inControlWord) {
|
||||
endControlWord();
|
||||
}
|
||||
else {
|
||||
sb.append(' ');
|
||||
}
|
||||
}
|
||||
break;
|
||||
case '\r':
|
||||
case '\n':
|
||||
if (blockCount==0) {
|
||||
if (inControlWord) {
|
||||
endControlWord();
|
||||
}
|
||||
// Otherwise, ignore
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (blockCount==0) {
|
||||
if (inControlWord) {
|
||||
controlWord.append(ch);
|
||||
}
|
||||
else {
|
||||
sb.append(ch);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Ends a control word. Checks whether it is a common one that affects
|
||||
* the plain text output (such as "<code>par</code>" or "<code>tab</code>")
|
||||
* and updates the text buffer accordingly.
|
||||
*/
|
||||
private void endControlWord() {
|
||||
String word = controlWord.toString();
|
||||
if ("par".equals(word)) {
|
||||
sb.append('\n');
|
||||
}
|
||||
else if ("tab".equals(word)) {
|
||||
sb.append('\t');
|
||||
}
|
||||
controlWord.setLength(0);
|
||||
inControlWord = false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Converts the contents of the specified byte array representing
|
||||
* an RTF document into plain text.
|
||||
*
|
||||
* @param rtf The byte array representing an RTF document.
|
||||
* @return The contents of the RTF document, in plain text.
|
||||
* @throws IOException If an IO error occurs.
|
||||
*/
|
||||
public static String getPlainText(byte[] rtf) throws IOException {
|
||||
return getPlainText(new ByteArrayInputStream(rtf));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Converts the contents of the specified RTF file to plain text.
|
||||
*
|
||||
* @param file The RTF file to convert.
|
||||
* @return The contents of the file, in plain text.
|
||||
* @throws IOException If an IO error occurs.
|
||||
*/
|
||||
public static String getPlainText(File file) throws IOException {
|
||||
return getPlainText(new BufferedReader(new FileReader(file)));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Converts the contents of the specified input stream to plain text.
|
||||
* The input stream will be closed when this method returns.
|
||||
*
|
||||
* @param in The input stream to convert. This will be closed when this
|
||||
* method returns.
|
||||
* @return The contents of the stream, in plain text.
|
||||
* @throws IOException If an IO error occurs.
|
||||
*/
|
||||
public static String getPlainText(InputStream in) throws IOException {
|
||||
return getPlainText(new InputStreamReader(in, "US-ASCII"));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Converts the contents of the specified <code>Reader</code> to plain text.
|
||||
*
|
||||
* @param r The <code>Reader</code>. This will be closed when this method
|
||||
* returns.
|
||||
* @return The contents of the <code>Reader</code>, in plain text.
|
||||
* @throws IOException If an IO error occurs.
|
||||
*/
|
||||
private static String getPlainText(Reader r) throws IOException {
|
||||
try {
|
||||
RtfToText converter = new RtfToText(r);
|
||||
return converter.convert();
|
||||
} finally {
|
||||
r.close();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Converts the contents of the specified String to plain text.
|
||||
*
|
||||
* @param rtf A string whose contents represent an RTF document.
|
||||
* @return The contents of the String, in plain text.
|
||||
* @throws IOException If an IO error occurs.
|
||||
*/
|
||||
public static String getPlainText(String rtf) throws IOException {
|
||||
return getPlainText(new StringReader(rtf));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,483 @@
|
||||
/*
|
||||
* 07/28/2008
|
||||
*
|
||||
* RtfGenerator.java - Generates RTF via a simple Java API.
|
||||
*
|
||||
* 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.Font;
|
||||
import java.awt.Toolkit;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.fife.ui.rtextarea.RTextArea;
|
||||
|
||||
|
||||
/**
|
||||
* Generates RTF text via a simple Java API.<p>
|
||||
*
|
||||
* The following RTF features are supported:
|
||||
* <ul>
|
||||
* <li>Fonts
|
||||
* <li>Font sizes
|
||||
* <li>Foreground and background colors
|
||||
* <li>Bold, italic, and underline
|
||||
* </ul>
|
||||
*
|
||||
* The RTF generated isn't really "optimized," but it will do, especially for
|
||||
* small amounts of text, such as what's common when copy-and-pasting. It
|
||||
* tries to be sufficient for the use case of copying syntax highlighted
|
||||
* code:
|
||||
* <ul>
|
||||
* <li>It assumes that tokens changing foreground color often is fairly
|
||||
* common.
|
||||
* <li>It assumes that background highlighting is fairly uncommon.
|
||||
* </ul>
|
||||
*
|
||||
* @author Robert Futrell
|
||||
* @version 1.0
|
||||
*/
|
||||
public class RtfGenerator {
|
||||
|
||||
private List<Font> fontList;
|
||||
private List<Color> colorList;
|
||||
private StringBuilder document;
|
||||
private boolean lastWasControlWord;
|
||||
private int lastFontIndex;
|
||||
private int lastFGIndex;
|
||||
private boolean lastBold;
|
||||
private boolean lastItalic;
|
||||
private int lastFontSize;
|
||||
|
||||
/**
|
||||
* Java2D assumes a 72 dpi screen resolution, but on Windows the screen
|
||||
* resolution is either 96 dpi or 120 dpi, depending on your font display
|
||||
* settings. This is an attempt to make the RTF generated match the
|
||||
* size of what's displayed in the RSyntaxTextArea.
|
||||
*/
|
||||
private int screenRes;
|
||||
|
||||
/**
|
||||
* The default font size for RTF. This is point size, in half
|
||||
* points.
|
||||
*/
|
||||
private static final int DEFAULT_FONT_SIZE = 12;//24;
|
||||
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public RtfGenerator() {
|
||||
fontList = new ArrayList<Font>(1); // Usually only 1.
|
||||
colorList = new ArrayList<Color>(1); // Usually only 1.
|
||||
document = new StringBuilder();
|
||||
reset();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds a newline to the RTF document.
|
||||
*
|
||||
* @see #appendToDoc(String, Font, Color, Color)
|
||||
*/
|
||||
public void appendNewline() {
|
||||
document.append("\\par");
|
||||
document.append('\n'); // Just for ease of reading RTF.
|
||||
lastWasControlWord = false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Appends styled text to the RTF document being generated.
|
||||
*
|
||||
* @param text The text to append.
|
||||
* @param f The font of the text. If this is <code>null</code>, the
|
||||
* default font is used.
|
||||
* @param fg The foreground of the text. If this is <code>null</code>,
|
||||
* the default foreground color is used.
|
||||
* @param bg The background color of the text. If this is
|
||||
* <code>null</code>, the default background color is used.
|
||||
* @see #appendNewline()
|
||||
*/
|
||||
public void appendToDoc(String text, Font f, Color fg, Color bg) {
|
||||
appendToDoc(text, f, fg, bg, false);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Appends styled text to the RTF document being generated.
|
||||
*
|
||||
* @param text The text to append.
|
||||
* @param f The font of the text. If this is <code>null</code>, the
|
||||
* default font is used.
|
||||
* @param bg The background color of the text. If this is
|
||||
* <code>null</code>, the default background color is used.
|
||||
* @param underline Whether the text should be underlined.
|
||||
* @see #appendNewline()
|
||||
*/
|
||||
public void appendToDocNoFG(String text, Font f, Color bg,
|
||||
boolean underline) {
|
||||
appendToDoc(text, f, null, bg, underline, false);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Appends styled text to the RTF document being generated.
|
||||
*
|
||||
* @param text The text to append.
|
||||
* @param f The font of the text. If this is <code>null</code>, the
|
||||
* default font is used.
|
||||
* @param fg The foreground of the text. If this is <code>null</code>,
|
||||
* the default foreground color is used.
|
||||
* @param bg The background color of the text. If this is
|
||||
* <code>null</code>, the default background color is used.
|
||||
* @param underline Whether the text should be underlined.
|
||||
* @see #appendNewline()
|
||||
*/
|
||||
public void appendToDoc(String text, Font f, Color fg, Color bg,
|
||||
boolean underline) {
|
||||
appendToDoc(text, f, fg, bg, underline, true);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Appends styled text to the RTF document being generated.
|
||||
*
|
||||
* @param text The text to append.
|
||||
* @param f The font of the text. If this is <code>null</code>, the
|
||||
* default font is used.
|
||||
* @param fg The foreground of the text. If this is <code>null</code>,
|
||||
* the default foreground color is used.
|
||||
* @param bg The background color of the text. If this is
|
||||
* <code>null</code>, the default background color is used.
|
||||
* @param underline Whether the text should be underlined.
|
||||
* @param setFG Whether the foreground specified by <code>fg</code> should
|
||||
* be honored (if it is non-<code>null</code>).
|
||||
* @see #appendNewline()
|
||||
*/
|
||||
public void appendToDoc(String text, Font f, Color fg, Color bg,
|
||||
boolean underline, boolean setFG) {
|
||||
|
||||
if (text!=null) {
|
||||
|
||||
// Set font to use, if different from last addition.
|
||||
int fontIndex = f==null ? 0 : (getFontIndex(fontList, f)+1);
|
||||
if (fontIndex!=lastFontIndex) {
|
||||
document.append("\\f").append(fontIndex);
|
||||
lastFontIndex = fontIndex;
|
||||
lastWasControlWord = true;
|
||||
}
|
||||
|
||||
// Set styles to use.
|
||||
if (f!=null) {
|
||||
int fontSize = fixFontSize(f.getSize2D()*2); // Half points!
|
||||
if (fontSize!=lastFontSize) {
|
||||
document.append("\\fs").append(fontSize);
|
||||
lastFontSize = fontSize;
|
||||
lastWasControlWord = true;
|
||||
}
|
||||
if (f.isBold()!=lastBold) {
|
||||
document.append(lastBold ? "\\b0" : "\\b");
|
||||
lastBold = !lastBold;
|
||||
lastWasControlWord = true;
|
||||
}
|
||||
if (f.isItalic()!=lastItalic) {
|
||||
document.append(lastItalic ? "\\i0" : "\\i");
|
||||
lastItalic = !lastItalic;
|
||||
lastWasControlWord = true;
|
||||
}
|
||||
}
|
||||
else { // No font specified - assume neither bold nor italic.
|
||||
if (lastFontSize!=DEFAULT_FONT_SIZE) {
|
||||
document.append("\\fs").append(DEFAULT_FONT_SIZE);
|
||||
lastFontSize = DEFAULT_FONT_SIZE;
|
||||
lastWasControlWord = true;
|
||||
}
|
||||
if (lastBold) {
|
||||
document.append("\\b0");
|
||||
lastBold = false;
|
||||
lastWasControlWord = true;
|
||||
}
|
||||
if (lastItalic) {
|
||||
document.append("\\i0");
|
||||
lastItalic = false;
|
||||
lastWasControlWord = true;
|
||||
}
|
||||
}
|
||||
if (underline) {
|
||||
document.append("\\ul");
|
||||
lastWasControlWord = true;
|
||||
}
|
||||
|
||||
// Set the foreground color.
|
||||
if (setFG) {
|
||||
int fgIndex = 0;
|
||||
if (fg!=null) { // null => fg color index 0
|
||||
fgIndex = getColorIndex(colorList, fg)+1;
|
||||
}
|
||||
if (fgIndex!=lastFGIndex) {
|
||||
document.append("\\cf").append(fgIndex);
|
||||
lastFGIndex = fgIndex;
|
||||
lastWasControlWord = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Set the background color.
|
||||
if (bg!=null) {
|
||||
int pos = getColorIndex(colorList, bg);
|
||||
document.append("\\highlight").append(pos+1);
|
||||
lastWasControlWord = true;
|
||||
}
|
||||
|
||||
if (lastWasControlWord) {
|
||||
document.append(' '); // Delimiter
|
||||
lastWasControlWord = false;
|
||||
}
|
||||
escapeAndAdd(document, text);
|
||||
|
||||
// Reset everything that was set for this text fragment.
|
||||
if (bg!=null) {
|
||||
document.append("\\highlight0");
|
||||
lastWasControlWord = true;
|
||||
}
|
||||
if (underline) {
|
||||
document.append("\\ul0");
|
||||
lastWasControlWord = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Appends some text to a buffer, with special care taken for special
|
||||
* characters as defined by the RTF spec.
|
||||
*
|
||||
* <ul>
|
||||
* <li>All tab characters are replaced with the string
|
||||
* "<code>\tab</code>"
|
||||
* <li>'\', '{' and '}' are changed to "\\", "\{" and "\}"
|
||||
* </ul>
|
||||
*
|
||||
* @param text The text to append (with tab chars substituted).
|
||||
* @param sb The buffer to append to.
|
||||
*/
|
||||
private void escapeAndAdd(StringBuilder sb, String text) {
|
||||
int count = text.length();
|
||||
for (int i=0; i<count; i++) {
|
||||
char ch = text.charAt(i);
|
||||
switch (ch) {
|
||||
case '\t':
|
||||
// Micro-optimization: for syntax highlighting with
|
||||
// tab indentation, there are often multiple tabs
|
||||
// back-to-back at the start of lines, so don't put
|
||||
// spaces between each "\tab".
|
||||
sb.append("\\tab");
|
||||
while ((++i<count) && text.charAt(i)=='\t') {
|
||||
sb.append("\\tab");
|
||||
}
|
||||
sb.append(' ');
|
||||
i--; // We read one too far.
|
||||
break;
|
||||
case '\\':
|
||||
case '{':
|
||||
case '}':
|
||||
sb.append('\\').append(ch);
|
||||
break;
|
||||
default:
|
||||
if (ch <= 127) {
|
||||
sb.append(ch);
|
||||
} else {
|
||||
sb.append("\\u").append((int)ch);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a font point size, adjusted for the current screen resolution.<p>
|
||||
*
|
||||
* Java2D assumes 72 dpi. On systems with larger dpi (Windows, GTK, etc.),
|
||||
* font rendering will appear too small if we simply return a Java "Font"
|
||||
* object's getSize() value. We need to adjust it for the screen
|
||||
* resolution.
|
||||
*
|
||||
* @param pointSize A Java Font's point size, as returned from
|
||||
* <code>getSize2D()</code>.
|
||||
* @return The font point size, adjusted for the current screen resolution.
|
||||
* This will allow other applications to render fonts the same
|
||||
* size as they appear in the Java application.
|
||||
*/
|
||||
private int fixFontSize(float pointSize) {
|
||||
if (screenRes!=72) { // Java2D assumes 72 dpi
|
||||
pointSize = Math.round(pointSize*72f/screenRes);
|
||||
}
|
||||
return (int)pointSize;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the index of the specified item in a list. If the item
|
||||
* is not in the list, it is added, and its new index is returned.
|
||||
*
|
||||
* @param list The list (possibly) containing the item.
|
||||
* @param item The item to get the index of.
|
||||
* @return The index of the item.
|
||||
*/
|
||||
private static int getColorIndex(List<Color> list, Color item) {
|
||||
int pos = list.indexOf(item);
|
||||
if (pos==-1) {
|
||||
list.add(item);
|
||||
pos = list.size()-1;
|
||||
}
|
||||
return pos;
|
||||
}
|
||||
|
||||
|
||||
private String getColorTableRtf() {
|
||||
|
||||
// Example:
|
||||
// "{\\colortbl ;\\red255\\green0\\blue0;\\red0\\green0\\blue255; }"
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append("{\\colortbl ;");
|
||||
for (Color c : colorList) {
|
||||
sb.append("\\red").append(c.getRed());
|
||||
sb.append("\\green").append(c.getGreen());
|
||||
sb.append("\\blue").append(c.getBlue());
|
||||
sb.append(';');
|
||||
}
|
||||
sb.append("}");
|
||||
|
||||
return sb.toString();
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the index of the specified font in a list of fonts. This
|
||||
* method only checks for a font by its family name; its attributes such
|
||||
* as bold and italic are ignored.<p>
|
||||
*
|
||||
* If the font is not in the list, it is added, and its new index is
|
||||
* returned.
|
||||
*
|
||||
* @param list The list (possibly) containing the font.
|
||||
* @param font The font to get the index of.
|
||||
* @return The index of the font.
|
||||
*/
|
||||
private static int getFontIndex(List<Font> list, Font font) {
|
||||
String fontName = font.getFamily();
|
||||
for (int i=0; i<list.size(); i++) {
|
||||
Font font2 = list.get(i);
|
||||
if (font2.getFamily().equals(fontName)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
list.add(font);
|
||||
return list.size()-1;
|
||||
}
|
||||
|
||||
|
||||
private String getFontTableRtf() {
|
||||
|
||||
// Example:
|
||||
// "{\\fonttbl{\\f0\\fmodern\\fcharset0 Courier;}}"
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
// Workaround for text areas using the Java logical font "Monospaced"
|
||||
// by default. There's no way to know what it's mapped to, so we
|
||||
// just search for a monospaced font on the system.
|
||||
String monoFamilyName = getMonospacedFontFamily();
|
||||
|
||||
sb.append("{\\fonttbl{\\f0\\fnil\\fcharset0 " + monoFamilyName + ";}");
|
||||
for (int i=0; i<fontList.size(); i++) {
|
||||
Font f = fontList.get(i);
|
||||
String familyName = f.getFamily();
|
||||
if (familyName.equals("Monospaced")) {
|
||||
familyName = monoFamilyName;
|
||||
}
|
||||
sb.append("{\\f").append(i+1).append("\\fnil\\fcharset0 ");
|
||||
sb.append(familyName).append(";}");
|
||||
}
|
||||
sb.append('}');
|
||||
|
||||
return sb.toString();
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a good "default" monospaced font to use when Java's logical
|
||||
* font "Monospaced" is found.
|
||||
*
|
||||
* @return The monospaced font family to use.
|
||||
*/
|
||||
private static String getMonospacedFontFamily() {
|
||||
String family = RTextArea.getDefaultFont().getFamily();
|
||||
if ("Monospaced".equals(family)) {
|
||||
family = "Courier";
|
||||
}
|
||||
return family;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the RTF document created by this generator.
|
||||
*
|
||||
* @return The RTF document, as a <code>String</code>.
|
||||
*/
|
||||
public String getRtf() {
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("{");
|
||||
|
||||
// Header
|
||||
sb.append("\\rtf1\\ansi\\ansicpg1252");
|
||||
sb.append("\\deff0"); // First font in font table is the default
|
||||
sb.append("\\deflang1033");
|
||||
sb.append("\\viewkind4"); // "Normal" view
|
||||
sb.append("\\uc\\pard\\f0");
|
||||
sb.append("\\fs20"); // Font size in half-points (default 24)
|
||||
sb.append(getFontTableRtf()).append('\n');
|
||||
sb.append(getColorTableRtf()).append('\n');
|
||||
|
||||
// Content
|
||||
sb.append(document);
|
||||
|
||||
sb.append("}");
|
||||
|
||||
//System.err.println("*** " + sb.length());
|
||||
return sb.toString();
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Resets this generator. All document information and content is
|
||||
* cleared.
|
||||
*/
|
||||
public void reset() {
|
||||
fontList.clear();
|
||||
colorList.clear();
|
||||
document.setLength(0);
|
||||
lastWasControlWord = false;
|
||||
lastFontIndex = 0;
|
||||
lastFGIndex = 0;
|
||||
lastBold = false;
|
||||
lastItalic = false;
|
||||
lastFontSize = DEFAULT_FONT_SIZE;
|
||||
screenRes = Toolkit.getDefaultToolkit().getScreenResolution();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* 07/28/2008
|
||||
*
|
||||
* RtfTransferable.java - Used during drag-and-drop to represent RTF text.
|
||||
*
|
||||
* 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.datatransfer.DataFlavor;
|
||||
import java.awt.datatransfer.Transferable;
|
||||
import java.awt.datatransfer.UnsupportedFlavorException;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
|
||||
|
||||
/**
|
||||
* Object used during copy/paste and DnD operations to represent RTF text.
|
||||
* It can return the text being moved as either RTF or plain text. This
|
||||
* class is basically the same as
|
||||
* <code>java.awt.datatransfer.StringSelection</code>, except that it can also
|
||||
* return the text as RTF.
|
||||
*
|
||||
* @author Robert Futrell
|
||||
* @version 1.0
|
||||
*/
|
||||
class RtfTransferable implements Transferable {
|
||||
|
||||
/**
|
||||
* The RTF data, in bytes (the RTF is 7-bit ascii).
|
||||
*/
|
||||
private byte[] data;
|
||||
|
||||
|
||||
/**
|
||||
* The "flavors" the text can be returned as.
|
||||
*/
|
||||
private static final DataFlavor[] FLAVORS = {
|
||||
new DataFlavor("text/rtf", "RTF"),
|
||||
DataFlavor.stringFlavor,
|
||||
DataFlavor.plainTextFlavor // deprecated
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param data The RTF data.
|
||||
*/
|
||||
RtfTransferable(byte[] data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Object getTransferData(DataFlavor flavor)
|
||||
throws UnsupportedFlavorException, IOException {
|
||||
if (flavor.equals(FLAVORS[0])) { // RTF
|
||||
return new ByteArrayInputStream(data==null ? new byte[0] : data);
|
||||
}
|
||||
else if (flavor.equals(FLAVORS[1])) { // stringFlavor
|
||||
return data==null ? "" : RtfToText.getPlainText(data);
|
||||
}
|
||||
else if (flavor.equals(FLAVORS[2])) { // plainTextFlavor (deprecated)
|
||||
String text = ""; // Valid if data==null
|
||||
if (data!=null) {
|
||||
text = RtfToText.getPlainText(data);
|
||||
}
|
||||
return new StringReader(text);
|
||||
}
|
||||
else {
|
||||
throw new UnsupportedFlavorException(flavor);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public DataFlavor[] getTransferDataFlavors() {
|
||||
return FLAVORS.clone();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isDataFlavorSupported(DataFlavor flavor) {
|
||||
for (int i=0; i<FLAVORS.length; i++) {
|
||||
if (flavor.equals(FLAVORS[i])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* 02/16/2012
|
||||
*
|
||||
* Copyright (C) 2013 Robert Futrell
|
||||
* robert_futrell at users.sourceforge.net
|
||||
* http://fifesoft.com/rsyntaxtextarea
|
||||
*
|
||||
* This library is distributed under a modified BSD license. See the included
|
||||
* RSyntaxTextArea.License.txt file for details.
|
||||
*/
|
||||
package org.fife.ui.rsyntaxtextarea;
|
||||
|
||||
import javax.swing.event.HyperlinkEvent;
|
||||
|
||||
|
||||
/**
|
||||
* A link generator result that selects a region of text in the text area.
|
||||
* This will typically be used by IDE-style applications, to provide support
|
||||
* for "linking" the use of a variable in a document to its declaration.
|
||||
*
|
||||
* @author Robert Futrell
|
||||
* @version 1.0
|
||||
* @see LinkGenerator
|
||||
*/
|
||||
public class SelectRegionLinkGeneratorResult implements LinkGeneratorResult {
|
||||
|
||||
private RSyntaxTextArea textArea;
|
||||
private int sourceOffset;
|
||||
private int selStart;
|
||||
private int selEnd;
|
||||
|
||||
|
||||
public SelectRegionLinkGeneratorResult(RSyntaxTextArea textArea,
|
||||
int sourceOffset, int selStart, int selEnd) {
|
||||
this.textArea = textArea;
|
||||
this.sourceOffset = sourceOffset;
|
||||
this.selStart = selStart;
|
||||
this.selEnd = selEnd;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Selects the text in the text area.
|
||||
*/
|
||||
@Override
|
||||
public HyperlinkEvent execute() {
|
||||
textArea.select(selStart, selEnd);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public int getSourceOffset() {
|
||||
return sourceOffset;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
* 09/13/2005
|
||||
*
|
||||
* SquiggleUnderlineHighlightPainter.java - Highlighter that draws a squiggle
|
||||
* underline under "highlighted" text, similar to error markers in Microsoft
|
||||
* Visual Studio or Eclipse.
|
||||
*
|
||||
* 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.Graphics;
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.Shape;
|
||||
import javax.swing.text.BadLocationException;
|
||||
import javax.swing.text.JTextComponent;
|
||||
import javax.swing.text.Position;
|
||||
import javax.swing.text.View;
|
||||
|
||||
import org.fife.ui.rtextarea.ChangeableHighlightPainter;
|
||||
|
||||
|
||||
/**
|
||||
* Highlight painter that paints a squiggly underline underneath text, similar
|
||||
* to what popular IDE's such as Visual Studio and Eclipse do to indicate
|
||||
* errors, warnings, etc.<p>
|
||||
*
|
||||
* This class must be used as a <code>LayerPainter</code>.
|
||||
*
|
||||
* @author Robert Futrell
|
||||
* @version 1.0
|
||||
*/
|
||||
public class SquiggleUnderlineHighlightPainter
|
||||
extends ChangeableHighlightPainter {
|
||||
|
||||
private static final int AMT = 2;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param color The color of the squiggle. This cannot be
|
||||
* <code>null</code>.
|
||||
*/
|
||||
public SquiggleUnderlineHighlightPainter(Color color) {
|
||||
super(color);
|
||||
setPaint(color);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Paints a portion of a highlight.
|
||||
*
|
||||
* @param g the graphics context
|
||||
* @param offs0 the starting model offset >= 0
|
||||
* @param offs1 the ending model offset >= offs1
|
||||
* @param bounds the bounding box of the view, which is not
|
||||
* necessarily the region to paint.
|
||||
* @param c the editor
|
||||
* @param view View painting for
|
||||
* @return region drawing occurred in
|
||||
*/
|
||||
@Override
|
||||
public Shape paintLayer(Graphics g, int offs0, int offs1,
|
||||
Shape bounds, JTextComponent c, View view) {
|
||||
|
||||
g.setColor((Color)getPaint());
|
||||
|
||||
if (offs0 == view.getStartOffset() && offs1 == view.getEndOffset()) {
|
||||
// Contained in view, can just use bounds.
|
||||
Rectangle alloc;
|
||||
if (bounds instanceof Rectangle) {
|
||||
alloc = (Rectangle)bounds;
|
||||
}
|
||||
else {
|
||||
alloc = bounds.getBounds();
|
||||
}
|
||||
paintSquiggle(g, alloc);
|
||||
return alloc;
|
||||
}
|
||||
|
||||
// Otherwise, should only render part of View.
|
||||
try {
|
||||
// --- determine locations ---
|
||||
Shape shape = view.modelToView(offs0, Position.Bias.Forward,
|
||||
offs1,Position.Bias.Backward,
|
||||
bounds);
|
||||
Rectangle r = (shape instanceof Rectangle) ?
|
||||
(Rectangle)shape : shape.getBounds();
|
||||
paintSquiggle(g, r);
|
||||
return r;
|
||||
} catch (BadLocationException e) {
|
||||
e.printStackTrace(); // can't render
|
||||
}
|
||||
|
||||
// Only if exception
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Paints a squiggle underneath text in the specified rectangle.
|
||||
*
|
||||
* @param g The graphics context with which to paint.
|
||||
* @param r The rectangle containing the text.
|
||||
*/
|
||||
protected void paintSquiggle(Graphics g, Rectangle r) {
|
||||
int x = r.x;
|
||||
int y = r.y + r.height - AMT;
|
||||
int delta = -AMT;
|
||||
while (x<r.x+r.width) {
|
||||
g.drawLine(x,y, x+AMT,y+delta);
|
||||
y += delta;
|
||||
delta = -delta;
|
||||
x += AMT;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
/*
|
||||
* 08/06/2004
|
||||
*
|
||||
* Style.java - A set of traits for a particular token type to use while
|
||||
* painting.
|
||||
*
|
||||
* 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.Font;
|
||||
import java.awt.FontMetrics;
|
||||
|
||||
import javax.swing.JPanel;
|
||||
|
||||
|
||||
/**
|
||||
* The color and style information for a token type. Each token type in an
|
||||
* <code>RSyntaxTextArea</code> has a corresponding <code>Style</code>; this
|
||||
* <code>Style</code> tells us the following things:
|
||||
*
|
||||
* <ul>
|
||||
* <li>What foreground color to use for tokens of this type.</li>
|
||||
* <li>What background color to use.</li>
|
||||
* <li>The font to use.</li>
|
||||
* <li>Whether the token should be underlined.</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author Robert Futrell
|
||||
* @version 0.6
|
||||
*/
|
||||
@SuppressWarnings({ "checkstyle:visibilitymodifier" })
|
||||
public class Style implements Cloneable {
|
||||
|
||||
public static final Color DEFAULT_FOREGROUND = Color.BLACK;
|
||||
public static final Color DEFAULT_BACKGROUND = null;
|
||||
public static final Font DEFAULT_FONT = null;
|
||||
|
||||
public Color foreground;
|
||||
public Color background;
|
||||
public boolean underline;
|
||||
public Font font;
|
||||
|
||||
public FontMetrics fontMetrics;
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new style defaulting to black foreground, no
|
||||
* background, and no styling.
|
||||
*/
|
||||
public Style() {
|
||||
this(DEFAULT_FOREGROUND);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new style with the specified foreground and no styling.
|
||||
*
|
||||
* @param fg The foreground color to use.
|
||||
*/
|
||||
public Style(Color fg) {
|
||||
this(fg, DEFAULT_BACKGROUND);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new style with the specified colors and no styling.
|
||||
*
|
||||
* @param fg The foreground color to use.
|
||||
* @param bg The background color to use.
|
||||
*/
|
||||
public Style(Color fg, Color bg) {
|
||||
this(fg, bg, DEFAULT_FONT);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new style.
|
||||
*
|
||||
* @param fg The foreground color to use.
|
||||
* @param bg The background color to use.
|
||||
* @param font The font for this syntax scheme.
|
||||
*/
|
||||
public Style(Color fg, Color bg, Font font) {
|
||||
this(fg, bg, font, false);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new style.
|
||||
*
|
||||
* @param fg The foreground color to use.
|
||||
* @param bg The background color to use.
|
||||
* @param font The font for this syntax scheme.
|
||||
* @param underline Whether or not to underline tokens with this style.
|
||||
*/
|
||||
public Style(Color fg, Color bg, Font font, boolean underline) {
|
||||
foreground = fg;
|
||||
background = bg;
|
||||
this.font = font;
|
||||
this.underline = underline;
|
||||
this.fontMetrics = font==null ? null :
|
||||
new JPanel().getFontMetrics(font); // Default, no rendering hints!
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether or not two (possibly <code>null</code>) objects are
|
||||
* equal.
|
||||
*/
|
||||
private boolean areEqual(Object o1, Object o2) {
|
||||
return (o1==null && o2==null) || (o1!=null && o1.equals(o2));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a deep copy of this object.
|
||||
*
|
||||
* @return The copy.
|
||||
*/
|
||||
@Override
|
||||
public Object clone() {
|
||||
Style clone = null;
|
||||
try {
|
||||
clone = (Style)super.clone();
|
||||
} catch (CloneNotSupportedException cnse) { // Never happens
|
||||
cnse.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
clone.foreground = foreground;
|
||||
clone.background = background;
|
||||
clone.font = font;
|
||||
clone.underline = underline;
|
||||
clone.fontMetrics = fontMetrics;
|
||||
return clone;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether or not two syntax schemes are equal.
|
||||
*
|
||||
* @param o2 The object with which to compare this syntax scheme.
|
||||
* @return Whether or not these two syntax schemes represent the same
|
||||
* scheme.
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object o2) {
|
||||
if (o2 instanceof Style) {
|
||||
Style ss2 = (Style)o2;
|
||||
if (this.underline==ss2.underline &&
|
||||
areEqual(foreground, ss2.foreground) &&
|
||||
areEqual(background, ss2.background) &&
|
||||
areEqual(font, ss2.font) &&
|
||||
areEqual(fontMetrics, ss2.fontMetrics)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Computes the hash code to use when adding this syntax scheme to
|
||||
* hash tables.<p>
|
||||
*
|
||||
* This method is implemented, since {@link #equals(Object)} is implemented,
|
||||
* to keep FindBugs happy.
|
||||
*
|
||||
* @return The hash code.
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hashCode = underline ? 1 : 0;
|
||||
if (foreground!=null) {
|
||||
hashCode ^= foreground.hashCode();
|
||||
}
|
||||
if (background!=null) {
|
||||
hashCode ^= background.hashCode();
|
||||
}
|
||||
return hashCode;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a string representation of this style.
|
||||
*
|
||||
* @return A string representation of this style.
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return "[Style: foreground: " + foreground +
|
||||
", background: " + background + ", underline: " +
|
||||
underline + ", font: " + font + "]";
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,320 @@
|
||||
/*
|
||||
* 03/08/2004
|
||||
*
|
||||
* SyntaxConstants.java - Constants used by RSyntaxTextArea and friends.
|
||||
*
|
||||
* This library is distributed under a modified BSD license. See the included
|
||||
* RSyntaxTextArea.License.txt file for details.
|
||||
*/
|
||||
package org.fife.ui.rsyntaxtextarea;
|
||||
|
||||
|
||||
/**
|
||||
* Constants that define the different programming languages understood by
|
||||
* <code>RSyntaxTextArea</code>. These constants are the values you can pass
|
||||
* to {@link RSyntaxTextArea#setSyntaxEditingStyle(String)} to get syntax
|
||||
* highlighting.<p>
|
||||
*
|
||||
* By default, all <code>RSyntaxTextArea</code>s can render all of these
|
||||
* languages, but this can be changed (the list can be augmented or completely
|
||||
* overwritten) on a per-text area basis. What languages can be rendered is
|
||||
* actually managed by the {@link TokenMakerFactory} installed on the text
|
||||
* area's {@link RSyntaxDocument}. By default, all
|
||||
* <code>RSyntaxDocumenet</code>s have a factory installed capable of handling
|
||||
* all of these languages.
|
||||
*
|
||||
* @author Robert Futrell
|
||||
* @version 1.0
|
||||
*/
|
||||
public interface SyntaxConstants {
|
||||
|
||||
/**
|
||||
* Style meaning don't syntax highlight anything.
|
||||
*/
|
||||
String SYNTAX_STYLE_NONE = "text/plain";
|
||||
|
||||
|
||||
/**
|
||||
* Style for highlighting ActionScript.
|
||||
*/
|
||||
String SYNTAX_STYLE_ACTIONSCRIPT = "text/actionscript";
|
||||
|
||||
|
||||
/**
|
||||
* Style for highlighting x86 assembler.
|
||||
*/
|
||||
String SYNTAX_STYLE_ASSEMBLER_X86 = "text/asm";
|
||||
|
||||
|
||||
/**
|
||||
* Style for highlighting BBCode.
|
||||
*/
|
||||
String SYNTAX_STYLE_BBCODE = "text/bbcode";
|
||||
|
||||
|
||||
/**
|
||||
* Style for highlighting C.
|
||||
*/
|
||||
String SYNTAX_STYLE_C = "text/c";
|
||||
|
||||
|
||||
/**
|
||||
* Style for highlighting Clojure.
|
||||
*/
|
||||
String SYNTAX_STYLE_CLOJURE = "text/clojure";
|
||||
|
||||
|
||||
/**
|
||||
* Style for highlighting C++.
|
||||
*/
|
||||
String SYNTAX_STYLE_CPLUSPLUS = "text/cpp";
|
||||
|
||||
|
||||
/**
|
||||
* Style for highlighting C#.
|
||||
*/
|
||||
String SYNTAX_STYLE_CSHARP = "text/cs";
|
||||
|
||||
|
||||
/**
|
||||
* Style for highlighting CSS.
|
||||
*/
|
||||
String SYNTAX_STYLE_CSS = "text/css";
|
||||
|
||||
|
||||
/**
|
||||
* Syntax style for highlighting D.
|
||||
*/
|
||||
String SYNTAX_STYLE_D = "text/d";
|
||||
|
||||
|
||||
/**
|
||||
* Syntax style for highlighting Dockerfiles.
|
||||
*/
|
||||
String SYNTAX_STYLE_DOCKERFILE = "text/dockerfile";
|
||||
|
||||
|
||||
/**
|
||||
* Style for highlighting Dart.
|
||||
*/
|
||||
String SYNTAX_STYLE_DART = "text/dart";
|
||||
|
||||
|
||||
/**
|
||||
* Style for highlighting Delphi/Pascal.
|
||||
*/
|
||||
String SYNTAX_STYLE_DELPHI = "text/delphi";
|
||||
|
||||
|
||||
/**
|
||||
* Style for highlighting DTD files.
|
||||
*/
|
||||
String SYNTAX_STYLE_DTD = "text/dtd";
|
||||
|
||||
|
||||
/**
|
||||
* Style for highlighting Fortran.
|
||||
*/
|
||||
String SYNTAX_STYLE_FORTRAN = "text/fortran";
|
||||
|
||||
|
||||
/**
|
||||
* Style for highlighting Groovy.
|
||||
*/
|
||||
String SYNTAX_STYLE_GROOVY = "text/groovy";
|
||||
|
||||
|
||||
/**
|
||||
* Style for highlighting hosts files.
|
||||
*/
|
||||
String SYNTAX_STYLE_HOSTS = "text/hosts";
|
||||
|
||||
|
||||
/**
|
||||
* Style for highlighting .htaccess files.
|
||||
*/
|
||||
String SYNTAX_STYLE_HTACCESS = "text/htaccess";
|
||||
|
||||
|
||||
/**
|
||||
* Style for highlighting HTML.
|
||||
*/
|
||||
String SYNTAX_STYLE_HTML = "text/html";
|
||||
|
||||
|
||||
/**
|
||||
* Style for highlighting INI files.
|
||||
*/
|
||||
String SYNTAX_STYLE_INI = "text/ini";
|
||||
|
||||
|
||||
/**
|
||||
* Style for highlighting Java.
|
||||
*/
|
||||
String SYNTAX_STYLE_JAVA = "text/java";
|
||||
|
||||
|
||||
/**
|
||||
* Style for highlighting Java.
|
||||
*/
|
||||
String SYNTAX_STYLE_JAVA_DISASSEMBLE = "text/javaassembly";
|
||||
|
||||
|
||||
/**
|
||||
* Style for highlighting JavaScript.
|
||||
*/
|
||||
String SYNTAX_STYLE_JAVASCRIPT = "text/javascript";
|
||||
|
||||
|
||||
/**
|
||||
* Style for highlighting JSON.
|
||||
*/
|
||||
String SYNTAX_STYLE_JSON = "text/json";
|
||||
|
||||
|
||||
/**
|
||||
* Style for highlighting .jshintrc files (JSON with comments, so can be
|
||||
* used for other times when you want this behavior).
|
||||
*/
|
||||
String SYNTAX_STYLE_JSON_WITH_COMMENTS = "text/jshintrc";
|
||||
|
||||
|
||||
/**
|
||||
* Style for highlighting JSP.
|
||||
*/
|
||||
String SYNTAX_STYLE_JSP = "text/jsp";
|
||||
|
||||
|
||||
/**
|
||||
* Style for highlighting LaTeX.
|
||||
*/
|
||||
String SYNTAX_STYLE_LATEX = "text/latex";
|
||||
|
||||
|
||||
/**
|
||||
* Style for highlighting Less.
|
||||
*/
|
||||
String SYNTAX_STYLE_LESS = "text/less";
|
||||
|
||||
|
||||
/**
|
||||
* Style for highlighting Lisp.
|
||||
*/
|
||||
String SYNTAX_STYLE_LISP = "text/lisp";
|
||||
|
||||
|
||||
/**
|
||||
* Style for highlighting Lua.
|
||||
*/
|
||||
String SYNTAX_STYLE_LUA = "text/lua";
|
||||
|
||||
|
||||
/**
|
||||
* Style for highlighting makefiles.
|
||||
*/
|
||||
String SYNTAX_STYLE_MAKEFILE = "text/makefile";
|
||||
|
||||
|
||||
/**
|
||||
* Style for highlighting MXML.
|
||||
*/
|
||||
String SYNTAX_STYLE_MXML = "text/mxml";
|
||||
|
||||
|
||||
/**
|
||||
* Style for highlighting NSIS install scripts.
|
||||
*/
|
||||
String SYNTAX_STYLE_NSIS = "text/nsis";
|
||||
|
||||
|
||||
/**
|
||||
* Style for highlighting Perl.
|
||||
*/
|
||||
String SYNTAX_STYLE_PERL = "text/perl";
|
||||
|
||||
|
||||
/**
|
||||
* Style for highlighting PHP.
|
||||
*/
|
||||
String SYNTAX_STYLE_PHP = "text/php";
|
||||
|
||||
|
||||
/**
|
||||
* Style for highlighting properties files.
|
||||
*/
|
||||
String SYNTAX_STYLE_PROPERTIES_FILE = "text/properties";
|
||||
|
||||
|
||||
/**
|
||||
* Style for highlighting Python.
|
||||
*/
|
||||
String SYNTAX_STYLE_PYTHON = "text/python";
|
||||
|
||||
|
||||
/**
|
||||
* Style for highlighting Ruby.
|
||||
*/
|
||||
String SYNTAX_STYLE_RUBY = "text/ruby";
|
||||
|
||||
|
||||
/**
|
||||
* Style for highlighting SAS keywords.
|
||||
*/
|
||||
String SYNTAX_STYLE_SAS = "text/sas";
|
||||
|
||||
|
||||
/**
|
||||
* Style for highlighting Scala.
|
||||
*/
|
||||
String SYNTAX_STYLE_SCALA = "text/scala";
|
||||
|
||||
|
||||
/**
|
||||
* Style for highlighting SQL.
|
||||
*/
|
||||
String SYNTAX_STYLE_SQL = "text/sql";
|
||||
|
||||
|
||||
/**
|
||||
* Style for highlighting Tcl.
|
||||
*/
|
||||
String SYNTAX_STYLE_TCL = "text/tcl";
|
||||
|
||||
|
||||
/**
|
||||
* Style for highlighting TypeScript.
|
||||
*/
|
||||
String SYNTAX_STYLE_TYPESCRIPT = "text/typescript";
|
||||
|
||||
|
||||
/**
|
||||
* Style for highlighting UNIX shell keywords.
|
||||
*/
|
||||
String SYNTAX_STYLE_UNIX_SHELL = "text/unix";
|
||||
|
||||
|
||||
/**
|
||||
* Style for highlighting Visual Basic.
|
||||
*/
|
||||
String SYNTAX_STYLE_VISUAL_BASIC = "text/vb";
|
||||
|
||||
|
||||
/**
|
||||
* Style for highlighting Windows batch files.
|
||||
*/
|
||||
String SYNTAX_STYLE_WINDOWS_BATCH = "text/bat";
|
||||
|
||||
|
||||
/**
|
||||
* Style for highlighting XML.
|
||||
*/
|
||||
String SYNTAX_STYLE_XML = "text/xml";
|
||||
|
||||
|
||||
/**
|
||||
* Syntax style for highlighting YAML.
|
||||
*/
|
||||
String SYNTAX_STYLE_YAML = "text/yaml";
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,732 @@
|
||||
/*
|
||||
* 02/26/2004
|
||||
*
|
||||
* SyntaxScheme.java - The set of colors and tokens used by an RSyntaxTextArea
|
||||
* to color tokens.
|
||||
*
|
||||
* 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.Font;
|
||||
import java.awt.Graphics2D;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
import javax.swing.text.StyleContext;
|
||||
|
||||
import org.xml.sax.Attributes;
|
||||
import org.xml.sax.InputSource;
|
||||
import org.xml.sax.SAXException;
|
||||
import org.xml.sax.XMLReader;
|
||||
import org.xml.sax.helpers.DefaultHandler;
|
||||
import org.xml.sax.helpers.XMLReaderFactory;
|
||||
|
||||
|
||||
/**
|
||||
* The set of colors and styles used by an <code>RSyntaxTextArea</code> to
|
||||
* color tokens.<p>
|
||||
* You can use this class to programmatically set the fonts and colors used in
|
||||
* an RSyntaxTextArea, but for more powerful, externalized control, consider
|
||||
* using {@link Theme}s instead.
|
||||
*
|
||||
* @author Robert Futrell
|
||||
* @version 1.0
|
||||
* @see Theme
|
||||
*/
|
||||
@SuppressWarnings({ "checkstyle:magicnumber" })
|
||||
public class SyntaxScheme implements Cloneable, TokenTypes {
|
||||
|
||||
private Style[] styles;
|
||||
|
||||
private static final String VERSION = "*ver1";
|
||||
|
||||
|
||||
/**
|
||||
* Creates a color scheme that either has all color values set to
|
||||
* a default value or set to <code>null</code>.
|
||||
*
|
||||
* @param useDefaults If <code>true</code>, all color values will
|
||||
* be set to default colors; if <code>false</code>, all colors
|
||||
* will be initially <code>null</code>.
|
||||
*/
|
||||
public SyntaxScheme(boolean useDefaults) {
|
||||
styles = new Style[DEFAULT_NUM_TOKEN_TYPES];
|
||||
if (useDefaults) {
|
||||
restoreDefaults(null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a default color scheme.
|
||||
*
|
||||
* @param baseFont The base font to use. Keywords will be a bold version
|
||||
* of this font, and comments will be an italicized version of this
|
||||
* font.
|
||||
*/
|
||||
public SyntaxScheme(Font baseFont) {
|
||||
this(baseFont, true);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a default color scheme.
|
||||
*
|
||||
* @param baseFont The base font to use. Keywords will be a bold version
|
||||
* of this font, and comments will be an italicized version of this
|
||||
* font.
|
||||
* @param fontStyles Whether bold and italic should be used in the scheme
|
||||
* (vs. all tokens using a plain font).
|
||||
*/
|
||||
public SyntaxScheme(Font baseFont, boolean fontStyles) {
|
||||
styles = new Style[DEFAULT_NUM_TOKEN_TYPES];
|
||||
restoreDefaults(baseFont, fontStyles);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Changes the "base font" for this syntax scheme. This is called by
|
||||
* <code>RSyntaxTextArea</code> when its font changes via
|
||||
* <code>setFont()</code>. This looks for tokens that use a derivative of
|
||||
* the text area's old font (but bolded and/or italicized) and make them
|
||||
* use the new font with those stylings instead. This is desirable because
|
||||
* most programmers prefer a single font to be used in their text editor,
|
||||
* but might want bold (say for keywords) or italics.
|
||||
*
|
||||
* @param old The old font of the text area.
|
||||
* @param font The new font of the text area.
|
||||
*/
|
||||
void changeBaseFont(Font old, Font font) {
|
||||
for (int i=0; i<styles.length; i++) {
|
||||
Style style = styles[i];
|
||||
if (style!=null && style.font!=null) {
|
||||
if (style.font.getFamily().equals(old.getFamily()) &&
|
||||
style.font.getSize()==old.getSize()) {
|
||||
int s = style.font.getStyle(); // Keep bold or italic
|
||||
StyleContext sc = StyleContext.getDefaultStyleContext();
|
||||
style.font= sc.getFont(font.getFamily(), s, font.getSize());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a deep copy of this color scheme.
|
||||
*
|
||||
* @return The copy.
|
||||
*/
|
||||
@Override
|
||||
public Object clone() {
|
||||
SyntaxScheme shcs = null;
|
||||
try {
|
||||
shcs = (SyntaxScheme)super.clone();
|
||||
} catch (CloneNotSupportedException cnse) { // Never happens
|
||||
cnse.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
shcs.styles = new Style[styles.length];
|
||||
for (int i=0; i<styles.length; i++) {
|
||||
Style s = styles[i];
|
||||
if (s!=null) {
|
||||
shcs.styles[i] = (Style)s.clone();
|
||||
}
|
||||
}
|
||||
return shcs;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tests whether this color scheme is the same as another color scheme.
|
||||
*
|
||||
* @param otherScheme The color scheme to compare to.
|
||||
* @return <code>true</code> if this color scheme and
|
||||
* <code>otherScheme</code> are the same scheme;
|
||||
* <code>false</code> otherwise.
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object otherScheme) {
|
||||
|
||||
// No need for null check; instanceof takes care of this for us,
|
||||
// i.e. "if (!(null instanceof Foo))" evaluates to "true".
|
||||
if (!(otherScheme instanceof SyntaxScheme)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Style[] otherSchemes = ((SyntaxScheme)otherScheme).styles;
|
||||
|
||||
int length = styles.length;
|
||||
for (int i=0; i<length; i++) {
|
||||
if (styles[i]==null) {
|
||||
if (otherSchemes[i]!=null) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (!styles[i].equals(otherSchemes[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a hex string representing an RGB color, of the form
|
||||
* <code>"$rrggbb"</code>.
|
||||
*
|
||||
* @param c The color.
|
||||
* @return The string representation of the color.
|
||||
*/
|
||||
private static String getHexString(Color c) {
|
||||
return "$" + Integer.toHexString((c.getRGB() & 0xffffff)+0x1000000).
|
||||
substring(1);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the specified style.
|
||||
*
|
||||
* @param index The index of the style.
|
||||
* @return The style.
|
||||
* @see #setStyle(int, Style)
|
||||
* @see #getStyleCount()
|
||||
*/
|
||||
public Style getStyle(int index) {
|
||||
return styles[index];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the number of styles.
|
||||
*
|
||||
* @return The number of styles.
|
||||
* @see #getStyle(int)
|
||||
*/
|
||||
public int getStyleCount() {
|
||||
return styles.length;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Used by third party implementors e.g. SquirreL SQL. Most applications do
|
||||
* not need to call this method.
|
||||
* <p>
|
||||
* Note that the returned array is not a copy of the style data; editing the
|
||||
* array will modify the styles used by any <code>RSyntaxTextArea</code>
|
||||
* using this scheme.
|
||||
*
|
||||
* @return The style array.
|
||||
* @see #setStyles(Style[])
|
||||
*/
|
||||
public Style[] getStyles() {
|
||||
return styles;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This is implemented to be consistent with {@link #equals(Object)}.
|
||||
* This is a requirement to keep FindBugs happy.
|
||||
*
|
||||
* @return The hash code for this object.
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
// Keep me fast. Iterating over *all* syntax schemes contained is
|
||||
// probably much slower than a "bad" hash code here.
|
||||
int hashCode = 0;
|
||||
int count = styles.length;
|
||||
for (int i=0; i<count; i++) {
|
||||
if (styles[i]!=null) {
|
||||
hashCode ^= styles[i].hashCode();
|
||||
break;
|
||||
}
|
||||
}
|
||||
return hashCode;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Loads a syntax scheme from an input stream.<p>
|
||||
*
|
||||
* Consider using the {@link Theme} class for saving and loading RSTA
|
||||
* styles rather than using this API.
|
||||
*
|
||||
* @param baseFont The font to use as the "base" for the syntax scheme.
|
||||
* If this is <code>null</code>, a default monospaced font is used.
|
||||
* @param in The stream to load from. It is up to the caller to close this
|
||||
* stream when they are done.
|
||||
* @return The syntax scheme.
|
||||
* @throws IOException If an IO error occurs.
|
||||
*/
|
||||
public static SyntaxScheme load(Font baseFont, InputStream in)
|
||||
throws IOException {
|
||||
if (baseFont==null) {
|
||||
baseFont = RSyntaxTextArea.getDefaultFont();
|
||||
}
|
||||
return SyntaxSchemeLoader.load(baseFont, in);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Loads a syntax highlighting color scheme from a string created from
|
||||
* <code>toCommaSeparatedString</code>. This method is useful for saving
|
||||
* and restoring color schemes.<p>
|
||||
*
|
||||
* Consider using the {@link Theme} class for saving and loading RSTA
|
||||
* styles rather than using this API.
|
||||
*
|
||||
* @param string A string generated from {@link #toCommaSeparatedString()}.
|
||||
* @return A color scheme.
|
||||
* @see #toCommaSeparatedString()
|
||||
*/
|
||||
public static SyntaxScheme loadFromString(String string) {
|
||||
return loadFromString(string, DEFAULT_NUM_TOKEN_TYPES);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Loads a syntax highlighting color scheme from a string created from
|
||||
* <code>toCommaSeparatedString</code>. This method is useful for saving
|
||||
* and restoring color schemes.<p>
|
||||
*
|
||||
* Consider using the {@link Theme} class for saving and loading RSTA
|
||||
* styles rather than using this API.
|
||||
*
|
||||
* @param string A string generated from {@link #toCommaSeparatedString()}.
|
||||
* @param tokenTypeCount The number of token types saved in this string.
|
||||
* This should be the number of token types saved by your custom
|
||||
* SyntaxScheme subclass,
|
||||
* or {@link TokenTypes#DEFAULT_NUM_TOKEN_TYPES} if you used the
|
||||
* standard implementation (which most people will).
|
||||
* @return A color scheme.
|
||||
* @see #loadFromString(String)
|
||||
* @see #toCommaSeparatedString()
|
||||
*/
|
||||
public static SyntaxScheme loadFromString(String string,
|
||||
int tokenTypeCount) {
|
||||
|
||||
SyntaxScheme scheme = new SyntaxScheme(true);
|
||||
|
||||
try {
|
||||
|
||||
if (string!=null) {
|
||||
|
||||
String[] tokens = string.split(",", -1);
|
||||
|
||||
// Check the version string, use defaults if incompatible
|
||||
if (tokens.length==0 || !VERSION.equals(tokens[0])) {
|
||||
return scheme; // Still set to defaults
|
||||
}
|
||||
|
||||
int tokenCount = tokenTypeCount*7 + 1; // Version string
|
||||
if (tokens.length!=tokenCount) {
|
||||
throw new Exception(
|
||||
"Not enough tokens in packed color scheme: expected " +
|
||||
tokenCount + ", found " + tokens.length);
|
||||
}
|
||||
|
||||
// Use StyleContext to create fonts to get composite fonts for
|
||||
// Asian glyphs.
|
||||
StyleContext sc = StyleContext.getDefaultStyleContext();
|
||||
|
||||
// Loop through each token style. Format:
|
||||
// "index,(fg|-),(bg|-),(t|f),((font,style,size)|(-,,))"
|
||||
for (int i=0; i<tokenTypeCount; i++) {
|
||||
|
||||
int pos = i*7 + 1;
|
||||
int integer = Integer.parseInt(tokens[pos]); // == i
|
||||
if (integer!=i) {
|
||||
throw new Exception("Expected " + i + ", found " +
|
||||
integer);
|
||||
}
|
||||
|
||||
Color fg = null; String temp = tokens[pos+1];
|
||||
if (!"-".equals(temp)) { // "-" => keep fg as null
|
||||
fg = stringToColor(temp);
|
||||
}
|
||||
Color bg = null; temp = tokens[pos+2];
|
||||
if (!"-".equals(temp)) { // "-" => keep bg as null
|
||||
bg = stringToColor(temp);
|
||||
}
|
||||
|
||||
// Check for "true" or "false" since we don't want to
|
||||
// accidentally suck in an int representing the next
|
||||
// packed color, and any string != "true" means false.
|
||||
temp = tokens[pos+3];
|
||||
if (!"t".equals(temp) && !"f".equals(temp)) {
|
||||
throw new Exception("Expected 't' or 'f', found " + temp);
|
||||
}
|
||||
boolean underline = "t".equals(temp);
|
||||
|
||||
Font font = null;
|
||||
String family = tokens[pos+4];
|
||||
if (!"-".equals(family)) {
|
||||
font = sc.getFont(family,
|
||||
Integer.parseInt(tokens[pos+5]), // style
|
||||
Integer.parseInt(tokens[pos+6])); // size
|
||||
}
|
||||
scheme.styles[i] = new Style(fg, bg, font, underline);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return scheme;
|
||||
|
||||
}
|
||||
|
||||
|
||||
void refreshFontMetrics(Graphics2D g2d) {
|
||||
// It is assumed that any rendering hints are already applied to g2d.
|
||||
for (int i=0; i<styles.length; i++) {
|
||||
Style s = styles[i];
|
||||
if (s!=null) {
|
||||
s.fontMetrics = s.font==null ? null :
|
||||
g2d.getFontMetrics(s.font);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Restores all colors and fonts to their default values.
|
||||
*
|
||||
* @param baseFont The base font to use when creating this scheme. If
|
||||
* this is <code>null</code>, then a default monospaced font is
|
||||
* used.
|
||||
*/
|
||||
public void restoreDefaults(Font baseFont) {
|
||||
restoreDefaults(baseFont, true);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Restores all colors and fonts to their default values.
|
||||
*
|
||||
* @param baseFont The base font to use when creating this scheme. If
|
||||
* this is <code>null</code>, then a default monospaced font is
|
||||
* used.
|
||||
* @param fontStyles Whether bold and italic should be used in the scheme
|
||||
* (vs. all tokens using a plain font).
|
||||
*/
|
||||
public void restoreDefaults(Font baseFont, boolean fontStyles) {
|
||||
|
||||
// Colors used by tokens.
|
||||
Color comment = new Color(0,128,0);
|
||||
Color docComment = new Color(164,0,0);
|
||||
Color markupComment = new Color(0, 96, 0);
|
||||
Color keyword = Color.BLUE;
|
||||
Color dataType = new Color(0,128,128);
|
||||
Color function = new Color(173,128,0);
|
||||
Color preprocessor = new Color(128,128,128);
|
||||
Color operator = new Color(128, 64, 64);
|
||||
Color regex = new Color(0,128,164);
|
||||
Color variable = new Color(255,153,0);
|
||||
Color literalNumber = new Color(100,0,200);
|
||||
Color literalString = new Color(220,0,156);
|
||||
Color error = new Color(148,148,0);
|
||||
|
||||
// (Possible) special font styles for keywords and comments.
|
||||
if (baseFont==null) {
|
||||
baseFont = RSyntaxTextArea.getDefaultFont();
|
||||
}
|
||||
Font commentFont = baseFont;
|
||||
Font keywordFont = baseFont;
|
||||
if (fontStyles) {
|
||||
// WORKAROUND for Sun JRE bug 6282887 (Asian font bug in 1.4/1.5)
|
||||
// That bug seems to be hidden now, see 6289072 instead.
|
||||
StyleContext sc = StyleContext.getDefaultStyleContext();
|
||||
Font boldFont = sc.getFont(baseFont.getFamily(), Font.BOLD,
|
||||
baseFont.getSize());
|
||||
Font italicFont = sc.getFont(baseFont.getFamily(), Font.ITALIC,
|
||||
baseFont.getSize());
|
||||
commentFont = italicFont;//baseFont.deriveFont(Font.ITALIC);
|
||||
keywordFont = boldFont;//baseFont.deriveFont(Font.BOLD);
|
||||
}
|
||||
|
||||
styles[COMMENT_EOL] = new Style(comment, null, commentFont);
|
||||
styles[COMMENT_MULTILINE] = new Style(comment, null, commentFont);
|
||||
styles[COMMENT_DOCUMENTATION] = new Style(docComment, null, commentFont);
|
||||
styles[COMMENT_KEYWORD] = new Style(new Color(255,152,0), null, commentFont);
|
||||
styles[COMMENT_MARKUP] = new Style(Color.gray, null, commentFont);
|
||||
styles[RESERVED_WORD] = new Style(keyword, null, keywordFont);
|
||||
styles[RESERVED_WORD_2] = new Style(keyword, null, keywordFont);
|
||||
styles[FUNCTION] = new Style(function);
|
||||
styles[LITERAL_BOOLEAN] = new Style(literalNumber);
|
||||
styles[LITERAL_NUMBER_DECIMAL_INT] = new Style(literalNumber);
|
||||
styles[LITERAL_NUMBER_FLOAT] = new Style(literalNumber);
|
||||
styles[LITERAL_NUMBER_HEXADECIMAL] = new Style(literalNumber);
|
||||
styles[LITERAL_STRING_DOUBLE_QUOTE] = new Style(literalString);
|
||||
styles[LITERAL_CHAR] = new Style(literalString);
|
||||
styles[LITERAL_BACKQUOTE] = new Style(literalString);
|
||||
styles[DATA_TYPE] = new Style(dataType, null, keywordFont);
|
||||
styles[VARIABLE] = new Style(variable);
|
||||
styles[REGEX] = new Style(regex);
|
||||
styles[ANNOTATION] = new Style(Color.gray);
|
||||
styles[IDENTIFIER] = new Style(null);
|
||||
styles[WHITESPACE] = new Style(Color.gray);
|
||||
styles[SEPARATOR] = new Style(Color.RED);
|
||||
styles[OPERATOR] = new Style(operator);
|
||||
styles[PREPROCESSOR] = new Style(preprocessor);
|
||||
styles[MARKUP_TAG_DELIMITER] = new Style(Color.RED);
|
||||
styles[MARKUP_TAG_NAME] = new Style(Color.BLUE);
|
||||
styles[MARKUP_TAG_ATTRIBUTE] = new Style(new Color(63,127,127));
|
||||
styles[MARKUP_TAG_ATTRIBUTE_VALUE]= new Style(literalString);
|
||||
styles[MARKUP_COMMENT] = new Style(markupComment, null, commentFont);
|
||||
styles[MARKUP_DTD] = new Style(function);
|
||||
styles[MARKUP_PROCESSING_INSTRUCTION] = new Style(preprocessor);
|
||||
styles[MARKUP_CDATA] = new Style(new Color(0xcc6600));
|
||||
styles[MARKUP_CDATA_DELIMITER] = new Style(new Color(0x008080));
|
||||
styles[MARKUP_ENTITY_REFERENCE] = new Style(dataType);
|
||||
styles[ERROR_IDENTIFIER] = new Style(error);
|
||||
styles[ERROR_NUMBER_FORMAT] = new Style(error);
|
||||
styles[ERROR_STRING_DOUBLE] = new Style(error);
|
||||
styles[ERROR_CHAR] = new Style(error);
|
||||
|
||||
// Issue #34: If an application modifies TokenTypes to add new built-in
|
||||
// token types, we'll get NPEs if not all styles are initialized.
|
||||
for (int i=0; i<styles.length; i++) {
|
||||
if (styles[i]==null) {
|
||||
styles[i] = new Style();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets a style to use when rendering a token type.
|
||||
*
|
||||
* @param type The token type.
|
||||
* @param style The style for the token type.
|
||||
* @see #getStyle(int)
|
||||
*/
|
||||
public void setStyle(int type, Style style) {
|
||||
styles[type] = style;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Used by third party implementors e.g. SquirreL SQL. Most applications do
|
||||
* not need to call this method; individual styles can be set via
|
||||
* {@link #setStyle(int, Style)}.
|
||||
*
|
||||
* @param styles The new array of styles to use. Note that this should
|
||||
* have length of at least
|
||||
* {@link TokenTypes#DEFAULT_NUM_TOKEN_TYPES}.
|
||||
* @see #setStyle(int, Style)
|
||||
* @see #getStyles()
|
||||
*/
|
||||
public void setStyles(Style[] styles) {
|
||||
this.styles = styles;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the color represented by a string. If the first char in the
|
||||
* string is '<code>$</code>', it is assumed to be in hex, otherwise it is
|
||||
* assumed to be decimal. So, for example, both of these:
|
||||
* <pre>
|
||||
* "$00ff00"
|
||||
* "65280"
|
||||
* </pre>
|
||||
* will return <code>new Color(0, 255, 0)</code>.
|
||||
*
|
||||
* @param s The string to evaluate.
|
||||
* @return The color.
|
||||
*/
|
||||
private static Color stringToColor(String s) {
|
||||
// Check for decimal as well as hex, for backward
|
||||
// compatibility (fix from GwynEvans on forums)
|
||||
char ch = s.charAt(0);
|
||||
return new Color((ch=='$' || ch=='#') ?
|
||||
Integer.parseInt(s.substring(1),16) :
|
||||
Integer.parseInt(s));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns this syntax highlighting scheme as a comma-separated list of
|
||||
* values as follows:
|
||||
* <ul>
|
||||
* <li>If a color is non-null, it is added as a 24-bit integer
|
||||
* of the form <code>((r<*lt;16) | (g<*lt;8) | (b))</code>; if
|
||||
* it is <code>null</code>, it is added as "<i>-,</i>".
|
||||
* <li>The font and style (bold/italic) is added as an integer like so:
|
||||
* "<i>family,</i> <i>style,</i> <i>size</i>".
|
||||
* <li>The entire syntax highlighting scheme is thus one long string of
|
||||
* color schemes of the format "<i>i,[fg],[bg],uline,[style]</i>,
|
||||
* where:
|
||||
* <ul>
|
||||
* <li><code>i</code> is the index of the syntax scheme.
|
||||
* <li><i>fg</i> and <i>bg</i> are the foreground and background
|
||||
* colors for the scheme, and may be null (represented by
|
||||
* <code>-</code>).
|
||||
* <li><code>uline</code> is whether or not the font should be
|
||||
* underlined, and is either <code>t</code> or <code>f</code>.
|
||||
* <li><code>style</code> is the <code>family,style,size</code>
|
||||
* triplet described above.
|
||||
* </ul>
|
||||
* </ul>
|
||||
*
|
||||
* @return A string representing the rgb values of the colors.
|
||||
* @see #loadFromString(String)
|
||||
*/
|
||||
public String toCommaSeparatedString() {
|
||||
|
||||
StringBuilder sb = new StringBuilder(VERSION);
|
||||
sb.append(',');
|
||||
|
||||
for (int i=0; i<styles.length; i++) {
|
||||
|
||||
sb.append(i).append(',');
|
||||
|
||||
Style ss = styles[i];
|
||||
if (ss==null) { // Only true for i==0 (NULL token)
|
||||
sb.append("-,-,f,-,,,");
|
||||
continue;
|
||||
}
|
||||
|
||||
Color c = ss.foreground;
|
||||
sb.append(c!=null ? (getHexString(c) + ",") : "-,");
|
||||
c = ss.background;
|
||||
sb.append(c!=null ? (getHexString(c) + ",") : "-,");
|
||||
|
||||
sb.append(ss.underline ? "t," : "f,");
|
||||
|
||||
Font font = ss.font;
|
||||
if (font!=null) {
|
||||
sb.append(font.getFamily()).append(',').
|
||||
append(font.getStyle()).append(',').
|
||||
append(font.getSize()).append(',');
|
||||
}
|
||||
else {
|
||||
sb.append("-,,,");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return sb.substring(0,sb.length()-1); // Take off final ','.
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a <code>SyntaxScheme</code> from an XML file.
|
||||
*/
|
||||
private static class SyntaxSchemeLoader extends DefaultHandler {
|
||||
|
||||
private Font baseFont;
|
||||
private SyntaxScheme scheme;
|
||||
|
||||
SyntaxSchemeLoader(Font baseFont) {
|
||||
scheme = new SyntaxScheme(baseFont);
|
||||
}
|
||||
|
||||
public static SyntaxScheme load(Font baseFont, InputStream in)
|
||||
throws IOException {
|
||||
SyntaxSchemeLoader parser = null;
|
||||
try {
|
||||
XMLReader reader = XMLReaderFactory.createXMLReader();
|
||||
parser = new SyntaxSchemeLoader(baseFont);
|
||||
parser.baseFont = baseFont;
|
||||
reader.setContentHandler(parser);
|
||||
InputSource is = new InputSource(in);
|
||||
is.setEncoding("UTF-8");
|
||||
reader.parse(is);
|
||||
} catch (SAXException se) {
|
||||
throw new IOException(se.toString());
|
||||
}
|
||||
return parser.scheme;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startElement(String uri, String localName, String qName,
|
||||
Attributes attrs) {
|
||||
|
||||
if ("style".equals(qName)) {
|
||||
|
||||
String type = attrs.getValue("token");
|
||||
Field field = null;
|
||||
try {
|
||||
field = Token.class.getField(type);
|
||||
} catch (RuntimeException re) {
|
||||
throw re; // FindBugs
|
||||
} catch (Exception e) {
|
||||
System.err.println("Invalid token type: " + type);
|
||||
return;
|
||||
}
|
||||
|
||||
if (field.getType()==int.class) {
|
||||
|
||||
int index = 0;
|
||||
try {
|
||||
index = field.getInt(scheme);
|
||||
} catch (IllegalArgumentException e) {
|
||||
e.printStackTrace();
|
||||
return;
|
||||
} catch (IllegalAccessException e) {
|
||||
e.printStackTrace();
|
||||
return;
|
||||
}
|
||||
|
||||
String fgStr = attrs.getValue("fg");
|
||||
if (fgStr!=null) {
|
||||
Color fg = stringToColor(fgStr);
|
||||
scheme.styles[index].foreground = fg;
|
||||
}
|
||||
|
||||
String bgStr = attrs.getValue("bg");
|
||||
if (bgStr!=null) {
|
||||
Color bg = stringToColor(bgStr);
|
||||
scheme.styles[index].background = bg;
|
||||
}
|
||||
|
||||
boolean styleSpecified = false;
|
||||
boolean bold = false;
|
||||
boolean italic = false;
|
||||
String boldStr = attrs.getValue("bold");
|
||||
if (boldStr!=null) {
|
||||
bold = Boolean.parseBoolean(boldStr);
|
||||
styleSpecified = true;
|
||||
}
|
||||
String italicStr = attrs.getValue("italic");
|
||||
if (italicStr!=null) {
|
||||
italic = Boolean.parseBoolean(italicStr);
|
||||
styleSpecified = true;
|
||||
}
|
||||
if (styleSpecified) {
|
||||
int style = 0;
|
||||
if (bold) {
|
||||
style |= Font.BOLD;
|
||||
}
|
||||
if (italic) {
|
||||
style |= Font.ITALIC;
|
||||
}
|
||||
scheme.styles[index].font = baseFont.deriveFont(style);
|
||||
}
|
||||
|
||||
String ulineStr = attrs.getValue("underline");
|
||||
if (ulineStr!=null) {
|
||||
boolean uline= Boolean.parseBoolean(ulineStr);
|
||||
scheme.styles[index].underline = uline;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,772 @@
|
||||
/*
|
||||
* 11/25/2008
|
||||
*
|
||||
* TextEditorPane.java - A syntax highlighting text area that has knowledge of
|
||||
* the file it is editing on disk.
|
||||
*
|
||||
* 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.io.BufferedReader;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.UnsupportedCharsetException;
|
||||
|
||||
import javax.swing.JFrame;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.event.DocumentEvent;
|
||||
import javax.swing.event.DocumentListener;
|
||||
import javax.swing.text.Document;
|
||||
|
||||
import org.fife.io.UnicodeReader;
|
||||
import org.fife.io.UnicodeWriter;
|
||||
import org.fife.ui.rtextarea.RTextAreaEditorKit;
|
||||
|
||||
/**
|
||||
* An extension of {@link org.fife.ui.rsyntaxtextarea.RSyntaxTextArea}
|
||||
* that adds information about the file being edited, such as:
|
||||
*
|
||||
* <ul>
|
||||
* <li>Its name and location.
|
||||
* <li>Is it dirty?
|
||||
* <li>Is it read-only?
|
||||
* <li>The last time it was loaded or saved to disk (local files only).
|
||||
* <li>The file's encoding on disk.
|
||||
* <li>Easy access to the line separator.
|
||||
* </ul>
|
||||
*
|
||||
* Loading and saving is also built into the editor.<p>
|
||||
*
|
||||
* When saving UTF-8 files, whether or not a BOM is written is controlled by
|
||||
* the {@link UnicodeWriter} class.
|
||||
* Use {@link UnicodeWriter#setWriteUtf8BOM(boolean)} to toggle writing BOMs
|
||||
* for UTF-8 files.<p>
|
||||
*
|
||||
* Both local and remote files (e.g. ftp) are supported. See the
|
||||
* {@link FileLocation} class for more information.
|
||||
*
|
||||
* @author Robert Futrell
|
||||
* @version 1.0
|
||||
* @see FileLocation
|
||||
*/
|
||||
public class TextEditorPane extends RSyntaxTextArea implements
|
||||
DocumentListener {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* Property change event fired when the file path this text area references
|
||||
* is updated.
|
||||
*
|
||||
* @see #load(FileLocation, String)
|
||||
* @see #saveAs(FileLocation)
|
||||
*/
|
||||
public static final String FULL_PATH_PROPERTY = "TextEditorPane.fileFullPath";
|
||||
|
||||
/**
|
||||
* Property change event fired when the text area's dirty flag changes.
|
||||
*
|
||||
* @see #setDirty(boolean)
|
||||
*/
|
||||
public static final String DIRTY_PROPERTY = "TextEditorPane.dirty";
|
||||
|
||||
/**
|
||||
* Property change event fired when the text area should be treated as
|
||||
* read-only, and previously it should not, or vice-versa.
|
||||
*
|
||||
* @see #setReadOnly(boolean)
|
||||
*/
|
||||
public static final String READ_ONLY_PROPERTY = "TextEditorPane.readOnly";
|
||||
|
||||
/**
|
||||
* Property change event fired when the text area's encoding changes.
|
||||
*
|
||||
* @see #setEncoding(String)
|
||||
*/
|
||||
public static final String ENCODING_PROPERTY = "TextEditorPane.encoding";
|
||||
|
||||
/**
|
||||
* The location of the file being edited.
|
||||
*/
|
||||
private FileLocation loc;
|
||||
|
||||
/**
|
||||
* The charset to use when reading or writing this file.
|
||||
*/
|
||||
private String charSet;
|
||||
|
||||
/**
|
||||
* Whether the file should be treated as read-only.
|
||||
*/
|
||||
private boolean readOnly;
|
||||
|
||||
/**
|
||||
* Whether the file is dirty.
|
||||
*/
|
||||
private boolean dirty;
|
||||
|
||||
/**
|
||||
* The last time this file was modified on disk, for local files.
|
||||
* For remote files, this value should always be
|
||||
* {@link #LAST_MODIFIED_UNKNOWN}.
|
||||
*/
|
||||
private long lastSaveOrLoadTime;
|
||||
|
||||
/**
|
||||
* The value returned by {@link #getLastSaveOrLoadTime()} for remote files.
|
||||
*/
|
||||
public static final long LAST_MODIFIED_UNKNOWN = 0;
|
||||
|
||||
/**
|
||||
* The default name given to files if none is specified in a constructor.
|
||||
*/
|
||||
private static final String DEFAULT_FILE_NAME = "Untitled.txt";
|
||||
|
||||
|
||||
/**
|
||||
* Constructor. The file will be given a default name.
|
||||
*/
|
||||
public TextEditorPane() {
|
||||
this(INSERT_MODE);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Constructor. The file will be given a default name.
|
||||
*
|
||||
* @param textMode Either <code>INSERT_MODE</code> or
|
||||
* <code>OVERWRITE_MODE</code>.
|
||||
*/
|
||||
public TextEditorPane(int textMode) {
|
||||
this(textMode, false);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new <code>TextEditorPane</code>. The file will be given
|
||||
* a default name.
|
||||
*
|
||||
* @param textMode Either <code>INSERT_MODE</code> or
|
||||
* <code>OVERWRITE_MODE</code>.
|
||||
* @param wordWrapEnabled Whether or not to use word wrap in this pane.
|
||||
*/
|
||||
public TextEditorPane(int textMode, boolean wordWrapEnabled) {
|
||||
super(textMode);
|
||||
setLineWrap(wordWrapEnabled);
|
||||
try {
|
||||
init(null, null);
|
||||
} catch (IOException ioe) { // Never happens
|
||||
ioe.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new <code>TextEditorPane</code>.
|
||||
*
|
||||
* @param textMode Either <code>INSERT_MODE</code> or
|
||||
* <code>OVERWRITE_MODE</code>.
|
||||
* @param wordWrapEnabled Whether or not to use word wrap in this pane.
|
||||
* @param loc The location of the text file being edited. If this value
|
||||
* is <code>null</code>, a file named "Untitled.txt" in the current
|
||||
* directory is used.
|
||||
* @throws IOException If an IO error occurs reading the file at
|
||||
* <code>loc</code>. This of course won't happen if
|
||||
* <code>loc</code> is <code>null</code>.
|
||||
*/
|
||||
public TextEditorPane(int textMode, boolean wordWrapEnabled,
|
||||
FileLocation loc) throws IOException {
|
||||
this(textMode, wordWrapEnabled, loc, null);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new <code>TextEditorPane</code>.
|
||||
*
|
||||
* @param textMode Either <code>INSERT_MODE</code> or
|
||||
* <code>OVERWRITE_MODE</code>.
|
||||
* @param wordWrapEnabled Whether or not to use word wrap in this pane.
|
||||
* @param loc The location of the text file being edited. If this value
|
||||
* is <code>null</code>, a file named "Untitled.txt" in the current
|
||||
* directory is used. This file is displayed as empty even if it
|
||||
* actually exists.
|
||||
* @param defaultEnc The default encoding to use when opening the file,
|
||||
* if the file is not Unicode. If this value is <code>null</code>,
|
||||
* a system default value is used.
|
||||
* @throws IOException If an IO error occurs reading the file at
|
||||
* <code>loc</code>. This of course won't happen if
|
||||
* <code>loc</code> is <code>null</code>.
|
||||
*/
|
||||
public TextEditorPane(int textMode, boolean wordWrapEnabled,
|
||||
FileLocation loc, String defaultEnc) throws IOException {
|
||||
super(textMode);
|
||||
setLineWrap(wordWrapEnabled);
|
||||
init(loc, defaultEnc);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Callback for when styles in the current document change.
|
||||
* This method is never called.
|
||||
*
|
||||
* @param e The document event.
|
||||
*/
|
||||
@Override
|
||||
public void changedUpdate(DocumentEvent e) {
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Returns the default encoding for this operating system.
|
||||
*
|
||||
* @return The default encoding.
|
||||
*/
|
||||
private static String getDefaultEncoding() {
|
||||
// NOTE: The "file.encoding" system property is not guaranteed to be
|
||||
// set by the spec, so we cannot rely on it.
|
||||
String encoding = Charset.defaultCharset().name();
|
||||
if (encoding==null) {
|
||||
try {
|
||||
File f = File.createTempFile("rsta", null);
|
||||
FileWriter w = new FileWriter(f);
|
||||
encoding = w.getEncoding();
|
||||
w.close();
|
||||
f.deleteOnExit();//delete(); Keep FindBugs happy
|
||||
} catch (IOException ioe) {
|
||||
encoding = "US-ASCII";
|
||||
}
|
||||
}
|
||||
return encoding;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the encoding to use when reading or writing this file.
|
||||
*
|
||||
* @return The encoding.
|
||||
* @see #setEncoding(String)
|
||||
*/
|
||||
public String getEncoding() {
|
||||
return charSet;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the full path to this document.
|
||||
*
|
||||
* @return The full path to the document.
|
||||
*/
|
||||
public String getFileFullPath() {
|
||||
return loc==null ? null : loc.getFileFullPath();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the file name of this document.
|
||||
*
|
||||
* @return The file name.
|
||||
*/
|
||||
public String getFileName() {
|
||||
return loc==null ? null : loc.getFileName();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the timestamp for when this file was last loaded or saved
|
||||
* <em>by this editor pane</em>. If the file has been modified on disk by
|
||||
* another process after it was loaded into this editor pane, this method
|
||||
* will not return the actual file's last modified time.<p>
|
||||
*
|
||||
* For remote files, this method will always return
|
||||
* {@link #LAST_MODIFIED_UNKNOWN}.
|
||||
*
|
||||
* @return The timestamp when this file was last loaded or saved by this
|
||||
* editor pane, if it is a local file, or
|
||||
* {@link #LAST_MODIFIED_UNKNOWN} if it is a remote file.
|
||||
* @see #isModifiedOutsideEditor()
|
||||
*/
|
||||
public long getLastSaveOrLoadTime() {
|
||||
return lastSaveOrLoadTime;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the line separator used when writing this file (e.g.
|
||||
* "<code>\n</code>", "<code>\r\n</code>", or "<code>\r</code>").<p>
|
||||
*
|
||||
* Note that this value is an <code>Object</code> and not a
|
||||
* <code>String</code> as that is the way the {@link Document} interface
|
||||
* defines its property values. If you always use
|
||||
* {@link #setLineSeparator(String)} to modify this value, then the value
|
||||
* returned from this method will always be a <code>String</code>.
|
||||
*
|
||||
* @return The line separator. If this value is <code>null</code>, then
|
||||
* the system default line separator is used (usually the value
|
||||
* of <code>System.getProperty("line.separator")</code>).
|
||||
* @see #setLineSeparator(String)
|
||||
* @see #setLineSeparator(String, boolean)
|
||||
*/
|
||||
public Object getLineSeparator() {
|
||||
return getDocument().getProperty(
|
||||
RTextAreaEditorKit.EndOfLineStringProperty);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Initializes this editor with the specified file location.
|
||||
*
|
||||
* @param loc The file location. If this is <code>null</code>, a default
|
||||
* location is used and an empty file is displayed.
|
||||
* @param defaultEnc The default encoding to use when opening the file,
|
||||
* if the file is not Unicode. If this value is <code>null</code>,
|
||||
* a system default value is used.
|
||||
* @throws IOException If an IO error occurs reading from <code>loc</code>.
|
||||
* If <code>loc</code> is <code>null</code>, this cannot happen.
|
||||
*/
|
||||
private void init(FileLocation loc, String defaultEnc) throws IOException {
|
||||
|
||||
if (loc==null) {
|
||||
// Don't call load() just in case Untitled.txt actually exists,
|
||||
// just to ensure there is no chance of an IOException being thrown
|
||||
// in the default case.
|
||||
this.loc = FileLocation.create(DEFAULT_FILE_NAME);
|
||||
charSet = defaultEnc==null ? getDefaultEncoding() : defaultEnc;
|
||||
// Ensure that line separator always has a value, even if the file
|
||||
// does not exist (or is the "default" file). This makes life
|
||||
// easier for host applications that want to display this value.
|
||||
setLineSeparator(System.getProperty("line.separator"));
|
||||
}
|
||||
else {
|
||||
load(loc, defaultEnc); // Sets this.loc
|
||||
}
|
||||
|
||||
if (this.loc.isLocalAndExists()) {
|
||||
File file = new File(this.loc.getFileFullPath());
|
||||
lastSaveOrLoadTime = file.lastModified();
|
||||
setReadOnly(!file.canWrite());
|
||||
}
|
||||
else {
|
||||
lastSaveOrLoadTime = LAST_MODIFIED_UNKNOWN;
|
||||
setReadOnly(false);
|
||||
}
|
||||
|
||||
setDirty(false);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Callback for when text is inserted into the document.
|
||||
*
|
||||
* @param e Information on the insertion.
|
||||
*/
|
||||
@Override
|
||||
public void insertUpdate(DocumentEvent e) {
|
||||
if (!dirty) {
|
||||
setDirty(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether or not the text in this editor has unsaved changes.
|
||||
*
|
||||
* @return Whether or not the text has unsaved changes.
|
||||
* @see #setDirty(boolean)
|
||||
*/
|
||||
public boolean isDirty() {
|
||||
return dirty;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether this file is a local file.
|
||||
*
|
||||
* @return Whether this is a local file.
|
||||
*/
|
||||
public boolean isLocal() {
|
||||
return loc.isLocal();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether this is a local file that already exists.
|
||||
*
|
||||
* @return Whether this is a local file that already exists.
|
||||
*/
|
||||
public boolean isLocalAndExists() {
|
||||
return loc.isLocalAndExists();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether the text file has been modified outside of this editor
|
||||
* since the last load or save operation. Note that if this is a remote
|
||||
* file, this method will always return <code>false</code>.<p>
|
||||
*
|
||||
* This method may be used by applications to implement a reloading
|
||||
* feature, where the user is prompted to reload a file if it has been
|
||||
* modified since their last open or save.
|
||||
*
|
||||
* @return Whether the text file has been modified outside of this
|
||||
* editor.
|
||||
* @see #getLastSaveOrLoadTime()
|
||||
*/
|
||||
public boolean isModifiedOutsideEditor() {
|
||||
return loc.getActualLastModified()>getLastSaveOrLoadTime();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether or not the text area should be treated as read-only.
|
||||
*
|
||||
* @return Whether or not the text area should be treated as read-only.
|
||||
* @see #setReadOnly(boolean)
|
||||
*/
|
||||
public boolean isReadOnly() {
|
||||
return readOnly;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Loads the specified file in this editor. This method fires a property
|
||||
* change event of type {@link #FULL_PATH_PROPERTY}.
|
||||
*
|
||||
* @param loc The location of the file to load. This cannot be
|
||||
* <code>null</code>.
|
||||
* @param defaultEnc The encoding to use when loading/saving the file.
|
||||
* This encoding will only be used if the file is not Unicode.
|
||||
* If this value is <code>null</code>, the system default encoding
|
||||
* is used.
|
||||
* @throws IOException If an IO error occurs.
|
||||
* @see #save()
|
||||
* @see #saveAs(FileLocation)
|
||||
*/
|
||||
public void load(FileLocation loc, String defaultEnc) throws IOException {
|
||||
|
||||
// For new local files, just go with it.
|
||||
if (loc.isLocal() && !loc.isLocalAndExists()) {
|
||||
this.charSet = defaultEnc!=null ? defaultEnc : getDefaultEncoding();
|
||||
this.loc = loc;
|
||||
setText(null);
|
||||
discardAllEdits();
|
||||
setDirty(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Old local files and remote files, load 'em up. UnicodeReader will
|
||||
// check for BOMs and handle them correctly in all cases, then pass
|
||||
// rest of stream down to InputStreamReader.
|
||||
UnicodeReader ur = new UnicodeReader(loc.getInputStream(), defaultEnc);
|
||||
|
||||
// Remove listener so dirty flag doesn't get set when loading a file.
|
||||
Document doc = getDocument();
|
||||
doc.removeDocumentListener(this);
|
||||
BufferedReader r = new BufferedReader(ur);
|
||||
try {
|
||||
read(r, null);
|
||||
} finally {
|
||||
doc.addDocumentListener(this);
|
||||
r.close();
|
||||
}
|
||||
|
||||
// No IOException thrown, so we can finally change the location.
|
||||
charSet = ur.getEncoding();
|
||||
String old = getFileFullPath();
|
||||
this.loc = loc;
|
||||
setDirty(false);
|
||||
setCaretPosition(0);
|
||||
discardAllEdits();
|
||||
firePropertyChange(FULL_PATH_PROPERTY, old, getFileFullPath());
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Reloads this file from disk. The file must exist for this operation
|
||||
* to not throw an exception.<p>
|
||||
*
|
||||
* The file's "dirty" state will be set to <code>false</code> after this
|
||||
* operation. If this is a local file, its "last modified" time is
|
||||
* updated to reflect that of the actual file.<p>
|
||||
*
|
||||
* Note that if the file has been modified on disk, and is now a Unicode
|
||||
* encoding when before it wasn't (or if it is a different Unicode now),
|
||||
* this will cause this {@link TextEditorPane}'s encoding to change.
|
||||
* Otherwise, the file's encoding will stay the same.
|
||||
*
|
||||
* @throws IOException If the file does not exist, or if an IO error
|
||||
* occurs reading the file.
|
||||
* @see #isLocalAndExists()
|
||||
*/
|
||||
public void reload() throws IOException {
|
||||
String oldEncoding = getEncoding();
|
||||
UnicodeReader ur = new UnicodeReader(loc.getInputStream(), oldEncoding);
|
||||
String encoding = ur.getEncoding();
|
||||
BufferedReader r = new BufferedReader(ur);
|
||||
try {
|
||||
read(r, null); // Dumps old contents.
|
||||
} finally {
|
||||
r.close();
|
||||
}
|
||||
setEncoding(encoding);
|
||||
setDirty(false);
|
||||
syncLastSaveOrLoadTimeToActualFile();
|
||||
discardAllEdits(); // Prevent user from being able to undo the reload
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Called whenever text is removed from this editor.
|
||||
*
|
||||
* @param e The document event.
|
||||
*/
|
||||
@Override
|
||||
public void removeUpdate(DocumentEvent e) {
|
||||
if (!dirty) {
|
||||
setDirty(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Saves the file in its current encoding.<p>
|
||||
*
|
||||
* The text area's "dirty" state is set to <code>false</code>, and if
|
||||
* this is a local file, its "last modified" time is updated.
|
||||
*
|
||||
* @throws IOException If an IO error occurs.
|
||||
* @see #saveAs(FileLocation)
|
||||
* @see #load(FileLocation, String)
|
||||
*/
|
||||
public void save() throws IOException {
|
||||
saveImpl(loc);
|
||||
setDirty(false);
|
||||
syncLastSaveOrLoadTimeToActualFile();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Saves this file in a new local location. This method fires a property
|
||||
* change event of type {@link #FULL_PATH_PROPERTY}.
|
||||
*
|
||||
* @param loc The location to save to.
|
||||
* @throws IOException If an IO error occurs.
|
||||
* @see #save()
|
||||
* @see #load(FileLocation, String)
|
||||
*/
|
||||
public void saveAs(FileLocation loc) throws IOException {
|
||||
saveImpl(loc);
|
||||
// No exception thrown - we can "rename" the file.
|
||||
String old = getFileFullPath();
|
||||
this.loc = loc;
|
||||
setDirty(false);
|
||||
lastSaveOrLoadTime = loc.getActualLastModified();
|
||||
firePropertyChange(FULL_PATH_PROPERTY, old, getFileFullPath());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Saves the text in this editor to the specified location.
|
||||
*
|
||||
* @param loc The location to save to.
|
||||
* @throws IOException If an IO error occurs.
|
||||
*/
|
||||
private void saveImpl(FileLocation loc) throws IOException {
|
||||
OutputStream out = loc.getOutputStream();
|
||||
BufferedWriter w = new BufferedWriter(
|
||||
new UnicodeWriter(out, getEncoding()));
|
||||
try {
|
||||
write(w);
|
||||
} finally {
|
||||
w.close();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets whether or not this text in this editor has unsaved changes.
|
||||
* This fires a property change event of type {@link #DIRTY_PROPERTY}.<p>
|
||||
*
|
||||
* Applications will usually have no need to call this method directly; the
|
||||
* only time you might have a need to call this method directly is if you
|
||||
* have to initialize an instance of TextEditorPane with content that does
|
||||
* not come from a file. <code>TextEditorPane</code> automatically sets its
|
||||
* own dirty flag when its content is edited, when its encoding is changed,
|
||||
* or when its line ending property is changed. It is cleared whenever
|
||||
* <code>load()</code>, <code>reload()</code>, <code>save()</code>, or
|
||||
* <code>saveAs()</code> are called.
|
||||
*
|
||||
* @param dirty Whether or not the text has been modified.
|
||||
* @see #isDirty()
|
||||
*/
|
||||
public void setDirty(boolean dirty) {
|
||||
if (this.dirty!=dirty) {
|
||||
this.dirty = dirty;
|
||||
firePropertyChange(DIRTY_PROPERTY, !dirty, dirty);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the document for this editor.
|
||||
*
|
||||
* @param doc The new document.
|
||||
*/
|
||||
@Override
|
||||
public void setDocument(Document doc) {
|
||||
Document old = getDocument();
|
||||
if (old!=null) {
|
||||
old.removeDocumentListener(this);
|
||||
}
|
||||
super.setDocument(doc);
|
||||
doc.addDocumentListener(this);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the encoding to use when reading or writing this file. This
|
||||
* method sets the editor's dirty flag when the encoding is changed, and
|
||||
* fires a property change event of type {@link #ENCODING_PROPERTY}.
|
||||
*
|
||||
* @param encoding The new encoding.
|
||||
* @throws UnsupportedCharsetException If the encoding is not supported.
|
||||
* @throws NullPointerException If <code>encoding</code> is
|
||||
* <code>null</code>.
|
||||
* @see #getEncoding()
|
||||
*/
|
||||
public void setEncoding(String encoding) {
|
||||
if (encoding==null) {
|
||||
throw new NullPointerException("encoding cannot be null");
|
||||
}
|
||||
else if (!Charset.isSupported(encoding)) {
|
||||
throw new UnsupportedCharsetException(encoding);
|
||||
}
|
||||
if (charSet==null || !charSet.equals(encoding)) {
|
||||
String oldEncoding = charSet;
|
||||
charSet = encoding;
|
||||
firePropertyChange(ENCODING_PROPERTY, oldEncoding, charSet);
|
||||
setDirty(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the line separator sequence to use when this file is saved (e.g.
|
||||
* "<code>\n</code>", "<code>\r\n</code>" or "<code>\r</code>").
|
||||
*
|
||||
* Besides parameter checking, this method is preferred over
|
||||
* <code>getDocument().putProperty()</code> because it sets the editor's
|
||||
* dirty flag when the line separator is changed.
|
||||
*
|
||||
* @param separator The new line separator.
|
||||
* @throws NullPointerException If <code>separator</code> is
|
||||
* <code>null</code>.
|
||||
* @throws IllegalArgumentException If <code>separator</code> is not one
|
||||
* of "<code>\n</code>", "<code>\r\n</code>" or "<code>\r</code>".
|
||||
* @see #getLineSeparator()
|
||||
*/
|
||||
public void setLineSeparator(String separator) {
|
||||
setLineSeparator(separator, true);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the line separator sequence to use when this file is saved (e.g.
|
||||
* "<code>\n</code>", "<code>\r\n</code>" or "<code>\r</code>").
|
||||
*
|
||||
* Besides parameter checking, this method is preferred over
|
||||
* <code>getDocument().putProperty()</code> because can set the editor's
|
||||
* dirty flag when the line separator is changed.
|
||||
*
|
||||
* @param separator The new line separator.
|
||||
* @param setDirty Whether the dirty flag should be set if the line
|
||||
* separator is changed.
|
||||
* @throws NullPointerException If <code>separator</code> is
|
||||
* <code>null</code>.
|
||||
* @throws IllegalArgumentException If <code>separator</code> is not one
|
||||
* of "<code>\n</code>", "<code>\r\n</code>" or "<code>\r</code>".
|
||||
* @see #getLineSeparator()
|
||||
*/
|
||||
public void setLineSeparator(String separator, boolean setDirty) {
|
||||
if (separator==null) {
|
||||
throw new NullPointerException("terminator cannot be null");
|
||||
}
|
||||
if (!"\r\n".equals(separator) && !"\n".equals(separator) &&
|
||||
!"\r".equals(separator)) {
|
||||
throw new IllegalArgumentException("Invalid line terminator");
|
||||
}
|
||||
Document doc = getDocument();
|
||||
Object old = doc.getProperty(
|
||||
RTextAreaEditorKit.EndOfLineStringProperty);
|
||||
if (!separator.equals(old)) {
|
||||
doc.putProperty(RTextAreaEditorKit.EndOfLineStringProperty,
|
||||
separator);
|
||||
if (setDirty) {
|
||||
setDirty(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets whether or not this text area should be treated as read-only.
|
||||
* This fires a property change event of type {@link #READ_ONLY_PROPERTY}.
|
||||
*
|
||||
* @param readOnly Whether or not the document is read-only.
|
||||
* @see #isReadOnly()
|
||||
*/
|
||||
public void setReadOnly(boolean readOnly) {
|
||||
if (this.readOnly!=readOnly) {
|
||||
this.readOnly = readOnly;
|
||||
firePropertyChange(READ_ONLY_PROPERTY, !readOnly, readOnly);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Syncs this text area's "last saved or loaded" time to that of the file
|
||||
* being edited, if that file is local and exists. If the file is
|
||||
* remote or is local but does not yet exist, nothing happens.<p>
|
||||
*
|
||||
* You normally do not have to call this method, as the "last saved or
|
||||
* loaded" time for {@link TextEditorPane}s is kept up-to-date internally
|
||||
* during such operations as {@link #save()}, {@link #reload()}, etc.
|
||||
*
|
||||
* @see #getLastSaveOrLoadTime()
|
||||
* @see #isModifiedOutsideEditor()
|
||||
*/
|
||||
public void syncLastSaveOrLoadTimeToActualFile() {
|
||||
if (loc.isLocalAndExists()) {
|
||||
lastSaveOrLoadTime = loc.getActualLastModified();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
try {
|
||||
TextEditorPane textArea = new TextEditorPane();
|
||||
textArea.load(FileLocation.create("d:/temp/test.txt"), "UTF-8");
|
||||
JPanel cp = new JPanel();
|
||||
cp.setPreferredSize(new java.awt.Dimension(300, 300));
|
||||
cp.setLayout(new java.awt.BorderLayout());
|
||||
cp.add(new JScrollPane(textArea));
|
||||
JFrame frame = new JFrame();
|
||||
frame.setContentPane(cp);
|
||||
frame.pack();
|
||||
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||
frame.setLocationByPlatform(true);
|
||||
frame.setVisible(true);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,894 @@
|
||||
/*
|
||||
* 10/30/2011
|
||||
*
|
||||
* Theme.java - A color theme for RSyntaxTextArea.
|
||||
*
|
||||
* This library is distributed under a modified BSD license. See the included
|
||||
* RSyntaxTextArea.License.txt file for details.
|
||||
*/
|
||||
package org.fife.ui.rsyntaxtextarea;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Font;
|
||||
import java.awt.SystemColor;
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
import javax.swing.UIManager;
|
||||
import javax.swing.plaf.ColorUIResource;
|
||||
import javax.swing.text.StyleContext;
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.SAXParser;
|
||||
import javax.xml.parsers.SAXParserFactory;
|
||||
import javax.xml.transform.OutputKeys;
|
||||
import javax.xml.transform.Transformer;
|
||||
import javax.xml.transform.TransformerFactory;
|
||||
import javax.xml.transform.dom.DOMSource;
|
||||
import javax.xml.transform.stream.StreamResult;
|
||||
|
||||
import org.fife.io.UnicodeWriter;
|
||||
import org.fife.ui.rtextarea.Gutter;
|
||||
import org.fife.ui.rtextarea.RTextArea;
|
||||
import org.w3c.dom.DOMImplementation;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
import org.xml.sax.Attributes;
|
||||
import org.xml.sax.InputSource;
|
||||
import org.xml.sax.SAXException;
|
||||
import org.xml.sax.SAXParseException;
|
||||
import org.xml.sax.XMLReader;
|
||||
import org.xml.sax.helpers.DefaultHandler;
|
||||
|
||||
|
||||
/**
|
||||
* A theme is a set of fonts and colors to use to style RSyntaxTextArea and
|
||||
* RTextScrollPane. Themes are defined in XML files that are validated against
|
||||
* <code>org/fife/ui/rsyntaxtextarea/themes/theme.dtd</code>. This provides
|
||||
* applications and other consumers with an easy way to style RSyntaxTextArea
|
||||
* without having to use the API.<p>
|
||||
*
|
||||
* Sample themes are included in the source tree in the
|
||||
* <code>org.fife.ui.rsyntaxtextarea.themes</code> package, and can be loaded
|
||||
* via <code>getClass().getResourceAsStream(...)</code>.<p>
|
||||
*
|
||||
* All fields are public to facilitate programmatic manipulation, but typically
|
||||
* you won't need to reference any fields directly, rather using the
|
||||
* <code>load()</code>, <code>save()</code>, and <code>apply()</code> methods
|
||||
* for various tasks.<p>
|
||||
*
|
||||
* Note that to save a <code>Theme</code> via {@link #save(OutputStream)},
|
||||
* you must currently create a <code>Theme</code> from a text area wrapped in
|
||||
* an <code>RTextScrollPane</code>, so that the color information for the
|
||||
* gutter can be retrieved.
|
||||
*
|
||||
* @author Robert Futrell
|
||||
* @version 1.0
|
||||
*/
|
||||
@SuppressWarnings({ "checkstyle:visibilitymodifier" })
|
||||
public class Theme {
|
||||
|
||||
public Font baseFont;
|
||||
public Color bgColor;
|
||||
public Color caretColor;
|
||||
public boolean useSelctionFG;
|
||||
public Color selectionFG;
|
||||
public Color selectionBG;
|
||||
public boolean selectionRoundedEdges;
|
||||
public Color currentLineHighlight;
|
||||
public boolean fadeCurrentLineHighlight;
|
||||
public Color marginLineColor;
|
||||
public Color markAllHighlightColor;
|
||||
public Color markOccurrencesColor;
|
||||
public boolean markOccurrencesBorder;
|
||||
public Color matchedBracketFG;
|
||||
public Color matchedBracketBG;
|
||||
public boolean matchedBracketHighlightBoth;
|
||||
public boolean matchedBracketAnimate;
|
||||
public Color hyperlinkFG;
|
||||
public Color[] secondaryLanguages;
|
||||
|
||||
public SyntaxScheme scheme;
|
||||
|
||||
public Color gutterBackgroundColor;
|
||||
public Color gutterBorderColor;
|
||||
public Color activeLineRangeColor;
|
||||
public boolean iconRowHeaderInheritsGutterBG;
|
||||
public Color lineNumberColor;
|
||||
public String lineNumberFont;
|
||||
public int lineNumberFontSize;
|
||||
public Color foldIndicatorFG;
|
||||
public Color foldBG;
|
||||
public Color armedFoldBG;
|
||||
|
||||
|
||||
/**
|
||||
* Private constructor, used when loading from a stream.
|
||||
*
|
||||
* @param baseFont The default font to use for any "base font" properties
|
||||
* not specified in the theme XML. If this is <code>null</code>,
|
||||
* a default monospaced font will be used.
|
||||
*/
|
||||
private Theme(Font baseFont) {
|
||||
// Optional fields that require a default value.
|
||||
this.baseFont = baseFont!=null ? baseFont : RTextArea.getDefaultFont();
|
||||
secondaryLanguages = new Color[3];
|
||||
activeLineRangeColor = Gutter.DEFAULT_ACTIVE_LINE_RANGE_COLOR;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a theme from an RSyntaxTextArea. It should be contained in
|
||||
* an <code>RTextScrollPane</code> to get all gutter color information.
|
||||
*
|
||||
* @param textArea The text area.
|
||||
*/
|
||||
public Theme(RSyntaxTextArea textArea) {
|
||||
|
||||
baseFont = textArea.getFont();
|
||||
bgColor = textArea.getBackground();
|
||||
caretColor = textArea.getCaretColor();
|
||||
useSelctionFG = textArea.getUseSelectedTextColor();
|
||||
selectionFG = textArea.getSelectedTextColor();
|
||||
selectionBG = textArea.getSelectionColor();
|
||||
selectionRoundedEdges = textArea.getRoundedSelectionEdges();
|
||||
currentLineHighlight = textArea.getCurrentLineHighlightColor();
|
||||
fadeCurrentLineHighlight = textArea.getFadeCurrentLineHighlight();
|
||||
marginLineColor = textArea.getMarginLineColor();
|
||||
markAllHighlightColor = textArea.getMarkAllHighlightColor();
|
||||
markOccurrencesColor = textArea.getMarkOccurrencesColor();
|
||||
markOccurrencesBorder = textArea.getPaintMarkOccurrencesBorder();
|
||||
matchedBracketBG = textArea.getMatchedBracketBGColor();
|
||||
matchedBracketFG = textArea.getMatchedBracketBorderColor();
|
||||
matchedBracketHighlightBoth = textArea.getPaintMatchedBracketPair();
|
||||
matchedBracketAnimate = textArea.getAnimateBracketMatching();
|
||||
hyperlinkFG = textArea.getHyperlinkForeground();
|
||||
|
||||
int count = textArea.getSecondaryLanguageCount();
|
||||
secondaryLanguages = new Color[count];
|
||||
for (int i=0; i<count; i++) {
|
||||
secondaryLanguages[i]= textArea.getSecondaryLanguageBackground(i+1);
|
||||
}
|
||||
|
||||
scheme = textArea.getSyntaxScheme();
|
||||
|
||||
Gutter gutter = RSyntaxUtilities.getGutter(textArea);
|
||||
if (gutter!=null) {
|
||||
gutterBackgroundColor = gutter.getBackground();
|
||||
gutterBorderColor = gutter.getBorderColor();
|
||||
activeLineRangeColor = gutter.getActiveLineRangeColor();
|
||||
iconRowHeaderInheritsGutterBG = gutter.getIconRowHeaderInheritsGutterBackground();
|
||||
lineNumberColor = gutter.getLineNumberColor();
|
||||
lineNumberFont = gutter.getLineNumberFont().getFamily();
|
||||
lineNumberFontSize = gutter.getLineNumberFont().getSize();
|
||||
foldIndicatorFG = gutter.getFoldIndicatorForeground();
|
||||
foldBG = gutter.getFoldBackground();
|
||||
armedFoldBG = gutter.getArmedFoldBackground();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Applies this theme to a text area.
|
||||
*
|
||||
* @param textArea The text area to apply this theme to.
|
||||
*/
|
||||
public void apply(RSyntaxTextArea textArea) {
|
||||
|
||||
textArea.setFont(baseFont);
|
||||
textArea.setBackground(bgColor);
|
||||
textArea.setCaretColor(caretColor);
|
||||
textArea.setUseSelectedTextColor(useSelctionFG);
|
||||
textArea.setSelectedTextColor(selectionFG);
|
||||
textArea.setSelectionColor(selectionBG);
|
||||
textArea.setRoundedSelectionEdges(selectionRoundedEdges);
|
||||
textArea.setCurrentLineHighlightColor(currentLineHighlight);
|
||||
textArea.setFadeCurrentLineHighlight(fadeCurrentLineHighlight);
|
||||
textArea.setMarginLineColor(marginLineColor);
|
||||
textArea.setMarkAllHighlightColor(markAllHighlightColor);
|
||||
textArea.setMarkOccurrencesColor(markOccurrencesColor);
|
||||
textArea.setPaintMarkOccurrencesBorder(markOccurrencesBorder);
|
||||
textArea.setMatchedBracketBGColor(matchedBracketBG);
|
||||
textArea.setMatchedBracketBorderColor(matchedBracketFG);
|
||||
textArea.setPaintMatchedBracketPair(matchedBracketHighlightBoth);
|
||||
textArea.setAnimateBracketMatching(matchedBracketAnimate);
|
||||
textArea.setHyperlinkForeground(hyperlinkFG);
|
||||
|
||||
int count = secondaryLanguages.length;
|
||||
for (int i=0; i<count; i++) {
|
||||
textArea.setSecondaryLanguageBackground(i+1, secondaryLanguages[i]);
|
||||
}
|
||||
|
||||
textArea.setSyntaxScheme(scheme);
|
||||
|
||||
Gutter gutter = RSyntaxUtilities.getGutter(textArea);
|
||||
if (gutter!=null) {
|
||||
gutter.setBackground(gutterBackgroundColor);
|
||||
gutter.setBorderColor(gutterBorderColor);
|
||||
gutter.setActiveLineRangeColor(activeLineRangeColor);
|
||||
gutter.setIconRowHeaderInheritsGutterBackground(iconRowHeaderInheritsGutterBG);
|
||||
gutter.setLineNumberColor(lineNumberColor);
|
||||
String fontName = lineNumberFont!=null ? lineNumberFont :
|
||||
baseFont.getFamily();
|
||||
int fontSize = lineNumberFontSize>0 ? lineNumberFontSize :
|
||||
baseFont.getSize();
|
||||
Font font = getFont(fontName, Font.PLAIN, fontSize);
|
||||
gutter.setLineNumberFont(font);
|
||||
gutter.setFoldIndicatorForeground(foldIndicatorFG);
|
||||
gutter.setFoldBackground(foldBG);
|
||||
gutter.setArmedFoldBackground(armedFoldBG);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
private static String colorToString(Color c) {
|
||||
int color = c.getRGB() & 0xffffff;
|
||||
StringBuilder stringBuilder = new StringBuilder(Integer.toHexString(color));
|
||||
while (stringBuilder.length()<6) {
|
||||
stringBuilder.insert(0, "0");
|
||||
}
|
||||
return stringBuilder.toString();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the default selection background color to use if "default" is
|
||||
* specified in a theme.
|
||||
*
|
||||
* @return The default selection background to use.
|
||||
* @see #getDefaultSelectionFG()
|
||||
*/
|
||||
private static Color getDefaultBG() {
|
||||
Color c = UIManager.getColor("nimbusLightBackground");
|
||||
if (c==null) {
|
||||
// Don't search for "text", as Nimbus defines that as the text
|
||||
// component "text" color, but Basic LAFs use it for text
|
||||
// component backgrounds! Nimbus also defines TextArea.background
|
||||
// as too dark, not what it actually uses for text area backgrounds
|
||||
c = UIManager.getColor("TextArea.background");
|
||||
if (c==null) {
|
||||
c = new ColorUIResource(SystemColor.text);
|
||||
}
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the default selection background color to use if "default" is
|
||||
* specified in a theme.
|
||||
*
|
||||
* @return The default selection background to use.
|
||||
* @see #getDefaultSelectionFG()
|
||||
*/
|
||||
private static Color getDefaultSelectionBG() {
|
||||
Color c = UIManager.getColor("TextArea.selectionBackground");
|
||||
if (c==null) {
|
||||
c = UIManager.getColor("textHighlight");
|
||||
if (c==null) {
|
||||
c = UIManager.getColor("nimbusSelectionBackground");
|
||||
if (c==null) {
|
||||
c = new ColorUIResource(SystemColor.textHighlight);
|
||||
}
|
||||
}
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the default "selected text" color to use if "default" is
|
||||
* specified in a theme.
|
||||
*
|
||||
* @return The default selection foreground color to use.
|
||||
* @see #getDefaultSelectionBG()
|
||||
*/
|
||||
private static Color getDefaultSelectionFG() {
|
||||
Color c = UIManager.getColor("TextArea.selectionForeground");
|
||||
if (c==null) {
|
||||
c = UIManager.getColor("textHighlightText");
|
||||
if (c==null) {
|
||||
c = UIManager.getColor("nimbusSelectedText");
|
||||
if (c==null) {
|
||||
c = new ColorUIResource(SystemColor.textHighlightText);
|
||||
}
|
||||
}
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the specified font.
|
||||
*
|
||||
* @param family The font family.
|
||||
* @param style The style of font.
|
||||
* @param size The size of the font.
|
||||
* @return The font.
|
||||
*/
|
||||
private static Font getFont(String family, int style, int size) {
|
||||
// Use StyleContext to get a composite font for Asian glyphs.
|
||||
StyleContext sc = StyleContext.getDefaultStyleContext();
|
||||
return sc.getFont(family, style, size);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Loads a theme.
|
||||
*
|
||||
* @param in The input stream to read from. This will be closed when this
|
||||
* method returns.
|
||||
* @return The theme.
|
||||
* @throws IOException If an IO error occurs.
|
||||
* @see #save(OutputStream)
|
||||
*/
|
||||
public static Theme load(InputStream in) throws IOException {
|
||||
return load(in, null);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Loads a theme.
|
||||
*
|
||||
* @param in The input stream to read from. This will be closed when this
|
||||
* method returns.
|
||||
* @param baseFont The default font to use for any "base font" properties
|
||||
* not specified in the theme XML. If this is <code>null</code>,
|
||||
* a default monospaced font will be used.
|
||||
* @return The theme.
|
||||
* @throws IOException If an IO error occurs.
|
||||
* @see #save(OutputStream)
|
||||
*/
|
||||
public static Theme load(InputStream in, Font baseFont) throws IOException {
|
||||
|
||||
Theme theme = new Theme(baseFont);
|
||||
|
||||
BufferedInputStream bin = new BufferedInputStream(in);
|
||||
try {
|
||||
XmlHandler.load(theme, bin);
|
||||
} finally {
|
||||
bin.close();
|
||||
}
|
||||
|
||||
return theme;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Saves this theme to an output stream.
|
||||
*
|
||||
* @param out The output stream to write to.
|
||||
* @throws IOException If an IO error occurs.
|
||||
* @see #load(InputStream)
|
||||
*/
|
||||
public void save(OutputStream out) throws IOException {
|
||||
|
||||
BufferedOutputStream bout = new BufferedOutputStream(out);
|
||||
try {
|
||||
|
||||
DocumentBuilder db = DocumentBuilderFactory.newInstance().
|
||||
newDocumentBuilder();
|
||||
DOMImplementation impl = db.getDOMImplementation();
|
||||
|
||||
Document doc = impl.createDocument(null, "RSyntaxTheme", null);
|
||||
Element root = doc.getDocumentElement();
|
||||
root.setAttribute("version", "1.0");
|
||||
|
||||
Element elem = doc.createElement("baseFont");
|
||||
if (!baseFont.getFamily().equals(
|
||||
RSyntaxTextArea.getDefaultFont().getFamily())) {
|
||||
elem.setAttribute("family", baseFont.getFamily());
|
||||
}
|
||||
elem.setAttribute("size", Integer.toString(baseFont.getSize()));
|
||||
root.appendChild(elem);
|
||||
|
||||
elem = doc.createElement("background");
|
||||
elem.setAttribute("color", colorToString(bgColor));
|
||||
root.appendChild(elem);
|
||||
|
||||
elem = doc.createElement("caret");
|
||||
elem.setAttribute("color", colorToString(caretColor));
|
||||
root.appendChild(elem);
|
||||
|
||||
elem = doc.createElement("selection");
|
||||
elem.setAttribute("useFG", Boolean.toString(useSelctionFG));
|
||||
elem.setAttribute("fg", colorToString(selectionFG));
|
||||
elem.setAttribute("bg", colorToString(selectionBG));
|
||||
elem.setAttribute("roundedEdges", Boolean.toString(selectionRoundedEdges));
|
||||
root.appendChild(elem);
|
||||
|
||||
elem = doc.createElement("currentLineHighlight");
|
||||
elem.setAttribute("color", colorToString(currentLineHighlight));
|
||||
elem.setAttribute("fade", Boolean.toString(fadeCurrentLineHighlight));
|
||||
root.appendChild(elem);
|
||||
|
||||
elem = doc.createElement("marginLine");
|
||||
elem.setAttribute("fg", colorToString(marginLineColor));
|
||||
root.appendChild(elem);
|
||||
|
||||
elem = doc.createElement("markAllHighlight");
|
||||
elem.setAttribute("color", colorToString(markAllHighlightColor));
|
||||
root.appendChild(elem);
|
||||
|
||||
elem = doc.createElement("markOccurrencesHighlight");
|
||||
elem.setAttribute("color", colorToString(markOccurrencesColor));
|
||||
elem.setAttribute("border", Boolean.toString(markOccurrencesBorder));
|
||||
root.appendChild(elem);
|
||||
|
||||
elem = doc.createElement("matchedBracket");
|
||||
elem.setAttribute("fg", colorToString(matchedBracketFG));
|
||||
elem.setAttribute("bg", colorToString(matchedBracketBG));
|
||||
elem.setAttribute("highlightBoth", Boolean.toString(matchedBracketHighlightBoth));
|
||||
elem.setAttribute("animate", Boolean.toString(matchedBracketAnimate));
|
||||
root.appendChild(elem);
|
||||
|
||||
elem = doc.createElement("hyperlinks");
|
||||
elem.setAttribute("fg", colorToString(hyperlinkFG));
|
||||
root.appendChild(elem);
|
||||
|
||||
elem = doc.createElement("secondaryLanguages");
|
||||
for (int i=0; i<secondaryLanguages.length; i++) {
|
||||
Color color = secondaryLanguages[i];
|
||||
Element elem2 = doc.createElement("language");
|
||||
elem2.setAttribute("index", Integer.toString(i+1));
|
||||
elem2.setAttribute("bg", color==null ? "":colorToString(color));
|
||||
elem.appendChild(elem2);
|
||||
}
|
||||
root.appendChild(elem);
|
||||
|
||||
elem = doc.createElement("gutterBackground");
|
||||
elem.setAttribute("color", colorToString(gutterBackgroundColor));
|
||||
root.appendChild(elem);
|
||||
|
||||
elem = doc.createElement("gutterBorder");
|
||||
elem.setAttribute("color", colorToString(gutterBorderColor));
|
||||
root.appendChild(elem);
|
||||
|
||||
elem = doc.createElement("lineNumbers");
|
||||
elem.setAttribute("fg", colorToString(lineNumberColor));
|
||||
if (lineNumberFont!=null) {
|
||||
elem.setAttribute("fontFamily", lineNumberFont);
|
||||
}
|
||||
if (lineNumberFontSize>0) {
|
||||
elem.setAttribute("fontSize",
|
||||
Integer.toString(lineNumberFontSize));
|
||||
}
|
||||
root.appendChild(elem);
|
||||
|
||||
elem = doc.createElement("foldIndicator");
|
||||
elem.setAttribute("fg", colorToString(foldIndicatorFG));
|
||||
elem.setAttribute("iconBg", colorToString(foldBG));
|
||||
elem.setAttribute("iconArmedBg", colorToString(armedFoldBG));
|
||||
root.appendChild(elem);
|
||||
|
||||
elem = doc.createElement("iconRowHeader");
|
||||
elem.setAttribute("activeLineRange", colorToString(activeLineRangeColor));
|
||||
elem.setAttribute("inheritsGutterBG", Boolean.toString(iconRowHeaderInheritsGutterBG));
|
||||
root.appendChild(elem);
|
||||
|
||||
elem = doc.createElement("tokenStyles");
|
||||
Field[] fields = TokenTypes.class.getFields();
|
||||
for (int i=0; i<fields.length; i++) {
|
||||
Field field = fields[i];
|
||||
int value = field.getInt(null);
|
||||
if (value!=TokenTypes.DEFAULT_NUM_TOKEN_TYPES) {
|
||||
Style style = scheme.getStyle(value);
|
||||
if (style!=null) {
|
||||
Element elem2 = doc.createElement("style");
|
||||
elem2.setAttribute("token", field.getName());
|
||||
Color fg = style.foreground;
|
||||
if (fg!=null) {
|
||||
elem2.setAttribute("fg", colorToString(fg));
|
||||
}
|
||||
Color bg = style.background;
|
||||
if (bg!=null) {
|
||||
elem2.setAttribute("bg", colorToString(bg));
|
||||
}
|
||||
Font font = style.font;
|
||||
if (font!=null) {
|
||||
if (!font.getFamily().equals(
|
||||
baseFont.getFamily())) {
|
||||
elem2.setAttribute("fontFamily", font.getFamily());
|
||||
}
|
||||
if (font.getSize()!=baseFont.getSize()) {
|
||||
elem2.setAttribute("fontSize", Integer.toString(font.getSize()));
|
||||
}
|
||||
if (font.isBold()) {
|
||||
elem2.setAttribute("bold", "true");
|
||||
}
|
||||
if (font.isItalic()) {
|
||||
elem2.setAttribute("italic", "true");
|
||||
}
|
||||
}
|
||||
if (style.underline) {
|
||||
elem2.setAttribute("underline", "true");
|
||||
}
|
||||
elem.appendChild(elem2);
|
||||
}
|
||||
}
|
||||
}
|
||||
root.appendChild(elem);
|
||||
|
||||
DOMSource source = new DOMSource(doc);
|
||||
// Use a writer instead of OutputStream to allow pretty printing.
|
||||
// See http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6337981
|
||||
StreamResult result = new StreamResult(new PrintWriter(
|
||||
new UnicodeWriter(bout, "UTF-8")));
|
||||
TransformerFactory transFac = TransformerFactory.newInstance();
|
||||
Transformer transformer = transFac.newTransformer();
|
||||
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
|
||||
transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
|
||||
transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
|
||||
transformer.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, "theme.dtd");
|
||||
transformer.transform(source, result);
|
||||
|
||||
} catch (RuntimeException re) {
|
||||
throw re; // FindBugs
|
||||
} catch (Exception e) {
|
||||
throw new IOException("Error generating XML: " + e.getMessage(), e);
|
||||
} finally {
|
||||
bout.close();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the color represented by a string. The input is expected to
|
||||
* be a 6-digit hex string, optionally prefixed by a '$'. For example,
|
||||
* either of the following:
|
||||
* <pre>
|
||||
* "$00ff00"
|
||||
* "00ff00"
|
||||
* </pre>
|
||||
* will return <code>new Color(0, 255, 0)</code>.
|
||||
*
|
||||
* @param s The string to evaluate.
|
||||
* @return The color.
|
||||
*/
|
||||
private static Color stringToColor(String s) {
|
||||
return stringToColor(s, null);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the color represented by a string. The input is expected to
|
||||
* be a 6-digit hex string, optionally prefixed by a '$'. For example,
|
||||
* either of the following:
|
||||
* <pre>
|
||||
* "$00ff00"
|
||||
* "00ff00"
|
||||
* </pre>
|
||||
* will return <code>new Color(0, 255, 0)</code>.
|
||||
*
|
||||
* @param s The string to evaluate.
|
||||
* @param defaultVal The color to use if <code>s</code> is
|
||||
* "<code>default</code>".
|
||||
* @return The color.
|
||||
*/
|
||||
private static Color stringToColor(String s, Color defaultVal) {
|
||||
if (s==null || "default".equalsIgnoreCase(s)) {
|
||||
return defaultVal;
|
||||
}
|
||||
if (s.length()==6 || s.length()==7) {
|
||||
if (s.charAt(0)=='$') {
|
||||
s = s.substring(1);
|
||||
}
|
||||
return new Color(Integer.parseInt(s, 16));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Loads a <code>SyntaxScheme</code> from an XML file.
|
||||
*/
|
||||
private static class XmlHandler extends DefaultHandler {
|
||||
|
||||
private Theme theme;
|
||||
|
||||
@Override
|
||||
public void error(SAXParseException e) throws SAXException {
|
||||
throw e;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fatalError(SAXParseException e) throws SAXException {
|
||||
throw e;
|
||||
}
|
||||
|
||||
public static void load(Theme theme, InputStream in) throws IOException {
|
||||
SAXParserFactory spf = SAXParserFactory.newInstance();
|
||||
spf.setValidating(true);
|
||||
try {
|
||||
SAXParser parser = spf.newSAXParser();
|
||||
XMLReader reader = parser.getXMLReader();
|
||||
XmlHandler handler = new XmlHandler();
|
||||
handler.theme = theme;
|
||||
reader.setEntityResolver(handler);
|
||||
reader.setContentHandler(handler);
|
||||
reader.setDTDHandler(handler);
|
||||
reader.setErrorHandler(handler);
|
||||
InputSource is = new InputSource(in);
|
||||
is.setEncoding("UTF-8");
|
||||
reader.parse(is);
|
||||
} catch (/*SAX|ParserConfiguration*/Exception se) {
|
||||
throw new IOException(se.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private static int parseInt(Attributes attrs, String attr,
|
||||
int def) {
|
||||
int value = def;
|
||||
String temp = attrs.getValue(attr);
|
||||
if (temp != null) {
|
||||
try {
|
||||
value = Integer.parseInt(temp);
|
||||
} catch (NumberFormatException nfe) {
|
||||
nfe.printStackTrace();
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputSource resolveEntity(String publicID,
|
||||
String systemID) throws SAXException {
|
||||
return new InputSource(getClass().
|
||||
getResourceAsStream("themes/theme.dtd"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startElement(String uri, String localName, String qName,
|
||||
Attributes attrs) {
|
||||
|
||||
if ("background".equals(qName)) {
|
||||
|
||||
String color = attrs.getValue("color");
|
||||
if (color!=null) {
|
||||
theme.bgColor = stringToColor(color, getDefaultBG());
|
||||
theme.gutterBackgroundColor = theme.bgColor;
|
||||
}
|
||||
else {
|
||||
String img = attrs.getValue("image");
|
||||
if (img!=null) {
|
||||
throw new IllegalArgumentException("Not yet implemented");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The base font to use in the editor.
|
||||
else if ("baseFont".equals(qName)) {
|
||||
int size = theme.baseFont.getSize();
|
||||
String sizeStr = attrs.getValue("size");
|
||||
if (sizeStr!=null) {
|
||||
size = Integer.parseInt(sizeStr);
|
||||
}
|
||||
String family = attrs.getValue("family");
|
||||
if (family!=null) {
|
||||
theme.baseFont = getFont(family, Font.PLAIN, size);
|
||||
}
|
||||
else if (sizeStr!=null) {
|
||||
// No family specified, keep original family
|
||||
theme.baseFont = theme.baseFont.deriveFont(size*1f);
|
||||
}
|
||||
}
|
||||
|
||||
else if ("caret".equals(qName)) {
|
||||
String color = attrs.getValue("color");
|
||||
theme.caretColor = stringToColor(color);
|
||||
}
|
||||
|
||||
else if ("currentLineHighlight".equals(qName)) {
|
||||
String color = attrs.getValue("color");
|
||||
theme.currentLineHighlight = stringToColor(color);
|
||||
String fadeStr = attrs.getValue("fade");
|
||||
boolean fade = Boolean.parseBoolean(fadeStr);
|
||||
theme.fadeCurrentLineHighlight = fade;
|
||||
}
|
||||
|
||||
else if ("foldIndicator".equals(qName)) {
|
||||
String color = attrs.getValue("fg");
|
||||
theme.foldIndicatorFG = stringToColor(color);
|
||||
color = attrs.getValue("iconBg");
|
||||
theme.foldBG = stringToColor(color);
|
||||
color = attrs.getValue("iconArmedBg");
|
||||
theme.armedFoldBG = stringToColor(color);
|
||||
}
|
||||
|
||||
else if ("gutterBackground".equals(qName)) {
|
||||
String color = attrs.getValue("color");
|
||||
if (color!=null) {
|
||||
theme.gutterBackgroundColor = stringToColor(color);
|
||||
}
|
||||
}
|
||||
|
||||
else if ("gutterBorder".equals(qName)) {
|
||||
String color = attrs.getValue("color");
|
||||
theme.gutterBorderColor = stringToColor(color);
|
||||
}
|
||||
|
||||
else if ("iconRowHeader".equals(qName)) {
|
||||
String color = attrs.getValue("activeLineRange");
|
||||
theme.activeLineRangeColor = stringToColor(color);
|
||||
String inheritBGStr = attrs.getValue("inheritsGutterBG");
|
||||
theme.iconRowHeaderInheritsGutterBG =
|
||||
inheritBGStr==null ? false : Boolean.valueOf(inheritBGStr);
|
||||
}
|
||||
|
||||
else if ("lineNumbers".equals(qName)) {
|
||||
String color = attrs.getValue("fg");
|
||||
theme.lineNumberColor = stringToColor(color);
|
||||
theme.lineNumberFont = attrs.getValue("fontFamily");
|
||||
theme.lineNumberFontSize = parseInt(attrs, "fontSize", -1);
|
||||
}
|
||||
|
||||
else if ("marginLine".equals(qName)) {
|
||||
String color = attrs.getValue("fg");
|
||||
theme.marginLineColor = stringToColor(color);
|
||||
}
|
||||
|
||||
else if ("markAllHighlight".equals(qName)) {
|
||||
String color = attrs.getValue("color");
|
||||
theme.markAllHighlightColor = stringToColor(color);
|
||||
}
|
||||
|
||||
else if ("markOccurrencesHighlight".equals(qName)) {
|
||||
String color = attrs.getValue("color");
|
||||
theme.markOccurrencesColor = stringToColor(color);
|
||||
String border = attrs.getValue("border");
|
||||
theme.markOccurrencesBorder = Boolean.parseBoolean(border);
|
||||
}
|
||||
|
||||
else if ("matchedBracket".equals(qName)) {
|
||||
String fg = attrs.getValue("fg");
|
||||
theme.matchedBracketFG = stringToColor(fg);
|
||||
String bg = attrs.getValue("bg");
|
||||
theme.matchedBracketBG = stringToColor(bg);
|
||||
String highlightBoth = attrs.getValue("highlightBoth");
|
||||
theme.matchedBracketHighlightBoth = Boolean.parseBoolean(highlightBoth);
|
||||
String animate = attrs.getValue("animate");
|
||||
theme.matchedBracketAnimate = Boolean.parseBoolean(animate);
|
||||
}
|
||||
|
||||
else if ("hyperlinks".equals(qName)) {
|
||||
String fg = attrs.getValue("fg");
|
||||
theme.hyperlinkFG = stringToColor(fg);
|
||||
}
|
||||
|
||||
else if ("language".equals(qName)) {
|
||||
String indexStr = attrs.getValue("index");
|
||||
int index = Integer.parseInt(indexStr) - 1;
|
||||
if (theme.secondaryLanguages.length>index) { // Sanity
|
||||
Color bg = stringToColor(attrs.getValue("bg"));
|
||||
theme.secondaryLanguages[index] = bg;
|
||||
}
|
||||
}
|
||||
|
||||
else if ("selection".equals(qName)) {
|
||||
String useStr = attrs.getValue("useFG");
|
||||
theme.useSelctionFG = Boolean.parseBoolean(useStr);
|
||||
String color = attrs.getValue("fg");
|
||||
theme.selectionFG = stringToColor(color,
|
||||
getDefaultSelectionFG());
|
||||
//System.out.println(theme.selectionFG);
|
||||
color = attrs.getValue("bg");
|
||||
theme.selectionBG = stringToColor(color,
|
||||
getDefaultSelectionBG());
|
||||
String roundedStr = attrs.getValue("roundedEdges");
|
||||
theme.selectionRoundedEdges = Boolean.parseBoolean(roundedStr);
|
||||
}
|
||||
|
||||
// Start of the syntax scheme definition
|
||||
else if ("tokenStyles".equals(qName)) {
|
||||
theme.scheme = new SyntaxScheme(theme.baseFont, false);
|
||||
}
|
||||
|
||||
// A style in the syntax scheme
|
||||
else if ("style".equals(qName)) {
|
||||
|
||||
String type = attrs.getValue("token");
|
||||
Field field = null;
|
||||
try {
|
||||
field = Token.class.getField(type);
|
||||
} catch (RuntimeException re) {
|
||||
throw re; // FindBugs
|
||||
} catch (Exception e) {
|
||||
System.err.println("Invalid token type: " + type);
|
||||
return;
|
||||
}
|
||||
|
||||
if (field.getType()==int.class) {
|
||||
|
||||
int index = 0;
|
||||
try {
|
||||
index = field.getInt(theme.scheme);
|
||||
} catch (IllegalArgumentException e) {
|
||||
e.printStackTrace();
|
||||
return;
|
||||
} catch (IllegalAccessException e) {
|
||||
e.printStackTrace();
|
||||
return;
|
||||
}
|
||||
|
||||
String fgStr = attrs.getValue("fg");
|
||||
Color fg = stringToColor(fgStr);
|
||||
theme.scheme.getStyle(index).foreground = fg;
|
||||
|
||||
String bgStr = attrs.getValue("bg");
|
||||
Color bg = stringToColor(bgStr);
|
||||
theme.scheme.getStyle(index).background = bg;
|
||||
|
||||
Font font = theme.baseFont;
|
||||
String familyName = attrs.getValue("fontFamily");
|
||||
if (familyName!=null) {
|
||||
font = getFont(familyName, font.getStyle(),
|
||||
font.getSize());
|
||||
}
|
||||
String sizeStr = attrs.getValue("fontSize");
|
||||
if (sizeStr!=null) {
|
||||
try {
|
||||
float size = Float.parseFloat(sizeStr);
|
||||
size = Math.max(size, 1f);
|
||||
font = font.deriveFont(size);
|
||||
} catch (NumberFormatException nfe) {
|
||||
nfe.printStackTrace();
|
||||
}
|
||||
}
|
||||
theme.scheme.getStyle(index).font = font;
|
||||
|
||||
boolean styleSpecified = false;
|
||||
boolean bold = false;
|
||||
boolean italic = false;
|
||||
String boldStr = attrs.getValue("bold");
|
||||
if (boldStr!=null) {
|
||||
bold = Boolean.parseBoolean(boldStr);
|
||||
styleSpecified = true;
|
||||
}
|
||||
String italicStr = attrs.getValue("italic");
|
||||
if (italicStr!=null) {
|
||||
italic = Boolean.parseBoolean(italicStr);
|
||||
styleSpecified = true;
|
||||
}
|
||||
if (styleSpecified) {
|
||||
int style = 0;
|
||||
if (bold) {
|
||||
style |= Font.BOLD;
|
||||
}
|
||||
if (italic) {
|
||||
style |= Font.ITALIC;
|
||||
}
|
||||
Font orig = theme.scheme.getStyle(index).font;
|
||||
theme.scheme.getStyle(index).font =
|
||||
orig.deriveFont(style);
|
||||
}
|
||||
|
||||
String ulineStr = attrs.getValue("underline");
|
||||
if (ulineStr!=null) {
|
||||
boolean uline= Boolean.parseBoolean(ulineStr);
|
||||
theme.scheme.getStyle(index).underline = uline;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warning(SAXParseException e) throws SAXException {
|
||||
throw e;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,567 @@
|
||||
/*
|
||||
* 02/21/2004
|
||||
*
|
||||
* Token.java - A token used in syntax highlighting.
|
||||
*
|
||||
* 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.Rectangle;
|
||||
|
||||
import javax.swing.text.TabExpander;
|
||||
|
||||
|
||||
/**
|
||||
* A generic token that functions as a node in a linked list of syntax
|
||||
* highlighted tokens for some language.<p>
|
||||
*
|
||||
* A <code>Token</code> is a piece of text representing some logical token in
|
||||
* source code for a programming language. For example, the line of C code:<p>
|
||||
* <pre>
|
||||
* int i = 0;
|
||||
* </pre>
|
||||
* would be broken into 8 <code>Token</code>s: the first representing
|
||||
* <code>int</code>, the second whitespace, the third <code>i</code>, the fourth
|
||||
* whitespace, the fifth <code>=</code>, etc.<p>
|
||||
*
|
||||
* <b>Note:</b> The tokens returned by {@link RSyntaxDocument}s are pooled and
|
||||
* should always be treated as immutable. Modifying tokens you did not create
|
||||
* yourself can and will result in rendering issues and/or runtime exceptions.
|
||||
* You have been warned!
|
||||
*
|
||||
* @author Robert Futrell
|
||||
* @version 0.3
|
||||
*/
|
||||
public interface Token extends TokenTypes {
|
||||
|
||||
/**
|
||||
* Appends HTML code for painting this token, using the given text area's
|
||||
* color scheme.
|
||||
*
|
||||
* @param sb The buffer to append to.
|
||||
* @param textArea The text area whose color scheme to use.
|
||||
* @param fontFamily Whether to include the font family in the HTML for
|
||||
* this token. You can pass <code>false</code> for this parameter
|
||||
* if, for example, you are making all your HTML be monospaced,
|
||||
* and don't want any crazy fonts being used in the editor to be
|
||||
* reflected in your HTML.
|
||||
* @return The buffer appended to.
|
||||
* @see #getHTMLRepresentation(RSyntaxTextArea)
|
||||
*/
|
||||
StringBuilder appendHTMLRepresentation(StringBuilder sb,
|
||||
RSyntaxTextArea textArea,
|
||||
boolean fontFamily);
|
||||
|
||||
|
||||
/**
|
||||
* Appends HTML code for painting this token, using the given text area's
|
||||
* color scheme.
|
||||
*
|
||||
* @param sb The buffer to append to.
|
||||
* @param textArea The text area whose color scheme to use.
|
||||
* @param fontFamily Whether to include the font family in the HTML for
|
||||
* this token. You can pass <code>false</code> for this parameter
|
||||
* if, for example, you are making all your HTML be monospaced,
|
||||
* and don't want any crazy fonts being used in the editor to be
|
||||
* reflected in your HTML.
|
||||
* @param tabsToSpaces Whether to convert tabs into spaces.
|
||||
* @return The buffer appended to.
|
||||
* @see #getHTMLRepresentation(RSyntaxTextArea)
|
||||
*/
|
||||
StringBuilder appendHTMLRepresentation(StringBuilder sb,
|
||||
RSyntaxTextArea textArea, boolean fontFamily,
|
||||
boolean tabsToSpaces);
|
||||
|
||||
|
||||
/**
|
||||
* Returns the character at the specified offset in the token.
|
||||
*
|
||||
* @param index The index. This should be in the range
|
||||
* <code>0-({@link #length()}-1)</code>.
|
||||
* @return The character.
|
||||
* @see #length()
|
||||
*/
|
||||
char charAt(int index);
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether the token straddles the specified position in the
|
||||
* document.
|
||||
*
|
||||
* @param pos The position in the document to check.
|
||||
* @return Whether the specified position is straddled by this token.
|
||||
*/
|
||||
boolean containsPosition(int pos);
|
||||
|
||||
|
||||
/**
|
||||
* Returns the position in the token's internal char array corresponding
|
||||
* to the specified document position.<p>
|
||||
* Note that this method does NOT do any bounds checking; you can pass in
|
||||
* a document position that does not correspond to a position in this
|
||||
* token, and you will not receive an Exception or any other notification;
|
||||
* it is up to the caller to ensure valid input.
|
||||
*
|
||||
* @param pos A position in the document that is represented by this token.
|
||||
* @return The corresponding token position >= <code>textOffset</code>
|
||||
* and < <code>textOffset+textCount</code>.
|
||||
* @see #tokenToDocument
|
||||
*/
|
||||
int documentToToken(int pos);
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether this token's lexeme ends with the specified characters.
|
||||
*
|
||||
* @param ch The characters.
|
||||
* @return Whether this token's lexeme ends with the specified characters.
|
||||
* @see #startsWith(char[])
|
||||
*/
|
||||
boolean endsWith(char[] ch);
|
||||
|
||||
|
||||
/**
|
||||
* Returns the end offset of this token in the document (exclusive). In
|
||||
* other words, the token ranges from
|
||||
* <code>[getOffset(), getEndOffset())</code>.
|
||||
*
|
||||
* @return The end offset of this token.
|
||||
* @see #getOffset()
|
||||
*/
|
||||
int getEndOffset();
|
||||
|
||||
|
||||
/**
|
||||
* Returns a <code>String</code> containing HTML code for painting this
|
||||
* token, using the given text area's color scheme.
|
||||
*
|
||||
* @param textArea The text area whose color scheme to use.
|
||||
* @return The HTML representation of the token.
|
||||
* @see #appendHTMLRepresentation(StringBuilder, RSyntaxTextArea, boolean)
|
||||
*/
|
||||
String getHTMLRepresentation(RSyntaxTextArea textArea);
|
||||
|
||||
|
||||
/**
|
||||
* Returns the language index of this token.
|
||||
*
|
||||
* @return The language index. A value of <code>0</code> denotes the
|
||||
* "main" language, any positive value denotes a specific secondary
|
||||
* language.
|
||||
* @see #setLanguageIndex(int)
|
||||
*/
|
||||
int getLanguageIndex();
|
||||
|
||||
|
||||
/**
|
||||
* Returns the last token in this list that is not whitespace or a
|
||||
* comment.
|
||||
*
|
||||
* @return The last non-comment, non-whitespace token, or <code>null</code>
|
||||
* if there isn't one.
|
||||
*/
|
||||
Token getLastNonCommentNonWhitespaceToken();
|
||||
|
||||
|
||||
/**
|
||||
* Returns the last paintable token in this token list, or <code>null</code>
|
||||
* if there is no paintable token.
|
||||
*
|
||||
* @return The last paintable token in this token list.
|
||||
*/
|
||||
Token getLastPaintableToken();
|
||||
|
||||
|
||||
/**
|
||||
* Returns the text of this token, as a string.<p>
|
||||
*
|
||||
* Note that this method isn't used much by the
|
||||
* <code>ryntaxtextarea</code> package internally, as it tries to limit
|
||||
* memory allocation.
|
||||
*
|
||||
* @return The text of this token.
|
||||
*/
|
||||
String getLexeme();
|
||||
|
||||
|
||||
/**
|
||||
* Determines the offset into this token list (i.e., into the
|
||||
* document) that covers pixel location <code>x</code> if the token list
|
||||
* starts at pixel location <code>x0</code><p>.
|
||||
* This method will return the document position "closest" to the
|
||||
* x-coordinate (i.e., if they click on the "right-half" of the
|
||||
* <code>w</code> in <code>awe</code>, the caret will be placed in
|
||||
* between the <code>w</code> and <code>e</code>; similarly, clicking on
|
||||
* the left-half places the caret between the <code>a</code> and
|
||||
* <code>w</code>). This makes it useful for methods such as
|
||||
* <code>viewToModel</code> found in <code>javax.swing.text.View</code>
|
||||
* subclasses.
|
||||
*
|
||||
* @param textArea The text area from which the token list was derived.
|
||||
* @param e How to expand tabs.
|
||||
* @param x0 The pixel x-location that is the beginning of
|
||||
* <code>tokenList</code>.
|
||||
* @param x The pixel-position for which you want to get the corresponding
|
||||
* offset.
|
||||
* @return The position (in the document, NOT into the token list!) that
|
||||
* covers the pixel location. If <code>tokenList</code> is
|
||||
* <code>null</code> or has type <code>Token.NULL</code>, then
|
||||
* <code>-1</code> is returned; the caller should recognize this and
|
||||
* return the actual end position of the (empty) line.
|
||||
*/
|
||||
int getListOffset(RSyntaxTextArea textArea, TabExpander e,
|
||||
float x0, float x);
|
||||
|
||||
|
||||
/**
|
||||
* Returns the token after this one in the linked list.
|
||||
*
|
||||
* @return The next token.
|
||||
*/
|
||||
Token getNextToken();
|
||||
|
||||
|
||||
/**
|
||||
* Returns the offset into the document at which this token resides.
|
||||
*
|
||||
* @return The offset into the document.
|
||||
* @see #getEndOffset()
|
||||
*/
|
||||
int getOffset();
|
||||
|
||||
|
||||
/**
|
||||
* Returns the position in the document that represents the last character
|
||||
* in the token that will fit into <code>endBeforeX-startX</code> pixels.
|
||||
* For example, if you're using a monospaced 8-pixel-per-character font,
|
||||
* have the token "while" and <code>startX</code> is <code>0</code> and
|
||||
* <code>endBeforeX</code> is <code>30</code>, this method will return the
|
||||
* document position of the "i" in "while", because the "i" ends at pixel
|
||||
* <code>24</code>, while the "l" ends at <code>32</code>. If not even the
|
||||
* first character fits in <code>endBeforeX-startX</code>, the first
|
||||
* character's position is still returned so calling methods don't go into
|
||||
* infinite loops.
|
||||
*
|
||||
* @param textArea The text area in which this token is being painted.
|
||||
* @param e How to expand tabs.
|
||||
* @param startX The x-coordinate at which the token will be painted. This
|
||||
* is needed because of tabs.
|
||||
* @param endBeforeX The x-coordinate for which you want to find the last
|
||||
* character of <code>t</code> which comes before it.
|
||||
* @return The last document position that will fit in the specified amount
|
||||
* of pixels.
|
||||
*/
|
||||
/*
|
||||
* @see #getTokenListOffsetBeforeX
|
||||
* FIXME: This method does not compute correctly! It needs to be abstract
|
||||
* and implemented by subclasses.
|
||||
*/
|
||||
int getOffsetBeforeX(RSyntaxTextArea textArea, TabExpander e,
|
||||
float startX, float endBeforeX);
|
||||
|
||||
|
||||
/**
|
||||
* Returns the character array backing the lexeme of this token. This
|
||||
* value should be treated as read-only.
|
||||
*
|
||||
* @return A character array containing the lexeme of this token.
|
||||
* @see #getTextOffset()
|
||||
* @see #length()
|
||||
*/
|
||||
char[] getTextArray();
|
||||
|
||||
|
||||
/**
|
||||
* Returns the offset into the character array of the lexeme in
|
||||
* {@link #getTextArray()}.
|
||||
*
|
||||
* @return The offset of the lexeme in the character array.
|
||||
* @see #getTextArray()
|
||||
*/
|
||||
int getTextOffset();
|
||||
|
||||
|
||||
/**
|
||||
* Returns the type of this token.
|
||||
*
|
||||
* @return The type of this token.
|
||||
* @see TokenTypes
|
||||
* @see #setType(int)
|
||||
*/
|
||||
int getType();
|
||||
|
||||
|
||||
/**
|
||||
* Returns the width of this token given the specified parameters.
|
||||
*
|
||||
* @param textArea The text area in which the token is being painted.
|
||||
* @param e Describes how to expand tabs. This parameter cannot be
|
||||
* <code>null</code>.
|
||||
* @param x0 The pixel-location at which the token begins. This is needed
|
||||
* because of tabs.
|
||||
* @return The width of the token, in pixels.
|
||||
* @see #getWidthUpTo
|
||||
*/
|
||||
float getWidth(RSyntaxTextArea textArea, TabExpander e, float x0);
|
||||
|
||||
|
||||
/**
|
||||
* Returns the width of a specified number of characters in this token.
|
||||
* For example, for the token "while", specifying a value of <code>3</code>
|
||||
* here returns the width of the "whi" portion of the token.
|
||||
*
|
||||
* @param numChars The number of characters for which to get the width.
|
||||
* @param textArea The text area in which the token is being painted.
|
||||
* @param e How to expand tabs. This value cannot be <code>null</code>.
|
||||
* @param x0 The pixel-location at which this token begins. This is needed
|
||||
* because of tabs.
|
||||
* @return The width of the specified number of characters in this token.
|
||||
* @see #getWidth
|
||||
*/
|
||||
float getWidthUpTo(int numChars, RSyntaxTextArea textArea,
|
||||
TabExpander e, float x0);
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether this token's lexeme matches a specific character array.
|
||||
*
|
||||
* @param lexeme The lexeme to check for.
|
||||
* @return Whether this token has that lexeme.
|
||||
* @see #is(int, char[])
|
||||
* @see #is(int, String)
|
||||
* @see #isSingleChar(int, char)
|
||||
* @see #startsWith(char[])
|
||||
*/
|
||||
boolean is(char[] lexeme);
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether this token is of the specified type, with the specified
|
||||
* lexeme.<p>
|
||||
* This method is preferred over the other overload in performance-critical
|
||||
* code where this operation may be called frequently, since it does not
|
||||
* involve any String allocations.
|
||||
*
|
||||
* @param type The type to check for.
|
||||
* @param lexeme The lexeme to check for.
|
||||
* @return Whether this token has that type and lexeme.
|
||||
* @see #is(int, String)
|
||||
* @see #is(char[])
|
||||
* @see #isSingleChar(int, char)
|
||||
* @see #startsWith(char[])
|
||||
*/
|
||||
boolean is(int type, char[] lexeme);
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether this token is of the specified type, with the specified
|
||||
* lexeme.<p>
|
||||
* The other overload of this method is preferred over this one in
|
||||
* performance-critical code, as this one involves a String allocation
|
||||
* while the other does not.
|
||||
*
|
||||
* @param type The type to check for.
|
||||
* @param lexeme The lexeme to check for.
|
||||
* @return Whether this token has that type and lexeme.
|
||||
* @see #is(int, char[])
|
||||
* @see #isSingleChar(int, char)
|
||||
* @see #startsWith(char[])
|
||||
*/
|
||||
boolean is(int type, String lexeme);
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether this token is a comment.
|
||||
*
|
||||
* @return Whether this token is a comment.
|
||||
* @see #isWhitespace()
|
||||
* @see #isCommentOrWhitespace()
|
||||
*/
|
||||
boolean isComment();
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether this token is a comment or whitespace.
|
||||
*
|
||||
* @return Whether this token is a comment or whitespace.
|
||||
* @see #isComment()
|
||||
* @see #isWhitespace()
|
||||
*/
|
||||
boolean isCommentOrWhitespace();
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether this token is a hyperlink.
|
||||
*
|
||||
* @return Whether this token is a hyperlink.
|
||||
* @see #setHyperlink(boolean)
|
||||
*/
|
||||
boolean isHyperlink();
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether this token is an identifier.
|
||||
*
|
||||
* @return Whether this token is an identifier.
|
||||
*/
|
||||
boolean isIdentifier();
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether this token is a {@link #SEPARATOR} representing a single
|
||||
* left curly brace.
|
||||
*
|
||||
* @return Whether this token is a left curly brace.
|
||||
* @see #isRightCurly()
|
||||
*/
|
||||
boolean isLeftCurly();
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether this token is a {@link #SEPARATOR} representing a single
|
||||
* right curly brace.
|
||||
*
|
||||
* @return Whether this token is a right curly brace.
|
||||
* @see #isLeftCurly()
|
||||
*/
|
||||
boolean isRightCurly();
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether or not this token is "paintable;" i.e., whether or not
|
||||
* the type of this token is one such that it has an associated syntax
|
||||
* style. What this boils down to is whether the token type is greater
|
||||
* than <code>Token.NULL</code>.
|
||||
*
|
||||
* @return Whether or not this token is paintable.
|
||||
*/
|
||||
boolean isPaintable();
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether this token is the specified single character.
|
||||
*
|
||||
* @param ch The character to check for.
|
||||
* @return Whether this token's lexeme is the single character specified.
|
||||
* @see #isSingleChar(int, char)
|
||||
*/
|
||||
boolean isSingleChar(char ch);
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether this token is the specified single character, and of a
|
||||
* specific type.
|
||||
*
|
||||
* @param type The token type.
|
||||
* @param ch The character to check for.
|
||||
* @return Whether this token is of the specified type, and with a lexeme
|
||||
* Equaling the single character specified.
|
||||
* @see #isSingleChar(char)
|
||||
*/
|
||||
boolean isSingleChar(int type, char ch);
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether or not this token is whitespace.
|
||||
*
|
||||
* @return <code>true</code> iff this token is whitespace.
|
||||
* @see #isComment()
|
||||
* @see #isCommentOrWhitespace()
|
||||
*/
|
||||
boolean isWhitespace();
|
||||
|
||||
|
||||
/**
|
||||
* Returns the length of this token.
|
||||
*
|
||||
* @return The length of this token.
|
||||
* @see #getOffset()
|
||||
*/
|
||||
int length();
|
||||
|
||||
|
||||
/**
|
||||
* Returns the bounding box for the specified document location. The
|
||||
* location must be in the specified token list; if it isn't,
|
||||
* <code>null</code> is returned.
|
||||
*
|
||||
* @param textArea The text area from which the token list was derived.
|
||||
* @param e How to expand tabs.
|
||||
* @param pos The position in the document for which to get the bounding
|
||||
* box in the view.
|
||||
* @param x0 The pixel x-location that is the beginning of
|
||||
* <code>tokenList</code>.
|
||||
* @param rect The rectangle in which we'll be returning the results. This
|
||||
* object is reused to keep from frequent memory allocations.
|
||||
* @return The bounding box for the specified position in the model.
|
||||
*/
|
||||
Rectangle listOffsetToView(RSyntaxTextArea textArea, TabExpander e,
|
||||
int pos, int x0, Rectangle rect);
|
||||
|
||||
|
||||
/**
|
||||
* Sets whether this token is a hyperlink.
|
||||
*
|
||||
* @param hyperlink Whether this token is a hyperlink.
|
||||
* @see #isHyperlink()
|
||||
*/
|
||||
void setHyperlink(boolean hyperlink);
|
||||
|
||||
|
||||
/**
|
||||
* Sets the language index for this token. If this value is positive, it
|
||||
* denotes a specific "secondary" language this token represents (such as
|
||||
* JavaScript code or CSS embedded in an HTML file). If this value is
|
||||
* <code>0</code>, this token is in the "main" language being edited.
|
||||
* Negative values are invalid and treated as <code>0</code>.
|
||||
*
|
||||
* @param languageIndex The new language index. A value of
|
||||
* <code>0</code> denotes the "main" language, any positive value
|
||||
* denotes a specific secondary language. Negative values will
|
||||
* be treated as <code>0</code>.
|
||||
* @see #getLanguageIndex()
|
||||
*/
|
||||
void setLanguageIndex(int languageIndex);
|
||||
|
||||
|
||||
/**
|
||||
* Sets the type of this token.
|
||||
*
|
||||
* @param type The new token type.
|
||||
* @see TokenTypes
|
||||
* @see #getType()
|
||||
*/
|
||||
void setType(int type);
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether this token starts with the specified characters.
|
||||
*
|
||||
* @param chars The characters.
|
||||
* @return Whether this token starts with those characters.
|
||||
* @see #endsWith(char[])
|
||||
* @see #is(int, char[])
|
||||
*/
|
||||
boolean startsWith(char[] chars);
|
||||
|
||||
|
||||
/**
|
||||
* Returns the position in the document corresponding to the specified
|
||||
* position in this token's internal char array (<code>textOffset</code> -
|
||||
* <code>textOffset+textCount-1</code>).<p>
|
||||
* Note that this method does NOT do any bounds checking; you can pass in
|
||||
* an invalid token position, and you will not receive an Exception or any
|
||||
* other indication that the returned document position is invalid. It is
|
||||
* up to the user to ensure valid input.
|
||||
*
|
||||
* @param pos A position in the token's internal char array
|
||||
* (<code>textOffset</code> - <code>textOffset+textCount</code>).
|
||||
* @return The corresponding position in the document.
|
||||
* @see #documentToToken(int)
|
||||
*/
|
||||
int tokenToDocument(int pos);
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* 10/28/2004
|
||||
*
|
||||
* TokenFactory.java - Interface for a class that generates tokens of some type.
|
||||
*
|
||||
* This library is distributed under a modified BSD license. See the included
|
||||
* RSyntaxTextArea.License.txt file for details.
|
||||
*/
|
||||
package org.fife.ui.rsyntaxtextarea;
|
||||
|
||||
import javax.swing.text.Segment;
|
||||
|
||||
|
||||
/**
|
||||
* Interface for a class that generates tokens somehow.
|
||||
*
|
||||
* @author Robert Futrell
|
||||
* @version 0.1
|
||||
*/
|
||||
interface TokenFactory {
|
||||
|
||||
|
||||
/**
|
||||
* Returns a null token.
|
||||
*
|
||||
* @return A null token.
|
||||
*/
|
||||
TokenImpl createToken();
|
||||
|
||||
|
||||
/**
|
||||
* Returns a token.
|
||||
*
|
||||
* @param line The segment from which to get the token's text.
|
||||
* @param beg The starting offset of the token's text in the segment.
|
||||
* @param end The ending offset of the token's text in the segment.
|
||||
* @param startOffset The offset in the document of the token.
|
||||
* @param type The type of token.
|
||||
* @return The token.
|
||||
*/
|
||||
TokenImpl createToken(final Segment line, final int beg,
|
||||
final int end, final int startOffset, final int type);
|
||||
|
||||
|
||||
/**
|
||||
* Returns a token.
|
||||
*
|
||||
* @param line The char array from which to get the token's text.
|
||||
* @param beg The starting offset of the token's text in the char array.
|
||||
* @param end The ending offset of the token's text in the char array.
|
||||
* @param startOffset The offset in the document of the token.
|
||||
* @param type The type of token.
|
||||
* @return The token.
|
||||
*/
|
||||
TokenImpl createToken(final char[] line, final int beg,
|
||||
final int end, final int startOffset, final int type);
|
||||
|
||||
|
||||
/**
|
||||
* Resets the state of this token maker. This method should be called
|
||||
* by the <code>TokenMaker</code> every time a token list is generated for
|
||||
* a new line so the tokens can be reused.
|
||||
*/
|
||||
void resetAllTokens();
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,962 @@
|
||||
/*
|
||||
* 02/21/2004
|
||||
*
|
||||
* Token.java - A token used in syntax highlighting.
|
||||
*
|
||||
* 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.Font;
|
||||
import java.awt.FontMetrics;
|
||||
import java.awt.Rectangle;
|
||||
|
||||
import javax.swing.text.Segment;
|
||||
import javax.swing.text.TabExpander;
|
||||
import javax.swing.text.Utilities;
|
||||
|
||||
|
||||
/**
|
||||
* The default implementation of {@link Token}.<p>
|
||||
*
|
||||
* <b>Note:</b> The instances of <code>Token</code> returned by
|
||||
* {@link RSyntaxDocument}s are pooled and should always be treated as
|
||||
* immutable. They should not be cast to <code>TokenImpl</code> and modified.
|
||||
* Modifying tokens you did not create yourself can and will result in
|
||||
* rendering issues and/or runtime exceptions. You have been warned!
|
||||
*
|
||||
* @author Robert Futrell
|
||||
* @version 0.3
|
||||
*/
|
||||
@SuppressWarnings({ "checkstyle:visibilitymodifier" })
|
||||
public class TokenImpl implements Token {
|
||||
|
||||
/**
|
||||
* The text this token represents. This is implemented as a segment so we
|
||||
* can point directly to the text in the document without having to make a
|
||||
* copy of it.
|
||||
*/
|
||||
public char[] text;
|
||||
public int textOffset;
|
||||
public int textCount;
|
||||
|
||||
/**
|
||||
* The offset into the document at which this token resides.
|
||||
*/
|
||||
private int offset;
|
||||
|
||||
/**
|
||||
* The type of token this is; for example, {@link #FUNCTION}.
|
||||
*/
|
||||
private int type;
|
||||
|
||||
/**
|
||||
* Whether this token is a hyperlink.
|
||||
*/
|
||||
private boolean hyperlink;
|
||||
|
||||
/**
|
||||
* The next token in this linked list.
|
||||
*/
|
||||
private Token nextToken;
|
||||
|
||||
/**
|
||||
* The language this token is in, <code>>= 0</code>.
|
||||
*/
|
||||
private int languageIndex;
|
||||
|
||||
|
||||
/**
|
||||
* Creates a "null" token. The token itself is not null; rather, it
|
||||
* signifies that it is the last token in a linked list of tokens and
|
||||
* that it is not part of a "multi-line token."
|
||||
*/
|
||||
public TokenImpl() {
|
||||
this.text = null;
|
||||
this.textOffset = -1;
|
||||
this.textCount = -1;
|
||||
this.setType(NULL);
|
||||
setOffset(-1);
|
||||
hyperlink = false;
|
||||
nextToken = null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param line The segment from which to get the token.
|
||||
* @param beg The first character's position in <code>line</code>.
|
||||
* @param end The last character's position in <code>line</code>.
|
||||
* @param startOffset The offset into the document at which this
|
||||
* token begins.
|
||||
* @param type A token type listed as "generic" above.
|
||||
* @param languageIndex The language index for this token.
|
||||
*/
|
||||
public TokenImpl(Segment line, int beg, int end, int startOffset, int type,
|
||||
int languageIndex) {
|
||||
this(line.array, beg,end, startOffset, type, languageIndex);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param line The segment from which to get the token.
|
||||
* @param beg The first character's position in <code>line</code>.
|
||||
* @param end The last character's position in <code>line</code>.
|
||||
* @param startOffset The offset into the document at which this
|
||||
* token begins.
|
||||
* @param type A token type listed as "generic" above.
|
||||
* @param languageIndex The language index for this token.
|
||||
*/
|
||||
public TokenImpl(char[] line, int beg, int end, int startOffset, int type,
|
||||
int languageIndex) {
|
||||
this();
|
||||
set(line, beg,end, startOffset, type);
|
||||
setLanguageIndex(languageIndex);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates this token as a copy of the passed-in token.
|
||||
*
|
||||
* @param t2 The token from which to make a copy.
|
||||
*/
|
||||
public TokenImpl(Token t2) {
|
||||
this();
|
||||
copyFrom(t2);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public StringBuilder appendHTMLRepresentation(StringBuilder sb,
|
||||
RSyntaxTextArea textArea,
|
||||
boolean fontFamily) {
|
||||
return appendHTMLRepresentation(sb, textArea, fontFamily, false);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public StringBuilder appendHTMLRepresentation(StringBuilder sb,
|
||||
RSyntaxTextArea textArea, boolean fontFamily,
|
||||
boolean tabsToSpaces) {
|
||||
|
||||
SyntaxScheme colorScheme = textArea.getSyntaxScheme();
|
||||
Style scheme = colorScheme.getStyle(getType());
|
||||
Font font = textArea.getFontForTokenType(getType());//scheme.font;
|
||||
|
||||
if (font.isBold()) {
|
||||
sb.append("<b>");
|
||||
}
|
||||
if (font.isItalic()) {
|
||||
sb.append("<em>");
|
||||
}
|
||||
if (scheme.underline || isHyperlink()) {
|
||||
sb.append("<u>");
|
||||
}
|
||||
|
||||
boolean needsFontTag = fontFamily || !isWhitespace();
|
||||
if (needsFontTag) {
|
||||
sb.append("<font");
|
||||
if (fontFamily) {
|
||||
sb.append(" face=\"").append(font.getFamily()).append('"');
|
||||
}
|
||||
if (!isWhitespace()) {
|
||||
sb.append(" color=\"").append(
|
||||
getHTMLFormatForColor(scheme.foreground)).append('"');
|
||||
}
|
||||
sb.append('>');
|
||||
}
|
||||
|
||||
// NOTE: Don't use getLexeme().trim() because whitespace tokens will
|
||||
// be turned into NOTHING.
|
||||
appendHtmlLexeme(textArea, sb, tabsToSpaces);
|
||||
|
||||
if (needsFontTag) {
|
||||
sb.append("</font>");
|
||||
}
|
||||
if (scheme.underline || isHyperlink()) {
|
||||
sb.append("</u>");
|
||||
}
|
||||
if (font.isItalic()) {
|
||||
sb.append("</em>");
|
||||
}
|
||||
if (font.isBold()) {
|
||||
sb.append("</b>");
|
||||
}
|
||||
|
||||
return sb;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Appends an HTML version of the lexeme of this token (i.e. no style
|
||||
* HTML, but replacing chars such as <code>\t</code>, <code><</code>
|
||||
* and <code>></code> with their escapes).
|
||||
*
|
||||
* @param textArea The text area.
|
||||
* @param sb The buffer to append to.
|
||||
* @param tabsToSpaces Whether to convert tabs into spaces.
|
||||
* @return The same buffer.
|
||||
*/
|
||||
private StringBuilder appendHtmlLexeme(RSyntaxTextArea textArea,
|
||||
StringBuilder sb, boolean tabsToSpaces) {
|
||||
|
||||
boolean lastWasSpace = false;
|
||||
int i = textOffset;
|
||||
int lastI = i;
|
||||
String tabStr = null;
|
||||
|
||||
while (i<textOffset+textCount) {
|
||||
char ch = text[i];
|
||||
switch (ch) {
|
||||
case ' ':
|
||||
sb.append(text, lastI, i-lastI);
|
||||
lastI = i+1;
|
||||
sb.append(lastWasSpace ? " " : " ");
|
||||
lastWasSpace = true;
|
||||
break;
|
||||
case '\t':
|
||||
sb.append(text, lastI, i-lastI);
|
||||
lastI = i+1;
|
||||
if (tabsToSpaces && tabStr==null) {
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
for (int j=0; j<textArea.getTabSize(); j++) {
|
||||
stringBuilder.append(" ");
|
||||
}
|
||||
tabStr = stringBuilder.toString();
|
||||
}
|
||||
sb.append(tabsToSpaces ? tabStr : "	");
|
||||
lastWasSpace = false;
|
||||
break;
|
||||
case '&':
|
||||
sb.append(text, lastI, i-lastI);
|
||||
lastI = i+1;
|
||||
sb.append("&");
|
||||
lastWasSpace = false;
|
||||
break;
|
||||
case '<':
|
||||
sb.append(text, lastI, i-lastI);
|
||||
lastI = i+1;
|
||||
sb.append("<");
|
||||
lastWasSpace = false;
|
||||
break;
|
||||
case '>':
|
||||
sb.append(text, lastI, i-lastI);
|
||||
lastI = i+1;
|
||||
sb.append(">");
|
||||
lastWasSpace = false;
|
||||
break;
|
||||
case '\'':
|
||||
sb.append(text, lastI, i-lastI);
|
||||
lastI = i+1;
|
||||
sb.append("'");
|
||||
lastWasSpace = false;
|
||||
break;
|
||||
case '"':
|
||||
sb.append(text, lastI, i-lastI);
|
||||
lastI = i+1;
|
||||
sb.append(""");
|
||||
lastWasSpace = false;
|
||||
break;
|
||||
case '/': // OWASP-recommended to escape even though unnecessary
|
||||
sb.append(text, lastI, i-lastI);
|
||||
lastI = i+1;
|
||||
sb.append("/");
|
||||
lastWasSpace = false;
|
||||
break;
|
||||
default:
|
||||
lastWasSpace = false;
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
if (lastI<textOffset+textCount) {
|
||||
sb.append(text, lastI, textOffset+textCount-lastI);
|
||||
}
|
||||
return sb;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public char charAt(int index) {
|
||||
return text[textOffset + index];
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean containsPosition(int pos) {
|
||||
return pos>=getOffset() && pos<getOffset()+textCount;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Makes one token point to the same text segment, and have the same value
|
||||
* as another token.
|
||||
*
|
||||
* @param t2 The token from which to copy.
|
||||
*/
|
||||
public void copyFrom(Token t2) {
|
||||
text = t2.getTextArray();
|
||||
textOffset = t2.getTextOffset();
|
||||
textCount = t2.length();
|
||||
setOffset(t2.getOffset());
|
||||
setType(t2.getType());
|
||||
hyperlink = t2.isHyperlink();
|
||||
languageIndex = t2.getLanguageIndex();
|
||||
nextToken = t2.getNextToken();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int documentToToken(int pos) {
|
||||
return pos + (textOffset-getOffset());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean endsWith(char[] ch) {
|
||||
if (ch==null || ch.length>textCount) {
|
||||
return false;
|
||||
}
|
||||
final int start = textOffset + textCount - ch.length;
|
||||
for (int i=0; i<ch.length; i++) {
|
||||
if (text[start+i]!=ch[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
|
||||
if (obj==this) {
|
||||
return true;
|
||||
}
|
||||
if (!(obj instanceof Token)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Token t2 = (Token)obj;
|
||||
return offset==t2.getOffset() &&
|
||||
type==t2.getType() &&
|
||||
languageIndex==t2.getLanguageIndex() &&
|
||||
hyperlink==t2.isHyperlink() &&
|
||||
((getLexeme()==null && t2.getLexeme()==null) ||
|
||||
(getLexeme()!=null && getLexeme().equals(t2.getLexeme())));
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int getEndOffset() {
|
||||
return offset + textCount;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a <code>String</code> of the form "#xxxxxx" good for use
|
||||
* in HTML, representing the given color.
|
||||
*
|
||||
* @param color The color to get a string for.
|
||||
* @return The HTML form of the color. If <code>color</code> is
|
||||
* <code>null</code>, <code>#000000</code> is returned.
|
||||
*/
|
||||
private static String getHTMLFormatForColor(Color color) {
|
||||
if (color==null) {
|
||||
return "black";
|
||||
}
|
||||
String hexRed = Integer.toHexString(color.getRed());
|
||||
if (hexRed.length()==1) {
|
||||
hexRed = "0" + hexRed;
|
||||
}
|
||||
String hexGreen = Integer.toHexString(color.getGreen());
|
||||
if (hexGreen.length()==1) {
|
||||
hexGreen = "0" + hexGreen;
|
||||
}
|
||||
String hexBlue = Integer.toHexString(color.getBlue());
|
||||
if (hexBlue.length()==1) {
|
||||
hexBlue = "0" + hexBlue;
|
||||
}
|
||||
return "#" + hexRed + hexGreen + hexBlue;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getHTMLRepresentation(RSyntaxTextArea textArea) {
|
||||
StringBuilder buf = new StringBuilder();
|
||||
appendHTMLRepresentation(buf, textArea, true);
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int getLanguageIndex() {
|
||||
return languageIndex;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Token getLastNonCommentNonWhitespaceToken() {
|
||||
|
||||
Token last = null;
|
||||
|
||||
for (Token t=this; t!=null && t.isPaintable(); t=t.getNextToken()) {
|
||||
switch (t.getType()) {
|
||||
case COMMENT_DOCUMENTATION:
|
||||
case COMMENT_EOL:
|
||||
case COMMENT_MULTILINE:
|
||||
case COMMENT_KEYWORD:
|
||||
case COMMENT_MARKUP:
|
||||
case WHITESPACE:
|
||||
break;
|
||||
default:
|
||||
last = t;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return last;
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Token getLastPaintableToken() {
|
||||
Token t = this;
|
||||
while (t.isPaintable()) {
|
||||
Token next = t.getNextToken();
|
||||
if (next==null || !next.isPaintable()) {
|
||||
return t;
|
||||
}
|
||||
t = next;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getLexeme() {
|
||||
return text==null ? null : new String(text, textOffset, textCount);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int getListOffset(RSyntaxTextArea textArea, TabExpander e,
|
||||
float x0, float x) {
|
||||
|
||||
// If the coordinate in question is before this line's start, quit.
|
||||
if (x0 >= x) {
|
||||
return getOffset();
|
||||
}
|
||||
|
||||
float currX = x0; // x-coordinate of current char.
|
||||
float nextX = x0; // x-coordinate of next char.
|
||||
float stableX = x0; // Cached ending x-coord. of last tab or token.
|
||||
TokenImpl token = this;
|
||||
int last = getOffset();
|
||||
FontMetrics fm = null;
|
||||
|
||||
while (token != null && token.isPaintable()) {
|
||||
|
||||
fm = textArea.getFontMetricsForTokenType(token.getType());
|
||||
char[] text = token.text;
|
||||
int start = token.textOffset;
|
||||
int end = start + token.textCount;
|
||||
|
||||
for (int i = start; i < end; i++) {
|
||||
currX = nextX;
|
||||
if (text[i] == '\t') {
|
||||
nextX = e.nextTabStop(nextX, 0);
|
||||
stableX = nextX; // Cache ending x-coord. of tab.
|
||||
start = i + 1; // Do charsWidth() from next char.
|
||||
}
|
||||
else {
|
||||
nextX = stableX + fm.charsWidth(text, start, i - start + 1);
|
||||
}
|
||||
if (x >= currX && x < nextX) {
|
||||
if ((x - currX) < (nextX - x)) {
|
||||
return last + i - token.textOffset;
|
||||
}
|
||||
return last + i + 1 - token.textOffset;
|
||||
}
|
||||
}
|
||||
|
||||
stableX = nextX; // Cache ending x-coordinate of token.
|
||||
last += token.textCount;
|
||||
token = (TokenImpl)token.getNextToken();
|
||||
|
||||
}
|
||||
|
||||
// If we didn't find anything, return the end position of the text.
|
||||
return last;
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Token getNextToken() {
|
||||
return nextToken;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int getOffset() {
|
||||
return offset;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int getOffsetBeforeX(RSyntaxTextArea textArea, TabExpander e,
|
||||
float startX, float endBeforeX) {
|
||||
|
||||
FontMetrics fm = textArea.getFontMetricsForTokenType(getType());
|
||||
int i = textOffset;
|
||||
int stop = i + textCount;
|
||||
float x = startX;
|
||||
|
||||
while (i<stop) {
|
||||
if (text[i]=='\t') {
|
||||
x = e.nextTabStop(x, 0);
|
||||
}
|
||||
else {
|
||||
x += fm.charWidth(text[i]);
|
||||
}
|
||||
if (x>endBeforeX) {
|
||||
// If not even the first character fits into the space, go
|
||||
// ahead and say the first char does fit so we don't go into
|
||||
// an infinite loop.
|
||||
int intoToken = Math.max(i-textOffset, 1);
|
||||
return getOffset() + intoToken;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
// If we got here, the whole token fit in (endBeforeX-startX) pixels.
|
||||
return getOffset() + textCount - 1;
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public char[] getTextArray() {
|
||||
return text;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int getTextOffset() {
|
||||
return textOffset;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public float getWidth(RSyntaxTextArea textArea, TabExpander e, float x0) {
|
||||
return getWidthUpTo(textCount, textArea, e, x0);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public float getWidthUpTo(int numChars, RSyntaxTextArea textArea,
|
||||
TabExpander e, float x0) {
|
||||
float width = x0;
|
||||
FontMetrics fm = textArea.getFontMetricsForTokenType(getType());
|
||||
if (fm != null) {
|
||||
int w;
|
||||
int currentStart = textOffset;
|
||||
int endBefore = textOffset + numChars;
|
||||
for (int i = currentStart; i < endBefore; i++) {
|
||||
if (text[i] == '\t') {
|
||||
// Since TokenMaker implementations usually group all
|
||||
// adjacent whitespace into a single token, there
|
||||
// aren't usually any characters to compute a width
|
||||
// for here, so we check before calling.
|
||||
w = i - currentStart;
|
||||
if (w > 0) {
|
||||
width += fm.charsWidth(text, currentStart, w);
|
||||
}
|
||||
currentStart = i + 1;
|
||||
width = e.nextTabStop(width, 0);
|
||||
}
|
||||
}
|
||||
// Most (non-whitespace) tokens will have characters at this
|
||||
// point to get the widths for, so we don't check for w>0 (mini-
|
||||
// optimization).
|
||||
w = endBefore - currentStart;
|
||||
width += fm.charsWidth(text, currentStart, w);
|
||||
}
|
||||
return width - x0;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return offset + (getLexeme()==null ? 0 : getLexeme().hashCode());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean is(char[] lexeme) {
|
||||
if (textCount==lexeme.length) {
|
||||
for (int i=0; i<textCount; i++) {
|
||||
if (text[textOffset+i]!=lexeme[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean is(int type, char[] lexeme) {
|
||||
if (this.getType()==type && textCount==lexeme.length) {
|
||||
for (int i=0; i<textCount; i++) {
|
||||
if (text[textOffset+i]!=lexeme[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean is(int type, String lexeme) {
|
||||
return this.getType()==type && textCount==lexeme.length() &&
|
||||
lexeme.equals(getLexeme());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isComment() {
|
||||
return getType()>=Token.COMMENT_EOL && getType()<=Token.COMMENT_MARKUP;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isCommentOrWhitespace() {
|
||||
return isComment() || isWhitespace();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isHyperlink() {
|
||||
return hyperlink;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isIdentifier() {
|
||||
return getType()==IDENTIFIER;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isLeftCurly() {
|
||||
return getType()==SEPARATOR && isSingleChar('{');
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isRightCurly() {
|
||||
return getType()==SEPARATOR && isSingleChar('}');
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isPaintable() {
|
||||
return getType()>Token.NULL;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isSingleChar(char ch) {
|
||||
return textCount==1 && text[textOffset]==ch;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isSingleChar(int type, char ch) {
|
||||
return this.getType()==type && isSingleChar(ch);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isWhitespace() {
|
||||
return getType()==WHITESPACE;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int length() {
|
||||
return textCount;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Rectangle listOffsetToView(RSyntaxTextArea textArea, TabExpander e,
|
||||
int pos, int x0, Rectangle rect) {
|
||||
|
||||
int stableX = x0; // Cached ending x-coord. of last tab or token.
|
||||
TokenImpl token = this;
|
||||
FontMetrics fm = null;
|
||||
Segment s = new Segment();
|
||||
|
||||
while (token != null && token.isPaintable()) {
|
||||
|
||||
fm = textArea.getFontMetricsForTokenType(token.getType());
|
||||
if (fm == null) {
|
||||
return rect; // Don't return null as things'll error.
|
||||
}
|
||||
char[] text = token.text;
|
||||
int start = token.textOffset;
|
||||
int end = start + token.textCount;
|
||||
|
||||
// If this token contains the position for which to get the
|
||||
// bounding box...
|
||||
if (token.containsPosition(pos)) {
|
||||
|
||||
s.array = token.text;
|
||||
s.offset = token.textOffset;
|
||||
s.count = pos - token.getOffset();
|
||||
|
||||
// Must use this (actually fm.charWidth()), and not
|
||||
// fm.charsWidth() for returned value to match up with where
|
||||
// text is actually painted on OS X!
|
||||
int w = Utilities.getTabbedTextWidth(s, fm, stableX, e,
|
||||
token.getOffset());
|
||||
rect.x = stableX + w;
|
||||
end = token.documentToToken(pos);
|
||||
|
||||
if (text[end] == '\t') {
|
||||
rect.width = fm.charWidth(' ');
|
||||
}
|
||||
else {
|
||||
rect.width = fm.charWidth(text[end]);
|
||||
}
|
||||
|
||||
return rect;
|
||||
|
||||
}
|
||||
|
||||
// If this token does not contain the position for which to get
|
||||
// the bounding box...
|
||||
else {
|
||||
s.array = token.text;
|
||||
s.offset = token.textOffset;
|
||||
s.count = token.textCount;
|
||||
stableX += Utilities.getTabbedTextWidth(s, fm, stableX, e,
|
||||
token.getOffset());
|
||||
}
|
||||
|
||||
token = (TokenImpl)token.getNextToken();
|
||||
|
||||
}
|
||||
|
||||
// If we didn't find anything, we're at the end of the line. Return
|
||||
// a width of 1 (so selection highlights don't extend way past line's
|
||||
// text). A ConfigurableCaret will know to paint itself with a larger
|
||||
// width.
|
||||
rect.x = stableX;
|
||||
rect.width = 1;
|
||||
return rect;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Makes this token start at the specified offset into the document.<p>
|
||||
*
|
||||
* <b>Note:</b> You should not modify <code>Token</code> instances you
|
||||
* did not create yourself (e.g., came from an
|
||||
* <code>RSyntaxDocument</code>). If you do, rendering issues and/or
|
||||
* runtime exceptions will likely occur. You have been warned!
|
||||
*
|
||||
* @param pos The offset into the document this token should start at.
|
||||
* Note that this token must already contain this position; if
|
||||
* it doesn't, an exception is thrown.
|
||||
* @throws IllegalArgumentException If pos is not already contained by
|
||||
* this token.
|
||||
* @see #moveOffset(int)
|
||||
*/
|
||||
public void makeStartAt(int pos) {
|
||||
if (pos<getOffset() || pos>=(getOffset()+textCount)) {
|
||||
throw new IllegalArgumentException("pos " + pos +
|
||||
" is not in range " + getOffset() + "-" + (getOffset()+textCount-1));
|
||||
}
|
||||
int shift = pos - getOffset();
|
||||
setOffset(pos);
|
||||
textOffset += shift;
|
||||
textCount -= shift;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Moves the starting offset of this token.<p>
|
||||
*
|
||||
* <b>Note:</b> You should not modify <code>Token</code> instances you
|
||||
* did not create yourself (e.g., came from an
|
||||
* <code>RSyntaxDocument</code>). If you do, rendering issues and/or
|
||||
* runtime exceptions will likely occur. You have been warned!
|
||||
*
|
||||
* @param amt The amount to move the starting offset. This should be
|
||||
* between <code>0</code> and <code>textCount</code>, inclusive.
|
||||
* @throws IllegalArgumentException If <code>amt</code> is an invalid value.
|
||||
* @see #makeStartAt(int)
|
||||
*/
|
||||
public void moveOffset(int amt) {
|
||||
if (amt<0 || amt>textCount) {
|
||||
throw new IllegalArgumentException("amt " + amt +
|
||||
" is not in range 0-" + textCount);
|
||||
}
|
||||
setOffset(getOffset() + amt);
|
||||
textOffset += amt;
|
||||
textCount -= amt;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the value of this token to a particular segment of a document.
|
||||
* The "next token" value is cleared.
|
||||
*
|
||||
* @param line The segment from which to get the token.
|
||||
* @param beg The first character's position in <code>line</code>.
|
||||
* @param end The last character's position in <code>line</code>.
|
||||
* @param offset The offset into the document at which this token begins.
|
||||
* @param type A token type listed as "generic" above.
|
||||
*/
|
||||
public void set(final char[] line, final int beg, final int end,
|
||||
final int offset, final int type) {
|
||||
this.text = line;
|
||||
this.textOffset = beg;
|
||||
this.textCount = end - beg + 1;
|
||||
this.setType(type);
|
||||
this.setOffset(offset);
|
||||
nextToken = null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets whether this token is a hyperlink.
|
||||
*
|
||||
* @param hyperlink Whether this token is a hyperlink.
|
||||
* @see #isHyperlink()
|
||||
*/
|
||||
@Override
|
||||
public void setHyperlink(boolean hyperlink) {
|
||||
this.hyperlink = hyperlink;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the language index for this token. If this value is positive, it
|
||||
* denotes a specific "secondary" language this token represents (such as
|
||||
* JavaScript code or CSS embedded in an HTML file). If this value is
|
||||
* <code>0</code>, this token is in the "main" language being edited.
|
||||
* Negative values are invalid and treated as <code>0</code>.
|
||||
*
|
||||
* @param languageIndex The new language index. A value of
|
||||
* <code>0</code> denotes the "main" language, any positive value
|
||||
* denotes a specific secondary language. Negative values will
|
||||
* be treated as <code>0</code>.
|
||||
* @see #getLanguageIndex()
|
||||
*/
|
||||
@Override
|
||||
public void setLanguageIndex(int languageIndex) {
|
||||
this.languageIndex = languageIndex;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the "next token" pointer of this token to point to the specified
|
||||
* token.
|
||||
*
|
||||
* @param nextToken The new next token.
|
||||
* @see #getNextToken()
|
||||
*/
|
||||
public void setNextToken(Token nextToken) {
|
||||
this.nextToken = nextToken;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the offset into the document at which this token resides.
|
||||
*
|
||||
* @param offset The new offset into the document.
|
||||
* @see #getOffset()
|
||||
*/
|
||||
public void setOffset(int offset) {
|
||||
this.offset = offset;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void setType(int type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public boolean startsWith(char[] chars) {
|
||||
if (chars.length<=textCount){
|
||||
for (int i=0; i<chars.length; i++) {
|
||||
if (text[textOffset+i]!=chars[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public int tokenToDocument(int pos) {
|
||||
return pos + (getOffset()-textOffset);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns this token as a <code>String</code>, which is useful for
|
||||
* debugging.
|
||||
*
|
||||
* @return A string describing this token.
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return "[Token: " +
|
||||
(getType()==Token.NULL ? "<null token>" :
|
||||
"text: '" +
|
||||
(text==null ? "<null>" : getLexeme() + "'; " +
|
||||
"offset: " + getOffset() + "; type: " + getType() + "; " +
|
||||
"isPaintable: " + isPaintable() +
|
||||
"; nextToken==null: " + (nextToken==null))) +
|
||||
"]";
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* 08/28/2013
|
||||
*
|
||||
* TokenIterator.java - An iterator over the Tokens in an RSyntaxDocument.
|
||||
*
|
||||
* 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.util.Iterator;
|
||||
|
||||
|
||||
/**
|
||||
* Allows you to iterate through all paintable tokens in an
|
||||
* <code>RSyntaxDocument</code>.
|
||||
*
|
||||
* @author Robert Futrell
|
||||
* @version 1.0
|
||||
*/
|
||||
class TokenIterator implements Iterator<Token> {
|
||||
|
||||
private RSyntaxDocument doc;
|
||||
private int curLine;
|
||||
private Token token;
|
||||
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param doc The document whose tokens we should iterate over.
|
||||
*/
|
||||
TokenIterator(RSyntaxDocument doc) {
|
||||
this.doc = doc;
|
||||
loadTokenListForCurLine();
|
||||
int lineCount = getLineCount();
|
||||
while ((token==null || !token.isPaintable()) && curLine<lineCount-1) {
|
||||
curLine++;
|
||||
loadTokenListForCurLine();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private int getLineCount() {
|
||||
return doc.getDefaultRootElement().getElementCount();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether any more paintable tokens are in the document.
|
||||
*
|
||||
* @return Whether there are any more paintable tokens.
|
||||
* @see #next()
|
||||
*/
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return token!=null;
|
||||
}
|
||||
|
||||
|
||||
private void loadTokenListForCurLine() {
|
||||
token = doc.getTokenListForLine(curLine);
|
||||
if (token!=null && !token.isPaintable()) {
|
||||
// Common end of document scenario
|
||||
token = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the next paintable token in the document.
|
||||
*
|
||||
* @return The next paintable token in the document.
|
||||
* @see #hasNext()
|
||||
*/
|
||||
@Override
|
||||
public Token next() {
|
||||
|
||||
Token t = token;
|
||||
boolean tIsCloned = false;
|
||||
int lineCount = getLineCount();
|
||||
|
||||
// Get the next token, going to the next line if necessary.
|
||||
if (token!=null && token.isPaintable()) {
|
||||
token = token.getNextToken();
|
||||
}
|
||||
else if (curLine<lineCount-1) {
|
||||
t = new TokenImpl(t); // Clone t since tokens are pooled
|
||||
tIsCloned = true;
|
||||
curLine++;
|
||||
loadTokenListForCurLine();
|
||||
}
|
||||
else if (token!=null && !token.isPaintable()) {
|
||||
// Ends with a non-paintable token (not sure this ever happens)
|
||||
token = null;
|
||||
}
|
||||
|
||||
while ((token==null || !token.isPaintable()) && curLine<lineCount-1) {
|
||||
if (!tIsCloned) {
|
||||
t = new TokenImpl(t); // Clone t since tokens are pooled
|
||||
tIsCloned = true;
|
||||
}
|
||||
curLine++;
|
||||
loadTokenListForCurLine();
|
||||
}
|
||||
if (token!=null && !token.isPaintable() && curLine==lineCount-1) {
|
||||
token = null;
|
||||
}
|
||||
|
||||
return t;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Always throws {@link UnsupportedOperationException}, as
|
||||
* <code>Token</code> removal is not supported.
|
||||
*
|
||||
* @throws UnsupportedOperationException always.
|
||||
*/
|
||||
@Override
|
||||
public void remove() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
/*
|
||||
* 02/24/2004
|
||||
*
|
||||
* TokenMaker.java - An object that can take a chunk of text and return a
|
||||
* linked list of <code>Token</code>s representing it.
|
||||
*
|
||||
* This library is distributed under a modified BSD license. See the included
|
||||
* RSyntaxTextArea.License.txt file for details.
|
||||
*/
|
||||
package org.fife.ui.rsyntaxtextarea;
|
||||
|
||||
import javax.swing.Action;
|
||||
import javax.swing.text.Segment;
|
||||
|
||||
|
||||
/**
|
||||
* An implementation of <code>TokenMaker</code> is a class that turns text into
|
||||
* a linked list of <code>Token</code>s for syntax highlighting
|
||||
* in a particular language.
|
||||
*
|
||||
* @see Token
|
||||
* @see AbstractTokenMaker
|
||||
*
|
||||
* @author Robert Futrell
|
||||
* @version 0.2
|
||||
*/
|
||||
public interface TokenMaker {
|
||||
|
||||
|
||||
/**
|
||||
* Adds a null token to the end of the current linked list of tokens.
|
||||
* This should be put at the end of the linked list whenever the last
|
||||
* token on the current line is NOT a multi-line token.
|
||||
*/
|
||||
public void addNullToken();
|
||||
|
||||
|
||||
/**
|
||||
* Adds the token specified to the current linked list of tokens.
|
||||
*
|
||||
* @param array The character array from which to get the text.
|
||||
* @param start Start offset in <code>segment</code> of token.
|
||||
* @param end End offset in <code>segment</code> of token.
|
||||
* @param tokenType The token's type.
|
||||
* @param startOffset The offset in the document at which this token
|
||||
* occurs.
|
||||
*/
|
||||
public void addToken(char[] array, int start, int end, int tokenType,
|
||||
int startOffset);
|
||||
|
||||
|
||||
/**
|
||||
* Returns the closest {@link TokenTypes "standard" token type} for a given
|
||||
* "internal" token type (e.g. one whose value is <code>< 0</code>).
|
||||
*
|
||||
* @param type The token type.
|
||||
* @return The closest "standard" token type. If a mapping is not defined
|
||||
* for this language, then <code>type</code> is returned.
|
||||
*/
|
||||
public int getClosestStandardTokenTypeForInternalType(int type);
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether this programming language uses curly braces
|
||||
* ('<code>{</code>' and '<code>}</code>') to denote code blocks.
|
||||
*
|
||||
* @param languageIndex The language index at the offset in question.
|
||||
* Since some <code>TokenMaker</code>s effectively have nested
|
||||
* languages (such as JavaScript in HTML), this parameter tells the
|
||||
* <code>TokenMaker</code> what sub-language to look at.
|
||||
* @return Whether curly braces denote code blocks.
|
||||
*/
|
||||
public boolean getCurlyBracesDenoteCodeBlocks(int languageIndex);
|
||||
|
||||
|
||||
/**
|
||||
* Returns the last token on this line's type if the token is "unfinished",
|
||||
* or {@link Token#NULL} if it was finished. For example, if C-style
|
||||
* syntax highlighting is being implemented, and <code>text</code>
|
||||
* contained a line of code that contained the beginning of a comment but
|
||||
* no end-comment marker ("*\/"), then this method would return
|
||||
* {@link Token#COMMENT_MULTILINE} for that line. This is useful
|
||||
* for doing syntax highlighting.
|
||||
*
|
||||
* @param text The line of tokens to examine.
|
||||
* @param initialTokenType The token type to start with (i.e., the value
|
||||
* of <code>getLastTokenTypeOnLine</code> for the line before
|
||||
* <code>text</code>).
|
||||
* @return The last token on this line's type, or {@link Token#NULL}
|
||||
* if the line was completed.
|
||||
*/
|
||||
public int getLastTokenTypeOnLine(Segment text, int initialTokenType);
|
||||
|
||||
|
||||
/**
|
||||
* Returns the text to place at the beginning and end of a
|
||||
* line to "comment" it in this programming language.
|
||||
*
|
||||
* @param languageIndex The language index at the offset in question.
|
||||
* Since some <code>TokenMaker</code>s effectively have nested
|
||||
* languages (such as JavaScript in HTML), this parameter tells the
|
||||
* <code>TokenMaker</code> what sub-language to look at.
|
||||
* @return The start and end strings to add to a line to "comment"
|
||||
* it out. A <code>null</code> value for either means there
|
||||
* is no string to add for that part. A value of
|
||||
* <code>null</code> for the array means this language
|
||||
* does not support commenting/uncommenting lines.
|
||||
*/
|
||||
public String[] getLineCommentStartAndEnd(int languageIndex);
|
||||
|
||||
|
||||
/**
|
||||
* Returns an action to handle "insert break" key presses (i.e. Enter).
|
||||
*
|
||||
* @return The action, or <code>null</code> if the default action should
|
||||
* be used.
|
||||
*/
|
||||
public Action getInsertBreakAction();
|
||||
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public boolean getMarkOccurrencesOfTokenType(int type);
|
||||
|
||||
|
||||
/**
|
||||
* Returns the object in charge of marking all occurrences of the token
|
||||
* at the current caret position, if it is a relevant token. If
|
||||
* <code>null</code> is returned, a default <code>OccurrenceMarker</code>
|
||||
* is used.
|
||||
*
|
||||
* @return The occurrence marker for this language, or <code>null</code>
|
||||
* for none.
|
||||
*/
|
||||
public OccurrenceMarker getOccurrenceMarker();
|
||||
|
||||
|
||||
/**
|
||||
* If a line ends in the specified token, this method returns whether
|
||||
* a new line inserted after that line should be indented.
|
||||
*
|
||||
* @param token The token the previous line ends with.
|
||||
* @return Whether the next line should be indented.
|
||||
*/
|
||||
public boolean getShouldIndentNextLineAfter(Token token);
|
||||
|
||||
|
||||
/**
|
||||
* Returns the first token in the linked list of tokens generated
|
||||
* from <code>text</code>. This method must be implemented by
|
||||
* subclasses so they can correctly implement syntax highlighting.
|
||||
*
|
||||
* @param text The text from which to get tokens.
|
||||
* @param initialTokenType The token type we should start with.
|
||||
* @param startOffset The offset into the document at which
|
||||
* <code>text</code> starts.
|
||||
* @return The first <code>Token</code> in a linked list representing
|
||||
* the syntax highlighted text.
|
||||
*/
|
||||
public Token getTokenList(Segment text, int initialTokenType,
|
||||
int startOffset);
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether a character could be part of an "identifier" token
|
||||
* in a specific language. This is used to identify such things as the
|
||||
* bounds of the "word" to select on double-clicking.
|
||||
*
|
||||
* @param languageIndex The language index the character was found in.
|
||||
* @param ch The character.
|
||||
* @return Whether the character could be part of an "identifier" token.
|
||||
*/
|
||||
public boolean isIdentifierChar(int languageIndex, char ch);
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether this language is a markup language.
|
||||
*
|
||||
* @return Whether this language is markup.
|
||||
*/
|
||||
public boolean isMarkupLanguage();
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,340 @@
|
||||
/*
|
||||
* 08/26/2004
|
||||
*
|
||||
* TokenMakerBase.java - A base class for token makers.
|
||||
*
|
||||
* This library is distributed under a modified BSD license. See the included
|
||||
* RSyntaxTextArea.License.txt file for details.
|
||||
*/
|
||||
package org.fife.ui.rsyntaxtextarea;
|
||||
|
||||
import javax.swing.Action;
|
||||
import javax.swing.text.Segment;
|
||||
|
||||
|
||||
/**
|
||||
* Base class for token makers.
|
||||
*
|
||||
* @author Robert Futrell
|
||||
* @version 1.0
|
||||
*/
|
||||
public abstract class TokenMakerBase implements TokenMaker {
|
||||
|
||||
/**
|
||||
* The first token in the returned linked list.
|
||||
*/
|
||||
protected TokenImpl firstToken;
|
||||
|
||||
/**
|
||||
* Used in the creation of the linked list.
|
||||
*/
|
||||
protected TokenImpl currentToken;
|
||||
|
||||
/**
|
||||
* Used in the creation of the linked list.
|
||||
*/
|
||||
protected TokenImpl previousToken;
|
||||
|
||||
/**
|
||||
* The factory that gives us our tokens to use.
|
||||
*/
|
||||
private TokenFactory tokenFactory;
|
||||
|
||||
/**
|
||||
* Highlights occurrences of the current token in the editor, if it is
|
||||
* relevant.
|
||||
*/
|
||||
private OccurrenceMarker occurrenceMarker;
|
||||
|
||||
/**
|
||||
* "0" implies this is the "main" language being highlighted. Positive
|
||||
* values imply various "secondary" or "embedded" languages, such as CSS
|
||||
* or JavaScript in HTML. While this value is non-zero, tokens will be
|
||||
* generated with this language index so they can (possibly) be painted
|
||||
* differently, so "embedded" languages can be rendered with a special
|
||||
* background.
|
||||
*/
|
||||
private int languageIndex;
|
||||
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public TokenMakerBase() {
|
||||
firstToken = currentToken = previousToken = null;
|
||||
tokenFactory = new DefaultTokenFactory();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void addNullToken() {
|
||||
if (firstToken==null) {
|
||||
firstToken = tokenFactory.createToken();
|
||||
currentToken = firstToken;
|
||||
}
|
||||
else {
|
||||
TokenImpl next = tokenFactory.createToken();
|
||||
currentToken.setNextToken(next);
|
||||
previousToken = currentToken;
|
||||
currentToken = next;
|
||||
}
|
||||
currentToken.setLanguageIndex(languageIndex);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds the token specified to the current linked list of tokens.
|
||||
*
|
||||
* @param segment <code>Segment</code> to get text from.
|
||||
* @param start Start offset in <code>segment</code> of token.
|
||||
* @param end End offset in <code>segment</code> of token.
|
||||
* @param tokenType The token's type.
|
||||
* @param startOffset The offset in the document at which this token
|
||||
* occurs.
|
||||
*/
|
||||
public void addToken(Segment segment, int start, int end, int tokenType,
|
||||
int startOffset) {
|
||||
addToken(segment.array, start,end, tokenType, startOffset);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void addToken(char[] array, int start, int end, int tokenType,
|
||||
int startOffset) {
|
||||
addToken(array, start, end, tokenType, startOffset, false);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds the token specified to the current linked list of tokens.
|
||||
*
|
||||
* @param array The character array.
|
||||
* @param start The starting offset in the array.
|
||||
* @param end The ending offset in the array.
|
||||
* @param tokenType The token's type.
|
||||
* @param startOffset The offset in the document at which this token
|
||||
* occurs.
|
||||
* @param hyperlink Whether this token is a hyperlink.
|
||||
*/
|
||||
public void addToken(char[] array, int start, int end, int tokenType,
|
||||
int startOffset, boolean hyperlink) {
|
||||
|
||||
if (firstToken==null) {
|
||||
firstToken = tokenFactory.createToken(array, start, end,
|
||||
startOffset, tokenType);
|
||||
currentToken = firstToken; // previous token is still null.
|
||||
}
|
||||
else {
|
||||
TokenImpl next = tokenFactory.createToken(array, start,end,
|
||||
startOffset, tokenType);
|
||||
currentToken.setNextToken(next);
|
||||
previousToken = currentToken;
|
||||
currentToken = next;
|
||||
}
|
||||
|
||||
currentToken.setLanguageIndex(languageIndex);
|
||||
currentToken.setHyperlink(hyperlink);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the occurrence marker to use for this token maker. Subclasses
|
||||
* can override to use different implementations.
|
||||
*
|
||||
* @return The occurrence marker to use.
|
||||
*/
|
||||
protected OccurrenceMarker createOccurrenceMarker() {
|
||||
return new DefaultOccurrenceMarker();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the closest {@link TokenTypes "standard" token type} for a given
|
||||
* "internal" token type (e.g. one whose value is <code>< 0</code>).<p>
|
||||
*
|
||||
* The default implementation returns <code>type</code> always, which
|
||||
* denotes that a mapping from internal token types to standard token types
|
||||
* is not defined; subclasses can override.
|
||||
*
|
||||
* @param type The token type.
|
||||
* @return The closest "standard" token type.
|
||||
*/
|
||||
@Override
|
||||
public int getClosestStandardTokenTypeForInternalType(int type) {
|
||||
return type;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether this programming language uses curly braces
|
||||
* ('<code>{</code>' and '<code>}</code>') to denote code blocks.
|
||||
*
|
||||
* The default implementation returns <code>false</code>; subclasses can
|
||||
* override this method if necessary.
|
||||
*
|
||||
* @param languageIndex The language index at the offset in question.
|
||||
* Since some <code>TokenMaker</code>s effectively have nested
|
||||
* languages (such as JavaScript in HTML), this parameter tells the
|
||||
* <code>TokenMaker</code> what sub-language to look at.
|
||||
* @return Whether curly braces denote code blocks.
|
||||
*/
|
||||
@Override
|
||||
public boolean getCurlyBracesDenoteCodeBlocks(int languageIndex) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns an action to handle "insert break" key presses (i.e. Enter).
|
||||
* The default implementation returns <code>null</code>. Subclasses
|
||||
* can override.
|
||||
*
|
||||
* @return The default implementation always returns <code>null</code>.
|
||||
*/
|
||||
@Override
|
||||
public Action getInsertBreakAction() {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the current language index.
|
||||
*
|
||||
* @return The current language index.
|
||||
* @see #setLanguageIndex(int)
|
||||
*/
|
||||
protected int getLanguageIndex() {
|
||||
return languageIndex;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public int getLastTokenTypeOnLine(Segment text, int initialTokenType) {
|
||||
|
||||
// Last parameter doesn't matter if we're not painting.
|
||||
Token t = getTokenList(text, initialTokenType, 0);
|
||||
|
||||
while (t.getNextToken()!=null) {
|
||||
t = t.getNextToken();
|
||||
}
|
||||
|
||||
return t.getType();
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public String[] getLineCommentStartAndEnd(int languageIndex) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether tokens of the specified type should have "mark
|
||||
* occurrences" enabled for the current programming language. The default
|
||||
* implementation returns true if <tt>type</tt> is {@link Token#IDENTIFIER}.
|
||||
* Subclasses can override this method to support other token types, such
|
||||
* as {@link Token#VARIABLE}.
|
||||
*
|
||||
* @param type The token type.
|
||||
* @return Whether tokens of this type should have "mark occurrences"
|
||||
* enabled.
|
||||
*/
|
||||
@Override
|
||||
public boolean getMarkOccurrencesOfTokenType(int type) {
|
||||
return type==Token.IDENTIFIER;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public OccurrenceMarker getOccurrenceMarker() {
|
||||
if (occurrenceMarker==null) {
|
||||
occurrenceMarker = createOccurrenceMarker();
|
||||
}
|
||||
return occurrenceMarker;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The default implementation returns <code>false</code> always. Languages
|
||||
* that wish to better support auto-indentation can override this method.
|
||||
*
|
||||
* @param token The token the previous line ends with.
|
||||
* @return Whether the next line should be indented.
|
||||
*/
|
||||
@Override
|
||||
public boolean getShouldIndentNextLineAfter(Token token) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether a character could be part of an "identifier" token
|
||||
* in a specific language. The default implementation returns
|
||||
* <code>true</code> for letters, numbers, and certain symbols.
|
||||
*/
|
||||
@Override
|
||||
public boolean isIdentifierChar(int languageIndex, char ch) {
|
||||
return Character.isLetterOrDigit(ch) || ch=='_' || ch=='$';
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The default implementation returns <code>false</code> always.
|
||||
* Subclasses that are highlighting a markup language should override this
|
||||
* method to return <code>true</code>.
|
||||
*
|
||||
* @return <code>false</code> always.
|
||||
*/
|
||||
@Override
|
||||
public boolean isMarkupLanguage() {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Deletes the linked list of tokens so we can begin anew. This should
|
||||
* never have to be called by the programmer, as it is automatically
|
||||
* called whenever the user calls
|
||||
* {@link #getLastTokenTypeOnLine(Segment, int)} or
|
||||
* {@link #getTokenList(Segment, int, int)}.
|
||||
*/
|
||||
protected void resetTokenList() {
|
||||
firstToken = currentToken = previousToken = null;
|
||||
tokenFactory.resetAllTokens();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the language index to assign to tokens moving forward. This
|
||||
* property is used to designate tokens as being in "secondary" languages
|
||||
* (such as CSS or JavaScript in HTML).
|
||||
*
|
||||
* @param languageIndex The new language index. A value of
|
||||
* <code>0</code> denotes the "main" language, any positive value
|
||||
* denotes a specific secondary language. Negative values will
|
||||
* be treated as <code>0</code>.
|
||||
* @see #getLanguageIndex()
|
||||
*/
|
||||
protected void setLanguageIndex(int languageIndex) {
|
||||
this.languageIndex = Math.max(0, languageIndex);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* 12/12/2008
|
||||
*
|
||||
* TokenMakerFactory.java - A factory for TokenMakers.
|
||||
*
|
||||
* 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.util.Set;
|
||||
|
||||
import org.fife.ui.rsyntaxtextarea.modes.PlainTextTokenMaker;
|
||||
|
||||
/**
|
||||
* A factory that maps syntax styles to {@link TokenMaker}s capable of splitting
|
||||
* text into tokens for those syntax styles.
|
||||
*
|
||||
* @author Robert Futrell
|
||||
* @version 1.0
|
||||
*/
|
||||
public abstract class TokenMakerFactory {
|
||||
|
||||
/**
|
||||
* If this system property is set, a custom <code>TokenMakerFactory</code> of
|
||||
* the specified class will be used as the default token maker factory.
|
||||
*/
|
||||
public static final String PROPERTY_DEFAULT_TOKEN_MAKER_FACTORY = "TokenMakerFactory";
|
||||
|
||||
/**
|
||||
* The singleton default <code>TokenMakerFactory</code> instance.
|
||||
*/
|
||||
private static TokenMakerFactory DEFAULT_INSTANCE;
|
||||
|
||||
/**
|
||||
* Returns the default <code>TokenMakerFactory</code> instance. This is the
|
||||
* factory used by all {@link RSyntaxDocument}s by default.
|
||||
*
|
||||
* @return The factory.
|
||||
* @see #setDefaultInstance(TokenMakerFactory)
|
||||
*/
|
||||
public static synchronized TokenMakerFactory getDefaultInstance() {
|
||||
if (DEFAULT_INSTANCE == null) {
|
||||
String clazz = null;
|
||||
try {
|
||||
clazz = System.getProperty(PROPERTY_DEFAULT_TOKEN_MAKER_FACTORY);
|
||||
} catch (java.security.AccessControlException ace) {
|
||||
clazz = null; // We're in an applet; take default.
|
||||
}
|
||||
if (clazz == null) {
|
||||
clazz = "org.fife.ui.rsyntaxtextarea.DefaultTokenMakerFactory";
|
||||
}
|
||||
try {
|
||||
DEFAULT_INSTANCE = (TokenMakerFactory) Class.forName(clazz).newInstance();
|
||||
} catch (RuntimeException re) { // FindBugs
|
||||
throw re;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
throw new InternalError("Cannot find TokenMakerFactory: " + clazz);
|
||||
}
|
||||
}
|
||||
return DEFAULT_INSTANCE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link TokenMaker} for the specified key.
|
||||
*
|
||||
* @param key
|
||||
* The key.
|
||||
* @return The corresponding <code>TokenMaker</code>, or
|
||||
* {@link PlainTextTokenMaker} if none matches the specified key.
|
||||
*/
|
||||
public final TokenMaker getTokenMaker(String key) {
|
||||
TokenMaker tm = getTokenMakerImpl(key);
|
||||
if (tm == null) {
|
||||
tm = new PlainTextTokenMaker();
|
||||
}
|
||||
return tm;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link TokenMaker} for the specified key.
|
||||
*
|
||||
* @param key
|
||||
* The key.
|
||||
* @return The corresponding <code>TokenMaker</code>, or <code>null</code> if
|
||||
* none matches the specified key.
|
||||
*/
|
||||
protected abstract TokenMaker getTokenMakerImpl(String key);
|
||||
|
||||
/**
|
||||
* Returns the set of keys that this factory maps to token makers.
|
||||
*
|
||||
* @return The set of keys.
|
||||
*/
|
||||
public abstract Set<String> keySet();
|
||||
|
||||
/**
|
||||
* Sets the default <code>TokenMakerFactory</code> instance. This is the factory
|
||||
* used by all future {@link RSyntaxDocument}s by default.
|
||||
* <code>RSyntaxDocument</code>s that have already been created are not
|
||||
* affected.
|
||||
*
|
||||
* @param tmf
|
||||
* The factory.
|
||||
* @throws IllegalArgumentException
|
||||
* If <code>tmf</code> is <code>null</code>.
|
||||
* @see #getDefaultInstance()
|
||||
*/
|
||||
public static synchronized void setDefaultInstance(TokenMakerFactory tmf) {
|
||||
if (tmf == null) {
|
||||
throw new IllegalArgumentException("tmf cannot be null");
|
||||
}
|
||||
DEFAULT_INSTANCE = tmf;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,279 @@
|
||||
/*
|
||||
* 08/26/2004
|
||||
*
|
||||
* TokenMap.java - Similar to a Map in Java, only designed specifically for
|
||||
* org.fife.ui.rsyntaxtextarea.Tokens.
|
||||
*
|
||||
* This library is distributed under a modified BSD license. See the included
|
||||
* RSyntaxTextArea.License.txt file for details.
|
||||
*/
|
||||
package org.fife.ui.rsyntaxtextarea;
|
||||
|
||||
import javax.swing.text.Segment;
|
||||
|
||||
|
||||
/**
|
||||
* A hash table for reserved words, etc. defined by a {@link TokenMaker}.
|
||||
* This class is designed for the quick lookup of tokens, as it can compare
|
||||
* <code>Segment</code>s without the need to allocate a new string.<p>
|
||||
*
|
||||
* The <code>org.fife.ui.rsyntaxtextarea</code> package uses this class to help
|
||||
* identify reserved words in programming languages. An instance of
|
||||
* {@link TokenMaker} will create and initialize an instance of this class
|
||||
* containing all reserved words, data types, and all other words that need to
|
||||
* be syntax-highlighted for that particular language. When the token maker
|
||||
* parses a line and identifies an individual token, it is looked up in the
|
||||
* <code>TokenMap</code> to see if it should be syntax-highlighted.
|
||||
*
|
||||
* @author Robert Futrell
|
||||
* @version 0.6
|
||||
*/
|
||||
public class TokenMap {
|
||||
|
||||
private int size;
|
||||
private TokenMapToken[] tokenMap;
|
||||
private boolean ignoreCase;
|
||||
|
||||
private static final int DEFAULT_TOKEN_MAP_SIZE = 52;
|
||||
|
||||
|
||||
/**
|
||||
* Constructs a new token map that is case-sensitive.
|
||||
*/
|
||||
public TokenMap() {
|
||||
this(DEFAULT_TOKEN_MAP_SIZE);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Constructs a new token map that is case-sensitive.
|
||||
*
|
||||
* @param size The size of the token map.
|
||||
*/
|
||||
public TokenMap(int size) {
|
||||
this(size, false);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Constructs a new token map.
|
||||
*
|
||||
* @param ignoreCase Whether or not this token map should ignore case
|
||||
* when comparing tokens.
|
||||
*/
|
||||
public TokenMap(boolean ignoreCase) {
|
||||
this(DEFAULT_TOKEN_MAP_SIZE, ignoreCase);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Constructs a new token map.
|
||||
*
|
||||
* @param size The size of the token map.
|
||||
* @param ignoreCase Whether or not this token map should ignore case
|
||||
* when comparing tokens.
|
||||
*/
|
||||
public TokenMap(int size, boolean ignoreCase) {
|
||||
this.size = size;
|
||||
tokenMap = new TokenMapToken[size];
|
||||
this.ignoreCase = ignoreCase;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds a token to a specified bucket in the token map.
|
||||
*
|
||||
* @param bucket The bucket in which to add the token.
|
||||
* @param token The token to add.
|
||||
*/
|
||||
private void addTokenToBucket(int bucket, TokenMapToken token) {
|
||||
TokenMapToken old = tokenMap[bucket];
|
||||
token.nextToken = old;
|
||||
tokenMap[bucket] = token;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the token type associated with the given text, if the given
|
||||
* text is in this token map. If it isn't, <code>-1</code> is returned.
|
||||
*
|
||||
* @param text The segment from which to get the text to compare.
|
||||
* @param start The starting index in the segment of the text.
|
||||
* @param end The ending index in the segment of the text.
|
||||
* @return The token type associated with the given text, or
|
||||
* <code>-1</code> if this token was not specified in this map.
|
||||
*/
|
||||
public int get(Segment text, int start, int end) {
|
||||
return get(text.array, start, end);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the token type associated with the given text, if the given
|
||||
* text is in this token map. If it isn't, <code>-1</code> is returned.
|
||||
*
|
||||
* @param array1 An array of characters containing the text.
|
||||
* @param start The starting index in the array of the text.
|
||||
* @param end The ending index in the array of the text.
|
||||
* @return The token type associated with the given text, or
|
||||
* <code>-1</code> if this token was not specified in this map.
|
||||
*/
|
||||
public int get(char[] array1, int start, int end) {
|
||||
|
||||
int length1 = end - start + 1;
|
||||
|
||||
int hash = getHashCode(array1, start, length1);
|
||||
TokenMapToken token = tokenMap[hash];
|
||||
|
||||
char[] array2;
|
||||
int offset2;
|
||||
int offset1;
|
||||
int length;
|
||||
|
||||
/* We check whether or not to ignore case before doing any looping to
|
||||
* minimize the number of extraneous comparisons we do. This makes
|
||||
* for slightly redundant code, but it'll be a little more efficient.
|
||||
*/
|
||||
|
||||
// If matches are case-sensitive (C, C++, Java, etc.)...
|
||||
if (!ignoreCase) {
|
||||
|
||||
mainLoop:
|
||||
while (token!=null) {
|
||||
if (token.length==length1) {
|
||||
array2 = token.text;
|
||||
offset2 = token.offset;
|
||||
offset1 = start;
|
||||
length = length1;
|
||||
while (length-- > 0) {
|
||||
if (array1[offset1++]!=array2[offset2++]) {
|
||||
token = token.nextToken;
|
||||
continue mainLoop;
|
||||
}
|
||||
}
|
||||
return token.tokenType;
|
||||
}
|
||||
token = token.nextToken;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// If matches are NOT case-sensitive (HTML)...
|
||||
// Note that all tokens saved in this map were converted to
|
||||
// lower-case already.
|
||||
else {
|
||||
|
||||
mainLoop2:
|
||||
while (token!=null) {
|
||||
if (token.length==length1) {
|
||||
array2 = token.text;
|
||||
offset2 = token.offset;
|
||||
offset1 = start;
|
||||
length = length1;
|
||||
while (length-- > 0) {
|
||||
if (RSyntaxUtilities.toLowerCase(
|
||||
array1[offset1++]) != array2[offset2++]) {
|
||||
token = token.nextToken;
|
||||
continue mainLoop2;
|
||||
}
|
||||
}
|
||||
return token.tokenType;
|
||||
}
|
||||
token = token.nextToken;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Didn't match any of the tokens in the bucket.
|
||||
return -1;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the hash code for a given string.
|
||||
*
|
||||
* @param text The text to hash.
|
||||
* @param offset The offset into the text at which to start hashing.
|
||||
* @param length The last character in the text to hash.
|
||||
* @return The hash code.
|
||||
*/
|
||||
private int getHashCode(char[] text, int offset, int length) {
|
||||
return (RSyntaxUtilities.toLowerCase(text[offset]) +
|
||||
RSyntaxUtilities.toLowerCase(text[offset+length-1])) % size;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether this token map ignores case when checking for tokens.
|
||||
* This property is set in the constructor and cannot be changed, as this
|
||||
* is an intrinsic property of a particular programming language.
|
||||
*
|
||||
* @return Whether or not this token maker is ignoring case.
|
||||
*/
|
||||
protected boolean isIgnoringCase() {
|
||||
return ignoreCase;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds a string to this token map.
|
||||
*
|
||||
* @param string The string to add.
|
||||
* @param tokenType The type of token the string is.
|
||||
*/
|
||||
public void put(final String string, final int tokenType) {
|
||||
if (isIgnoringCase()) {
|
||||
put(string.toLowerCase().toCharArray(), tokenType);
|
||||
}
|
||||
else {
|
||||
put(string.toCharArray(), tokenType);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds a string to this token map. The char array passed-in will be used
|
||||
* as the actual data for the token, so it may well be modified (such as
|
||||
* lower-casing it if <code>ignoreCase</code> is <code>true</code>). This
|
||||
* shouldn't be an issue though as this method is only called from the
|
||||
* public <code>put</code> method, which allocates a new char array.
|
||||
*
|
||||
* @param string The string to add.
|
||||
* @param tokenType The type of token the string is.
|
||||
*/
|
||||
private void put(char[] string, int tokenType) {
|
||||
int hashCode = getHashCode(string, 0, string.length);
|
||||
addTokenToBucket(hashCode, new TokenMapToken(string, tokenType));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The "token" used by a token map. Note that this isn't the same thing
|
||||
* as the {@link Token} class, but it's basically a 1-1 correspondence
|
||||
* for reserved words, etc.
|
||||
*/
|
||||
private static final class TokenMapToken {
|
||||
|
||||
private char[] text;
|
||||
private int offset;
|
||||
private int length;
|
||||
private int tokenType;
|
||||
private TokenMapToken nextToken;
|
||||
|
||||
private TokenMapToken(char[] text, int tokenType) {
|
||||
this.text = text;
|
||||
this.offset = 0;
|
||||
this.length = text.length;
|
||||
this.tokenType = tokenType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "[TokenMapToken: " + new String(text,offset,length) + "]";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* 08/06/2004
|
||||
*
|
||||
* TokenOrientedView.java - An interface for the syntax-highlighting token-
|
||||
* oriented views for token-oriented methods.
|
||||
*
|
||||
* This library is distributed under a modified BSD license. See the included
|
||||
* RSyntaxTextArea.License.txt file for details.
|
||||
*/
|
||||
package org.fife.ui.rsyntaxtextarea;
|
||||
|
||||
|
||||
/**
|
||||
* An interface for the syntax-highlighting token oriented views for
|
||||
* token-oriented methods. This way callers won't need to know what specific
|
||||
* class a view is an instance of to access its tokens.<p>
|
||||
*
|
||||
* Currently, this interface is only useful for obtaining token lists for
|
||||
* "physical lines" (i.e., a word-wrapped view's logical lines may be
|
||||
* represented as several physical lines, thus getting the "physical line" above
|
||||
* a given position may prove complicated).
|
||||
*
|
||||
* @author Robert Futrell
|
||||
* @version 0.1
|
||||
*/
|
||||
public interface TokenOrientedView {
|
||||
|
||||
|
||||
/**
|
||||
* Returns a token list for the <i>physical</i> line above the physical
|
||||
* line containing the specified offset into the document. Note that for
|
||||
* a plain (non-wrapped) view, this is simply the token list for the
|
||||
* logical line above the line containing <code>offset</code>, since lines
|
||||
* are not wrapped. For a wrapped view, this may or may not be tokens from
|
||||
* the same line.
|
||||
*
|
||||
* @param offset The offset in question.
|
||||
* @return A token list for the physical (and in this view, logical) line
|
||||
* before this one. If no physical line is above the one
|
||||
* containing <code>offset</code>, <code>null</code> is returned.
|
||||
*/
|
||||
Token getTokenListForPhysicalLineAbove(int offset);
|
||||
|
||||
|
||||
/**
|
||||
* Returns a token list for the <i>physical</i> line below the physical
|
||||
* line containing the specified offset into the document. Note that for
|
||||
* a plain (non-wrapped) view, this is simply the token list for the
|
||||
* logical line below the line containing <code>offset</code>, since lines
|
||||
* are not wrapped. For a wrapped view, this may or may not be tokens from
|
||||
* the same line.
|
||||
*
|
||||
* @param offset The offset in question.
|
||||
* @return A token list for the physical (and in this view, logical) line
|
||||
* after this one. If no physical line is after the one
|
||||
* containing <code>offset</code>, <code>null</code> is returned.
|
||||
*/
|
||||
Token getTokenListForPhysicalLineBelow(int offset);
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* 03/16/2013
|
||||
*
|
||||
* TokenPainter - Renders tokens in an instance of RSyntaxTextArea.
|
||||
*
|
||||
* This library is distributed under a modified BSD license. See the included
|
||||
* RSyntaxTextArea.License.txt file for details.
|
||||
*/
|
||||
package org.fife.ui.rsyntaxtextarea;
|
||||
|
||||
import java.awt.Graphics2D;
|
||||
|
||||
import javax.swing.text.TabExpander;
|
||||
|
||||
|
||||
/**
|
||||
* Renders tokens in an instance of {@link RSyntaxTextArea}. One instance
|
||||
* may render tokens "regularly," another may render visible whitespace, for
|
||||
* example.
|
||||
*
|
||||
* @author Robert Futrell
|
||||
* @version 1.0
|
||||
*/
|
||||
interface TokenPainter {
|
||||
|
||||
|
||||
/**
|
||||
* Paints this token.
|
||||
*
|
||||
* @param token The token to render.
|
||||
* @param g The graphics context in which to paint.
|
||||
* @param x The x-coordinate at which to paint.
|
||||
* @param y The y-coordinate at which to paint.
|
||||
* @param host The text area this token is in.
|
||||
* @param e How to expand tabs.
|
||||
* @return The x-coordinate representing the end of the painted text.
|
||||
*/
|
||||
float paint(Token token, Graphics2D g, float x, float y,
|
||||
RSyntaxTextArea host, TabExpander e);
|
||||
|
||||
|
||||
/**
|
||||
* Paints this token.
|
||||
*
|
||||
* @param token The token to render.
|
||||
* @param g The graphics context in which to paint.
|
||||
* @param x The x-coordinate at which to paint.
|
||||
* @param y The y-coordinate at which to paint.
|
||||
* @param host The text area this token is in.
|
||||
* @param e How to expand tabs.
|
||||
* @param clipStart The left boundary of the clip rectangle in which we're
|
||||
* painting. This optimizes painting by allowing us to not paint
|
||||
* paint when this token is "to the left" of the clip rectangle.
|
||||
* @return The x-coordinate representing the end of the painted text.
|
||||
*/
|
||||
float paint(Token token, Graphics2D g, float x, float y,
|
||||
RSyntaxTextArea host, TabExpander e, float clipStart);
|
||||
|
||||
|
||||
/**
|
||||
* Paints this token.
|
||||
*
|
||||
* @param token The token to render.
|
||||
* @param g The graphics context in which to paint.
|
||||
* @param x The x-coordinate at which to paint.
|
||||
* @param y The y-coordinate at which to paint.
|
||||
* @param host The text area this token is in.
|
||||
* @param e How to expand tabs.
|
||||
* @param clipStart The left boundary of the clip rectangle in which we're
|
||||
* painting. This optimizes painting by allowing us to not paint
|
||||
* paint when this token is "to the left" of the clip rectangle.
|
||||
* @param paintBG Whether to paint the background.
|
||||
* @return The x-coordinate representing the end of the painted text.
|
||||
*/
|
||||
float paint(Token token, Graphics2D g, float x, float y,
|
||||
RSyntaxTextArea host, TabExpander e, float clipStart,
|
||||
boolean paintBG);
|
||||
|
||||
|
||||
/**
|
||||
* Paints this token as it should appear in a selected region of text
|
||||
* (assuming painting with a selection-foreground color is enabled in the
|
||||
* parent <code>RSyntaxTextArea</code>).
|
||||
*
|
||||
* @param token The token to render.
|
||||
* @param g The graphics context in which to paint.
|
||||
* @param x The x-coordinate at which to paint.
|
||||
* @param y The y-coordinate at which to paint.
|
||||
* @param host The text area this token is in.
|
||||
* @param e How to expand tabs.
|
||||
* @param useSTC Whether to use the text area's "selected text color."
|
||||
* @return The x-coordinate representing the end of the painted text.
|
||||
*/
|
||||
float paintSelected(Token token, Graphics2D g, float x, float y,
|
||||
RSyntaxTextArea host, TabExpander e, boolean useSTC);
|
||||
|
||||
|
||||
/**
|
||||
* Paints this token as it should appear in a selected region of text
|
||||
* (assuming painting with a selection-foreground color is enabled in the
|
||||
* parent <code>RSyntaxTextArea</code>).
|
||||
*
|
||||
* @param token The token to render.
|
||||
* @param g The graphics context in which to paint.
|
||||
* @param x The x-coordinate at which to paint.
|
||||
* @param y The y-coordinate at which to paint.
|
||||
* @param host The text area this token is in.
|
||||
* @param e How to expand tabs.
|
||||
* @param clipStart The left boundary of the clip rectangle in which we're
|
||||
* painting. This optimizes painting by allowing us to not paint
|
||||
* paint when this token is "to the left" of the clip rectangle.
|
||||
* @param useSTC Whether to use the text area's "selected text color."
|
||||
* @return The x-coordinate representing the end of the painted text.
|
||||
*/
|
||||
float paintSelected(Token token, Graphics2D g, float x, float y,
|
||||
RSyntaxTextArea host, TabExpander e, float clipStart,
|
||||
boolean useSTC);
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* 12/04/2011
|
||||
*
|
||||
* TokenTypes.java - All token types supported by RSyntaxTextArea.
|
||||
*
|
||||
* This library is distributed under a modified BSD license. See the included
|
||||
* RSyntaxTextArea.License.txt file for details.
|
||||
*/
|
||||
package org.fife.ui.rsyntaxtextarea;
|
||||
|
||||
|
||||
/**
|
||||
* All token types supported by RSyntaxTextArea.<p>
|
||||
*
|
||||
* Note that all valid token types are >= 0, so extensions of the TokenMaker
|
||||
* class are free to internally use all ints < 0 ONLY for "end-of-line"
|
||||
* style markers; they are ignored by painting implementations.
|
||||
*
|
||||
* @author Robert Futrell
|
||||
* @version 1.0
|
||||
*/
|
||||
public interface TokenTypes {
|
||||
|
||||
/**
|
||||
* Tokens of type <code>NULL</code> mark the end of lines with no
|
||||
* multi-line token at the end (such as a block comment in C++).
|
||||
*/
|
||||
int NULL = 0;
|
||||
|
||||
int COMMENT_EOL = 1;
|
||||
int COMMENT_MULTILINE = 2;
|
||||
int COMMENT_DOCUMENTATION = 3;
|
||||
int COMMENT_KEYWORD = 4;
|
||||
int COMMENT_MARKUP = 5;
|
||||
|
||||
int RESERVED_WORD = 6;
|
||||
int RESERVED_WORD_2 = 7;
|
||||
|
||||
int FUNCTION = 8;
|
||||
|
||||
int LITERAL_BOOLEAN = 9;
|
||||
int LITERAL_NUMBER_DECIMAL_INT = 10;
|
||||
int LITERAL_NUMBER_FLOAT = 11;
|
||||
int LITERAL_NUMBER_HEXADECIMAL = 12;
|
||||
int LITERAL_STRING_DOUBLE_QUOTE = 13;
|
||||
int LITERAL_CHAR = 14;
|
||||
int LITERAL_BACKQUOTE = 15;
|
||||
|
||||
int DATA_TYPE = 16;
|
||||
|
||||
int VARIABLE = 17;
|
||||
|
||||
int REGEX = 18;
|
||||
|
||||
int ANNOTATION = 19;
|
||||
|
||||
int IDENTIFIER = 20;
|
||||
|
||||
int WHITESPACE = 21;
|
||||
|
||||
int SEPARATOR = 22;
|
||||
|
||||
int OPERATOR = 23;
|
||||
|
||||
int PREPROCESSOR = 24;
|
||||
|
||||
int MARKUP_TAG_DELIMITER = 25;
|
||||
int MARKUP_TAG_NAME = 26;
|
||||
int MARKUP_TAG_ATTRIBUTE = 27;
|
||||
int MARKUP_TAG_ATTRIBUTE_VALUE = 28;
|
||||
int MARKUP_COMMENT = 29;
|
||||
int MARKUP_DTD = 30;
|
||||
int MARKUP_PROCESSING_INSTRUCTION = 31;
|
||||
int MARKUP_CDATA_DELIMITER = 32;
|
||||
int MARKUP_CDATA = 33;
|
||||
int MARKUP_ENTITY_REFERENCE = 34;
|
||||
|
||||
int ERROR_IDENTIFIER = 35;
|
||||
int ERROR_NUMBER_FORMAT = 36;
|
||||
int ERROR_STRING_DOUBLE = 37;
|
||||
int ERROR_CHAR = 38;
|
||||
|
||||
int DEFAULT_NUM_TOKEN_TYPES = 39;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
package org.fife.ui.rsyntaxtextarea;
|
||||
|
||||
import javax.swing.text.TabExpander;
|
||||
|
||||
|
||||
/**
|
||||
* Utility methods for dealing with tokens.
|
||||
*
|
||||
* @author Robert Futrell
|
||||
* @version 1.0
|
||||
*/
|
||||
public final class TokenUtils {
|
||||
|
||||
|
||||
private TokenUtils() {}
|
||||
|
||||
|
||||
/**
|
||||
* Modifies the passed-in token list to start at the specified offset.
|
||||
* For example, if the token list covered positions 20-60 in the document
|
||||
* (inclusive) like so:
|
||||
* <pre>
|
||||
* [token1] -> [token2] -> [token3] -> [token4]
|
||||
* 20 30 31 40 41 50 51 60
|
||||
* </pre>
|
||||
* and you used this method to make the token list start at position 44,
|
||||
* then the token list would be modified to be the following:
|
||||
* <pre>
|
||||
* [part-of-old-token3] -> [token4]
|
||||
* 44 50 51 60
|
||||
* </pre>
|
||||
* Tokens that come before the specified position are forever lost, and
|
||||
* the token containing that position is made to begin at that position if
|
||||
* necessary. All token types remain the same as they were originally.<p>
|
||||
*
|
||||
* This method can be useful if you are only interested in part of a token
|
||||
* list (i.e., the line it represents), but you don't want to modify the
|
||||
* token list yourself.
|
||||
*
|
||||
* @param tokenList The list to make start at the specified position.
|
||||
* This parameter is modified.
|
||||
* @param pos The position at which the new token list is to start. If
|
||||
* this position is not in the passed-in token list,
|
||||
* returned token list will either be <code>null</code> or the
|
||||
* unpaintable token(s) at the end of the passed-in token list.
|
||||
* @param e How to expand tabs.
|
||||
* @param textArea The text area from which the token list came.
|
||||
* @param x0 The initial x-pixel position of the old token list.
|
||||
* @return Information about the "sub" token list. This will be
|
||||
* <code>null</code> if <code>pos</code> was not a valid offset
|
||||
* into the token list.
|
||||
* @see #getSubTokenList(Token, int, TabExpander, RSyntaxTextArea, float, TokenImpl)
|
||||
*/
|
||||
public static TokenSubList getSubTokenList(Token tokenList, int pos,
|
||||
TabExpander e,
|
||||
final RSyntaxTextArea textArea,
|
||||
float x0) {
|
||||
return getSubTokenList(tokenList, pos, e, textArea, x0, null);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Modifies the passed-in token list to start at the specified offset.
|
||||
* For example, if the token list covered positions 20-60 in the document
|
||||
* (inclusive) like so:
|
||||
* <pre>
|
||||
* [token1] -> [token2] -> [token3] -> [token4]
|
||||
* 20 30 31 40 41 50 51 60
|
||||
* </pre>
|
||||
* and you used this method to make the token list start at position 44,
|
||||
* then the token list would be modified to be the following:
|
||||
* <pre>
|
||||
* [part-of-old-token3] -> [token4]
|
||||
* 44 50 51 60
|
||||
* </pre>
|
||||
* Tokens that come before the specified position are forever lost, and
|
||||
* the token containing that position is made to begin at that position if
|
||||
* necessary. All token types remain the same as they were originally.<p>
|
||||
*
|
||||
* This method can be useful if you are only interested in part of a token
|
||||
* list (i.e., the line it represents), but you don't want to modify the
|
||||
* token list yourself.
|
||||
*
|
||||
* @param tokenList The list to make start at the specified position.
|
||||
* This parameter is modified.
|
||||
* @param pos The position at which the new token list is to start. If
|
||||
* this position is not in the passed-in token list,
|
||||
* returned token list will either be <code>null</code> or the
|
||||
* unpaintable token(s) at the end of the passed-in token list.
|
||||
* @param e How to expand tabs.
|
||||
* @param textArea The text area from which the token list came.
|
||||
* @param x0 The initial x-pixel position of the old token list.
|
||||
* @param tempToken A temporary token to use when creating the token list
|
||||
* result. This may be <code>null</code> but callers can pass in
|
||||
* a "buffer" token for performance if desired.
|
||||
* @return Information about the "sub" token list. This will be
|
||||
* <code>null</code> if <code>pos</code> was not a valid offset
|
||||
* into the token list.
|
||||
* @see #getSubTokenList(Token, int, TabExpander, RSyntaxTextArea, float)
|
||||
*/
|
||||
public static TokenSubList getSubTokenList(Token tokenList, int pos,
|
||||
TabExpander e,
|
||||
final RSyntaxTextArea textArea,
|
||||
float x0,
|
||||
TokenImpl tempToken) {
|
||||
|
||||
if (tempToken==null) {
|
||||
tempToken = new TokenImpl();
|
||||
}
|
||||
Token t = tokenList;
|
||||
|
||||
// Loop through the token list until you find the one that contains
|
||||
// pos. Remember the cumulative width of all of these tokens.
|
||||
while (t!=null && t.isPaintable() && !t.containsPosition(pos)) {
|
||||
x0 += t.getWidth(textArea, e, x0);
|
||||
t = t.getNextToken();
|
||||
}
|
||||
|
||||
// Make the token that contains pos start at pos.
|
||||
if (t!=null && t.isPaintable()) {
|
||||
|
||||
if (t.getOffset()!=pos) {
|
||||
// Number of chars between p0 and token start.
|
||||
int difference = pos - t.getOffset();
|
||||
x0 += t.getWidthUpTo(t.length()-difference+1, textArea, e, x0);
|
||||
tempToken.copyFrom(t);
|
||||
tempToken.makeStartAt(pos);
|
||||
|
||||
return new TokenSubList(tempToken, x0);
|
||||
|
||||
}
|
||||
else { // t.getOffset()==pos
|
||||
return new TokenSubList(t, x0);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// This could be a null token, so we need to just return it.
|
||||
return new TokenSubList(tokenList, x0);
|
||||
//return null;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* A sub-list of tokens.
|
||||
*/
|
||||
@SuppressWarnings({ "checkstyle:visibilitymodifier" })
|
||||
public static class TokenSubList {
|
||||
|
||||
/**
|
||||
* The "sub" token list.
|
||||
*/
|
||||
public Token tokenList;
|
||||
|
||||
/**
|
||||
* The width, in pixels, of the part of the token list "removed from
|
||||
* the front." This way, you know the x-offset of the "new" token list.
|
||||
*/
|
||||
public float x;
|
||||
|
||||
public TokenSubList(Token tokenList, float x) {
|
||||
this.tokenList = tokenList;
|
||||
this.x = x;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
* 11/13/2008
|
||||
*
|
||||
* URLFileLocation.java - The location of a file at a (remote) URL.
|
||||
*
|
||||
* 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.io.InputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.net.URL;
|
||||
|
||||
|
||||
/**
|
||||
* The location of a file at a (remote) URL.
|
||||
*
|
||||
* @author Robert Futrell
|
||||
* @version 1.0
|
||||
*/
|
||||
class URLFileLocation extends FileLocation {
|
||||
|
||||
/**
|
||||
* URL of the remote file.
|
||||
*/
|
||||
private URL url;
|
||||
|
||||
/**
|
||||
* A prettied-up full path of the URL (password removed, etc.).
|
||||
*/
|
||||
private String fileFullPath;
|
||||
|
||||
/**
|
||||
* A prettied-up filename (leading slash, and possibly "<code>%2F</code>",
|
||||
* removed).
|
||||
*/
|
||||
private String fileName;
|
||||
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param url The URL of the file.
|
||||
*/
|
||||
URLFileLocation(URL url) {
|
||||
this.url = url;
|
||||
fileFullPath = createFileFullPath();
|
||||
fileName = createFileName();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a "prettied-up" URL to use. This will be stripped of
|
||||
* sensitive information such as passwords.
|
||||
*
|
||||
* @return The full path to use.
|
||||
*/
|
||||
private String createFileFullPath() {
|
||||
String fullPath = url.toString();
|
||||
fullPath = fullPath.replaceFirst("://([^:]+)(?:.+)@", "://$1@");
|
||||
return fullPath;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates the "prettied-up" filename to use.
|
||||
*
|
||||
* @return The base name of the file of this URL.
|
||||
*/
|
||||
private String createFileName() {
|
||||
String fileName = url.getPath();
|
||||
if (fileName.startsWith("/%2F/")) { // Absolute path
|
||||
fileName = fileName.substring(4);
|
||||
}
|
||||
else if (fileName.startsWith("/")) { // All others
|
||||
fileName = fileName.substring(1);
|
||||
}
|
||||
return fileName;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the last time this file was modified, or
|
||||
* {@link TextEditorPane#LAST_MODIFIED_UNKNOWN} if this value cannot be
|
||||
* computed (such as for a remote file).
|
||||
*
|
||||
* @return The last time this file was modified. This will always be
|
||||
* {@link TextEditorPane#LAST_MODIFIED_UNKNOWN} for URL's.
|
||||
*/
|
||||
@Override
|
||||
protected long getActualLastModified() {
|
||||
return TextEditorPane.LAST_MODIFIED_UNKNOWN;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public String getFileFullPath() {
|
||||
return fileFullPath;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public String getFileName() {
|
||||
return fileName;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
protected InputStream getInputStream() throws IOException {
|
||||
return url.openStream();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
protected OutputStream getOutputStream() throws IOException {
|
||||
return url.openConnection().getOutputStream();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether this file location is a local file.
|
||||
*
|
||||
* @return Whether this is a local file.
|
||||
* @see #isLocalAndExists()
|
||||
*/
|
||||
@Override
|
||||
public boolean isLocal() {
|
||||
return "file".equalsIgnoreCase(url.getProtocol());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether this file location is a local file and already
|
||||
* exists. This method always returns <code>false</code> since we
|
||||
* cannot check this value easily.
|
||||
*
|
||||
* @return <code>false</code> always.
|
||||
* @see #isLocal()
|
||||
*/
|
||||
@Override
|
||||
public boolean isLocalAndExists() {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
/*
|
||||
* 03/16/2013
|
||||
*
|
||||
* VisibleWhitespaceTokenPainter - Renders tokens in an instance of
|
||||
* RSyntaxTextArea, with special glyphs to denote spaces and tabs.
|
||||
*
|
||||
* 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.FontMetrics;
|
||||
import java.awt.Graphics2D;
|
||||
import javax.swing.text.TabExpander;
|
||||
|
||||
|
||||
/**
|
||||
* A token painter that visibly renders whitespace (spaces and tabs).<p>
|
||||
*
|
||||
* The current implementation paints as follows:
|
||||
* <ul>
|
||||
* <li>The first tab or space, if any, is found in the token.</li>
|
||||
* <li>If a tab was found, all characters up to it are painted as a
|
||||
* group.</li>
|
||||
* <li>If a space was found, all characters up to and including it are
|
||||
* painted (it is painted with a special symbol to denote it as
|
||||
* a space).</li>
|
||||
* <li>If neither a tab nor a whitespace was found, all characters in the
|
||||
* token are painted.</li>
|
||||
* <li>Repeat until all characters are painted.</li>
|
||||
* </ul>
|
||||
* This means that rendering hints are applied to all groups of characters
|
||||
* within a token, excluding whitespace and tabs.<p>
|
||||
*
|
||||
* A problem with this implementation is that FontMetrics.charsWidth() is still
|
||||
* used to calculate the width of a group of chars painted. Thus, the group of
|
||||
* characters will be painted with the rendering hints specified, but the
|
||||
* following tab (or group of characters if the current group was the end of a
|
||||
* token) will not necessarily be painted at the proper x-coordinate (as
|
||||
* FontMetrics.charsWidth() returns an <code>int</code> and not a
|
||||
* <code>float</code>). The way around this would be to calculate the token's
|
||||
* width in such a way that a float is returned (Font.getStringBounds()?).
|
||||
*
|
||||
* @author Robert Futrell
|
||||
* @version 1.0
|
||||
*/
|
||||
class VisibleWhitespaceTokenPainter extends DefaultTokenPainter {
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
protected float paintImpl(Token token, Graphics2D g, float x, float y,
|
||||
RSyntaxTextArea host, TabExpander e, float clipStart,
|
||||
boolean selected, boolean useSTC) {
|
||||
|
||||
int origX = (int)x;
|
||||
int textOffs = token.getTextOffset();
|
||||
char[] text = token.getTextArray();
|
||||
int end = textOffs + token.length();
|
||||
float nextX = x;
|
||||
int flushLen = 0;
|
||||
int flushIndex = textOffs;
|
||||
Color fg = useSTC ? host.getSelectedTextColor() :
|
||||
host.getForegroundForToken(token);
|
||||
Color bg = selected ? null : host.getBackgroundForToken(token);
|
||||
g.setFont(host.getFontForTokenType(token.getType()));
|
||||
FontMetrics fm = host.getFontMetricsForTokenType(token.getType());
|
||||
|
||||
int ascent = fm.getAscent();
|
||||
int height = fm.getHeight();
|
||||
|
||||
for (int i=textOffs; i<end; i++) {
|
||||
|
||||
switch (text[i]) {
|
||||
|
||||
case '\t':
|
||||
|
||||
// Fill in background.
|
||||
nextX = x+fm.charsWidth(text, flushIndex,flushLen);
|
||||
float nextNextX = e.nextTabStop(nextX, 0);
|
||||
if (bg!=null) {
|
||||
paintBackground(x,y, nextNextX-x,height, g,
|
||||
ascent, host, bg);
|
||||
}
|
||||
g.setColor(fg);
|
||||
|
||||
// Paint chars cached before the tab.
|
||||
if (flushLen > 0) {
|
||||
g.drawChars(text, flushIndex, flushLen, (int)x,(int)y);
|
||||
flushLen = 0;
|
||||
}
|
||||
flushIndex = i + 1;
|
||||
|
||||
// Draw an arrow representing the tab.
|
||||
int halfHeight = height / 2;
|
||||
int quarterHeight = halfHeight / 2;
|
||||
int ymid = (int)y - ascent + halfHeight;
|
||||
g.drawLine((int)nextX,ymid, (int)nextNextX,ymid);
|
||||
g.drawLine((int)nextNextX,ymid, (int)nextNextX-4,ymid-quarterHeight);
|
||||
g.drawLine((int)nextNextX,ymid, (int)nextNextX-4,ymid+quarterHeight);
|
||||
|
||||
x = nextNextX;
|
||||
break;
|
||||
|
||||
case ' ':
|
||||
|
||||
// NOTE: There is a little bit of a "fudge factor"
|
||||
// here when "smooth text" is enabled, as "width"
|
||||
// below may well not be the width given to the space
|
||||
// by fm.charsWidth() (it depends on how it places the
|
||||
// space with respect to the preceding character).
|
||||
// But, we assume the approximation is close enough for
|
||||
// our drawing a dot for the space.
|
||||
|
||||
// "flushLen+1" ensures text is aligned correctly (or,
|
||||
// aligned the same as in getWidth()).
|
||||
nextX = x+fm.charsWidth(text, flushIndex,flushLen+1);
|
||||
int width = fm.charWidth(' ');
|
||||
|
||||
// Paint background.
|
||||
if (bg!=null) {
|
||||
paintBackground(x,y, nextX-x,height, g,
|
||||
ascent, host, bg);
|
||||
}
|
||||
g.setColor(fg);
|
||||
|
||||
// Paint chars before space.
|
||||
if (flushLen>0) {
|
||||
g.drawChars(text, flushIndex, flushLen, (int)x,(int)y);
|
||||
flushLen = 0;
|
||||
}
|
||||
|
||||
// Paint a dot representing the space.
|
||||
int dotX = (int)(nextX - width/2f); // "2.0f" for FindBugs
|
||||
int dotY = (int)(y - ascent + height/2f); // Ditto
|
||||
g.drawLine(dotX, dotY, dotX, dotY);
|
||||
flushIndex = i + 1;
|
||||
x = nextX;
|
||||
break;
|
||||
|
||||
|
||||
case '\f':
|
||||
// ???
|
||||
// fall-through for now.
|
||||
|
||||
default:
|
||||
flushLen += 1;
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
nextX = x+fm.charsWidth(text, flushIndex,flushLen);
|
||||
|
||||
if (flushLen>0 && nextX>=clipStart) {
|
||||
if (bg!=null) {
|
||||
paintBackground(x,y, nextX-x,height, g,
|
||||
ascent, host, bg);
|
||||
}
|
||||
g.setColor(fg);
|
||||
g.drawChars(text, flushIndex, flushLen, (int)x,(int)y);
|
||||
}
|
||||
|
||||
if (host.getUnderlineForToken(token)) {
|
||||
g.setColor(fg);
|
||||
int y2 = (int)(y+1);
|
||||
g.drawLine(origX,y2, (int)nextX,y2);
|
||||
}
|
||||
|
||||
// Don't check if it's whitespace - some TokenMakers may return types
|
||||
// other than Token.WHITESPACE for spaces (such as Token.IDENTIFIER).
|
||||
// This also allows us to paint tab lines for MLC's.
|
||||
if (host.getPaintTabLines() && origX==host.getMargin().left) {// && isWhitespace()) {
|
||||
paintTabLines(token, origX, (int)y, (int)nextX, g, e, host);
|
||||
}
|
||||
|
||||
return nextX;
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,157 @@
|
||||
package org.fife.ui.rsyntaxtextarea;
|
||||
|
||||
//import java.awt.Graphics;
|
||||
//import java.awt.Rectangle;
|
||||
//import java.awt.Shape;
|
||||
|
||||
//import javax.swing.text.CompositeView;
|
||||
import javax.swing.text.Element;
|
||||
//import javax.swing.text.View;
|
||||
|
||||
|
||||
/**
|
||||
* Replacement for the old <code>WrappedSyntaxView</code> class, designed to
|
||||
* be faster with large wrapped documents. Heavily based off of
|
||||
* <code>BoxView</code>, but streamlined to only care about the y-axis, and
|
||||
* takes code folding into account.<p>
|
||||
*
|
||||
* This class is not currently used.
|
||||
*
|
||||
* @author Robert Futrell
|
||||
* @version 1.0
|
||||
*/
|
||||
public class WrappedSyntaxView2 {//extends CompositeView {
|
||||
|
||||
// private Rectangle tempRect;
|
||||
// private int[] cachedOffsets;
|
||||
// private int[] cachedSpans;
|
||||
// private boolean sizeRequirementsValid;
|
||||
|
||||
|
||||
public WrappedSyntaxView2(Element root) {
|
||||
//super(root);
|
||||
// tempRect = new Rectangle();
|
||||
// cachedOffsets = new int[0];
|
||||
// cachedSpans = new int[0];
|
||||
// sizeRequirementsValid = false;
|
||||
}
|
||||
|
||||
|
||||
// protected void childAllocation(int index, Rectangle alloc) {
|
||||
// alloc.y += getOffset(index);
|
||||
// alloc.height = getHeight(index);
|
||||
// }
|
||||
//
|
||||
//
|
||||
// private int getHeight(int childIndex) {
|
||||
// return cachedSpans[childIndex];
|
||||
// }
|
||||
//
|
||||
//
|
||||
// private int getOffset(int childIndex) {
|
||||
// return cachedOffsets[childIndex];
|
||||
// }
|
||||
//
|
||||
//
|
||||
// protected View getViewAtPoint(int x, int y, Rectangle alloc) {
|
||||
// // TODO Auto-generated method stub
|
||||
// return null;
|
||||
// }
|
||||
//
|
||||
//
|
||||
// /**
|
||||
// * @param alloc The allocated region; this is the area inside of the insets
|
||||
// * @return Whether the point lies after the region.
|
||||
// */
|
||||
// protected boolean isAfter(int x, int y, Rectangle alloc) {
|
||||
// return y > (alloc.y + alloc.height);
|
||||
// }
|
||||
//
|
||||
//
|
||||
// /**
|
||||
// * @param alloc The allocated region; this is the area inside of the insets
|
||||
// * @return Whether the point lies before the region.
|
||||
// */
|
||||
// protected boolean isBefore(int x, int y, Rectangle alloc) {
|
||||
// return y < alloc.y;
|
||||
// }
|
||||
//
|
||||
//
|
||||
// public float getPreferredSpan(int axis) {
|
||||
// if (axis==X_AXIS) {
|
||||
// return preferredWidth + getLeftInset() + getRightInset();
|
||||
// }
|
||||
// else {
|
||||
// return preferredHeight + getTopInset() + getBottomInset();
|
||||
// }
|
||||
// }
|
||||
//
|
||||
//
|
||||
// public void paint(Graphics g, Shape allocation) {
|
||||
//
|
||||
// Rectangle alloc = (allocation instanceof Rectangle) ?
|
||||
// (Rectangle)allocation : allocation.getBounds();
|
||||
// int n = getViewCount();
|
||||
//
|
||||
// int x = alloc.x + getLeftInset();
|
||||
// int y = alloc.y + getTopInset();
|
||||
// Rectangle clip = g.getClipBounds();
|
||||
// int preferredWidth = (int)getPreferredSpan(X_AXIS);
|
||||
//
|
||||
// for (int i = 0; i < n; i++) {
|
||||
// tempRect.x = x;
|
||||
// tempRect.y = y + getOffset(i);
|
||||
// tempRect.width = preferredWidth;
|
||||
// tempRect.height = getHeight(i);
|
||||
// if (tempRect.intersects(clip)) {
|
||||
// paintChild(g, tempRect, i);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// }
|
||||
//
|
||||
//
|
||||
// /**
|
||||
// * Called when a child view's preferred span changes. This invalidates
|
||||
// * our layout cache and calls the super implementation.
|
||||
// */
|
||||
// public void preferenceChanged(View child, boolean widthPreferenceChanged,
|
||||
// boolean heightPreferenceChanged) {
|
||||
//
|
||||
// if (heightPreferenceChanged) {
|
||||
// sizeRequirementsValid = false;
|
||||
//// majorAllocValid = false;
|
||||
// }
|
||||
//// if (width) {
|
||||
//// minorReqValid = false;
|
||||
//// minorAllocValid = false;
|
||||
//// }
|
||||
//
|
||||
// super.preferenceChanged(child, widthPreferenceChanged, heightPreferenceChanged);
|
||||
//
|
||||
// }
|
||||
//
|
||||
// public void replace(int index, int length, View[] elems) {
|
||||
//
|
||||
// super.replace(index, length, elems);
|
||||
//
|
||||
// // Invalidate cache
|
||||
// int insertCount = elems==null ? 0 : elems.length;
|
||||
// cachedOffsets = updateLayoutArray(cachedOffsets, index, insertCount);
|
||||
// majorReqValid = false;
|
||||
// majorAllocValid = false;
|
||||
//
|
||||
// }
|
||||
//
|
||||
//
|
||||
// private int[] updateLayoutArray(int[] oldArray, int offset, int nInserted) {
|
||||
// int n = getViewCount(); // Called after super.replace() so this is accurate
|
||||
// int[] newArray = new int[n];
|
||||
// System.arraycopy(oldArray, 0, newArray, 0, offset);
|
||||
// System.arraycopy(oldArray, offset,
|
||||
// newArray, offset + nInserted, n - nInserted - offset);
|
||||
// return newArray;
|
||||
// }
|
||||
//
|
||||
|
||||
}
|
||||
@@ -0,0 +1,232 @@
|
||||
/*
|
||||
* 03/09/2013
|
||||
*
|
||||
* XmlOccurrenceMarker - Marks occurrences of the current token for XML.
|
||||
*
|
||||
* 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.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.swing.text.BadLocationException;
|
||||
import javax.swing.text.Element;
|
||||
|
||||
import org.fife.ui.rtextarea.SmartHighlightPainter;
|
||||
|
||||
|
||||
/**
|
||||
* Marks occurrences of the current token for XML.
|
||||
*
|
||||
* @author Robert Futrell
|
||||
* @version 1.0
|
||||
*/
|
||||
public class XmlOccurrenceMarker implements OccurrenceMarker {
|
||||
|
||||
private static final char[] CLOSE_TAG_START = { '<', '/' };
|
||||
private static final char[] TAG_SELF_CLOSE = { '/', '>' };
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public Token getTokenToMark(RSyntaxTextArea textArea) {
|
||||
return HtmlOccurrenceMarker.getTagNameTokenForCaretOffset(
|
||||
textArea, this);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public boolean isValidType(RSyntaxTextArea textArea, Token t) {
|
||||
return textArea.getMarkOccurrencesOfTokenType(t.getType());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void markOccurrences(RSyntaxDocument doc, Token t,
|
||||
RSyntaxTextAreaHighlighter h, SmartHighlightPainter p) {
|
||||
|
||||
char[] lexeme = t.getLexeme().toCharArray();
|
||||
int tokenOffs = t.getOffset();
|
||||
Element root = doc.getDefaultRootElement();
|
||||
int lineCount = root.getElementCount();
|
||||
int curLine = root.getElementIndex(t.getOffset());
|
||||
int depth = 0;
|
||||
|
||||
// For now, we only check for tags on the current line, for
|
||||
// simplicity. Tags spanning multiple lines aren't common anyway.
|
||||
boolean found = false;
|
||||
boolean forward = true;
|
||||
t = doc.getTokenListForLine(curLine);
|
||||
while (t!=null && t.isPaintable()) {
|
||||
if (t.getType()==Token.MARKUP_TAG_DELIMITER) {
|
||||
if (t.isSingleChar('<') && t.getOffset()+1==tokenOffs) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
else if (t.is(CLOSE_TAG_START) && t.getOffset()+2==tokenOffs) {
|
||||
found = true;
|
||||
forward = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
t = t.getNextToken();
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (forward) {
|
||||
|
||||
t = t.getNextToken().getNextToken();
|
||||
|
||||
do {
|
||||
|
||||
while (t!=null && t.isPaintable()) {
|
||||
if (t.getType()==Token.MARKUP_TAG_DELIMITER) {
|
||||
if (t.is(CLOSE_TAG_START)) {
|
||||
Token match = t.getNextToken();
|
||||
if (match!=null && match.is(lexeme)) {
|
||||
if (depth>0) {
|
||||
depth--;
|
||||
}
|
||||
else {
|
||||
try {
|
||||
int end = match.getOffset() + match.length();
|
||||
h.addMarkedOccurrenceHighlight(match.getOffset(), end, p);
|
||||
end = tokenOffs + match.length();
|
||||
h.addMarkedOccurrenceHighlight(tokenOffs, end, p);
|
||||
} catch (BadLocationException ble) {
|
||||
ble.printStackTrace(); // Never happens
|
||||
}
|
||||
return; // We're done!
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (t.isSingleChar('<')) {
|
||||
t = t.getNextToken();
|
||||
if (t!=null && t.is(lexeme)) {
|
||||
depth++;
|
||||
}
|
||||
}
|
||||
}
|
||||
t = t==null ? null : t.getNextToken();
|
||||
}
|
||||
|
||||
if (++curLine<lineCount) {
|
||||
t = doc.getTokenListForLine(curLine);
|
||||
}
|
||||
|
||||
} while (curLine<lineCount);
|
||||
|
||||
|
||||
}
|
||||
|
||||
else { // !forward
|
||||
|
||||
// Idea: Get all opening and closing tags of the relevant type on
|
||||
// the current line. Find the opening tag paired to the closing
|
||||
// tag we found originally; if it's not on this line, keep going
|
||||
// to the previous line.
|
||||
|
||||
List<Entry> openCloses = new ArrayList<Entry>();
|
||||
boolean inPossibleMatch = false;
|
||||
t = doc.getTokenListForLine(curLine);
|
||||
final int endBefore = tokenOffs - 2; // Stop before "</".
|
||||
|
||||
do {
|
||||
|
||||
while (t!=null && t.getOffset()<endBefore && t.isPaintable()) {
|
||||
if (t.getType()==Token.MARKUP_TAG_DELIMITER) {
|
||||
if (t.isSingleChar('<')) {
|
||||
Token next = t.getNextToken();
|
||||
if (next!=null) {
|
||||
if (next.is(lexeme)) {
|
||||
openCloses.add(new Entry(true, next));
|
||||
inPossibleMatch = true;
|
||||
}
|
||||
else {
|
||||
inPossibleMatch = false;
|
||||
}
|
||||
t = next;
|
||||
}
|
||||
}
|
||||
else if (t.isSingleChar('>')) {
|
||||
inPossibleMatch = false;
|
||||
}
|
||||
else if (inPossibleMatch && t.is(TAG_SELF_CLOSE)) {
|
||||
openCloses.remove(openCloses.size()-1);
|
||||
inPossibleMatch = false;
|
||||
}
|
||||
else if (t.is(CLOSE_TAG_START)) {
|
||||
Token next = t.getNextToken();
|
||||
if (next!=null) {
|
||||
// Invalid XML might not have a match
|
||||
if (next.is(lexeme)) {
|
||||
openCloses.add(new Entry(false, next));
|
||||
}
|
||||
t = next;
|
||||
}
|
||||
}
|
||||
}
|
||||
t = t.getNextToken();
|
||||
}
|
||||
|
||||
for (int i=openCloses.size()-1; i>=0; i--) {
|
||||
Entry entry = openCloses.get(i);
|
||||
depth += entry.open ? -1 : 1;
|
||||
if (depth==-1) {
|
||||
try {
|
||||
Token match = entry.t;
|
||||
int end = match.getOffset() + match.length();
|
||||
h.addMarkedOccurrenceHighlight(match.getOffset(), end, p);
|
||||
end = tokenOffs + match.length();
|
||||
h.addMarkedOccurrenceHighlight(tokenOffs, end, p);
|
||||
} catch (BadLocationException ble) {
|
||||
ble.printStackTrace(); // Never happens
|
||||
}
|
||||
openCloses.clear();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
openCloses.clear();
|
||||
if (--curLine>=0) {
|
||||
t = doc.getTokenListForLine(curLine);
|
||||
}
|
||||
|
||||
} while (curLine>=0);
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Used internally when searching backward for a matching "open" tag.
|
||||
*/
|
||||
private static class Entry {
|
||||
|
||||
private boolean open;
|
||||
private Token t;
|
||||
|
||||
Entry(boolean open, Token t) {
|
||||
this.open = open;
|
||||
this.t = t;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,389 @@
|
||||
/*
|
||||
* 07/29/2009
|
||||
*
|
||||
* FocusableTip.java - A focusable tool tip, like those in Eclipse.
|
||||
*
|
||||
* This library is distributed under a modified BSD license. See the included
|
||||
* RSyntaxTextArea.License.txt file for details.
|
||||
*/
|
||||
package org.fife.ui.rsyntaxtextarea.focusabletip;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.awt.ComponentOrientation;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Point;
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.Window;
|
||||
import java.awt.event.ComponentEvent;
|
||||
import java.awt.event.ComponentListener;
|
||||
import java.awt.event.FocusEvent;
|
||||
import java.awt.event.FocusListener;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.awt.event.KeyListener;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.net.URL;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
import javax.swing.JTextArea;
|
||||
import javax.swing.SwingUtilities;
|
||||
import javax.swing.ToolTipManager;
|
||||
import javax.swing.event.CaretEvent;
|
||||
import javax.swing.event.CaretListener;
|
||||
import javax.swing.event.HyperlinkListener;
|
||||
import javax.swing.event.MouseInputAdapter;
|
||||
|
||||
import org.fife.ui.rsyntaxtextarea.PopupWindowDecorator;
|
||||
|
||||
|
||||
/**
|
||||
* A focusable tool tip, similar to those found in Eclipse. The user
|
||||
* can click in the tip and it becomes a "real," resizable window.
|
||||
*
|
||||
* @author Robert Futrell
|
||||
* @version 1.0
|
||||
*/
|
||||
public class FocusableTip {
|
||||
|
||||
private JTextArea textArea;
|
||||
private TipWindow tipWindow;
|
||||
private URL imageBase;
|
||||
private TextAreaListener textAreaListener;
|
||||
private HyperlinkListener hyperlinkListener;
|
||||
private String lastText;
|
||||
|
||||
private Dimension maxSize; // null to automatic.
|
||||
|
||||
/**
|
||||
* The screen bounds in which the mouse has to stay for the currently
|
||||
* displayed tip to stay visible.
|
||||
*/
|
||||
private Rectangle tipVisibleBounds;
|
||||
|
||||
/**
|
||||
* Margin from mouse cursor at which to draw focusable tip.
|
||||
*/
|
||||
private static final int X_MARGIN = 18;
|
||||
|
||||
/**
|
||||
* Margin from mouse cursor at which to draw focusable tip.
|
||||
*/
|
||||
private static final int Y_MARGIN = 12;
|
||||
|
||||
private static final ResourceBundle MSG = ResourceBundle.getBundle(
|
||||
"org.fife.ui.rsyntaxtextarea.focusabletip.FocusableTip");
|
||||
|
||||
|
||||
public FocusableTip(JTextArea textArea, HyperlinkListener listener) {
|
||||
setTextArea(textArea);
|
||||
this.hyperlinkListener = listener;
|
||||
textAreaListener = new TextAreaListener();
|
||||
tipVisibleBounds = new Rectangle();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Compute the bounds in which the user can move the mouse without the
|
||||
* tip window disappearing.
|
||||
*/
|
||||
private void computeTipVisibleBounds() {
|
||||
// Compute area that the mouse can move in without hiding the
|
||||
// tip window. Note that Java 1.4 can only detect mouse events
|
||||
// in Java windows, not globally.
|
||||
Rectangle r = tipWindow.getBounds();
|
||||
Point p = r.getLocation();
|
||||
SwingUtilities.convertPointFromScreen(p, textArea);
|
||||
r.setLocation(p);
|
||||
tipVisibleBounds.setBounds(r.x,r.y-15, r.width,r.height+15*2);
|
||||
}
|
||||
|
||||
|
||||
private void createAndShowTipWindow(final MouseEvent e, final String text) {
|
||||
|
||||
Window owner = SwingUtilities.getWindowAncestor(textArea);
|
||||
tipWindow = new TipWindow(owner, this, text);
|
||||
tipWindow.setHyperlinkListener(hyperlinkListener);
|
||||
|
||||
// Give apps a chance to decorate us with drop shadows, etc.
|
||||
PopupWindowDecorator decorator = PopupWindowDecorator.get();
|
||||
if (decorator!=null) {
|
||||
decorator.decorate(tipWindow);
|
||||
}
|
||||
|
||||
// TODO: Position tip window better (handle RTL, edges of screen, etc.).
|
||||
// Wrap in an invokeLater() to work around a JEditorPane issue where it
|
||||
// doesn't return its proper preferred size until after it is displayed.
|
||||
// See http://forums.sun.com/thread.jspa?forumID=57&threadID=574810
|
||||
// for a discussion.
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
// If a new FocusableTip is requested while another one is
|
||||
// *focused* and visible, the focused tip (i.e. "tipWindow")
|
||||
// will be disposed of. If this Runnable is run after the
|
||||
// dispose(), tipWindow will be null. All of this is done on
|
||||
// the EDT so no synchronization should be necessary.
|
||||
if (tipWindow==null) {
|
||||
return;
|
||||
}
|
||||
|
||||
tipWindow.fixSize();
|
||||
ComponentOrientation o = textArea.getComponentOrientation();
|
||||
|
||||
Point p = e.getPoint();
|
||||
SwingUtilities.convertPointToScreen(p, textArea);
|
||||
|
||||
// Ensure tool tip is in the window bounds.
|
||||
// Multi-monitor support - make sure the completion window (and
|
||||
// description window, if applicable) both fit in the same
|
||||
// window in a multi-monitor environment. To do this, we decide
|
||||
// which monitor the rectangle "p" is in, and use that one.
|
||||
Rectangle sb = TipUtil.getScreenBoundsForPoint(p.x, p.y);
|
||||
//Dimension ss = tipWindow.getToolkit().getScreenSize();
|
||||
|
||||
// Try putting our stuff "below" the mouse first.
|
||||
int y = p.y + Y_MARGIN;
|
||||
if (y+tipWindow.getHeight()>=sb.y+sb.height) {
|
||||
y = p.y - Y_MARGIN - tipWindow.getHeight();
|
||||
}
|
||||
|
||||
// Get x-coordinate of completions. Try to align left edge
|
||||
// with the mouse first (with a slight margin).
|
||||
int x = p.x - X_MARGIN; // ltr
|
||||
if (!o.isLeftToRight()) {
|
||||
x = p.x - tipWindow.getWidth() + X_MARGIN;
|
||||
}
|
||||
if (x<sb.x) {
|
||||
x = sb.x;
|
||||
}
|
||||
else if (x+tipWindow.getWidth()>sb.x+sb.width) { // completions don't fit
|
||||
x = sb.x + sb.width - tipWindow.getWidth();
|
||||
}
|
||||
|
||||
tipWindow.setLocation(x, y);
|
||||
tipWindow.setVisible(true);
|
||||
|
||||
computeTipVisibleBounds(); // Do after tip is visible
|
||||
textAreaListener.install(textArea);
|
||||
lastText = text;
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the base URL to use when loading images in this focusable tip.
|
||||
*
|
||||
* @return The base URL to use.
|
||||
* @see #setImageBase(URL)
|
||||
*/
|
||||
public URL getImageBase() {
|
||||
return imageBase;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The maximum size for unfocused tool tips.
|
||||
*
|
||||
* @return The maximum size for unfocused tool tips. A value of
|
||||
* <code>null</code> will use a default size.
|
||||
* @see #setMaxSize(Dimension)
|
||||
*/
|
||||
public Dimension getMaxSize() {
|
||||
return maxSize;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns localized text for the given key.
|
||||
*
|
||||
* @param key The key into the resource bundle.
|
||||
* @return The localized text.
|
||||
*/
|
||||
static String getString(String key) {
|
||||
return MSG.getString(key);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Disposes of the focusable tip currently displayed, if any.
|
||||
*/
|
||||
public void possiblyDisposeOfTipWindow() {
|
||||
if (tipWindow != null) {
|
||||
tipWindow.dispose();
|
||||
tipWindow = null;
|
||||
textAreaListener.uninstall();
|
||||
tipVisibleBounds.setBounds(-1, -1, 0, 0);
|
||||
lastText = null;
|
||||
textArea.requestFocus();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void removeListeners() {
|
||||
//System.out.println("DEBUG: Removing text area listeners");
|
||||
textAreaListener.uninstall();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the base URL to use when loading images in this focusable tip.
|
||||
*
|
||||
* @param url The base URL to use.
|
||||
* @see #getImageBase()
|
||||
*/
|
||||
public void setImageBase(URL url) {
|
||||
imageBase = url;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the maximum size for unfocused tool tips.
|
||||
*
|
||||
* @param maxSize The new maximum size. A value of <code>null</code> will
|
||||
* cause a default size to be used.
|
||||
* @see #getMaxSize()
|
||||
*/
|
||||
public void setMaxSize(Dimension maxSize) {
|
||||
this.maxSize = maxSize;
|
||||
}
|
||||
|
||||
|
||||
private void setTextArea(JTextArea textArea) {
|
||||
this.textArea = textArea;
|
||||
// Is okay to do multiple times.
|
||||
ToolTipManager.sharedInstance().registerComponent(textArea);
|
||||
}
|
||||
|
||||
|
||||
public void toolTipRequested(MouseEvent e, String text) {
|
||||
|
||||
if (text==null || text.length()==0) {
|
||||
possiblyDisposeOfTipWindow();
|
||||
lastText = text;
|
||||
return;
|
||||
}
|
||||
|
||||
if (lastText==null || text.length()!=lastText.length() ||
|
||||
!text.equals(lastText)) {
|
||||
possiblyDisposeOfTipWindow();
|
||||
createAndShowTipWindow(e, text);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Listens for events in a text area.
|
||||
*/
|
||||
private class TextAreaListener extends MouseInputAdapter implements
|
||||
CaretListener, ComponentListener, FocusListener, KeyListener {
|
||||
|
||||
@Override
|
||||
public void caretUpdate(CaretEvent e) {
|
||||
Object source = e.getSource();
|
||||
if (source == textArea) {
|
||||
possiblyDisposeOfTipWindow();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void componentHidden(ComponentEvent e) {
|
||||
handleComponentEvent(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void componentMoved(ComponentEvent e) {
|
||||
handleComponentEvent(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void componentResized(ComponentEvent e) {
|
||||
handleComponentEvent(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void componentShown(ComponentEvent e) {
|
||||
handleComponentEvent(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void focusGained(FocusEvent e) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void focusLost(FocusEvent e) {
|
||||
// Only dispose of tip if it wasn't the TipWindow that was clicked
|
||||
// "c" can be null, at least on OS X, so we must check that before
|
||||
// calling SwingUtilities.getWindowAncestor() to guard against an
|
||||
// NPE.
|
||||
Component c = e.getOppositeComponent();
|
||||
boolean tipClicked = (c instanceof TipWindow) ||
|
||||
(c!=null &&
|
||||
SwingUtilities.getWindowAncestor(c) instanceof TipWindow);
|
||||
if (!tipClicked) {
|
||||
possiblyDisposeOfTipWindow();
|
||||
}
|
||||
}
|
||||
|
||||
private void handleComponentEvent(ComponentEvent e) {
|
||||
possiblyDisposeOfTipWindow();
|
||||
}
|
||||
|
||||
public void install(JTextArea textArea) {
|
||||
textArea.addCaretListener(this);
|
||||
textArea.addComponentListener(this);
|
||||
textArea.addFocusListener(this);
|
||||
textArea.addKeyListener(this);
|
||||
textArea.addMouseListener(this);
|
||||
textArea.addMouseMotionListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void keyPressed(KeyEvent e) {
|
||||
if (e.getKeyCode()==KeyEvent.VK_ESCAPE) {
|
||||
possiblyDisposeOfTipWindow();
|
||||
}
|
||||
else if (e.getKeyCode()==KeyEvent.VK_F2) {
|
||||
if (tipWindow!=null && !tipWindow.getFocusableWindowState()) {
|
||||
tipWindow.actionPerformed(null);
|
||||
e.consume(); // Don't do bookmarking stuff
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void keyReleased(KeyEvent e) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void keyTyped(KeyEvent e) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseExited(MouseEvent e) {
|
||||
// possiblyDisposeOfTipWindow();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseMoved(MouseEvent e) {
|
||||
if (tipVisibleBounds==null ||
|
||||
!tipVisibleBounds.contains(e.getPoint())) {
|
||||
possiblyDisposeOfTipWindow();
|
||||
}
|
||||
}
|
||||
|
||||
public void uninstall() {
|
||||
textArea.removeCaretListener(this);
|
||||
textArea.removeComponentListener(this);
|
||||
textArea.removeFocusListener(this);
|
||||
textArea.removeKeyListener(this);
|
||||
textArea.removeMouseListener(this);
|
||||
textArea.removeMouseMotionListener(this);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
FocusHotkey=Press 'F2' for focus
|
||||
@@ -0,0 +1 @@
|
||||
FocusHotkey=\u0627\u0636\u063a\u0637 'F2' \u0645\u0646 \u0627\u062c\u0644 \u0627\u0644\u062a\u0631\u0643\u064a\u0632
|
||||
@@ -0,0 +1 @@
|
||||
FocusHotkey=Zum Fokussieren 'F2' dr\u00fccken
|
||||
@@ -0,0 +1 @@
|
||||
FocusHotkey=Presionar 'F2' para enfocar
|
||||
@@ -0,0 +1 @@
|
||||
FocusHotkey=Press 'F2' pour se concentrer
|
||||
Loaded 100 of 352 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user