File indexing completed on 2024-04-21 15:24:06

0001 /*
0002     SPDX-FileCopyrightText: 2009 Milian Wolff <mail@milianw.de>
0003     Basec on Cpp ImplementationHelperItem
0004 
0005     SPDX-License-Identifier: LGPL-2.0-or-later
0006 */
0007 
0008 #include "implementationitem.h"
0009 
0010 #include <KLocalizedString>
0011 
0012 #include <KTextEditor/Document>
0013 #include <KTextEditor/View>
0014 
0015 #include <language/duchain/duchain.h>
0016 #include <language/duchain/duchainlock.h>
0017 #include <language/duchain/declaration.h>
0018 #include <language/duchain/types/functiontype.h>
0019 #include <language/duchain/duchainutils.h>
0020 #include <language/duchain/classdeclaration.h>
0021 #include <language/duchain/types/integraltype.h>
0022 
0023 #include <language/codecompletion/codecompletionmodel.h>
0024 
0025 #include "declarations/classmethoddeclaration.h"
0026 
0027 #include "completiondebug.h"
0028 #include "helpers.h"
0029 
0030 using namespace KDevelop;
0031 
0032 namespace Php
0033 {
0034 
0035 #define RETURN_CACHED_ICON(name) {static QIcon icon(QIcon::fromTheme(QStringLiteral(name)).pixmap(QSize(16, 16))); return icon;}
0036 
0037 QVariant ImplementationItem::data(const QModelIndex& index, int role, const CodeCompletionModel* model) const
0038 {
0039     QVariant ret = NormalDeclarationCompletionItem::data(index, role, model);
0040     switch (role) {
0041     case Qt::DecorationRole:
0042         if (index.column() == KTextEditor::CodeCompletionModel::Icon) {
0043             switch (m_type) {
0044             case Override:
0045             case OverrideVar:
0046                 RETURN_CACHED_ICON("CTparents");
0047             case Implement:
0048                 RETURN_CACHED_ICON("CTsuppliers");
0049             }
0050         }
0051         break;
0052     case Qt::DisplayRole:
0053         if (index.column() == KTextEditor::CodeCompletionModel::Prefix) {
0054             QString prefix;
0055             switch (m_type) {
0056             case Override:
0057             case OverrideVar:
0058                 prefix = i18n("Override");
0059                 break;
0060             case Implement:
0061                 prefix = i18n("Implement");
0062                 break;
0063             }
0064 
0065             ret = prefix + ' ' + ret.toString();
0066         }
0067         //TODO column == Name - required?
0068         break;
0069     case KTextEditor::CodeCompletionModel::ItemSelected: {
0070         DUChainReadLocker lock(DUChain::lock());
0071         if (declaration().data()) {
0072             QualifiedIdentifier parentScope = declaration()->context()->scopeIdentifier(true);
0073             return i18n("From %1", parentScope.toString());
0074         }
0075     }
0076     break;
0077     case KTextEditor::CodeCompletionModel::InheritanceDepth:
0078         return QVariant(0);
0079     default:
0080         //pass
0081         break;
0082     }
0083 
0084     return ret;
0085 }
0086 
0087 void ImplementationItem::execute(KTextEditor::View* view, const KTextEditor::Range& word)
0088 {
0089     DUChainReadLocker lock(DUChain::lock());
0090     KTextEditor::Document *document = view->document();
0091 
0092     QString replText;
0093 
0094     if (m_declaration) {
0095         //TODO:respect custom code styles
0096 
0097         // get existing modifiers so we can respect the user's choice of public/protected and final
0098         QStringList modifiers = getMethodTokens(document->text(KTextEditor::Range(KTextEditor::Cursor::start(), word.start())));
0099         // get range to replace
0100         KTextEditor::Range replaceRange(word);
0101         if (!modifiers.isEmpty()) {
0102             // TODO: is there no easy API to map QString Index to a KTextEditor::Cursor ?!
0103             QString methodText = document->text(KTextEditor::Range(KTextEditor::Cursor::start(), word.start()));
0104             methodText = methodText.left(methodText.lastIndexOf(modifiers.last(), -1, Qt::CaseInsensitive));
0105             replaceRange.start() = KTextEditor::Cursor(methodText.count('\n'), methodText.length() - methodText.lastIndexOf('\n') - 1);
0106         }
0107 
0108         // get indentation
0109         QString indentation;
0110         {
0111             QString currentLine = document->line(replaceRange.start().line());
0112             indentation = getIndentation(currentLine);
0113 
0114             if ( !currentLine.isEmpty() && currentLine != indentation ) {
0115                 // since theres some non-whitespace in this line, skip to the enxt one
0116                 replText += '\n' + indentation;
0117             }
0118 
0119             if (indentation.isEmpty()) {
0120                 // use a minimal indentation
0121                 // TODO: respect code style
0122                 indentation = QStringLiteral("  ");
0123                 replText += indentation;
0124             }
0125         }
0126 
0127         #if 0
0128         //Disabled, because not everyone writes phpdoc for every function
0129         //TODO: move to a phpdoc helper
0130         // build phpdoc comment
0131         {
0132             QualifiedIdentifier parentClassIdentifier;
0133             if (DUContext* pctx = m_declaration->context()) {
0134                 parentClassIdentifier = pctx->localScopeIdentifier();
0135             } else {
0136                 qCDebug(COMPLETION) << "completion item for implementation has no parent context!";
0137             }
0138 
0139             replText += "/**\n" + indentation + " * ";
0140             // insert old comment:
0141             const QString indentationWithExtra = "\n" + indentation + " *";
0142             replText += m_declaration->comment().replace('\n', indentationWithExtra.toAscii().constData());
0143             replText += "\n" + indentation + " * @overload " + m_declaration->internalContext()->scopeIdentifier(true).toString();
0144             replText += "\n" + indentation + " **/\n" + indentation;
0145         }
0146         #endif
0147 
0148         // write function signature
0149 
0150         // copy existing modifiers
0151         if (!modifiers.isEmpty()) {
0152             // the tokens are in a bad order and there's no reverse method or similar, so we can't simply join the tokens
0153             QStringList::const_iterator i = modifiers.constEnd() - 1;
0154             while (true) {
0155                 replText += (*i) + ' ';
0156                 if (i == modifiers.constBegin()) {
0157                     break;
0158                 } else {
0159                     --i;
0160                 }
0161             }
0162         }
0163 
0164         QString functionName;
0165         bool isConstructorOrDestructor = false;
0166         bool isInterface = false;
0167 
0168         if (ClassMemberDeclaration* member = dynamic_cast<ClassMemberDeclaration*>(m_declaration.data())) {
0169             // NOTE: it should _never_ be private - but that's the completionmodel / context / worker's job
0170             if (!modifiers.contains(QStringLiteral("public")) && !modifiers.contains(QStringLiteral("protected"))) {
0171                 if (member->accessPolicy() == Declaration::Protected) {
0172                     replText += QLatin1String("protected ");
0173                 } else {
0174                     replText += QLatin1String("public ");
0175                 }
0176             }
0177             if (!modifiers.contains(QStringLiteral("static")) && member->isStatic()) {
0178                 replText += QLatin1String("static ");
0179             }
0180             functionName = member->identifier().toString();
0181 
0182             ClassMethodDeclaration* method = dynamic_cast<ClassMethodDeclaration*>(m_declaration.data());
0183             if (method) {
0184                 functionName = method->prettyName().str();
0185                 isConstructorOrDestructor = method->isConstructor() || method->isDestructor();
0186             }
0187 
0188             if (member->context() && member->context()->owner()) {
0189                 ClassDeclaration* classDec = dynamic_cast<ClassDeclaration*>(member->context()->owner());
0190                 if (classDec) {
0191                     isInterface = (classDec->classType() == ClassDeclarationData::Interface);
0192                 }
0193             }
0194         } else {
0195             qCDebug(COMPLETION) << "completion item for implementation was not a classfunction declaration!";
0196             functionName = m_declaration->identifier().toString();
0197         }
0198 
0199         if (m_type == ImplementationItem::OverrideVar) {
0200             replText += "$" + functionName + " = ";
0201         } else {
0202             if (!modifiers.contains(QStringLiteral("function"))) {
0203                 replText += QLatin1String("function ");
0204             }
0205 
0206             replText += functionName;
0207 
0208             {
0209                 // get argument list
0210                 QString arguments;
0211                 createArgumentList(*this, arguments, nullptr, true);
0212                 replText += arguments;
0213             }
0214 
0215             QString arguments;
0216             QVector<Declaration*> parameters;
0217             if (DUChainUtils::argumentContext(m_declaration.data()))
0218                 parameters = DUChainUtils::argumentContext(m_declaration.data())->localDeclarations();
0219             arguments = '(';
0220             bool first = true;
0221             foreach(Declaration* dec, parameters) {
0222                 if (first)
0223                     first = false;
0224                 else
0225                     arguments += QLatin1String(", ");
0226 
0227                 arguments += '$' + dec->identifier().toString();
0228             }
0229             arguments += ')';
0230 
0231             bool voidReturnType = false;
0232             if (auto functionType = m_declaration->abstractType().dynamicCast<FunctionType>()) {
0233                 AbstractType::Ptr retType = functionType->returnType();
0234                 if (retType->equals(new IntegralType(IntegralType::TypeVoid))) {
0235                     voidReturnType = true;
0236                 }
0237             }
0238 
0239             replText += QStringLiteral("\n%1{\n%1    ").arg(indentation);
0240             if (isInterface || m_type == ImplementationItem::Implement) {
0241             } else if (!isConstructorOrDestructor && !voidReturnType) {
0242                 replText += QStringLiteral("$ret = parent::%2%3;\n%1    return $ret;").arg(indentation, functionName, arguments);
0243             } else {
0244                 replText += QStringLiteral("parent::%1%2;").arg(functionName, arguments);
0245             }
0246             replText += QStringLiteral("\n%1}\n%1")
0247                     .arg(indentation);
0248 
0249         }
0250 
0251 
0252         //TODO: properly place the cursor inside the {} part
0253         document->replaceText(replaceRange, replText);
0254 
0255     } else {
0256         qCDebug(COMPLETION) << "Declaration disappeared";
0257     }
0258 }
0259 
0260 }