/**
* 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.
*/
/*
* Created on 07/09/2005
*/
package com.python.pydev.codecompletion.ctxinsensitive;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;
import org.eclipse.jface.text.contentassist.ICompletionProposal;
import org.eclipse.jface.text.contentassist.IContextInformation;
import org.python.pydev.core.FullRepIterable;
import org.python.pydev.core.ICodeCompletionASTManager;
import org.python.pydev.core.ICompletionCache;
import org.python.pydev.core.ICompletionState;
import org.python.pydev.core.IDefinition;
import org.python.pydev.core.ILocalScope;
import org.python.pydev.core.IModule;
import org.python.pydev.core.IPythonNature;
import org.python.pydev.core.IToken;
import org.python.pydev.core.MisconfigurationException;
import org.python.pydev.core.docutils.PySelection.ActivationTokenAndQual;
import org.python.pydev.core.log.Log;
import org.python.pydev.core.structure.CompletionRecursionException;
import org.python.pydev.editor.codecompletion.CompletionRequest;
import org.python.pydev.editor.codecompletion.IPyDevCompletionParticipant;
import org.python.pydev.editor.codecompletion.IPyDevCompletionParticipant2;
import org.python.pydev.editor.codecompletion.IPyDevCompletionParticipant3;
import org.python.pydev.editor.codecompletion.ProposalsComparator.CompareContext;
import org.python.pydev.editor.codecompletion.PyCodeCompletionPreferencesPage;
import org.python.pydev.editor.codecompletion.PyCodeCompletionUtils;
import org.python.pydev.editor.codecompletion.PyCodeCompletionUtils.IFilter;
import org.python.pydev.editor.codecompletion.revisited.modules.SourceToken;
import org.python.pydev.editor.codecompletion.revisited.visitors.Definition;
import org.python.pydev.editor.model.ItemPointer;
import org.python.pydev.parser.jython.ast.FunctionDef;
import org.python.pydev.parser.jython.ast.Name;
import org.python.pydev.parser.jython.ast.decoratorsType;
import org.python.pydev.parser.visitors.NodeUtils;
import org.python.pydev.plugin.nature.SystemPythonNature;
import org.python.pydev.shared_core.string.FastStringBuffer;
import org.python.pydev.shared_core.structure.FastStack;
import org.python.pydev.shared_core.structure.LinkedListWarningOnSlowOperations;
import org.python.pydev.shared_interactive_console.console.ui.IScriptConsoleViewer;
import org.python.pydev.shared_ui.proposals.IPyCompletionProposal;
import com.python.pydev.analysis.AnalysisPlugin;
import com.python.pydev.analysis.CtxInsensitiveImportComplProposal;
import com.python.pydev.analysis.additionalinfo.AbstractAdditionalTokensInfo;
import com.python.pydev.analysis.additionalinfo.AdditionalProjectInterpreterInfo;
import com.python.pydev.analysis.additionalinfo.AdditionalSystemInterpreterInfo;
import com.python.pydev.analysis.additionalinfo.IInfo;
import com.python.pydev.analysis.ui.AutoImportsPreferencesPage;
import com.python.pydev.codecompletion.ui.CodeCompletionPreferencesPage;
/**
* Provides the completions in a context-insensitive way for classes and methods (both for the editor or the console).
*
* @author Fabio
*/
public class CtxParticipant
implements IPyDevCompletionParticipant, IPyDevCompletionParticipant2, IPyDevCompletionParticipant3 {
// Console completions ---------------------------------------------------------------------------------------------
/**
* IPyDevCompletionParticipant2
*/
@Override
public Collection<ICompletionProposal> computeConsoleCompletions(ActivationTokenAndQual tokenAndQual,
Set<IPythonNature> naturesUsed, IScriptConsoleViewer viewer, int requestOffset) {
List<ICompletionProposal> completions = new ArrayList<ICompletionProposal>();
if (tokenAndQual.activationToken != null && tokenAndQual.activationToken.length() > 0) {
//we only want
return completions;
}
String qual = tokenAndQual.qualifier;
if (qual.length() >= CodeCompletionPreferencesPage.getCharsForContextInsensitiveGlobalTokensCompletion()
&& naturesUsed != null && naturesUsed.size() > 0) { //at least n characters required...
boolean addAutoImport = AutoImportsPreferencesPage.doAutoImport();
int qlen = qual.length();
boolean useSubstringMatchInCodeCompletion = PyCodeCompletionPreferencesPage
.getUseSubstringMatchInCodeCompletion();
IFilter nameFilter = PyCodeCompletionUtils.getNameFilter(useSubstringMatchInCodeCompletion, qual);
for (IPythonNature nature : naturesUsed) {
AbstractAdditionalTokensInfo additionalInfo;
try {
if (nature instanceof SystemPythonNature) {
SystemPythonNature systemPythonNature = (SystemPythonNature) nature;
additionalInfo = AdditionalSystemInterpreterInfo.getAdditionalSystemInfo(
systemPythonNature.getRelatedInterpreterManager(),
systemPythonNature.getProjectInterpreter().getExecutableOrJar());
fillNatureCompletionsForConsole(viewer, requestOffset, completions, qual, addAutoImport, qlen,
nameFilter, nature, additionalInfo, useSubstringMatchInCodeCompletion);
} else {
additionalInfo = AdditionalProjectInterpreterInfo.getAdditionalInfoForProject(nature);
fillNatureCompletionsForConsole(viewer, requestOffset, completions, qual, addAutoImport, qlen,
nameFilter, nature, additionalInfo, useSubstringMatchInCodeCompletion);
}
} catch (MisconfigurationException e) {
Log.log(e);
}
}
}
return completions;
}
private void fillNatureCompletionsForConsole(IScriptConsoleViewer viewer, int requestOffset,
List<ICompletionProposal> completions, String qual, boolean addAutoImport, int qlen, IFilter nameFilter,
IPythonNature nature, AbstractAdditionalTokensInfo additionalInfo,
boolean useSubstringMatchInCodeCompletion) {
Collection<IInfo> tokensStartingWith;
if (useSubstringMatchInCodeCompletion) {
tokensStartingWith = additionalInfo.getTokensStartingWith("",
AbstractAdditionalTokensInfo.TOP_LEVEL);
} else {
tokensStartingWith = additionalInfo.getTokensStartingWith(qual,
AbstractAdditionalTokensInfo.TOP_LEVEL);
}
FastStringBuffer realImportRep = new FastStringBuffer();
FastStringBuffer displayString = new FastStringBuffer();
FastStringBuffer tempBuf = new FastStringBuffer();
boolean doIgnoreImportsStartingWithUnder = AutoImportsPreferencesPage.doIgnoreImportsStartingWithUnder();
CompareContext compareContext = new CompareContext(nature);
for (IInfo info : tokensStartingWith) {
//there always must be a declaringModuleName
String declaringModuleName = info.getDeclaringModuleName();
boolean hasInit = false;
if (declaringModuleName.endsWith(".__init__")) {
declaringModuleName = declaringModuleName.substring(0, declaringModuleName.length() - 9);//remove the .__init__
hasInit = true;
}
String rep = info.getName();
if (!nameFilter.acceptName(rep)) {
continue;
}
if (addAutoImport) {
realImportRep.clear();
realImportRep.append("from ");
realImportRep.append(AutoImportsPreferencesPage.removeImportsStartingWithUnderIfNeeded(
declaringModuleName, tempBuf, doIgnoreImportsStartingWithUnder));
realImportRep.append(" import ");
realImportRep.append(rep);
}
displayString.clear();
displayString.append(rep);
displayString.append(" - ");
displayString.append(declaringModuleName);
if (hasInit) {
displayString.append(".__init__");
}
String displayAsStr = displayString.toString();
PyConsoleCompletion proposal = new PyConsoleCompletion(rep, requestOffset - qlen, qlen,
realImportRep.length(), AnalysisPlugin.getImageForAutoImportTypeInfo(info),
displayAsStr, (IContextInformation) null, "",
displayAsStr.equals(qual) ? IPyCompletionProposal.PRIORITY_GLOBALS_EXACT
: IPyCompletionProposal.PRIORITY_GLOBALS,
realImportRep.toString(), viewer, compareContext);
completions.add(proposal);
}
}
// Editor completions ----------------------------------------------------------------------------------------------
private Collection<CtxInsensitiveImportComplProposal> getThem(CompletionRequest request, ICompletionState state,
boolean addAutoImport) throws MisconfigurationException {
ArrayList<CtxInsensitiveImportComplProposal> completions = new ArrayList<CtxInsensitiveImportComplProposal>();
if (request.isInCalltip) {
return completions;
}
HashSet<String> importedNames = getImportedNames(state);
String qual = request.qualifier;
if (qual.length() >= CodeCompletionPreferencesPage.getCharsForContextInsensitiveGlobalTokensCompletion()) { //at least n characters required...
IFilter nameFilter = PyCodeCompletionUtils.getNameFilter(request.useSubstringMatchInCodeCompletion, qual);
String initialModule = request.resolveModule();
List<IInfo> tokensStartingWith;
if (request.useSubstringMatchInCodeCompletion) {
tokensStartingWith = AdditionalProjectInterpreterInfo.getTokensStartingWith("",
request.nature, AbstractAdditionalTokensInfo.TOP_LEVEL);
} else {
tokensStartingWith = AdditionalProjectInterpreterInfo.getTokensStartingWith(qual,
request.nature, AbstractAdditionalTokensInfo.TOP_LEVEL);
}
FastStringBuffer realImportRep = new FastStringBuffer();
FastStringBuffer displayString = new FastStringBuffer();
FastStringBuffer tempBuf = new FastStringBuffer();
boolean doIgnoreImportsStartingWithUnder = AutoImportsPreferencesPage.doIgnoreImportsStartingWithUnder();
for (IInfo info : tokensStartingWith) {
//there always must be a declaringModuleName
String declaringModuleName = info.getDeclaringModuleName();
if (initialModule != null && declaringModuleName != null) {
if (initialModule.equals(declaringModuleName)) {
continue;
}
}
boolean hasInit = false;
if (declaringModuleName.endsWith(".__init__")) {
declaringModuleName = declaringModuleName.substring(0, declaringModuleName.length() - 9);//remove the .__init__
hasInit = true;
}
String rep = info.getName();
if (!nameFilter.acceptName(rep) || importedNames.contains(rep)) {
continue;
}
if (addAutoImport) {
realImportRep.clear();
realImportRep.append("from ");
realImportRep.append(AutoImportsPreferencesPage.removeImportsStartingWithUnderIfNeeded(
declaringModuleName, tempBuf, doIgnoreImportsStartingWithUnder));
realImportRep.append(" import ");
realImportRep.append(rep);
}
displayString.clear();
displayString.append(rep);
displayString.append(" - ");
displayString.append(declaringModuleName);
if (hasInit) {
displayString.append(".__init__");
}
String displayAsStr = displayString.toString();
CtxInsensitiveImportComplProposal proposal = new CtxInsensitiveImportComplProposal(rep,
request.documentOffset - request.qlen, request.qlen, realImportRep.length(),
AnalysisPlugin.getImageForAutoImportTypeInfo(info), displayAsStr,
(IContextInformation) null, "",
displayAsStr.equals(qual) ? IPyCompletionProposal.PRIORITY_GLOBALS_EXACT
: IPyCompletionProposal.PRIORITY_GLOBALS,
realImportRep.toString(),
new CompareContext(info.getNature()));
completions.add(proposal);
}
}
return completions;
}
/**
* @return the names that are already imported in the current document
*/
private HashSet<String> getImportedNames(ICompletionState state) {
List<IToken> tokenImportedModules = state.getTokenImportedModules();
HashSet<String> importedNames = new HashSet<String>();
if (tokenImportedModules != null) {
for (IToken token : tokenImportedModules) {
importedNames.add(token.getRepresentation());
}
}
return importedNames;
}
@Override
@SuppressWarnings({ "unchecked", "rawtypes" })
public Collection getGlobalCompletions(CompletionRequest request, ICompletionState state)
throws MisconfigurationException {
return getThem(request, state, AutoImportsPreferencesPage.doAutoImport());
}
@Override
public IDefinition findDefinitionForMethodParameter(Definition d, IPythonNature nature,
ICompletionCache completionCache) {
if (d.ast instanceof Name) {
Name name = (Name) d.ast;
if (name.ctx == Name.Param) {
if (d.scope != null && !d.scope.getScopeStack().empty()) {
Object peek = d.scope.getScopeStack().peek();
if (peek instanceof FunctionDef) {
FunctionDef functionDef = (FunctionDef) peek;
String representationString = NodeUtils.getRepresentationString(functionDef);
if (representationString != null && representationString.startsWith("test")) {
ItemPointer itemPointer = findItemPointerFromPyTestFixture(nature, completionCache,
name.id);
if (itemPointer != null) {
return itemPointer.definition;
}
}
}
}
}
}
return null;
}
private ItemPointer findItemPointerFromPyTestFixture(IPythonNature nature, ICompletionCache completionCache,
String fixtureName) {
try {
ICodeCompletionASTManager astManager = nature.getAstManager();
if (astManager != null) {
List<IInfo> tokensEqualTo = AdditionalProjectInterpreterInfo.getTokensEqualTo(
fixtureName, nature, AdditionalProjectInterpreterInfo.TOP_LEVEL);
for (IInfo iInfo : tokensEqualTo) {
List<ItemPointer> pointers = new LinkedListWarningOnSlowOperations<>();
AnalysisPlugin.getDefinitionFromIInfo(pointers, astManager, nature, iInfo,
completionCache);
for (ItemPointer itemPointer : pointers) {
if (itemPointer.definition.ast instanceof FunctionDef) {
FunctionDef functionDef = (FunctionDef) itemPointer.definition.ast;
if (functionDef.decs != null) {
for (decoratorsType dec : functionDef.decs) {
String decoratorFuncName = NodeUtils.getRepresentationString(dec.func);
if (decoratorFuncName != null) {
if (FIXTURE_PATTERN.matcher(decoratorFuncName).find()
|| YIELD_FIXTURE_PATTERN.matcher(decoratorFuncName)
.find()) {
return itemPointer;
}
}
}
}
}
}
}
}
} catch (MisconfigurationException e1) {
Log.log(e1);
}
return null;
}
private final static Pattern FIXTURE_PATTERN = Pattern.compile("\\bfixture\\b");
private final static Pattern YIELD_FIXTURE_PATTERN = Pattern.compile("\\byield_fixture\\b");
/**
* IPyDevCompletionParticipant
* @throws CompletionRecursionException
*/
@Override
public Collection<IToken> getCompletionsForMethodParameter(ICompletionState state, ILocalScope localScope,
Collection<IToken> interfaceForLocal) throws CompletionRecursionException {
ArrayList<IToken> ret = new ArrayList<IToken>();
String qual = state.getQualifier();
String activationToken = state.getActivationToken();
FastStack scopeStack = localScope.getScopeStack();
if (!scopeStack.empty()) {
Object peek = scopeStack.peek();
if (peek instanceof FunctionDef) {
FunctionDef testFuncDef = (FunctionDef) peek;
String representationString = NodeUtils.getRepresentationString(testFuncDef);
if (representationString != null && representationString.startsWith("test")) {
ICodeCompletionASTManager astManager = state.getNature().getAstManager();
if (astManager != null) {
ItemPointer itemPointer = findItemPointerFromPyTestFixture(state.getNature(), state,
activationToken);
if (itemPointer != null) {
List<IToken> completionsFromItemPointer = getCompletionsFromItemPointer(state, astManager,
itemPointer);
if (completionsFromItemPointer != null && completionsFromItemPointer.size() > 0) {
return completionsFromItemPointer;
}
}
}
}
}
}
if (qual.length() >= CodeCompletionPreferencesPage.getCharsForContextInsensitiveGlobalTokensCompletion()) { //at least n characters
// if we have a parameter, do code-completion with all available tokens, since we don't know what's the type which
// may actually be received
boolean useSubstringMatchInCodeCompletion = PyCodeCompletionPreferencesPage
.getUseSubstringMatchInCodeCompletion();
List<IInfo> tokensStartingWith;
if (useSubstringMatchInCodeCompletion) {
IFilter nameFilter = PyCodeCompletionUtils.getNameFilter(useSubstringMatchInCodeCompletion, qual);
try {
tokensStartingWith = AdditionalProjectInterpreterInfo.getTokensStartingWith("", state.getNature(),
AbstractAdditionalTokensInfo.INNER);
} catch (MisconfigurationException e) {
Log.log(e);
return ret;
}
for (IInfo info : tokensStartingWith) {
if (nameFilter.acceptName(info.getName())) {
ret.add(new SourceToken(null, info.getName(), null, null, info.getDeclaringModuleName(),
info.getType(), info.getNature()));
}
}
} else {
try {
tokensStartingWith = AdditionalProjectInterpreterInfo.getTokensStartingWith(qual, state.getNature(),
AbstractAdditionalTokensInfo.INNER);
} catch (MisconfigurationException e) {
Log.log(e);
return ret;
}
for (IInfo info : tokensStartingWith) {
ret.add(new SourceToken(null, info.getName(), null, null, info.getDeclaringModuleName(),
info.getType(), info.getNature()));
}
}
}
return ret;
}
private List<IToken> getCompletionsFromItemPointer(ICompletionState state, ICodeCompletionASTManager astManager,
ItemPointer itemPointer) throws CompletionRecursionException {
int initialLookingFor = state.getLookingFor();
try {
state.setLookingFor(
ICompletionState.LOOKING_FOR_INSTANCED_VARIABLE);
List<IToken> completionFromFuncDefReturn = astManager
.getCompletionFromFuncDefReturn(state,
itemPointer.definition.module,
itemPointer.definition, true);
if (completionFromFuncDefReturn != null
&& completionFromFuncDefReturn.size() > 0) {
return completionFromFuncDefReturn;
}
} finally {
state.setLookingFor(initialLookingFor, true);
}
return null;
}
/**
* IPyDevCompletionParticipant
* @throws MisconfigurationException
*/
@Override
@SuppressWarnings({ "unchecked", "rawtypes" })
public Collection getStringGlobalCompletions(CompletionRequest request, ICompletionState state)
throws MisconfigurationException {
return getThem(request, state, false);
}
@Override
public Collection<Object> getArgsCompletion(ICompletionState state, ILocalScope localScope,
Collection<IToken> interfaceForLocal) {
throw new RuntimeException("Deprecated");
}
@Override
public Collection<IToken> getCompletionsForTokenWithUndefinedType(ICompletionState state, ILocalScope localScope,
Collection<IToken> interfaceForLocal) throws CompletionRecursionException {
return getCompletionsForMethodParameter(state, localScope, interfaceForLocal);
}
@Override
public Collection<IToken> getCompletionsForType(ICompletionState state) throws CompletionRecursionException {
String activationToken = state.getActivationToken();
String qual = activationToken;
String module = null;
if (activationToken.indexOf('.') != -1) {
String[] headAndTail = FullRepIterable.headAndTail(activationToken);
qual = headAndTail[1];
module = headAndTail[0];
}
try {
IPythonNature nature = state.getNature();
List<IInfo> tokensStartingWith = AdditionalProjectInterpreterInfo.getTokensEqualTo(qual,
nature, AbstractAdditionalTokensInfo.TOP_LEVEL | AbstractAdditionalTokensInfo.INNER);
int size = tokensStartingWith.size();
if (size == 0) {
return null;
}
if (size == 1) {
return getCompletionsForIInfo(state, nature, tokensStartingWith.get(0));
}
//If we got here, we have more than 1 choice: let's try to find it given the module.
if (module != null) {
for (int i = 0; i < size; i++) {
IInfo iInfo = tokensStartingWith.get(i);
if (module.equals(iInfo.getDeclaringModuleName())) {
List<IToken> ret = getCompletionsForIInfo(state, nature, iInfo);
if (ret != null && ret.size() > 0) {
return ret; //seems like we found it.
}
}
}
}
//Couldn't find an exact match: go for the type (prefer classes).
ArrayList<IInfo> newList = new ArrayList<IInfo>();
for (int i = 0; i < size; i++) {
IInfo iInfo = tokensStartingWith.get(i);
if (iInfo.getType() == IInfo.CLASS_WITH_IMPORT_TYPE) {
newList.add(iInfo);
}
}
tokensStartingWith = newList;
size = tokensStartingWith.size();
if (size == 0) {
return null;
}
if (size == 1) {
return getCompletionsForIInfo(state, nature, tokensStartingWith.get(0));
}
if (size < 5) { // Don't go for it if we have too many things there!
List<IToken> ret = new ArrayList<IToken>();
for (int i = 0; i < size; i++) {
IInfo iInfo = tokensStartingWith.get(i);
List<IToken> found = getCompletionsForIInfo(state, nature, iInfo);
if (found != null) {
ret.addAll(found);
}
}
return ret;
}
return null; //Too many matches: skip this one (instead of returning something random).
} catch (MisconfigurationException e) {
return null;
}
}
/**
* Gets completions given a module and related info.
*/
private List<IToken> getCompletionsForIInfo(ICompletionState state, IPythonNature nature, IInfo iInfo)
throws CompletionRecursionException {
ICompletionState copy = state.getCopy();
String path = iInfo.getPath();
String act = iInfo.getName();
if (path != null) {
act = path + "." + act;
}
copy.setActivationToken(act);
ICodeCompletionASTManager manager = nature.getAstManager();
IModule mod = manager.getModule(iInfo.getDeclaringModuleName(), nature, true);
if (mod != null) {
state.checkFindDefinitionMemory(mod, iInfo.getDeclaringModuleName() + "." + act);
IToken[] tks = manager.getCompletionsForModule(mod, copy);
if (tks != null) {
return Arrays.asList(tks);
}
}
return null;
}
}