1 /*
2     SPDX-FileCopyrightText: 2007 Piyush verma <piyush.verma@gmail.com>
3     SPDX-FileCopyrightText: 2009 Niko Sams <niko.sams@gmail.com>
4     SPDX-FileCopyrightText: 2010 Milian Wolff <mail@milianw.de>
5 
6     SPDX-License-Identifier: GPL-2.0-or-later
7 */
8 
9 #include "phplanguagesupport.h"
10 
11 #include <QMutexLocker>
12 #include <QReadWriteLock>
13 
14 #include <kpluginfactory.h>
15 #include <kpluginloader.h>
16 #include <KTextEditor/Document>
17 
18 #include <interfaces/icore.h>
19 #include <interfaces/ilanguagecontroller.h>
20 #include <interfaces/iplugincontroller.h>
21 #include <interfaces/idocument.h>
22 #include <interfaces/iproject.h>
23 #include <language/backgroundparser/backgroundparser.h>
24 #include <language/duchain/duchain.h>
25 #include <language/duchain/duchainlock.h>
26 #include <interfaces/idocumentcontroller.h>
27 #include <interfaces/contextmenuextension.h>
28 #include <language/interfaces/editorcontext.h>
29 
30 #include "phpparsejob.h"
31 #include "phphighlighting.h"
32 #include "kdevphpversion.h"
33 #include "phpdebug.h"
34 #include "codegen/refactoring.h"
35 
36 #include <language/codecompletion/codecompletion.h>
37 #include <language/codecompletion/codecompletionmodel.h>
38 
39 #include "completion/model.h"
40 #include "completion/worker.h"
41 
42 #include "navigation/navigationwidget.h"
43 #include <language/duchain/parsingenvironment.h>
44 
45 #include "duchain/helper.h"
46 #include <QTimer>
47 
48 using namespace KTextEditor;
49 using namespace KDevelop;
50 
51 K_PLUGIN_FACTORY_WITH_JSON(KDevPhpSupportFactory, "kdevphpsupport.json", registerPlugin<Php::LanguageSupport>(); )
52 
53 namespace Php
54 {
55 
LanguageSupport(QObject * parent,const QVariantList &)56 LanguageSupport::LanguageSupport(QObject* parent, const QVariantList& /*args*/)
57         : KDevelop::IPlugin(QStringLiteral("kdevphpsupport"), parent),
58         KDevelop::ILanguageSupport()
59 {
60     Q_ASSERT(internalFunctionFile().toUrl().isValid());
61 
62     m_highlighting = new Php::Highlighting(this);
63     m_refactoring = new Php::Refactoring(this);
64 
65     auto* ccModel = new CodeCompletionModel(this);
66     new KDevelop::CodeCompletion(this, ccModel, name());
67 }
68 
~LanguageSupport()69 LanguageSupport::~LanguageSupport()
70 {
71     parseLock()->lockForWrite();
72     //By locking the parse-mutexes, we make sure that parse- and preprocess-jobs
73     //get a chance to finish in a good state
74     parseLock()->unlock();
75 }
76 
createParseJob(const IndexedString & url)77 KDevelop::ParseJob *LanguageSupport::createParseJob(const IndexedString &url)
78 {
79     auto *job = new ParseJob(url, this);
80 
81     // bypass the 5 MB maximum file size limit for the internal file
82     if (url == internalFunctionFile()) {
83         job->setMaximumFileSize(std::numeric_limits<qint64>::max());
84         job->setMinimumFeatures(TopDUContext::AllDeclarationsAndContexts);
85     }
86 
87     return job;
88 }
89 
name() const90 QString LanguageSupport::name() const
91 {
92     return QStringLiteral("Php");
93 }
94 
codeHighlighting() const95 KDevelop::ICodeHighlighting* LanguageSupport::codeHighlighting() const
96 {
97     return m_highlighting;
98 }
99 
contextMenuExtension(Context * context,QWidget * parent)100 ContextMenuExtension LanguageSupport::contextMenuExtension(Context* context, QWidget* parent)
101 {
102     ContextMenuExtension cm;
103     EditorContext *ed = dynamic_cast<KDevelop::EditorContext *>(context);
104 
105     if (ed && ICore::self()->languageController()->languagesForUrl(ed->url()).contains(this)) {
106         // It's safe to add our own ContextMenuExtension.
107         m_refactoring->fillContextMenu(cm, context, parent);
108     }
109     return cm;
110 }
111 
wordUnderCursor(const QUrl & url,const Cursor & position)112 QPair<QString, Range> LanguageSupport::wordUnderCursor(const QUrl& url, const Cursor& position)
113 {
114     KDevelop::IDocument* doc = core()->documentController()->documentForUrl(url);
115     if(!doc || !doc->textDocument())
116         return {};
117 
118     int lineNumber = position.line();
119     int lineLength = doc->textDocument()->lineLength(lineNumber);
120 
121     QString line = doc->textDocument()->text(Range(lineNumber, 0, lineNumber, lineLength));
122 
123     int startCol = position.column();
124     for ( ; startCol >= 0; --startCol ) {
125         if ( !line[startCol].isLetter() && line[startCol] != '_' ) {
126             // don't include the wrong char
127             if ( startCol != position.column() ) {
128                 ++startCol;
129             }
130             break;
131         }
132     }
133     int endCol = position.column();
134     for ( ; endCol <= lineLength; ++endCol ) {
135         if ( !line[endCol].isLetter() && line[endCol] != '_' ) {
136             break;
137         }
138     }
139     QString word = line.mid(startCol, endCol - startCol);
140     Range range(lineNumber, startCol, lineNumber, endCol);
141     return qMakePair(word, range);
142 }
143 
isMagicConstant(QPair<QString,Range> word)144 bool isMagicConstant(QPair<QString, Range> word) {
145     if ( word.second.isValid() && !word.second.isEmpty() ) {
146         if ( word.first == QLatin1String("__FILE__") || word.first == QLatin1String("__LINE__") ||
147              word.first == QLatin1String("__METHOD__") || word.first == QLatin1String("__CLASS__") ||
148              word.first == QLatin1String("__FUNCTION__") || word.first == QLatin1String("__NAMESPACE__") ||
149              word.first == QLatin1String("__DIR__") || word.first == QLatin1String("__TRAIT__")
150            )
151         {
152             ///TODO: maybe we should use the tokenizer to really make sure this is such a token
153             ///      and we are not inside a string, comment or similar
154             ///      otoh, it doesn't hurt imo
155             return true;
156         }
157     }
158     return false;
159 }
160 
specialLanguageObjectNavigationWidget(const QUrl & url,const Cursor & position)161 QPair<QWidget*, Range> LanguageSupport::specialLanguageObjectNavigationWidget(const QUrl& url, const Cursor& position)
162 {
163     QPair<QString, Range> word = wordUnderCursor(url, position);
164     if ( isMagicConstant(word) ) {
165         DUChainReadLocker lock;
166         if (TopDUContext* top = standardContext(url)) {
167             return {new NavigationWidget(TopDUContextPointer(top), position, word.first), word.second};
168         } else {
169             return {nullptr, Range::invalid()};
170         }
171     }
172     return ILanguageSupport::specialLanguageObjectNavigationWidget(url, position);
173 }
174 
specialLanguageObjectRange(const QUrl & url,const Cursor & position)175 Range LanguageSupport::specialLanguageObjectRange(const QUrl& url, const Cursor& position)
176 {
177     QPair<QString, Range> word = wordUnderCursor(url, position);
178     if ( isMagicConstant(word) ) {
179         return word.second;
180     }
181     return ILanguageSupport::specialLanguageObjectRange(url, position);
182 }
183 
184 }
185 
186 #include "phplanguagesupport.moc"
187