package com.redhat.ceylon.eclipse.util;
import static com.redhat.ceylon.eclipse.code.preferences.CeylonPreferenceInitializer.MATCH_HIGHLIGHTING;
import static com.redhat.ceylon.eclipse.ui.CeylonPlugin.PLUGIN_ID;
import static com.redhat.ceylon.eclipse.util.EditorUtil.getCurrentTheme;
import static java.lang.Character.isDigit;
import static java.lang.Character.isJavaIdentifierStart;
import static java.lang.Character.isLowerCase;
import static java.lang.Character.isUpperCase;
import static org.eclipse.ui.PlatformUI.getWorkbench;
import java.util.List;
import java.util.StringTokenizer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.antlr.runtime.CommonToken;
import org.eclipse.jface.resource.ColorRegistry;
import org.eclipse.jface.text.TextAttribute;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.jface.viewers.StyledString;
import org.eclipse.jface.viewers.StyledString.Styler;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.graphics.TextStyle;
import org.eclipse.ui.themes.IThemeManager;
import com.redhat.ceylon.compiler.typechecker.parser.CeylonParser;
import com.redhat.ceylon.eclipse.code.editor.CeylonTaskUtil;
import com.redhat.ceylon.eclipse.code.editor.CeylonTaskUtil.Task;
import com.redhat.ceylon.eclipse.ui.CeylonPlugin;
import com.redhat.ceylon.ide.common.util.escaping_;
public class Highlights {
public static String IDENTIFIERS = "identifiers";
public static String TYPES = "types";
public static String TYPE_LITERALS = "typeLiterals";
public static String KEYWORDS = "keywords";
public static String NUMBERS = "numbers";
public static String STRINGS = "strings";
public static String CHARS = "characters";
public static String INTERP = "interpolation";
public static String COMMENTS = "comments";
public static String TODOS = "todos";
public static String ANNOTATIONS = "annotations";
public static String ANNOTATION_STRINGS = "annotationstrings";
public static String SEMIS = "semis";
public static String BRACES = "braces";
public static String PACKAGES = "packages";
public static String MEMBERS = "members";
public static String OUTLINE_TYPES = "outlineTypes";
public static String MATCHES = "matches";
public static final String DOC_BACKGROUND = "documentationBackground";
private static TextAttribute identifierAttribute,
typeAttribute, typeLiteralAttribute,
keywordAttribute, numberAttribute,
annotationAttribute, annotationStringAttribute,
commentAttribute, stringAttribute, todoAttribute,
semiAttribute, braceAttribute, packageAttribute,
interpAttribute, charAttribute, memberAttribute;
private static TextAttribute text(ColorRegistry colorRegistry,
String key, int style) {
return new TextAttribute(color(colorRegistry, key),
null, style);
}
public static Color color(ColorRegistry colorRegistry, String key) {
return colorRegistry.get(PLUGIN_ID + ".theme.color." + key);
}
static {
initColors(getCurrentTheme().getColorRegistry());
IThemeManager themeManager =
getWorkbench().getThemeManager();
themeManager.addPropertyChangeListener(new IPropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent event) {
if (isColorChange(event)) {
refreshColors();
}
}
});
}
public static boolean isColorChange(PropertyChangeEvent event) {
String property = event.getProperty();
return property.startsWith(PLUGIN_ID + ".theme.color.") ||
property.equals(IThemeManager.CHANGE_CURRENT_THEME);
}
public static Color getCurrentThemeColor(String key) {
return color(getCurrentTheme().getColorRegistry(), key);
}
public static void refreshColors() {
initColors(getCurrentTheme().getColorRegistry());
}
private static void initColors(ColorRegistry colorRegistry) {
identifierAttribute = text(colorRegistry, IDENTIFIERS, SWT.NORMAL);
typeAttribute = text(colorRegistry, TYPES, SWT.NORMAL);
typeLiteralAttribute = text(colorRegistry, TYPE_LITERALS, SWT.NORMAL);
keywordAttribute = text(colorRegistry, KEYWORDS, SWT.BOLD);
numberAttribute = text(colorRegistry, NUMBERS, SWT.NORMAL);
commentAttribute = text(colorRegistry, COMMENTS, SWT.NORMAL);
stringAttribute = text(colorRegistry, STRINGS, SWT.NORMAL);
charAttribute = text(colorRegistry, CHARS, SWT.NORMAL);
interpAttribute = text(colorRegistry, INTERP, SWT.NORMAL);
annotationStringAttribute = text(colorRegistry, ANNOTATION_STRINGS, SWT.NORMAL);
annotationAttribute = text(colorRegistry, ANNOTATIONS, SWT.NORMAL);
todoAttribute = text(colorRegistry, TODOS, SWT.NORMAL);
semiAttribute = text(colorRegistry, SEMIS, SWT.NORMAL);
braceAttribute = text(colorRegistry, BRACES, SWT.NORMAL);
packageAttribute = text(colorRegistry, PACKAGES, SWT.NORMAL);
memberAttribute = text(colorRegistry, MEMBERS, SWT.NORMAL);
}
public static TextAttribute getInterpolationColoring() {
return interpAttribute;
}
public static TextAttribute getMetaLiteralColoring() {
return typeLiteralAttribute;
}
public static TextAttribute getMemberColoring() {
return memberAttribute;
}
public static TextAttribute getColoring(CommonToken token) {
switch (token.getType()) {
case CeylonParser.PIDENTIFIER:
return packageAttribute;
case CeylonParser.AIDENTIFIER:
return annotationAttribute;
case CeylonParser.UIDENTIFIER:
return typeAttribute;
case CeylonParser.LIDENTIFIER:
return identifierAttribute;
case CeylonParser.FLOAT_LITERAL:
case CeylonParser.NATURAL_LITERAL:
return numberAttribute;
case CeylonParser.ASTRING_LITERAL:
case CeylonParser.AVERBATIM_STRING:
return annotationStringAttribute;
case CeylonParser.CHAR_LITERAL:
return charAttribute;
case CeylonParser.STRING_LITERAL:
case CeylonParser.STRING_END:
case CeylonParser.STRING_START:
case CeylonParser.STRING_MID:
case CeylonParser.VERBATIM_STRING:
return stringAttribute;
case CeylonParser.MULTI_COMMENT:
case CeylonParser.LINE_COMMENT:
List<Task> tasks = CeylonTaskUtil.getTasks(token);
if (tasks != null && tasks.size() > 0) {
return todoAttribute;
}
else {
return commentAttribute;
}
case CeylonParser.BACKTICK:
return typeLiteralAttribute;
case CeylonParser.SEMICOLON:
return semiAttribute;
case CeylonParser.LBRACE:
case CeylonParser.RBRACE:
return braceAttribute;
case CeylonParser.EOF:
case CeylonParser.WS:
return null;
default:
if (escaping_.get_().isKeyword(token.getText())) {
return keywordAttribute;
}
else {
return null;
}
}
}
private static final Pattern path =
Pattern.compile("\\b(\\p{javaLowerCase}|_)(\\p{Digit}|\\p{javaLowerCase}|_)*(\\.(\\p{javaLowerCase}|_)(\\p{Digit}|\\p{javaLowerCase}|_)*)+\\b");
public static class FontStyler extends Styler {
private final Font font;
private Styler styler;
public FontStyler(Font font) {
this.font = font;
}
public FontStyler(Font font, Styler styler) {
this(font);
this.styler = styler;
}
@Override
public void applyStyles(TextStyle textStyle) {
if (font!=null) {
textStyle.font = font;
}
if (styler!=null) {
styler.applyStyles(textStyle);
}
}
}
public static void styleFragment(
StyledString result,
String codeFragment,
boolean qualifiedNameIsPath,
String prefix, Font font) {
qualifiedNameIsPath &=
path.matcher(codeFragment).find();
StringTokenizer tokens =
new StringTokenizer(codeFragment,
qualifiedNameIsPath ?
" |&()<>*+?,:{}[]@\"'!^/%~-=;" :
" |&()<>*+?,:{}[]@\"'!^/%~-=;.",
true);
boolean version = false;
boolean qualified = false;
boolean matchHighlighting = prefix!=null;
while (tokens.hasMoreTokens()) {
final String token = tokens.nextToken();
if (token.equals("\"") || token.equals("'")) {
version = !version;
append(result, token, font, STRING_STYLER);
}
else if (version) {
append(result, token, font, STRING_STYLER);
}
else if (token.equals(".")) {
qualified = true;
append(result, token, font, null);
continue;
}
else {
int initial = token.codePointAt(0);
if (initial=='\\' && token.length()>1) {
initial = token.codePointAt(1);
}
if (isDigit(initial)) {
append(result, token, font, NUM_STYLER);
}
else if (isUpperCase(initial)) {
if (matchHighlighting) {
styleIdentifier(
result, prefix, token,
new FontStyler(font,
TYPE_ID_STYLER),
font);
matchHighlighting = false;
}
else {
append(result, token, font,
TYPE_ID_STYLER);
}
}
else if (isLowerCase(initial)) {
if (escaping_.get_().isKeyword(token)) {
append(result, token, font,
KW_STYLER);
}
else if (token.contains(".")) {
append(result, token, font,
PACKAGE_STYLER);
}
else if (qualified) {
if (matchHighlighting) {
styleIdentifier(
result, prefix, token,
new FontStyler(font,
MEMBER_STYLER),
font);
matchHighlighting = false;
}
else {
append(result, token, font,
MEMBER_STYLER);
}
}
else {
if (matchHighlighting) {
styleIdentifier(
result, prefix, token,
new FontStyler(font,
ID_STYLER),
font);
matchHighlighting = false;
}
else {
append(result, token, font,
ID_STYLER);
}
}
}
else {
append(result, token, font, null);
}
}
qualified = false;
}
}
private static void append(StyledString result,
String token, Font font, Styler styler) {
result.append(token, new FontStyler(font, styler));
}
private static Font getBoldFont(Font font) {
FontData[] data = font.getFontData();
for (int i= 0; i<data.length; i++) {
data[i].setStyle(SWT.BOLD);
}
return new Font(font.getDevice(), data);
}
private static final Pattern HUMP =
Pattern.compile("(\\w|\\\\[iI])\\p{Ll}*");
public static void styleIdentifier(StyledString result,
String prefix, String token,
Styler colorStyler, final Font font) {
final Styler fontAndColorStyler =
new FontStyler(font, colorStyler);
final String type =
CeylonPlugin.getPreferences()
.getString(MATCH_HIGHLIGHTING);
if ("none".equals(type) || prefix.isEmpty()) {
result.append(token, fontAndColorStyler);
return;
}
Matcher m = HUMP.matcher(prefix);
int i = 0;
while (i<token.length() && m.find()) {
String bit = m.group();
//look for an exact-case match
int loc = token.indexOf(bit, i);
if (loc<0) {
//look for an inexact-case match
loc = token.toLowerCase()
.indexOf(bit.toLowerCase(), i);
}
if (i==0 && loc>0 && !prefix.startsWith("*")) {
//first match must be at start of identifier
break;
}
if (loc<0) {
//roll back the highlighting already done
result.setStyle(result.length()-i, i,
fontAndColorStyler);
break;
}
result.append(token.substring(i, loc),
fontAndColorStyler);
Styler matchStyler = new Styler() {
@Override
public void applyStyles(TextStyle textStyle) {
fontAndColorStyler.applyStyles(textStyle);
switch (type) {
case "underline":
textStyle.underline = true;
break;
case "bold":
textStyle.font = getBoldFont(font);
break;
case "color":
textStyle.foreground =
color(getCurrentTheme()
.getColorRegistry(),
MATCHES);
break;
case "background":
textStyle.background =
color(getCurrentTheme()
.getColorRegistry(),
MATCHES);
break;
}
}
};
result.append(token.substring(loc, loc+bit.length()),
matchStyler);
i = loc + bit.length();
}
result.append(token.substring(i), fontAndColorStyler);
}
public static void styleJavaType(StyledString result,
String string) {
styleJavaType(result, string, TYPE_ID_STYLER);
}
public static void styleJavaType(StyledString result,
String string, Styler styler) {
StringTokenizer tokens =
new StringTokenizer(string,
" <>,[]?.", true);
while (tokens.hasMoreTokens()) {
String token = tokens.nextToken();
if (token.equals("int") ||
token.equals("short") ||
token.equals("byte") ||
token.equals("long") ||
token.equals("double") ||
token.equals("float") ||
token.equals("boolean") ||
token.equals("char") ||
token.equals("extends") ||
token.equals("super")) {
result.append(token, KW_STYLER);
}
else if (isJavaIdentifierStart(token.charAt(0))) {
result.append(token, styler);
}
else {
result.append(token);
}
}
}
public static StyledString styleProposal(
String description,
boolean qualifiedNameIsPath,
boolean eliminateQuotes) {
StyledString result = new StyledString();
StringTokenizer tokens =
new StringTokenizer(description,
"'\"", true);
result.append(tokens.nextToken());
while (tokens.hasMoreTokens()) {
String tok = tokens.nextToken();
if (tok.equals("\'")) {
if (!eliminateQuotes) {
result.append(tok);
}
while (tokens.hasMoreTokens()) {
String token = tokens.nextToken();
if (token.equals("\'")) {
if (!eliminateQuotes) {
result.append(token);
}
break;
}
else if (token.equals("\"")) {
result.append(token, STRING_STYLER);
while (tokens.hasMoreTokens()) {
String quoted = tokens.nextToken();
result.append(quoted, STRING_STYLER);
if (quoted.equals("\"")) {
break;
}
}
}
else {
styleFragment(result, token,
qualifiedNameIsPath, null,
CeylonPlugin.getCompletionFont());
}
}
}
else {
result.append(tok);
}
}
return result;
}
public static StyledString styleProposal(
String description,
boolean qualifiedNameIsPath) {
return styleProposal(description,
qualifiedNameIsPath, true);
}
public static final Styler TYPE_STYLER =
new Styler() {
@Override
public void applyStyles(TextStyle textStyle) {
textStyle.foreground =
color(getCurrentTheme()
.getColorRegistry(),
TYPES);
}
};
public static final Styler MEMBER_STYLER =
new Styler() {
@Override
public void applyStyles(TextStyle textStyle) {
textStyle.foreground =
color(getCurrentTheme()
.getColorRegistry(),
MEMBERS);
}
};
public static final Styler TYPE_ID_STYLER =
new Styler() {
@Override
public void applyStyles(TextStyle textStyle) {
textStyle.foreground =
color(getCurrentTheme()
.getColorRegistry(),
TYPES);
}
};
public static final Styler KW_STYLER =
new Styler() {
@Override
public void applyStyles(TextStyle textStyle) {
textStyle.foreground =
color(getCurrentTheme()
.getColorRegistry(),
KEYWORDS);
}
};
public static final Styler STRING_STYLER =
new Styler() {
@Override
public void applyStyles(TextStyle textStyle) {
textStyle.foreground =
color(getCurrentTheme()
.getColorRegistry(),
STRINGS);
}
};
public static final Styler NUM_STYLER =
new Styler() {
@Override
public void applyStyles(TextStyle textStyle) {
textStyle.foreground =
color(getCurrentTheme()
.getColorRegistry(),
NUMBERS);
}
};
public static final Styler PACKAGE_STYLER =
new Styler() {
@Override
public void applyStyles(TextStyle textStyle) {
textStyle.foreground =
color(getCurrentTheme()
.getColorRegistry(),
PACKAGES);
}
};
public static final Styler ARROW_STYLER =
new Styler() {
@Override
public void applyStyles(TextStyle textStyle) {
textStyle.foreground =
color(getCurrentTheme()
.getColorRegistry(),
OUTLINE_TYPES);
}
};
public static final Styler ANN_STYLER =
new Styler() {
@Override
public void applyStyles(TextStyle textStyle) {
textStyle.foreground =
color(getCurrentTheme()
.getColorRegistry(),
ANNOTATIONS);
}
};
public static final Styler ID_STYLER =
new Styler() {
@Override
public void applyStyles(TextStyle textStyle) {
textStyle.foreground =
color(getCurrentTheme()
.getColorRegistry(),
IDENTIFIERS);
}
};
}