diff --git a/codecompletion/items/functiondeclaration.cpp b/codecompletion/items/functiondeclaration.cpp index a686d257..2abccfb9 100644 --- a/codecompletion/items/functiondeclaration.cpp +++ b/codecompletion/items/functiondeclaration.cpp @@ -1,185 +1,186 @@ /***************************************************************************** * Copyright (c) 2011 Sven Brauch * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of * * the License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************** */ #include "functiondeclaration.h" #include #include #include #include #include #include #include #include #include #include "duchain/navigation/navigationwidget.h" #include "codecompletion/helpers.h" #include "declaration.h" #include "declarations/functiondeclaration.h" #include "duchain/helpers.h" #include #include using namespace KDevelop; using namespace KTextEditor; namespace Python { FunctionDeclarationCompletionItem::FunctionDeclarationCompletionItem(DeclarationPointer decl, CodeCompletionContext::Ptr context) : PythonDeclarationCompletionItem(decl, context) , m_atArgument(-1) , m_depth(0) , m_doNotCall(false) { } int FunctionDeclarationCompletionItem::atArgument() const { return m_atArgument; } void FunctionDeclarationCompletionItem::setDepth(int d) { m_depth = d; } void FunctionDeclarationCompletionItem::setAtArgument(int d) { m_atArgument = d; } int FunctionDeclarationCompletionItem::argumentHintDepth() const { return m_depth; } QVariant FunctionDeclarationCompletionItem::data(const QModelIndex& index, int role, const KDevelop::CodeCompletionModel* model) const { DUChainReadLocker lock; FunctionDeclaration* dec = dynamic_cast(m_declaration.data()); switch ( role ) { case Qt::DisplayRole: { if ( ! dec ) { break; // use the default } if ( index.column() == KDevelop::CodeCompletionModel::Arguments ) { if (FunctionType::Ptr functionType = dec->type()) { QString ret; createArgumentList(dec, ret, nullptr, 0, false); return ret; } } if ( index.column() == KDevelop::CodeCompletionModel::Prefix ) { FunctionType::Ptr type = dec->type(); if ( type && type->returnType() ) { return i18n("function") + " -> " + type->returnType()->toString(); } } break; } case KDevelop::CodeCompletionModel::HighlightingMethod: { if ( index.column() == KDevelop::CodeCompletionModel::Arguments ) return QVariant(KDevelop::CodeCompletionModel::CustomHighlighting); break; } case KDevelop::CodeCompletionModel::CustomHighlight: { if ( index.column() == KDevelop::CodeCompletionModel::Arguments ) { if ( ! dec ) return QVariant(); QString ret; QList highlight; if ( atArgument() ) { createArgumentList(dec, ret, &highlight, atArgument(), false); } else { createArgumentList(dec, ret, nullptr, false); } return QVariant(highlight); } + break; } case KDevelop::CodeCompletionModel::MatchQuality: { if ( m_typeHint == PythonCodeCompletionContext::IterableRequested && dec && dec->type() && dynamic_cast(dec->type()->returnType().data()) ) { return 2 + PythonDeclarationCompletionItem::data(index, role, model).toInt(); } return PythonDeclarationCompletionItem::data(index, role, model); } } return Python::PythonDeclarationCompletionItem::data(index, role, model); } void FunctionDeclarationCompletionItem::setDoNotCall(bool doNotCall) { m_doNotCall = doNotCall; } void FunctionDeclarationCompletionItem::executed(KTextEditor::View* view, const KTextEditor::Range& word) { qCDebug(KDEV_PYTHON_CODECOMPLETION) << "FunctionDeclarationCompletionItem executed"; KTextEditor::Document* document = view->document(); auto resolvedDecl = Helper::resolveAliasDeclaration(declaration().data()); DUChainReadLocker lock; auto functionDecl = Helper::functionForCalled(resolvedDecl).declaration; lock.unlock(); if ( ! functionDecl && (! resolvedDecl || ! resolvedDecl->abstractType() || resolvedDecl->abstractType()->whichType() != AbstractType::TypeStructure) ) { qCritical(KDEV_PYTHON_CODECOMPLETION) << "ERROR: could not get declaration data, not executing completion item!"; return; } QString suffix = "()"; KTextEditor::Range checkPrefix(word.start().line(), 0, word.start().line(), word.start().column()); KTextEditor::Range checkSuffix(word.end().line(), word.end().column(), word.end().line(), document->lineLength(word.end().line())); if ( m_doNotCall || document->text(checkSuffix).trimmed().startsWith('(') || document->text(checkPrefix).trimmed().endsWith('@') || (functionDecl && functionDecl->isProperty()) ) { // don't insert brackets if they're already there, // the item is a decorator, or if it's an import item. suffix.clear(); } // place cursor behind bracktes by default int skip = 2; if ( functionDecl ) { bool needsArguments = false; int argumentCount = functionDecl->type()->arguments().length(); if ( functionDecl->context()->type() == KDevelop::DUContext::Class ) { // it's a member function, so it has the implicit self // TODO static methods needsArguments = argumentCount > 1; } else { // it's a free function needsArguments = argumentCount > 0; } if ( needsArguments ) { // place cursor in brackets if there's parameters skip = 1; } } document->replaceText(word, declaration()->identifier().toString() + suffix); view->setCursorPosition( Cursor(word.end().line(), word.end().column() + skip) ); } FunctionDeclarationCompletionItem::~FunctionDeclarationCompletionItem() { } } diff --git a/codecompletion/items/implementfunction.cpp b/codecompletion/items/implementfunction.cpp index 5c619b43..0fb14c6d 100644 --- a/codecompletion/items/implementfunction.cpp +++ b/codecompletion/items/implementfunction.cpp @@ -1,86 +1,87 @@ /***************************************************************************** * Copyright (c) 2011 Sven Brauch * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of * * the License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************** */ #include "implementfunction.h" #include #include #include #include #include #include #include #include #include using namespace KDevelop; using namespace KTextEditor; namespace Python { ImplementFunctionCompletionItem::ImplementFunctionCompletionItem(const QString& name, const QStringList& arguments, const QString& previousIndent) : m_arguments(arguments), m_name(name), m_previousIndent(previousIndent) { } void ImplementFunctionCompletionItem::execute(KTextEditor::View* view, const KTextEditor::Range& word) { auto document = view->document(); const QString finalText = m_name + "(" + m_arguments.join(", ") + "):"; document->replaceText(word, finalText); // 4 spaces is indentation for python. everyone does it like this. you must, too. // TODO use kate settings document->insertLine(word.start().line() + 1, m_previousIndent + " "); if ( View* view = static_cast(ICore::self()->partController())->activeView() ) { view->setCursorPosition(Cursor(word.end().line() + 1, m_previousIndent.length() + 4)); } } QVariant ImplementFunctionCompletionItem::data(const QModelIndex& index, int role, const KDevelop::CodeCompletionModel* model) const { switch ( role ) { case KDevelop::CodeCompletionModel::MatchQuality: { return QVariant(m_name.startsWith("__") ? 0 : 10); } case KDevelop::CodeCompletionModel::BestMatchesCount: { return QVariant(5); } case Qt::DisplayRole: switch ( index.column() ) { case KDevelop::CodeCompletionModel::Name: return m_name + "(" + m_arguments.join(", ") + ")"; case KDevelop::CodeCompletionModel::Postfix: return ""; case KDevelop::CodeCompletionModel::Prefix: return "Override method"; default: return ""; } case Qt::DecorationRole: if( index.column() == KDevelop::CodeCompletionModel::Icon ) { KDevelop::CodeCompletionModel::CompletionProperties p(KDevelop::CodeCompletionModel::Function); return DUChainUtils::iconForProperties(p); } + // Fall through default: return CompletionTreeItem::data(index, role, model); } } } // namespace Python diff --git a/codegen/correctionfilegenerator.cpp b/codegen/correctionfilegenerator.cpp index 75229716..bb79a25d 100644 --- a/codegen/correctionfilegenerator.cpp +++ b/codegen/correctionfilegenerator.cpp @@ -1,506 +1,507 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2013 Sven Brauch * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "correctionfilegenerator.h" #include #include #include #include #include #include #include #include #include #include #include #include "duchain/helpers.h" #include "parser/codehelpers.h" #include #include #include "codegendebug.h" using namespace KDevelop; namespace Python { TypeCorrection::TypeCorrection() : m_ui(new Ui::CorrectionWidget) { } TypeCorrection &TypeCorrection::self() { static TypeCorrection instance; return instance; } void TypeCorrection::doContextMenu(ContextMenuExtension &extension, Context *context) { if ( DeclarationContext* declContext = dynamic_cast(context) ) { qRegisterMetaType("KDevelop::IndexedDeclaration"); DUChainReadLocker lock; KDevelop::Declaration* declaration = declContext->declaration().data(); if ( declaration && (declaration->kind() == Declaration::Instance || (declaration->kind() == Declaration::Type && declaration->abstractType()->whichType() == AbstractType::TypeFunction)) ) { QAction* action = new QAction(i18n("Specify type for \"%1\"...", declaration->qualifiedIdentifier().toString()), nullptr); action->setData(QVariant::fromValue(IndexedDeclaration(declaration))); action->setIcon(QIcon::fromTheme("code-class")); connect(action, &QAction::triggered, this, &TypeCorrection::executeSpecifyTypeAction); extension.addAction(ContextMenuExtension::ExtensionGroup, action); } } } void TypeCorrection::executeSpecifyTypeAction() { QAction* action = qobject_cast(sender()); if ( ! action ) { qCWarning(KDEV_PYTHON_CODEGEN) << "slot not invoked by triggering a QAction, should not happen"; // :) return; } DUChainReadLocker lock; IndexedDeclaration decl = action->data().value(); if ( ! decl.isValid() ) { decl = Helper::declarationUnderCursor(); } if ( ! decl.isValid() ) { qCWarning(KDEV_PYTHON_CODEGEN) << "No declaration found!"; return; } CorrectionFileGenerator::HintType hintType; if ( decl.data()->isFunctionDeclaration() ) { hintType = CorrectionFileGenerator::FunctionReturnHint; } else if ( decl.data()->kind() == Declaration::Instance ) { hintType = CorrectionFileGenerator::LocalVariableHint; } else { qCWarning(KDEV_PYTHON_CODEGEN) << "Correction requested for something that's not a local variable or function."; return; } CorrectionAssistant *dialog = new CorrectionAssistant(decl, hintType); dialog->setAttribute(Qt::WA_DeleteOnClose); dialog->setWindowTitle("Specify type for " + decl.data()->identifier().toString()); connect(dialog, &QDialog::accepted, this, &TypeCorrection::accepted); m_ui->setupUi(dialog); connect(m_ui->buttonBox, &QDialogButtonBox::accepted, dialog, &QDialog::accept); connect(m_ui->buttonBox, &QDialogButtonBox::rejected, dialog, &QDialog::reject); if ( hintType == CorrectionFileGenerator::FunctionReturnHint ) { m_ui->kindLabel->setText(i18n("Function return type")); } else if ( hintType == CorrectionFileGenerator::LocalVariableHint ) { m_ui->kindLabel->setText(i18n("Local variable")); } m_ui->identifierLabel->setText(decl.data()->qualifiedIdentifier().toString()); m_ui->typeText->setFocus(); dialog->resize(560, 180); lock.unlock(); dialog->show(); } void TypeCorrection::accepted() { CorrectionAssistant *dialog = qobject_cast(sender()); Q_ASSERT(dialog); if ( ! dialog ) { qCWarning(KDEV_PYTHON_CODEGEN) << "accepted() called without a sender"; return; } DUChainReadLocker lock; IndexedDeclaration decl; decl = dialog->declaration(); if ( ! decl.isValid() ) { decl = Helper::declarationUnderCursor(); } if ( ! decl.isValid() ) { qCWarning(KDEV_PYTHON_CODEGEN) << "No declaration found!"; return; } auto correctionFile = Helper::getLocalCorrectionFile(decl.data()->topContext()->url().toUrl()); if ( correctionFile.isEmpty() ) { KMessageBox::error(nullptr, i18n("Sorry, cannot create hints for files which are not part of a project.")); return; } CorrectionFileGenerator generator(correctionFile.path()); CorrectionFileGenerator::HintType hintType = dialog->hintType(); generator.addHint(m_ui->typeText->text(), m_ui->importsText->text().split(',', QString::SkipEmptyParts), decl.data(), hintType); qCDebug(KDEV_PYTHON_CODEGEN) << "Forcing a reparse on " << decl.data()->topContext()->url(); ICore::self()->languageController()->backgroundParser()->addDocument(IndexedString(decl.data()->topContext()->url()), TopDUContext::ForceUpdate); ICore::self()->languageController()->backgroundParser()->addDocument(IndexedString(correctionFile), TopDUContext::ForceUpdate); } CorrectionFileGenerator::CorrectionFileGenerator(const QString &filePath) : m_file(filePath) , m_filePath(filePath) { Q_ASSERT(! filePath.isEmpty()); qCDebug(KDEV_PYTHON_CODEGEN) << "Correction file path: " << filePath; QFileInfo info(m_file); if ( ! info.absoluteDir().exists() ) { qCDebug(KDEV_PYTHON_CODEGEN) << "Directory does not exist. Creating..."; info.absoluteDir().mkpath(info.absolutePath()); } m_file.open(QFile::ReadWrite); m_code = QString(m_file.readAll()).split('\n'); m_oldContents = m_code; m_fileIndents.reset(new FileIndentInformation(m_code)); } void CorrectionFileGenerator::addHint(const QString &typeCode, const QStringList &modules, Declaration *forDeclaration, CorrectionFileGenerator::HintType hintType) { if ( ! forDeclaration || ! forDeclaration->context() ) { qCWarning(KDEV_PYTHON_CODEGEN) << "Declaration does not have context!" << (forDeclaration ? forDeclaration->toString() : ""); return; } DUContext* context = forDeclaration->context(); if ( context->type() == DUContext::Function ) { auto otherImporters = context->importers(); if ( otherImporters.isEmpty() ) { return; } context = otherImporters.first(); } // We're in a class if the context of the declaration is a Class or if its // parent context is a class. This is because a function body has a context // of type Other. bool inClass = context->type() == DUContext::Class || (context->parentContext() && context->parentContext()->type() == DUContext::Class); // If the declaration is part of the function's arguments or it's parent // context is one of a function. bool inFunction = context->type() == DUContext::Function || (context->owner() && context->owner()->abstractType()->whichType() == AbstractType::TypeFunction); qCDebug(KDEV_PYTHON_CODEGEN) << "Are we in a class: " << inClass; qCDebug(KDEV_PYTHON_CODEGEN) << "Are we in a function: " << inFunction; QString enclosingClassIdentifier, enclosingFunctionIdentifier; if ( context->owner() ) { if ( inClass && inFunction ) { Declaration *functionDeclaration = context->owner(); enclosingClassIdentifier = functionDeclaration->context()->owner()->identifier().identifier().str(); enclosingFunctionIdentifier = functionDeclaration->identifier().identifier().str(); } else if ( inClass ) { enclosingClassIdentifier = context->owner()->identifier().identifier().str(); } else if ( inFunction ) { enclosingFunctionIdentifier = context->owner()->identifier().identifier().str(); } } qCDebug(KDEV_PYTHON_CODEGEN) << "Enclosing class: " << enclosingClassIdentifier; qCDebug(KDEV_PYTHON_CODEGEN) << "Enclosing function: " << enclosingFunctionIdentifier; QString declarationIdentifier = forDeclaration->identifier().identifier().str(); bool foundClassDeclaration = false; bool foundFunctionDeclaration = false; QString functionIdentifier; if ( hintType == FunctionReturnHint ) { functionIdentifier = declarationIdentifier; } else if ( hintType == LocalVariableHint ) { functionIdentifier = enclosingFunctionIdentifier; } int line = findStructureFor(enclosingClassIdentifier, functionIdentifier); if ( line == -1 ) { line = findStructureFor(enclosingClassIdentifier, QString()); } else if ( inFunction || hintType == FunctionReturnHint ) { foundFunctionDeclaration = true; } if ( line == -1 ) { line = findStructureFor(QString(), QString()); } else if ( inClass ) { foundClassDeclaration = true; } qCDebug(KDEV_PYTHON_CODEGEN) << "Found class declaration: " << foundClassDeclaration << enclosingClassIdentifier; qCDebug(KDEV_PYTHON_CODEGEN) << "Found function declaration: " << foundFunctionDeclaration << functionIdentifier; qCDebug(KDEV_PYTHON_CODEGEN) << "Line: " << line; int indentsForNextStatement = m_fileIndents->indentForLine(line); if ( foundClassDeclaration ) { indentsForNextStatement += DEFAULT_INDENT_LEVEL; } QStringList newCode; if ( inClass ) { if ( ! foundClassDeclaration ) { QString classDeclaration = createStructurePart(enclosingClassIdentifier, ClassType); classDeclaration.prepend(QString(indentsForNextStatement, ' ')); newCode.append(classDeclaration); indentsForNextStatement += DEFAULT_INDENT_LEVEL; } else { line++; } } if ( inFunction || hintType == FunctionReturnHint ) { if ( ! foundFunctionDeclaration ) { QString functionDeclaration; if ( inClass ) { functionDeclaration = createStructurePart(functionIdentifier, MemberFunctionType); } else { functionDeclaration = createStructurePart(functionIdentifier, FunctionType); } functionDeclaration.prepend(QString(indentsForNextStatement, ' ')); newCode.append(functionDeclaration); indentsForNextStatement += DEFAULT_INDENT_LEVEL; } else { line++; } } if ( foundFunctionDeclaration && ! foundClassDeclaration ) { indentsForNextStatement += DEFAULT_INDENT_LEVEL; } QString hintCode; if ( hintType == FunctionReturnHint ) { hintCode = "returns = " + typeCode; } else if ( hintType == LocalVariableHint ) { hintCode = "l_" + declarationIdentifier + " = " + typeCode; } qCDebug(KDEV_PYTHON_CODEGEN) << "Hint code: " << hintCode; hintCode.prepend(QString(indentsForNextStatement, ' ')); newCode.append(hintCode); for ( int i = 0; i < newCode.length(); i++ ) { m_code.insert(line + i, newCode.at(i)); } // We safely insert any import declaration at the top foreach ( const QString &moduleName, modules ) { bool importExists = false; foreach (const QString &line, m_code) { if ( ! line.startsWith("import") && ! line.startsWith("from") && ! line.isEmpty() ) { break; } // In both import ... and from ... import ..., the second part is what we want if ( line.section(' ', 1, 1, QString::SectionSkipEmpty) == moduleName.trimmed() ) { importExists = true; } } if ( ! importExists ) { m_code.prepend("import " + moduleName.trimmed()); } } QTemporaryFile temp; if ( checkForValidSyntax() && temp.open() ) { qCDebug(KDEV_PYTHON_CODEGEN) << "File path: " << m_file.fileName(); qCDebug(KDEV_PYTHON_CODEGEN) << "Temporary file path: " << temp.fileName(); QTextStream stream(&temp); stream << m_code.join("\n"); m_fileIndents.reset(new FileIndentInformation(m_code)); stream.flush(); bool success = m_file.remove(); success = success ? QFile::rename(temp.fileName(), m_file.fileName()) : false; if ( success && m_file.open(QFile::ReadWrite) ) { qCDebug(KDEV_PYTHON_CODEGEN) << "Successfully saved correction file."; m_oldContents = m_code; } else { m_code = m_oldContents; } } else { qCDebug(KDEV_PYTHON_CODEGEN) << "Something went wrong, reverting changes to correction file"; m_code = m_oldContents; } } class StructureFindVisitor : public Python::AstDefaultVisitor { public: StructureFindVisitor(const QString &klass, const QString &function) : m_line(-1) { if ( ! klass.isNull() ) { m_searchedDeclaration.push(klass); } if ( ! function.isNull() ) { m_searchedDeclaration.push(function); } } void visitFunctionDefinition(FunctionDefinitionAst* node) override { m_declaration.push(node->name->value); if ( m_declaration == m_searchedDeclaration ) { m_line = node->startLine; } AstDefaultVisitor::visitFunctionDefinition(node); m_declaration.pop(); } void visitClassDefinition(ClassDefinitionAst* node) override { m_declaration.push(node->name->value); if ( m_declaration == m_searchedDeclaration ) { m_line = node->startLine; } AstDefaultVisitor::visitClassDefinition(node); m_declaration.pop(); } int line() const { return m_line; } private: QStack m_searchedDeclaration; QStack m_declaration; int m_line; }; int CorrectionFileGenerator::findStructureFor(const QString &klass, const QString &function) { // If we're not looking for a specific thing, return the end of the file if ( klass.isNull() && function.isNull() ) { return m_code.length() - 1; } ParseSession parseSession; parseSession.setContents(m_code.join("\n")); parseSession.setCurrentDocument(IndexedString(m_filePath)); QPair parsed = parseSession.parse(); QString classIdentifier = ( ! klass.isNull() ) ? "class_" + klass : QString(); QString functionIdentifier = ( ! function.isNull() ) ? "function_" + function : QString(); StructureFindVisitor visitor(classIdentifier, functionIdentifier); visitor.visitCode(parsed.first.data()); return visitor.line(); } QString CorrectionFileGenerator::createStructurePart(const QString &identifierSuffix, CorrectionFileGenerator::StructureType type) { QString code; QString params; switch ( type ) { case ClassType: code = "class class_" + identifierSuffix + ":"; break; case MemberFunctionType: params = "self"; + // Fall through case FunctionType: code = "def function_" + identifierSuffix + "(" + params + "):"; break; } return code; } bool CorrectionFileGenerator::checkForValidSyntax() { ParseSession parseSession; parseSession.setContents(m_code.join("\n")); parseSession.setCurrentDocument(IndexedString(m_filePath)); QPair parsed = parseSession.parse(); return parsed.second && parseSession.m_problems.isEmpty(); } CorrectionAssistant::CorrectionAssistant(IndexedDeclaration declaration, CorrectionFileGenerator::HintType hintType, QWidget *parent) : QDialog(parent), m_declaration(declaration), m_hintType(hintType) { } IndexedDeclaration CorrectionAssistant::declaration() const { return m_declaration; } CorrectionFileGenerator::HintType CorrectionAssistant::hintType() const { return m_hintType; } }