package com.aptana.rdt.internal.ui.text; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.jface.text.BadLocationException; import org.jruby.ast.ArrayNode; import org.jruby.ast.CallNode; import org.jruby.ast.Node; import org.jruby.ast.RootNode; import org.rubypeople.rdt.core.CompletionProposal; import org.rubypeople.rdt.core.IMethod; import org.rubypeople.rdt.core.IRubyElement; import org.rubypeople.rdt.core.RubyModelException; import org.rubypeople.rdt.core.search.CollectingSearchRequestor; import org.rubypeople.rdt.core.search.IRubySearchConstants; import org.rubypeople.rdt.core.search.IRubySearchScope; import org.rubypeople.rdt.core.search.SearchEngine; import org.rubypeople.rdt.core.search.SearchMatch; import org.rubypeople.rdt.core.search.SearchParticipant; import org.rubypeople.rdt.core.search.SearchPattern; import org.rubypeople.rdt.internal.core.parser.InOrderVisitor; import org.rubypeople.rdt.internal.core.util.ASTUtil; import org.rubypeople.rdt.internal.ti.util.OffsetNodeLocator; import org.rubypeople.rdt.internal.ui.rubyeditor.ASTProvider; import org.rubypeople.rdt.internal.ui.text.ruby.RubyContentAssistInvocationContext; import org.rubypeople.rdt.ui.text.ruby.RubyCompletionProposalComputer; import com.aptana.rdt.AptanaRDTPlugin; public class HashKeyHeuristicProposalComputer extends RubyCompletionProposalComputer { public HashKeyHeuristicProposalComputer() { super(); } @Override protected List<CompletionProposal> doComputeCompletionProposals(RubyContentAssistInvocationContext context, IProgressMonitor monitor) { return computeHashKeySuggestions(); } private List<CompletionProposal> computeHashKeySuggestions() { List<CompletionProposal> proposals = new ArrayList<CompletionProposal>(); String methodCall = getMethodName(); String args = getArgumentsToMethodCall(); int argIndex = calculateArgIndex(args); List<IRubyElement> methods = search(IRubyElement.METHOD, methodCall, IRubySearchConstants.DECLARATIONS, SearchPattern.R_EXACT_MATCH); for (IRubyElement element : methods) { IMethod method = (IMethod) element; try { String[] parameters = method.getParameterNames(); if (parameters == null || parameters.length == 0) continue; if (parameters.length <= argIndex) { argIndex = parameters.length - 1; } String param = parameters[argIndex]; if (!param.endsWith(" = {}")) continue; // Now traverse the method's AST and find out what valid options are! RootNode ast = ASTProvider.getASTProvider().getAST(method.getRubyScript(), ASTProvider.WAIT_YES, new NullProgressMonitor()); Node methodDefNode = OffsetNodeLocator.Instance().getNodeAtOffset(ast, method.getSourceRange().getOffset()); String variableName = param.substring(0, param.indexOf(' ')); ValidOptionVisitor visitor = new ValidOptionVisitor(variableName); methodDefNode.accept(visitor); Set<String> options = visitor.getValidOptions(); for (String option : options) { CompletionProposal proposal = new CompletionProposal(CompletionProposal.KEYWORD, option, 201); proposal.setName(option); int start = fContext.getInvocationOffset(); proposal.setReplaceRange(start, start + option.length()); proposals.add(proposal); } } catch (RubyModelException e) { AptanaRDTPlugin.log(e); } } return proposals; } protected List<IRubyElement> search(int type, String patternString, int limitTo, int matchRule) { List<IRubyElement> elements = new ArrayList<IRubyElement>(); try { SearchEngine engine = new SearchEngine(); SearchPattern pattern = SearchPattern.createPattern(type, patternString, limitTo, matchRule); SearchParticipant[] participants = new SearchParticipant[] { SearchEngine.getDefaultSearchParticipant() }; IRubySearchScope scope = null; if (fContext.getRubyScript() == null) { scope = SearchEngine.createWorkspaceScope(); } else { scope = SearchEngine.createRubySearchScope(new IRubyElement[] { fContext.getRubyScript() .getRubyProject() }); } CollectingSearchRequestor requestor = new CollectingSearchRequestor(); engine.search(pattern, participants, scope, requestor, new NullProgressMonitor()); List<SearchMatch> matches = requestor.getResults(); for (SearchMatch match : matches) { elements.add((IRubyElement) match.getElement()); } } catch (CoreException e) { AptanaRDTPlugin.log(e); } return elements; } private String getArgumentsToMethodCall() { String prefix = getStatementPrefix(); String methodCall = getMethodName(); String args = prefix.trim().substring(methodCall.length()); if (args.startsWith("(")) args = args.substring(1); return args; } private String getMethodName() { String prefix = getStatementPrefix(); String methodCall = prefix.trim(); int space = methodCall.indexOf(" "); if (space != -1) { methodCall = methodCall.substring(0, space); } space = methodCall.indexOf("("); if (space != -1) { methodCall = methodCall.substring(0, space); } return methodCall; } private String getStatementPrefix() { try { return fContext.computeStatementPrefix().toString(); } catch (BadLocationException e) { AptanaRDTPlugin.log(e); return ""; } } private int calculateArgIndex(String prefix) { String[] args = prefix.split(","); if (args.length == 1) { if (prefix.indexOf(",") == -1) return 0; return 1; } return args.length; } private static class ValidOptionVisitor extends InOrderVisitor { private Set<String> options = new HashSet<String>(); private String variableName; private boolean stringify = false; public ValidOptionVisitor(String variableName) { this.variableName = variableName; } public Set<String> getValidOptions() { if (stringify) { // convert strings to symbols Set<String> symbols = new HashSet<String>(); for (String option : options) { if (option.startsWith("'") || option.startsWith("\"")) { symbols.add(":" + option.substring(1, option.length() - 5) + " => "); } } return symbols; } return options; } @Override public Object visitCallNode(CallNode iVisited) { String methodName = iVisited.getName(); if (methodName.equals("[]")) { Node receiver = iVisited.getReceiverNode(); if (ASTUtil.getNameReflectively(receiver).equals(variableName)) { ArrayNode arguments = (ArrayNode) iVisited.getArgsNode(); Node arg = arguments.get(0); String value = ASTUtil.stringRepresentation(arg); if (value != null) { options.add(value + " => "); } } } else if (methodName.equals("stringify_keys")) { Node receiver = iVisited.getReceiverNode(); if (ASTUtil.getNameReflectively(receiver).equals(variableName)) { stringify = true; } } return super.visitCallNode(iVisited); } } }