Revision: 1568 Author: jasonpmorrison Date: 2006-08-14 21:21:04 -0700 (Mon, 14 Aug 2006) ViewCVS: http://svn.sourceforge.net/rubyeclipse/?rev=1568&view=rev Log Message: ----------- * Updated RubyCompletionProcessor to add element completion for method locals/args, instance variables, class variables, globals, method definitions, class and module definitions available to the current scope and those included through mixins and inheritance. Items not covered include "class << self" insertions and modules included inside method calls (such as acts_as_*) Modified Paths: -------------- branches/type_inferrence/trunk/org.rubypeople.rdt.ui/src/org/rubypeople/rdt/internal/ui/text/ruby/RubyCompletionProcessor.java Modified: branches/type_inferrence/trunk/org.rubypeople.rdt.ui/src/org/rubypeople/rdt/internal/ui/text/ruby/RubyCompletionProcessor.java =================================================================== --- branches/type_inferrence/trunk/org.rubypeople.rdt.ui/src/org/rubypeople/rdt/internal/ui/text/ruby/RubyCompletionProcessor.java 2006-08-15 04:18:13 UTC (rev 1567) +++ branches/type_inferrence/trunk/org.rubypeople.rdt.ui/src/org/rubypeople/rdt/internal/ui/text/ruby/RubyCompletionProcessor.java 2006-08-15 04:21:04 UTC (rev 1568) @@ -28,34 +28,39 @@ import org.eclipse.jface.text.templates.TemplateContextType; import org.eclipse.swt.graphics.Image; import org.eclipse.ui.IEditorPart; -import org.jruby.ast.ArgsNode; -import org.jruby.ast.ArgumentNode; -import org.jruby.ast.CallNode; import org.jruby.ast.ClassNode; import org.jruby.ast.ClassVarAsgnNode; import org.jruby.ast.ClassVarDeclNode; import org.jruby.ast.ClassVarNode; +import org.jruby.ast.Colon2Node; +import org.jruby.ast.ConstNode; import org.jruby.ast.DefnNode; import org.jruby.ast.DefsNode; -import org.jruby.ast.FCallNode; import org.jruby.ast.InstAsgnNode; import org.jruby.ast.InstVarNode; import org.jruby.ast.ModuleNode; import org.jruby.ast.Node; import org.jruby.ast.ScopeNode; -import org.jruby.ast.VCallNode; import org.jruby.lexer.yacc.SyntaxException; -import org.rubypeople.rdt.core.CompletionRequestor; +import org.jruby.parser.RubyParserPool; import org.rubypeople.rdt.core.IParent; import org.rubypeople.rdt.core.IRubyElement; import org.rubypeople.rdt.core.IRubyProject; import org.rubypeople.rdt.core.IRubyScript; +import org.rubypeople.rdt.core.ISourceReference; +import org.rubypeople.rdt.core.IType; import org.rubypeople.rdt.core.RubyModelException; +import org.rubypeople.rdt.internal.codeassist.RubyElementRequestor; +import org.rubypeople.rdt.internal.core.RubyElement; +import org.rubypeople.rdt.internal.core.RubyScript; +import org.rubypeople.rdt.internal.core.RubyScriptStructureBuilder; +import org.rubypeople.rdt.internal.core.RubyType; import org.rubypeople.rdt.internal.core.parser.RubyParser; import org.rubypeople.rdt.internal.corext.template.ruby.RubyContextType; import org.rubypeople.rdt.internal.ti.util.AttributeLocator; -import org.rubypeople.rdt.internal.ti.util.FirstPrecursorNodeLocator; +import org.rubypeople.rdt.internal.ti.util.ClosestSpanningNodeLocator; import org.rubypeople.rdt.internal.ti.util.INodeAcceptor; +import org.rubypeople.rdt.internal.ti.util.MethodDefinitionLocator; import org.rubypeople.rdt.internal.ti.util.ScopedNodeLocator; import org.rubypeople.rdt.internal.ui.RubyPlugin; import org.rubypeople.rdt.internal.ui.RubyPluginImages; @@ -380,45 +385,91 @@ /** * Gets all the distinct elements in the current RubyScript - * @param documentOffset + * @param offset * * @return a List of the names of all the elements in the current RubyScript */ - private Collection getDocumentsRubyElementsInScope(int documentOffset) { + private Collection getDocumentsRubyElementsInScope(int offset) { IRubyScript script = fManager.getWorkingCopy(fEditor.getEditorInput()); - // FIXME Get only the elements in the current scope! + - Collection elements = getElementsInScope(script, documentOffset); +// Collection elements = getElementsInScope(script, offset); -// Collection elements = getElements(script); -// System.out.println(" :: " + elements.size() ); - - IRubyProject project = script.getRubyProject(); - // Add all the classes and modules in the project - elements.addAll(addClassesAndModulesInProject(project)); -// System.out.println(" :: " + elements.size() ); - - - // Add all the classes and modules in referenced projects - for (Iterator iter = project.getReferencedProjects().iterator(); iter - .hasNext();) { - elements.addAll(addClassesAndModulesInProject(((IRubyProject) iter - .next()))); + String source = ""; + Collection elements = new ArrayList(); + try { + // Get the script's source. If possible, get the most recent contents. + if ( script instanceof RubyScript ) { + source = new String(((RubyScript)script).getContents()); + } else { + source = script.getSource(); + } + + // Get all references projects + List<IRubyProject> projects = new ArrayList<IRubyProject>(); + projects.add(script.getRubyProject()); + projects.addAll(script.getRubyProject().getReferencedProjects()); + + // Parse + Node rootNode = (new RubyParser()).parse(source); + if ( rootNode == null ) { return elements; } + + // Find the enclosing method to get locals and args + Node enclosingMethodNode = ClosestSpanningNodeLocator.Instance().findClosestSpanner(rootNode, offset, new INodeAcceptor() { + public boolean doesAccept(Node node) { + return ( node instanceof DefnNode || node instanceof DefsNode ); + } + }); + + // Add local vars and arguments + if ( enclosingMethodNode != null ) { + ScopeNode scopeNode = null; + if ( enclosingMethodNode instanceof DefnNode ) { scopeNode = (ScopeNode)((DefnNode)enclosingMethodNode).getBodyNode(); } + if ( enclosingMethodNode instanceof DefsNode ) { scopeNode = (ScopeNode)((DefsNode)enclosingMethodNode).getBodyNode(); } + if ( scopeNode != null && scopeNode.getLocalNames().length > 0 ) { + elements.addAll( Arrays.asList (scopeNode.getLocalNames()) ); + } + } + + // Find the enclosing type (class or module) to get instance and classvars from + Node enclosingTypeNode = ClosestSpanningNodeLocator.Instance().findClosestSpanner(rootNode, offset, new INodeAcceptor() { + public boolean doesAccept(Node node) { + return ( node instanceof ClassNode || node instanceof ModuleNode ); + } + }); + + // Add members from enclosing type + if ( enclosingTypeNode != null ) { + elements.addAll( getMembersAvailableInsideType( enclosingTypeNode, script ) ); + } + + // Add all globals, classes, and modules + for (Iterator iter = projects.iterator(); iter.hasNext();) { + IRubyProject nextProject = (IRubyProject)(iter.next()); + + System.out.println("*** Adding globals/classes/modules available in project: " + nextProject.getElementName() ); + + elements.addAll(getElementsOfType( nextProject, new int[] { IRubyElement.GLOBAL })); + elements.addAll(addClassesAndModulesInProject( nextProject )); + } + + + // always add Kernel methods + elements.addAll(addKernelMethods()); + + + } catch ( RubyModelException rme ) { + System.out.println("RubyModelException in RubyCompletionProcessor::getElementsInScope()"); + rme.printStackTrace(); + // Return empty 'elements' + } catch ( SyntaxException se ) { + System.out.println("SyntaxError in RubyCompletionProcessor::getElementsInScope()"); + se.printStackTrace(); + // Return empty 'elements' } - System.out.println(" :: " + elements.size() ); + - - // TODO Add all the methods defined in included modules for the class - // TODO Add all the methods defined in superclasses for the class/module - // always add Kernel methods - elements.addAll(addKernelMethods()); - System.out.println(" :: " + elements.size() ); - - for ( Object element : elements ) - { -// System.out.println(" -- Element: " + (String)element); - } return elements; } @@ -461,100 +512,190 @@ IRubyElement.INSTANCE_VAR }); } + /** - * Gets the names of elements available in the specified scope. This includes: - * - Method arguments - * - Method locals - * - Enclosing class/module instance variables - * - Enclosing class/module class variables - * - Globals - * - * @param script Script to collect available elements from - * @param offset Offset in script to determine the access scope (for args/locals/instvars/classvars) + * Gets the memebrs available inside a type node (ModuleNode, ClassNode): + * - Instance variables + * - Class variables + * - Methods + * + * @param typeNode * @return */ - public Collection getElementsInScope(IRubyScript script, int offset) { - String source = ""; - Collection elements = new ArrayList(); - try { - // Get the script's source, and parse it. - source = script.getSource(); - Node rootNode = (new RubyParser()).parse(source); - if ( rootNode == null ) { return elements; } + private List<String> getMembersAvailableInsideType(Node typeNode, IRubyScript script) throws RubyModelException { + List<String> elements = new LinkedList<String>(); + if ( typeNode == null ) { return elements; } + + // Get type name + String typeName = null; + if ( typeNode instanceof ClassNode ) { typeName = ((Colon2Node)((ClassNode)typeNode).getCPath()).getName(); } + if ( typeNode instanceof ModuleNode ) { typeName = ((Colon2Node)((ModuleNode)typeNode).getCPath()).getName(); } + if ( typeName == null ) { return elements; } + + // XXX rubyType may not be in script, but rather be defined in another script +// IType rubyType = new RubyType( (RubyElement)script, typeName ); + //Better method: + // Find the named type +// IType rubyType = findTypeFromAllProjects(typeName, script); - // Find the enclosing method to get locals and args - Node enclosingMethodNode = FirstPrecursorNodeLocator.Instance().findFirstPrecursor(rootNode, offset, new INodeAcceptor() { - public boolean doesAccept(Node node) { - return ( node instanceof DefnNode || node instanceof DefsNode ); - }}); - - // Add locally available arguments -// ArgsNode argsNode = null; -// if ( enclosingMethodNode instanceof DefnNode ) { argsNode = (ArgsNode)((DefnNode)enclosingMethodNode).getArgsNode(); } -// if ( enclosingMethodNode instanceof DefsNode ) { argsNode = (ArgsNode)((DefsNode)enclosingMethodNode).getArgsNode(); } -// if ( argsNode != null ) { -// for (Iterator iter = argsNode.getArgs().iterator(); iter.hasNext();) { -// elements.add( ((ArgumentNode)iter.next()).getName() ); +// System.out.println(" -- Located RubyType info."); +// System.out.println(" -- Superclass: " + rubyType.getSuperclassName() ); + +// if ( rubyType != null ) { +// String[] includedModuleNames = rubyType.getIncludedModuleNames(); +// if ( includedModuleNames != null ) { +// for ( String moduleName : rubyType.getIncludedModuleNames() ) { +// System.out.println(" -- Includes module: " + moduleName); // } // } - +// } + + + + // Get superclass and add its public members + List<Node> superclassNodes = getSuperclassNodes( typeNode, script ); + + for ( Node superclassNode : superclassNodes ) { + elements.addAll( getMembersAvailableInsideType( superclassNode, script ) ); + } + + // Get public members of mixins + List<String> mixinNames = getIncludedMixinNames( typeName, script ); + for ( String mixinName : mixinNames ) { + List<Node> mixinDeclarations = getTypeDeclarationNodes( mixinName, script ); + for ( Node mixinDeclaration : mixinDeclarations ) { + elements.addAll( getMembersAvailableInsideType( mixinDeclaration, script ) ); + } + } + + // Get instance and class variables available in the enclosing type + List<Node> instanceAndClassVars = ScopedNodeLocator.Instance().findNodesInScope(typeNode, new INodeAcceptor() { + public boolean doesAccept(Node node) { + return ( node instanceof InstVarNode || + node instanceof InstAsgnNode || + node instanceof ClassVarNode || + node instanceof ClassVarDeclNode || + node instanceof ClassVarAsgnNode ); + } + }); + + if ( instanceAndClassVars != null ) { + // Get the unique names of instance and class variables + Set instanceAndClassVarNames = new HashSet(instanceAndClassVars.size()); + for ( Node varNode : instanceAndClassVars ) { + String name = getNameReflectively(varNode); + if ( name != null ) { + instanceAndClassVarNames.add(name); + } + } + + // Add instance and class variables to matched elements + elements.addAll( instanceAndClassVarNames ); + } + + // Get method names defined by DefnNodes and DefsNodes + elements.addAll( MethodDefinitionLocator.Instance().findMethodDefinitionsInScope(typeNode) ); + + // Get instance and class vars defined by [c]attr_* calls + elements.addAll( AttributeLocator.Instance().findInstanceAttributesInScope(typeNode) ); + + return elements; + } - // Add local vars - arguments are included - ScopeNode scopeNode = null; - if ( enclosingMethodNode instanceof DefnNode ) { scopeNode = (ScopeNode)((DefnNode)enclosingMethodNode).getBodyNode(); } - if ( enclosingMethodNode instanceof DefsNode ) { scopeNode = (ScopeNode)((DefsNode)enclosingMethodNode).getBodyNode(); } - if ( scopeNode != null && scopeNode.getLocalNames().length > 0 ) { - elements.addAll( Arrays.asList (scopeNode.getLocalNames()) ); + /** + * Finds all nodes that declare a type that is a superclass of the specified node. Example: + * + * """ + * class Klass;def meth_1;1;end;end + * class Klass;def meth_2;2;end;end + * + * class SubKlass < Klass;end + * """ + * + * Issuing getSuperClassNodes() on the ClassNode declaring SubKlass would return two ClassNodes; + * one for each definition of Klass. + * + * @param typeNode Node to find superclass nodes of + * @return List of ClassNode or ModuleNode + */ + private List<Node> getSuperclassNodes( Node typeNode, IRubyScript script ) { + if ( typeNode instanceof ClassNode ) { + Node superNode = ((ClassNode)typeNode).getSuperNode(); + if ( superNode instanceof ConstNode ) { + String superclassName = ((ConstNode)superNode).getName(); + return getTypeDeclarationNodes( superclassName, script ); } + } + + return new ArrayList<Node>(); + } + + private IType findTypeFromAllProjects(String typeName, IRubyScript rootScript) { + // Grab the project and all referred projects + List<IRubyProject> projects = new LinkedList<IRubyProject>(); + projects.add(rootScript.getRubyProject()); + projects.addAll(rootScript.getRubyProject().getReferencedProjects()); + List<IRubyProject> refProjects = rootScript.getRubyProject().getReferencedProjects(); - // Find the enclosing type (class or module) to get instance and classvars from - Node enclosingTypeNode = FirstPrecursorNodeLocator.Instance().findFirstPrecursor(rootNode, offset, new INodeAcceptor() { - public boolean doesAccept(Node node) { - return ( node instanceof ClassNode || node instanceof ModuleNode ); - }}); + // Find the named type + RubyElementRequestor completer = new RubyElementRequestor(projects.toArray(new IRubyProject[]{})); + return completer.findType(typeName); + } + + /** Lookup type declaration nodes */ + private List<Node> getTypeDeclarationNodes( String typeName, IRubyScript script ) { + System.out.println("Being asked for the type decl node for " + typeName ); + + // Find the named type + IType type = findTypeFromAllProjects(typeName, script); + + try { + if ( type instanceof RubyType ) { - // Get instance and class variables available in the enclosing type - List<Node> instanceAndClassVars = ScopedNodeLocator.Instance().findNodesInScope(enclosingTypeNode, new INodeAcceptor() { - public boolean doesAccept(Node node) { - return ( node instanceof InstVarNode || - node instanceof InstAsgnNode || - node instanceof ClassVarNode || - node instanceof ClassVarDeclNode || - node instanceof ClassVarAsgnNode ); - }}); - - if ( instanceAndClassVars != null ) { - // Get the unique names of instance and class variables - Set instanceAndClassVarNames = new HashSet(instanceAndClassVars.size()); - for ( Node varNode : instanceAndClassVars ) { - String name = getNameReflectively(varNode); - if ( name != null ) { - instanceAndClassVarNames.add(name); + // FIXME This feels a little hacky and backwards - RubyType.getSource() and then parse... consider reworking the clients to this method to accept RubyTypes or something similar? + // Find source and parse + RubyType rubyType = (RubyType)type; + String source = rubyType.getSource(); + + // FIXME Why does the parser balk on \r chars? + source = source.replace('\r', ' '); + Node rootNode = (new RubyParser()).parse( source ); + + // Bail if the parse fails + if ( rootNode == null ) { return new ArrayList(); } + + // Return any type declaration nodes in included source + return ScopedNodeLocator.Instance().findNodesInScope(rootNode, new INodeAcceptor() { + public boolean doesAccept(Node node) { + return ( node instanceof ClassNode ) || + ( node instanceof ModuleNode ); } - } - - // Add instnace and class variables to matched elements - elements.addAll( instanceAndClassVarNames ); + }); } - // Get instance and class vars defined by [c]attr_* calls - List<String> attributes = AttributeLocator.Instance().findInstanceAttributesInScope(enclosingTypeNode); - elements.addAll( attributes ); - - - // Add all the globals: like magic compared to the others, hey? - elements.addAll( getElementsOfType( script, new int[] { IRubyElement.GLOBAL })); - } catch ( RubyModelException rme ) { - // Return empty 'elements' - } catch ( SyntaxException se ) { - // Return empty 'elements' + rme.printStackTrace(); } - - return elements; + + return new ArrayList<Node>(0); } + private List<String> getIncludedMixinNames( String typeName, IRubyScript script ) { + IType rubyType = new RubyType( (RubyElement)script, typeName ); + + try { + String[] includedModuleNames = rubyType.getIncludedModuleNames(); + if ( includedModuleNames != null ) { + return Arrays.asList(rubyType.getIncludedModuleNames()); + } else { + return new ArrayList<String>(0); + } + } catch (RubyModelException e) { + return new ArrayList<String>(0); + } + } + /** * Gets the name of a node by reflectively invoking "getName()" on it; * helper method just to cut many "instanceof/cast" pairs. This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |