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 }