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 <kdebug.h>
15 #include <kcomponentdata.h>
16 #include <kpluginfactory.h>
17 #include <kpluginloader.h>
18 #include <KTextEditor/Document>
19 
20 #include <interfaces/icore.h>
21 #include <interfaces/ilanguagecontroller.h>
22 #include <interfaces/iplugincontroller.h>
23 #include <interfaces/ilanguage.h>
24 #include <interfaces/idocument.h>
25 #include <interfaces/iproject.h>
26 #include <language/backgroundparser/backgroundparser.h>
27 #include <language/duchain/duchain.h>
28 #include <language/duchain/duchainlock.h>
29 #include <interfaces/idocumentcontroller.h>
30 #include <interfaces/contextmenuextension.h>
31 #include <language/interfaces/editorcontext.h>
32 
33 #include "phpparsejob.h"
34 #include "phphighlighting.h"
35 #include "kdevphpversion.h"
36 #include "codegen/refactoring.h"
37 
38 #include <language/codecompletion/codecompletion.h>
39 #include <language/codecompletion/codecompletionmodel.h>
40 
41 #include "completion/model.h"
42 #include "completion/worker.h"
43 
44 #include "navigation/navigationwidget.h"
45 #include <language/duchain/parsingenvironment.h>
46 
47 #include "duchain/helper.h"
48 #include <QTimer>
49 
50 using namespace KDevelop;
51 
52 K_PLUGIN_FACTORY(KDevPhpSupportFactory, registerPlugin<Php::LanguageSupport>();)
53 K_EXPORT_PLUGIN(KDevPhpSupportFactory(
54     KAboutData("kdevphpsupport", "kdevphp", ki18n("PHP Support"),
55                KDEVPHP_VERSION_STR, ki18n("Support for PHP Language"), KAboutData::License_GPL)
56     .addAuthor(ki18n("Milian Wolff"), ki18n("Author"), "mail@milianw.de", "https://milianw.de")
57     .addAuthor(ki18n("Niko Sams"), ki18n("Author"), "niko.sams@gmail.com", "https://nikosams.blogspot.com")
58 ))
59 
60 namespace Php
61 {
62 
LanguageSupport(QObject * parent,const QVariantList &)63 LanguageSupport::LanguageSupport(QObject* parent, const QVariantList& /*args*/)
64         : KDevelop::IPlugin(KDevPhpSupportFactory::componentData(), parent),
65         KDevelop::ILanguageSupport(), m_internalFunctionsLoaded(false)
66 {
67     Q_ASSERT(internalFunctionFile().toUrl().isValid());
68     m_internalFunctionsLock.lockForWrite();
69 
70     KDEV_USE_EXTENSION_INTERFACE(KDevelop::ILanguageSupport)
71 
72     m_highlighting = new Php::Highlighting(this);
73     m_refactoring = new Php::Refactoring(this);
74 
75     CodeCompletionModel* ccModel = new CodeCompletionModel(this);
76     new KDevelop::CodeCompletion(this, ccModel, name());
77 
78     QTimer::singleShot(0, this, SLOT(updateInternalFunctions()));
79 }
80 
~LanguageSupport()81 LanguageSupport::~LanguageSupport()
82 {
83     ILanguage* lang = language();
84     if ( lang ) {
85         lang->parseLock()->lockForWrite();
86         //By locking the parse-mutexes, we make sure that parse- and preprocess-jobs
87         //get a chance to finish in a good state
88         lang->parseLock()->unlock();
89     }
90 }
91 
updateInternalFunctions()92 void LanguageSupport::updateInternalFunctions()
93 {
94     Q_ASSERT(core()->pluginController()->loadedPlugins().contains(this));
95     kDebug() << "making sure that internal function file is up to date";
96     DUChain::self()->updateContextForUrl(internalFunctionFile(), KDevelop::TopDUContext::AllDeclarationsAndContexts, this, -10);
97 }
98 
updateReady(const IndexedString & url,const ReferencedTopDUContext & topContext)99 void LanguageSupport::updateReady( const IndexedString& url, const ReferencedTopDUContext& topContext )
100 {
101     Q_UNUSED(topContext);
102     if (url == internalFunctionFile())
103     {
104         DUChain::self()->updateContextForUrl(internalTestFile(), KDevelop::TopDUContext::AllDeclarationsAndContexts, this, -10);
105         m_internalFunctionsLoaded = true;
106         m_internalFunctionsLock.unlock();
107     }
108 }
109 
internalFunctionsLoaded() const110 bool LanguageSupport::internalFunctionsLoaded() const
111 {
112     return m_internalFunctionsLoaded;
113 }
114 
internalFunctionsLock()115 QReadWriteLock* LanguageSupport::internalFunctionsLock()
116 {
117     return &m_internalFunctionsLock;
118 }
119 
createParseJob(const IndexedString & url)120 KDevelop::ParseJob *LanguageSupport::createParseJob(const IndexedString &url)
121 {
122     return new ParseJob(url, this);
123 }
124 
name() const125 QString LanguageSupport::name() const
126 {
127     return "Php";
128 }
129 
language()130 KDevelop::ILanguage *LanguageSupport::language()
131 {
132     return core()->languageController()->language(name());
133 }
134 
codeHighlighting() const135 KDevelop::ICodeHighlighting* LanguageSupport::codeHighlighting() const
136 {
137     return m_highlighting;
138 }
139 
contextMenuExtension(Context * context)140 KDevelop::ContextMenuExtension LanguageSupport::contextMenuExtension(Context* context)
141 {
142     ContextMenuExtension cm;
143     EditorContext *ed = dynamic_cast<KDevelop::EditorContext *>(context);
144 
145     if (ed && ICore::self()->languageController()->languagesForUrl(ed->url()).contains(language())) {
146         // It's safe to add our own ContextMenuExtension.
147         m_refactoring->fillContextMenu(cm, context);
148     }
149     return cm;
150 }
151 
wordUnderCursor(const KUrl & url,const SimpleCursor & position)152 QPair<QString, SimpleRange> LanguageSupport::wordUnderCursor(const KUrl& url, const SimpleCursor& position)
153 {
154     KDevelop::IDocument* doc = core()->documentController()->documentForUrl(url);
155     if(!doc || !doc->textDocument() || !doc->textDocument()->activeView())
156         return qMakePair(QString(), SimpleRange::invalid());
157 
158     int lineNumber = position.line;
159     int lineLength = doc->textDocument()->lineLength(lineNumber);
160 
161     QString line = doc->textDocument()->text(KTextEditor::Range(lineNumber, 0, lineNumber, lineLength));
162 
163     int startCol = position.column;
164     for ( ; startCol >= 0; --startCol ) {
165         if ( !line[startCol].isLetter() && line[startCol] != '_' ) {
166             // don't include the wrong char
167             if ( startCol != position.column ) {
168                 ++startCol;
169             }
170             break;
171         }
172     }
173     int endCol = position.column;
174     for ( ; endCol <= lineLength; ++endCol ) {
175         if ( !line[endCol].isLetter() && line[endCol] != '_' ) {
176             break;
177         }
178     }
179     QString word = line.mid(startCol, endCol - startCol);
180     SimpleRange range(lineNumber, startCol, lineNumber, endCol);
181     return qMakePair(word, range);
182 }
183 
isMagicConstant(QPair<QString,SimpleRange> word)184 bool isMagicConstant(QPair<QString, SimpleRange> word) {
185     if ( word.second.isValid() && !word.second.isEmpty() ) {
186         if ( word.first == "__FILE__" || word.first == "__LINE__" ||
187              word.first == "__METHOD__" || word.first == "__CLASS__" ||
188              word.first == "__FUNCTION__" || word.first == "__NAMESPACE__"
189              ///TODO: php 5.3: __DIR__
190            )
191         {
192             ///TODO: maybe we should use the tokenizer to really make sure this is such a token
193             ///      and we are not inside a string, comment or similar
194             ///      otoh, it doesn't hurt imo
195             return true;
196         }
197     }
198     return false;
199 }
200 
specialLanguageObjectNavigationWidget(const KUrl & url,const SimpleCursor & position)201 QWidget* LanguageSupport::specialLanguageObjectNavigationWidget(const KUrl& url, const SimpleCursor& position)
202 {
203     QPair<QString, SimpleRange> word = wordUnderCursor(url, position);
204     if ( isMagicConstant(word) ) {
205         DUChainReadLocker lock;
206         if (TopDUContext* top = standardContext(url)) {
207             return new NavigationWidget(TopDUContextPointer(top), position, word.first);
208         } else {
209             return 0;
210         }
211     }
212     return ILanguageSupport::specialLanguageObjectNavigationWidget(url, position);
213 }
214 
specialLanguageObjectRange(const KUrl & url,const SimpleCursor & position)215 SimpleRange LanguageSupport::specialLanguageObjectRange(const KUrl& url, const SimpleCursor& position)
216 {
217     QPair<QString, SimpleRange> word = wordUnderCursor(url, position);
218     if ( isMagicConstant(word) ) {
219         return word.second;
220     }
221     return ILanguageSupport::specialLanguageObjectRange(url, position);
222 }
223 
224 }
225 
226 #include "phplanguagesupport.moc"
227