| /******************************************************************************* |
| * Copyright (c) 2005, 2007 IBM Corporation and others. |
| * 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 |
| * |
| |
| *******************************************************************************/ |
| package org.eclipse.dltk.ruby.internal.core.codeassist; |
| |
| import java.text.Collator; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.Comparator; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Locale; |
| import java.util.Set; |
| |
| import org.eclipse.dltk.ast.ASTNode; |
| import org.eclipse.dltk.ast.declarations.ModuleDeclaration; |
| import org.eclipse.dltk.ast.expressions.CallExpression; |
| import org.eclipse.dltk.ast.expressions.NumericLiteral; |
| import org.eclipse.dltk.ast.parser.ISourceParser; |
| import org.eclipse.dltk.ast.references.ConstantReference; |
| import org.eclipse.dltk.ast.references.SimpleReference; |
| import org.eclipse.dltk.codeassist.IAssistParser; |
| import org.eclipse.dltk.codeassist.ScriptCompletionEngine; |
| import org.eclipse.dltk.core.CompletionProposal; |
| import org.eclipse.dltk.core.DLTKLanguageManager; |
| import org.eclipse.dltk.core.IField; |
| import org.eclipse.dltk.core.IMethod; |
| import org.eclipse.dltk.core.IModelElement; |
| import org.eclipse.dltk.core.ISourceModule; |
| import org.eclipse.dltk.core.IType; |
| import org.eclipse.dltk.core.ModelException; |
| import org.eclipse.dltk.core.mixin.IMixinElement; |
| import org.eclipse.dltk.core.mixin.MixinModel; |
| import org.eclipse.dltk.evaluation.types.AmbiguousType; |
| import org.eclipse.dltk.evaluation.types.IClassType; |
| import org.eclipse.dltk.internal.core.ModelElement; |
| import org.eclipse.dltk.ruby.ast.RubyBlock; |
| import org.eclipse.dltk.ruby.ast.RubyCallArgumentsList; |
| import org.eclipse.dltk.ruby.ast.RubyColonExpression; |
| import org.eclipse.dltk.ruby.ast.RubyDAssgnExpression; |
| import org.eclipse.dltk.ruby.ast.RubyDVarExpression; |
| import org.eclipse.dltk.ruby.core.RubyNature; |
| import org.eclipse.dltk.ruby.core.RubyPlugin; |
| import org.eclipse.dltk.ruby.core.model.FakeField; |
| import org.eclipse.dltk.ruby.core.utils.RubySyntaxUtils; |
| import org.eclipse.dltk.ruby.internal.parser.mixin.IRubyMixinElement; |
| import org.eclipse.dltk.ruby.internal.parser.mixin.RubyMixinClass; |
| import org.eclipse.dltk.ruby.internal.parser.mixin.RubyMixinElementInfo; |
| import org.eclipse.dltk.ruby.internal.parser.mixin.RubyMixinMethod; |
| import org.eclipse.dltk.ruby.internal.parser.mixin.RubyMixinModel; |
| import org.eclipse.dltk.ruby.internal.parser.mixin.RubyMixinVariable; |
| import org.eclipse.dltk.ruby.internal.parsers.jruby.ASTUtils; |
| import org.eclipse.dltk.ruby.typeinference.IMixinSearchRequestor; |
| import org.eclipse.dltk.ruby.typeinference.RubyClassType; |
| import org.eclipse.dltk.ruby.typeinference.RubyModelUtils; |
| import org.eclipse.dltk.ruby.typeinference.RubyTypeInferencingUtils; |
| import org.eclipse.dltk.ti.BasicContext; |
| import org.eclipse.dltk.ti.DLTKTypeInferenceEngine; |
| import org.eclipse.dltk.ti.goals.ExpressionTypeGoal; |
| import org.eclipse.dltk.ti.types.IEvaluatedType; |
| import org.jruby.util.collections.WeakHashSet; |
| |
| public class RubyCompletionEngine extends ScriptCompletionEngine { |
| |
| // private final static int RELEVANCE_FREE_SPACE = 10000000; |
| |
| /** |
| * this variable is updated when keyword proposals are added |
| */ |
| private int relevanceKeyword = 1000000; |
| |
| private final static int RELEVANCE_METHODS = 100000; |
| |
| private final static String[] globalVars = { "$DEBUG", "$$", "$-i", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ |
| "$deferr", "$/", "$'", "$stdout", "$-l", "$-I", "$.", "$KCODE", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$ |
| "$binding", "$-w", "$FILENAME", "$defout", "$,", "$`", "$-F", "$*", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$ |
| "$LOADED_FEATURES", "$stdin", "$-p", "$:", "$\\", "$=", "$!", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ |
| "$-v", "$>", "$&", "$;", "$SAFE", "$PROGRAM_NAME", "$\"", "$-d", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$ |
| "$?", "$-0", "$+", "$@", "$-a", "$VERBOSE", "$stderr", "$~", "$0", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$ //$NON-NLS-9$ |
| "$LOAD_PATH", "$<", "$_", "$-K" }; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ |
| |
| private DLTKTypeInferenceEngine inferencer; |
| private ISourceParser parser = null; |
| private MixinModel model; |
| private HashSet completedNames = new HashSet(); |
| private WeakHashSet intresting = new WeakHashSet(); |
| |
| private ASTNode completionNode; |
| |
| private final Comparator modelElementComparator = new Comparator() { |
| |
| Collator collator = Collator.getInstance(Locale.ENGLISH); |
| |
| public int compare(Object arg0, Object arg1) { |
| if (arg0 instanceof IModelElement && arg1 instanceof IModelElement) { |
| IModelElement me1 = (IModelElement) arg1; |
| IModelElement me2 = (IModelElement) arg0; |
| int r = -collator.compare(me1.getElementName(), me2 |
| .getElementName()); |
| if (r == 0) { |
| // prefer elements from current module |
| boolean cur1 = me1.getAncestor(IModelElement.SOURCE_MODULE) |
| .equals(currentModule); |
| boolean cur2 = me2.getAncestor(IModelElement.SOURCE_MODULE) |
| .equals(currentModule); |
| if (cur1 == cur2) { |
| return 0; |
| } else |
| return cur1 ? -1 : 1; |
| } else |
| return r; |
| } |
| return 0; |
| } |
| |
| /* |
| * private int calcVisibility(IMethod method1) throws ModelException { |
| * return (method1.getFlags() & Modifiers.AccPrivate) + |
| * (method1.getFlags() & Modifiers.AccProtected) + (method1.getFlags() & |
| * Modifiers.AccPublic); } |
| */ |
| |
| }; |
| |
| private ISourceModule currentModule; |
| |
| public RubyCompletionEngine() { |
| this.inferencer = new DLTKTypeInferenceEngine(); |
| this.model = RubyMixinModel.getRawInstance(); |
| this.parser = DLTKLanguageManager.getSourceParser(RubyNature.NATURE_ID); |
| } |
| |
| protected int getEndOfEmptyToken() { |
| return 0; |
| } |
| |
| protected String processMethodName(IMethod method, String token) { |
| return null; |
| } |
| |
| protected String processTypeName(IType method, String token) { |
| return null; |
| } |
| |
| public IAssistParser getParser() { |
| return null; |
| } |
| |
| private boolean afterColons(String content, int position) { |
| if (position < 2) |
| return false; |
| if (content.charAt(position - 1) == ':' |
| && content.charAt(position - 2) == ':') |
| return true; |
| return false; |
| } |
| |
| private boolean afterDollar(String content, int position) { |
| if (position < 1) |
| return false; |
| if (content.charAt(position - 1) == '$') |
| return true; |
| return false; |
| } |
| |
| private boolean afterAt(String content, int position) { |
| if (position < 1) |
| return false; |
| if (content.charAt(position - 1) == '@') |
| return true; |
| return false; |
| } |
| |
| private boolean afterAt2(String content, int position) { |
| if (position < 2) |
| return false; |
| if (content.charAt(position - 1) == '@' |
| && content.charAt(position - 2) == '@') |
| return true; |
| return false; |
| } |
| |
| private String getWordStarting(String content, int position, int maxLen) { |
| if (position <= 0 || position > content.length()) |
| return null; |
| final int original = position; |
| while (position > 0 |
| && maxLen > 0 |
| && RubySyntaxUtils.isLessStrictIdentifierCharacter(content |
| .charAt(position - 1))) { |
| --position; |
| --maxLen; |
| } |
| if (position < original) { |
| return content.substring(position, original); |
| } |
| return null; |
| } |
| |
| public void complete(org.eclipse.dltk.compiler.env.ISourceModule module, |
| int position, int i) { |
| this.currentModule = (ISourceModule) module; |
| |
| completedNames.clear(); |
| this.actualCompletionPosition = position; |
| this.requestor.beginReporting(); |
| try { |
| final String content = module.getSourceContents(); |
| |
| String wordStarting = getWordStarting(content, position, 10); |
| |
| if (wordStarting != null) { |
| this.setSourceRange(position - wordStarting.length(), position); |
| String[] keywords = RubyKeyword.findByPrefix(wordStarting); |
| for (int j = 0; j < keywords.length; j++) { |
| reportKeyword(keywords[j]); |
| } |
| } |
| |
| ModuleDeclaration moduleDeclaration = parser.parse(module |
| .getFileName(), content.toCharArray(), null); |
| |
| if (afterDollar(content, position)) { |
| completeGlobalVar(moduleDeclaration, "$", position); //$NON-NLS-1$ |
| } else if (afterAt2(content, position)) { |
| completeSimpleRef(moduleDeclaration, "@@", position); //$NON-NLS-1$ |
| } else if (afterAt(content, position)) { |
| completeSimpleRef(moduleDeclaration, "@", position); //$NON-NLS-1$ |
| } else if (afterColons(content, position)) { |
| |
| ASTNode node = ASTUtils.findMaximalNodeEndingAt( |
| moduleDeclaration, position - 2); |
| this.setSourceRange(position, position); |
| if (node != null) { |
| BasicContext basicContext = new BasicContext(currentModule, |
| moduleDeclaration); |
| ExpressionTypeGoal goal = new ExpressionTypeGoal( |
| basicContext, node); |
| IEvaluatedType type = inferencer.evaluateType(goal, 3000); |
| reportSubElements(type, ""); //$NON-NLS-1$ |
| } else { |
| completeConstant(moduleDeclaration, "", //$NON-NLS-1$ |
| position, true); |
| } |
| } else { |
| ASTNode minimalNode = ASTUtils.findMinimalNode( |
| moduleDeclaration, position, position); |
| if (minimalNode != null) { |
| this.completionNode = minimalNode; |
| completeContextMethod(position, moduleDeclaration, |
| minimalNode); |
| if (minimalNode instanceof CallExpression |
| && !RubySyntaxUtils |
| .isRubyOperator(((CallExpression) minimalNode) |
| .getName())) { |
| completeCall(moduleDeclaration, |
| (CallExpression) minimalNode, position); |
| } else if (minimalNode instanceof ConstantReference) { |
| completeConstant(moduleDeclaration, |
| (ConstantReference) minimalNode, position); |
| } else if (minimalNode instanceof RubyColonExpression) { |
| completeColonExpression(moduleDeclaration, |
| (RubyColonExpression) minimalNode, position); |
| } else if (minimalNode instanceof SimpleReference) { |
| completeSimpleRef(moduleDeclaration, |
| ((SimpleReference) minimalNode).getName(), |
| position); |
| } else if (minimalNode instanceof RubyDVarExpression) { |
| completeSimpleRef(moduleDeclaration, |
| ((RubyDVarExpression) minimalNode).getName(), |
| position); |
| } else if (minimalNode instanceof NumericLiteral |
| && position > 0 |
| && position == minimalNode.sourceEnd() |
| && position > minimalNode.sourceStart() |
| && content.charAt(position - 1) == '.') { |
| setSourceRange(position, position); |
| completeClassMethods(moduleDeclaration, minimalNode, ""); //$NON-NLS-1$ |
| } else { // worst case |
| if (wordStarting == null |
| && !requestor.isContextInformationMode()) { |
| reportCurrentElements(moduleDeclaration, position); |
| } |
| } |
| } |
| } |
| } finally { |
| this.requestor.endReporting(); |
| } |
| } |
| |
| private void reportCurrentElements(ModuleDeclaration moduleDeclaration, |
| int position) { |
| setSourceRange(position, position); |
| completeSimpleRef(moduleDeclaration, "", position); //$NON-NLS-1$ |
| IClassType self = RubyTypeInferencingUtils.determineSelfClass( |
| currentModule, moduleDeclaration, position); |
| completeClassMethods(moduleDeclaration, self, ""); //$NON-NLS-1$ |
| } |
| |
| private boolean completeContextMethod(int position, |
| ModuleDeclaration moduleDeclaration, ASTNode minimalNode) { |
| ASTNode[] path = ASTUtils.restoreWayToNode(moduleDeclaration, |
| minimalNode); |
| if (path == null) { |
| return false; |
| } |
| for (int k = path.length; --k >= 1;) { |
| if (path[k] instanceof RubyCallArgumentsList |
| && path[k - 1] instanceof CallExpression) { |
| final CallExpression call = (CallExpression) path[k - 1]; |
| final RubyCallArgumentsList args = (RubyCallArgumentsList) path[k]; |
| if (!RubySyntaxUtils.isRubyOperator(call.getName()) |
| && isValidCallArgs(args)) { |
| completeCallArgumentList(moduleDeclaration, call, position); |
| // TODO check if there are proposals found |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * @param args |
| * @return |
| */ |
| private static boolean isValidCallArgs(RubyCallArgumentsList args) { |
| return args.sourceStart() >= 0 && args.sourceEnd() >= 0 |
| && args.sourceEnd() > args.sourceStart(); |
| } |
| |
| private void completeCallArgumentList(ModuleDeclaration moduleDeclaration, |
| final CallExpression call, int position) { |
| final ASTNode receiver = call.getReceiver(); |
| final String methodName = call.getCallName().getName(); |
| if (receiver != null) { |
| // if ("new".equals(methodName) && receiver instanceof |
| // ConstantReference) |
| completeClassMethods(moduleDeclaration, receiver, methodName); |
| } else { |
| IClassType self = RubyTypeInferencingUtils.determineSelfClass( |
| currentModule, moduleDeclaration, position); |
| completeClassMethods(moduleDeclaration, self, methodName); |
| } |
| } |
| |
| private class CompletionMixinMethodRequestor implements |
| IMixinSearchRequestor { |
| private String lastParent = null; |
| private List group = new ArrayList(); |
| private int relevance = 0; |
| private final RubyMixinClass klass; |
| |
| public CompletionMixinMethodRequestor(RubyMixinClass klass) { |
| super(); |
| this.klass = klass; |
| } |
| |
| public void acceptResult(IRubyMixinElement element) { |
| if (element instanceof RubyMixinMethod) { |
| RubyMixinMethod method = (RubyMixinMethod) element; |
| String parent = method.getSelfType().getKey(); |
| if (lastParent == null || !lastParent.equals(parent)) { |
| this.flush(); |
| lastParent = parent; |
| } |
| group.add(method); |
| } |
| } |
| |
| public void flush() { |
| if (group.size() > 0) { |
| RubyMixinMethod[] mixinMethods = (RubyMixinMethod[]) group |
| .toArray(new RubyMixinMethod[group.size()]); |
| List allSourceMethods = RubyModelUtils.getAllSourceMethods( |
| mixinMethods, klass); |
| IMethod[] methods = (IMethod[]) allSourceMethods |
| .toArray(new IMethod[allSourceMethods.size()]); |
| Arrays.sort(methods, modelElementComparator); |
| for (int j = 0; j < methods.length; j++) { |
| reportMethod(methods[j], RELEVANCE_METHODS + relevance); |
| } |
| group.clear(); |
| } |
| relevance--; |
| } |
| |
| } |
| |
| private void completeClassMethods(ModuleDeclaration moduleDeclaration, |
| RubyMixinClass rubyClass, String prefix) { |
| CompletionMixinMethodRequestor mixinSearchRequestor = new CompletionMixinMethodRequestor( |
| rubyClass); |
| rubyClass.findMethods(prefix, true, mixinSearchRequestor); |
| mixinSearchRequestor.flush(); |
| } |
| |
| private void completeClassMethods(ModuleDeclaration moduleDeclaration, |
| IEvaluatedType type, String prefix) { |
| if (type instanceof RubyClassType) { |
| RubyClassType rubyClassType = (RubyClassType) type; |
| RubyMixinClass rubyClass = RubyMixinModel.getInstance() |
| .createRubyClass(rubyClassType); |
| if (rubyClass != null) { |
| completeClassMethods(moduleDeclaration, rubyClass, prefix); |
| } |
| |
| } else if (type instanceof AmbiguousType) { |
| AmbiguousType type2 = (AmbiguousType) type; |
| IEvaluatedType[] possibleTypes = type2.getPossibleTypes(); |
| for (int i = 0; i < possibleTypes.length; i++) { |
| completeClassMethods(moduleDeclaration, possibleTypes[i], |
| prefix); |
| } |
| } |
| } |
| |
| private void completeClassMethods(ModuleDeclaration moduleDeclaration, |
| ASTNode receiver, String pattern) { |
| ExpressionTypeGoal goal = new ExpressionTypeGoal(new BasicContext( |
| currentModule, moduleDeclaration), receiver); |
| IEvaluatedType type = inferencer.evaluateType(goal, 2000); |
| completeClassMethods(moduleDeclaration, type, pattern); |
| } |
| |
| private void completeGlobalVar(ModuleDeclaration moduleDeclaration, |
| String prefix, int position) { |
| int relevance = 424242; |
| this.setSourceRange(position - prefix.length(), position); |
| |
| IMixinElement[] elements = RubyMixinModel.getRawInstance().find( |
| prefix + "*"); //$NON-NLS-1$ |
| |
| // String[] findKeys = RubyMixinModel.getRawInstance().findKeys( |
| // prefix + "*"); |
| for (int i = 0; i < elements.length; i++) { |
| IRubyMixinElement rubyElement = RubyMixinModel.getInstance() |
| .createRubyElement(elements[i]); |
| if (rubyElement instanceof RubyMixinVariable) { |
| RubyMixinVariable variable = (RubyMixinVariable) rubyElement; |
| IField[] sourceFields = variable.getSourceFields(); |
| for (int j = 0; j < sourceFields.length; j++) { |
| if (sourceFields[j] != null) { |
| reportField(sourceFields[j], relevance--); |
| break; |
| } |
| } |
| } |
| } |
| |
| for (int i = 0; i < globalVars.length; i++) { |
| if (globalVars[i].startsWith(prefix)) |
| reportField(new FakeField((ModelElement) currentModule, |
| globalVars[i], 0, 0), relevance--); |
| } |
| } |
| |
| private void completeSimpleRef(ModuleDeclaration moduleDeclaration, |
| String prefix, int position) { |
| int relevance = 424242; |
| |
| this.setSourceRange(position - prefix.length(), position); |
| ASTNode[] wayToNode = ASTUtils.restoreWayToNode(moduleDeclaration, |
| this.completionNode); |
| for (int i = wayToNode.length - 1; i > 0; i--) { |
| if (wayToNode[i] instanceof RubyBlock) { |
| RubyBlock rubyBlock = (RubyBlock) wayToNode[i]; |
| Set vars = rubyBlock.getVars(); |
| for (Iterator iterator = vars.iterator(); iterator.hasNext();) { |
| ASTNode n = (ASTNode) iterator.next(); |
| if (n instanceof RubyDAssgnExpression) { |
| RubyDAssgnExpression rd = (RubyDAssgnExpression) n; |
| if (rd.getName().startsWith(prefix)) { |
| reportField(new FakeField( |
| (ModelElement) currentModule, rd.getName(), |
| 0, 0), relevance--); |
| } |
| } |
| } |
| } |
| } |
| |
| if (prefix.startsWith("$")) { // globals //$NON-NLS-1$ |
| completeGlobalVar(moduleDeclaration, prefix, position); |
| } else { // class & instance & locals |
| IField[] fields = RubyModelUtils.findFields(currentModule, |
| moduleDeclaration, prefix, position); |
| for (int i = 0; i < fields.length; i++) { |
| reportField(fields[i], relevance--); |
| } |
| } |
| |
| } |
| |
| private void reportSubElements(IEvaluatedType type, String prefix) { |
| if (!(type instanceof RubyClassType)) { |
| return; |
| } |
| RubyClassType rubyClassType = (RubyClassType) type; |
| IMixinElement mixinElement = model.get(rubyClassType.getModelKey()); |
| if (mixinElement == null) { |
| return; |
| } |
| List types = new ArrayList(); |
| List methods = new ArrayList(); |
| List fields = new ArrayList(); |
| IMixinElement[] children = mixinElement.getChildren(); |
| for (int i = 0; i < children.length; i++) { |
| Object[] infos = children[i].getAllObjects(); |
| for (int j = 0; j < infos.length; j++) { |
| RubyMixinElementInfo obj = (RubyMixinElementInfo) infos[j]; |
| if (obj.getObject() == null) |
| continue; |
| if (obj.getKind() == RubyMixinElementInfo.K_CLASS |
| || obj.getKind() == RubyMixinElementInfo.K_MODULE) { |
| IType type2 = (IType) obj.getObject(); |
| if (type2 != null |
| && type2.getElementName().startsWith(prefix)) { |
| types.add(type2); |
| } |
| } else if (obj.getKind() == RubyMixinElementInfo.K_METHOD) { |
| IMethod method2 = (IMethod) obj.getObject(); |
| if (method2 != null |
| && method2.getElementName().startsWith(prefix)) { |
| methods.add(method2); |
| } |
| } |
| if (obj.getKind() == RubyMixinElementInfo.K_VARIABLE) { |
| IField fff = (IField) obj.getObject(); |
| if (fff != null && fff.getElementName().startsWith(prefix)) { |
| fields.add(fff); |
| } |
| } |
| break; |
| } |
| } |
| |
| int relevance = 424242; |
| Collections.sort(fields, modelElementComparator); |
| for (Iterator iterator = fields.iterator(); iterator.hasNext();) { |
| IField t = (IField) iterator.next(); |
| reportField(t, relevance--); |
| } |
| |
| Collections.sort(types, modelElementComparator); |
| for (Iterator iterator = types.iterator(); iterator.hasNext();) { |
| IType t = (IType) iterator.next(); |
| reportType(t, relevance--); |
| } |
| |
| Collections.sort(methods, modelElementComparator); |
| for (Iterator iterator = methods.iterator(); iterator.hasNext();) { |
| IMethod t = (IMethod) iterator.next(); |
| reportMethod(t, relevance--); |
| } |
| |
| } |
| |
| private void completeColonExpression(ModuleDeclaration moduleDeclaration, |
| RubyColonExpression node, int position) { |
| String content; |
| try { |
| content = currentModule.getSource(); |
| } catch (ModelException e) { |
| return; |
| } |
| int pos = (node.getLeft() != null) ? (node.getLeft().sourceEnd() + 2) |
| : (node.sourceStart()); |
| String starting = null; |
| try { |
| starting = content.substring(pos, position).trim(); |
| } catch (IndexOutOfBoundsException e) { |
| e.printStackTrace(); |
| return; |
| } |
| |
| if (starting.startsWith("::")) { //$NON-NLS-1$ |
| this.setSourceRange(position - starting.length() + 2, position); |
| completeConstant(moduleDeclaration, starting.substring(2), |
| position, true); |
| return; |
| } |
| |
| this.setSourceRange(position - starting.length(), position); |
| |
| ExpressionTypeGoal goal = new ExpressionTypeGoal(new BasicContext( |
| currentModule, moduleDeclaration), node.getLeft()); |
| IEvaluatedType type = inferencer.evaluateType(goal, 3000); |
| reportSubElements(type, starting); |
| } |
| |
| private int relevance; |
| |
| private void completeConstant(ModuleDeclaration moduleDeclaration, |
| String prefix, int position, boolean topLevelOnly) { |
| |
| relevance = 4242; |
| reportProjectTypes(prefix); |
| |
| if (!topLevelOnly) { |
| IMixinElement[] modelStaticScopes = RubyTypeInferencingUtils |
| .getModelStaticScopes(model, moduleDeclaration, position); |
| for (int i = modelStaticScopes.length - 1; i >= 0; i--) { |
| IMixinElement scope = modelStaticScopes[i]; |
| if (scope == null) |
| continue; |
| reportSubElements(new RubyClassType(scope.getKey()), prefix); |
| } |
| } |
| |
| // try { |
| if (prefix.length() > 0) { |
| String varkey = "Object" + MixinModel.SEPARATOR + prefix; //$NON-NLS-1$ |
| RubyMixinModel rubyModel = RubyMixinModel.getInstance(); |
| String[] keys2 = rubyModel.getRawModel().findKeys(varkey + "*"); //$NON-NLS-1$ |
| for (int i = 0; i < keys2.length; i++) { |
| IRubyMixinElement element = rubyModel |
| .createRubyElement(keys2[i]); |
| if (element instanceof RubyMixinVariable) { |
| RubyMixinVariable variable = (RubyMixinVariable) element; |
| IField[] sourceFields = variable.getSourceFields(); |
| for (int j = 0; j < sourceFields.length; j++) { |
| if (sourceFields[j] != null) { |
| reportField(sourceFields[j], relevance--); |
| break; |
| } |
| } |
| } |
| } |
| } |
| // } catch (ModelException e) { |
| // e.printStackTrace(); |
| // } |
| |
| } |
| |
| private void reportProjectTypes(String prefix) { |
| IType[] types = RubyTypeInferencingUtils.getAllTypes(currentModule, |
| prefix); |
| Arrays.sort(types, new ProjectTypeComparator(currentModule)); |
| final Set names = new HashSet(); |
| for (int i = 0; i < types.length; i++) { |
| final String elementName = types[i].getElementName(); |
| if (names.add(elementName)) { |
| reportType(types[i], relevance--); |
| } |
| } |
| } |
| |
| private void completeConstant(ModuleDeclaration moduleDeclaration, |
| ConstantReference node, int position) { |
| String content; |
| try { |
| content = currentModule.getSource(); |
| } catch (ModelException e) { |
| return; |
| } |
| |
| String prefix = content.substring(node.sourceStart(), position); |
| this.setSourceRange(position - prefix.length(), position); |
| completeConstant(moduleDeclaration, prefix, position, false); |
| } |
| |
| private void completeCall(ModuleDeclaration moduleDeclaration, |
| CallExpression node, int position) { |
| ASTNode receiver = node.getReceiver(); |
| |
| String content; |
| try { |
| content = currentModule.getSource(); |
| } catch (ModelException e) { |
| return; |
| } |
| |
| int pos = (receiver != null) ? (receiver.sourceEnd() + 1) : (node |
| .sourceStart()); |
| |
| for (int t = 0; t < 2; t++) { // correct not more 2 chars |
| if (pos < position |
| && !RubySyntaxUtils.isStrictIdentifierCharacter(content |
| .charAt(pos))) // for (...).name and Foo::name |
| // calls |
| pos++; |
| } |
| |
| String starting = content.substring(pos, position).trim(); |
| |
| if (receiver == null) |
| completeSimpleRef(moduleDeclaration, starting, position); |
| |
| this.setSourceRange(position - starting.length(), position); |
| |
| if (starting.startsWith("__")) { //$NON-NLS-1$ |
| String[] keywords = RubyKeyword.findByPrefix("__"); //$NON-NLS-1$ |
| for (int j = 0; j < keywords.length; j++) { |
| reportKeyword(keywords[j]); |
| } |
| } |
| |
| if (receiver != null) { |
| completeClassMethods(moduleDeclaration, receiver, starting); |
| } else { |
| IClassType self = RubyTypeInferencingUtils.determineSelfClass( |
| currentModule, moduleDeclaration, position); |
| completeClassMethods(moduleDeclaration, self, starting); |
| } |
| |
| } |
| |
| protected String processFieldName(IField field, String token) { |
| return field.getElementName(); |
| } |
| |
| private void reportMethod(IMethod method, int rel) { |
| this.intresting.add(method); |
| String elementName = method.getElementName(); |
| if (completedNames.contains(elementName)) { |
| return; |
| } |
| completedNames.add(elementName); |
| if (elementName.indexOf('.') != -1) { |
| elementName = elementName.substring(elementName.indexOf('.') + 1); |
| } |
| char[] name = elementName.toCharArray(); |
| char[] compl = name; |
| |
| // accept result |
| noProposal = false; |
| if (!requestor.isIgnored(CompletionProposal.METHOD_DECLARATION)) { |
| CompletionProposal proposal = createProposal( |
| CompletionProposal.METHOD_DECLARATION, |
| actualCompletionPosition); |
| |
| String[] params = null; |
| try { |
| params = method.getParameters(); |
| } catch (ModelException e) { |
| e.printStackTrace(); |
| } |
| |
| if (params != null && params.length > 0) { |
| char[][] args = new char[params.length][]; |
| for (int i = 0; i < params.length; ++i) { |
| args[i] = params[i].toCharArray(); |
| } |
| proposal.setParameterNames(args); |
| } |
| |
| proposal.setModelElement(method); |
| proposal.setName(name); |
| proposal.setCompletion(compl); |
| try { |
| proposal.setFlags(method.getFlags()); |
| } catch (ModelException e) { |
| RubyPlugin.log(e); |
| } |
| proposal.setReplaceRange(this.startPosition - this.offset, |
| this.endPosition - this.offset); |
| proposal.setRelevance(rel); |
| this.requestor.accept(proposal); |
| if (DEBUG) { |
| this.printDebug(proposal); |
| } |
| } |
| |
| } |
| |
| private void reportType(IType type, int rel) { |
| this.intresting.add(type); |
| String elementName = type.getElementName(); |
| if (completedNames.contains(elementName)) { |
| return; |
| } |
| completedNames.add(elementName); |
| char[] name = elementName.toCharArray(); |
| if (name.length == 0) |
| return; |
| |
| // accept result |
| noProposal = false; |
| if (!requestor.isIgnored(CompletionProposal.TYPE_REF)) { |
| CompletionProposal proposal = createProposal( |
| CompletionProposal.TYPE_REF, actualCompletionPosition); |
| |
| proposal.setModelElement(type); |
| proposal.setName(name); |
| proposal.setCompletion(elementName.toCharArray()); |
| // proposal.setFlags(Flags.AccDefault); |
| try { |
| proposal.setFlags(type.getFlags()); |
| } catch (ModelException e) { |
| } |
| proposal.setReplaceRange(this.startPosition - this.offset, |
| this.endPosition - this.offset); |
| proposal.setRelevance(rel); |
| this.requestor.accept(proposal); |
| if (DEBUG) { |
| this.printDebug(proposal); |
| } |
| } |
| |
| } |
| |
| private void reportField(IField field, int rel) { |
| this.intresting.add(field); |
| String elementName = field.getElementName(); |
| if (completedNames.contains(elementName)) { |
| return; |
| } |
| completedNames.add(elementName); |
| char[] name = elementName.toCharArray(); |
| if (name.length == 0) |
| return; |
| |
| // accept result |
| noProposal = false; |
| if (!requestor.isIgnored(CompletionProposal.FIELD_REF)) { |
| CompletionProposal proposal = createProposal( |
| CompletionProposal.FIELD_REF, actualCompletionPosition); |
| |
| proposal.setModelElement(field); |
| proposal.setName(name); |
| proposal.setCompletion(elementName.toCharArray()); |
| // proposal.setFlags(Flags.AccDefault); |
| proposal.setReplaceRange(this.startPosition - this.offset, |
| this.endPosition - this.offset); |
| proposal.setRelevance(rel); |
| this.requestor.accept(proposal); |
| if (DEBUG) { |
| this.printDebug(proposal); |
| } |
| } |
| |
| } |
| |
| private void reportKeyword(String name) { |
| // accept result |
| noProposal = false; |
| if (!requestor.isIgnored(CompletionProposal.KEYWORD)) { |
| CompletionProposal proposal = createProposal( |
| CompletionProposal.KEYWORD, actualCompletionPosition); |
| |
| proposal.setName(name.toCharArray()); |
| proposal.setCompletion(name.toCharArray()); |
| // proposal.setFlags(Flags.AccDefault); |
| proposal.setReplaceRange(this.startPosition - this.offset, |
| this.endPosition - this.offset); |
| proposal.setRelevance(--relevanceKeyword); |
| this.requestor.accept(proposal); |
| if (DEBUG) { |
| this.printDebug(proposal); |
| } |
| } |
| |
| } |
| |
| } |