File indexing completed on 2024-04-14 14:47:58

0001 /*
0002     SPDX-FileCopyrightText: 2007 Piyush verma <piyush.verma@gmail.com>
0003     SPDX-FileCopyrightText: 2009 Niko Sams <niko.sams@gmail.com>
0004     SPDX-FileCopyrightText: 2010 Milian Wolff <mail@milianw.de>
0005 
0006     SPDX-License-Identifier: GPL-2.0-or-later
0007 */
0008 
0009 #include "phplanguagesupport.h"
0010 
0011 #include <QMutexLocker>
0012 #include <QReadWriteLock>
0013 
0014 #include <kpluginfactory.h>
0015 #include <kpluginloader.h>
0016 #include <KTextEditor/Document>
0017 
0018 #include <interfaces/icore.h>
0019 #include <interfaces/ilanguagecontroller.h>
0020 #include <interfaces/iplugincontroller.h>
0021 #include <interfaces/idocument.h>
0022 #include <interfaces/iproject.h>
0023 #include <language/backgroundparser/backgroundparser.h>
0024 #include <language/duchain/duchain.h>
0025 #include <language/duchain/duchainlock.h>
0026 #include <interfaces/idocumentcontroller.h>
0027 #include <interfaces/contextmenuextension.h>
0028 #include <language/interfaces/editorcontext.h>
0029 
0030 #include "phpparsejob.h"
0031 #include "phphighlighting.h"
0032 #include "kdevphpversion.h"
0033 #include "phpdebug.h"
0034 #include "codegen/refactoring.h"
0035 
0036 #include <language/codecompletion/codecompletion.h>
0037 #include <language/codecompletion/codecompletionmodel.h>
0038 
0039 #include "completion/model.h"
0040 #include "completion/worker.h"
0041 
0042 #include "navigation/navigationwidget.h"
0043 #include <language/duchain/parsingenvironment.h>
0044 
0045 #include "duchain/helper.h"
0046 #include <QTimer>
0047 
0048 using namespace KTextEditor;
0049 using namespace KDevelop;
0050 
0051 K_PLUGIN_FACTORY_WITH_JSON(KDevPhpSupportFactory, "kdevphpsupport.json", registerPlugin<Php::LanguageSupport>(); )
0052 
0053 namespace Php
0054 {
0055 
0056 LanguageSupport::LanguageSupport(QObject* parent, const QVariantList& /*args*/)
0057         : KDevelop::IPlugin(QStringLiteral("kdevphpsupport"), parent),
0058         KDevelop::ILanguageSupport()
0059 {
0060     Q_ASSERT(internalFunctionFile().toUrl().isValid());
0061 
0062     m_highlighting = new Php::Highlighting(this);
0063     m_refactoring = new Php::Refactoring(this);
0064 
0065     auto* ccModel = new CodeCompletionModel(this);
0066     new KDevelop::CodeCompletion(this, ccModel, name());
0067 }
0068 
0069 LanguageSupport::~LanguageSupport()
0070 {
0071     parseLock()->lockForWrite();
0072     //By locking the parse-mutexes, we make sure that parse- and preprocess-jobs
0073     //get a chance to finish in a good state
0074     parseLock()->unlock();
0075 }
0076 
0077 KDevelop::ParseJob *LanguageSupport::createParseJob(const IndexedString &url)
0078 {
0079     auto *job = new ParseJob(url, this);
0080 
0081     // bypass the 5 MB maximum file size limit for the internal file
0082     if (url == internalFunctionFile()) {
0083         job->setMaximumFileSize(std::numeric_limits<qint64>::max());
0084         job->setMinimumFeatures(TopDUContext::AllDeclarationsAndContexts);
0085     }
0086 
0087     return job;
0088 }
0089 
0090 QString LanguageSupport::name() const
0091 {
0092     return QStringLiteral("Php");
0093 }
0094 
0095 KDevelop::ICodeHighlighting* LanguageSupport::codeHighlighting() const
0096 {
0097     return m_highlighting;
0098 }
0099 
0100 ContextMenuExtension LanguageSupport::contextMenuExtension(Context* context, QWidget* parent)
0101 {
0102     ContextMenuExtension cm;
0103     EditorContext *ed = dynamic_cast<KDevelop::EditorContext *>(context);
0104 
0105     if (ed && ICore::self()->languageController()->languagesForUrl(ed->url()).contains(this)) {
0106         // It's safe to add our own ContextMenuExtension.
0107         m_refactoring->fillContextMenu(cm, context, parent);
0108     }
0109     return cm;
0110 }
0111 
0112 QPair<QString, Range> LanguageSupport::wordUnderCursor(const QUrl& url, const Cursor& position)
0113 {
0114     KDevelop::IDocument* doc = core()->documentController()->documentForUrl(url);
0115     if(!doc || !doc->textDocument())
0116         return {};
0117 
0118     int lineNumber = position.line();
0119     int lineLength = doc->textDocument()->lineLength(lineNumber);
0120 
0121     QString line = doc->textDocument()->text(Range(lineNumber, 0, lineNumber, lineLength));
0122 
0123     int startCol = position.column();
0124     for ( ; startCol >= 0; --startCol ) {
0125         if ( !line[startCol].isLetter() && line[startCol] != '_' ) {
0126             // don't include the wrong char
0127             if ( startCol != position.column() ) {
0128                 ++startCol;
0129             }
0130             break;
0131         }
0132     }
0133     int endCol = position.column();
0134     for ( ; endCol <= lineLength; ++endCol ) {
0135         if ( !line[endCol].isLetter() && line[endCol] != '_' ) {
0136             break;
0137         }
0138     }
0139     QString word = line.mid(startCol, endCol - startCol);
0140     Range range(lineNumber, startCol, lineNumber, endCol);
0141     return qMakePair(word, range);
0142 }
0143 
0144 bool isMagicConstant(QPair<QString, Range> word) {
0145     if ( word.second.isValid() && !word.second.isEmpty() ) {
0146         if ( word.first == QLatin1String("__FILE__") || word.first == QLatin1String("__LINE__") ||
0147              word.first == QLatin1String("__METHOD__") || word.first == QLatin1String("__CLASS__") ||
0148              word.first == QLatin1String("__FUNCTION__") || word.first == QLatin1String("__NAMESPACE__") ||
0149              word.first == QLatin1String("__DIR__") || word.first == QLatin1String("__TRAIT__")
0150            )
0151         {
0152             ///TODO: maybe we should use the tokenizer to really make sure this is such a token
0153             ///      and we are not inside a string, comment or similar
0154             ///      otoh, it doesn't hurt imo
0155             return true;
0156         }
0157     }
0158     return false;
0159 }
0160 
0161 QPair<QWidget*, Range> LanguageSupport::specialLanguageObjectNavigationWidget(const QUrl& url, const Cursor& position)
0162 {
0163     QPair<QString, Range> word = wordUnderCursor(url, position);
0164     if ( isMagicConstant(word) ) {
0165         DUChainReadLocker lock;
0166         if (TopDUContext* top = standardContext(url)) {
0167             return {new NavigationWidget(TopDUContextPointer(top), position, word.first), word.second};
0168         } else {
0169             return {nullptr, Range::invalid()};
0170         }
0171     }
0172     return ILanguageSupport::specialLanguageObjectNavigationWidget(url, position);
0173 }
0174 
0175 Range LanguageSupport::specialLanguageObjectRange(const QUrl& url, const Cursor& position)
0176 {
0177     QPair<QString, Range> word = wordUnderCursor(url, position);
0178     if ( isMagicConstant(word) ) {
0179         return word.second;
0180     }
0181     return ILanguageSupport::specialLanguageObjectRange(url, position);
0182 }
0183 
0184 }
0185 
0186 #include "phplanguagesupport.moc"
0187 
0188 #include "moc_phplanguagesupport.cpp"