File indexing completed on 2024-05-12 15:45:13
0001 /* 0002 This file is part of the KDE libraries 0003 SPDX-FileCopyrightText: 2014 Miquel Sabaté Solà <mikisabate@gmail.com> 0004 0005 SPDX-License-Identifier: LGPL-2.0-or-later 0006 */ 0007 0008 #include "fakecodecompletiontestmodel.h" 0009 #include <QRegularExpression> 0010 #include <katecompletionwidget.h> 0011 #include <katedocument.h> 0012 #include <kateglobal.h> 0013 #include <kateview.h> 0014 #include <katewordcompletion.h> 0015 0016 using namespace KTextEditor; 0017 0018 FakeCodeCompletionTestModel::FakeCodeCompletionTestModel(KTextEditor::View *parent) 0019 : KTextEditor::CodeCompletionModel(parent) 0020 , m_kateView(qobject_cast<KTextEditor::ViewPrivate *>(parent)) 0021 , m_kateDoc(parent->document()) 0022 , m_removeTailOnCompletion(false) 0023 , m_failTestOnInvocation(false) 0024 , m_wasInvoked(false) 0025 { 0026 Q_ASSERT(m_kateView); 0027 setRowCount(3); 0028 cc()->setAutomaticInvocationEnabled(false); 0029 cc()->unregisterCompletionModel(KTextEditor::EditorPrivate::self()->wordCompletionModel()); // would add additional items, we don't want that in tests 0030 connect(static_cast<KTextEditor::DocumentPrivate *>(parent->document()), 0031 &KTextEditor::DocumentPrivate::textInsertedRange, 0032 this, 0033 &FakeCodeCompletionTestModel::textInserted); 0034 connect(static_cast<KTextEditor::DocumentPrivate *>(parent->document()), 0035 &KTextEditor::DocumentPrivate::textRemoved, 0036 this, 0037 &FakeCodeCompletionTestModel::textRemoved); 0038 } 0039 0040 void FakeCodeCompletionTestModel::setCompletions(const QStringList &completions) 0041 { 0042 QStringList sortedCompletions = completions; 0043 std::sort(sortedCompletions.begin(), sortedCompletions.end()); 0044 Q_ASSERT(completions == sortedCompletions 0045 && "QCompleter seems to sort the items, so it's best to provide them pre-sorted so it's easier to predict the order"); 0046 setRowCount(sortedCompletions.length()); 0047 m_completions = completions; 0048 } 0049 0050 void FakeCodeCompletionTestModel::setRemoveTailOnComplete(bool removeTailOnCompletion) 0051 { 0052 m_removeTailOnCompletion = removeTailOnCompletion; 0053 } 0054 0055 void FakeCodeCompletionTestModel::setFailTestOnInvocation(bool failTestOnInvocation) 0056 { 0057 m_failTestOnInvocation = failTestOnInvocation; 0058 } 0059 0060 bool FakeCodeCompletionTestModel::wasInvoked() 0061 { 0062 return m_wasInvoked; 0063 } 0064 0065 void FakeCodeCompletionTestModel::clearWasInvoked() 0066 { 0067 m_wasInvoked = false; 0068 } 0069 0070 void FakeCodeCompletionTestModel::forceInvocationIfDocTextIs(const QString &desiredDocText) 0071 { 0072 m_forceInvocationIfDocTextIs = desiredDocText; 0073 } 0074 0075 void FakeCodeCompletionTestModel::doNotForceInvocation() 0076 { 0077 m_forceInvocationIfDocTextIs.clear(); 0078 } 0079 0080 QVariant FakeCodeCompletionTestModel::data(const QModelIndex &index, int role) const 0081 { 0082 m_wasInvoked = true; 0083 if (m_failTestOnInvocation) { 0084 failTest(); 0085 return QVariant(); 0086 } 0087 // Order is important, here, as the completion widget seems to do its own sorting. 0088 if (role == Qt::DisplayRole) { 0089 if (index.column() == Name) { 0090 return QString(m_completions[index.row()]); 0091 } 0092 } 0093 return QVariant(); 0094 } 0095 0096 void FakeCodeCompletionTestModel::executeCompletionItem(KTextEditor::View *view, const KTextEditor::Range &word, const QModelIndex &index) const 0097 { 0098 qDebug() << "word: " << word << "(" << view->document()->text(word) << ")"; 0099 const Cursor origCursorPos = m_kateView->cursorPosition(); 0100 const QString textToInsert = m_completions[index.row()]; 0101 const QString textAfterCursor = view->document()->text(Range(word.end(), Cursor(word.end().line(), view->document()->lineLength(word.end().line())))); 0102 view->document()->removeText(Range(word.start(), origCursorPos)); 0103 const int lengthStillToRemove = word.end().column() - origCursorPos.column(); 0104 QString actualTextInserted = textToInsert; 0105 // Merge brackets? 0106 const QString noArgFunctionCallMarker = "()"; 0107 const QString withArgFunctionCallMarker = "(...)"; 0108 const bool endedWithSemiColon = textToInsert.endsWith(';'); 0109 if (textToInsert.contains(noArgFunctionCallMarker) || textToInsert.contains(withArgFunctionCallMarker)) { 0110 Q_ASSERT(m_removeTailOnCompletion && "Function completion items without removing tail is not yet supported!"); 0111 const bool takesArgument = textToInsert.contains(withArgFunctionCallMarker); 0112 // The code for a function call to a function taking no arguments. 0113 const QString justFunctionName = textToInsert.left(textToInsert.indexOf("(")); 0114 0115 static const QRegularExpression whitespaceThenOpeningBracket("^\\s*(\\()", QRegularExpression::UseUnicodePropertiesOption); 0116 const QRegularExpressionMatch match = whitespaceThenOpeningBracket.match(textAfterCursor); 0117 int openingBracketPos = -1; 0118 if (match.hasMatch()) { 0119 openingBracketPos = match.capturedStart(1) + word.start().column() + justFunctionName.length() + 1 + lengthStillToRemove; 0120 } 0121 const bool mergeOpenBracketWithExisting = (openingBracketPos != -1) && !endedWithSemiColon; 0122 // Add the function name, for now: we don't yet know if we'll be adding the "()", too. 0123 view->document()->insertText(word.start(), justFunctionName); 0124 if (mergeOpenBracketWithExisting) { 0125 // Merge with opening bracket. 0126 actualTextInserted = justFunctionName; 0127 m_kateView->setCursorPosition(Cursor(word.start().line(), openingBracketPos)); 0128 } else { 0129 // Don't merge. 0130 const QString afterFunctionName = endedWithSemiColon ? "();" : "()"; 0131 view->document()->insertText(Cursor(word.start().line(), word.start().column() + justFunctionName.length()), afterFunctionName); 0132 if (takesArgument) { 0133 // Place the cursor immediately after the opening "(" we just added. 0134 m_kateView->setCursorPosition(Cursor(word.start().line(), word.start().column() + justFunctionName.length() + 1)); 0135 } 0136 } 0137 } else { 0138 // Plain text. 0139 view->document()->insertText(word.start(), textToInsert); 0140 } 0141 if (m_removeTailOnCompletion) { 0142 const int tailLength = word.end().column() - origCursorPos.column(); 0143 const Cursor tailStart = Cursor(word.start().line(), word.start().column() + actualTextInserted.length()); 0144 const Cursor tailEnd = Cursor(tailStart.line(), tailStart.column() + tailLength); 0145 view->document()->removeText(Range(tailStart, tailEnd)); 0146 } 0147 } 0148 0149 KTextEditor::CodeCompletionInterface *FakeCodeCompletionTestModel::cc() const 0150 { 0151 return dynamic_cast<KTextEditor::CodeCompletionInterface *>(const_cast<QObject *>(QObject::parent())); 0152 } 0153 0154 void FakeCodeCompletionTestModel::failTest() const 0155 { 0156 QFAIL("Shouldn't be invoking me!"); 0157 } 0158 0159 void FakeCodeCompletionTestModel::textInserted(Document *document, Range range) 0160 { 0161 Q_UNUSED(document); 0162 Q_UNUSED(range); 0163 checkIfShouldForceInvocation(); 0164 } 0165 0166 void FakeCodeCompletionTestModel::textRemoved(Document *document, Range range) 0167 { 0168 Q_UNUSED(document); 0169 Q_UNUSED(range); 0170 checkIfShouldForceInvocation(); 0171 } 0172 0173 void FakeCodeCompletionTestModel::checkIfShouldForceInvocation() 0174 { 0175 if (m_forceInvocationIfDocTextIs.isEmpty()) { 0176 return; 0177 } 0178 0179 if (m_kateDoc->text() == m_forceInvocationIfDocTextIs) { 0180 m_kateView->completionWidget()->userInvokedCompletion(); 0181 BaseTest::waitForCompletionWidgetToActivate(m_kateView); 0182 } 0183 } 0184 0185 #include "moc_fakecodecompletiontestmodel.cpp"