Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 18 additions & 32 deletions plugins/itemfakevim/itemfakevim.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ using namespace FakeVim::Internal;
#include <QMessageBox>
#include <QMetaMethod>
#include <QKeyEvent>
#include <QInputMethodEvent>
#include <QProcess>
#include <QPaintEvent>
#include <QPainter>
Expand Down Expand Up @@ -164,33 +163,26 @@ class TextEditWrapper final : public QObject
m_handler->installEventFilter();
m_handler->setupWidget();
m_handler->enterCommandMode();
}

void setInsertMode(bool insertMode)
{
m_insertMode = insertMode;
m_textEditWidget->setAttribute(Qt::WA_InputMethodEnabled, insertMode);
// Disable input method so the platform IM framework (ibus, fcitx,
// Wayland text-input) does not intercept key presses. With IM
// enabled, presses arrive as QInputMethodEvents instead of
// QKeyEvents, which bypasses FakeVim's key handler and breaks
// undo grouping (each character becomes a separate undo step)
// and visual-block insert replay.
m_textEditWidget->setAttribute(Qt::WA_InputMethodEnabled, false);
}

bool eventFilter(QObject *obj, QEvent *ev) override
{
// Block all input method events in command mode.
// On Wayland compositors with press-and-hold for alternative
// characters (KDE Plasma 6.7+), key events are intercepted at the
// compositor level. Preedit events trigger an alternatives popup,
// and commit events insert text — both bypass FakeVim's QKeyEvent
// filter. Block the entire IM interaction in command mode and
// route any committed text through FakeVim's input handler.
if (obj == m_textEditWidget && !m_insertMode) {
if (ev->type() == QEvent::InputMethod) {
auto *imEvent = static_cast<QInputMethodEvent *>(ev);
const QString text = imEvent->commitString();
if (!text.isEmpty())
m_handler->handleInput(text);
return true;
}
if (ev->type() == QEvent::InputMethodQuery)
return true;
// Block input method events unconditionally. Disabling
// WA_InputMethodEnabled is not sufficient on Wayland where the
// compositor can still deliver IM events via the text-input
// protocol. Letting them through would bypass FakeVim's
// QKeyEvent handler and insert raw text into the document.
if (ev->type() == QEvent::InputMethod
|| ev->type() == QEvent::InputMethodQuery)
{
return true;
}

// Handle completion popup.
Expand Down Expand Up @@ -441,7 +433,6 @@ class TextEditWrapper final : public QObject

bool m_hasBlockSelection = false;

bool m_insertMode = false;

using Selection = QAbstractTextDocumentLayout::Selection;
using SelectionList = QVector<Selection>;
Expand Down Expand Up @@ -681,7 +672,7 @@ class Proxy final : public QObject
QLabel *m_statusBarIcon;
};

void connectSignals(FakeVimHandler *handler, Proxy *proxy, TextEditWrapper *wrapper)
void connectSignals(FakeVimHandler *handler, Proxy *proxy)
{
handler->commandBufferChanged
.set([proxy](const QString &contents, int cursorPos, int anchorPos, int messageLevel) {
Expand Down Expand Up @@ -743,11 +734,6 @@ void connectSignals(FakeVimHandler *handler, Proxy *proxy, TextEditWrapper *wrap
*output = QString::fromLocal8Bit(proc.readAllStandardOutput());
}
);
handler->modeChanged.set(
[wrapper](bool insertMode) {
wrapper->setInsertMode(insertMode);
}
);
}

bool installEditor(QAbstractScrollArea *textEdit, const QString &sourceFileName, ItemFakeVimLoader *loader)
Expand All @@ -767,7 +753,7 @@ bool installEditor(QAbstractScrollArea *textEdit, const QString &sourceFileName,

// Connect slots to FakeVimHandler signals.
auto proxy = new Proxy(wrapper, statusBar, wrapper);
connectSignals( &wrapper->fakeVimHandler(), proxy, wrapper );
connectSignals( &wrapper->fakeVimHandler(), proxy );

wrapper->install();

Expand Down
18 changes: 18 additions & 0 deletions src/tests/itemfakevimtests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,12 @@ void ItemFakeVimTests::blockSelection()
RUN(args << "edit" << "0", "");
KEYS(":ggl" << FAKEVIM_CTRL "+V" << ":jjs_" << "ESC" << "::wq" << "ENTER");
RUN(args << "read" << "0", "A_C\nD_F\nG_I");

// Test block insert with 'I' command.
// Start from "A_C\nD_F\nG_I" (result of previous 's' test).
RUN(args << "edit" << "0", "");
KEYS(":ggl" << FAKEVIM_CTRL "+V" << ":jjI_" << "ESC" << "::wq" << "ENTER");
RUN(args << "read" << "0", "A__C\nD__F\nG__I");
}

void ItemFakeVimTests::search()
Expand Down Expand Up @@ -134,3 +140,15 @@ void ItemFakeVimTests::incDecNumbers()
KEYS("l" << "2" << FAKEVIM_CTRL "+x" << "l" << "3" << FAKEVIM_CTRL "+x" << "F2");
RUN(args << "read" << "0", "1 -1 -2");
}

void ItemFakeVimTests::undoGroupsInsertSession()
{
const QString tab1 = testTab(1);
const Args args = Args() << "tab" << tab1;

// Type "Hello" in insert mode, then undo in the same session.
// Undo should remove the entire insert session, not just one character.
RUN(args << "edit", "");
KEYS(":iHello" << "ESC" << ":u:wq" << "ENTER");
RUN(args << "read" << "0", "");
}
2 changes: 2 additions & 0 deletions src/tests/itemfakevimtests.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ private slots:

void incDecNumbers();

void undoGroupsInsertSession();

private:
TestInterfacePtr m_test;
};
Loading