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"