/** * Copyright (c) 2005-2013 by Appcelerator, Inc. All Rights Reserved. * Licensed under the terms of the Eclipse Public License (EPL). * Please see the license.txt included with this distribution for details. * Any modifications to this file must keep this entire header intact. */ package org.python.pydev.editor.codecompletion.revisited; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import org.python.pydev.core.FullRepIterable; import org.python.pydev.core.ICodeCompletionASTManager; import org.python.pydev.core.ICompletionState; import org.python.pydev.core.ILocalScope; import org.python.pydev.core.IModule; import org.python.pydev.core.IToken; import org.python.pydev.core.ITypeInfo; import org.python.pydev.core.UnpackInfo; import org.python.pydev.core.log.Log; import org.python.pydev.core.structure.CompletionRecursionException; import org.python.pydev.editor.codecompletion.revisited.modules.SourceModule; import org.python.pydev.editor.codecompletion.revisited.modules.SourceToken; import org.python.pydev.editor.codecompletion.revisited.visitors.AssignDefinition; import org.python.pydev.editor.codecompletion.revisited.visitors.Definition; import org.python.pydev.parser.jython.SimpleNode; import org.python.pydev.parser.jython.ast.Assign; import org.python.pydev.parser.jython.ast.Attribute; import org.python.pydev.parser.jython.ast.Call; import org.python.pydev.parser.jython.ast.ClassDef; import org.python.pydev.parser.jython.ast.FunctionDef; import org.python.pydev.parser.jython.ast.Index; import org.python.pydev.parser.jython.ast.Num; import org.python.pydev.parser.jython.ast.Str; import org.python.pydev.parser.jython.ast.Subscript; import org.python.pydev.parser.jython.ast.UnaryOp; import org.python.pydev.parser.jython.ast.exprType; import org.python.pydev.parser.visitors.NodeUtils; import org.python.pydev.parser.visitors.TypeInfo; import org.python.pydev.shared_core.string.StringUtils; /** * This class is used to analyse the assigns in the code and bring actual completions for them. */ public class AssignAnalysis { /** * If we got here, either there really is no definition from the token * or it is not looking for a definition. This means that probably * it is something like. * * It also can happen in many scopes, so, first we have to check the current * scope and then pass to higher scopes * * e.g. foo = Foo() * foo. | Ctrl+Space * * so, first thing is discovering in which scope we are (Storing previous scopes so * that we can search in other scopes as well). * @param localScope */ public AssignCompletionInfo getAssignCompletions(ICodeCompletionASTManager manager, IModule module, ICompletionState state, ILocalScope localScope) { int assignLevel = state.pushAssign(); try { ArrayList<IToken> ret = new ArrayList<IToken>(); Definition[] defs = new Definition[0]; if (module instanceof SourceModule) { SourceModule s = (SourceModule) module; try { defs = s.findDefinition(state, state.getLine() + 1, state.getCol() + 1, state.getNature()); if (defs.length > 0) { for (int i = 0; i < defs.length; i++) { //go through all definitions found and make a merge of it... Definition definition = defs[i]; if (state.getAlreadySearchedInAssign(definition.line, definition.col, definition.module, definition.value, state.getActivationToken())) { // It's possible that we have many assigns where it may be normal to have loops // i.e.: cp = self.x[:] ... self.x = cp, so, let's mark those places so that we don't recurse. // System.out.println("Skip: " + definition); continue; } if (state.getLine() == definition.line && state.getCol() == definition.col) { //Check the module if (definition.module != null && definition.module.equals(s)) { //initial and final are the same if (state.checkFoudSameDefinition(definition.line, definition.col, definition.module)) { //We found the same place we found previously (so, we're recursing here... Just go on) continue; } } } AssignDefinition assignDefinition = null; if (definition instanceof AssignDefinition) { assignDefinition = (AssignDefinition) definition; } if (definition.ast instanceof FunctionDef) { List<IToken> found = manager.getCompletionFromFuncDefReturn(state, s, definition, false); ret.addAll(found); } else { List<IToken> found = getNonFunctionDefCompletionsFromAssign(manager, state, s, definition, assignDefinition); //String spaces = new FastStringBuffer().appendN(' ', assignLevel).toString(); //System.out.println(spaces + "Tok: " + state.getActivationToken()); //System.out.println(spaces + "Def: " + definition); //System.out.println(spaces + "Adding: " + found.size()); ret.addAll(found); } } } else { if (localScope != null) { IToken[] tokens = searchInLocalTokens(manager, state, true, state.getLine() + 1, state.getCol() + 1, module, localScope, state.getActivationToken()); if (tokens != null) { ret.addAll(Arrays.asList(tokens)); } } } } catch (CompletionRecursionException e) { //thats ok } catch (Exception e) { Log.log(e); throw new RuntimeException("Error when getting assign completions for:" + module.getName(), e); } catch (Throwable t) { throw new RuntimeException("A throwable exception has been detected " + t.getClass()); } } return new AssignCompletionInfo(defs, ret); } finally { state.popAssign(); } } /** * The user should be able to configure that, but let's leave it hard-coded until the next release... * * Names of methods that will return instance of the passed class -> index of class parameter. */ public final static Map<String, Integer> CALLS_FOR_ASSIGN_WITH_RESULTING_CLASS = new HashMap<String, Integer>(); static { //method factory that receives parameter with class -> class parameter index CALLS_FOR_ASSIGN_WITH_RESULTING_CLASS.put("adapt".toLowerCase(), 2); CALLS_FOR_ASSIGN_WITH_RESULTING_CLASS.put("GetSingleton".toLowerCase(), 1); CALLS_FOR_ASSIGN_WITH_RESULTING_CLASS.put("GetImplementation".toLowerCase(), 1); CALLS_FOR_ASSIGN_WITH_RESULTING_CLASS.put("GetAdapter".toLowerCase(), 1); CALLS_FOR_ASSIGN_WITH_RESULTING_CLASS.put("get_adapter".toLowerCase(), 1); } /** * This method will look into the right side of an assign and its definition and will try to gather the tokens for * it, knowing that it is dealing with a non-function def token for the definition found. * * @param ret the place where the completions should be added * @param assignDefinition may be null if it was not actually found as an assign * @return */ private List<IToken> getNonFunctionDefCompletionsFromAssign(ICodeCompletionASTManager manager, ICompletionState state, SourceModule sourceModule, Definition definition, AssignDefinition assignDefinition) throws CompletionRecursionException { IModule module; ArrayList<IToken> ret = new ArrayList<IToken>(); if (definition.ast instanceof ClassDef) { state.setLookingFor(ICompletionState.LOOKING_FOR_UNBOUND_VARIABLE); ret.addAll(((SourceModule) definition.module).getClassToks(state, manager, definition.ast)); } else { boolean lookForAssign = true; //ok, see what we can do about adaptation here... //pyprotocols does adapt(xxx, Interface), so, knowing the type of the interface can get us to nice results... //the user can usually have other factory methods that do that too. E.g.: GetSingleton(Class) may return an //expected class and so on, so, this should be configured somehow if (assignDefinition != null) { Assign assign = (Assign) assignDefinition.ast; if (assign.value instanceof Call) { Call call = (Call) assign.value; String lastPart = FullRepIterable.getLastPart(assignDefinition.value); if ("namedtuple".equals(lastPart)) { //throw new AssertionError("deal with named tuple"); if (call.args != null && call.args.length > 1) { exprType args = call.args[1]; exprType[] elts = null; if (args instanceof org.python.pydev.parser.jython.ast.List) { org.python.pydev.parser.jython.ast.List list = (org.python.pydev.parser.jython.ast.List) args; elts = list.elts; } else if (args instanceof org.python.pydev.parser.jython.ast.Tuple) { org.python.pydev.parser.jython.ast.Tuple tuple = (org.python.pydev.parser.jython.ast.Tuple) args; elts = tuple.elts; } else if (args instanceof org.python.pydev.parser.jython.ast.Set) { org.python.pydev.parser.jython.ast.Set set = (org.python.pydev.parser.jython.ast.Set) args; elts = set.elts; } if (elts != null) { for (exprType exprType : elts) { if (exprType instanceof Str) { ret.add(new SourceToken(exprType, ((Str) exprType).s, "", "", sourceModule.getName(), sourceModule.getNature())); } } return ret; } if (args instanceof Call) { Call call2 = (Call) args; if (call2.func instanceof Attribute) { Attribute attribute = (Attribute) call2.func; if ("split".equals(NodeUtils.getRepresentationString(attribute.attr))) { if (attribute.value instanceof Str) { Str str = (Str) attribute.value; if (str.s != null) { List<String> split = StringUtils.split(str.s, " "); for (String string : split) { ret.add(new SourceToken(str, string, "", "", sourceModule.getName(), sourceModule.getNature())); } return ret; } } } } } } } Integer parameterIndex = CALLS_FOR_ASSIGN_WITH_RESULTING_CLASS.get(lastPart.toLowerCase()); if (parameterIndex != null && call.args.length >= parameterIndex) { String rep = NodeUtils.getFullRepresentationString(call.args[parameterIndex - 1]); HashSet<IToken> hashSet = new HashSet<IToken>(); List<ITypeInfo> lookForClass = new ArrayList<>(); lookForClass.add(new TypeInfo(rep)); manager.getCompletionsForClassInLocalScope(sourceModule, state, true, false, lookForClass, hashSet); if (hashSet.size() > 0) { lookForAssign = false; ret.addAll(hashSet); } } } if (lookForAssign) { IToken[] tokens = searchInLocalTokens(manager, state, lookForAssign, definition.line, definition.col, definition.module, assignDefinition.scope, assignDefinition.value); if (tokens != null && tokens.length > 0) { ret.addAll(Arrays.asList(tokens)); lookForAssign = false; } } } if (lookForAssign) { //TODO: we might want to extend that later to check the return of some function for code-completion purposes... state.setLookingFor(ICompletionState.LOOKING_FOR_ASSIGN); ICompletionState copy = state.getCopy(); if (definition.ast instanceof Attribute) { copy.setActivationToken(NodeUtils.getFullRepresentationString(definition.ast)); } else { copy.setActivationToken(definition.value); } copy.setLine(definition.line); copy.setCol(definition.col); module = definition.module; state.checkDefinitionMemory(module, definition); if (assignDefinition != null) { Collection<IToken> interfaceForLocal = assignDefinition.scope .getInterfaceForLocal(assignDefinition.target); ret.addAll(interfaceForLocal); } int unpackPos = -1; boolean unpackBackwards = false; if (assignDefinition != null) { unpackPos = assignDefinition.unpackPos; // Let's see if we have if (definition.ast instanceof Assign) { Assign assign = (Assign) definition.ast; if (assign.value instanceof Subscript) { Subscript subscript = (Subscript) assign.value; if (subscript.slice instanceof Index) { Index index = (Index) subscript.slice; exprType indexValue = index.value; if (indexValue instanceof UnaryOp) { // i.e.: x = a[-1] UnaryOp unaryOp = (UnaryOp) indexValue; if (unaryOp.op == UnaryOp.USub) { //negative unpackBackwards = true; } indexValue = unaryOp.operand; } if (indexValue instanceof Num) { Num num = (Num) indexValue; // i.e.: x = a[0] or x = a[-1] String rep = NodeUtils.getRepresentationString(num); try { int subscriptIndex = Integer.parseInt(rep); unpackPos = subscriptIndex; // Note that we can be dealing with negative numbers! } catch (NumberFormatException e) { //ignore } } } } } } if (assignDefinition != null && unpackPos >= 0) { IToken[] tks = manager.getCompletionsUnpackingObject( module, copy, assignDefinition.scope, new UnpackInfo(false, unpackPos, unpackBackwards)); if (tks != null) { ret.addAll(Arrays.asList(tks)); } } else { IToken[] tks = manager.getCompletionsForModule(module, copy, true, true); ret.addAll(Arrays.asList(tks)); } } } return ret; } /** * * @param manager * @param state * @param lookForAssign * @param line starts at 1 * @param col starts at 1 * @param module * @param scope * @return * @throws CompletionRecursionException */ public IToken[] searchInLocalTokens(ICodeCompletionASTManager manager, ICompletionState state, boolean lookForAssign, int line, int col, IModule module, ILocalScope scope, String activationToken) throws CompletionRecursionException { //it may be declared as a global with a class defined in the local scope IToken[] allLocalTokens = scope.getAllLocalTokens(); for (IToken token : allLocalTokens) { if (token.getRepresentation().equals(activationToken)) { if (token instanceof SourceToken) { SourceToken srcToken = (SourceToken) token; SimpleNode ast = srcToken.getAst(); if (ast instanceof ClassDef && module instanceof SourceModule) { List<IToken> classToks = ((SourceModule) module).getClassToks( state, manager, ast); if (classToks.size() > 0) { return classToks.toArray(new IToken[0]); } } } } } ICompletionState copy = state.getCopy(); copy.setLine(line); copy.setCol(col); copy.setActivationToken(activationToken); IToken[] tokens = manager.getCompletionsFromTokenInLocalScope(module, copy, false, false, scope); if (tokens != null && tokens.length > 0) { return tokens; } return null; } }