File indexing completed on 2024-03-24 04:00:55
0001 /* 0002 SPDX-FileCopyrightText: KDE Developers 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "completionreplayer.h" 0008 #include "completionrecorder.h" 0009 #include "katedocument.h" 0010 #include "katepartdebug.h" 0011 #include "kateview.h" 0012 #include "lastchangerecorder.h" 0013 #include "macrorecorder.h" 0014 #include <vimode/inputmodemanager.h> 0015 0016 #include <QKeyEvent> 0017 #include <QRegularExpression> 0018 0019 using namespace KateVi; 0020 0021 CompletionReplayer::CompletionReplayer(InputModeManager *viInputModeManager) 0022 : m_viInputModeManager(viInputModeManager) 0023 { 0024 } 0025 0026 CompletionReplayer::~CompletionReplayer() = default; 0027 0028 void CompletionReplayer::start(const CompletionList &completions) 0029 { 0030 m_nextCompletionIndex.push(0); 0031 m_CompletionsToReplay.push(completions); 0032 } 0033 0034 void CompletionReplayer::stop() 0035 { 0036 m_CompletionsToReplay.pop(); 0037 m_nextCompletionIndex.pop(); 0038 } 0039 0040 void CompletionReplayer::replay() 0041 { 0042 const Completion completion = nextCompletion(); 0043 KTextEditor::ViewPrivate *m_view = m_viInputModeManager->view(); 0044 KTextEditor::DocumentPrivate *doc = m_view->doc(); 0045 0046 // Find beginning of the word. 0047 KTextEditor::Cursor cursorPos = m_view->cursorPosition(); 0048 KTextEditor::Cursor wordStart = KTextEditor::Cursor::invalid(); 0049 if (!doc->characterAt(cursorPos).isLetterOrNumber() && doc->characterAt(cursorPos) != QLatin1Char('_')) { 0050 cursorPos.setColumn(cursorPos.column() - 1); 0051 } 0052 while (cursorPos.column() >= 0 && (doc->characterAt(cursorPos).isLetterOrNumber() || doc->characterAt(cursorPos) == QLatin1Char('_'))) { 0053 wordStart = cursorPos; 0054 cursorPos.setColumn(cursorPos.column() - 1); 0055 } 0056 // Find end of current word. 0057 cursorPos = m_view->cursorPosition(); 0058 KTextEditor::Cursor wordEnd = KTextEditor::Cursor(cursorPos.line(), cursorPos.column() - 1); 0059 while (cursorPos.column() < doc->lineLength(cursorPos.line()) 0060 && (doc->characterAt(cursorPos).isLetterOrNumber() || doc->characterAt(cursorPos) == QLatin1Char('_'))) { 0061 wordEnd = cursorPos; 0062 cursorPos.setColumn(cursorPos.column() + 1); 0063 } 0064 QString completionText = completion.completedText(); 0065 const KTextEditor::Range currentWord = KTextEditor::Range(wordStart, KTextEditor::Cursor(wordEnd.line(), wordEnd.column() + 1)); 0066 // Should we merge opening brackets? Yes, if completion is a function with arguments and after the cursor 0067 // there is (optional whitespace) followed by an open bracket. 0068 int offsetFinalCursorPosBy = 0; 0069 if (completion.completionType() == Completion::FunctionWithArgs) { 0070 const int nextMergableBracketAfterCursorPos = findNextMergeableBracketPos(currentWord.end()); 0071 if (nextMergableBracketAfterCursorPos != -1) { 0072 if (completionText.endsWith(QLatin1String("()"))) { 0073 // Strip "()". 0074 completionText.chop(2); 0075 } else if (completionText.endsWith(QLatin1String("();"))) { 0076 // Strip "();". 0077 completionText.chop(3); 0078 } 0079 // Ensure cursor ends up after the merged open bracket. 0080 offsetFinalCursorPosBy = nextMergableBracketAfterCursorPos + 1; 0081 } else { 0082 if (!completionText.endsWith(QLatin1String("()")) && !completionText.endsWith(QLatin1String("();"))) { 0083 // Original completion merged with an opening bracket; we'll have to add our own brackets. 0084 completionText.append(QLatin1String("()")); 0085 } 0086 // Position cursor correctly i.e. we'll have added "functionname()" or "functionname();"; need to step back by 0087 // one or two to be after the opening bracket. 0088 offsetFinalCursorPosBy = completionText.endsWith(QLatin1Char(';')) ? -2 : -1; 0089 } 0090 } 0091 KTextEditor::Cursor deleteEnd = 0092 completion.removeTail() ? currentWord.end() : KTextEditor::Cursor(m_view->cursorPosition().line(), m_view->cursorPosition().column() + 0); 0093 0094 if (currentWord.isValid()) { 0095 doc->removeText(KTextEditor::Range(currentWord.start(), deleteEnd)); 0096 doc->insertText(currentWord.start(), completionText); 0097 } else { 0098 doc->insertText(m_view->cursorPosition(), completionText); 0099 } 0100 0101 if (offsetFinalCursorPosBy != 0) { 0102 m_view->setCursorPosition(KTextEditor::Cursor(m_view->cursorPosition().line(), m_view->cursorPosition().column() + offsetFinalCursorPosBy)); 0103 } 0104 0105 if (!m_viInputModeManager->lastChangeRecorder()->isReplaying()) { 0106 Q_ASSERT(m_viInputModeManager->macroRecorder()->isReplaying()); 0107 // Post the completion back: it needs to be added to the "last change" list ... 0108 m_viInputModeManager->completionRecorder()->logCompletionEvent(completion); 0109 // ... but don't log the ctrl-space that led to this call to replayCompletion(), as 0110 // a synthetic ctrl-space was just added to the last change keypresses by logCompletionEvent(), and we don't 0111 // want to duplicate them! 0112 m_viInputModeManager->doNotLogCurrentKeypress(); 0113 } 0114 } 0115 0116 Completion CompletionReplayer::nextCompletion() 0117 { 0118 Q_ASSERT(m_viInputModeManager->lastChangeRecorder()->isReplaying() || m_viInputModeManager->macroRecorder()->isReplaying()); 0119 0120 if (m_nextCompletionIndex.top() >= m_CompletionsToReplay.top().length()) { 0121 qCDebug(LOG_KTE) << "Something wrong here: requesting more completions for macro than we actually have. Returning dummy."; 0122 return Completion(QString(), false, Completion::PlainText); 0123 } 0124 0125 return m_CompletionsToReplay.top()[m_nextCompletionIndex.top()++]; 0126 } 0127 0128 int CompletionReplayer::findNextMergeableBracketPos(const KTextEditor::Cursor startPos) const 0129 { 0130 KTextEditor::DocumentPrivate *doc = m_viInputModeManager->view()->doc(); 0131 const QString lineAfterCursor = doc->text(KTextEditor::Range(startPos, KTextEditor::Cursor(startPos.line(), doc->lineLength(startPos.line())))); 0132 static const QRegularExpression whitespaceThenOpeningBracket(QStringLiteral("^\\s*(\\()"), QRegularExpression::UseUnicodePropertiesOption); 0133 QRegularExpressionMatch match = whitespaceThenOpeningBracket.match(lineAfterCursor); 0134 int nextMergableBracketAfterCursorPos = -1; 0135 if (match.hasMatch()) { 0136 nextMergableBracketAfterCursorPos = match.capturedStart(1); 0137 } 0138 return nextMergableBracketAfterCursorPos; 0139 }