File indexing completed on 2024-05-05 16:41:31

0001 /*
0002     SPDX-FileCopyrightText: 2011-2012 Sven Brauch <svenbrauch@googlemail.com>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "context.h"
0008 
0009 #include "items/keyword.h"
0010 #include "items/importfile.h"
0011 #include "items/functiondeclaration.h"
0012 #include "items/implementfunction.h"
0013 #include "items/missingincludeitem.h"
0014 #include "items/replacementvariable.h"
0015 
0016 #include "worker.h"
0017 #include "helpers.h"
0018 #include "duchain/pythoneditorintegrator.h"
0019 #include "duchain/expressionvisitor.h"
0020 #include "duchain/declarationbuilder.h"
0021 #include "duchain/helpers.h"
0022 #include "duchain/types/unsuretype.h"
0023 #include "duchain/navigation/navigationwidget.h"
0024 #include "parser/astbuilder.h"
0025 
0026 #include <language/duchain/functiondeclaration.h>
0027 #include <language/duchain/classdeclaration.h>
0028 #include <language/duchain/aliasdeclaration.h>
0029 #include <language/duchain/duchainutils.h>
0030 #include <language/util/includeitem.h>
0031 #include <language/codecompletion/normaldeclarationcompletionitem.h>
0032 #include <language/codecompletion/codecompletionitem.h>
0033 #include <language/codecompletion/codecompletionitemgrouper.h>
0034 #include <interfaces/icore.h>
0035 #include <interfaces/iprojectcontroller.h>
0036 #include <interfaces/iproject.h>
0037 #include <interfaces/idocumentcontroller.h>
0038 #include <project/projectmodel.h>
0039 
0040 #include <QProcess>
0041 #include <QRegExp>
0042 #include <KTextEditor/View>
0043 #include <memory>
0044 
0045 #include <QDebug>
0046 #include "codecompletiondebug.h"
0047 
0048 using namespace KTextEditor;
0049 using namespace KDevelop;
0050 
0051 namespace Python {
0052 
0053 PythonCodeCompletionContext::ItemTypeHint PythonCodeCompletionContext::itemTypeHint()
0054 {
0055     return m_itemTypeHint;
0056 }
0057 
0058 PythonCodeCompletionContext::CompletionContextType PythonCodeCompletionContext::completionContextType()
0059 {
0060     return m_operation;
0061 }
0062 
0063 std::unique_ptr<ExpressionVisitor> visitorForString(QString str, DUContext* context,
0064                                                     CursorInRevision scanUntil = CursorInRevision::invalid())
0065 {
0066     ENSURE_CHAIN_READ_LOCKED
0067     if ( !context ) {
0068         return nullptr;
0069     }
0070     AstBuilder builder;
0071     CodeAst::Ptr tmpAst = builder.parse({}, str);
0072     if ( ! tmpAst ) {
0073         return nullptr;
0074     }
0075     ExpressionVisitor* v = new ExpressionVisitor(context);
0076     v->enableGlobalSearching();
0077     if ( scanUntil.isValid() ) {
0078         v->scanUntil(scanUntil);
0079         v->enableUnknownNameReporting();
0080     }
0081     v->visitCode(tmpAst.data());
0082     return std::unique_ptr<ExpressionVisitor>(v);
0083 }
0084 
0085 void PythonCodeCompletionContext::eventuallyAddGroup(QString name, int priority,
0086                                                      QList<CompletionTreeItemPointer> items)
0087 {
0088     if ( items.isEmpty() ) {
0089         return;
0090     }
0091     KDevelop::CompletionCustomGroupNode* node = new KDevelop::CompletionCustomGroupNode(name, priority);
0092     node->appendChildren(items);
0093     m_storedGroups << CompletionTreeElementPointer(node);
0094 }
0095 
0096 QList< CompletionTreeElementPointer > PythonCodeCompletionContext::ungroupedElements()
0097 {
0098     return m_storedGroups;
0099 }
0100 
0101 static QList<CompletionTreeItemPointer> setOmitParentheses(QList<CompletionTreeItemPointer> items) {
0102     for ( auto current: items ) {
0103         if ( auto func = dynamic_cast<FunctionDeclarationCompletionItem*>(current.data()) ) {
0104             func->setDoNotCall(true);
0105         }
0106     }
0107     return items;
0108 };
0109 
0110 PythonCodeCompletionContext::ItemList PythonCodeCompletionContext::shebangItems()
0111 {
0112     KeywordItem::Flags f = (KeywordItem::Flags) ( KeywordItem::ForceLineBeginning | KeywordItem::ImportantItem );
0113     QList<CompletionTreeItemPointer> shebangGroup;
0114     if ( m_position.line == 0 && ( m_text.startsWith('#') || m_text.isEmpty() ) ) {
0115         QString i18ndescr = i18n("insert Shebang line");
0116         shebangGroup << CompletionTreeItemPointer(new KeywordItem(KDevelop::CodeCompletionContext::Ptr(this),
0117                                                     "#!/usr/bin/env python\n", i18ndescr, f));
0118         shebangGroup << CompletionTreeItemPointer(new KeywordItem(KDevelop::CodeCompletionContext::Ptr(this),
0119                                                     "#!/usr/bin/env python3\n", i18ndescr, f));
0120     }
0121     else if ( m_position.line <= 1 && m_text.endsWith('#') ) {
0122         shebangGroup << CompletionTreeItemPointer(new KeywordItem(KDevelop::CodeCompletionContext::Ptr(this),
0123                                                     "# -*- coding:utf-8 -*-\n\n", i18n("specify document encoding"), f));
0124     }
0125     eventuallyAddGroup(i18n("Add file header"), 1000, shebangGroup);
0126     return ItemList();
0127 }
0128 
0129 PythonCodeCompletionContext::ItemList PythonCodeCompletionContext::functionCallItems()
0130 {
0131     ItemList resultingItems;
0132 
0133     // gather additional items to show above the real ones (for parameters, and stuff)
0134     FunctionDeclaration* functionCalled = nullptr;
0135     DUChainReadLocker lock;
0136     auto v = visitorForString(m_guessTypeOfExpression, m_duContext.data());
0137     if ( ! v || ! v->lastDeclaration() ) {
0138         qCWarning(KDEV_PYTHON_CODECOMPLETION) << "Did not receive a function declaration from expression visitor! Not offering call tips.";
0139         qCWarning(KDEV_PYTHON_CODECOMPLETION) << "Tried: " << m_guessTypeOfExpression;
0140         return resultingItems;
0141     }
0142     functionCalled = Helper::functionForCalled(v->lastDeclaration().data()).declaration;
0143 
0144     auto current = Helper::resolveAliasDeclaration(functionCalled);
0145     QList<Declaration*> calltips;
0146     if ( current && current->isFunctionDeclaration() ) {
0147         calltips << current;
0148     }
0149 
0150     auto calltipItems = declarationListToItemList(calltips);
0151     foreach ( CompletionTreeItemPointer current, calltipItems ) {
0152         qCDebug(KDEV_PYTHON_CODECOMPLETION) << "Adding calltip item, at argument:" << m_alreadyGivenParametersCount+1;
0153         FunctionDeclarationCompletionItem* item = static_cast<FunctionDeclarationCompletionItem*>(current.data());
0154         item->setAtArgument(m_alreadyGivenParametersCount + 1);
0155         item->setDepth(depth());
0156     }
0157 
0158     resultingItems.append(calltipItems);
0159 
0160     // If this is the top-level calltip, add additional items for the default-parameters of the function,
0161     // but only if all non-default arguments (the mandatory ones) already have been provided.
0162     // TODO fancy feature: Filter out already provided default-parameters
0163     if ( depth() != 1 || ! functionCalled ) {
0164         return resultingItems;
0165     }
0166     if ( DUContext* args = DUChainUtils::argumentContext(functionCalled) ) {
0167         int normalParameters = args->localDeclarations().count() - functionCalled->defaultParametersSize();
0168         if ( normalParameters > m_alreadyGivenParametersCount ) {
0169             qCDebug(KDEV_PYTHON_CODECOMPLETION) << "Not at default arguments yet";
0170             return resultingItems;
0171         }
0172         for ( unsigned int i = 0; i < functionCalled->defaultParametersSize(); i++ ) {
0173             QString paramName = args->localDeclarations().at(normalParameters + i)->identifier().toString();
0174             resultingItems << CompletionTreeItemPointer(new KeywordItem(CodeCompletionContext::Ptr(m_child),
0175                                                         paramName + "=", i18n("specify default parameter"),
0176                                                         KeywordItem::ImportantItem));
0177         }
0178         qCDebug(KDEV_PYTHON_CODECOMPLETION) << "adding " << functionCalled->defaultParametersSize() << "default args";
0179     }
0180 
0181     return resultingItems;
0182 }
0183 
0184 PythonCodeCompletionContext::ItemList PythonCodeCompletionContext::defineItems()
0185 {
0186     DUChainReadLocker lock;
0187     ItemList resultingItems;
0188     // Find all base classes of the current class context
0189     if ( m_duContext->type() != DUContext::Class ) {
0190         qCWarning(KDEV_PYTHON_CODECOMPLETION) << "current context is not a class context, not offering define completion";
0191         return resultingItems;
0192     }
0193     ClassDeclaration* klass = dynamic_cast<ClassDeclaration*>(m_duContext->owner());
0194     if ( ! klass ) {
0195         return resultingItems;
0196     }
0197     auto baseClassContexts = Helper::internalContextsForClass(
0198         klass->type<StructureType>(), m_duContext->topContext()
0199     );
0200     // This class' context is put first in the list, so all functions existing here
0201     // can be skipped.
0202     baseClassContexts.removeAll(m_duContext.data());
0203     baseClassContexts.prepend(m_duContext.data());
0204     Q_ASSERT(baseClassContexts.size() >= 1);
0205     QList<IndexedString> existingIdentifiers;
0206 
0207     bool isOwnContext = true;
0208     foreach ( DUContext* c, baseClassContexts ) {
0209         const auto declarations = c->allDeclarations(
0210             CursorInRevision::invalid(), m_duContext->topContext(), false
0211         );
0212         foreach ( const DeclarationDepthPair& d, declarations ) {
0213             if ( FunctionDeclaration* funcDecl = dynamic_cast<FunctionDeclaration*>(d.first) ) {
0214                 // python does not have overloads or similar, so comparing the function names is enough.
0215                 const IndexedString identifier = funcDecl->identifier().identifier();
0216                 if ( isOwnContext ) {
0217                     existingIdentifiers << identifier;
0218                 }
0219 
0220                 if ( existingIdentifiers.contains(identifier) ) {
0221                     continue;
0222                 }
0223                 existingIdentifiers << identifier;
0224                 QStringList argumentNames;
0225                 DUContext* argumentsContext = DUChainUtils::argumentContext(funcDecl);
0226                 if ( argumentsContext ) {
0227                     foreach ( Declaration* argument, argumentsContext->localDeclarations() ) {
0228                         argumentNames << argument->identifier().toString();
0229                     }
0230                     resultingItems << CompletionTreeItemPointer(new ImplementFunctionCompletionItem(
0231                         funcDecl->identifier().toString(), argumentNames, m_indent)
0232                     );
0233                 }
0234             }
0235         }
0236         isOwnContext = false;
0237     }
0238     return resultingItems;
0239 }
0240 
0241 PythonCodeCompletionContext::ItemList PythonCodeCompletionContext::raiseItems()
0242 {
0243     qCDebug(KDEV_PYTHON_CODECOMPLETION) << "Finding items for raise statement";
0244     DUChainReadLocker lock;
0245     ItemList resultingItems;
0246     ReferencedTopDUContext ctx = Helper::getDocumentationFileContext();
0247     if ( !ctx ) {
0248         return {};
0249     }
0250     QList< Declaration* > declarations = ctx->findDeclarations(QualifiedIdentifier("BaseException"));
0251     if ( declarations.isEmpty() || ! declarations.first()->abstractType() ) {
0252         qCDebug(KDEV_PYTHON_CODECOMPLETION) << "No valid exception classes found, aborting";
0253         return resultingItems;
0254     }
0255     Declaration* base = declarations.first();
0256     IndexedType baseType = base->abstractType()->indexed();
0257     QVector<DeclarationDepthPair> validDeclarations;
0258     ClassDeclaration* current = nullptr;
0259     StructureType::Ptr type;
0260     auto decls = m_duContext->topContext()->allDeclarations(CursorInRevision::invalid(), m_duContext->topContext());
0261     foreach ( const DeclarationDepthPair d, decls ) {
0262         current = dynamic_cast<ClassDeclaration*>(d.first);
0263         if ( ! current || ! current->baseClassesSize() ) {
0264             continue;
0265         }
0266         FOREACH_FUNCTION( const BaseClassInstance& base, current->baseClasses ) {
0267             if ( base.baseClass == baseType ) {
0268                 validDeclarations << d;
0269             }
0270         }
0271     }
0272     auto items = declarationListToItemList(validDeclarations);
0273     if ( m_itemTypeHint == ClassTypeRequested ) {
0274         // used for except <cursor>, we don't want the parentheses there
0275         items = setOmitParentheses(items);
0276     }
0277     resultingItems.append(items);
0278     return resultingItems;
0279 }
0280 
0281 PythonCodeCompletionContext::ItemList PythonCodeCompletionContext::importFileItems()
0282 {
0283     DUChainReadLocker lock;
0284     ItemList resultingItems;
0285     qCDebug(KDEV_PYTHON_CODECOMPLETION) << "Preparing to do autocompletion for import...";
0286     m_maxFolderScanDepth = 1;
0287     resultingItems << includeItemsForSubmodule("");
0288     return resultingItems;
0289 }
0290 
0291 PythonCodeCompletionContext::ItemList PythonCodeCompletionContext::inheritanceItems()
0292 {
0293     ItemList resultingItems;
0294     DUChainReadLocker lock;
0295     qCDebug(KDEV_PYTHON_CODECOMPLETION) << "InheritanceCompletion";
0296     QVector<DeclarationDepthPair> declarations;
0297     if ( ! m_guessTypeOfExpression.isEmpty() ) {
0298         // The class completion is a member access
0299         auto v = visitorForString(m_guessTypeOfExpression, m_duContext.data());
0300         if ( v ) {
0301             auto cls = v->lastType().dynamicCast<StructureType>();
0302             if ( cls && cls->declaration(m_duContext->topContext()) ) {
0303                 if ( DUContext* internal = cls->declaration(m_duContext->topContext())->internalContext() ) {
0304                     declarations = internal->allDeclarations(m_position, m_duContext->topContext(), false);
0305                 }
0306             }
0307         }
0308     }
0309     else {
0310         declarations = m_duContext->allDeclarations(m_position, m_duContext->topContext());
0311     }
0312     QVector<DeclarationDepthPair> remainingDeclarations;
0313     foreach ( const DeclarationDepthPair& d, declarations ) {
0314         Declaration* r = Helper::resolveAliasDeclaration(d.first);
0315         if ( r && r->topContext() == Helper::getDocumentationFileContext() ) {
0316             continue;
0317         }
0318         if ( r && dynamic_cast<ClassDeclaration*>(r) ) {
0319             remainingDeclarations << d;
0320         }
0321     }
0322     resultingItems.append(setOmitParentheses(declarationListToItemList(remainingDeclarations)));
0323     return resultingItems;
0324 }
0325 
0326 PythonCodeCompletionContext::ItemList PythonCodeCompletionContext::memberAccessItems()
0327 {
0328     ItemList resultingItems;
0329     DUChainReadLocker lock;
0330     auto v = visitorForString(m_guessTypeOfExpression, m_duContext.data());
0331     if ( v ) {
0332         if ( v->lastType() ) {
0333             qCDebug(KDEV_PYTHON_CODECOMPLETION) << v->lastType()->toString();
0334             resultingItems << getCompletionItemsForType(v->lastType());
0335         }
0336         else {
0337             qCWarning(KDEV_PYTHON_CODECOMPLETION) << "Did not receive a type from expression visitor! Not offering autocompletion.";
0338         }
0339     }
0340     else {
0341         qCWarning(KDEV_PYTHON_CODECOMPLETION) << "Completion requested for syntactically invalid expression, not offering anything";
0342     }
0343 
0344     // append eventually stripped postfix, for e.g. os.chdir|
0345     bool needDot = true;
0346     foreach ( const QChar& c, m_followingText ) {
0347         if ( needDot ) {
0348             m_guessTypeOfExpression.append('.');
0349             needDot = false;
0350         }
0351         if ( c.isLetterOrNumber() || c == '_' ) {
0352             m_guessTypeOfExpression.append(c);
0353         }
0354     }
0355     if ( resultingItems.isEmpty() && m_fullCompletion ) {
0356         resultingItems << getMissingIncludeItems(m_guessTypeOfExpression);
0357     }
0358     return resultingItems;
0359 }
0360 
0361 PythonCodeCompletionContext::ItemList PythonCodeCompletionContext::stringFormattingItems()
0362 {
0363     if ( ! m_fullCompletion ) {
0364         return ItemList();
0365     }
0366     DUChainReadLocker lock;
0367     ItemList resultingItems;
0368     int cursorPosition;
0369     StringFormatter stringFormatter(CodeHelpers::extractStringUnderCursor(m_text,
0370                                                                           m_duContext->range().castToSimpleRange(),
0371                                                                           m_position.castToSimpleCursor(),
0372                                                                           &cursorPosition));
0373 
0374     qCDebug(KDEV_PYTHON_CODECOMPLETION) << "Next identifier id: " << stringFormatter.nextIdentifierId();
0375     qCDebug(KDEV_PYTHON_CODECOMPLETION) << "Cursor position in string: " << cursorPosition;
0376 
0377     bool insideReplacementVariable = stringFormatter.isInsideReplacementVariable(cursorPosition);
0378     RangeInString variablePosition = stringFormatter.getVariablePosition(cursorPosition);
0379 
0380     bool onVariableBoundary = (cursorPosition == variablePosition.beginIndex || cursorPosition == variablePosition.endIndex);
0381     if ( ! insideReplacementVariable || onVariableBoundary ) {
0382         resultingItems << CompletionTreeItemPointer(new ReplacementVariableItem(
0383             ReplacementVariable(QString::number(stringFormatter.nextIdentifierId())),
0384                                 i18n("Insert next positional variable"), false)
0385         );
0386 
0387         resultingItems << CompletionTreeItemPointer(new ReplacementVariableItem(
0388             ReplacementVariable("${argument}"),
0389                                 i18n("Insert named variable"), true)
0390         );
0391 
0392     }
0393 
0394     if ( ! insideReplacementVariable ) {
0395         return resultingItems;
0396     }
0397 
0398     const ReplacementVariable *variable = stringFormatter.getReplacementVariable(cursorPosition);
0399 
0400     // Convert the range relative to the beginning of the string to the absolute position
0401     // in the document. We can safely assume that the replacement variable is on one line,
0402     // because the regex does not allow newlines inside replacement variables.
0403     KTextEditor::Range range;
0404     range.setStart({m_position.line, m_position.column - (cursorPosition - variablePosition.beginIndex)});
0405     range.setEnd({m_position.line, m_position.column + (variablePosition.endIndex - cursorPosition)});
0406 
0407     qCDebug(KDEV_PYTHON_CODECOMPLETION) << "Variable under cursor: " << variable->toString();
0408     bool hasNumericOnlyOption =     variable->hasPrecision()
0409                                 || (variable->hasType() && variable->type() != 's')
0410                                 ||  variable->align() == '=';
0411 
0412     auto makeFormattingItem = [&variable, &range](const QChar& conversion, const QString& spec,
0413                                                   const QString& description, bool useTemplateEngine)
0414     {
0415         return CompletionTreeItemPointer(
0416             new ReplacementVariableItem(ReplacementVariable(variable->identifier(), conversion, spec),
0417                                                             description, useTemplateEngine, range)
0418         );
0419     };
0420 
0421     if ( ! variable->hasConversion() && ! hasNumericOnlyOption ) {
0422         auto addConversionItem = [&](const QChar& conversion, const QString& title) {
0423             resultingItems.append(makeFormattingItem(conversion, variable->formatSpec(), title, false));
0424         };
0425         addConversionItem('s', i18n("Format using str()"));
0426         addConversionItem('r', i18n("Format using repr()"));
0427     }
0428 
0429     if ( ! variable->hasFormatSpec() ) {
0430         auto addFormatSpec = [&](const QString& format, const QString& title, bool useTemplateEngine)
0431         {
0432             resultingItems.append(makeFormattingItem(variable->conversion(), format, title, useTemplateEngine));
0433         };
0434         addFormatSpec("<${width}", i18n("Format as left-aligned"), true);
0435         addFormatSpec(">${width}", i18n("Format as right-aligned"), true);
0436         addFormatSpec("^${width}", i18n("Format as centered"), true);
0437 
0438         // These options don't make sense if we've set conversion using str() or repr()
0439         if ( ! variable->hasConversion() ) {
0440             addFormatSpec(".${precision}", i18n("Specify precision"), true);
0441             addFormatSpec("%", i18n("Format as percentage"), false);
0442             addFormatSpec("c", i18n("Format as character"), false);
0443             addFormatSpec("b", i18n("Format as binary number"), false);
0444             addFormatSpec("o", i18n("Format as octal number"), false);
0445             addFormatSpec("x", i18n("Format as hexadecimal number"), false);
0446             addFormatSpec("e", i18n("Format in scientific (exponent) notation"), false);
0447             addFormatSpec("f", i18n("Format as fixed point number"), false);
0448         }
0449     }
0450 
0451     qCDebug(KDEV_PYTHON_CODECOMPLETION) << "Resulting items size: " << resultingItems.size();
0452     return resultingItems;
0453 }
0454 
0455 PythonCodeCompletionContext::ItemList PythonCodeCompletionContext::keywordItems()
0456 {
0457     ItemList resultingItems;
0458     QStringList keywordItems;
0459     keywordItems << "def" << "class" << "lambda" << "global" << "import"
0460                  << "from" << "while" << "for" << "yield" << "return";
0461     foreach ( const QString& current, keywordItems ) {
0462         KeywordItem* k = new KeywordItem(KDevelop::CodeCompletionContext::Ptr(this), current + " ", "");
0463         resultingItems << CompletionTreeItemPointer(k);
0464     }
0465     return resultingItems;
0466 }
0467 
0468 PythonCodeCompletionContext::ItemList PythonCodeCompletionContext::classMemberInitItems()
0469 {
0470     DUChainReadLocker lock;
0471     ItemList resultingItems;
0472     Declaration* decl = duContext()->owner();
0473     if ( ! decl ) {
0474         return resultingItems;
0475     }
0476     DUContext* args = DUChainUtils::argumentContext(duContext()->owner());
0477     if ( ! args ) {
0478         return resultingItems;
0479     }
0480     if ( ! decl->isFunctionDeclaration() || decl->identifier() != KDevelop::Identifier("__init__") ) {
0481         return resultingItems;
0482     }
0483     // the current context actually belongs to a constructor
0484     foreach ( const Declaration* argument, args->localDeclarations() ) {
0485         const QString argName = argument->identifier().toString();
0486         // Do not suggest "self.self = self"
0487         if ( argName == "self" ) {
0488             continue;
0489         }
0490         bool usedAlready = false;
0491         // Do not suggest arguments which already have a use in the context
0492         // This is uesful because you can then do { Ctrl+Space Enter Enter } while ( 1 )
0493         // to initialize all available class variables, without using arrow keys.
0494         for ( int i = 0; i < duContext()->usesCount(); i++ ) {
0495             if ( duContext()->uses()[i].usedDeclaration(duContext()->topContext()) == argument ) {
0496                 usedAlready = true;
0497                 break;
0498             }
0499         }
0500         if ( usedAlready ) {
0501             continue;
0502         }
0503         const QString value = "self." + argName + " = " + argName;
0504         KeywordItem* item = new KeywordItem(KDevelop::CodeCompletionContext::Ptr(this),
0505                                             value, i18n("Initialize property"),
0506                                             KeywordItem::ImportantItem);
0507         resultingItems.append(CompletionTreeItemPointer(item));
0508     }
0509     return resultingItems;
0510 }
0511 
0512 PythonCodeCompletionContext::ItemList PythonCodeCompletionContext::generatorItems()
0513 {
0514     ItemList resultingItems;
0515     QList<KeywordItem*> items;
0516 
0517     DUChainReadLocker lock;
0518     auto v = visitorForString(m_guessTypeOfExpression, m_duContext.data(), m_position);
0519     if ( ! v || v->unknownNames().isEmpty() ) {
0520         return resultingItems;
0521     }
0522     if ( v->unknownNames().size() >= 2 ) {
0523         // we only take the first two, and only two. It gets too much items otherwise.
0524         QStringList combinations;
0525         auto names = v->unknownNames().values();
0526         combinations << names.at(0) + ", " + names.at(1);
0527         combinations << names.at(1) + ", " + names.at(0);
0528         foreach ( const QString& c, combinations ) {
0529             items << new KeywordItem(KDevelop::CodeCompletionContext::Ptr(this), "" + c + " in ", "");
0530         }
0531     }
0532     foreach ( const QString& n, v->unknownNames() ) {
0533         items << new KeywordItem(KDevelop::CodeCompletionContext::Ptr(this), "" + n + " in ", "");
0534     }
0535 
0536     foreach ( KeywordItem* item, items ) {
0537         resultingItems << CompletionTreeItemPointer(item);
0538     }
0539     return resultingItems;
0540 }
0541 
0542 QList<CompletionTreeItemPointer> PythonCodeCompletionContext::completionItems(bool& abort, bool fullCompletion)
0543 {
0544     m_fullCompletion = fullCompletion;
0545     ItemList resultingItems;
0546     
0547     qCDebug(KDEV_PYTHON_CODECOMPLETION) << "Line: " << m_position.line;
0548     qCDebug(KDEV_PYTHON_CODECOMPLETION) << "Completion type:" << m_operation;
0549     
0550     if ( m_operation != FunctionCallCompletion ) {
0551         resultingItems.append(shebangItems());
0552     }
0553     
0554     // Find all calltips recursively
0555     if ( parentContext() ) {
0556         resultingItems.append(parentContext()->completionItems(abort, fullCompletion));
0557     }
0558     
0559     if ( m_operation == PythonCodeCompletionContext::NoCompletion ) {
0560         qCDebug(KDEV_PYTHON_CODECOMPLETION) << "no code completion";
0561     }
0562     else if ( m_operation == PythonCodeCompletionContext::GeneratorVariableCompletion ) {
0563         resultingItems.append(generatorItems());
0564     }
0565     else if ( m_operation == PythonCodeCompletionContext::FunctionCallCompletion ) {
0566         resultingItems.append(functionCallItems());
0567     }
0568     else if ( m_operation == PythonCodeCompletionContext::DefineCompletion ) {
0569         resultingItems.append(defineItems());
0570     }
0571     else if ( m_operation == PythonCodeCompletionContext::RaiseExceptionCompletion ) {
0572         resultingItems.append(raiseItems());
0573     }
0574     else if ( m_operation == PythonCodeCompletionContext::ImportFileCompletion ) {
0575         resultingItems.append(importFileItems());
0576     }
0577     else if ( m_operation == PythonCodeCompletionContext::ImportSubCompletion ) {
0578         DUChainReadLocker lock;
0579         resultingItems.append(includeItemsForSubmodule(m_searchImportItemsInModule));
0580     }
0581     else if ( m_operation == PythonCodeCompletionContext::InheritanceCompletion ) {
0582         resultingItems.append(inheritanceItems());
0583     }
0584     else if ( m_operation == PythonCodeCompletionContext::MemberAccessCompletion ) {
0585         resultingItems.append(memberAccessItems());
0586     }
0587     else if ( m_operation == PythonCodeCompletionContext::StringFormattingCompletion ) {
0588         resultingItems.append(stringFormattingItems());
0589     }
0590     else {
0591         // it's stupid to display a 3-letter completion item on manually invoked code completion and makes everything look crowded
0592         if ( m_operation == PythonCodeCompletionContext::NewStatementCompletion && ! fullCompletion ) {
0593             resultingItems.append(keywordItems());
0594         }
0595         if ( m_operation == PythonCodeCompletionContext::NewStatementCompletion ) {
0596             // Eventually suggest initializing class members from constructor arguments
0597             resultingItems.append(classMemberInitItems());
0598         }
0599         if ( abort ) {
0600             return ItemList();
0601         }
0602         DUChainReadLocker lock;
0603         auto declarations = m_duContext->allDeclarations(m_position, m_duContext->topContext());
0604         foreach ( const DeclarationDepthPair& d, declarations ) {
0605             if ( d.first && d.first->context()->type() == DUContext::Class ) {
0606                 declarations.removeAll(d);
0607             }
0608         }
0609         resultingItems.append(declarationListToItemList(declarations));
0610     }
0611     
0612     m_searchingForModule.clear();
0613     m_searchImportItemsInModule.clear();
0614     
0615     return resultingItems;
0616 }
0617 
0618 QList<CompletionTreeItemPointer> PythonCodeCompletionContext::getMissingIncludeItems(QString forString)
0619 {
0620     QList<CompletionTreeItemPointer> items;
0621 
0622     // Find all the non-empty name components (mainly, remove the last empty one for "sys." or similar)
0623     QStringList components = forString.split('.');
0624     components.removeAll(QString());
0625 
0626     // Check all components are alphanumeric
0627     QRegExp alnum("\\w*");
0628     foreach ( const QString& component, components ) {
0629         if ( ! alnum.exactMatch(component) ) return items;
0630     }
0631 
0632     if ( components.isEmpty() ) {
0633         return items;
0634     }
0635 
0636     Declaration* existing = Helper::declarationForName(components.first(), m_position,
0637                                                        DUChainPointer<const DUContext>(m_duContext.data()));
0638     if ( existing ) {
0639         // There's already a declaration for the first component; no need to suggest it
0640         return items;
0641     }
0642 
0643     // See if there's a module called like that.
0644     auto found = ContextBuilder::findModulePath(components.join("."), m_workingOnDocument);
0645 
0646     // Check if anything was found
0647     if ( found.first.isValid() ) {
0648         // Add items for the "from" and the plain import
0649         if ( components.size() > 1 && found.second.isEmpty() ) {
0650             // There's something left for X in "from foo import X",
0651             // and it's not a declaration inside the module so offer that
0652             const QString module = QStringList(components.mid(0, components.size() - 1)).join(".");
0653             const QString text = QString("from %1 import %2").arg(module, components.last());
0654             MissingIncludeItem* item = new MissingIncludeItem(text, components.last(), forString);
0655             items << CompletionTreeItemPointer(item);
0656         }
0657 
0658         const QString module = QStringList(components.mid(0, components.size() - found.second.size())).join(".");
0659         const QString text = QString("import %1").arg(module);
0660         MissingIncludeItem* item = new MissingIncludeItem(text, components.last());
0661         items << CompletionTreeItemPointer(item);
0662     }
0663 
0664     return items;
0665 }
0666 
0667 QList<CompletionTreeItemPointer> PythonCodeCompletionContext::declarationListToItemList(const QVector<DeclarationDepthPair>& declarations, int maxDepth)
0668 {
0669     QList<CompletionTreeItemPointer> items;
0670     
0671     DeclarationPointer currentDeclaration;
0672     Declaration* checkDeclaration = nullptr;
0673     int count = declarations.length();
0674     for ( int i = 0; i < count; i++ ) {
0675         if ( maxDepth && maxDepth > declarations.at(i).second ) {
0676             qCDebug(KDEV_PYTHON_CODECOMPLETION) << "Skipped completion item because of its depth";
0677             continue;
0678         }
0679         currentDeclaration = DeclarationPointer(declarations.at(i).first);
0680         
0681         PythonDeclarationCompletionItem* item = nullptr;
0682         checkDeclaration = Helper::resolveAliasDeclaration(currentDeclaration.data());
0683         if ( ! checkDeclaration ) {
0684             continue;
0685         }
0686         if ( checkDeclaration->isFunctionDeclaration()
0687                 || (checkDeclaration->internalContext() && checkDeclaration->internalContext()->type() == DUContext::Class) ) {
0688             item = new FunctionDeclarationCompletionItem(currentDeclaration, KDevelop::CodeCompletionContext::Ptr(this));
0689         }
0690         else {
0691             item = new PythonDeclarationCompletionItem(currentDeclaration, KDevelop::CodeCompletionContext::Ptr(this));
0692         }
0693         if ( ! m_matchAgainst.isEmpty() ) {
0694             item->addMatchQuality(identifierMatchQuality(m_matchAgainst, checkDeclaration->identifier().toString()));
0695         }
0696         items << CompletionTreeItemPointer(item);
0697     }
0698     return items;
0699 }
0700 
0701 QList< CompletionTreeItemPointer > PythonCodeCompletionContext::declarationListToItemList(QList< Declaration* > declarations)
0702 {
0703     QVector<DeclarationDepthPair> fakeItems;
0704     fakeItems.reserve(declarations.size());
0705     foreach ( Declaration* d, declarations ) {
0706         fakeItems << DeclarationDepthPair(d, 0);
0707     }
0708     return declarationListToItemList(fakeItems);
0709 }
0710 
0711 QList< CompletionTreeItemPointer > PythonCodeCompletionContext::getCompletionItemsForType(AbstractType::Ptr type)
0712 {
0713     type = Helper::resolveAliasType(type);
0714     if ( type->whichType() != AbstractType::TypeUnsure ) {
0715         return getCompletionItemsForOneType(type);
0716     }
0717 
0718     QList<CompletionTreeItemPointer> result;
0719     auto unsure = type.staticCast<UnsureType>();
0720     int count = unsure->typesSize();
0721     for ( int i = 0; i < count; i++ ) {
0722         result.append(getCompletionItemsForOneType(unsure->types()[i].abstractType()));
0723     }
0724     // Do some weighting: the more often an entry appears, the better the entry.
0725     // That way, entries which are in all of the types this object could have will
0726     // be sorted higher up.
0727     QStringList itemTitles;
0728     QList<CompletionTreeItemPointer> remove;
0729     for ( int i = 0; i < result.size(); i++ ) {
0730         DeclarationPointer decl = result.at(i)->declaration();
0731         if ( ! decl ) {
0732             itemTitles.append(QString());
0733             continue;
0734         }
0735         const QString& title = decl->identifier().toString();
0736         if ( itemTitles.contains(title) ) {
0737             // there's already an item with that title, increase match quality
0738             int item = itemTitles.indexOf(title);
0739             PythonDeclarationCompletionItem* declItem = dynamic_cast<PythonDeclarationCompletionItem*>(result[item].data());
0740             if ( ! m_fullCompletion ) {
0741                 remove.append(result.at(i));
0742             }
0743             if ( declItem ) {
0744                 // Add 1 to the match quality of the first item in the list.
0745                 declItem->addMatchQuality(1);
0746             }
0747         }
0748         itemTitles.append(title);
0749     }
0750     foreach ( const CompletionTreeItemPointer& ptr, remove ) {
0751         result.removeOne(ptr);
0752     }
0753     return result;
0754 }
0755 
0756 QList<CompletionTreeItemPointer> PythonCodeCompletionContext::getCompletionItemsForOneType(AbstractType::Ptr type)
0757 {
0758     type = Helper::resolveAliasType(type);
0759     ReferencedTopDUContext builtinTopContext = Helper::getDocumentationFileContext();
0760     if ( type->whichType() != AbstractType::TypeStructure ) {
0761         return ItemList();
0762     }
0763     // find properties of class declaration
0764     auto cls = type.dynamicCast<StructureType>();
0765     qCDebug(KDEV_PYTHON_CODECOMPLETION) << "Finding completion items for class type";
0766     if ( ! cls || ! cls->internalContext(m_duContext->topContext()) ) {
0767         qCWarning(KDEV_PYTHON_CODECOMPLETION) << "No class type available, no completion offered";
0768         return QList<CompletionTreeItemPointer>();
0769     }
0770     // the PublicOnly will filter out non-explictly defined __get__ etc. functions inherited from object
0771     auto searchContexts = Helper::internalContextsForClass(cls, m_duContext->topContext(), Helper::PublicOnly);
0772     QVector<DeclarationDepthPair> keepDeclarations;
0773     foreach ( const DUContext* currentlySearchedContext, searchContexts ) {
0774         qCDebug(KDEV_PYTHON_CODECOMPLETION) << "searching context " << currentlySearchedContext->scopeIdentifier() << "for autocompletion items";
0775         const auto declarations = currentlySearchedContext->allDeclarations(CursorInRevision::invalid(),
0776                                                                                                 m_duContext->topContext(),
0777                                                                                                 false);
0778         qCDebug(KDEV_PYTHON_CODECOMPLETION) << "found" << declarations.length() << "declarations";
0779 
0780         // filter out those which are builtin functions, and those which were imported; we don't want those here
0781         // also, discard all magic functions from autocompletion
0782         // TODO rework this, it's maybe not the most elegant solution possible
0783         // TODO rework the magic functions thing, I want them sorted at the end of the list but KTE doesn't seem to allow that
0784         foreach ( const DeclarationDepthPair& current, declarations ) {
0785             if ( current.first->context() != builtinTopContext && ! current.first->identifier().identifier().str().startsWith("__") ) {
0786                 keepDeclarations.append(current);
0787             }
0788             else {
0789                 qCDebug(KDEV_PYTHON_CODECOMPLETION) << "Discarding declaration " << current.first->toString();
0790             }
0791         }
0792     }
0793     return declarationListToItemList(keepDeclarations);
0794 }
0795 
0796 QList<CompletionTreeItemPointer> PythonCodeCompletionContext::findIncludeItems(IncludeSearchTarget item)
0797 {
0798     qCDebug(KDEV_PYTHON_CODECOMPLETION) << "TARGET:" << item.directory.path() << item.remainingIdentifiers;
0799     QDir currentDirectory(item.directory.path());
0800     QFileInfoList contents = currentDirectory.entryInfoList(QStringList(), QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot);
0801     bool atBottom = item.remainingIdentifiers.isEmpty();
0802     QList<CompletionTreeItemPointer> items;
0803     
0804     QString sourceFile;
0805     
0806     if ( item.remainingIdentifiers.isEmpty() ) {
0807         // check for the __init__ file
0808         QFileInfo initFile(item.directory.path(), "__init__.py");
0809         if ( initFile.exists() ) {
0810             IncludeItem init;
0811             init.basePath = item.directory;
0812             init.isDirectory = true;
0813             init.name = "";
0814             if ( ! item.directory.fileName().contains('-') ) {
0815                 // Do not include items which contain "-", those are not valid
0816                 // modules but instead often e.g. .egg directories
0817                 ImportFileItem* importfile = new ImportFileItem(init);
0818                 importfile->moduleName = item.directory.fileName();
0819                 items << CompletionTreeItemPointer(importfile);
0820                 sourceFile = initFile.filePath();
0821             }
0822         }
0823     }
0824     else {
0825         QFileInfo file(item.directory.path(), item.remainingIdentifiers.first() + ".py");
0826         item.remainingIdentifiers.removeFirst();
0827         qCDebug(KDEV_PYTHON_CODECOMPLETION) << " CHECK:" << file.absoluteFilePath();
0828         if ( file.exists() ) {
0829             sourceFile = file.absoluteFilePath();
0830         }
0831     }
0832     
0833     if ( ! sourceFile.isEmpty() ) {
0834         IndexedString filename(sourceFile);
0835         TopDUContext* top = DUChain::self()->chainForDocument(filename);
0836         qCDebug(KDEV_PYTHON_CODECOMPLETION) << top;
0837         DUContext* c = internalContextForDeclaration(top, item.remainingIdentifiers);
0838         qCDebug(KDEV_PYTHON_CODECOMPLETION) << "  GOT:" << c;
0839         if ( c ) {
0840             // tell function declaration items not to add brackets
0841             items << setOmitParentheses(declarationListToItemList(c->localDeclarations().toList()));
0842         }
0843         else {
0844             // do better next time
0845             DUChain::self()->updateContextForUrl(filename, TopDUContext::AllDeclarationsAndContexts);
0846         }
0847     }
0848     
0849     if ( atBottom ) {
0850         // append all python files in the directory
0851         foreach ( QFileInfo file, contents ) {
0852             qCDebug(KDEV_PYTHON_CODECOMPLETION) << " > CONTENT:" << file.absolutePath() << file.fileName();
0853             if ( file.isFile() ) {
0854                 if ( file.fileName().endsWith(".py") || file.fileName().endsWith(".so") ) {
0855                     IncludeItem fileInclude;
0856                     fileInclude.basePath = item.directory;
0857                     fileInclude.isDirectory = false;
0858                     fileInclude.name = file.fileName().mid(0, file.fileName().length() - 3); // remove ".py"
0859                     ImportFileItem* import = new ImportFileItem(fileInclude);
0860                     import->moduleName = fileInclude.name;
0861                     items << CompletionTreeItemPointer(import);
0862                 }
0863             }
0864             else if ( ! file.fileName().contains('-') ) {
0865                 IncludeItem dirInclude;
0866                 dirInclude.basePath = item.directory;
0867                 dirInclude.isDirectory = true;
0868                 dirInclude.name = file.fileName();
0869                 ImportFileItem* import = new ImportFileItem(dirInclude);
0870                 import->moduleName = dirInclude.name;
0871                 items << CompletionTreeItemPointer(import);
0872             }
0873         }
0874     }
0875     return items;
0876 }
0877 
0878 QList<CompletionTreeItemPointer> PythonCodeCompletionContext::findIncludeItems(QList< Python::IncludeSearchTarget > items)
0879 {
0880     QList<CompletionTreeItemPointer> results;
0881     foreach ( const IncludeSearchTarget& item, items ) {
0882         results << findIncludeItems(item);
0883     }
0884     return results;
0885 }
0886 
0887 DUContext* PythonCodeCompletionContext::internalContextForDeclaration(TopDUContext* topContext, QStringList remainingIdentifiers)
0888 {
0889     Declaration* d = nullptr;
0890     DUContext* c = topContext;
0891     if ( ! topContext ) {
0892         return nullptr;
0893     }
0894     if ( remainingIdentifiers.isEmpty() ) {
0895         return topContext;
0896     }
0897     do {
0898         QList< Declaration* > decls = c->findDeclarations(QualifiedIdentifier(remainingIdentifiers.first()));
0899         remainingIdentifiers.removeFirst();
0900         if ( decls.isEmpty() ) {
0901             return nullptr;
0902         }
0903         d = decls.first();
0904         if ( (c = d->internalContext()) ) {
0905             if ( remainingIdentifiers.isEmpty() ) {
0906                 return c;
0907             }
0908         }
0909         else return nullptr;
0910         
0911     } while ( d && ! remainingIdentifiers.isEmpty() );
0912     return nullptr;
0913 }
0914 
0915 QList<CompletionTreeItemPointer> PythonCodeCompletionContext::includeItemsForSubmodule(QString submodule)
0916 {
0917     auto searchPaths = Helper::getSearchPaths(m_workingOnDocument);
0918     
0919     QStringList subdirs;
0920     if ( ! submodule.isEmpty() ) {
0921         subdirs = submodule.split(".");
0922     }
0923     
0924     Q_ASSERT(! subdirs.contains(""));
0925     
0926     QList<IncludeSearchTarget> foundPaths;
0927     
0928     // this is a bit tricky. We need to find every path formed like /.../foo/bar for
0929     // a query string ("submodule" variable) like foo.bar
0930     // we also need paths like /foo.py, because then bar is probably a module in that file.
0931     // Thus, we first generate a list of possible paths, then match them against those which actually exist
0932     // and then gather all the items in those paths.
0933     
0934     foreach ( QUrl currentPath, searchPaths ) {
0935         auto d = QDir(currentPath.path());
0936         qCDebug(KDEV_PYTHON_CODECOMPLETION) << "Searching: " << currentPath << subdirs;
0937         int identifiersUsed = 0;
0938         foreach ( const QString& subdir, subdirs ) {
0939             qCDebug(KDEV_PYTHON_CODECOMPLETION) << "changing into subdir" << subdir;
0940             if ( ! d.cd(subdir) ) {
0941                 break;
0942             }
0943             qCDebug(KDEV_PYTHON_CODECOMPLETION) << d.absolutePath() << d.exists();
0944             identifiersUsed++;
0945         }
0946         QStringList remainingIdentifiers = subdirs.mid(identifiersUsed, -1);
0947         foundPaths.append(IncludeSearchTarget(QUrl::fromLocalFile(d.absolutePath()), remainingIdentifiers));
0948         qCDebug(KDEV_PYTHON_CODECOMPLETION) << "Found path:" << d.absolutePath() << remainingIdentifiers << subdirs;
0949     }
0950     return findIncludeItems(foundPaths);
0951 }
0952 
0953 PythonCodeCompletionContext::PythonCodeCompletionContext(DUContextPointer context, const QString& remainingText,
0954                                                          QString calledFunction,
0955                                                          int depth, int alreadyGivenParameters,
0956                                                          CodeCompletionContext* child)
0957     : CodeCompletionContext(context, remainingText, CursorInRevision::invalid(), depth)
0958     , m_operation(FunctionCallCompletion)
0959     , m_itemTypeHint(NoHint)
0960     , m_child(child)
0961     , m_guessTypeOfExpression(calledFunction)
0962     , m_alreadyGivenParametersCount(alreadyGivenParameters)
0963     , m_fullCompletion(false)
0964 {
0965     ExpressionParser p(remainingText);
0966     summonParentForEventualCall(p.popAll(), remainingText);
0967 }
0968 
0969 void PythonCodeCompletionContext::summonParentForEventualCall(TokenList allExpressions, const QString& text)
0970 {
0971     DUChainReadLocker lock;
0972     int offset = 0;
0973     while ( true ) {
0974         QPair<int, int> nextCall = allExpressions.nextIndexOfStatus(ExpressionParser::EventualCallFound, offset);
0975         qCDebug(KDEV_PYTHON_CODECOMPLETION) << "next call:" << nextCall;
0976         qCDebug(KDEV_PYTHON_CODECOMPLETION) << allExpressions.toString();
0977         if ( nextCall.first == -1 ) {
0978             // no more eventual calls
0979             break;
0980         }
0981         offset = nextCall.first;
0982         allExpressions.reset(offset);
0983         TokenListEntry eventualFunction = allExpressions.weakPop();
0984         qCDebug(KDEV_PYTHON_CODECOMPLETION) << eventualFunction.expression << eventualFunction.status;
0985         // it's only a call if a "(" bracket is followed (<- direction) by an expression.
0986         if ( eventualFunction.status != ExpressionParser::ExpressionFound ) {
0987             continue; // not a call, try the next opening "(" bracket
0988         }
0989         qCDebug(KDEV_PYTHON_CODECOMPLETION) << "Call found! Creating parent-context.";
0990         // determine the amount of "free" commas in between
0991         allExpressions.reset();
0992         int atParameter = 0;
0993         for ( int i = 0; i < offset-1; i++ ) {
0994             TokenListEntry entry = allExpressions.weakPop();
0995             if ( entry.status == ExpressionParser::CommaFound ) {
0996                 atParameter += 1;
0997             }
0998             // clear the param count for something like "f([1, 2, 3" (it will be cleared when the "[" is read)
0999             if ( entry.status == ExpressionParser::InitializerFound || entry.status == ExpressionParser::EventualCallFound ) {
1000                 atParameter = 0;
1001             }
1002         }
1003         m_parentContext = new PythonCodeCompletionContext(m_duContext,
1004                                                             text.mid(0, eventualFunction.charOffset),
1005                                                             eventualFunction.expression, depth() + 1,
1006                                                             atParameter, this
1007                                                             );
1008         break;
1009     }
1010     allExpressions.reset(1);
1011 }
1012 
1013 // decide what kind of completion will be offered based on the code before the current cursor position
1014 PythonCodeCompletionContext::PythonCodeCompletionContext(DUContextPointer context, const QString& text,
1015                                                          const QString& followingText,
1016                                                          const KDevelop::CursorInRevision& position,
1017                                                          int depth, const PythonCodeCompletionWorker* /*parent*/)
1018     : CodeCompletionContext(context, text, position, depth)
1019     , m_operation(PythonCodeCompletionContext::DefaultCompletion)
1020     , m_itemTypeHint(NoHint)
1021     , m_child(nullptr)
1022     , m_followingText(followingText)
1023     , m_position(position)
1024 {
1025     m_workingOnDocument = context->topContext()->url().toUrl();
1026     QString textWithoutStrings = CodeHelpers::killStrings(text);
1027     
1028     qCDebug(KDEV_PYTHON_CODECOMPLETION) << text << position << context->localScopeIdentifier().toString() << context->range();
1029     
1030     QPair<QString, QString> beforeAndAfterCursor = CodeHelpers::splitCodeByCursor(text,
1031                                                                                   context->range().castToSimpleRange(),
1032                                                                                   position.castToSimpleCursor());
1033 
1034     // check if the current position is inside a multi-line comment -> no completion if this is the case
1035     CodeHelpers::EndLocation location = CodeHelpers::endsInside(beforeAndAfterCursor.first);
1036     if ( location == CodeHelpers::Comment ) {
1037         m_operation = PythonCodeCompletionContext::NoCompletion;
1038         return;
1039     }
1040     else if ( location == CodeHelpers::String ) {
1041         m_operation = PythonCodeCompletionContext::StringFormattingCompletion;
1042         return;
1043     }
1044     
1045     // The expression parser used to determine the type of completion required.
1046     ExpressionParser parser(textWithoutStrings);
1047     TokenList allExpressions = parser.popAll();
1048     allExpressions.reset(1);
1049     ExpressionParser::Status firstStatus = allExpressions.last().status;
1050     
1051     // TODO reuse already calculated information
1052     FileIndentInformation indents(text);
1053     
1054     DUContext* currentlyChecked = context.data();
1055     // This will set the line to use for the completion to the beginning of the expression.
1056     // In reality, the line we're in might mismatch the beginning of the current expression,
1057     // for example in multi-line list initializers.
1058     int currentlyCheckedLine = position.line - text.mid(text.length() - allExpressions.first().charOffset).count('\n');
1059     
1060     // The following code will check whether the DUContext directly at the cursor should be used, or a previous one.
1061     // The latter might be the case if there's code like this:
1062     /* class foo():
1063      * ..pass
1064      * .
1065      * ..# completion requested here; note that there's less indent in the previous line!
1066      * */
1067     // In that case, the DUContext of "foo" would end at line 2, but should still be used for completion.
1068     {
1069         DUChainReadLocker lock(DUChain::lock());
1070         while ( currentlyChecked == context.data() && currentlyCheckedLine >= 0 ) {
1071             currentlyChecked = context->topContext()->findContextAt(CursorInRevision(currentlyCheckedLine, 0), true);
1072             currentlyCheckedLine -= 1;
1073         }
1074         while ( currentlyChecked && context->parentContextOf(currentlyChecked) ) {
1075             qCDebug(KDEV_PYTHON_CODECOMPLETION) << "checking:" << currentlyChecked->range() << currentlyChecked->type();
1076             // FIXME: "<=" is not really good, it must be exactly one indent-level less
1077             int offset = position.line-currentlyChecked->range().start.line;
1078             // If the check leaves the current context, abort.
1079             if ( offset >= indents.linesCount() ) {
1080                 break;
1081             }
1082             if (    indents.indentForLine(indents.linesCount()-1-offset)
1083                  <= indents.indentForLine(indents.linesCount()-1) )
1084             {
1085                 qCDebug(KDEV_PYTHON_CODECOMPLETION) << "changing context to" << currentlyChecked->range() 
1086                         << ( currentlyChecked->type() == DUContext::Class );
1087                 context = currentlyChecked;
1088                 break;
1089             }
1090             currentlyChecked = currentlyChecked->parentContext();
1091         }
1092     }
1093     
1094     m_duContext = context;
1095     
1096     QList<ExpressionParser::Status> defKeywords;
1097     defKeywords << ExpressionParser::DefFound << ExpressionParser::ClassFound;
1098     
1099     if ( allExpressions.nextIndexOfStatus(ExpressionParser::EventualCallFound).first != -1 ) {
1100         // 3 is always the case for "def foo(" or class foo(": one names the function, the other is the keyword
1101         if ( allExpressions.length() >= 4 && allExpressions.at(1).status == ExpressionParser::DefFound ) {
1102             // The next thing the user probably wants to type are parameters for his function.
1103             // We cannot offer completion for this.
1104             m_operation = NoCompletion;
1105             return;
1106         }
1107         if ( allExpressions.length() >= 4 ) {
1108             // TODO: optimally, filter out classes we already inherit from. That's a bonus, tough.
1109             if ( allExpressions.at(1).status == ExpressionParser::ClassFound ) {
1110                 // show only items of type "class" for completion
1111                 if ( allExpressions.at(allExpressions.length()-1).status == ExpressionParser::MemberAccessFound ) {
1112                     m_guessTypeOfExpression = allExpressions.at(allExpressions.length()-2).expression;
1113                 }
1114                 m_operation = InheritanceCompletion;
1115                 return;
1116             }
1117         }
1118     }
1119     
1120     // For something like "func1(3, 5, func2(7, ", we want to show all calltips recursively
1121     summonParentForEventualCall(allExpressions, textWithoutStrings);
1122     
1123     if ( firstStatus == ExpressionParser::MeaninglessKeywordFound ) {
1124         m_operation = DefaultCompletion;
1125         return;
1126     }
1127     
1128     if ( firstStatus == ExpressionParser::InFound ) {
1129         m_operation = DefaultCompletion;
1130         m_itemTypeHint = IterableRequested;
1131         return;
1132     }
1133     
1134     if ( firstStatus == ExpressionParser::NoCompletionKeywordFound ) {
1135         m_operation = NoCompletion;
1136         return;
1137     }
1138     
1139     if ( firstStatus == ExpressionParser::RaiseFound || firstStatus == ExpressionParser::ExceptFound ) {
1140         m_operation = RaiseExceptionCompletion;
1141         if ( firstStatus == ExpressionParser::ExceptFound ) {
1142             m_itemTypeHint = ClassTypeRequested;
1143         }
1144         return;
1145     }
1146 
1147     if ( firstStatus == ExpressionParser::NothingFound ) {
1148         m_operation = NewStatementCompletion;
1149         return;
1150     }
1151     
1152     if ( firstStatus == ExpressionParser::DefFound ) {
1153         if ( context->type() == DUContext::Class ) {
1154             m_indent = QString(" ").repeated(indents.indentForLine(indents.linesCount()-1));
1155             m_operation = DefineCompletion;
1156         }
1157         else {
1158             qCDebug(KDEV_PYTHON_CODECOMPLETION) << "def outside class context";
1159             m_operation = NoCompletion;
1160         }
1161         return;
1162     }
1163     
1164     // The "def in class context" case is handled above already
1165     if ( defKeywords.contains(firstStatus) ) {
1166         m_operation = NoCompletion;
1167         return;
1168     }
1169     
1170     if ( firstStatus == ExpressionParser::ForFound ) {
1171         int offset = allExpressions.length() - 2; // one for the "for", and one for the general off-by-one thing
1172         QPair<int, int> nextInitializer = allExpressions.nextIndexOfStatus(ExpressionParser::InitializerFound);
1173         if ( nextInitializer.first == -1 ) {
1174             // no opening bracket, so no generator completion.
1175             m_operation = NoCompletion;
1176             return;
1177         }
1178         // check that all statements in between are part of a generator initializer list
1179         bool ok = true;
1180         QStringList exprs;
1181         int currentOffset = 0;
1182         while ( ok && offset > currentOffset ) {
1183             ok = allExpressions.at(offset).status == ExpressionParser::ExpressionFound;
1184             if ( ! ok ) break;
1185             exprs.prepend(allExpressions.at(offset).expression);
1186             offset -= 1;
1187             ok = allExpressions.at(offset).status == ExpressionParser::CommaFound;
1188             // the last expression must *not* have a comma
1189             currentOffset = allExpressions.length() - nextInitializer.first;
1190             if ( ! ok && currentOffset == offset ) {
1191                 ok = true;
1192             }
1193             offset -= 1;
1194         }
1195         if ( ok ) {
1196             m_guessTypeOfExpression = exprs.join(",");
1197             m_operation = GeneratorVariableCompletion;
1198             return;
1199         }
1200         else {
1201             m_operation = NoCompletion;
1202             return;
1203         }
1204     }
1205     
1206     QPair<int, int> import = allExpressions.nextIndexOfStatus(ExpressionParser::ImportFound);
1207     QPair<int, int> from = allExpressions.nextIndexOfStatus(ExpressionParser::FromFound);
1208     int importIndex = import.first;
1209     int fromIndex = from.first;
1210     
1211     if ( importIndex != -1 && fromIndex != -1 ) {
1212         // it's either "import ... from" or "from ... import"
1213         // we treat both in exactly the same way. This is not quite correct, as python
1214         // forbids some combinations. TODO fix this.
1215         
1216         // There's two relevant pieces of text, the one between the "from...import" (or "import...from"),
1217         // and the one behind the "import" or the "from" (at the end of the line).
1218         // Both need to be extracted and passed to the completion computer.
1219         QString firstPiece, secondPiece;
1220         if ( fromIndex > importIndex ) {
1221             // import ... from
1222             if ( fromIndex == allExpressions.length() ) {
1223                 // The "from" is the last item in the chain
1224                 m_operation = ImportFileCompletion;
1225                 return;
1226             }
1227             firstPiece = allExpressions.at(allExpressions.length() - importIndex - 1).expression;
1228         }
1229         else {
1230             firstPiece = allExpressions.at(allExpressions.length() - fromIndex - 1).expression;
1231         }
1232         // might be "from foo import bar as baz, bang as foobar, ..."
1233         if ( allExpressions.length() > 4 && firstStatus == ExpressionParser::MemberAccessFound ) {
1234             secondPiece = allExpressions.at(allExpressions.length() - 2).expression;
1235         }
1236         
1237         if ( fromIndex < importIndex ) {
1238             // if it's "from ... import", swap the two pieces.
1239             qSwap(firstPiece, secondPiece);
1240         }
1241         
1242         if ( firstPiece.isEmpty() ) {
1243             m_searchImportItemsInModule = secondPiece;
1244         }
1245         else if ( secondPiece.isEmpty() ) {
1246             m_searchImportItemsInModule = firstPiece;
1247         }
1248         else {
1249             m_searchImportItemsInModule = firstPiece + "." + secondPiece;
1250         }
1251         qCDebug(KDEV_PYTHON_CODECOMPLETION) << firstPiece << secondPiece;
1252         qCDebug(KDEV_PYTHON_CODECOMPLETION) << "Got submodule to search:" << m_searchImportItemsInModule << "from text" << textWithoutStrings;
1253         m_operation = ImportSubCompletion;
1254         return;
1255     }
1256     
1257     if ( firstStatus == ExpressionParser::FromFound || firstStatus == ExpressionParser::ImportFound ) {
1258         // it's either "from ..." or "import ...", which both come down to the same completion offered
1259         m_operation = ImportFileCompletion;
1260         return;
1261     }
1262     
1263     if ( fromIndex != -1 || importIndex != -1 ) {
1264         if ( firstStatus == ExpressionParser::CommaFound ) {
1265             // "import foo.bar, "
1266             m_operation = ImportFileCompletion;
1267             return;
1268         }
1269         if ( firstStatus == ExpressionParser::MemberAccessFound ) {
1270             m_operation = ImportSubCompletion;
1271             m_searchImportItemsInModule = allExpressions.at(allExpressions.length() - 2).expression;
1272             return;
1273         }
1274         // "from foo ..."
1275         m_operation = NoCompletion;
1276         return;
1277     }
1278     
1279     if ( firstStatus == ExpressionParser::MemberAccessFound ) {
1280         TokenListEntry item = allExpressions.weakPop();
1281         if ( item.status == ExpressionParser::ExpressionFound ) {
1282             m_guessTypeOfExpression = item.expression;
1283             m_operation = MemberAccessCompletion;
1284         }
1285         else {
1286             m_operation = NoCompletion;
1287         }
1288         return;
1289     }
1290 
1291     if ( firstStatus == ExpressionParser::EqualsFound && allExpressions.length() >= 2 ) {
1292         m_operation = DefaultCompletion;
1293         m_matchAgainst = allExpressions.at(allExpressions.length() - 2).expression;
1294     }
1295 }
1296 
1297 }