File indexing completed on 2024-04-21 04:34:27
0001 /* 0002 Copyright (C) 2009 Niko Sams <niko.sams@gmail.com> 0003 0004 This library is free software; you can redistribute it and/or 0005 modify it under the terms of the GNU Library General Public 0006 License version 2 as published by the Free Software Foundation. 0007 0008 This library is distributed in the hope that it will be useful, 0009 but WITHOUT ANY WARRANTY; without even the implied warranty of 0010 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 0011 Library General Public License for more details. 0012 0013 You should have received a copy of the GNU Library General Public License 0014 along with this library; see the file COPYING.LIB. If not, write to 0015 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 0016 Boston, MA 02110-1301, USA. 0017 */ 0018 0019 #include "csslanguagesupport.h" 0020 0021 #include <kpluginfactory.h> 0022 #include <KTextEditor/Document> 0023 0024 #include <language/codecompletion/codecompletion.h> 0025 #include <interfaces/idocument.h> 0026 #include <interfaces/icore.h> 0027 #include <interfaces/idocumentcontroller.h> 0028 #include <language/duchain/duchain.h> 0029 #include <language/duchain/duchainlock.h> 0030 0031 #include "debug.h" 0032 #include "completion/model.h" 0033 #include "parsejob.h" 0034 #include "navigation/navigationwidget.h" 0035 #include "parser/parsesession.h" 0036 #include "parser/editorintegrator.h" 0037 #include "parser/cssdebugvisitor.h" 0038 #include "version.h" 0039 0040 using namespace Css; 0041 0042 LanguageSupport* LanguageSupport::m_self = nullptr; 0043 0044 K_PLUGIN_FACTORY_WITH_JSON(KDevCssSupportFactory, "kdevcsssupport.json", registerPlugin<LanguageSupport>();) 0045 0046 LanguageSupport::LanguageSupport(QObject* parent, const QVariantList& /*args*/) 0047 : KDevelop::IPlugin(QStringLiteral("kdevcsssupport"), parent), 0048 KDevelop::ILanguageSupport() 0049 { 0050 m_self = this; 0051 0052 CodeCompletionModel* ccModel = new CodeCompletionModel(this); 0053 new KDevelop::CodeCompletion(this, ccModel, name()); 0054 } 0055 0056 QString LanguageSupport::name() const 0057 { 0058 return QStringLiteral("Css"); 0059 } 0060 0061 KDevelop::ParseJob *LanguageSupport::createParseJob(const KDevelop::IndexedString& url) 0062 { 0063 qCDebug(KDEV_CSS) << url; 0064 return new ParseJob(url, this); 0065 } 0066 0067 LanguageSupport *LanguageSupport::self() 0068 { 0069 return m_self; 0070 } 0071 0072 class FindNodeVisitor : public DefaultVisitor 0073 { 0074 public: 0075 FindNodeVisitor(EditorIntegrator* editor, const KTextEditor::Cursor& position) 0076 : DefaultVisitor() 0077 , m_node(nullptr) 0078 , m_property(nullptr) 0079 , m_editor(editor) 0080 , m_position(position) 0081 {} 0082 0083 void visitProperty(PropertyAst* node) override 0084 { 0085 if (!m_node && m_editor->findRange(node->ident).castToSimpleRange().contains(m_position)) { 0086 m_node = node; 0087 } 0088 if (!m_node) { 0089 m_property = node; 0090 } 0091 DefaultVisitor::visitProperty(node); 0092 } 0093 0094 void visitTerm(TermAst* node) override 0095 { 0096 if (!m_node && m_editor->findRange(node).castToSimpleRange().contains(m_position)) { 0097 m_node = node; 0098 } 0099 DefaultVisitor::visitTerm(node); 0100 } 0101 0102 AstNode *node() 0103 { 0104 return m_node; 0105 } 0106 0107 PropertyAst *property() 0108 { 0109 return m_property; 0110 } 0111 private: 0112 AstNode *m_node; 0113 PropertyAst *m_property; 0114 EditorIntegrator* m_editor; 0115 KTextEditor::Cursor m_position; 0116 }; 0117 0118 struct CursorIdentifier { 0119 CursorIdentifier(int kind_) : kind(kind_) {} 0120 int kind; 0121 KDevelop::RangeInRevision range; 0122 QString contents; 0123 QString property; //when ExprKind 0124 }; 0125 0126 CursorIdentifier cursorIdentifier(const QUrl& url, const KTextEditor::Cursor& position) 0127 { 0128 KDevelop::IDocument* doc = KDevelop::ICore::self()->documentController()->documentForUrl(url); 0129 if(!doc || !doc->textDocument() || doc->textDocument()->views().isEmpty()) 0130 return CursorIdentifier(0); 0131 0132 KDevelop::RangeInRevision ctxRange; 0133 { 0134 KDevelop::DUChainReadLocker lock(KDevelop::DUChain::lock()); 0135 KDevelop::TopDUContext* top = KDevelop::DUChain::self()->chainForDocument(url); 0136 if (!top) return CursorIdentifier(0); 0137 KDevelop::DUContext* ctx = top->findContextAt(KDevelop::CursorInRevision::castFromSimpleCursor(position)); 0138 if (!ctx || ctx->type() != KDevelop::DUContext::Class) { 0139 return CursorIdentifier(0); 0140 } 0141 ctxRange = ctx->range(); 0142 } 0143 0144 0145 ParseSession session; 0146 Css::DeclarationListAst* ast = nullptr; 0147 session.setOffset(ctxRange.start); 0148 session.setContents(doc->textDocument()->text(ctxRange.castToSimpleRange())); 0149 session.parse(&ast); 0150 { 0151 EditorIntegrator editor; 0152 editor.setParseSession(&session); 0153 FindNodeVisitor v(&editor, position); 0154 v.visitNode(ast); 0155 if (v.node() && v.node()->kind == PropertyAst::KIND) { 0156 PropertyAst* n = static_cast<PropertyAst*>(v.node()); 0157 CursorIdentifier ret(v.node()->kind); 0158 ret.contents = editor.tokenToString(n->ident); 0159 ret.range = editor.findRange(n->ident); 0160 return ret; 0161 } else if (v.node() && v.node()->kind == TermAst::KIND && v.property()) { 0162 TermAst* n = static_cast<TermAst*>(v.node()); 0163 if (n->ident != -1) { 0164 CursorIdentifier ret(v.node()->kind); 0165 ret.property = editor.tokenToString(v.property()->ident); 0166 ret.contents = editor.tokenToString(n->ident); 0167 ret.range = editor.findRange(n->ident); 0168 return ret; 0169 } else if (n->hexcolor) { 0170 CursorIdentifier ret(n->hexcolor->kind); 0171 ret.contents = editor.nodeToString(n->hexcolor); 0172 ret.range = editor.findRange(n->hexcolor, n->hexcolor); 0173 return ret; 0174 } 0175 } 0176 } 0177 return CursorIdentifier(0); 0178 } 0179 0180 KTextEditor::Range LanguageSupport::specialLanguageObjectRange(const QUrl& url, const KTextEditor::Cursor& position) 0181 { 0182 CursorIdentifier id = cursorIdentifier(url, position); 0183 qCDebug(KDEV_CSS) << id.kind << id.contents; 0184 if (id.kind) { 0185 return id.range.castToSimpleRange(); 0186 } 0187 return KDevelop::ILanguageSupport::specialLanguageObjectRange(url, position); 0188 } 0189 0190 QPair<QWidget*, KTextEditor::Range> LanguageSupport::specialLanguageObjectNavigationWidget(const QUrl& url, const KTextEditor::Cursor& position) 0191 { 0192 CursorIdentifier id = cursorIdentifier(url, position); 0193 qCDebug(KDEV_CSS) << id.kind << id.contents; 0194 if (id.kind) { 0195 KDevelop::TopDUContextPointer top; 0196 { 0197 KDevelop::DUChainReadLocker lock(KDevelop::DUChain::lock()); 0198 top = KDevelop::DUChain::self()->chainForDocument(url); 0199 if (!top) { 0200 return {nullptr, KTextEditor::Range::invalid()}; 0201 } 0202 } 0203 QWidget* widget = nullptr; 0204 if (id.kind == PropertyAst::KIND) { 0205 ContentAssistData::Field field = ContentAssistData::self()->field(id.contents); 0206 if (!field.name.isEmpty()) { 0207 widget = new NavigationWidget(top, field); 0208 } 0209 } else if (id.kind == TermAst::KIND) { 0210 ContentAssistData::Field field = ContentAssistData::self()->field(id.property); 0211 if (field.values.contains(id.contents)) { 0212 widget = new NavigationWidget(top, field.values[id.contents]); 0213 } else if (QColor(id.contents.trimmed()).isValid()) { 0214 widget = new NavigationWidget(top, id.contents.trimmed()); 0215 } 0216 } else if (id.kind == HexcolorAst::KIND) { 0217 widget = new NavigationWidget(top, id.contents.trimmed()); 0218 } else { 0219 Q_ASSERT_X(false, "Css::LanguageSupport::specialLanguageObjectNavigationWidget", 0220 qPrintable(QString("unhandled id kind: %1").arg(id.kind))); 0221 } 0222 if (widget) { 0223 return {widget, id.range.castToSimpleRange()}; 0224 } 0225 } 0226 return KDevelop::ILanguageSupport::specialLanguageObjectNavigationWidget(url, position); 0227 } 0228 0229 #include "csslanguagesupport.moc"