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