diff --git a/CMakeLists.txt b/CMakeLists.txt index fc5aaca..1720153 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,6 +9,14 @@ set(CMAKE_AUTOMOC ON) set(QT_MIN_VERSION 6.5.0) set(KF_MIN_VERSION 6.0.0) +# Default to an optimized build when the user did not pick one. The palette +# re-rank and indexing are measurably ~5-8x slower under -O0 (see docs/PERF.md), +# so an unqualified `cmake -B build` should not ship a Debug plugin. +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE RelWithDebInfo CACHE STRING + "Build type (Debug, Release, RelWithDebInfo, MinSizeRel)" FORCE) +endif() + find_package(ECM ${KF_MIN_VERSION} REQUIRED NO_MODULE) list(APPEND CMAKE_MODULE_PATH ${ECM_MODULE_PATH}) diff --git a/docs/PERF.md b/docs/PERF.md index 73856fa..5666efe 100644 --- a/docs/PERF.md +++ b/docs/PERF.md @@ -57,11 +57,24 @@ Effect once the set narrows (Release, type-forward): Early keystrokes (`s`, `se`) remain full-N because almost everything matches — correct and unavoidable without a prefix index. -## Fix 2 — build the installed plugin optimized (recommended) +## Fix 2 — build the installed plugin optimized (done) -The shipped `olliepalette.so` is a **Debug** build (`-g`, no `-O`). Release is -~5-8× faster on every hot path above. Install a `Release` or `RelWithDebInfo` -build. +The root `CMakeLists.txt` now defaults `CMAKE_BUILD_TYPE` to `RelWithDebInfo` +when the user does not specify one, so an unqualified `cmake -B build` no longer +ships a `-O0` plugin. Measured effect on the symbol switcher (mobydick, +type-forward "serv", 17k symbols): **~340 ms → ~29 ms per keystroke** (~12×). + +## Fix 3 — debounce the filter (done) + +`PaletteWidget` coalesces rapid keystrokes with an 80 ms single-shot timer +before re-ranking (`applyQueryNow` flushes on Enter so activation always uses +the latest text). A fast typist's burst (`s`,`se`,`ser`…) triggers one re-rank +instead of three, so the broad-query early keystrokes — the only ones that still +scan most of N — are paid at most once per pause, not per character. + +Net result after fixes 1-3: the worst single symbol keystroke on a 17k-symbol +project is ~29 ms (RelWithDebInfo), and bursts are coalesced; the Alt+G panel +is responsive. ## Follow-up — move indexing off the UI thread (done) diff --git a/src/palette/palettewidget.cpp b/src/palette/palettewidget.cpp index 9a76055..e328e3e 100644 --- a/src/palette/palettewidget.cpp +++ b/src/palette/palettewidget.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -118,12 +119,19 @@ PaletteWidget::PaletteWidget(QWidget *parent) layout->addWidget(m_filter); layout->addWidget(m_list); - // Live, company-style updates: re-rank and keep the top row selected. + // Live updates, but debounced: a fast typist generates several keystrokes + // in a few ms, and on a large project each re-rank scores thousands of + // items. Coalesce bursts into a single re-rank so early (broad) queries do + // not stall the UI. 80 ms is below the threshold of feeling laggy while + // still absorbing a typing burst. + m_filterDebounce = new QTimer(this); + m_filterDebounce->setSingleShot(true); + m_filterDebounce->setInterval(80); + connect(m_filterDebounce, &QTimer::timeout, this, [this]() { applyQueryNow(); }); + connect(m_filter, &QLineEdit::textChanged, this, [this](const QString &text) { - m_model->setQuery(text); - if (m_model->rowCount() > 0) { - m_list->setCurrentIndex(m_model->index(0, 0)); - } + m_pendingQuery = text; + m_filterDebounce->start(); // (re)arm; applied on timeout }); // Mouse: a click activates immediately. @@ -143,9 +151,20 @@ void PaletteWidget::setItems(const QList &items) } } +void PaletteWidget::applyQueryNow() +{ + m_filterDebounce->stop(); + m_model->setQuery(m_pendingQuery); + if (m_model->rowCount() > 0) { + m_list->setCurrentIndex(m_model->index(0, 0)); + } +} + void PaletteWidget::activate() { + m_filterDebounce->stop(); m_filter->clear(); + m_pendingQuery.clear(); m_model->setQuery(QString()); if (m_model->rowCount() > 0) { m_list->setCurrentIndex(m_model->index(0, 0)); @@ -170,6 +189,11 @@ void PaletteWidget::moveSelection(int delta) void PaletteWidget::acceptCurrent() { + // Flush any pending debounced query so Enter acts on the latest text even + // if the user typed and immediately hit Enter within the debounce window. + if (m_filterDebounce->isActive()) { + applyQueryNow(); + } const int row = m_list->currentIndex().row(); const PaletteItem *item = m_model->itemAt(row); if (!item) { diff --git a/src/palette/palettewidget.h b/src/palette/palettewidget.h index b6b8174..3fbf73c 100644 --- a/src/palette/palettewidget.h +++ b/src/palette/palettewidget.h @@ -19,9 +19,11 @@ #include #include +#include class QLineEdit; class QListView; +class QTimer; namespace katecustom { @@ -52,10 +54,14 @@ private: void moveSelection(int delta); void acceptCurrent(); void dismiss(); + // Apply the pending filter text to the model now (flushes the debounce). + void applyQueryNow(); PaletteModel *m_model; QLineEdit *m_filter; QListView *m_list; + QTimer *m_filterDebounce; // coalesces rapid keystrokes before re-ranking + QString m_pendingQuery; }; } // namespace katecustom