|
|
@@ -0,0 +1,321 @@
|
|
|
+#include "vtexteditcompleter.h"
|
|
|
+
|
|
|
+#include <QStringListModel>
|
|
|
+#include <QStyledItemDelegate>
|
|
|
+#include <QScrollBar>
|
|
|
+#include <QDebug>
|
|
|
+#include <QEvent>
|
|
|
+#include <QKeyEvent>
|
|
|
+
|
|
|
+#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<void(QCompleter::*)(const QString &)>(&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<QKeyEvent *>(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();
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|