Added source for ByteEdit

This commit is contained in:
SuspiciousActivity committed 2018-04-20 18:08:44 +02:00
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("&nbsp;&nbsp;&nbsp;- ");
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>&lt; 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, &gt;= 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 &gt;= 0
* @param offs1 the ending model offset &gt;= 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;
}
}
}
+199
View File
@@ -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;*lt;16) | (g&lt;*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();
}
}
}
+894
View File
@@ -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;
}
}
}
+567
View File
@@ -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 &gt;= <code>textOffset</code>
* and &lt; <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>&gt;= 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>&lt;</code>
* and <code>&gt;</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 ? "&nbsp;" : " ");
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("&nbsp;");
}
tabStr = stringBuilder.toString();
}
sb.append(tabsToSpaces ? tabStr : "&#09;");
lastWasSpace = false;
break;
case '&':
sb.append(text, lastI, i-lastI);
lastI = i+1;
sb.append("&amp;");
lastWasSpace = false;
break;
case '<':
sb.append(text, lastI, i-lastI);
lastI = i+1;
sb.append("&lt;");
lastWasSpace = false;
break;
case '>':
sb.append(text, lastI, i-lastI);
lastI = i+1;
sb.append("&gt;");
lastWasSpace = false;
break;
case '\'':
sb.append(text, lastI, i-lastI);
lastI = i+1;
sb.append("&#39;");
lastWasSpace = false;
break;
case '"':
sb.append(text, lastI, i-lastI);
lastI = i+1;
sb.append("&#34;");
lastWasSpace = false;
break;
case '/': // OWASP-recommended to escape even though unnecessary
sb.append(text, lastI, i-lastI);
lastI = i+1;
sb.append("&#47;");
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>&lt; 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>&lt; 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 &gt;= 0, so extensions of the TokenMaker
* class are free to internally use all ints &lt; 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] -&gt; [token2] -&gt; [token3] -&gt; [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] -&gt; [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] -&gt; [token2] -&gt; [token3] -&gt; [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] -&gt; [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