#include "vtexteditcompleter.h" #include #include #include #include #include #include #include "utils/vutils.h" #include "veditor.h" #include "vmainwindow.h" extern VMainWindow *g_mainWin; VTextEditCompleter::VTextEditCompleter(QObject *p_parent) : QCompleter(p_parent), m_initialized(false) { } void VTextEditCompleter::performCompletion(const QStringList &p_words, const QString &p_prefix, Qt::CaseSensitivity p_cs, bool p_reversed, const QRect &p_rect, VEditor *p_editor) { init(); m_editor = p_editor; setWidget(m_editor->getEditor()); m_model->setStringList(p_words); setCaseSensitivity(p_cs); setCompletionPrefix(p_prefix); int cnt = completionCount(); if (cnt == 0) { finishCompletion(); return; } selectRow(p_reversed ? cnt - 1 : 0); if (cnt == 1 && currentCompletion() == p_prefix) { finishCompletion(); return; } g_mainWin->setCaptainModeEnabled(false); m_insertedCompletion = p_prefix; insertCurrentCompletion(); auto pu = popup(); QRect rt(p_rect); rt.setWidth(pu->sizeHintForColumn(0) + pu->verticalScrollBar()->sizeHint().width()); complete(rt); } void VTextEditCompleter::init() { if (m_initialized) { return; } m_initialized = true; m_model = new QStringListModel(this); setModel(m_model); popup()->setProperty("TextEdit", true); popup()->setItemDelegate(new QStyledItemDelegate(this)); connect(this, static_cast(&QCompleter::activated), this, [this](const QString &p_text) { insertCompletion(p_text); finishCompletion(); }); } void VTextEditCompleter::selectNextCompletion(bool p_reversed) { QModelIndex curIndex = popup()->currentIndex(); if (p_reversed) { if (curIndex.isValid()) { int row = curIndex.row(); if (row == 0) { setCurrentIndex(QModelIndex()); } else { selectRow(row - 1); } } else { selectRow(completionCount() - 1); } } else { if (curIndex.isValid()) { int row = curIndex.row(); if (!selectRow(row + 1)) { setCurrentIndex(QModelIndex()); } } else { selectRow(0); } } } bool VTextEditCompleter::selectRow(int p_row) { if (setCurrentRow(p_row)) { setCurrentIndex(currentIndex()); return true; } return false; } void VTextEditCompleter::setCurrentIndex(QModelIndex p_index, bool p_select) { auto pu = popup(); if (!pu) { return; } if (!p_select) { pu->selectionModel()->setCurrentIndex(p_index, QItemSelectionModel::NoUpdate); } else { if (!p_index.isValid()) { pu->selectionModel()->clear(); } else { pu->selectionModel()->setCurrentIndex(p_index, QItemSelectionModel::ClearAndSelect); } } p_index = pu->selectionModel()->currentIndex(); if (!p_index.isValid()) { pu->scrollToTop(); } else { pu->scrollTo(p_index); } } bool VTextEditCompleter::eventFilter(QObject *p_obj, QEvent *p_eve) { switch (p_eve->type()) { case QEvent::KeyPress: { if (p_obj != popup() || !m_editor) { break; } bool exited = false; QKeyEvent *ke = static_cast(p_eve); const int key = ke->key(); const int modifiers = ke->modifiers(); switch (key) { case Qt::Key_N: V_FALLTHROUGH; case Qt::Key_P: if (VUtils::isControlModifierForVim(modifiers)) { selectNextCompletion(key == Qt::Key_P); insertCurrentCompletion(); return true; } break; case Qt::Key_J: V_FALLTHROUGH; case Qt::Key_K: if (VUtils::isControlModifierForVim(modifiers)) { selectNextCompletion(key == Qt::Key_K); return true; } break; case Qt::Key_BracketLeft: if (!VUtils::isControlModifierForVim(modifiers)) { break; } V_FALLTHROUGH; case Qt::Key_Escape: exited = true; // Propogate this event to the editor widget for Vim mode. if (!m_editor->getVim()) { finishCompletion(); return true; } break; case Qt::Key_E: if (VUtils::isControlModifierForVim(modifiers)) { cancelCompletion(); return true; } break; case Qt::Key_Enter: case Qt::Key_Return: { if (m_insertedCompletion != currentCompletion()) { insertCurrentCompletion(); finishCompletion(); return true; } else { exited = true; } break; } default: break; } int cursorPos = -1; if (!exited) { cursorPos = m_editor->textCursorW().position(); } bool ret = QCompleter::eventFilter(p_obj, p_eve); if (!exited) { // Detect if cursor position changed after key press. int pos = m_editor->textCursorW().position(); if (pos == cursorPos - 1) { // Deleted one char. if (m_insertedCompletion.size() > 1) { updatePrefix(m_editor->fetchCompletionPrefix()); } else { exited = true; } } else if (pos == cursorPos + 1) { // Added one char. QString prefix = m_editor->fetchCompletionPrefix(); if (prefix.size() == m_insertedCompletion.size() + 1 && prefix.startsWith(m_insertedCompletion)) { updatePrefix(prefix); } else { exited = true; } } else if (pos != cursorPos) { exited = true; } } if (exited) { // finishCompletion() will do clean up. Must be called after QCompleter::eventFilter(). finishCompletion(); } return ret; } case QEvent::Hide: { // Completion exited. cleanUp(); break; } default: break; } return QCompleter::eventFilter(p_obj, p_eve); } void VTextEditCompleter::insertCurrentCompletion() { QString completion; QModelIndex curIndex = popup()->currentIndex(); if (curIndex.isValid()) { completion = currentCompletion(); } else { completion = completionPrefix(); } insertCompletion(completion); } void VTextEditCompleter::insertCompletion(const QString &p_completion) { if (m_insertedCompletion == p_completion) { return; } m_editor->insertCompletion(m_insertedCompletion, p_completion); m_insertedCompletion = p_completion; } void VTextEditCompleter::cancelCompletion() { insertCompletion(completionPrefix()); finishCompletion(); } void VTextEditCompleter::cleanUp() { // Do not clean up m_editor and m_insertedCompletion, since activated() // signal is after the HideEvent. setWidget(NULL); g_mainWin->setCaptainModeEnabled(true); } void VTextEditCompleter::updatePrefix(const QString &p_prefix) { m_insertedCompletion = p_prefix; setCompletionPrefix(p_prefix); int cnt = completionCount(); if (cnt == 0) { finishCompletion(); } else if (cnt == 1) { setCurrentRow(0); if (currentCompletion() == p_prefix) { finishCompletion(); } } }