| /******************************************************************************* |
| * Copyright (c) 2008, 2016 xored software, Inc. |
| * |
| * 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: |
| * xored software, Inc. - initial API and Implementation (Alex Panchenko) |
| *******************************************************************************/ |
| package org.eclipse.dltk.ruby.internal.ui.text; |
| |
| import java.util.HashSet; |
| import java.util.Set; |
| import java.util.Stack; |
| |
| import org.eclipse.dltk.ast.ASTNode; |
| import org.eclipse.dltk.ast.ASTVisitor; |
| import org.eclipse.dltk.ast.declarations.Declaration; |
| import org.eclipse.dltk.ast.declarations.ModuleDeclaration; |
| import org.eclipse.dltk.ast.expressions.BigNumericLiteral; |
| import org.eclipse.dltk.ast.expressions.CallExpression; |
| import org.eclipse.dltk.ast.expressions.FloatNumericLiteral; |
| import org.eclipse.dltk.ast.expressions.NumericLiteral; |
| import org.eclipse.dltk.ast.expressions.StringLiteral; |
| import org.eclipse.dltk.ast.parser.IModuleDeclaration; |
| import org.eclipse.dltk.ast.references.SimpleReference; |
| import org.eclipse.dltk.ast.references.VariableReference; |
| import org.eclipse.dltk.compiler.env.IModuleSource; |
| import org.eclipse.dltk.core.ISourceModule; |
| import org.eclipse.dltk.core.ModelException; |
| import org.eclipse.dltk.core.SourceParserUtil; |
| import org.eclipse.dltk.ruby.ast.RubyConstantDeclaration; |
| import org.eclipse.dltk.ruby.ast.RubyDAssgnExpression; |
| import org.eclipse.dltk.ruby.ast.RubyDRegexpExpression; |
| import org.eclipse.dltk.ruby.ast.RubyDVarExpression; |
| import org.eclipse.dltk.ruby.ast.RubyDynamicStringExpression; |
| import org.eclipse.dltk.ruby.ast.RubyEvaluatableStringExpression; |
| import org.eclipse.dltk.ruby.ast.RubyRegexpExpression; |
| import org.eclipse.dltk.ruby.ast.RubySymbolReference; |
| import org.eclipse.dltk.ruby.core.RubyNature; |
| import org.eclipse.dltk.ruby.core.utils.RubySyntaxUtils; |
| import org.eclipse.dltk.ruby.internal.ui.RubyPreferenceConstants; |
| import org.eclipse.dltk.ruby.ui.preferences.RubyPreferencesMessages; |
| import org.eclipse.dltk.ui.editor.highlighting.AbortSemanticHighlightingException; |
| import org.eclipse.dltk.ui.editor.highlighting.ISemanticHighlighter; |
| import org.eclipse.dltk.ui.editor.highlighting.ISemanticHighlighterExtension; |
| import org.eclipse.dltk.ui.editor.highlighting.ISemanticHighlightingRequestor; |
| import org.eclipse.dltk.ui.editor.highlighting.SemanticHighlighting; |
| import org.eclipse.dltk.ui.preferences.PreferencesMessages; |
| |
| public class RubySemanticUpdateWorker extends ASTVisitor implements |
| ISemanticHighlighter, ISemanticHighlighterExtension { |
| |
| @Override |
| public SemanticHighlighting[] getSemanticHighlightings() { |
| return new SemanticHighlighting[] { |
| new RubySemanticHighlighting( |
| RubyPreferenceConstants.EDITOR_REGEXP_COLOR, |
| PreferencesMessages.DLTKEditorPreferencePage_regexps), |
| new RubySemanticHighlighting( |
| RubyPreferenceConstants.EDITOR_STRING_COLOR, null), |
| new RubySemanticHighlighting( |
| RubyPreferenceConstants.EDITOR_SYMBOLS_COLOR, null), |
| new RubySemanticHighlighting( |
| RubyPreferenceConstants.EDITOR_VARIABLE_COLOR, |
| RubyPreferencesMessages.RubyLocalVariable), |
| new RubySemanticHighlighting( |
| RubyPreferenceConstants.EDITOR_INSTANCE_VARIABLE_COLOR, |
| null), |
| new RubySemanticHighlighting( |
| RubyPreferenceConstants.EDITOR_CLASS_VARIABLE_COLOR, |
| null), |
| new RubySemanticHighlighting( |
| RubyPreferenceConstants.EDITOR_GLOBAL_VARIABLE_COLOR, |
| null), |
| new RubySemanticHighlighting( |
| RubyPreferenceConstants.EDITOR_CONST_COLOR, |
| RubyPreferencesMessages.RubyConstants), |
| new RubySemanticHighlighting( |
| RubyPreferenceConstants.EDITOR_NUMBER_COLOR, null), |
| new RubySemanticHighlighting( |
| RubyPreferenceConstants.EDITOR_EVAL_EXPR_COLOR, |
| PreferencesMessages.DLTKEditorPreferencePage_evaluated_expressions), |
| new RubySemanticHighlighting(IRubyColorConstants.RUBY_DEFAULT, |
| null) }; |
| } |
| |
| private static final String HL_REGEXP = RubyPreferenceConstants.EDITOR_REGEXP_COLOR; |
| private static final String HL_STRING = RubyPreferenceConstants.EDITOR_STRING_COLOR; |
| private static final String HL_SYMBOL = RubyPreferenceConstants.EDITOR_SYMBOLS_COLOR; |
| private static final String HL_LOCAL_VARIABLE = RubyPreferenceConstants.EDITOR_VARIABLE_COLOR; |
| private static final String HL_INSTANCE_VARIABLE = RubyPreferenceConstants.EDITOR_INSTANCE_VARIABLE_COLOR; |
| private static final String HL_CLASS_VARIABLE = RubyPreferenceConstants.EDITOR_CLASS_VARIABLE_COLOR; |
| private static final String HL_GLOBAL_VARIABLE = RubyPreferenceConstants.EDITOR_GLOBAL_VARIABLE_COLOR; |
| private static final String HL_CONST = RubyPreferenceConstants.EDITOR_CONST_COLOR; |
| private static final String HL_NUMBER = RubyPreferenceConstants.EDITOR_NUMBER_COLOR; |
| private static final String HL_EVAL_EXPR = RubyPreferenceConstants.EDITOR_EVAL_EXPR_COLOR; |
| private static final String HL_DEFAULT = IRubyColorConstants.RUBY_DEFAULT; |
| |
| private ISemanticHighlightingRequestor requestor; |
| private char[] content; |
| |
| private static final boolean ACTIVE = true; |
| |
| @Override |
| public boolean visitGeneral(ASTNode node) throws Exception { |
| if (!ACTIVE) { |
| return true; |
| } |
| if (node instanceof RubyRegexpExpression |
| || node instanceof RubyDRegexpExpression) { |
| handleRegexp(node); |
| } else if (node instanceof RubySymbolReference) { |
| requestor.addPosition(node.sourceStart(), node.sourceEnd(), |
| HL_SYMBOL); |
| } else if (node instanceof VariableReference) { |
| handleVariableReference((VariableReference) node); |
| } else if (node instanceof RubyDVarExpression) { |
| requestor.addPosition(node.sourceStart(), node.sourceEnd(), |
| HL_LOCAL_VARIABLE); |
| } else if (node instanceof RubyDAssgnExpression) { |
| ASTNode var = ((RubyDAssgnExpression) node).getLeft(); |
| requestor.addPosition(var.sourceStart(), var.sourceEnd(), |
| HL_LOCAL_VARIABLE); |
| } else if (node instanceof StringLiteral) { |
| if (isStringLiteralNeeded(node)) { |
| requestor.addPosition(node.sourceStart(), node.sourceEnd(), |
| HL_STRING); |
| } |
| } else if (node instanceof NumericLiteral |
| || node instanceof FloatNumericLiteral |
| || node instanceof BigNumericLiteral) { |
| requestor.addPosition(node.sourceStart(), node.sourceEnd(), |
| HL_NUMBER); |
| } else if (node instanceof RubyEvaluatableStringExpression) { |
| handleEvaluatableExpression(node); |
| } else if (node instanceof CallExpression) { |
| final CallExpression call = (CallExpression) node; |
| if (!(RubySyntaxUtils.isRubyOperator(call.getName()) || call |
| .getReceiver() == null |
| && RubyCodeScanner.isPseudoKeyword(call.getName()))) { |
| final SimpleReference callName = call.getCallName(); |
| if (callName.sourceStart() >= 0 |
| && callName.sourceEnd() > callName.sourceStart()) { |
| requestor.addPosition(callName.sourceStart(), |
| callName.sourceEnd(), HL_DEFAULT); |
| } |
| } |
| } else if (node instanceof Declaration) { |
| final Declaration declaration = (Declaration) node; |
| requestor.addPosition(declaration.getNameStart(), |
| declaration.getNameEnd(), HL_DEFAULT); |
| } else if (node instanceof RubyConstantDeclaration) { |
| final RubyConstantDeclaration declaration = (RubyConstantDeclaration) node; |
| final SimpleReference name = declaration.getName(); |
| requestor.addPosition(name.sourceStart(), name.sourceEnd(), |
| HL_CONST); |
| } |
| stack.push(node); |
| return true; |
| } |
| |
| private boolean isStringLiteralNeeded(ASTNode node) { |
| if (stack.empty()) { |
| return true; |
| } |
| final ASTNode top = stack.peek(); |
| if (top instanceof RubyDRegexpExpression) { |
| return false; |
| } |
| if (top instanceof RubyDynamicStringExpression) { |
| return node.sourceStart() >= top.sourceStart() |
| && node.sourceEnd() <= top.sourceEnd(); |
| } |
| return true; |
| } |
| |
| @Override |
| public void endvisitGeneral(ASTNode node) throws Exception { |
| stack.pop(); |
| } |
| |
| private final Stack<ASTNode> stack = new Stack<ASTNode>(); |
| |
| private void handleVariableReference(VariableReference ref) { |
| final String varName = ref.getName(); |
| if (varName.length() != 0) { |
| if (varName.charAt(0) == '$') { |
| requestor.addPosition(ref.sourceStart(), ref.sourceEnd(), |
| HL_GLOBAL_VARIABLE); |
| } else if (varName.charAt(0) == '@') { |
| if (varName.length() > 2 && varName.charAt(1) == '@') { |
| requestor.addPosition(ref.sourceStart(), ref.sourceEnd(), |
| HL_CLASS_VARIABLE); |
| } else { |
| requestor.addPosition(ref.sourceStart(), ref.sourceEnd(), |
| HL_INSTANCE_VARIABLE); |
| } |
| } else { |
| requestor.addPosition(ref.sourceStart(), ref.sourceEnd(), |
| HL_LOCAL_VARIABLE); |
| } |
| } |
| } |
| |
| private void handleEvaluatableExpression(ASTNode node) { |
| int start = node.sourceStart(); |
| int end = node.sourceEnd(); |
| if (content[start] == '#' && content[start + 1] == '{') { |
| if (content[end - 1] == '\r') { |
| // FIXME JRuby bug |
| --end; |
| } |
| if (content[end - 1] == '}') { |
| requestor.addPosition(start, start + 2, HL_EVAL_EXPR); |
| requestor.addPosition(end - 1, end - 0, HL_EVAL_EXPR); |
| } |
| } |
| } |
| |
| private void handleRegexp(ASTNode node) { |
| int start = node.sourceStart(); |
| int end = node.sourceEnd(); |
| if (start >= 1 && content[start - 1] == '/') { |
| --start; |
| if (end < content.length && content[end] == '/') { |
| ++end; |
| } |
| while (end < content.length |
| && RubySyntaxUtils.isValidRegexpModifier(content[end])) { |
| ++end; |
| } |
| } else if (start >= 3 && content[start - 3] == '%' |
| && content[start - 2] == 'r') { |
| char terminator = RubySyntaxUtils |
| .getPercentStringTerminator(content[start - 1]); |
| if (terminator != 0 && end < content.length |
| && content[end] == terminator) { |
| start -= 3; |
| ++end; |
| while (end < content.length |
| && RubySyntaxUtils.isValidRegexpModifier(content[end])) { |
| ++end; |
| } |
| } |
| } |
| requestor.addPosition(start, end, HL_REGEXP); |
| } |
| |
| @Override |
| public String[] getHighlightingKeys() { |
| final Set<String> result = new HashSet<String>(); |
| for (SemanticHighlighting highlighting : getSemanticHighlightings()) { |
| result.add(highlighting.getPreferenceKey()); |
| } |
| return result.toArray(new String[result.size()]); |
| } |
| |
| @Override |
| public void process(IModuleSource code, |
| ISemanticHighlightingRequestor requestor) { |
| this.requestor = requestor; |
| this.content = code.getContentsAsCharArray(); |
| try { |
| ((ModuleDeclaration) parseCode(code)).traverse(this); |
| } catch (ModelException e) { |
| throw new AbortSemanticHighlightingException(); |
| } catch (Exception e) { |
| throw new AbortSemanticHighlightingException(); |
| } |
| } |
| |
| /** |
| * @param code |
| * @return |
| * @throws ModelException |
| */ |
| protected IModuleDeclaration parseCode(IModuleSource code) |
| throws ModelException { |
| if (code instanceof ISourceModule) { |
| return parseSourceModule((ISourceModule) code); |
| } else { |
| return parseSourceCode(code); |
| } |
| } |
| |
| private IModuleDeclaration parseSourceCode(IModuleSource code) { |
| return SourceParserUtil.parse(code, RubyNature.NATURE_ID, null); |
| } |
| |
| private IModuleDeclaration parseSourceModule( |
| final ISourceModule sourceModule) { |
| return SourceParserUtil.parse(sourceModule, null); |
| } |
| |
| } |