Warning, file /frameworks/ktexteditor/src/vimode/emulatedcommandbar/completer.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).
0001 /* 0002 SPDX-FileCopyrightText: 2013-2016 Simon St James <kdedevel@etotheipiplusone.com> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "completer.h" 0008 #include "emulatedcommandbar.h" 0009 0010 using namespace KateVi; 0011 0012 #include "activemode.h" 0013 #include "kateview.h" 0014 #include "vimode/definitions.h" 0015 0016 #include <QAbstractItemView> 0017 #include <QCompleter> 0018 #include <QLineEdit> 0019 #include <QRegularExpression> 0020 #include <QStringListModel> 0021 0022 namespace 0023 { 0024 bool caseInsensitiveLessThan(const QString &s1, const QString &s2) 0025 { 0026 return s1.toLower() < s2.toLower(); 0027 } 0028 } 0029 0030 Completer::Completer(EmulatedCommandBar *emulatedCommandBar, KTextEditor::ViewPrivate *view, QLineEdit *edit) 0031 : m_edit(edit) 0032 , m_view(view) 0033 { 0034 m_completer = new QCompleter(QStringList(), edit); 0035 // Can't find a way to stop the QCompleter from auto-completing when attached to a QLineEdit, 0036 // so don't actually set it as the QLineEdit's completer. 0037 m_completer->setWidget(edit); 0038 m_completer->setObjectName(QStringLiteral("completer")); 0039 m_completionModel = new QStringListModel(emulatedCommandBar); 0040 m_completer->setModel(m_completionModel); 0041 m_completer->setCaseSensitivity(Qt::CaseInsensitive); 0042 m_completer->popup()->installEventFilter(emulatedCommandBar); 0043 } 0044 0045 void Completer::startCompletion(const CompletionStartParams &completionStartParams) 0046 { 0047 if (completionStartParams.completionType != CompletionStartParams::None) { 0048 m_completionModel->setStringList(completionStartParams.completions); 0049 const QString completionPrefix = m_edit->text().mid(completionStartParams.wordStartPos, m_edit->cursorPosition() - completionStartParams.wordStartPos); 0050 m_completer->setCompletionPrefix(completionPrefix); 0051 m_completer->complete(); 0052 m_currentCompletionStartParams = completionStartParams; 0053 m_currentCompletionType = completionStartParams.completionType; 0054 } 0055 } 0056 0057 void Completer::deactivateCompletion() 0058 { 0059 m_completer->popup()->hide(); 0060 m_currentCompletionType = CompletionStartParams::None; 0061 } 0062 0063 bool Completer::isCompletionActive() const 0064 { 0065 return m_currentCompletionType != CompletionStartParams::None; 0066 } 0067 0068 bool Completer::isNextTextChangeDueToCompletionChange() const 0069 { 0070 return m_isNextTextChangeDueToCompletionChange; 0071 } 0072 0073 bool Completer::completerHandledKeypress(const QKeyEvent *keyEvent) 0074 { 0075 if (!m_edit->isVisible()) { 0076 return false; 0077 } 0078 0079 if (keyEvent->modifiers() == CONTROL_MODIFIER && (keyEvent->key() == Qt::Key_C || keyEvent->key() == Qt::Key_BracketLeft)) { 0080 if (m_currentCompletionType != CompletionStartParams::None && m_completer->popup()->isVisible()) { 0081 abortCompletionAndResetToPreCompletion(); 0082 return true; 0083 } 0084 } 0085 if (keyEvent->modifiers() == CONTROL_MODIFIER && keyEvent->key() == Qt::Key_Space) { 0086 CompletionStartParams completionStartParams = activateWordFromDocumentCompletion(); 0087 startCompletion(completionStartParams); 0088 return true; 0089 } 0090 if ((keyEvent->modifiers() == CONTROL_MODIFIER && keyEvent->key() == Qt::Key_P) || keyEvent->key() == Qt::Key_Down) { 0091 if (!m_completer->popup()->isVisible()) { 0092 const CompletionStartParams completionStartParams = m_currentMode->completionInvoked(CompletionInvocation::ExtraContext); 0093 startCompletion(completionStartParams); 0094 if (m_currentCompletionType != CompletionStartParams::None) { 0095 setCompletionIndex(0); 0096 } 0097 } else { 0098 // Descend to next row, wrapping around if necessary. 0099 if (m_completer->currentRow() + 1 == m_completer->completionCount()) { 0100 setCompletionIndex(0); 0101 } else { 0102 setCompletionIndex(m_completer->currentRow() + 1); 0103 } 0104 } 0105 return true; 0106 } 0107 if ((keyEvent->modifiers() == CONTROL_MODIFIER && keyEvent->key() == Qt::Key_N) || keyEvent->key() == Qt::Key_Up) { 0108 if (!m_completer->popup()->isVisible()) { 0109 const CompletionStartParams completionStartParams = m_currentMode->completionInvoked(CompletionInvocation::NormalContext); 0110 startCompletion(completionStartParams); 0111 setCompletionIndex(m_completer->completionCount() - 1); 0112 } else { 0113 // Ascend to previous row, wrapping around if necessary. 0114 if (m_completer->currentRow() == 0) { 0115 setCompletionIndex(m_completer->completionCount() - 1); 0116 } else { 0117 setCompletionIndex(m_completer->currentRow() - 1); 0118 } 0119 } 0120 return true; 0121 } 0122 if (keyEvent->key() == Qt::Key_Enter || keyEvent->key() == Qt::Key_Return) { 0123 if (!m_completer->popup()->isVisible() || m_currentCompletionType != CompletionStartParams::WordFromDocument) { 0124 m_currentMode->completionChosen(); 0125 } 0126 deactivateCompletion(); 0127 return true; 0128 } 0129 return false; 0130 } 0131 0132 void Completer::editTextChanged(const QString &newText) 0133 { 0134 if (!m_isNextTextChangeDueToCompletionChange) { 0135 m_textToRevertToIfCompletionAborted = newText; 0136 m_cursorPosToRevertToIfCompletionAborted = m_edit->cursorPosition(); 0137 } 0138 // If we edit the text after having selected a completion, this means we implicitly accept it, 0139 // and so we should dismiss it. 0140 if (!m_isNextTextChangeDueToCompletionChange && m_completer->popup()->currentIndex().row() != -1) { 0141 deactivateCompletion(); 0142 } 0143 0144 if (m_currentCompletionType != CompletionStartParams::None && !m_isNextTextChangeDueToCompletionChange) { 0145 updateCompletionPrefix(); 0146 } 0147 } 0148 0149 void Completer::setCurrentMode(ActiveMode *currentMode) 0150 { 0151 m_currentMode = currentMode; 0152 } 0153 0154 void Completer::setCompletionIndex(int index) 0155 { 0156 const QModelIndex modelIndex = m_completer->popup()->model()->index(index, 0); 0157 // Need to set both of these, for some reason. 0158 m_completer->popup()->setCurrentIndex(modelIndex); 0159 m_completer->setCurrentRow(index); 0160 0161 m_completer->popup()->scrollTo(modelIndex); 0162 0163 currentCompletionChanged(); 0164 } 0165 0166 void Completer::currentCompletionChanged() 0167 { 0168 const QString newCompletion = m_completer->currentCompletion(); 0169 if (newCompletion.isEmpty()) { 0170 return; 0171 } 0172 QString transformedCompletion = newCompletion; 0173 if (m_currentCompletionStartParams.completionTransform) { 0174 transformedCompletion = m_currentCompletionStartParams.completionTransform(newCompletion); 0175 } 0176 0177 m_isNextTextChangeDueToCompletionChange = true; 0178 m_edit->setSelection(m_currentCompletionStartParams.wordStartPos, m_edit->cursorPosition() - m_currentCompletionStartParams.wordStartPos); 0179 m_edit->insert(transformedCompletion); 0180 m_isNextTextChangeDueToCompletionChange = false; 0181 } 0182 0183 void Completer::updateCompletionPrefix() 0184 { 0185 const QString completionPrefix = 0186 m_edit->text().mid(m_currentCompletionStartParams.wordStartPos, m_edit->cursorPosition() - m_currentCompletionStartParams.wordStartPos); 0187 m_completer->setCompletionPrefix(completionPrefix); 0188 // Seem to need a call to complete() else the size of the popup box is not altered appropriately. 0189 m_completer->complete(); 0190 } 0191 0192 CompletionStartParams Completer::activateWordFromDocumentCompletion() 0193 { 0194 static const QRegularExpression wordRegEx(QStringLiteral("\\w+"), QRegularExpression::UseUnicodePropertiesOption); 0195 QRegularExpressionMatch match; 0196 0197 QStringList foundWords; 0198 // Narrow the range of lines we search around the cursor so that we don't die on huge files. 0199 const int startLine = qMax(0, m_view->cursorPosition().line() - 4096); 0200 const int endLine = qMin(m_view->document()->lines(), m_view->cursorPosition().line() + 4096); 0201 for (int lineNum = startLine; lineNum < endLine; lineNum++) { 0202 const QString line = m_view->document()->line(lineNum); 0203 int wordSearchBeginPos = 0; 0204 while ((match = wordRegEx.match(line, wordSearchBeginPos)).hasMatch()) { 0205 const QString foundWord = match.captured(); 0206 foundWords << foundWord; 0207 wordSearchBeginPos = match.capturedEnd(); 0208 } 0209 } 0210 foundWords.removeDuplicates(); 0211 std::sort(foundWords.begin(), foundWords.end(), caseInsensitiveLessThan); 0212 CompletionStartParams completionStartParams; 0213 completionStartParams.completionType = CompletionStartParams::WordFromDocument; 0214 completionStartParams.completions = foundWords; 0215 completionStartParams.wordStartPos = wordBeforeCursorBegin(); 0216 return completionStartParams; 0217 } 0218 0219 QString Completer::wordBeforeCursor() 0220 { 0221 const int wordBeforeCursorBegin = this->wordBeforeCursorBegin(); 0222 return m_edit->text().mid(wordBeforeCursorBegin, m_edit->cursorPosition() - wordBeforeCursorBegin); 0223 } 0224 0225 int Completer::wordBeforeCursorBegin() 0226 { 0227 int wordBeforeCursorBegin = m_edit->cursorPosition() - 1; 0228 while (wordBeforeCursorBegin >= 0 0229 && (m_edit->text()[wordBeforeCursorBegin].isLetterOrNumber() || m_edit->text()[wordBeforeCursorBegin] == QLatin1Char('_'))) { 0230 wordBeforeCursorBegin--; 0231 } 0232 wordBeforeCursorBegin++; 0233 return wordBeforeCursorBegin; 0234 } 0235 0236 void Completer::abortCompletionAndResetToPreCompletion() 0237 { 0238 deactivateCompletion(); 0239 m_isNextTextChangeDueToCompletionChange = true; 0240 m_edit->setText(m_textToRevertToIfCompletionAborted); 0241 m_edit->setCursorPosition(m_cursorPosToRevertToIfCompletionAborted); 0242 m_isNextTextChangeDueToCompletionChange = false; 0243 }