File indexing completed on 2024-05-05 16:46:47
0001 /* 0002 SPDX-FileCopyrightText: 2012 Aleix Pol <aleixpol@kde.org> 0003 SPDX-FileCopyrightText: 2012 Milian Wolff <mail@milianw.de> 0004 SPDX-FileCopyrightText: 2013 Sven Brauch <svenbrauch@gmail.com> 0005 0006 SPDX-License-Identifier: GPL-2.0-or-later 0007 */ 0008 0009 #include "kdevqmljsplugin.h" 0010 0011 #include "qmljsparsejob.h" 0012 #include "qmljshighlighting.h" 0013 #include "codecompletion/model.h" 0014 #include "navigation/propertypreviewwidget.h" 0015 #include "duchain/helper.h" 0016 0017 #include <qmljs/qmljsmodelmanagerinterface.h> 0018 0019 #include <KPluginFactory> 0020 0021 #include <language/codecompletion/codecompletion.h> 0022 #include <language/assistant/renameassistant.h> 0023 #include <language/assistant/staticassistantsmanager.h> 0024 #include <language/codegen/basicrefactoring.h> 0025 #include <language/duchain/duchain.h> 0026 #include <language/duchain/duchainutils.h> 0027 #include <language/interfaces/editorcontext.h> 0028 #include <interfaces/icore.h> 0029 #include <interfaces/idocumentcontroller.h> 0030 #include <interfaces/ilanguagecontroller.h> 0031 #include <interfaces/contextmenuextension.h> 0032 0033 #include <QReadWriteLock> 0034 0035 K_PLUGIN_FACTORY_WITH_JSON(KDevQmlJsSupportFactory, "kdevqmljs.json", registerPlugin<KDevQmlJsPlugin>(); ) 0036 0037 using namespace KDevelop; 0038 0039 /// TODO: Extend? See qmljsmodelmanager.h in qt-creator.git 0040 class ModelManager: public QmlJS::ModelManagerInterface 0041 { 0042 Q_OBJECT 0043 0044 public: 0045 explicit ModelManager(QObject *parent = nullptr); 0046 ~ModelManager() override; 0047 }; 0048 0049 ModelManager::ModelManager(QObject* parent) 0050 : QmlJS::ModelManagerInterface(parent) {} 0051 0052 ModelManager::~ModelManager() {} 0053 0054 KDevQmlJsPlugin::KDevQmlJsPlugin(QObject* parent, const QVariantList& ) 0055 : IPlugin(QStringLiteral("kdevqmljssupport"), parent ) 0056 , ILanguageSupport() 0057 , m_highlighting(new QmlJsHighlighting(this)) 0058 , m_refactoring(new BasicRefactoring(this)) 0059 , m_modelManager(new ModelManager(this)) 0060 { 0061 QmlJS::registerDUChainItems(); 0062 0063 CodeCompletionModel* codeCompletion = new QmlJS::CodeCompletionModel(this); 0064 new KDevelop::CodeCompletion(this, codeCompletion, name()); 0065 0066 auto assistantsManager = core()->languageController()->staticAssistantsManager(); 0067 assistantsManager->registerAssistant(StaticAssistant::Ptr(new RenameAssistant(this))); 0068 } 0069 0070 KDevQmlJsPlugin::~KDevQmlJsPlugin() 0071 { 0072 parseLock()->lockForWrite(); 0073 // By locking the parse-mutexes, we make sure that parse jobs get a chance to finish in a good state 0074 parseLock()->unlock(); 0075 0076 QmlJS::unregisterDUChainItems(); 0077 } 0078 0079 ParseJob* KDevQmlJsPlugin::createParseJob(const IndexedString& url) 0080 { 0081 return new QmlJsParseJob(url, this); 0082 } 0083 0084 QString KDevQmlJsPlugin::name() const 0085 { 0086 return QStringLiteral("qml/js"); 0087 } 0088 0089 ICodeHighlighting* KDevQmlJsPlugin::codeHighlighting() const 0090 { 0091 return m_highlighting; 0092 } 0093 0094 BasicRefactoring* KDevQmlJsPlugin::refactoring() const 0095 { 0096 return m_refactoring; 0097 } 0098 0099 ContextMenuExtension KDevQmlJsPlugin::contextMenuExtension(Context* context, QWidget* parent) 0100 { 0101 ContextMenuExtension cm; 0102 auto *ec = dynamic_cast<KDevelop::EditorContext *>(context); 0103 0104 if (ec && ICore::self()->languageController()->languagesForUrl(ec->url()).contains(this)) { 0105 // It's a QML/JS file, let's add our context menu. 0106 m_refactoring->fillContextMenu(cm, context, parent); 0107 } 0108 0109 return cm; 0110 } 0111 0112 const QString textFromDoc(const IDocument* doc, const KTextEditor::Range& range) { 0113 return doc->textDocument()->line(range.start().line()).mid(range.start().column(), range.end().column()-range.start().column()); 0114 } 0115 0116 // Finds how many spaces the given string has at one end. 0117 // direction=+1 -> left end of the string, -1 for right end. 0118 int spacesAtCorner(const QString& string, int direction = +1) { 0119 int spaces = 0; 0120 QString::const_iterator it; 0121 for ( it = direction == 1 ? string.begin() : string.end()-1 ; it != string.end(); it += direction ) { 0122 if ( ! it->isSpace() ) break; 0123 spaces += 1; 0124 } 0125 return spaces; 0126 } 0127 0128 // Take the given QML line and check if it's a line of the form foo.bar: value. 0129 // Return ranges for the key and the value. 0130 const QPair<KTextEditor::Range, KTextEditor::Range> parseProperty(const QString& line, const KTextEditor::Cursor& position) { 0131 const QStringList items = line.split(QLatin1Char(';')); 0132 QString matchingItem; 0133 int col_offset = -1; 0134 // This is to also support FooAnimation { foo: bar; baz: bang; duration: 200 } 0135 // or similar 0136 for (const QString& item : items) { 0137 col_offset += item.size() + 1; 0138 if ( position.column() < col_offset ) { 0139 matchingItem = item; 0140 break; 0141 } 0142 } 0143 QStringList split = matchingItem.split(QLatin1Char(':')); 0144 if ( split.size() != 2 ) { 0145 // The expression is not of the form foo:bar, thus invalid. 0146 return qMakePair(KTextEditor::Range::invalid(), KTextEditor::Range::invalid()); 0147 } 0148 QString key = split.at(0); 0149 QString value = split.at(1); 0150 0151 // For animations or similar, remove the trailing '}' if there's no semicolon after the last entry 0152 if (value.trimmed().endsWith(QLatin1Char('}'))) { 0153 col_offset -= value.size() - value.lastIndexOf(QLatin1Char('}')) + 1; 0154 value = value.left(value.lastIndexOf(QLatin1Char('}'))-1); 0155 } 0156 0157 return qMakePair( 0158 KTextEditor::Range( 0159 KTextEditor::Cursor(position.line(), col_offset - value.size() - key.size() + spacesAtCorner(key, +1) - 1), 0160 KTextEditor::Cursor(position.line(), col_offset - value.size() - 1 + spacesAtCorner(key, -1)) 0161 ), 0162 KTextEditor::Range( 0163 KTextEditor::Cursor(position.line(), col_offset - value.size() + spacesAtCorner(value, +1)), 0164 KTextEditor::Cursor(position.line(), col_offset + spacesAtCorner(value, -1)) 0165 )); 0166 } 0167 0168 QPair<QWidget*, KTextEditor::Range> KDevQmlJsPlugin::specialLanguageObjectNavigationWidget(const QUrl& url, const KTextEditor::Cursor& position) 0169 { 0170 IDocument* doc = ICore::self()->documentController()->documentForUrl(url); 0171 if ( doc && doc->textDocument() ) { 0172 // Check for a QML property, and construct a property preview widget 0173 // if the property key is listed in the supported properties. 0174 QPair<KTextEditor::Range, KTextEditor::Range> property = parseProperty(doc->textDocument()->line(position.line()), position); 0175 if ( property.first.isValid() && property.second.isValid() ) { 0176 const auto itemUnderCursor = DUChainUtils::itemUnderCursor(url, property.first.start()); 0177 0178 return {PropertyPreviewWidget::constructIfPossible( 0179 doc->textDocument(), 0180 property.first, 0181 property.second, 0182 itemUnderCursor.declaration, 0183 textFromDoc(doc, property.first), 0184 textFromDoc(doc, property.second) 0185 ), itemUnderCursor.range}; 0186 } 0187 } 0188 // Otherwise, display no special "navigation" widget. 0189 return KDevelop::ILanguageSupport::specialLanguageObjectNavigationWidget(url, position); 0190 } 0191 0192 #include "kdevqmljsplugin.moc" 0193 #include "moc_kdevqmljsplugin.cpp"