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"