/** * Copyright (c) 2005-2011 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.io.File; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.SortedMap; import org.eclipse.core.resources.IProject; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.jface.text.IDocument; import org.python.pydev.core.FullRepIterable; import org.python.pydev.core.ICodeCompletionASTManager; import org.python.pydev.core.ICompletionRequest; import org.python.pydev.core.ICompletionState; import org.python.pydev.core.IGrammarVersionProvider; import org.python.pydev.core.ILocalScope; import org.python.pydev.core.IModule; import org.python.pydev.core.IModulesManager; import org.python.pydev.core.IPythonNature; import org.python.pydev.core.IToken; import org.python.pydev.core.ImmutableTuple; import org.python.pydev.core.MisconfigurationException; import org.python.pydev.core.ModulesKey; import org.python.pydev.core.Tuple3; import org.python.pydev.core.TupleN; import org.python.pydev.core.callbacks.ICallback0; import org.python.pydev.core.docutils.StringUtils; import org.python.pydev.core.log.Log; import org.python.pydev.core.structure.CompletionRecursionException; import org.python.pydev.editor.actions.PyAction; import org.python.pydev.editor.codecompletion.revisited.modules.AbstractModule; 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.AbstractVisitor; import org.python.pydev.editor.codecompletion.revisited.visitors.Definition; import org.python.pydev.editor.codecompletion.revisited.visitors.GlobalModelVisitor; import org.python.pydev.logging.DebugSettings; import org.python.pydev.parser.PyParser; import org.python.pydev.parser.jython.SimpleNode; import org.python.pydev.parser.jython.ast.Import; import org.python.pydev.parser.jython.ast.ImportFrom; import org.python.pydev.parser.jython.ast.NameTok; import org.python.pydev.parser.jython.ast.aliasType; import org.python.pydev.parser.visitors.NodeUtils; import com.aptana.shared_core.io.FileUtils; import com.aptana.shared_core.structure.Tuple; public abstract class AbstractASTManager implements ICodeCompletionASTManager { private static final IToken[] EMPTY_ITOKEN_ARRAY = new IToken[0]; private static final boolean DEBUG_CACHE = false; private final AssignAnalysis assignAnalysis = new AssignAnalysis(); public AbstractASTManager() { } private final Object lock = new Object(); public Object getLock() { return lock; } /** * This is the guy that will handle project things for us */ public volatile IModulesManager modulesManager; public IModulesManager getModulesManager() { return modulesManager; } /** * Set the nature this ast manager works with (if no project is available and a nature is). */ public void setNature(IPythonNature nature) { getModulesManager().setPythonNature(nature); } public IPythonNature getNature() { return getModulesManager().getNature(); } public abstract void setProject(IProject project, IPythonNature nature, boolean restoreDeltas); public abstract void rebuildModule(File file, ICallback0<IDocument> doc, IProject project, IProgressMonitor monitor, IPythonNature nature); public abstract void removeModule(File file, IProject project, IProgressMonitor monitor); /** * Returns the imports that start with a given string. The comparison is not case dependent. Passes all the modules in the cache. * * @param original is the name of the import module eg. 'from toimport import ' would mean that the original is 'toimport' * or something like 'foo.bar' or an empty string (if only 'import'). * @return a Set with the imports as tuples with the name, the docstring. * @throws CompletionRecursionException * @throws MisconfigurationException */ public IToken[] getCompletionsForImport(ImportInfo importInfo, ICompletionRequest r, boolean onlyGetDirectModules) throws CompletionRecursionException, MisconfigurationException { String original = importInfo.importsTipperStr; String afterDots = null; int level = 0; //meaning: no absolute import boolean onlyDots = true; if (original.startsWith(".")) { //if the import has leading dots, this means it is something like //from ...bar import xxx (new way to express the relative import) for (int i = 0; i < original.length(); i++) { if (original.charAt(i) != '.') { onlyDots = false; afterDots = original.substring(i); break; } //add one to the relative import level level++; } } ICompletionRequest request = r; IPythonNature nature = request.getNature(); String relative = null; String moduleName = null; if (request.getEditorFile() != null) { moduleName = modulesManager.resolveModule(FileUtils.getFileAbsolutePath(request.getEditorFile())); if (moduleName != null) { if (level > 0) { //ok, it is the import added on python 2.5 (from .. import xxx) List<String> moduleParts = StringUtils.dotSplit(moduleName); if (moduleParts.size() > level) { relative = FullRepIterable.joinParts(moduleParts, moduleParts.size() - level); } if (!onlyDots) { //ok, we have to add the other part too, as we have more than the leading dots //from ..bar import relative += "." + afterDots; } } else { boolean isAbsoluteImportEnabled = isAbsoluteImportEnabled(request, nature); if (!isAbsoluteImportEnabled) { String tail = FullRepIterable.headAndTail(moduleName)[0]; if (original.length() > 0) { relative = tail + "." + original; } else { relative = tail; } } } } } //set to hold the completion (no duplicates allowed). Set<IToken> set = new HashSet<IToken>(); String absoluteModule = original; if (absoluteModule.endsWith(".")) { absoluteModule = absoluteModule.substring(0, absoluteModule.length() - 1); //remove last char } //If we have a relative import, first match with the relative and only try to match the absolute if the relative //was not found. if (relative != null && relative.equals(absoluteModule) == false) { getAbsoluteImportTokens(relative, set, IToken.TYPE_RELATIVE_IMPORT, false, importInfo, onlyGetDirectModules); if (importInfo.hasImportSubstring) { getTokensForModule(relative, nature, relative, set); } } if (set.size() == 0 || absoluteModule.length() == 0 //In the case of an "import zi", the absoluteModule will be an empty string, and we have to get the roots in the completion! ) { if (level == 0) { //first we get the imports... that complete for the token. getAbsoluteImportTokens(absoluteModule, set, IToken.TYPE_IMPORT, false, importInfo, onlyGetDirectModules); //Now, if we have an initial module, we have to get the completions //for it. getTokensForModule(original, nature, absoluteModule, set); } } if (level == 1 && moduleName != null) { //has returned itself, so, let's remove it String strToRemove = FullRepIterable.getLastPart(moduleName); for (Iterator<IToken> it = set.iterator(); it.hasNext();) { IToken o = it.next(); if (o.getRepresentation().equals(strToRemove)) { it.remove(); //don't break because the token might be different, but not the representation... } } } return set.toArray(EMPTY_ITOKEN_ARRAY); } private boolean isAbsoluteImportEnabled(IModule module, IPythonNature nature) throws MisconfigurationException { boolean isAbsoluteImportEnabled = false; try { isAbsoluteImportEnabled = nature.getGrammarVersion() >= IGrammarVersionProvider.GRAMMAR_PYTHON_VERSION_3_0; } catch (MisconfigurationException e) { Log.log(e); } if (!isAbsoluteImportEnabled) { //Let's check in Python 2.x if the from __future__ import absolute_import is there. if (module != null) { isAbsoluteImportEnabled = module.hasFutureImportAbsoluteImportDeclared(); } } return isAbsoluteImportEnabled; } private boolean isAbsoluteImportEnabled(ICompletionRequest request, IPythonNature nature) throws MisconfigurationException { boolean isAbsoluteImportEnabled = false; try { isAbsoluteImportEnabled = nature.getGrammarVersion() >= IGrammarVersionProvider.GRAMMAR_PYTHON_VERSION_3_0; } catch (MisconfigurationException e) { Log.log(e); } if (!isAbsoluteImportEnabled) { //Let's check in Python 2.x if the from __future__ import absolute_import is there. IModule module = request.getModule(); if (module != null) { isAbsoluteImportEnabled = module.hasFutureImportAbsoluteImportDeclared(); } } return isAbsoluteImportEnabled; } /** * @param moduleToGetTokensFrom the string that represents the token from where we are getting the imports * @param set the set where the tokens should be added * @param importInfo if null, only the 1st element of the module will be added, otherwise, it'll check the info * to see if it should add only the 1st element of the module or the complete module (e.g.: add only xml or * xml.dom and other submodules too) */ public void getAbsoluteImportTokens(String moduleToGetTokensFrom, Set<IToken> inputOutput, int type, boolean onlyFilesOnSameLevel, ImportInfo importInfo, boolean onlyGetDirectModules) { // boolean getSubModules = false; // if(importInfo != null){ // //we only want to get submodules if we're in: // //from xxx // //import xxx // // // //We do NOT want to get it on: // //from xxx import yyy // if(importInfo.hasFromSubstring != importInfo.hasImportSubstring){ // getSubModules = true; // } // } HashMap<String, IToken> temp = new HashMap<String, IToken>(); SortedMap<ModulesKey, ModulesKey> modulesStartingWith; if (onlyGetDirectModules) { modulesStartingWith = modulesManager.getAllDirectModulesStartingWith(moduleToGetTokensFrom); } else { modulesStartingWith = modulesManager.getAllModulesStartingWith(moduleToGetTokensFrom); } Iterator<ModulesKey> itModules = modulesStartingWith.keySet().iterator(); while (itModules.hasNext()) { ModulesKey key = itModules.next(); String element = key.name; // if (element.startsWith(moduleToGetTokensFrom)) { we don't check that anymore because we get all the modules starting with it already if (onlyFilesOnSameLevel && key.file != null && key.file.isDirectory()) { continue; // we only want those that are in the same directory, and not in other directories... } element = element.substring(moduleToGetTokensFrom.length()); //we just want those that are direct //this means that if we had initially element = testlib.unittest.anothertest //and element became later = .unittest.anothertest, it will be ignored (we //should only analyze it if it was something as testlib.unittest and became .unittest //we only check this if we only want file modules (in if (onlyFilesOnSameLevel && PyAction.countChars('.', element) > 1) { continue; } boolean goForIt = false; //if initial is not empty only get those that start with a dot (submodules, not //modules that start with the same name). //e.g. we want xml.dom //and not xmlrpclib //if we have xml token (not using the qualifier here) if (moduleToGetTokensFrom.length() != 0) { if (element.length() > 0 && element.charAt(0) == ('.')) { element = element.substring(1); goForIt = true; } } else { goForIt = true; } if (element.length() > 0 && goForIt) { List<String> splitted = StringUtils.dotSplit(element); if (splitted.size() > 0) { String strToAdd; strToAdd = splitted.get(0); // if(!getSubModules){ // }else{ // if(element.endsWith(".__init__")){ // strToAdd = element.substring(0, element.length()-9); // }else{ // strToAdd = element; // } // } //this is the completion temp.put(strToAdd, new ConcreteToken(strToAdd, "", "", moduleToGetTokensFrom, type)); } } // } } inputOutput.addAll(temp.values()); } /** * @param original this is the initial module where the completion should happen (may have class in it too) * @param moduleToGetTokensFrom * @param set set where the tokens should be added * @throws CompletionRecursionException */ protected void getTokensForModule(String original, IPythonNature nature, String moduleToGetTokensFrom, Set<IToken> set) throws CompletionRecursionException { if (moduleToGetTokensFrom.length() > 0) { if (original.endsWith(".")) { original = original.substring(0, original.length() - 1); } Tuple<IModule, String> modTok = findModuleFromPath(original, nature, false, null); //the current module name is not used as it is not relative IModule m = modTok.o1; String tok = modTok.o2; if (m == null) { //we were unable to find it with the given path, so, there's nothing else to do here... return; } IToken[] globalTokens; if (tok != null && tok.length() > 0) { CompletionState state2 = new CompletionState(-1, -1, tok, nature, ""); state2.setBuiltinsGotten(true); //we don't want to get builtins here globalTokens = m.getGlobalTokens(state2, this); } else { CompletionState state2 = new CompletionState(-1, -1, "", nature, ""); state2.setBuiltinsGotten(true); //we don't want to get builtins here globalTokens = getCompletionsForModule(m, state2); } for (int i = 0; i < globalTokens.length; i++) { IToken element = globalTokens[i]; //this is the completion set.add(element); } } } /** * @param file * @param doc * @param state * @return * @throws MisconfigurationException */ public static IModule createModule(File file, IDocument doc, IPythonNature nature) throws MisconfigurationException { return AbstractModule.createModuleFromDoc(file, doc, nature); } // /** // * @throws MisconfigurationException // * @see org.python.pydev.core.ICodeCompletionASTManager#getCompletionsForToken(java.io.File, org.eclipse.jface.text.IDocument, org.python.pydev.editor.codecompletion.revisited.CompletionState) // */ // public IToken[] getCompletionsForToken(File file, IDocument doc, ICompletionState state) throws CompletionRecursionException, MisconfigurationException { // IModule module = createModule(file, doc, state, this); // return getCompletionsForModule(module, state, true, true); // } /** * @see org.python.pydev.editor.codecompletion.revisited.ICodeCompletionASTManage#getCompletionsForToken(org.eclipse.jface.text.IDocument, org.python.pydev.editor.codecompletion.revisited.CompletionState) */ public IToken[] getCompletionsForToken(IDocument doc, ICompletionState state) { IToken[] completionsForModule; try { Tuple<SimpleNode, Throwable> obj = PyParser .reparseDocument(new PyParser.ParserInfo(doc, state.getNature())); SimpleNode n = obj.o1; IModule module = AbstractModule.createModule(n); completionsForModule = getCompletionsForModule(module, state, true, true); } catch (Exception e) { String message = e.getMessage(); if (message == null) { if (e instanceof NullPointerException) { Log.log(e); message = "NullPointerException"; } else { message = "Null error message"; } } completionsForModule = new IToken[] { new ConcreteToken(message, message, "", "", IToken.TYPE_UNKNOWN) }; } return completionsForModule; } /** * By default does not look for relative import */ public IModule getModule(String name, IPythonNature nature, boolean dontSearchInit) { return modulesManager.getModule(name, nature, dontSearchInit, false); } /** * This method returns the module that corresponds to the path passed as a parameter. * * @param name the name of the module we're looking for * @param lookingForRelative determines whether we're looking for a relative module (in which case we should * not check in other places... only in the module) * @return the module represented by this name */ public IModule getModule(String name, IPythonNature nature, boolean dontSearchInit, boolean lookingForRelative) { if (lookingForRelative) { return modulesManager.getRelativeModule(name, nature); } else { return modulesManager.getModule(name, nature, dontSearchInit); } } /** * Identifies the token passed and if it maps to a builtin not 'easily recognizable', as * a string or list, we return it. * * @param state * @return */ protected IToken[] getBuiltinsCompletions(ICompletionState state) { ICompletionState state2 = state.getCopy(); String act = state.getActivationToken(); //check for the builtin types. state2.setActivationToken(NodeUtils.getBuiltinType(act)); if (state2.getActivationToken() != null) { IModule m = getBuiltinMod(state.getNature()); if (m != null) { return m.getGlobalTokens(state2, this); } } if (act.equals("__builtins__") || act.startsWith("__builtins__.")) { act = act.substring(12); if (act.startsWith(".")) { act = act.substring(1); } IModule m = getBuiltinMod(state.getNature()); ICompletionState state3 = state.getCopy(); state3.setActivationToken(act); return m.getGlobalTokens(state3, this); } return null; } /** * @throws CompletionRecursionException * @see org.python.pydev.editor.codecompletion.revisited.ICodeCompletionASTManage#getCompletionsForModule(org.python.pydev.editor.codecompletion.revisited.modules.AbstractModule, org.python.pydev.editor.codecompletion.revisited.CompletionState) */ public IToken[] getCompletionsForModule(IModule module, ICompletionState state) throws CompletionRecursionException { return getCompletionsForModule(module, state, true); } /** * @throws CompletionRecursionException * @see org.python.pydev.editor.codecompletion.revisited.ICodeCompletionASTManage#getCompletionsForModule(org.python.pydev.editor.codecompletion.revisited.modules.AbstractModule, org.python.pydev.editor.codecompletion.revisited.CompletionState, boolean) */ public IToken[] getCompletionsForModule(IModule module, ICompletionState state, boolean searchSameLevelMods) throws CompletionRecursionException { return getCompletionsForModule(module, state, true, false); } /** * @see org.python.pydev.editor.codecompletion.revisited.ICodeCompletionASTManage#getCompletionsForModule(org.python.pydev.editor.codecompletion.revisited.modules.AbstractModule, org.python.pydev.editor.codecompletion.revisited.CompletionState, boolean, boolean) */ public IToken[] getCompletionsForModule(IModule module, ICompletionState state, boolean searchSameLevelMods, boolean lookForArgumentCompletion) throws CompletionRecursionException { return getCompletionsForModule(module, state, searchSameLevelMods, lookForArgumentCompletion, false); } /** * @see #getCompletionsForModule(IModule, ICompletionState, boolean, boolean) * * Same thing but may handle things as if it was a wild import (in which case, the tokens starting with '_' are * removed and if __all__ is available, only the tokens contained in __all__ are returned) */ public IToken[] getCompletionsForModule(IModule module, ICompletionState state, boolean searchSameLevelMods, boolean lookForArgumentCompletion, boolean handleAsWildImport) throws CompletionRecursionException { String name = module.getName(); Object key = new TupleN("getCompletionsForModule", name != null ? name : "", state.getActivationToken(), searchSameLevelMods, lookForArgumentCompletion, state.getBuiltinsGotten(), state.getLocalImportsGotten(), handleAsWildImport); IToken[] ret = (IToken[]) state.getObj(key); if (ret != null) { if (DEBUG_CACHE) { System.out.println("Checking if cache is correct for: " + key); IToken[] internal = internalGenerateGetCompletionsForModule(module, state, searchSameLevelMods, lookForArgumentCompletion); internal = filterForWildImport(module, handleAsWildImport, internal); //the new request may actually have no tokens if a completion exception occurred. if (internal.length != 0 && ret.length != internal.length) { throw new RuntimeException("This can't happen... it should always return the same completions!"); } } return ret; } IToken[] completionsForModule = internalGenerateGetCompletionsForModule(module, state, searchSameLevelMods, lookForArgumentCompletion); completionsForModule = filterForWildImport(module, handleAsWildImport, completionsForModule); state.add(key, completionsForModule); return completionsForModule; } /** * Filters the tokens according to the wild import rules: * - the tokens starting with '_' are removed * - if __all__ is available, only the tokens contained in __all__ are returned) */ private IToken[] filterForWildImport(IModule module, boolean handleAsWildImport, IToken[] completionsForModule) { if (module != null && handleAsWildImport) { ArrayList<IToken> ret = new ArrayList<IToken>(); for (int j = 0; j < completionsForModule.length; j++) { IToken token = completionsForModule[j]; //on wild imports we don't get names that start with '_' if (!token.getRepresentation().startsWith("_")) { ret.add(token); } } if (module instanceof SourceModule) { //Support for __all__: filter things if __all__ is available. SourceModule sourceModule = (SourceModule) module; GlobalModelVisitor globalModelVisitorCache = sourceModule.getGlobalModelVisitorCache(); if (globalModelVisitorCache != null) { globalModelVisitorCache.filterAll(ret); } } return ret.toArray(new IToken[ret.size()]); } else { return completionsForModule; } } private void log(String message, IModule module, ICompletionState state) { String name; if (module == null) { name = "null module"; } else { name = module.getName(); } Log.toLogFile(this, message + ": " + name + " -- " + state.getActivationToken()); } /** * This method should only be accessed from the public getCompletionsForModule (which caches the result). */ private IToken[] internalGenerateGetCompletionsForModule(IModule module, ICompletionState state, boolean searchSameLevelMods, boolean lookForArgumentCompletion) throws CompletionRecursionException { if (DebugSettings.DEBUG_CODE_COMPLETION) { log("internalGenerateGetCompletionsForModule", module, state); } ArrayList<IToken> importedModules = new ArrayList<IToken>(); ILocalScope localScope = null; int line = state.getLine(); int col = state.getCol(); if (state.getLocalImportsGotten() == false) { //in the first analyzed module, we have to get the local imports too. state.setLocalImportsGotten(true); if (module != null && line >= 0) { localScope = module.getLocalScope(line, col); if (localScope != null) { importedModules.addAll(localScope.getLocalImportedModules(line + 1, col + 1, module.getName())); } } } IToken[] builtinsCompletions = getBuiltinsCompletions(state); if (builtinsCompletions != null) { return builtinsCompletions; } String act = state.getActivationToken(); int parI = act.indexOf('('); if (parI != -1) { state.setFullActivationToken(act); act = act.substring(0, parI); state.setActivationToken(act); state.setLookingFor(ICompletionState.LOOKING_FOR_INSTANCED_VARIABLE); } if (module != null) { //get the tokens (global, imported and wild imported) IToken[] globalTokens = module.getGlobalTokens(); List<IToken> tokenImportedModules = Arrays.asList(module.getTokenImportedModules()); importedModules.addAll(tokenImportedModules); state.setTokenImportedModules(importedModules); IToken[] wildImportedModules = module.getWildImportedModules(); //now, lets check if this is actually a module that is an __init__ (if so, we have to get all //the other .py files as modules that are in the same level as the __init__) Set<IToken> initial = new HashSet<IToken>(); if (searchSameLevelMods) { //now, we have to ask for the module if it's a 'package' (folders that have __init__.py for python //or only folders -- not classes -- in java). if (module.isPackage()) { HashSet<IToken> gotten = new HashSet<IToken>(); //the module also decides how to get its submodules getAbsoluteImportTokens(module.getPackageFolderName(), gotten, IToken.TYPE_IMPORT, true, null, false); for (IToken token : gotten) { if (token.getRepresentation().equals("__init__") == false) { initial.add(token); } } } } if (state.getActivationToken().length() == 0) { List<IToken> completions = getGlobalCompletions(globalTokens, importedModules.toArray(EMPTY_ITOKEN_ARRAY), wildImportedModules, state, module); //now find the locals for the module if (line >= 0) { IToken[] localTokens = module.getLocalTokens(line, col, localScope); for (int i = 0; i < localTokens.length; i++) { completions.add(localTokens[i]); } } completions.addAll(initial); //just add all that are in the same level if it was an __init__ return completions.toArray(EMPTY_ITOKEN_ARRAY); } else { //ok, we have a token, find it and get its completions. //first check if the token is a module... if it is, get the completions for that module. IToken[] tokens = findTokensOnImportedMods(importedModules.toArray(EMPTY_ITOKEN_ARRAY), state, module); if (tokens != null && tokens.length > 0) { return decorateWithLocal(tokens, localScope, state); } //if it is an __init__, modules on the same level are treated as local tokens if (searchSameLevelMods) { tokens = searchOnSameLevelMods(initial, state); if (tokens != null && tokens.length > 0) { return decorateWithLocal(tokens, localScope, state); } } //for wild imports, we must get the global completions with __all__ filtered //wild imports: recursively go and get those completions and see if any matches it. for (int i = 0; i < wildImportedModules.length; i++) { IToken name = wildImportedModules[i]; IModule mod = getModule(name.getAsRelativeImport(module.getName()), state.getNature(), false); //relative (for wild imports this is ok... only a module can be used in wild imports) if (mod == null) { mod = getModule(name.getOriginalRep(), state.getNature(), false); //absolute } if (mod != null) { state.checkFindModuleCompletionsMemory(mod, state.getActivationToken()); IToken[] completionsForModule = getCompletionsForModule(mod, state); if (completionsForModule.length > 0) return decorateWithLocal(completionsForModule, localScope, state); } else { //"Module not found:" + name.getRepresentation() } } //it was not a module (would have returned already), so, try to get the completions for a global token defined. tokens = module.getGlobalTokens(state, this); if (tokens.length > 0) { return decorateWithLocal(tokens, localScope, state); } //If it was still not found, go to builtins. IModule builtinsMod = getBuiltinMod(state.getNature()); if (builtinsMod != null && builtinsMod != module) { tokens = getCompletionsForModule(builtinsMod, state); if (tokens.length > 0) { if (tokens[0].getRepresentation().equals("ERROR:") == false) { return decorateWithLocal(tokens, localScope, state); } } } if (lookForArgumentCompletion && localScope != null) { //now, if we have to look for arguments and search things in the local scope, let's also //check for assert (isinstance...) in this scope with the given variable. List<String> lookForClass = localScope.getPossibleClassesForActivationToken(state .getActivationToken()); if (lookForClass.size() > 0) { HashSet<IToken> hashSet = new HashSet<IToken>(); getCompletionsForClassInLocalScope(module, state, searchSameLevelMods, lookForArgumentCompletion, lookForClass, hashSet); if (hashSet.size() > 0) { return hashSet.toArray(EMPTY_ITOKEN_ARRAY); } } //ok, didn't find in assert isinstance... keep going //if there was no assert for the class, get from extensions / local scope interface tokens = CompletionParticipantsHelper.getCompletionsForMethodParameter(state, localScope).toArray( EMPTY_ITOKEN_ARRAY); if (tokens != null && tokens.length > 0) { return tokens; } } //nothing worked so far, so, let's look for an assignment... return getAssignCompletions(module, state, lookForArgumentCompletion, localScope); } } else { Log.log("Module passed in is null!!"); } return EMPTY_ITOKEN_ARRAY; } private IToken[] decorateWithLocal(IToken[] tokens, ILocalScope localScope, ICompletionState state) { if (localScope != null) { Collection<IToken> interfaceForLocal = localScope.getInterfaceForLocal(state.getActivationToken()); if (interfaceForLocal != null && interfaceForLocal.size() > 0) { IToken[] ret = new IToken[tokens.length + interfaceForLocal.size()]; Object[] array = interfaceForLocal.toArray(); System.arraycopy(array, 0, ret, 0, array.length); System.arraycopy(tokens, 0, ret, array.length, tokens.length); return ret; } } return tokens; } private IToken[] getAssignCompletions(IModule module, ICompletionState state, boolean lookForArgumentCompletion, ILocalScope localScope) { AssignCompletionInfo assignCompletions = assignAnalysis.getAssignCompletions(this, module, state); boolean useExtensions = assignCompletions.completions.size() == 0; if (lookForArgumentCompletion && localScope != null && assignCompletions.completions.size() == 0 && assignCompletions.defs.length > 0) { //Now, if a definition found was available in the same scope we started on, let's add the //tokens that are available from that scope. for (Definition d : assignCompletions.defs) { if (d.module.equals(module) && localScope.equals(d.scope)) { Collection<IToken> interfaceForLocal = localScope.getInterfaceForLocal(state.getActivationToken()); assignCompletions.completions.addAll(interfaceForLocal); break; } } } if (useExtensions && localScope != null) { assignCompletions.completions.addAll(CompletionParticipantsHelper.getCompletionsForTokenWithUndefinedType( state, localScope)); } return assignCompletions.completions.toArray(EMPTY_ITOKEN_ARRAY); } /** * @see ICodeCompletionASTManager#getCompletionsForClassInLocalScope(IModule, ICompletionState, boolean, boolean, List, HashSet) */ public void getCompletionsForClassInLocalScope(IModule module, ICompletionState state, boolean searchSameLevelMods, boolean lookForArgumentCompletion, List<String> lookForClass, HashSet<IToken> hashSet) throws CompletionRecursionException { IToken[] tokens; //if found here, it's an instanced variable (force it and restore if we didn't find it here...) ICompletionState stateCopy = state.getCopy(); int prevLookingFor = stateCopy.getLookingFor(); //force looking for instance stateCopy.setLookingFor(ICompletionState.LOOKING_FOR_INSTANCED_VARIABLE, true); for (String classFound : lookForClass) { stateCopy.setLocalImportsGotten(false); stateCopy.setActivationToken(classFound); //same thing as the initial request, but with the class we could find... tokens = getCompletionsForModule(module, stateCopy, searchSameLevelMods, lookForArgumentCompletion); if (tokens != null) { for (IToken tok : tokens) { hashSet.add(tok); } } } if (hashSet.size() == 0) { //force looking for what was set before... stateCopy.setLookingFor(prevLookingFor, true); } } /** * Attempt to search on modules on the same level as this one (this will only happen if we are in an __init__ * module (otherwise, the initial set will be empty) * * @param initial this is the set of tokens generated from modules in the same level * @param state the current state of the completion * * @return a list of tokens found. * @throws CompletionRecursionException */ protected IToken[] searchOnSameLevelMods(Set<IToken> initial, ICompletionState state) throws CompletionRecursionException { IToken[] ret = null; Tuple<IModule, IModulesManager> modUsed = null; String actTokUsed = null; for (IToken token : initial) { //ok, maybe it was from the set that is in the same level as this one (this will only happen if we are on an __init__ module) String rep = token.getRepresentation(); if (state.getActivationToken().startsWith(rep)) { String absoluteImport = token.getAsAbsoluteImport(); modUsed = modulesManager.getModuleAndRelatedModulesManager(absoluteImport, state.getNature(), true, false); IModule sameLevelMod = null; if (modUsed != null) { sameLevelMod = modUsed.o1; } if (sameLevelMod == null) { return null; } String qualifier = state.getActivationToken().substring(rep.length()); if (state.getActivationToken().equals(rep)) { actTokUsed = ""; } else if (qualifier.startsWith(".")) { actTokUsed = qualifier.substring(1); } if (actTokUsed != null) { ICompletionState copy = state.getCopyWithActTok(actTokUsed); copy.setBuiltinsGotten(true); //we don't want builtins... ret = getCompletionsForModule(sameLevelMod, copy); break; } } } return ret; } /** * @see ICodeCompletionASTManager#getGlobalCompletions */ public List<IToken> getGlobalCompletions(IToken[] globalTokens, IToken[] importedModules, IToken[] wildImportedModules, ICompletionState state, IModule current) { if (DebugSettings.DEBUG_CODE_COMPLETION) { log("getGlobalCompletions", current, state); } List<IToken> completions = new ArrayList<IToken>(); //in completion with nothing, just go for what is imported and global tokens. for (int i = 0; i < globalTokens.length; i++) { completions.add(globalTokens[i]); } //now go for the token imports for (int i = 0; i < importedModules.length; i++) { completions.add(importedModules[i]); } if (!state.getBuiltinsGotten()) { state.setBuiltinsGotten(true); if (DebugSettings.DEBUG_CODE_COMPLETION) { Log.toLogFile(this, "getBuiltinCompletions"); } //last thing: get completions from module __builtin__ getBuiltinCompletions(state, completions); if (DebugSettings.DEBUG_CODE_COMPLETION) { Log.toLogFile(this, "END getBuiltinCompletions"); } } //wild imports: recursively go and get those completions. Must be done before getting the builtins, because //when we do a wild import, we may get tokens that are filtered, and there's a chance that the builtins get //filtered out if they are gotten from a wild import and not from the module itself. for (int i = 0; i < wildImportedModules.length; i++) { //for wild imports, we must get the global completions with __all__ filtered IToken name = wildImportedModules[i]; getCompletionsForWildImport(state, current, completions, name); } return completions; } /** * @return the builtin completions */ public List<IToken> getBuiltinCompletions(ICompletionState state, List<IToken> completions) { IPythonNature nature = state.getNature(); IToken[] builtinCompletions = getBuiltinComps(nature); if (builtinCompletions != null) { for (int i = 0; i < builtinCompletions.length; i++) { completions.add(builtinCompletions[i]); } } return completions; } /** * @return the tokens in the builtins */ protected IToken[] getBuiltinComps(IPythonNature nature) { return nature.getBuiltinCompletions(); } /** * @return the module that represents the builtins */ protected IModule getBuiltinMod(IPythonNature nature) { return nature.getBuiltinMod(); } /** * Resolves a token defined with 'from module import something' statement * to a proper type, as defined in module. * @param imported the token to resolve. * @return the resolved token or the original token in case no additional information could be obtained. * @throws CompletionRecursionException */ public ImmutableTuple<IModule, IToken> resolveImport(ICompletionState state, final IToken imported, IModule current) throws CompletionRecursionException { String currModName = imported.getParentPackage(); Tuple3<IModule, String, IToken> modTok = findOnImportedMods(new IToken[] { imported }, state.getCopyWithActTok(imported.getRepresentation()), currModName, current); if (modTok != null && modTok.o1 != null) { if (modTok.o2.length() == 0) { return new ImmutableTuple<IModule, IToken>(current, imported); //it's a module actually, so, no problems... } else { try { state.checkResolveImportMemory(modTok.o1, modTok.o2); } catch (CompletionRecursionException e) { return new ImmutableTuple<IModule, IToken>(current, imported); } IToken repInModule = getRepInModule(modTok.o1, modTok.o2, state.getNature(), state); if (repInModule != null) { return new ImmutableTuple<IModule, IToken>(modTok.o1, repInModule); } } } return new ImmutableTuple<IModule, IToken>(current, imported); } /** * This is the public interface * @throws CompletionRecursionException * @see org.python.pydev.core.ICodeCompletionASTManager#getRepInModule(org.python.pydev.core.IModule, java.lang.String, org.python.pydev.core.IPythonNature) */ public IToken getRepInModule(IModule module, String tokName, IPythonNature nature) throws CompletionRecursionException { return getRepInModule(module, tokName, nature, null); } /** * Get the actual token representing the tokName in the passed module * @param module the module where we're looking * @param tokName the name of the token we're looking for * @param nature the nature we're looking for * @return the actual token in the module (or null if it was not possible to find it). * @throws CompletionRecursionException */ private IToken getRepInModule(IModule module, String tokName, IPythonNature nature, ICompletionState state) throws CompletionRecursionException { if (module != null) { if (tokName.startsWith(".")) { tokName = tokName.substring(1); } //ok, we are getting some token from the module... let's see if it is really available. String[] headAndTail = FullRepIterable.headAndTail(tokName); String actToken = headAndTail[0]; //tail (if os.path, it is os) String hasToBeFound = headAndTail[1]; //head (it is path) //if it was os.path: //initial would be os.path //foundAs would be os //actToken would be path //now, what we will do is try to do a code completion in os and see if path is found if (state == null) { state = CompletionStateFactory.getEmptyCompletionState(actToken, nature, new CompletionCache()); } else { state = state.getCopy(); state.setActivationToken(actToken); } IToken[] completionsForModule = getCompletionsForModule(module, state); int len = completionsForModule.length; for (int i = 0; i < len; i++) { IToken foundTok = completionsForModule[i]; if (foundTok.getRepresentation().equals(hasToBeFound)) { return foundTok; } } } return null; } /* (non-Javadoc) * @see ICodeCompletionASTManager#getCompletionsForWildImport(ICompletionState, IModule, List, IToken) */ public boolean getCompletionsForWildImport(ICompletionState state, IModule current, List<IToken> completions, IToken name) { try { //this one is an exception... even though we are getting the name as a relative import, we say it //is not because we want to get the module considering __init__ IModule mod = null; if (current != null) { //we cannot get the relative path if we don't have a current module mod = getModule(name.getAsRelativeImport(current.getName()), state.getNature(), false); } if (mod == null) { mod = getModule(name.getOriginalRep(), state.getNature(), false); //absolute import } if (mod != null) { state.checkWildImportInMemory(current, mod); IToken[] completionsForModule = getCompletionsForModule(mod, state, true, false, true); for (IToken token : completionsForModule) { completions.add(token); } return true; } else { //"Module not found:" + name.getRepresentation() } } catch (CompletionRecursionException e) { //probably found a recursion... let's return the tokens we have so far } return false; } public IToken[] findTokensOnImportedMods(IToken[] importedModules, ICompletionState state, IModule current) throws CompletionRecursionException { Tuple3<IModule, String, IToken> o = findOnImportedMods(importedModules, state, current.getName(), current); if (o == null) return null; IModule mod = o.o1; String tok = o.o2; String tokForSearchInOtherModule = getTokToSearchInOtherModule(o); if (tok.length() == 0) { //the activation token corresponds to an imported module. We have to get its global tokens and return them. ICompletionState copy = state.getCopy(); copy.setActivationToken(""); copy.setBuiltinsGotten(true); //we don't want builtins... return getCompletionsForModule(mod, copy); } else if (mod != null) { ICompletionState copy = state.getCopy(); copy.setActivationToken(tokForSearchInOtherModule); copy.setCol(-1); copy.setLine(-1); copy.raiseNFindTokensOnImportedModsCalled(mod, tokForSearchInOtherModule); String parentPackage = o.o3.getParentPackage(); if (parentPackage.trim().length() > 0 && parentPackage.equals(current.getName()) && state.getActivationToken().equals(tok) && !parentPackage.endsWith("__init__")) { String name = mod.getName(); if (name.endsWith(".__init__")) { name = name.substring(0, name.length() - 9); } if (o.o3.getAsAbsoluteImport().startsWith(name)) { if (current.isInDirectGlobalTokens(tok, state)) { return null; } } } return getCompletionsForModule(mod, copy); } return null; } /** * When we have an import, we have one token which we used to find it and another which is the * one we refer to at the current module. This method will get the way it's referred at the * actual module and not at the current module (at the current module it's modTok.o2). */ public static String getTokToSearchInOtherModule(Tuple3<IModule, String, IToken> modTok) { String tok = modTok.o2; String tokForSearchInOtherModule = tok; if (tok.length() > 0) { IToken sourceToken = modTok.o3; if (sourceToken instanceof SourceToken) { SourceToken sourceToken2 = (SourceToken) sourceToken; if (sourceToken2.getAst() instanceof ImportFrom) { ImportFrom importFrom = (ImportFrom) sourceToken2.getAst(); if (importFrom.names.length > 0 && importFrom.names[0].asname != null) { String originalRep = sourceToken.getOriginalRep(); tokForSearchInOtherModule = FullRepIterable.getLastPart(originalRep); } } } } return tokForSearchInOtherModule; } /** * @param activationToken * @param importedModules * @param module * @return tuple with: * 0: mod * 1: tok * @throws CompletionRecursionException */ public Tuple3<IModule, String, IToken> findOnImportedMods(ICompletionState state, IModule current) throws CompletionRecursionException { IToken[] importedModules = current.getTokenImportedModules(); return findOnImportedMods(importedModules, state, current.getName(), current); } /** * This function tries to find some activation token defined in some imported module. * @return tuple with: the module and the token that should be used from it. * * @param this is the activation token we have. It may be a single token or some dotted name. * * If it is a dotted name, such as testcase.TestCase, we need to match against some import * represented as testcase or testcase.TestCase. * * If a testcase.TestCase matches against some import named testcase, the import is returned and * the TestCase is put as the module * * 0: mod * 1: tok (string) * 2: actual tok * @throws CompletionRecursionException */ public Tuple3<IModule, String, IToken> findOnImportedMods(IToken[] importedModules, ICompletionState state, String currentModuleName, IModule current) throws CompletionRecursionException { FullRepIterable iterable = new FullRepIterable(state.getActivationToken(), true); for (String tok : iterable) { for (IToken importedModule : importedModules) { final String modRep = importedModule.getRepresentation(); //this is its 'real' representation (alias) on the file (if it is from xxx import a as yyy, it is yyy) if (modRep.equals(tok)) { String act = state.getActivationToken(); Tuple<IModule, String> r; try { r = findOnImportedMods(importedModule, tok, state, act, currentModuleName, current); if (r != null) { return new Tuple3<IModule, String, IToken>(r.o1, r.o2, importedModule); } //Note, if r==null, even though the name matched, keep on going (to handle cases of //try..except ImportError, as we cannot be sure of which version will actually match). } catch (MisconfigurationException e) { Log.log(e); } } } } return null; } public Tuple<IModule, String> findModule(String moduleToFind, String currentModule, ICompletionState state, IModule current) throws CompletionRecursionException, MisconfigurationException { NameTok name = new NameTok(moduleToFind, NameTok.ImportModule); Import impTok = new Import(new aliasType[] { new aliasType(name, null) }); List<IToken> tokens = new ArrayList<IToken>(); List<IToken> imp = AbstractVisitor.makeImportToken(impTok, tokens, currentModule, true); IToken importedModule = imp.get(imp.size() - 1); //get the last one (it's the one with the 'longest' representation). return this.findOnImportedMods(importedModule, "", state, "", currentModule, current); } /** * Checks if some module can be resolved and returns the module it is resolved to (and to which token). * @throws CompletionRecursionException * @throws MisconfigurationException * */ public Tuple<IModule, String> findOnImportedMods(IToken importedModule, String tok, ICompletionState state, String activationToken, String currentModuleName, IModule current) throws CompletionRecursionException, MisconfigurationException { Tuple<IModule, String> modTok = null; IModule mod = null; //ok, check if it is a token for the new import IPythonNature nature = state.getNature(); if (importedModule instanceof SourceToken) { SourceToken token = (SourceToken) importedModule; if (token.isImportFrom()) { ImportFrom importFrom = (ImportFrom) token.getAst(); int level = importFrom.level; if (level > 0) { //ok, it must be treated as a relative import //ok, it is the import added on python 2.5 (from .. import xxx) String parentPackage = token.getParentPackage(); List<String> moduleParts = StringUtils.dotSplit(parentPackage); String relative = null; if (moduleParts.size() > level) { relative = FullRepIterable.joinParts(moduleParts, moduleParts.size() - level); } String modName = ((NameTok) importFrom.module).id; if (modName.length() > 0) { //ok, we have to add the other part too, as we have more than the leading dots //from ..bar import relative += "." + modName; } if (!AbstractVisitor.isWildImport(importFrom)) { tok = FullRepIterable.getLastPart(token.originalRep); relative += "." + tok; } modTok = findModuleFromPath(relative, nature, false, null); mod = modTok.o1; if (checkValidity(currentModuleName, mod)) { Tuple<IModule, String> ret = fixTok(modTok, tok, activationToken); return ret; } //ok, it is 'forced' as relative import because it has a level, so, it MUST return here return null; } } } boolean isAbsoluteImportEnabledx = this.isAbsoluteImportEnabled(current, nature); String asRelativeImport = ""; if (!isAbsoluteImportEnabledx) { //check as relative with complete rep asRelativeImport = importedModule.getAsRelativeImport(currentModuleName); if (!asRelativeImport.startsWith(".")) { modTok = findModuleFromPath(asRelativeImport, nature, true, currentModuleName); mod = modTok.o1; if (checkValidity(currentModuleName, mod)) { Tuple<IModule, String> ret = fixTok(modTok, tok, activationToken); return ret; } } } //check if the import actually represents some token in an __init__ file String originalWithoutRep = importedModule.getOriginalWithoutRep(); if (originalWithoutRep.length() > 0) { if (!originalWithoutRep.endsWith("__init__")) { originalWithoutRep = originalWithoutRep + ".__init__"; } modTok = findModuleFromPath(originalWithoutRep, nature, true, null); mod = modTok.o1; if (modTok.o2.endsWith("__init__") == false && checkValidity(currentModuleName, mod)) { if (mod.isInGlobalTokens(importedModule.getRepresentation(), nature, false, state)) { //then this is the token we're looking for (otherwise, it might be a module). Tuple<IModule, String> ret = fixTok(modTok, tok, activationToken); if (ret.o2.length() == 0) { ret.o2 = importedModule.getRepresentation(); } else { ret.o2 = importedModule.getRepresentation() + "." + ret.o2; } return ret; } } } //the most 'simple' case: check as absolute with original rep modTok = findModuleFromPath(importedModule.getOriginalRep(), nature, false, null); mod = modTok.o1; if (checkValidity(currentModuleName, mod)) { Tuple<IModule, String> ret = fixTok(modTok, tok, activationToken); return ret; } if (!isAbsoluteImportEnabledx) { //ok, one last shot, to see a relative looking in folders __init__ modTok = findModuleFromPath(asRelativeImport, nature, false, null); mod = modTok.o1; if (checkValidity(currentModuleName, mod, true)) { Tuple<IModule, String> ret = fixTok(modTok, tok, activationToken); //now let's see if what we did when we found it as a relative import is correct: //if we didn't find it in an __init__ module, all should be ok if (!mod.getName().endsWith("__init__")) { return ret; } //otherwise, we have to be more cautious... //if the activation token is empty, then it is the module we were looking for //if it is not the initial token we were looking for, it is correct //if it is in the global tokens of the found module it is correct //if none of this situations was found, we probably just found the same token we had when we started (unless I'm mistaken...) else if (activationToken.length() == 0 || ret.o2.equals(activationToken) == false || mod.isInGlobalTokens(activationToken, nature, false, state)) { return ret; } } } return null; } protected boolean checkValidity(String currentModuleName, IModule mod) { return checkValidity(currentModuleName, mod, false); } /** * @param isRelative: On a relative import we have to check some more conditions... */ protected boolean checkValidity(String currentModuleName, IModule mod, boolean isRelative) { if (mod == null) { return false; } String modName = mod.getName(); if (modName == null) { return true; } //still in the same module if (modName.equals(currentModuleName)) { return false; } if (isRelative && currentModuleName != null && modName.endsWith(".__init__")) { //we have to check it without the __init__ //what happens here is that considering the structure: // // xxx.__init__ // xxx.mod1 // // we cannot have tokens from the mod1 getting __init__ String withoutLastPart = FullRepIterable.getWithoutLastPart(modName); String currentWithoutLastPart = FullRepIterable.getWithoutLastPart(currentModuleName); if (currentWithoutLastPart.equals(withoutLastPart)) { return false; } } return true; } /** * Fixes the token if we found a module that was just a substring from the initial activation token. * * This means that if we had testcase.TestCase and found it as TestCase, the token is added with TestCase */ protected Tuple<IModule, String> fixTok(Tuple<IModule, String> modTok, String tok, String activationToken) { if (activationToken.length() > tok.length() && activationToken.startsWith(tok)) { String toAdd = activationToken.substring(tok.length() + 1); if (modTok.o2.length() == 0) { modTok.o2 = toAdd; } else { modTok.o2 += "." + toAdd; } } return modTok; } /** * This function receives a path (rep) and extracts a module from that path. * First it tries with the full path, and them removes a part of the final of * that path until it finds the module or the path is empty. * * @param currentModuleName this is the module name (used to check validity for relative imports) -- not used if dontSearchInit is false * if this parameter is not null, it means we're looking for a relative import. When checking for relative imports, * we should only check the modules that are directly under this project (so, we should not check the whole pythonpath for * it, just direct modules) * * @return tuple with found module and the String removed from the path in * order to find the module. */ public Tuple<IModule, String> findModuleFromPath(String rep, IPythonNature nature, boolean dontSearchInit, String currentModuleName) { String tok = ""; boolean lookingForRelative = currentModuleName != null; IModule mod = getModule(rep, nature, dontSearchInit, lookingForRelative); String mRep = rep; int index; while (mod == null && (index = mRep.lastIndexOf('.')) != -1) { tok = mRep.substring(index + 1) + "." + tok; mRep = mRep.substring(0, index); if (mRep.length() > 0) { mod = getModule(mRep, nature, dontSearchInit, lookingForRelative); } } if (tok.endsWith(".")) { tok = tok.substring(0, tok.length() - 1); //remove last point if found. } if (dontSearchInit && currentModuleName != null && mod != null) { String parentModule = FullRepIterable.getParentModule(currentModuleName); //if we are looking for some relative import token, it can only match if the name found is not less than the parent //of the current module because of the way in that relative imports are meant to be written. //if it equal, it should not match either, as it was found as the parent module... this can not happen because it must find //it with __init__ if it was the parent module if (mod.getName().length() <= parentModule.length()) { return new Tuple<IModule, String>(null, null); } } return new Tuple<IModule, String>((AbstractModule) mod, tok); } }