/*******************************************************************************
* Copyright (c) 2000, 2008 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
* Philippe Ombredanne <pombredanne@nexb.com> - https://bugs.eclipse.org/bugs/show_bug.cgi?id=150989
* Anton Leherbauer (Wind River Systems) - [misc] Allow custom token for WhitespaceRule - https://bugs.eclipse.org/bugs/show_bug.cgi?id=251224
* Andrew Eisenberg - adapted for use in Groovy Eclipse
*******************************************************************************/
package eu.esdihumboldt.hale.ui.util.groovy.internal;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.core.runtime.Assert;
import org.eclipse.jface.text.rules.ICharacterScanner;
import org.eclipse.jface.text.rules.IRule;
import org.eclipse.jface.text.rules.IToken;
import org.eclipse.jface.text.rules.IWhitespaceDetector;
import org.eclipse.jface.text.rules.IWordDetector;
import org.eclipse.jface.text.rules.NumberRule;
import org.eclipse.jface.text.rules.SingleLineRule;
import org.eclipse.jface.text.rules.Token;
import org.eclipse.jface.text.rules.WhitespaceRule;
import eu.esdihumboldt.hale.ui.util.IColorManager;
import eu.esdihumboldt.hale.ui.util.groovy.internal.CombinedWordRule.WordMatcher;
/**
* A code scanner for Groovy files.
*
* Much of this class has been adapted from JavaCodeScanner.
*/
public class GroovyTagScanner extends AbstractJavaScanner {
/**
* Rule to detect java operators.
*
* @since 3.0
*/
protected class OperatorRule implements IRule {
/** Java operators */
private final char[] JAVA_OPERATORS = { ';', '(', ')', '{', '}', '.', '=', '/', '\\', '+',
'-', '*', '[', ']', '<', '>', ':', '?', '!', ',', '|', '&', '^', '%', '~' };
/** Token to return for this rule */
private final IToken fToken;
/**
* Creates a new operator rule.
*
* @param token Token to use for this rule
*/
public OperatorRule(IToken token) {
fToken = token;
}
/**
* Is this character an operator character?
*
* @param character Character to determine whether it is an operator
* character
* @return <code>true</code> iff the character is an operator,
* <code>false</code> otherwise.
*/
public boolean isOperator(char character) {
for (int index = 0; index < JAVA_OPERATORS.length; index++) {
if (JAVA_OPERATORS[index] == character)
return true;
}
return false;
}
/*
* @see
* org.eclipse.jface.text.rules.IRule#evaluate(org.eclipse.jface.text
* .rules.ICharacterScanner)
*/
@Override
public IToken evaluate(ICharacterScanner scanner) {
int character = scanner.read();
if (isOperator((char) character)) {
do {
character = scanner.read();
} while (isOperator((char) character));
scanner.unread();
return fToken;
}
else {
scanner.unread();
return Token.UNDEFINED;
}
}
}
/**
* Rule to detect java brackets.
*
* @since 3.3
*/
private static final class BracketRule implements IRule {
/** Java brackets */
private final char[] JAVA_BRACKETS = { '(', ')', '{', '}', '[', ']' };
/** Token to return for this rule */
private final IToken fToken;
/**
* Creates a new bracket rule.
*
* @param token Token to use for this rule
*/
public BracketRule(IToken token) {
fToken = token;
}
/**
* Is this character a bracket character?
*
* @param character Character to determine whether it is a bracket
* character
* @return <code>true</code> iff the character is a bracket,
* <code>false</code> otherwise.
*/
public boolean isBracket(char character) {
for (int index = 0; index < JAVA_BRACKETS.length; index++) {
if (JAVA_BRACKETS[index] == character)
return true;
}
return false;
}
/*
* @see
* org.eclipse.jface.text.rules.IRule#evaluate(org.eclipse.jface.text
* .rules.ICharacterScanner)
*/
@Override
public IToken evaluate(ICharacterScanner scanner) {
int character = scanner.read();
if (isBracket((char) character)) {
do {
character = scanner.read();
} while (isBracket((char) character));
scanner.unread();
return fToken;
}
else {
scanner.unread();
return Token.UNDEFINED;
}
}
}
/**
* An annotation rule matches the '@' symbol, any following whitespace and a
* following java identifier or the <code>interface</code> keyword.
*
* It does not match if there is a comment between the '@' symbol and the
* identifier. See https://bugs.eclipse.org/bugs/show_bug.cgi?id=82452
*
* @since 3.1
*/
private static class AnnotationRule implements IRule {
/**
* A resettable scanner supports marking a position in a scanner and
* unreading back to the marked position.
*/
private static final class ResettableScanner implements ICharacterScanner {
private final ICharacterScanner fDelegate;
private int fReadCount;
/**
* Creates a new resettable scanner that will forward calls to
* <code>scanner</code>, but store a marked position.
*
* @param scanner the delegate scanner
*/
public ResettableScanner(final ICharacterScanner scanner) {
Assert.isNotNull(scanner);
fDelegate = scanner;
mark();
}
/*
* @see org.eclipse.jface.text.rules.ICharacterScanner#getColumn()
*/
@Override
public int getColumn() {
return fDelegate.getColumn();
}
/*
* @see
* org.eclipse.jface.text.rules.ICharacterScanner#getLegalLineDelimiters
* ()
*/
@Override
public char[][] getLegalLineDelimiters() {
return fDelegate.getLegalLineDelimiters();
}
/*
* @see org.eclipse.jface.text.rules.ICharacterScanner#read()
*/
@Override
public int read() {
int ch = fDelegate.read();
if (ch != ICharacterScanner.EOF)
fReadCount++;
return ch;
}
/*
* @see org.eclipse.jface.text.rules.ICharacterScanner#unread()
*/
@Override
public void unread() {
if (fReadCount > 0)
fReadCount--;
fDelegate.unread();
}
/**
* Marks an offset in the scanned content.
*/
public void mark() {
fReadCount = 0;
}
/**
* Resets the scanner to the marked position.
*/
public void reset() {
while (fReadCount > 0)
unread();
while (fReadCount < 0)
read();
}
}
private final IWhitespaceDetector fWhitespaceDetector = new JavaWhitespaceDetector();
private final IWordDetector fWordDetector = new JavaWordDetector();
private final IToken fInterfaceToken;
private final IToken fAnnotationToken;
/**
* Creates a new rule.
*
* @param interfaceToken the token to return if
* <code>'@\s*interface'</code> is matched
* @param annotationToken the token to return if <code>'@\s*\w+'</code>
* is matched, but not <code>'@\s*interface'</code>
*/
public AnnotationRule(IToken interfaceToken, Token annotationToken) {
fInterfaceToken = interfaceToken;
fAnnotationToken = annotationToken;
}
/*
* @see
* org.eclipse.jface.text.rules.IRule#evaluate(org.eclipse.jface.text
* .rules.ICharacterScanner)
*/
@Override
public IToken evaluate(ICharacterScanner scanner) {
ResettableScanner resettable = new ResettableScanner(scanner);
if (resettable.read() == '@')
if (skipWhitespace(resettable))
return readAnnotation(resettable);
resettable.reset();
return Token.UNDEFINED;
}
private IToken readAnnotation(ResettableScanner scanner) {
StringBuffer buffer = new StringBuffer();
if (!readIdentifier(scanner, buffer)) {
scanner.reset();
return Token.UNDEFINED;
}
if ("interface".equals(buffer.toString())) //$NON-NLS-1$
return fInterfaceToken;
while (readSegment(new ResettableScanner(scanner))) {
// do nothing
}
return fAnnotationToken;
}
private boolean readSegment(ResettableScanner scanner) {
scanner.mark();
if (skipWhitespace(scanner) && skipDot(scanner) && skipWhitespace(scanner)
&& readIdentifier(scanner, null))
return true;
scanner.reset();
return false;
}
private boolean skipDot(ICharacterScanner scanner) {
int ch = scanner.read();
if (ch == '.')
return true;
scanner.unread();
return false;
}
private boolean readIdentifier(ICharacterScanner scanner, StringBuffer buffer) {
int ch = scanner.read();
boolean read = false;
while (fWordDetector.isWordPart((char) ch)) {
if (buffer != null)
buffer.append((char) ch);
ch = scanner.read();
read = true;
}
if (ch != ICharacterScanner.EOF)
scanner.unread();
return read;
}
private boolean skipWhitespace(ICharacterScanner scanner) {
while (fWhitespaceDetector.isWhitespace((char) scanner.read())) {
// do nothing
}
scanner.unread();
return true;
}
}
private static String[] types = { "boolean", "byte", "char", "class", "double", "float", "int",
"interface", "long", "short", "void" };
private static String[] keywords = { "abstract", "break", "case", "catch", "const", "continue",
"def", "default", "do", "else", "enum", "extends", "final", "finally", "for", "goto",
"if", "implements", "import", "instanceof", "interface", "native", "new", "package",
"private", "protected",
"public",
// "return", use the special return keyword now so returns can be
// highlighted differently
"static", "super", "switch", "synchronized", "this", "throw", "throws", "transient",
"try", "volatile", "while", "true", "false", "null", "void" };
private static String[] groovyKeywords = { "as", "def", "assert", "in", };
private static String[] gjdkWords = { "abs", "any", "append", "asList", "asWritable", "call",
"collect", "compareTo", "count", "div", "dump", "each", "eachByte", "eachFile",
"eachLine", "every", "find", "findAll", "flatten", "getAt", "getErr", "getIn",
"getOut", "getText", "grep", "immutable", "inject", "inspect", "intersect",
"invokeMethods", "isCase", "it", "join", "leftShift", "minus", "multiply",
"newInputStream", "newOutputStream", "newPrintWriter", "newReader", "newWriter",
"next", "plus", "pop", "power", "previous", "print", "println", "push", "putAt",
"read", "readBytes", "readLines", "reverse", "reverseEach", "round", "size", "sort",
"splitEachLine", "step", "subMap", "times", "toInteger", "toList", "tokenize", "upto",
"use", "waitForOrKill", "withPrintWriter", "withReader", "withStream", "withWriter",
"withWriterAppend", "write", "writeLine", };
private static final String RETURN = "return"; //$NON-NLS-1$
private static String[] fgTokenProperties = { PreferenceConstants.GROOVY_EDITOR_DEFAULT_COLOR,
PreferenceConstants.GROOVY_EDITOR_HIGHLIGHT_GJDK_COLOR,
PreferenceConstants.GROOVY_EDITOR_HIGHLIGHT_JAVAKEYWORDS_COLOR,
PreferenceConstants.GROOVY_EDITOR_HIGHLIGHT_GROOVYKEYWORDS_COLOR,
PreferenceConstants.GROOVY_EDITOR_HIGHLIGHT_JAVATYPES_COLOR,
PreferenceConstants.GROOVY_EDITOR_HIGHLIGHT_NUMBERS_COLOR,
PreferenceConstants.GROOVY_EDITOR_HIGHLIGHT_ANNOTATION_COLOR,
PreferenceConstants.GROOVY_EDITOR_HIGHLIGHT_BRACKET_COLOR,
PreferenceConstants.GROOVY_EDITOR_HIGHLIGHT_OPERATOR_COLOR,
PreferenceConstants.GROOVY_EDITOR_HIGHLIGHT_RETURN_COLOR,
PreferenceConstants.GROOVY_EDITOR_HIGHLIGHT_STRINGS_COLOR,
PreferenceConstants.GROOVY_EDITOR_HIGHLIGHT_CUSTOM_KEYWORD_COLOR };
private final List<IRule> additionalRules;
private final List<String> additionalGroovyKeywords;
private final List<String> additionalGJDKWords;
private final List<String> customKeywords;
/**
* Constructor.
*
* @param manager the color manager
* @param customKeywords list of custom keywords or <code>null</code>
*/
public GroovyTagScanner(IColorManager manager, List<String> customKeywords) {
this(manager, null, null, null, customKeywords);
}
/**
* Constructor.
*
* @param manager the color manager
* @param additionalRules Additional scanner rules for sub-types to add new
* kinds of partitioning
* @param additionalGroovyKeywords Additional keywords for sub-types to add
* new kinds of groovy keyword syntax highlighting
* @param additionalGJDKKeywords Additional keywords for sub-types to add
* new kinds of gjdk syntax highlighting
* @param customKeywords list of custom keywords or <code>null</code>
*/
public GroovyTagScanner(IColorManager manager, List<IRule> additionalRules,
List<String> additionalGroovyKeywords, List<String> additionalGJDKKeywords,
List<String> customKeywords) {
super(manager, GroovyUIPlugin.getDefault().getPreferenceStore());
this.additionalRules = additionalRules;
this.additionalGroovyKeywords = additionalGroovyKeywords;
this.additionalGJDKWords = additionalGJDKKeywords;
this.customKeywords = customKeywords;
initialize();
}
@Override
protected String[] getTokenProperties() {
return fgTokenProperties;
}
/*
* @see AbstractJavaScanner#createRules()
*/
@Override
protected List<IRule> createRules() {
List<IRule> rules = new ArrayList<IRule>();
// Add rule for character constants.
Token token = getToken(PreferenceConstants.GROOVY_EDITOR_HIGHLIGHT_STRINGS_COLOR);
rules.add(new SingleLineRule("'", "'", token, '\\')); //$NON-NLS-2$ //$NON-NLS-1$
// Add generic whitespace rule.
rules.add(new WhitespaceRule(new JavaWhitespaceDetector()));
// Add JLS3 rule for /@\s*interface/
AnnotationRule atInterfaceRule = new AnnotationRule(
getToken(PreferenceConstants.GROOVY_EDITOR_HIGHLIGHT_ANNOTATION_COLOR),
getToken(PreferenceConstants.GROOVY_EDITOR_HIGHLIGHT_ANNOTATION_COLOR));
rules.add(atInterfaceRule);
// Numbers rule
rules.add(new NumberRule(
getToken(PreferenceConstants.GROOVY_EDITOR_HIGHLIGHT_NUMBERS_COLOR)));
// combined rule for all keywords
JavaWordDetector wordDetector = new JavaWordDetector();
token = getToken(PreferenceConstants.GROOVY_EDITOR_DEFAULT_COLOR);
CombinedWordRule combinedWordRule = new CombinedWordRule(wordDetector, token);
// Java keywords
WordMatcher javaKeywordsMatcher = new WordMatcher();
token = getToken(PreferenceConstants.GROOVY_EDITOR_HIGHLIGHT_JAVAKEYWORDS_COLOR);
for (int i = 0; i < keywords.length; i++) {
javaKeywordsMatcher.addWord(keywords[i], token);
}
combinedWordRule.addWordMatcher(javaKeywordsMatcher);
// Java types
WordMatcher javaTypesMatcher = new WordMatcher();
token = getToken(PreferenceConstants.GROOVY_EDITOR_HIGHLIGHT_JAVATYPES_COLOR);
for (int i = 0; i < types.length; i++) {
javaTypesMatcher.addWord(types[i], token);
}
combinedWordRule.addWordMatcher(javaTypesMatcher);
// Groovy Keywords, including additional keywords
WordMatcher groovyKeywordsMatcher = new WordMatcher();
token = getToken(PreferenceConstants.GROOVY_EDITOR_HIGHLIGHT_GROOVYKEYWORDS_COLOR);
for (int i = 0; i < groovyKeywords.length; i++) {
groovyKeywordsMatcher.addWord(groovyKeywords[i], token);
}
if (additionalGroovyKeywords != null) {
for (String additional : additionalGroovyKeywords) {
groovyKeywordsMatcher.addWord(additional, token);
}
}
combinedWordRule.addWordMatcher(groovyKeywordsMatcher);
// gjdk words, including additional keywords
WordMatcher gjdkWordsMatcher = new WordMatcher();
token = getToken(PreferenceConstants.GROOVY_EDITOR_HIGHLIGHT_GJDK_COLOR);
for (int i = 0; i < gjdkWords.length; i++) {
gjdkWordsMatcher.addWord(gjdkWords[i], token);
}
if (additionalGJDKWords != null) {
for (String additional : additionalGJDKWords) {
gjdkWordsMatcher.addWord(additional, token);
}
}
combinedWordRule.addWordMatcher(gjdkWordsMatcher);
// custom keywords
if (customKeywords != null && !customKeywords.isEmpty()) {
WordMatcher customWordsMatcher = new WordMatcher();
token = getToken(PreferenceConstants.GROOVY_EDITOR_HIGHLIGHT_CUSTOM_KEYWORD_COLOR);
for (String keyword : customKeywords) {
customWordsMatcher.addWord(keyword, token);
}
combinedWordRule.addWordMatcher(customWordsMatcher);
}
// Add rule for brackets
// this rule must come before the operator rule
token = getToken(PreferenceConstants.GROOVY_EDITOR_HIGHLIGHT_BRACKET_COLOR);
rules.add(new BracketRule(token));
// Add rule for operators
token = getToken(PreferenceConstants.GROOVY_EDITOR_HIGHLIGHT_OPERATOR_COLOR);
rules.add(new OperatorRule(token));
// Add word rule for keyword 'return'.
CombinedWordRule.WordMatcher returnWordRule = new CombinedWordRule.WordMatcher();
token = getToken(PreferenceConstants.GROOVY_EDITOR_HIGHLIGHT_RETURN_COLOR);
returnWordRule.addWord(RETURN, token);
combinedWordRule.addWordMatcher(returnWordRule);
rules.add(combinedWordRule);
// additional rules
if (additionalRules != null) {
rules.addAll(additionalRules);
}
setDefaultReturnToken(getToken(PreferenceConstants.GROOVY_EDITOR_DEFAULT_COLOR));
return rules;
}
}