/******************************************************************************* * 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); } }