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 }