File indexing completed on 2024-04-14 04:29:44

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 "model.h"
0020 
0021 #include "debug.h"
0022 
0023 #include <KTextEditor/View>
0024 #include <KTextEditor/Document>
0025 
0026 #include "cssdefaultvisitor.h"
0027 #include "cssdebugvisitor.h"
0028 #include "../parser/parsesession.h"
0029 #include "../parser/editorintegrator.h"
0030 #include "contentassistdata.h"
0031 #include "../parser/htmlparser.h"
0032 
0033 namespace Css {
0034 
0035 #define ifDebug(x) x
0036 
0037 CodeCompletionModel::CodeCompletionModel(QObject *parent)
0038     : KTextEditor::CodeCompletionModel(parent)
0039     , m_completionContext(NoContext)
0040 {
0041 }
0042 
0043 class FindCurrentNodeVisitor : public DefaultVisitor
0044 {
0045 public:
0046     FindCurrentNodeVisitor(EditorIntegrator* editor, const KTextEditor::Range& range)
0047         : m_editor(editor), m_range(range), m_lastSelectorElement(-1), m_lastProperty(-1),
0048           m_context(CodeCompletionModel::SelectorContext)
0049     {}
0050 
0051     void visitDeclaration(DeclarationAst* node) override
0052     {
0053         {
0054             KTextEditor::Cursor pos = findPosition(node->startToken, EditorIntegrator::FrontEdge);
0055             ifDebug( qCDebug(KDEV_CSS) << m_editor->tokenToString(node->startToken) << m_range.start() << pos; )
0056             if (m_range.start() >= pos) {
0057                 if (node->colon != -1 && m_range.start() >= findPosition(node->colon, EditorIntegrator::FrontEdge)) {
0058                     ifDebug( qCDebug(KDEV_CSS) << "using ValueContext"; )
0059                     m_context = CodeCompletionModel::ValueContext;
0060                 } else {
0061                     ifDebug( qCDebug(KDEV_CSS) << "using PropertyContext 1"; )
0062                     m_context = CodeCompletionModel::PropertyContext;
0063                 }
0064             }
0065         }
0066         if (node->semicolon != -1) {
0067             ifDebug( qCDebug(KDEV_CSS) << m_range.start() << findPosition(node->semicolon, EditorIntegrator::FrontEdge); )
0068             if (m_range.start() > findPosition(node->semicolon, EditorIntegrator::FrontEdge)) {
0069                 ifDebug( qCDebug(KDEV_CSS) << "using PropertyContext 2"; )
0070                 m_context = CodeCompletionModel::PropertyContext;
0071             }
0072         }
0073         DefaultVisitor::visitDeclaration(node);
0074     }
0075 
0076     void visitRuleset(RulesetAst* node) override
0077     {
0078         if (node->lbrace != -1
0079               && m_range.start() >= findPosition(node->lbrace))
0080         {
0081             ifDebug( qCDebug(KDEV_CSS) << "using PropertyContext"; )
0082             m_context = CodeCompletionModel::PropertyContext;
0083         } else if (m_range.start() >= findPosition(node->startToken, EditorIntegrator::FrontEdge)) {
0084             ifDebug( qCDebug(KDEV_CSS) << "using SelectorContext 1"; )
0085             m_context = CodeCompletionModel::SelectorContext;
0086         }
0087         DefaultVisitor::visitRuleset(node);
0088         if (node->rbrace != -1 && m_range.start() >= findPosition(node->rbrace)) {
0089             ifDebug( qCDebug(KDEV_CSS) << "using SelectorContext 2"; )
0090             m_context = CodeCompletionModel::SelectorContext;
0091         }
0092     }
0093 
0094     void visitSimpleSelector(SimpleSelectorAst* node) override
0095     {
0096         if (node->element) {
0097             ifDebug( qCDebug(KDEV_CSS) << m_lastProperty << m_range.start() << findPosition(node->endToken); )
0098             if (m_range.start() > findPosition(node->endToken)) {
0099                 m_lastSelectorElement = node->element->ident;
0100                 ifDebug( qCDebug(KDEV_CSS) << "set lastSelectorElement" << m_editor->tokenToString(m_lastSelectorElement); )
0101             }
0102         }
0103         DefaultVisitor::visitSimpleSelector(node);
0104     }
0105 
0106     void visitProperty(PropertyAst* node) override
0107     {
0108         ifDebug( qCDebug(KDEV_CSS) << m_lastProperty << m_range.start() << findPosition(node->endToken); )
0109         if (m_range.start() > findPosition(node->endToken)) {
0110             m_lastProperty = node->ident;
0111             ifDebug( qCDebug(KDEV_CSS) << "set lastProperty" << m_editor->tokenToString(m_lastProperty); )
0112         }
0113         DefaultVisitor::visitProperty(node);
0114     }
0115 
0116     CodeCompletionModel::CompletionContext currentContext() { return m_context; }
0117     QString lastSelectorElement() {
0118         if (m_lastSelectorElement == -1) return QString();
0119         return m_editor->tokenToString(m_lastSelectorElement);
0120     }
0121     QString lastProperty() {
0122         if (m_lastProperty == -1) return QString();
0123         return m_editor->tokenToString(m_lastProperty);
0124     }
0125     KTextEditor::Cursor findPosition(qint64 token,
0126                                         EditorIntegrator::Edge edge = EditorIntegrator::BackEdge)
0127     {
0128       return m_editor->findPosition(token, edge).castToSimpleCursor();
0129     }
0130 private:
0131     EditorIntegrator *m_editor;
0132     KTextEditor::Range m_range;
0133     qint64 m_lastSelectorElement;
0134     qint64 m_lastProperty;
0135     CodeCompletionModel::CompletionContext m_context;
0136 };
0137 
0138 void CodeCompletionModel::completionInvoked(KTextEditor::View* view, const KTextEditor::Range& range, InvocationType invocationType)
0139 {
0140     Q_UNUSED(invocationType);
0141 
0142     ifDebug( qCDebug(KDEV_CSS) << range; )
0143 
0144     QList<HtmlParser::Part> parts;
0145     if (view->document()->mimeType() == "text/css") {
0146         ifDebug( qCDebug(KDEV_CSS) << "css"; )
0147         HtmlParser::Part part;
0148         part.kind = HtmlParser::Part::Standalone;
0149         part.contents = view->document()->text();
0150         part.range = view->document()->documentRange();
0151         parts << part;
0152     } else {
0153         ifDebug( qCDebug(KDEV_CSS) << "html"; )
0154         HtmlParser p;
0155         p.setContents(view->document()->text());
0156         parts = p.parse();
0157         if (parts.isEmpty()) {
0158             parts << HtmlParser::Part(); //empty part
0159         }
0160     }
0161     Q_ASSERT(!parts.isEmpty());
0162 
0163     ParseSession session;
0164     Css::AstNode* ast = nullptr;
0165     HtmlParser::Part part;
0166     foreach (const HtmlParser::Part &p, parts) {
0167         if (p.range.contains(range.start())
0168                 || p.range.start() == range.start()
0169                 || p.range.end() == range.end()
0170                 ) {
0171             session.setContents(p.contents);
0172             session.setOffset(KDevelop::CursorInRevision::castFromSimpleCursor(p.range.start()));
0173             if (p.kind != HtmlParser::Part::InlineStyle) {
0174                 StartAst* a = static_cast<StartAst*>(ast);
0175                 session.parse(&a);
0176                 ast = a;
0177             } else {
0178                 DeclarationListAst* a = static_cast<DeclarationListAst*>(ast);
0179                 session.parse(&a);
0180                 ast = a;
0181             }
0182             part = p;
0183             break;
0184         }
0185     }
0186 
0187     ifDebug(
0188 //         DebugVisitor vis(session.tokenStream(), session.contents());
0189 //         vis.visitNode(ast);
0190     )
0191 
0192     beginResetModel();
0193     if (ast) {
0194         EditorIntegrator editor;
0195         editor.setParseSession(&session);
0196 
0197         FindCurrentNodeVisitor visitor(&editor, range);
0198         visitor.visitNode(ast);
0199         m_completionContext = visitor.currentContext();
0200         qCDebug(KDEV_CSS) << "context" << m_completionContext;
0201         switch (m_completionContext) {
0202             case PropertyContext:
0203             {
0204                 QString el;
0205                 if (part.kind != HtmlParser::Part::InlineStyle) {
0206                     qCDebug(KDEV_CSS) << "lastSelectorElement" << visitor.lastSelectorElement();
0207                     el = visitor.lastSelectorElement();
0208                 } else {
0209                     el = part.tag;
0210                 }
0211                 ContentAssistData::Element element = ContentAssistData::self()->element(el);
0212                 m_items = element.fields;
0213                 setRowCount(m_items.count());
0214                 endResetModel();
0215                 return;
0216             }
0217             case ValueContext:
0218             {
0219                 qCDebug(KDEV_CSS) << "lastProperty" << visitor.lastProperty();
0220                 ContentAssistData::Field field = ContentAssistData::self()->field(visitor.lastProperty());
0221                 m_items = field.values.keys();
0222                 setRowCount(m_items.count());
0223                 endResetModel();
0224                 return;
0225             }
0226             case SelectorContext:
0227                 m_items = ContentAssistData::self()->elements();
0228                 setRowCount(m_items.count());
0229                 endResetModel();
0230                 return;
0231             default:
0232                 break;
0233         }
0234     }
0235     m_items.clear();
0236     setRowCount(0);
0237     endResetModel();
0238 }
0239 
0240 QVariant CodeCompletionModel::data(const QModelIndex & index, int role) const
0241 {
0242     if (index.parent().isValid()) return QVariant();
0243     if (role == Qt::DisplayRole && index.column() == CodeCompletionModel::Name) {
0244         if (m_items.count() < index.row()) return QVariant();
0245         return m_items.at(index.row());
0246     }
0247     return QVariant();
0248 }
0249 
0250 //default implementation with '-' added to reg exps
0251 KTextEditor::Range CodeCompletionModel::completionRange(KTextEditor::View* view, const KTextEditor::Cursor &position)
0252 {
0253     KTextEditor::Cursor end = position;
0254 
0255     QString text = " "+view->document()->line(end.line())+" ";
0256 
0257     static QRegExp findWordStart( "[^_\\w\\-]([_\\w\\-]+)$" );
0258     static QRegExp findWordEnd( "^([_\\w\\-]*)[^_\\w\\-]" );
0259 
0260     KTextEditor::Cursor start = end;
0261 
0262     //qCDebug(KDEV_CSS) << end << text.left(end.column()+1);
0263     if (findWordStart.lastIndexIn(text.left(end.column()+1)) >= 0)
0264         start.setColumn(findWordStart.pos(1)-1);
0265     //qCDebug(KDEV_CSS) << findWordStart.cap(0);
0266 
0267     if (findWordEnd.indexIn(text.mid(end.column()+1)) >= 0)
0268         end.setColumn(end.column()+1 + findWordEnd.cap(1).length()-1);
0269     //qCDebug(KDEV_CSS) << findWordEnd.cap(0);
0270 
0271     KTextEditor::Range ret = KTextEditor::Range(start, end);
0272 
0273     return ret;
0274 }
0275 
0276 //default implementation with '-' added to reg exp
0277 bool CodeCompletionModel::shouldAbortCompletion(KTextEditor::View* view, const KTextEditor::Range& range, const QString& currentCompletion)
0278 {
0279     Q_UNUSED(view);
0280     Q_UNUSED(range);
0281     static const QRegExp allowedText("^([\\w\\-]*)");
0282     bool ret = !allowedText.exactMatch(currentCompletion);
0283     qCDebug(KDEV_CSS) << currentCompletion << "shouldAbort:" << ret;
0284     return ret;
0285 }
0286 
0287 bool CodeCompletionModel::shouldStartCompletion(KTextEditor::View* view, const QString& insertedText, bool userInsertion, const KTextEditor::Cursor& position)
0288 {
0289         Q_UNUSED(view);
0290     Q_UNUSED(position);
0291     if(insertedText.isEmpty())
0292         return false;
0293 
0294     QChar lastChar = insertedText.at(insertedText.count() - 1);
0295     if (userInsertion && (lastChar.isLetter() || lastChar.isNumber() || lastChar == ':')) {
0296         return true;
0297     }
0298     return false;
0299 }
0300 
0301 void CodeCompletionModel::executeCompletionItem2(KTextEditor::Document* document, const KTextEditor::Range& word, const QModelIndex& index) const
0302 {
0303     QString text = data(index.sibling(index.row(), Name), Qt::DisplayRole).toString();
0304     if (m_completionContext == PropertyContext) {
0305         text += ':';
0306     } else if (m_completionContext == ValueContext) {
0307         text += ';';
0308     }
0309     document->replaceText(word, text);
0310 }
0311 
0312 
0313 }
0314