/******************************************************************************* | |
* 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.core.resources.ResourcesPlugin; | |
import org.eclipse.core.runtime.CoreException; | |
import org.eclipse.core.runtime.IStatus; | |
import org.eclipse.core.runtime.jobs.Job; | |
import org.eclipse.dltk.ast.ASTNode; | |
import org.eclipse.dltk.ast.Modifiers; | |
import org.eclipse.dltk.ast.declarations.ModuleDeclaration; | |
import org.eclipse.dltk.ast.expressions.CallExpression; | |
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.compiler.env.ISourceModule; | |
import org.eclipse.dltk.compiler.problem.DefaultProblem; | |
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.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.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.text.RubyKeyword; | |
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; | |
private final static int RELEVANCE_KEYWORD = 1000000; | |
private final static int RELEVANCE_METHODS = 100000; | |
private final static String[] globalVars = { "$DEBUG", "$$", "$-i", | |
"$deferr", "$/", "$'", "$stdout", "$-l", "$-I", "$.", "$KCODE", | |
"$binding", "$-w", "$FILENAME", "$defout", "$,", "$`", "$-F", "$*", | |
"$LOADED_FEATURES", "$stdin", "$-p", "$:", "$\\", "$=", "$!", | |
"$-v", "$>", "$&", "$;", "$SAFE", "$PROGRAM_NAME", "$\"", "$-d", | |
"$?", "$-0", "$+", "$@", "$-a", "$VERBOSE", "$stderr", "$~", "$0", | |
"$LOAD_PATH", "$<", "$_", "$-K" }; | |
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(); | |
try { | |
this.parser = DLTKLanguageManager | |
.getSourceParser(RubyNature.NATURE_ID); | |
} catch (CoreException e) { | |
throw new RuntimeException( | |
"Failed to initialize RubyCompletionEngine", e); | |
} | |
} | |
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) { | |
int original = position; | |
if (position <= 0) | |
return ""; | |
if (position >= content.length()) | |
position = content.length(); | |
position--; | |
int len = 0; | |
while (position >= 0 | |
&& len < maxLen | |
&& RubySyntaxUtils.isLessStrictIdentifierCharacter(content | |
.charAt(position))) { | |
position--; | |
} | |
if (position + 1 > original) | |
return ""; | |
if ((position >= 0 && Character.isWhitespace(content.charAt(position))) | |
|| position == -1) | |
return content.substring(position + 1, original); | |
return null; | |
} | |
public void complete(ISourceModule module, int position, int i) { | |
this.currentModule = module; | |
if (Job.getJobManager().find(ResourcesPlugin.FAMILY_AUTO_BUILD).length > 0) { // `FIXIT, | |
// make | |
// more | |
// correct | |
// awaiting | |
// for | |
// building | |
this.requestor.completionFailure(new DefaultProblem(null, | |
"Please wait until building is ready...", 0, null, | |
IStatus.WARNING, startPosition, endPosition, -1)); | |
return; | |
} | |
if (!RubyPlugin.initialized) { | |
this.requestor.completionFailure(new DefaultProblem(null, | |
"Please wait while DLTK Ruby being initialized...", 0, | |
null, IStatus.WARNING, startPosition, endPosition, -1)); | |
return; | |
} | |
completedNames.clear(); | |
this.actualCompletionPosition = position; | |
this.requestor.beginReporting(); | |
org.eclipse.dltk.core.ISourceModule modelModule = (org.eclipse.dltk.core.ISourceModule) module; | |
try { | |
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((org.eclipse.dltk.core.ISourceModule) module, | |
moduleDeclaration, "$", position); | |
} else if (afterAt2(content, position)) { | |
completeSimpleRef((org.eclipse.dltk.core.ISourceModule) module, | |
moduleDeclaration, "@@", position); | |
} else if (afterAt(content, position)) { | |
completeSimpleRef((org.eclipse.dltk.core.ISourceModule) module, | |
moduleDeclaration, "@", position); | |
} else if (afterColons(content, position)) { | |
ASTNode node = ASTUtils.findMaximalNodeEndingAt( | |
moduleDeclaration, position - 2); | |
this.setSourceRange(position, position); | |
if (node != null) { | |
BasicContext basicContext = new BasicContext(modelModule, | |
moduleDeclaration); | |
ExpressionTypeGoal goal = new ExpressionTypeGoal( | |
basicContext, node); | |
IEvaluatedType type = inferencer.evaluateType(goal, 3000); | |
reportSubElements(modelModule, type, ""); | |
} else { | |
completeConstant(modelModule, moduleDeclaration, "", | |
position, true); | |
} | |
} else { | |
ASTNode minimalNode = ASTUtils.findMinimalNode( | |
moduleDeclaration, position, position); | |
if (minimalNode != null) { | |
this.completionNode = minimalNode; | |
if (minimalNode instanceof CallExpression) { | |
completeCall(modelModule, moduleDeclaration, | |
(CallExpression) minimalNode, position); | |
} else if (minimalNode instanceof ConstantReference) { | |
completeConstant(modelModule, moduleDeclaration, | |
(ConstantReference) minimalNode, position); | |
} else if (minimalNode instanceof RubyColonExpression) { | |
completeColonExpression(modelModule, moduleDeclaration, | |
(RubyColonExpression) minimalNode, position); | |
} else if (minimalNode instanceof SimpleReference) { | |
completeSimpleRef(modelModule, moduleDeclaration, | |
((SimpleReference) minimalNode).getName(), | |
position); | |
} else if (minimalNode instanceof RubyDVarExpression) { | |
completeSimpleRef(modelModule, moduleDeclaration, | |
((RubyDVarExpression) minimalNode).getName(), | |
position); | |
} else { // worst case | |
if (wordStarting == null || wordStarting.length() == 0) { | |
int rel = RELEVANCE_FREE_SPACE; | |
try { | |
IModelElement[] children = modelModule | |
.getChildren(); | |
if (children != null) | |
for (int j = 0; j < children.length; j++) { | |
if (children[j] instanceof IField) | |
reportField((IField) children[j], | |
rel); | |
if (children[j] instanceof IMethod) { | |
IMethod method = (IMethod) children[j]; | |
if ((method.getFlags() & Modifiers.AccStatic) == 0) | |
reportMethod(method, rel); | |
} | |
if (children[j] instanceof IType | |
&& !children[j] | |
.getElementName() | |
.trim() | |
.startsWith("<<")) | |
reportType((IType) children[j], rel); | |
} | |
} catch (ModelException e) { | |
e.printStackTrace(); | |
} | |
} | |
} | |
} | |
} | |
} finally { | |
this.requestor.endReporting(); | |
} | |
} | |
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( | |
org.eclipse.dltk.core.ISourceModule modelModule, | |
ModuleDeclaration moduleDeclaration, RubyMixinClass rubyClass, | |
String prefix) { | |
CompletionMixinMethodRequestor mixinSearchRequestor = new CompletionMixinMethodRequestor( | |
rubyClass); | |
rubyClass.findMethods(prefix, true, mixinSearchRequestor); | |
mixinSearchRequestor.flush(); | |
} | |
private void completeClassMethods( | |
org.eclipse.dltk.core.ISourceModule modelModule, | |
ModuleDeclaration moduleDeclaration, IEvaluatedType type, | |
String prefix) { | |
if (type instanceof RubyClassType) { | |
RubyClassType rubyClassType = (RubyClassType) type; | |
RubyMixinClass rubyClass = RubyMixinModel.getInstance() | |
.createRubyClass(rubyClassType); | |
if (rubyClass != null) { | |
completeClassMethods(modelModule, 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(modelModule, moduleDeclaration, | |
possibleTypes[i], prefix); | |
} | |
} | |
} | |
private void completeClassMethods( | |
org.eclipse.dltk.core.ISourceModule modelModule, | |
ModuleDeclaration moduleDeclaration, ASTNode receiver, | |
String pattern) { | |
ExpressionTypeGoal goal = new ExpressionTypeGoal(new BasicContext( | |
modelModule, moduleDeclaration), receiver); | |
IEvaluatedType type = inferencer.evaluateType(goal, 2000); | |
completeClassMethods(modelModule, moduleDeclaration, type, pattern); | |
} | |
private void completeGlobalVar(org.eclipse.dltk.core.ISourceModule module, | |
ModuleDeclaration moduleDeclaration, String prefix, int position) { | |
int relevance = 424242; | |
this.setSourceRange(position - prefix.length(), position); | |
IMixinElement[] elements = RubyMixinModel.getRawInstance().find( | |
prefix + "*"); | |
// 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) module, globalVars[i], | |
0, 0), relevance--); | |
} | |
} | |
private void completeSimpleRef(org.eclipse.dltk.core.ISourceModule module, | |
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) module, rd | |
.getName(), 0, 0), relevance--); | |
} | |
} | |
} | |
} | |
} | |
if (prefix.startsWith("$")) { // globals | |
completeGlobalVar(module, moduleDeclaration, prefix, position); | |
} else { // class & instance & locals | |
IField[] fields = RubyModelUtils.findFields(module, | |
moduleDeclaration, prefix, position); | |
for (int i = 0; i < fields.length; i++) { | |
reportField(fields[i], relevance--); | |
} | |
} | |
} | |
private void reportSubElements(org.eclipse.dltk.core.ISourceModule module, | |
IEvaluatedType type, String prefix) { | |
int relevance = 424242; | |
ArrayList types = new ArrayList(); | |
ArrayList methods = new ArrayList(); | |
ArrayList fields = new ArrayList(); | |
if (type instanceof RubyClassType) { | |
RubyClassType rubyClassType = (RubyClassType) type; | |
IMixinElement mixinElement = model.get(rubyClassType.getModelKey()); | |
if (mixinElement != null) { | |
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)) { | |
// reportType(type2, relevance--); | |
types.add(type2); | |
} | |
} else if (obj.getKind() == RubyMixinElementInfo.K_METHOD) { | |
IMethod method2 = (IMethod) obj.getObject(); | |
if (method2 != null | |
&& method2.getElementName().startsWith( | |
prefix)) { | |
// reportMethod(method2, relevance--); | |
methods.add(method2); | |
} | |
} | |
if (obj.getKind() == RubyMixinElementInfo.K_VARIABLE) { | |
IField fff = (IField) obj.getObject(); | |
if (fff != null | |
&& fff.getElementName().startsWith(prefix)) { | |
// reportField(fff, relevance--); | |
fields.add(fff); | |
} | |
} | |
break; | |
} | |
} | |
} | |
} else { | |
// never should be here | |
} | |
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( | |
org.eclipse.dltk.core.ISourceModule module, | |
ModuleDeclaration moduleDeclaration, RubyColonExpression node, | |
int position) { | |
String content; | |
try { | |
content = module.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("::")) { | |
this.setSourceRange(position - starting.length() + 2, position); | |
completeConstant(module, moduleDeclaration, starting.substring(2), | |
position, true); | |
return; | |
} | |
this.setSourceRange(position - starting.length(), position); | |
ExpressionTypeGoal goal = new ExpressionTypeGoal(new BasicContext( | |
module, moduleDeclaration), node.getLeft()); | |
IEvaluatedType type = inferencer.evaluateType(goal, 3000); | |
reportSubElements(module, type, starting); | |
} | |
private void completeConstant(org.eclipse.dltk.core.ISourceModule module, | |
ModuleDeclaration moduleDeclaration, String prefix, int position, | |
boolean topLevelOnly) { | |
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(module, new RubyClassType(scope.getKey()), | |
prefix); | |
} | |
} | |
int relevance = 4242; | |
// try { | |
if (prefix.length() > 0) { | |
String varkey = "Object" + MixinModel.SEPARATOR + prefix; | |
RubyMixinModel rubyModel = RubyMixinModel.getInstance(); | |
String[] keys2 = rubyModel.getRawModel().findKeys(varkey + "*"); | |
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(); | |
// } | |
HashSet names = new HashSet(); | |
IType[] allTypes = RubyTypeInferencingUtils.getAllTypes(module, prefix); | |
for (int i = 0; i < allTypes.length; i++) { | |
String elementName = allTypes[i].getElementName(); | |
if (names.contains(elementName)) | |
continue; | |
names.add(elementName); | |
reportType(allTypes[i], relevance--); | |
} | |
} | |
private void completeConstant(org.eclipse.dltk.core.ISourceModule module, | |
ModuleDeclaration moduleDeclaration, ConstantReference node, | |
int position) { | |
String content; | |
try { | |
content = module.getSource(); | |
} catch (ModelException e) { | |
return; | |
} | |
String prefix = content.substring(node.sourceStart(), position); | |
this.setSourceRange(position - prefix.length(), position); | |
completeConstant(module, moduleDeclaration, prefix, position, false); | |
} | |
private void completeCall(org.eclipse.dltk.core.ISourceModule module, | |
ModuleDeclaration moduleDeclaration, CallExpression node, | |
int position) { | |
ASTNode receiver = node.getReceiver(); | |
String content; | |
try { | |
content = module.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(module, moduleDeclaration, starting, position); | |
this.setSourceRange(position - starting.length(), position); | |
if (starting.startsWith("__")) { | |
String[] keywords = RubyKeyword.findByPrefix("__"); | |
for (int j = 0; j < keywords.length; j++) { | |
reportKeyword(keywords[j]); | |
} | |
} | |
if (receiver != null) { | |
completeClassMethods(module, moduleDeclaration, receiver, starting); | |
} else { | |
IClassType self = RubyTypeInferencingUtils.determineSelfClass( | |
module, moduleDeclaration, position); | |
completeClassMethods(module, 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; | |
int relevance = rel; | |
// 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(relevance); | |
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; | |
int relevance = rel; | |
// 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(relevance); | |
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; | |
int relevance = rel; | |
// 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(relevance); | |
this.requestor.accept(proposal); | |
if (DEBUG) { | |
this.printDebug(proposal); | |
} | |
} | |
} | |
private void reportKeyword(String name) { | |
// accept result | |
noProposal = false; | |
if (!requestor.isIgnored(CompletionProposal.FIELD_REF)) { | |
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(RELEVANCE_KEYWORD); | |
this.requestor.accept(proposal); | |
if (DEBUG) { | |
this.printDebug(proposal); | |
} | |
} | |
} | |
} |