blob: dad4ea2527c13677b27ebbedbe83a1161e37a8be [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2005, 2016 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.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.eclipse.dltk.ast.ASTNode;
import org.eclipse.dltk.ast.Modifiers;
import org.eclipse.dltk.ast.declarations.MethodDeclaration;
import org.eclipse.dltk.ast.declarations.ModuleDeclaration;
import org.eclipse.dltk.ast.declarations.TypeDeclaration;
import org.eclipse.dltk.ast.expressions.CallArgumentsList;
import org.eclipse.dltk.ast.expressions.CallExpression;
import org.eclipse.dltk.ast.expressions.NumericLiteral;
import org.eclipse.dltk.ast.expressions.StringLiteral;
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.ScriptCompletionEngine;
import org.eclipse.dltk.compiler.env.IModuleSource;
import org.eclipse.dltk.compiler.util.Util;
import org.eclipse.dltk.core.CompletionProposal;
import org.eclipse.dltk.core.DLTKLanguageManager;
import org.eclipse.dltk.core.Flags;
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.util.WeakHashSet;
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.ast.RubySelfReference;
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.IMixinSearchRequestor;
import org.eclipse.dltk.ruby.internal.parser.mixin.IRubyMixinElement;
import org.eclipse.dltk.ruby.internal.parser.mixin.PrefixNoCaseMixinSearchPattern;
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.RubyMixinUtils;
import org.eclipse.dltk.ruby.internal.parser.mixin.RubyMixinVariable;
import org.eclipse.dltk.ruby.internal.parsers.jruby.ASTUtils;
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;
public class RubyCompletionEngine extends ScriptCompletionEngine {
/**
* Type inferencer timeout
*/
private static final int TI_TIMEOUT = 2000;
private final static int RELEVANCE_FREE_SPACE = 100000;
/**
* Relevance for keywords. Should be used only for keywords - the distinct
* value is required to sort templates (their relevance is much lower by
* default) before the matching keywords - see
* ScriptCompletionProposalComputer#updateTemplateProposalRelevance()
*/
private final static int RELEVANCE_KEYWORD = 1000000;
private final static int RELEVANCE_TYPE = 2000000;
private final static int RELEVANCE_METHODS = 10000000;
private final static int RELEVANCE_VARIABLES = 100000000;
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 RubyMixinModel mixinModel;
private HashSet<String> completedNames = new HashSet<String>();
private WeakHashSet intresting = new WeakHashSet();
private ASTNode completionNode;
private ISourceModule currentModule;
public RubyCompletionEngine() {
this.inferencer = new DLTKTypeInferenceEngine();
this.parser = DLTKLanguageManager.getSourceParser(RubyNature.NATURE_ID);
}
@Override
protected int getEndOfEmptyToken() {
return 0;
}
@Override
protected String processMethodName(IMethod method, String token) {
return null;
}
@Override
protected String processTypeName(IType method, String token) {
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 boolean afterDot(String content, int position) {
return position >= 1 && content.charAt(position - 1) == '.';
}
private String getWordStarting(String content, int position, int maxLen) {
if (position <= 0 || position > content.length())
return Util.EMPTY_STRING;
final int original = position;
while (position > 0
&& maxLen > 0
&& ((content.charAt(position - 1) == ':')
|| (content.charAt(position - 1) == '\'')
|| (content.charAt(position - 1) == '"') || RubySyntaxUtils
.isLessStrictIdentifierCharacter(content
.charAt(position - 1)))) {
--position;
--maxLen;
}
return content.substring(position, original);
}
@Override
public void complete(IModuleSource module, int position, int i) {
this.currentModule = (ISourceModule) module;
this.mixinModel = RubyMixinModel.getInstance(currentModule
.getScriptProject());
completedNames.clear();
this.actualCompletionPosition = position;
this.requestor.beginReporting();
try {
final String content = module.getSourceContents();
String wordStarting = getWordStarting(content, position, 10);
if (wordStarting.length() != 0) {
this.setSourceRange(position - wordStarting.length(), position);
String[] keywords = RubyKeyword.findByPrefix(wordStarting);
for (int j = 0; j < keywords.length; j++) {
reportKeyword(keywords[j]);
}
}
ModuleDeclaration moduleDeclaration = (ModuleDeclaration) parser
.parse(module, 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,
TI_TIMEOUT * 3 / 2);
reportSubElements(type, Util.EMPTY_STRING);
} else {
completeConstant(moduleDeclaration, Util.EMPTY_STRING,
position, true);
}
} else {
ASTNode minimalNode = ASTUtils.findMinimalNode(
moduleDeclaration, position, position);
if (minimalNode != null) {
if (minimalNode instanceof CallExpression) {
CallExpression callExp = (CallExpression) minimalNode;
if ((position > 0)
&& (content.charAt(position - 1) == ' ')) {
minimalNode = callExp.getArgs();
}
}
this.completionNode = minimalNode;
if (minimalNode instanceof CallExpression) {
completeCall(moduleDeclaration,
(CallExpression) minimalNode, position);
} else if (minimalNode instanceof CallArgumentsList) {
completeSimpleRef(moduleDeclaration, wordStarting,
position);
IEvaluatedType self = RubyTypeInferencingUtils
.determineSelfClass(mixinModel, currentModule,
moduleDeclaration, position);
if ((self != null)
&& "Object".equals(self.getTypeName())) { //$NON-NLS-1$
ExpressionTypeGoal goal = new ExpressionTypeGoal(
new BasicContext(currentModule,
moduleDeclaration), minimalNode);
IEvaluatedType self2 = inferencer.evaluateType(
goal, TI_TIMEOUT);
if (self2 != null) {
self = self2;
}
}
completeClassMethods(moduleDeclaration, self,
Util.EMPTY_STRING, true);
} 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, wordStarting,
position);
} else if (minimalNode instanceof RubyDVarExpression) {
if (afterDot(content, position)) {
completeClassMethods(moduleDeclaration,
minimalNode, wordStarting);
} else {
completeSimpleRef(moduleDeclaration, wordStarting,
position);
}
} else if (minimalNode instanceof MethodDeclaration
|| minimalNode instanceof TypeDeclaration
|| minimalNode instanceof StringLiteral) {
completeSimpleRef(moduleDeclaration, wordStarting,
position);
IEvaluatedType self = RubyTypeInferencingUtils
.determineSelfClass(mixinModel, currentModule,
moduleDeclaration, position);
if ((self != null)
&& "Object".equals(self.getTypeName())) { //$NON-NLS-1$
ExpressionTypeGoal goal = new ExpressionTypeGoal(
new BasicContext(currentModule,
moduleDeclaration), minimalNode);
IEvaluatedType self2 = inferencer.evaluateType(
goal, TI_TIMEOUT);
if (self2 != null) {
self = self2;
}
}
completeClassMethods(moduleDeclaration, self,
wordStarting, true);
} else if (minimalNode instanceof ModuleDeclaration) {
ExpressionTypeGoal goal = new ExpressionTypeGoal(
new BasicContext(currentModule,
moduleDeclaration), minimalNode);
IEvaluatedType self = inferencer.evaluateType(goal,
TI_TIMEOUT);
completeClassMethods(moduleDeclaration, self,
wordStarting, true);
} else if (minimalNode instanceof NumericLiteral
&& position > 0
&& position == minimalNode.sourceEnd()
&& position > minimalNode.sourceStart()
&& content.charAt(position - 1) == '.') {
setSourceRange(position, position);
completeClassMethods(moduleDeclaration, minimalNode,
Util.EMPTY_STRING);
} else { // worst case
completeSimpleRef(moduleDeclaration, wordStarting,
position);
if (wordStarting.length() == 0
&& !requestor.isContextInformationMode()) {
if (!afterContentAndSpace(moduleDeclaration,
content, position)) {
reportCurrentElements(moduleDeclaration,
position);
}
}
}
}
}
} finally {
this.requestor.endReporting();
}
}
/**
* @param content
* @param position
* @return
*/
private boolean afterContentAndSpace(ModuleDeclaration moduleDeclaration,
String content, int position) {
while (position > 0) {
final char c = content.charAt(position - 1);
if (c == ' ' || c == '\t') {
--position;
} else {
break;
}
}
if (position > 0
&& RubySyntaxUtils.isIdentifierCharacter(content
.charAt(position - 1))) {
ASTNode node = ASTUtils.findMinimalNode(moduleDeclaration,
position, position);
if (node instanceof CallExpression) {
int begin = position;
while (begin > 0 && content.charAt(begin - 1) != '\r'
&& content.charAt(begin - 1) != '\n') {
--begin;
}
ASTNode[] way = ASTUtils.restoreWayToNode(moduleDeclaration,
node);
for (int i = way.length - 1; --i >= 0;) {
if (way[i] instanceof CallExpression) {
// if multiple method calls in the same line
return way[i].sourceStart() >= begin;
}
}
return false;
}
return true;
}
return false;
}
private void reportCurrentElements(ModuleDeclaration moduleDeclaration,
int position) {
setSourceRange(position, position);
completeSimpleRef(moduleDeclaration, Util.EMPTY_STRING, position);
IClassType self = RubyTypeInferencingUtils.determineSelfClass(
mixinModel, currentModule, moduleDeclaration, position);
if (self == null) {
return;
}
completeClassMethods(moduleDeclaration, self, Util.EMPTY_STRING, true);
if ("Object".equals(self.getTypeName())) { //$NON-NLS-1$
try {
final IModelElement[] children = currentModule.getChildren();
if (children != null) {
for (int i = 0; i < children.length; ++i) {
IModelElement element = children[i];
if (element instanceof IField) {
reportField((IField) element, RELEVANCE_FREE_SPACE);
} else if (element instanceof IMethod) {
IMethod method = (IMethod) element;
if ((method.getFlags() & Modifiers.AccStatic) == 0) {
reportMethod(method, RELEVANCE_FREE_SPACE);
}
} else if (element instanceof IType) {
if (!element.getElementName().trim().startsWith(
"<<")) //$NON-NLS-1$
reportType((IType) element,
RELEVANCE_FREE_SPACE);
}
}
}
} catch (ModelException e) {
RubyPlugin.log(e);
}
}
}
private class CompletionMixinMethodRequestor implements
IMixinSearchRequestor {
private String lastParent = null;
private boolean lastParentIsSuperClass = true;
private final List<RubyMixinMethod> group = new ArrayList<RubyMixinMethod>();
private final RubyMixinClass klass;
private final boolean isSelf;
public CompletionMixinMethodRequestor(RubyMixinClass klass,
boolean isSelf) {
this.klass = klass;
this.isSelf = isSelf;
}
/**
* Tests that the specified key identifies superclass of the
* {@link #klass} or the same class.
*
* @param key
* @return
*/
private boolean isSuperClass(String key) {
if (!RubyMixinUtils.isObjectOrKernel(key)) {
if (key.equals(klass.getKey())) {
return true;
}
RubyMixinClass superclass = klass.getSuperclass();
while (superclass != null) {
final String superClassKey = superclass.getKey();
if (RubyMixinUtils.isObjectOrKernel(superClassKey)) {
break;
}
if (key.equals(superClassKey)) {
return true;
}
superclass = superclass.getSuperclass();
}
}
return false;
}
@Override
public void acceptResult(IRubyMixinElement element) {
if (element instanceof RubyMixinMethod) {
final RubyMixinMethod method = (RubyMixinMethod) element;
final RubyMixinClass selfClass = method.getSelfType();
if (lastParent == null
|| !lastParent.equals(selfClass.getKey())) {
if (lastParent != null) {
this.flush();
}
lastParent = selfClass.getKey();
lastParentIsSuperClass = isSuperClass(lastParent);
}
int flags = 0;
final IMethod[] methods = method.getSourceMethods();
for (int cnt = 0, max = methods.length; cnt < max; cnt++) {
final IMethod m = methods[cnt];
if (m != null) {
try {
flags |= m.getFlags();
} catch (ModelException mxcn) {
// ignore
}
}
}
final boolean shouldAdd = Flags.isPublic(flags)
|| lastParentIsSuperClass && Flags.isProtected(flags)
|| Flags.isPrivate(flags) && isSelf
|| RubyMixinUtils.isKernel(selfClass.getKey());
if (shouldAdd)
group.add(method);
}
}
public void flush() {
if (group.size() > 0) {
RubyMixinMethod[] mixinMethods = group.toArray(new RubyMixinMethod[group.size()]);
final List<IMethod> methods = RubyModelUtils.getAllSourceMethods(
mixinMethods, klass);
int skew = 0;
if (klass.getKey().equals(lastParent)) {
skew = 2;
} else if (lastParentIsSuperClass) {
// TODO calculate the distance in the hierarchy
skew = 1;
}
for (int j = 0, size = methods.size(); j < size; j++) {
reportMethod(methods.get(j), RELEVANCE_METHODS + skew);
}
group.clear();
}
}
}
private void completeClassMethods(ModuleDeclaration moduleDeclaration,
RubyMixinClass rubyClass, String prefix, boolean isSelf) {
CompletionMixinMethodRequestor mixinSearchRequestor = new CompletionMixinMethodRequestor(
rubyClass, isSelf);
rubyClass.findMethods(new PrefixNoCaseMixinSearchPattern(prefix),
mixinSearchRequestor);
mixinSearchRequestor.flush();
}
private void completeClassMethods(ModuleDeclaration moduleDeclaration,
IEvaluatedType type, String prefix, boolean isSelf) {
if (type instanceof RubyClassType) {
RubyClassType rubyClassType = (RubyClassType) type;
RubyMixinClass rubyClass = mixinModel
.createRubyClass(rubyClassType);
if (rubyClass != null) {
completeClassMethods(moduleDeclaration, rubyClass, prefix,
isSelf);
}
} 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, isSelf);
}
}
}
private void completeClassMethods(ModuleDeclaration moduleDeclaration,
ASTNode receiver, String pattern) {
ExpressionTypeGoal goal = new ExpressionTypeGoal(new BasicContext(
currentModule, moduleDeclaration), receiver);
IEvaluatedType type = inferencer.evaluateType(goal, TI_TIMEOUT);
completeClassMethods(moduleDeclaration, type, pattern,
receiver instanceof RubySelfReference);
}
private void completeGlobalVar(ModuleDeclaration moduleDeclaration,
String prefix, int position) {
this.setSourceRange(position - (prefix != null ? prefix.length() : 0),
position);
IMixinElement[] elements = mixinModel.getRawModel().find(
(prefix != null ? prefix : Util.EMPTY_STRING) + "*"); //$NON-NLS-1$
// String[] findKeys = RubyMixinModel.getRawInstance().findKeys(
// prefix + "*");
for (int i = 0; i < elements.length; i++) {
IRubyMixinElement rubyElement = mixinModel
.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_VARIABLES);
break;
}
}
}
}
for (int i = 0; i < globalVars.length; i++) {
if (prefix == null || globalVars[i].startsWith(prefix))
reportField(new FakeField(currentModule, globalVars[i], 0, 0),
RELEVANCE_VARIABLES);
}
}
private void completeSimpleRef(ModuleDeclaration moduleDeclaration,
String prefix, int position) {
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<ASTNode> vars = rubyBlock.getVars();
for (Iterator<ASTNode> iterator = vars.iterator(); iterator.hasNext();) {
ASTNode n = iterator.next();
if (n instanceof RubyDAssgnExpression) {
RubyDAssgnExpression rd = (RubyDAssgnExpression) n;
if (rd.getName().startsWith(prefix)) {
reportField(new FakeField(currentModule, rd
.getName(), 0, 0), RELEVANCE_VARIABLES);
}
}
}
}
}
if (prefix.startsWith("$")) { // globals //$NON-NLS-1$
completeGlobalVar(moduleDeclaration, prefix, position);
} else { // class & instance & locals
IField[] fields = RubyModelUtils.findFields(mixinModel,
currentModule, moduleDeclaration, prefix, position);
for (int i = 0; i < fields.length; i++) {
reportField(fields[i], RELEVANCE_VARIABLES);
}
}
}
private void reportSubElements(IEvaluatedType type, String prefix) {
if (!(type instanceof RubyClassType)) {
return;
}
RubyClassType rubyClassType = (RubyClassType) type;
IMixinElement mixinElement = mixinModel.getRawModel().get(
rubyClassType.getModelKey());
if (mixinElement == null) {
return;
}
List<IType> types = new ArrayList<IType>();
List<IMethod> methods = new ArrayList<IMethod>();
List<IField> fields = new ArrayList<IField>();
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
&& (prefix == null || type2.getElementName()
.startsWith(prefix))) {
types.add(type2);
}
} else if (obj.getKind() == RubyMixinElementInfo.K_METHOD) {
IMethod method2 = (IMethod) obj.getObject();
if (method2 != null
&& (prefix == null || method2.getElementName()
.startsWith(prefix))) {
methods.add(method2);
}
}
if (obj.getKind() == RubyMixinElementInfo.K_VARIABLE) {
IField fff = (IField) obj.getObject();
if (fff != null
&& (prefix == null || fff.getElementName()
.startsWith(prefix))) {
fields.add(fff);
}
}
break;
}
}
for (Iterator<IField> iterator = fields.iterator(); iterator.hasNext();) {
IField t = iterator.next();
reportField(t, RELEVANCE_VARIABLES);
}
for (Iterator<IType> iterator = types.iterator(); iterator.hasNext();) {
IType t = iterator.next();
reportType(t, RELEVANCE_TYPE);
}
for (Iterator<IMethod> iterator = methods.iterator(); iterator.hasNext();) {
IMethod t = iterator.next();
reportMethod(t, RELEVANCE_METHODS);
}
}
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, TI_TIMEOUT * 3 / 2);
reportSubElements(type, starting);
}
private void completeConstant(ModuleDeclaration moduleDeclaration,
String prefix, int position, boolean topLevelOnly) {
IType[] types = RubyTypeInferencingUtils.getAllTypes(currentModule,
prefix);
Arrays.sort(types, new ProjectTypeComparator(currentModule));
final Set<String> names = new HashSet<String>();
for (int i = 0; i < types.length; i++) {
final String elementName = types[i].getElementName();
if (names.add(elementName)) {
reportType(types[i], RELEVANCE_TYPE);
}
}
if (!topLevelOnly) {
IMixinElement[] modelStaticScopes = RubyTypeInferencingUtils
.getModelStaticScopes(mixinModel.getRawModel(),
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);
}
}
if (prefix != null && prefix.length() > 0) {
String varkey = "Object" + MixinModel.SEPARATOR + prefix; //$NON-NLS-1$
String[] keys2 = mixinModel.getRawModel().findKeys(varkey + "*"); //$NON-NLS-1$
for (int i = 0; i < keys2.length; i++) {
IRubyMixinElement element = mixinModel
.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_VARIABLES);
break;
}
}
}
}
}
}
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() == node
.sourceStart()) ? receiver.sourceStart() : (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) || (receiver.sourceEnd() == node.sourceStart())) {
completeSimpleRef(moduleDeclaration, starting, position);
completeConstant(moduleDeclaration, starting, position, false);
}
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) && (receiver.sourceEnd() != node.sourceStart())) {
completeClassMethods(moduleDeclaration, receiver, starting);
} else {
IEvaluatedType self = RubyTypeInferencingUtils.determineSelfClass(
mixinModel, currentModule, moduleDeclaration, position);
if ((self != null) && "Object".equals(self.getTypeName())) { //$NON-NLS-1$
ASTNode minNode = node;
ASTNode[] wayToNode = ASTUtils.restoreWayToNode(
moduleDeclaration, node);
for (int cnt = (wayToNode.length - 2); cnt >= 0; cnt--) {
if ((wayToNode[cnt] instanceof TypeDeclaration)
|| (wayToNode[cnt] instanceof ModuleDeclaration)) {
minNode = wayToNode[cnt];
break;
}
}
ExpressionTypeGoal goal = new ExpressionTypeGoal(
new BasicContext(currentModule, moduleDeclaration),
minNode);
IEvaluatedType self2 = inferencer
.evaluateType(goal, TI_TIMEOUT);
if (self2 != null) {
self = self2;
}
}
completeClassMethods(moduleDeclaration, self, starting, true);
}
}
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);
}
String name = elementName;
String compl = name;
// accept result
noProposal = false;
if (!requestor.isIgnored(CompletionProposal.METHOD_REF)) {
CompletionProposal proposal = createProposal(
CompletionProposal.METHOD_REF, actualCompletionPosition);
String[] params = null;
try {
params = method.getParameterNames();
} catch (ModelException e) {
// ssanders: Ignore
}
if (params != null && params.length > 0) {
proposal.setParameterNames(params);
}
proposal.setModelElement(method);
proposal.setName(name);
proposal.setCompletion(compl);
try {
proposal.setFlags(method.getFlags());
} catch (ModelException e) {
// ssanders: Ignore
}
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);
String name = elementName;
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);
// 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);
String name = elementName;
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);
// 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);
proposal.setCompletion(name);
// 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);
}
}
}
}