/**
* Copyright (c) 2005-2011 by Appcelerator, Inc. All Rights Reserved.
* Licensed under the terms of the Eclipse Public License (EPL).
* Please see the license.txt included with this distribution for details.
* Any modifications to this file must keep this entire header intact.
*/
/*
* @author: atotic, Scott Schlesier
* Created: March 5, 2005
*/
package org.python.pydev.editor;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
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.RuleBasedScanner;
import org.eclipse.jface.text.rules.Token;
import org.eclipse.jface.text.rules.WhitespaceRule;
import org.eclipse.jface.text.rules.WordRule;
import org.python.pydev.core.ArrayUtils;
import org.python.pydev.core.callbacks.ICallbackListener;
import org.python.pydev.ui.ColorAndStyleCache;
import com.aptana.shared_core.string.FastStringBuffer;
/**
* PyCodeScanner - A scanner that looks for python keywords and code
* and supports the updating of named colors through the colorCache
*
* GreatWhite, GreatKeywordDetector came from PyEditConfiguration
*/
public class PyCodeScanner extends RuleBasedScanner {
// keywords list has to be alphabetized for the keyword detector to work properly
static final public String[] DEFAULT_KEYWORDS = { "and", "as", "assert", "break", "class", "continue", "def",
"del", "elif", "else", "except", "exec", "finally", "for", "from", "global", "if", "import", "in", "is",
"lambda", "nonlocal", "not", "or", "pass", "print", "raise", "return", "self", "try", "while", "with",
"yield", "False", "None", "True",
};
static public String[] CYTHON_KEYWORDS;
static {
CYTHON_KEYWORDS = new String[] { "cimport", "cdef", "ctypedef" };
CYTHON_KEYWORDS = ArrayUtils.concatArrays(DEFAULT_KEYWORDS, CYTHON_KEYWORDS);
// keywords list has to be alphabetized for the keyword detector to work properly
Arrays.sort(CYTHON_KEYWORDS);
}
private ColorAndStyleCache colorCache;
private IToken keywordToken;
private IToken selfToken;
private IToken defaultToken;
private IToken decoratorToken;
private IToken numberToken;
private IToken classNameToken;
private IToken funcNameToken;
private IToken parensToken;
private IToken operatorsToken;
private String[] keywords;
private ICodeScannerKeywords codeScannerKeywords;
/**
* Whitespace detector.
*
* I know, naming the class after a band that burned
* is not funny, but I've got to get my brain off my
* annoyance with the abstractions of JFace.
* So many classes and interfaces for a single method?
* f$%@#$!!
*/
static private class GreatWhite implements IWhitespaceDetector {
public boolean isWhitespace(char c) {
return Character.isWhitespace(c);
}
}
/**
* Python keyword detector
*/
static private class GreatKeywordDetector implements IWordDetector {
public GreatKeywordDetector() {
}
public boolean isWordStart(char c) {
return Character.isJavaIdentifierStart(c);
}
public boolean isWordPart(char c) {
return Character.isJavaIdentifierPart(c);
}
}
static private class DecoratorDetector implements IWordDetector {
/**
* @see org.eclipse.jface.text.rules.IWordDetector#isWordStart(char)
*/
public boolean isWordStart(char c) {
return c == '@';
}
/**
* @see org.eclipse.jface.text.rules.IWordDetector#isWordPart(char)
*/
public boolean isWordPart(char c) {
return c != '\n' && c != '\r' && c != '(';
}
}
static public class NumberDetector implements IWordDetector {
/**
* Used to keep the state of the token
*/
private FastStringBuffer buffer = new FastStringBuffer();
/**
* Defines if we are at an hexa number
*/
private boolean isInHexa;
/**
* @see org.eclipse.jface.text.rules.IWordDetector#isWordStart(char)
*/
public boolean isWordStart(char c) {
isInHexa = false;
buffer.clear();
buffer.append(c);
return Character.isDigit(c);
}
/**
* Check if we are still in the number
*/
public boolean isWordPart(char c) {
//ok, we have to test for scientific notation e.g.: 10.9e10
if ((c == 'x' || c == 'X') && buffer.length() == 1 && buffer.charAt(0) == '0') {
//it is an hexadecimal
buffer.append(c);
isInHexa = true;
return true;
} else {
buffer.append(c);
}
if (isInHexa) {
return Character.isDigit(c) || c == 'a' || c == 'A' || c == 'b' || c == 'B' || c == 'c' || c == 'C'
|| c == 'd' || c == 'D' || c == 'e' || c == 'E' || c == 'f' || c == 'F';
} else {
return Character.isDigit(c) || c == 'e' || c == '.';
}
}
}
public PyCodeScanner(ColorAndStyleCache colorCache) {
this(colorCache, DEFAULT_KEYWORDS);
}
public PyCodeScanner(ColorAndStyleCache colorCache, String[] keywords) {
super();
this.keywords = keywords;
this.colorCache = colorCache;
setupRules();
}
/**
* @param colorCache2
* @param codeScannerKeywords
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public PyCodeScanner(ColorAndStyleCache colorCache, ICodeScannerKeywords codeScannerKeywords) {
super();
this.colorCache = colorCache;
this.codeScannerKeywords = codeScannerKeywords;
this.keywords = codeScannerKeywords.getKeywords();
setupRules();
codeScannerKeywords.getOnChangeCallbackWithListeners().registerListener(new ICallbackListener() {
public Object call(Object obj) {
keywords = PyCodeScanner.this.codeScannerKeywords.getKeywords();
setupRules();
return null;
}
});
}
public void updateColors() {
setupRules();
}
private void setupRules() {
keywordToken = new Token(colorCache.getKeywordTextAttribute());
selfToken = new Token(colorCache.getSelfTextAttribute());
defaultToken = new Token(colorCache.getCodeTextAttribute());
decoratorToken = new Token(colorCache.getDecoratorTextAttribute());
numberToken = new Token(colorCache.getNumberTextAttribute());
classNameToken = new Token(colorCache.getClassNameTextAttribute());
funcNameToken = new Token(colorCache.getFuncNameTextAttribute());
parensToken = new Token(colorCache.getParensTextAttribute());
operatorsToken = new Token(colorCache.getOperatorsTextAttribute());
setDefaultReturnToken(defaultToken);
List<IRule> rules = new ArrayList<IRule>();
// Scanning strategy:
// 1) whitespace
// 2) code
// 3) regular words?
WhitespaceRule whitespaceRule;
try {
whitespaceRule = new WhitespaceRule(new GreatWhite(), defaultToken);
} catch (Throwable e) {
//Compatibility with Eclipse 3.4 and below.
whitespaceRule = new WhitespaceRule(new GreatWhite());
}
rules.add(whitespaceRule);
Map<String, IToken> defaults = new HashMap<String, IToken>();
defaults.put("self", selfToken);
PyWordRule wordRule = new PyWordRule(new GreatKeywordDetector(), defaultToken, classNameToken, funcNameToken,
parensToken, operatorsToken);
for (String keyword : keywords) {
IToken token = defaults.get(keyword);
if (token == null) {
token = keywordToken;
}
wordRule.addWord(keyword, token);
}
rules.add(wordRule);
rules.add(new WordRule(new DecoratorDetector(), decoratorToken));
rules.add(new WordRule(new NumberDetector(), numberToken));
setRules(rules.toArray(new IRule[0]));
}
/**
* Used from the django templates editor.
*/
public void setKeywords(String[] keywords) {
this.keywords = keywords;
this.setupRules();
}
}