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