Revision: 1558 Author: jasonpmorrison Date: 2006-08-08 19:40:35 -0700 (Tue, 08 Aug 2006) ViewCVS: http://svn.sourceforge.net/rubyeclipse/?rev=1558&view=rev Log Message: ----------- Working on completion for elements available in scope (args/locals/instvars/classvars/globals) 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-08 23:59:21 UTC (rev 1557) +++ branches/type_inferrence/trunk/org.rubypeople.rdt.ui/src/org/rubypeople/rdt/internal/ui/text/ruby/RubyCompletionProcessor.java 2006-08-09 02:40:35 UTC (rev 1558) @@ -1,12 +1,16 @@ package org.rubypeople.rdt.internal.ui.text.ruby; +import java.lang.reflect.Method; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.HashSet; import java.util.Iterator; +import java.util.LinkedList; import java.util.List; +import java.util.Set; import org.eclipse.jface.text.IRegion; import org.eclipse.jface.text.ITextSelection; @@ -24,13 +28,31 @@ 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.ClassNode; +import org.jruby.ast.ClassVarAsgnNode; +import org.jruby.ast.ClassVarDeclNode; +import org.jruby.ast.ClassVarNode; +import org.jruby.ast.DefnNode; +import org.jruby.ast.DefsNode; +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.lexer.yacc.SyntaxException; import org.rubypeople.rdt.core.CompletionRequestor; 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.RubyModelException; +import org.rubypeople.rdt.internal.core.parser.RubyParser; import org.rubypeople.rdt.internal.corext.template.ruby.RubyContextType; +import org.rubypeople.rdt.internal.ti.util.FirstPrecursorNodeLocator; +import org.rubypeople.rdt.internal.ti.util.INodeAcceptor; +import org.rubypeople.rdt.internal.ti.util.ScopedNodeLocator; import org.rubypeople.rdt.internal.ui.RubyPlugin; import org.rubypeople.rdt.internal.ui.RubyPluginImages; import org.rubypeople.rdt.internal.ui.text.template.contentassist.RubyTemplateAccess; @@ -181,7 +203,7 @@ */ private ICompletionProposal[] determineRubyElementProposals( ITextViewer viewer, int documentOffset) { - Collection completionProposals = getDocumentsRubyElements(documentOffset); + Collection completionProposals = getDocumentsRubyElementsInScope(documentOffset); String prefix = getCurrentPrefix(viewer.getDocument().get(), documentOffset); // following the JDT convention, if there's no text already entered, @@ -358,25 +380,41 @@ * * @return a List of the names of all the elements in the current RubyScript */ - private Collection getDocumentsRubyElements(int documentOffset) { + private Collection getDocumentsRubyElementsInScope(int documentOffset) { IRubyScript script = fManager.getWorkingCopy(fEditor.getEditorInput()); // FIXME Get only the elements in the current scope! - Collection elements = getElements(script); + Collection elements = getElementsInScope(script, documentOffset); + +// 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()))); } + 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; } @@ -418,7 +456,114 @@ IRubyElement.CONSTANT, IRubyElement.CLASS_VAR, 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) + * @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; } + // 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() ); +// } +// } + + + // 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()) ); + } + + + // 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 ); + }}); + + // 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); + } + } + + // Add instnace and class variables to matched elements + elements.addAll( instanceAndClassVarNames ); + } + + // 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' + } + + return elements; + } + + /** + * Gets the name of a node by reflectively invoking "getName()" on it; + * helper method just to cut many "instanceof/cast" pairs. + * @param node + * @return name or null + */ + // TODO Copy/pasted from DefaultOccurrencesFinder, refactor these two methods to a common location. + private String getNameReflectively( Node node ) { + try { + Method getNameMethod = node.getClass().getMethod("getName", new Class[]{}); + Object name = getNameMethod.invoke( node, new Object[0] ); + return (String)name; + } catch (Exception e) { + return null; + } + } + + private ICompletionProposal[] determineKeywordProposals(ITextViewer viewer, int documentOffset) { initKeywordProposals(); This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |