/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright 1997-2010 Oracle and/or its affiliates. All rights reserved. * * Oracle and Java are registered trademarks of Oracle and/or its affiliates. * Other names may be trademarks of their respective owners. * * The contents of this file are subject to the terms of either the GNU * General Public License Version 2 only ("GPL") or the Common * Development and Distribution License("CDDL") (collectively, the * "License"). You may not use this file except in compliance with the * License. You can obtain a copy of the License at * http://www.netbeans.org/cddl-gplv2.html * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the * specific language governing permissions and limitations under the * License. When distributing the software, include this License Header * Notice in each file and include the License file at * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the GPL Version 2 section of the License file that * accompanied this code. If applicable, add the following below the * License Header, with the fields enclosed by brackets [] replaced by * your own identifying information: * "Portions Copyrighted [year] [name of copyright owner]" * * Contributor(s): * * The Original Software is NetBeans. The Initial Developer of the Original * Software is Sun Microsystems, Inc. Portions Copyright 1997-2008 Sun * Microsystems, Inc. All Rights Reserved. * * If you wish your version of this file to be governed by only the CDDL * or only the GPL Version 2, indicate your decision by adding * "[Contributor] elects to include this software in this distribution * under the [CDDL or GPL Version 2] license." If you do not indicate a * single choice of license, a recipient has the option to distribute * your version of this file under either the CDDL, the GPL Version 2 or * to extend the choice of license to its licensees as provided above. * However, if you add GPL Version 2 code and therefore, elected the GPL * Version 2 license, then the option applies only if the new code is * made subject to such option by the copyright holder. */ package org.netbeans.modules.ruby; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.swing.text.BadLocationException; import org.jrubyparser.SourcePosition; import org.jrubyparser.ast.AliasNode; import org.jrubyparser.ast.ArgsNode; import org.jrubyparser.ast.ArgumentNode; import org.jrubyparser.ast.BackRefNode; import org.jrubyparser.ast.BlockArgNode; import org.jrubyparser.ast.CallNode; import org.jrubyparser.ast.ClassVarAsgnNode; import org.jrubyparser.ast.ClassVarDeclNode; import org.jrubyparser.ast.ClassVarNode; import org.jrubyparser.ast.Colon2Node; import org.jrubyparser.ast.ConstDeclNode; import org.jrubyparser.ast.ConstNode; import org.jrubyparser.ast.DAsgnNode; import org.jrubyparser.ast.DVarNode; import org.jrubyparser.ast.FCallNode; import org.jrubyparser.ast.GlobalAsgnNode; import org.jrubyparser.ast.GlobalVarNode; import org.jrubyparser.ast.InstAsgnNode; import org.jrubyparser.ast.InstVarNode; import org.jrubyparser.ast.ListNode; import org.jrubyparser.ast.LocalAsgnNode; import org.jrubyparser.ast.LocalVarNode; import org.jrubyparser.ast.MethodDefNode; import org.jrubyparser.ast.Node; import org.jrubyparser.ast.NodeType; import org.jrubyparser.ast.NthRefNode; import org.jrubyparser.ast.ReturnNode; import org.jrubyparser.ast.SymbolNode; import org.jrubyparser.ast.VCallNode; import org.jrubyparser.ast.YieldNode; import org.jrubyparser.ast.INameNode; import org.jrubyparser.ast.SelfNode; import org.netbeans.api.lexer.Token; import org.netbeans.editor.BaseDocument; import org.netbeans.editor.Utilities; import org.netbeans.modules.csl.api.ColoringAttributes; import org.netbeans.modules.csl.api.OccurrencesFinder; import org.netbeans.modules.csl.api.OffsetRange; import org.netbeans.modules.parsing.spi.Parser.Result; import org.netbeans.modules.parsing.spi.Scheduler; import org.netbeans.modules.parsing.spi.SchedulerEvent; import org.netbeans.modules.ruby.lexer.LexUtilities; import org.netbeans.modules.ruby.lexer.RubyTokenId; import org.openide.filesystems.FileObject; /** * Walk through the JRuby AST and find occurrences of symbols related to the symbol under the cursor * * @todo Highlight exit points: break (if exits method?), uncaught exceptions, throws, etc. * It would be cool if I can highlight exits out of some types of blocks too, like for and while * loops where I highlight retry, break, redo(?), return, uncaught throws. * @todo Highlight symbol nodes. If you have a "class Foo" and refer to :Foo then class Foo should * be marked. * * @author Tor Norbye */ public class RubyOccurrencesFinder extends OccurrencesFinder { private boolean cancelled; private int caretPosition; private Map<OffsetRange, ColoringAttributes> occurrences; private FileObject file; private Node root; /** System property for turning off mark occurrences. */ private static boolean ENABLED = Boolean.parseBoolean(System.getProperty("ruby.mark.occurrences", "true")); /** When true, don't match alias nodes as reads. Used during traversal of the AST. */ private boolean ignoreAlias; public RubyOccurrencesFinder() { } public Map<OffsetRange, ColoringAttributes> getOccurrences() { return occurrences; } protected final synchronized boolean isCancelled() { return cancelled; } protected final synchronized void resume() { cancelled = false; } public final synchronized void cancel() { cancelled = true; } @Override public void run(Result info, SchedulerEvent event) { if (!ENABLED) { return; } resume(); if (isCancelled()) { return; } FileObject currentFile = RubyUtils.getFileObject(info); if (currentFile != file) { // Ensure that we don't reuse results from a different file occurrences = null; file = currentFile; } RubyParseResult rpr = AstUtilities.getParseResult(info); if (rpr == null) { return; } root = rpr.getRootNode(); if (root == null) { return; } Map<OffsetRange, ColoringAttributes> highlights = new HashMap<OffsetRange, ColoringAttributes>(100); int astOffset = AstUtilities.getAstOffset(info, caretPosition); if (astOffset == -1) { return; } AstPath path = new AstPath(root, astOffset); Node closest = path.leaf(); if (closest == null) { return; } // When we sanitize the line around the caret, occurrences // highlighting can get really ugly OffsetRange blankRange = rpr.getSanitizedRange(); if (blankRange.containsInclusive(astOffset)) { closest = null; } // JRuby sometimes gives me some "weird" sections. For example, // if you have // obj.| // // Scanf // rather than give a parse error on obj, it marks the whole region from // . to the end of Scanf as a CallNode, which is a weird highlight. // We don't want occurrences highlights that span lines. if (closest != null) { //SourcePosition pos = closest.getPosition(); BaseDocument doc = RubyUtils.getDocument(info); if (doc == null) { // Document was just closed return; } try { doc.readLock(); int length = doc.getLength(); OffsetRange astRange = AstUtilities.getRange(closest); OffsetRange lexRange = LexUtilities.getLexerOffsets(info, astRange); int lexStartPos = lexRange.getStart(); int lexEndPos = lexRange.getEnd(); // If the buffer was just modified where a lot of text was deleted, // the parse tree positions could be pointing outside the valid range if (lexStartPos > length) { lexStartPos = length; } if (lexEndPos > length) { lexEndPos = length; } if (lexStartPos != -1 && lexEndPos != -1 && Utilities.getRowStart(doc, lexStartPos) != Utilities.getRowStart(doc, lexEndPos)) { // One special case I care about: highlighting method exit points. In // this case, the full def node is selected, which typically spans // lines. This should trigger if you put the caret on the method definition // line, unless it's in a comment there. Token<?extends RubyTokenId> token = LexUtilities.getToken(doc, caretPosition); if (((token != null) && (token.id() != RubyTokenId.LINE_COMMENT)) && (closest instanceof MethodDefNode) && (Utilities.getRowStart(doc, lexStartPos) == Utilities.getRowStart( doc, caretPosition))) { // Highlight exit points highlightExits((MethodDefNode)closest, highlights, info); // Fall through and set closest to null such that I don't do other highlighting } // Some nodes may span multiple lines, but the range we care about is only // on a single line because we're pulling out the lvalue - for example, // a method call may span multiple lines because of a long parameter list, // but we only highlight the methodname itself if (!(closest instanceof LocalAsgnNode || closest instanceof FCallNode || closest instanceof DAsgnNode || closest instanceof InstAsgnNode || closest instanceof ClassVarDeclNode || closest instanceof ClassVarAsgnNode || closest instanceof GlobalAsgnNode || closest instanceof ConstDeclNode)) { closest = null; } } } catch (BadLocationException ble) { // do nothing - see #154991 } finally { doc.readUnlock(); } } if (closest != null) { if (closest instanceof LocalVarNode || closest instanceof LocalAsgnNode) { // A local variable read or a parameter read, or an assignment to one of these String name = ((INameNode)closest).getName(); Node method = AstUtilities.findLocalScope(closest, path); highlightLocal(method, name, highlights); } else if (closest instanceof DAsgnNode) { // A dynamic variable read or assignment String name = ((INameNode)closest).getName(); List<Node> applicableBlocks = AstUtilities.getApplicableBlocks(path, true); for (Node block : applicableBlocks) { highlightDynamnic(block, name, highlights); } } else if (closest instanceof DVarNode) { // A dynamic variable read or assignment String name = ((DVarNode)closest).getName(); // Does not implement INameNode List<Node> applicableBlocks = AstUtilities.getApplicableBlocks(path, true); for (Node block : applicableBlocks) { highlightDynamnic(block, name, highlights); } } else if (closest instanceof InstAsgnNode) { // A field assignment String name = ((INameNode)closest).getName(); highlightInstance(root, name, highlights); } else if (closest instanceof InstVarNode) { // A field variable read highlightInstance(root, ((INameNode)closest).getName(), highlights); } else if (closest instanceof ClassVarDeclNode || closest instanceof ClassVarAsgnNode) { // A classvar assignment String name = ((INameNode)closest).getName(); highlightClassVar(root, name, highlights); } else if (closest instanceof ClassVarNode) { // A xclass variable read highlightClassVar(root, ((ClassVarNode)closest).getName(), highlights); } else if (closest instanceof GlobalVarNode) { // A global variable read String name = ((GlobalVarNode)closest).getName(); // GlobalVarNode does not implement INameNode highlightGlobal(root, name, highlights); } else if (closest instanceof BackRefNode) { // A global variable read String name = "" + ((BackRefNode)closest).getType(); // BackRefNode does not implement INameNode highlightGlobal(root, name, highlights); } else if (closest instanceof NthRefNode) { // A global variable read String name = "" + ((NthRefNode)closest).getMatchNumber(); // NthRefNode does not implement INameNode highlightGlobal(root, name, highlights); } else if (closest instanceof GlobalAsgnNode) { // A global variable assignment String name = ((INameNode)closest).getName(); highlightGlobal(root, name, highlights); } else if (closest instanceof FCallNode || closest instanceof VCallNode || closest instanceof CallNode) { // A method call String name = ((INameNode)closest).getName(); if ("raise".equals(name) || "fail".equals(name)) { // NOI18N Node def = AstUtilities.findMethod(path); if (def instanceof MethodDefNode) { highlightExits((MethodDefNode)def, highlights, info); } } else { // I shouldn't just highlight matches that match my call arity; I want // to highlight all other calls that match the same set of methods. Arity callArity = Arity.getCallArity(closest); List<Arity> defArities = new ArrayList<Arity>(); findDefArities(defArities, root, name, callArity); boolean noMatchingDefs = defArities.isEmpty(); if (noMatchingDefs) { // No matching declarations; just use this call defArities.add(callArity); } // Try placing the caret on a "?" - you'll see a method call to []. // While it's a method call it's not what the user thinks of as one, so suppress it. if (!name.equals("[]")) { highlightMethod(root, name, defArities, highlights); } if (closest instanceof CallNode && ((CallNode) closest).getReceiverNode() instanceof SelfNode && noMatchingDefs && callArity.getMinArgs() == 0 && callArity.getMaxArgs() == 0) { // could be self.inst_var highlightInstance(root, "@" + name, highlights); } } } else if (closest instanceof YieldNode || closest instanceof ReturnNode) { Node def = AstUtilities.findMethod(path); if (def instanceof MethodDefNode) { highlightExits((MethodDefNode)def, highlights, info); } } else if (closest instanceof MethodDefNode) { // A method definition. Only highlight if the caret is on the // actual name, since otherwise just placing the caret on a blank // line in a method will cause it to highlight. OffsetRange range = AstUtilities.getFunctionNameRange(root); if (range.containsInclusive(astOffset)) { String name = ((MethodDefNode)closest).getName(); highlightMethod(root, name, Collections.singletonList(Arity.getDefArity(closest)), highlights); } } else if (closest instanceof Colon2Node) { // A Class definition highlights.put(AstUtilities.getRange(closest), ColoringAttributes.MARK_OCCURRENCES); highlightClass(root, ((INameNode)closest).getName(), highlights); // TODO: alias nodes } else if (closest instanceof ConstNode || closest instanceof ConstDeclNode) { // POSSIBLY a class usage. //highlights.put(AstUtilities.getRange(closest), ColoringAttributes.MARK_OCCURRENCES); highlightClass(root, ((INameNode)closest).getName(), highlights); } else if (closest instanceof SymbolNode) { // TODO - what about Symbols for other things than fields? String name = ((INameNode)closest).getName(); highlightInstance(root, "@" + name, highlights); highlightClassVar(root, "@@" + name, highlights); highlightMethod(root, name, Collections.singletonList(Arity.UNKNOWN), highlights); highlightClass(root, name, highlights); } else if (closest instanceof AliasNode) { AliasNode an = (AliasNode)closest; // TODO - determine if the click is over the new name or the old name String newName = AstUtilities.getNameOrValue(an.getNewName()); if (newName != null) { // XXX I don't know where the old and new names are since the user COULD // have used more than one whitespace character for separation. For now I'll // just have to assume it's the normal case with one space: alias new old. // I -could- use the getPosition.getEndOffset() to see if this looks like it's // the case (e.g. node length != "alias ".length + old.length+new.length+1). // In this case I could go peeking in the source buffer to see where the // spaces are - between alias and the first word or between old and new. XXX. int newLength = newName.length(); int aliasPos = an.getPosition().getStartOffset(); String name = null; if (astOffset > (aliasPos + 6)) { // 6: "alias ".length() if (astOffset > (aliasPos + 6 + newLength)) { OffsetRange range = AstUtilities.getAliasOldRange(an); highlights.put(range, ColoringAttributes.MARK_OCCURRENCES); name = AstUtilities.getNameOrValue(an.getOldName()); } else { OffsetRange range = AstUtilities.getAliasNewRange(an); highlights.put(range, ColoringAttributes.MARK_OCCURRENCES); name = AstUtilities.getNameOrValue(an.getNewName()); } } if (name != null) { // It's over the old word: this counts as a usage. // The problem is that we don't know if it's a local, a dynamic, an instance // variable, etc. (The $ and @ parts are not included in the alias statement). // First see if it's a local variable. int count = highlights.size(); Node method = AstUtilities.findLocalScope(closest, path); // We don't want alias nodes being added while searching for locals since that // will make it look like a local was found (since the set will grow) ignoreAlias = true; try { highlightLocal(method, name, highlights); if (highlights.size() == count) { // Didn't find locals... try dynvars List<Node> applicableBlocks = AstUtilities.getApplicableBlocks(path, true); for (Node block : applicableBlocks) { highlightDynamnic(block, name, highlights); } if (highlights.size() == count) { // Didn't find locals... try methods highlightMethod(root, name, Collections.singletonList(Arity.UNKNOWN), highlights); if (highlights.size() == count) { // Didn't find methods... try instance fields highlightInstance(root, name, highlights); if (highlights.size() == count) { // Didn't find instance methods, try globals highlightGlobal(root, name, highlights); if (highlights.size() == count) { // Didn't find globals, try classes highlightClass(root, name, highlights); if (highlights.size() == count) { // Now try classvars highlightClassVar(root, name, highlights); } } } } } } } finally { ignoreAlias = false; } } } } else if (closest instanceof ArgumentNode) { // A method name (if under a DefnNode or DefsNode) or a parameter (if indirectly under an ArgsNode) String name = ((ArgumentNode)closest).getName(); // ArgumentNode doesn't implement INameNode Node parent = path.leafParent(); if (parent != null) { if (parent instanceof MethodDefNode) { //highlightMethod(root, name, // Collections.singletonList(Arity.getDefArity(parent)), highlights); highlightExits((MethodDefNode)parent, highlights, info); } else { // Parameter (check to see if its under ArgumentNode) Node method = AstUtilities.findLocalScope(closest, path); highlightLocal(method, name, highlights); } } } } if (isCancelled()) { return; } if (highlights.size() > 0) { // XXX Parsing API // if (rpr.getTranslatedSource() != null) { Map<OffsetRange, ColoringAttributes> translated = new HashMap<OffsetRange,ColoringAttributes>(2*highlights.size()); for (Map.Entry<OffsetRange,ColoringAttributes> entry : highlights.entrySet()) { OffsetRange range = LexUtilities.getLexerOffsets(info, entry.getKey()); if (range != OffsetRange.NONE) { translated.put(range, entry.getValue()); } } highlights = translated; // } this.occurrences = highlights; } else { this.occurrences = null; } } private void highlightExits( final MethodDefNode node, final Map<OffsetRange, ColoringAttributes> highlights, final Result info) { BaseDocument doc = RubyUtils.getDocument(info); if (doc == null) { return; } try { doc.readLock(); Set<Node> exits = new HashSet<Node>(); AstUtilities.findExitPoints(node, exits); for (Node exit : exits) { if (exit.isInvisible()) { continue; } highlightExitPoint(exit, highlights, info); } } finally { doc.readUnlock(); } } private void highlightExitPoint( final Node node, final Map<OffsetRange, ColoringAttributes> highlights, final Result info) { if (node.getNodeType() == NodeType.RETURNNODE) { OffsetRange astRange = AstUtilities.getRange(node); BaseDocument doc = RubyUtils.getDocument(info); if (doc != null) { try { OffsetRange lexRange = LexUtilities.getLexerOffsets(info, astRange); if (lexRange != OffsetRange.NONE) { int lineStart = Utilities.getRowStart(doc, lexRange.getStart()); int endLineStart = Utilities.getRowStart(doc, lexRange.getEnd()); if (lineStart != endLineStart) { lexRange = new OffsetRange(lexRange.getStart(), Utilities.getRowEnd(doc, lexRange.getStart())); astRange = AstUtilities.getAstOffsets(info, lexRange); } } } catch (BadLocationException ble) { // do nothing - see #154991 } highlights.put(astRange, ColoringAttributes.MARK_OCCURRENCES); } } else if (node.getNodeType() == NodeType.YIELDNODE) { // Workaround JRuby AST position error /* Yield in the following code has the wrong offsets in JRuby if component.size == 1 yield component.first else raise Cyclic.new("topological sort failed: #{component.inspect}") end */ OffsetRange range = AstUtilities.getRange(node); highlights.put(range, ColoringAttributes.MARK_OCCURRENCES); } else if (node instanceof FCallNode && (getFunctionName(node).equals("fail") || getFunctionName(node).equals("raise"))) { // NOI18N OffsetRange range = AstUtilities.getCallRange(node); highlights.put(range, ColoringAttributes.MARK_OCCURRENCES); } else { // last node // TODO: Find the last statement, and highlight it. // Be careful not to highlight the entire statement (which could be a giant if // statement spanning the whole screen); just pick the first line. try { SourcePosition pos = node.getPosition(); OffsetRange lexRange = LexUtilities.getLexerOffsets(info, new OffsetRange(pos.getStartOffset(), pos.getEndOffset())); if (lexRange != OffsetRange.NONE) { BaseDocument doc = RubyUtils.getDocument(info); if (Utilities.getRowStart(doc, lexRange.getStart()) != Utilities.getRowStart(doc, lexRange.getEnd())) { // Highlight the first line - where the nonwhitespace is int begin = Utilities.getRowFirstNonWhite(doc, lexRange.getStart()); int end = Utilities.getRowLastNonWhite(doc, lexRange.getStart()); if ((begin != -1) && (end != -1)) { OffsetRange range = new OffsetRange(begin, end + 1); highlights.put(range, ColoringAttributes.MARK_OCCURRENCES); } } else { OffsetRange range = AstUtilities.getRangeIncludeNil(node); highlights.put(range, ColoringAttributes.MARK_OCCURRENCES); } } } catch (BadLocationException ble) { // do nothing - see #154991 } } } private void highlightLocal(Node node, String name, Map<OffsetRange, ColoringAttributes> highlights) { if (node instanceof LocalVarNode) { if (((INameNode)node).getName().equals(name)) { OffsetRange range = AstUtilities.getRange(node); highlights.put(range, ColoringAttributes.MARK_OCCURRENCES); } } else if (node instanceof LocalAsgnNode) { if (((INameNode)node).getName().equals(name)) { OffsetRange range = AstUtilities.getLValueRange((LocalAsgnNode)node); highlights.put(range, ColoringAttributes.MARK_OCCURRENCES); } } else if (node instanceof ArgsNode) { ArgsNode an = (ArgsNode)node; if (an.getRequiredCount() > 0) { List<Node> args = an.childNodes(); for (Node arg : args) { if (arg instanceof ListNode) { List<Node> args2 = arg.childNodes(); for (Node arg2 : args2) { if (arg2 instanceof ArgumentNode) { if (((ArgumentNode)arg2).getName().equals(name)) { OffsetRange range = AstUtilities.getRange(arg2); highlights.put(range, ColoringAttributes.MARK_OCCURRENCES); } } else if (arg2 instanceof LocalAsgnNode) { if (((LocalAsgnNode)arg2).getName().equals(name)) { OffsetRange range = AstUtilities.getNameRange(arg2); highlights.put(range, ColoringAttributes.MARK_OCCURRENCES); } } } } } } // Rest args if (an.getRest() != null) { ArgumentNode bn = an.getRest(); if (bn.getName().equals(name)) { OffsetRange range = AstUtilities.getRange(bn); highlights.put(range, ColoringAttributes.MARK_OCCURRENCES); } } if (an.getBlock() != null) { BlockArgNode bn = an.getBlock(); if (bn.getName().equals(name)) { OffsetRange range = AstUtilities.getRange(bn); highlights.put(range, ColoringAttributes.MARK_OCCURRENCES); } } } else if (!ignoreAlias && node instanceof AliasNode) { AliasNode an = (AliasNode)node; handleAliasNode(an, name, highlights); } else if (node instanceof SymbolNode) { if (((INameNode)node).getName().equals(name)) { // NOI18N OffsetRange range = AstUtilities.getRange(node); highlights.put(range, ColoringAttributes.MARK_OCCURRENCES); } } List<Node> list = node.childNodes(); for (Node child : list) { if (child.isInvisible()) { continue; } highlightLocal(child, name, highlights); } } private void highlightDynamnic(Node node, String name, Map<OffsetRange, ColoringAttributes> highlights) { switch (node.getNodeType()) { case DVARNODE: if (((DVarNode)node).getName().equals(name)) { // Does not implement INameNode OffsetRange range = AstUtilities.getRange(node); highlights.put(range, ColoringAttributes.MARK_OCCURRENCES); } break; case DASGNNODE: if (((INameNode)node).getName().equals(name)) { OffsetRange range = AstUtilities.getLValueRange((DAsgnNode)node); highlights.put(range, ColoringAttributes.MARK_OCCURRENCES); } break; case ALIASNODE: if (!ignoreAlias) { AliasNode an = (AliasNode)node; handleAliasNode(an, name, highlights); } break; } List<Node> list = node.childNodes(); for (Node child : list) { if (child.isInvisible()) { continue; } switch (child.getNodeType()) { case ITERNODE: //case BLOCKNODE: case DEFNNODE: case DEFSNODE: case CLASSNODE: case SCLASSNODE: case MODULENODE: continue; } highlightDynamnic(child, name, highlights); } } private static void handleAliasNode(AliasNode alias, String name, Map<OffsetRange, ColoringAttributes> highlights) { String oldName = AstUtilities.getNameOrValue(alias.getOldName()); String newName = AstUtilities.getNameOrValue(alias.getNewName()); if (name.equals(newName)) { OffsetRange range = AstUtilities.getAliasNewRange(alias); highlights.put(range, ColoringAttributes.MARK_OCCURRENCES); } else if (name.equals(oldName)) { OffsetRange range = AstUtilities.getAliasOldRange(alias); highlights.put(range, ColoringAttributes.MARK_OCCURRENCES); } } private void highlightInstance(Node node, String name, Map<OffsetRange, ColoringAttributes> highlights) { if (node instanceof InstVarNode) { if (((INameNode)node).getName().equals(name)) { OffsetRange range = AstUtilities.getRange(node); highlights.put(range, ColoringAttributes.MARK_OCCURRENCES); } } else if (node instanceof InstAsgnNode) { if (((INameNode)node).getName().equals(name)) { OffsetRange range = AstUtilities.getLValueRange((InstAsgnNode)node); highlights.put(range, ColoringAttributes.MARK_OCCURRENCES); } } else if (!ignoreAlias && node instanceof AliasNode) { handleAliasNode((AliasNode) node, name, highlights); } else if (AstUtilities.isAttr(node)) { // TODO: Compute the symbols and check for equality // attr_reader, attr_accessor, attr_writer SymbolNode[] symbols = AstUtilities.getAttrSymbols(node); for (int i = 0; i < symbols.length; i++) { if (name.equals("@" + symbols[i].getName())) { OffsetRange range = AstUtilities.getRange(symbols[i]); highlights.put(range, ColoringAttributes.MARK_OCCURRENCES); } } } else if (node instanceof SymbolNode) { if (("@" + ((INameNode)node).getName()).equals(name)) { // NOI18N OffsetRange range = AstUtilities.getRange(node); highlights.put(range, ColoringAttributes.MARK_OCCURRENCES); } } else if (node instanceof CallNode) { CallNode callNode = (CallNode) node; Node receiver = callNode.getReceiverNode(); if (receiver != null && receiver instanceof SelfNode) { // check first this isn't a method call - not really exact // as it could be an inherited method, but we can't use index here String methodName = name.startsWith("@") ? name.substring(1) : name; Node method = AstUtilities.findMethod(root, methodName, Arity.getCallArity(callNode)); if (method == null && methodName.equals(callNode.getName())) { // seems to be self.inst_var, so highlight it OffsetRange range = AstUtilities.getRange(callNode); highlights.put(range, ColoringAttributes.MARK_OCCURRENCES); } } } List<Node> list = node.childNodes(); for (Node child : list) { if (child.isInvisible()) { continue; } highlightInstance(child, name, highlights); } } private void highlightClassVar(Node node, String name, Map<OffsetRange, ColoringAttributes> highlights) { if (node instanceof ClassVarNode) { if (((ClassVarNode)node).getName().equals(name)) { OffsetRange range = AstUtilities.getRange(node); highlights.put(range, ColoringAttributes.MARK_OCCURRENCES); } } else if (node instanceof ClassVarDeclNode) { if (((INameNode)node).getName().equals(name)) { OffsetRange range = AstUtilities.getLValueRange((ClassVarDeclNode)node); highlights.put(range, ColoringAttributes.MARK_OCCURRENCES); } } else if (node instanceof ClassVarAsgnNode) { if (((INameNode)node).getName().equals(name)) { OffsetRange range = AstUtilities.getLValueRange((ClassVarAsgnNode)node); highlights.put(range, ColoringAttributes.MARK_OCCURRENCES); } } else if (!ignoreAlias && node instanceof AliasNode) { handleAliasNode((AliasNode) node, name, highlights); } else if (node instanceof SymbolNode) { if (("@@" + ((INameNode)node).getName()).equals(name)) { // NOI18N OffsetRange range = AstUtilities.getRange(node); highlights.put(range, ColoringAttributes.MARK_OCCURRENCES); } // TODO - are there attr writers for class vars? // } else if (AstUtilities.isAttrReader(node) || AstUtilities.isAttrWriter(node)) { // // TODO: Compute the symbols and check for equality // // attr_reader, attr_accessor, attr_writer // SymbolNode[] symbols = AstUtilities.getAttrSymbols(node); // // for (int i = 0; i < symbols.length; i++) { // if (name.equals("@@" + symbols[i].getName())) { // OffsetRange range = AstUtilities.getRange(symbols[i]); // highlights.put(range, ColoringAttributes.MARK_OCCURRENCES); // } // } } List<Node> list = node.childNodes(); for (Node child : list) { if (child.isInvisible()) { continue; } highlightClassVar(child, name, highlights); } } private void highlightGlobal(Node node, String name, Map<OffsetRange, ColoringAttributes> highlights) { if (node instanceof GlobalVarNode) { //if (((INameNode)node).getName().equals(name)) { // GlobalVarNode does not implement INameNode if (((GlobalVarNode)node).getName().equals(name)) { OffsetRange range = AstUtilities.getRange(node); highlights.put(range, ColoringAttributes.MARK_OCCURRENCES); } } else if (node instanceof BackRefNode) { //if (((INameNode)node).getName().equals(name)) { // BackRefNode does not implement INameNode if (("" + ((BackRefNode)node).getType() + "").equals(name)) { OffsetRange range = AstUtilities.getRange(node); highlights.put(range, ColoringAttributes.MARK_OCCURRENCES); } } else if (node instanceof NthRefNode) { //if (((INameNode)node).getName().equals(name)) { // NthRefNode does not implement INameNode if (("" + ((NthRefNode)node).getMatchNumber()).equals(name)) { OffsetRange range = AstUtilities.getRange(node); highlights.put(range, ColoringAttributes.MARK_OCCURRENCES); } } else if (node instanceof GlobalAsgnNode) { if (((INameNode)node).getName().equals(name)) { OffsetRange range = AstUtilities.getLValueRange((GlobalAsgnNode)node); highlights.put(range, ColoringAttributes.MARK_OCCURRENCES); } } else if (!ignoreAlias && node instanceof AliasNode) { AliasNode an = (AliasNode)node; handleAliasNode(an, name, highlights); } else if (node instanceof SymbolNode) { if (("$" + ((INameNode)node).getName()).equals(name)) { // NOI18N OffsetRange range = AstUtilities.getRange(node); highlights.put(range, ColoringAttributes.MARK_OCCURRENCES); } } List<Node> list = node.childNodes(); for (Node child : list) { if (child.isInvisible()) { continue; } highlightGlobal(child, name, highlights); } } private void highlightMethod(Node node, String name, List<Arity> arities, Map<OffsetRange, ColoringAttributes> highlights) { // Recursively search for methods or method calls that match the name and arity if (node instanceof MethodDefNode && ((MethodDefNode)node).getName().equals(name)) { Arity defArity = Arity.getDefArity(node); for (Arity arity : arities) { if (Arity.matches(arity, defArity)) { OffsetRange range = AstUtilities.getFunctionNameRange(node); highlights.put(range, ColoringAttributes.MARK_OCCURRENCES); break; } } } else if ((node instanceof FCallNode || node instanceof CallNode || node instanceof VCallNode) && ((INameNode)node).getName().equals(name)) { Arity callArity = Arity.getCallArity(node); for (Arity arity : arities) { if (Arity.matches(callArity, arity)) { OffsetRange range = AstUtilities.getCallRange(node); highlights.put(range, ColoringAttributes.MARK_OCCURRENCES); } } } else if (!ignoreAlias && node instanceof AliasNode) { AliasNode an = (AliasNode)node; handleAliasNode(an, name, highlights); } else if (node instanceof SymbolNode) { if (((INameNode)node).getName().equals(name)) { OffsetRange range = AstUtilities.getRange(node); highlights.put(range, ColoringAttributes.MARK_OCCURRENCES); } } List<Node> list = node.childNodes(); for (Node child : list) { if (child.isInvisible()) { continue; } highlightMethod(child, name, arities, highlights); } } /** Find the definition arity that matches a given call arity */ private void findDefArities(List<Arity> defArities, Node node, String name, Arity callArity) { // Recursively search for methods or method calls that match the name and arity if (node instanceof MethodDefNode && ((MethodDefNode)node).getName().equals(name)) { Arity defArity = Arity.getDefArity(node); if (Arity.matches(callArity, defArity)) { defArities.add(defArity); } } List<Node> list = node.childNodes(); for (Node child : list) { if (child.isInvisible()) { continue; } findDefArities(defArities, child, name, callArity); } } private void highlightClass(Node node, String name, Map<OffsetRange, ColoringAttributes> highlights) { if (node instanceof ConstNode) { if (((INameNode)node).getName().equals(name)) { OffsetRange range = AstUtilities.getRange(node); highlights.put(range, ColoringAttributes.MARK_OCCURRENCES); } } else if (node instanceof ConstDeclNode) { if (((INameNode)node).getName().equals(name)) { OffsetRange range = AstUtilities.getLValueRange((ConstDeclNode)node); highlights.put(range, ColoringAttributes.MARK_OCCURRENCES); } } else if (node instanceof Colon2Node) { if (((INameNode)node).getName().equals(name)) { OffsetRange range = AstUtilities.getRange(node); highlights.put(range, ColoringAttributes.MARK_OCCURRENCES); } } else if (!ignoreAlias && node instanceof AliasNode) { AliasNode an = (AliasNode)node; handleAliasNode(an, name, highlights); } else if (node instanceof SymbolNode) { if (((INameNode)node).getName().equals(name)) { OffsetRange range = AstUtilities.getRange(node); highlights.put(range, ColoringAttributes.MARK_OCCURRENCES); } } List<Node> list = node.childNodes(); for (Node child : list) { if (child.isInvisible()) { continue; } highlightClass(child, name, highlights); } } public void setCaretPosition(int position) { this.caretPosition = position; } private String getFunctionName(Node node) { return ((FCallNode) node).getName(); } @Override public int getPriority() { return 200; } @Override public Class<? extends Scheduler> getSchedulerClass() { return Scheduler.CURSOR_SENSITIVE_TASK_SCHEDULER; } }