package org.geogebra.common.gui.inputfield; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import org.geogebra.common.awt.GColor; import org.geogebra.common.kernel.Kernel; import org.geogebra.common.kernel.parser.cashandlers.ParserFunctions; import org.geogebra.common.main.App; import org.geogebra.common.main.GeoGebraColorConstants; import com.google.gwt.regexp.shared.MatchResult; import com.google.gwt.regexp.shared.RegExp; import com.google.gwt.regexp.shared.SplitResult; /** * Class for coloring the labels in input bar * * @author bencze */ @SuppressWarnings({ "unchecked", "rawtypes" }) public class ColorProvider { private static final int TEXT_LENGHT_LIMIT = 1000; /** Regular expression strings */ private static final String LABEL_REGEX_STRING = "((\\p{L}\\p{M}*)(\\p{L}\\p{M}*|\\p{Nd})*'?(\\_\\{+(\\P{M}\\p{M}*)+\\}|\\_(\\P{M}\\p{M})?)?(\\p{L}\\p{M}|\\p{Nd})*)"; private static final String LABEL_PARAM = LABEL_REGEX_STRING + "(\\(|\\[)?"; private static final String STRING = "((\\P{M}\\p{M}*)*)"; private static final String WHITESPACE = "\\p{Z}*"; /** Colors */ private static final GColor COLOR_DEFINED = GeoGebraColorConstants.DEFINED_OBJECT_COLOR; private static final GColor COLOR_UNDEFINED = GeoGebraColorConstants.UNDEFINED_OBJECT_COLOR; private static final GColor COLOR_LOCAL = GeoGebraColorConstants.LOCAL_OBJECT_COLOR; private static final GColor COLOR_DEFAULT = GColor.BLACK; private Kernel kernel; private Set<String> labels; private Set<String> locals; private ParserFunctions pf; private List<Integer[]> definedObjectsIntervals; private List<Integer[]> undefinedObjectsIntervals; private List<Integer[]> ignoreIntervals; private List<Integer[]> localVariableIntervals; private String text; private boolean isCasInput; /** Regular expression objects */ private RegExp commandReg = RegExp .compile(LABEL_REGEX_STRING + "\\[(" + STRING + "|,)\\]", "g"); private RegExp commandParamReg = RegExp.compile("<(\\p{L}\\p{M}*| |\\-)*>", "g"); private RegExp splitter = RegExp.compile(","); private RegExp assignmentReg; /** * @param app * for getting kernel and command dictionary * @param isCasInput1 * whether we are coloring CAS input labels */ public ColorProvider(App app, boolean isCasInput1) { kernel = app.getKernel(); setIsCasInput(isCasInput1); assignmentReg = createAssignmentRegExp(isCasInput); labels = null; locals = null; definedObjectsIntervals = new ArrayList<Integer[]>(); undefinedObjectsIntervals = new ArrayList<Integer[]>(); ignoreIntervals = new ArrayList<Integer[]>(); localVariableIntervals = new ArrayList<Integer[]>(); pf = app.getParserFunctions(); text = ""; } /** * @param text1 * text in we are looking for labels */ public void setText(String text1) { text = text1; if (text1.length() > TEXT_LENGHT_LIMIT) { return; } getIntervals(); } /** * Every time the text changes, setText(String) must be called * * @param i * the cursor in the text * @return Color */ public GColor getColor(int i) { for (Integer[] in : definedObjectsIntervals) { if (in[0] <= i && in[1] > i) { return COLOR_DEFINED; } } for (Integer[] in : localVariableIntervals) { if (in[0] <= i && in[1] > i) { return COLOR_LOCAL; } } if (isCasInput) { return COLOR_DEFAULT; } for (Integer[] in : ignoreIntervals) { if (in[0] <= i && in[1] > i) { return COLOR_DEFAULT; } } for (Integer[] in : undefinedObjectsIntervals) { if (in[0] <= i && in[1] > i) { return COLOR_UNDEFINED; } } return COLOR_DEFAULT; } /** * Sets the flags for algebra or CAS input * * @param isCasInput1 * true if it is CAS input false if algebra input */ public void setIsCasInput(boolean isCasInput1) { if (isCasInput != isCasInput1) { isCasInput = isCasInput1; assignmentReg = createAssignmentRegExp(isCasInput); } } private static RegExp createAssignmentRegExp(boolean isCasInput) { return RegExp.compile("^" + WHITESPACE + LABEL_REGEX_STRING + // f - // function // label "(\\(" + WHITESPACE + "((" + LABEL_REGEX_STRING + WHITESPACE + "," + WHITESPACE + ")*)" + LABEL_REGEX_STRING + WHITESPACE + "\\))" + // ( x1 , x2 , x3 , ... ) - function parameters WHITESPACE + (!isCasInput ? "(\\:\\=|\\=)" : "(\\:\\=)")); // :=/= // - // assignment // operator } private void getIntervals() { if (isCasInput) { labels = kernel.getConstruction().getAllLabels(); } else { labels = kernel.getConstruction().getAllGeoLabels(); } locals = new HashSet(); definedObjectsIntervals.clear(); undefinedObjectsIntervals.clear(); ignoreIntervals.clear(); localVariableIntervals.clear(); MatchResult res; // Only for algebra input if (!isCasInput) { while ((res = commandReg.exec(text)) != null) { int i = res.getIndex(); ignoreIntervals .add(new Integer[] { i, i + res.getGroup(1).length() }); } while ((res = commandParamReg.exec(text)) != null) { int i = res.getIndex(); ignoreIntervals .add(new Integer[] { i, i + res.getGroup(0).length() }); } } res = assignmentReg.exec(text); if (res != null) { // It is a function assignment // We add the parameters to the locals set // so we can color them differently String label = res.getGroup(1); if (labels.contains(label)) { addTo(definedObjectsIntervals, 0, label.length()); } SplitResult split = getVariables(res.getGroup(8)); for (int i = 0; i < split.length(); i++) { String var = split.get(i); String trimmedVar = trimVar(var); locals.add(trimmedVar); } } getIntervalsRecursively(text, 0); } private void getIntervalsRecursively(String text1, int startIndex) { MyLabelParamRegExp labelParam = new MyLabelParamRegExp(text1); MyMatchResult res = null; // While we get matches against text while ((res = labelParam.exec()) != null) { String label = res.getGroup(0); // Params is null if we got a label String params = res.getGroup(1); // We don't color commands if (!res.isCommand()) { addToInterval(label, startIndex + res.getIndex(), label.length()); } SplitResult split = getVariables(params); int j = startIndex + res.getIndex() + label.length(); if (split != null) { for (int i = 0; i < split.length(); i++) { // For every parameter we call this function recursively // this way we can color inner commands and function calls // as sin(cos(f(x))) String sub = split.get(i); getIntervalsRecursively(sub, j); j += sub.length() + 1; } } } } private SplitResult getVariables(String vars) { return vars == null ? null : splitter.split(vars); } private static String trimVar(String var) { String ret = var; if (ret.charAt(0) == '(') { ret = ret.substring(1); } if (ret.charAt(ret.length() - 1) == ')') { ret = ret.substring(0, ret.length() - 1); } return ret.trim(); } private static void addTo(List list, int s, int e) { list.add(new Integer[] { s, e }); } private void addToInterval(String label, int s, int len) { if (locals.contains(label)) { addTo(localVariableIntervals, s, s + len); } else if (labels.contains(label)) { addTo(definedObjectsIntervals, s, s + len); } else if (!isCasInput && !pf.isReserved(label)) { addTo(undefinedObjectsIntervals, s, s + len); } } // MyMatchResult and MyLabelParamRegExp are // inner classes used for matching labels/functions/commands private static class MyMatchResult { int index; List<String> groups; private boolean isCommand; public MyMatchResult(int index, List<String> groups, boolean isCommand) { this.index = index; this.groups = groups; setCommand(isCommand); } public boolean isCommand() { return isCommand; } public void setCommand(boolean isCommand) { this.isCommand = isCommand; } public int getIndex() { return index; } public String getGroup(int i) { return groups.get(i); } } private static class MyLabelParamRegExp { RegExp regExp = RegExp.compile(LABEL_PARAM); String text; int index; public MyLabelParamRegExp(String text) { setText(text); } public MyMatchResult exec() { MatchResult res = regExp.exec(text); if (res == null) { return null; } String label = res.getGroup(1); String openingBracket = res.getGroup(8); List groups = new ArrayList(2); groups.add(label); MyMatchResult ret; int step = 0; String params = null; if (openingBracket == null) { // this is a label without parameters step = res.getIndex() + label.length(); } else { // we have a label and parameters // we look for the closing parentheses int paramsStart = res.getIndex() + label.length(); int i = paramsStart + 1; int nrOfBrackets = 1; char closingBracket = getClosingBracket(openingBracket); for (; i < text.length() && nrOfBrackets != 0; i++) { if (text.charAt(i) == openingBracket.charAt(0)) { nrOfBrackets++; } else if (text.charAt(i) == closingBracket) { nrOfBrackets--; } } params = text.substring(paramsStart, i); step = paramsStart + params.length(); } // Set the second parameter and create return value groups.add(params); ret = new MyMatchResult(index + res.getIndex(), groups, "[".equals(openingBracket)); index += step; text = text.substring(step); return ret; } public void setText(String text) { this.text = text; index = 0; } private static char getClosingBracket(String openingBracket) { if ("[".equals(openingBracket)) { return ']'; } // default return ')'; } } }