# Plan — kate-custom ## Goal Build a suite of Kate plugins that make the editor usable without menus, tuned to one specific operator profile: - mouse-heavy, with the mouse used at the caret rather than at the menubar - one-handed keyboard typing - no complex chords; single modifier at most - compact keyboard where F-keys/nav clusters sit behind a layer toggle - long Emacs history and some Sublime Text; `M-x` and type-to-filter palettes are familiar and welcome The spine of the suite is a single **action registry** reached through three interchangeable doors: `M-x` (keyboard), a radial caret menu (mouse), and the `:` command line (optional). Every feature registers its commands once and becomes reachable from all three. No action depends on an F-key or a chord. ## Why a custom matcher is the keystone Kate's built-in command bar cannot be fixed in place: - `KCommandBar` (KConfigWidgets) exposes only `setActions()`. There is no hook to replace or configure its matcher. - `KFuzzyMatcher` (KCoreAddons) is single-needle, strictly in-order subsequence, and not typo tolerant. KDE's own docs state `"gti"` will not match `"git"`. No orderless, no company-style completion. Therefore the palette is replaced, not extended, and the replacement is built on `FuzzyRanker`, a matcher with: - **orderless** tokenization (space-separated needles matched in any order) - **layered scoring**, strongest tier first: 1. exact 2. substring 3. word-boundary initials (`rs` -> **R**ename **S**ymbol) 4. in-order subsequence 5. bounded typo via a single adjacent transposition (`gti` -> `git`; deliberately narrow to avoid false positives like `ren` -> "Open File") - **merged highlight ranges** for the UI - AND semantics across needles, case-folded, shorter-candidate preference ## Milestones ### M1 — FuzzyRanker (keystone) — DONE - Repo scaffold: top-level CMake (KF6/Qt6/ECM), `src/`, `src/fuzzy/`. - `src/fuzzy/fuzzyranker.{h,cpp}`: tokenize + layered scoring + ranges. - `src/fuzzy/test_fuzzyranker.cpp`: QTest suite, 14 cases, all green, including `gti -> git` and `ren sym -> Rename Symbol`. ### M2 — Palette widget (replaces KCommandBar) — DONE - `PaletteModel` (`src/palette/palettemodel.{h,cpp}`): `QAbstractListModel`, re-ranks via `FuzzyRanker` on `setQuery`, exposes id/label/group/score and highlight ranges per row, frecency bonus. No widget dependency. - `PaletteWidget` (`src/palette/palettewidget.{h,cpp}`): frameless popup, filter line + results list, live company-style updates, top hit preselected, custom delegate bolds matched ranges. Keyboard: Up/Down/PageUp/PageDown, Enter activates, Esc cancels — cursor stays in the filter line, no chords. Mouse: click a row to activate. Emits `activatedId(id)` / `cancelled()`. - `src/palette/test_palettemodel.cpp`: 10 QTest cases, all green (filtering, orderless, typo, best-first ordering, frecency tie-break, highlight ranges). ### M3 — KTextEditor plugin + M-x + action registry — DONE - `src/plugin/ollieplugin.{h,cpp}` + `ollieplugin.json`: a loadable `KTextEditor::Plugin` (`olliepalette.so`) built with `kcoreaddons_add_plugin`. - `OllieView` (one per MainWindow) registers the **M-x** action, `Alt+X`, a single modifier with the keys adjacent on a compact layout; `ApplicationShortcut` context so it is always live. - On trigger it aggregates every enabled, visible `QAction` from the window's `guiFactory()->clients()` action collections, keyed by stable `objectName`, and feeds them to `PaletteWidget`. The action's current shortcut is shown in the group column. Accepting a row triggers the underlying `QAction`. - Installs to `kf6/ktexteditor`; metadata verified valid via `KPluginMetaData`. - Not yet wired: frecency (the model supports a bonus, but the plugin currently passes 0 for every action — usage tracking is deferred). - Duplicate handling: actions are deduplicated only by pointer identity (the exact same action object reachable through several GUI clients is listed once — functionally lossless). Distinct actions are never dropped. When two distinct actions would render with the same label, the label is enriched through a fallback chain until every visible row is unique: component name → objectName → shortcut → numeric suffix. Each entry also gets a unique id so activation always triggers the intended action. ### M4 — Radial caret menu (mouse door) — NEXT - Mouse-triggered radial menu positioned at the caret (`View::cursorToCoordinate`), flick-to-select by angle. - Slices invoke registry actions; one slice opens the palette / command line, so the keyboard-expensive paths are never required. ### M5 — Command vocabulary + project model + switchers - `:`-verb pack via `KTextEditor::Command`: `sort`, `align`, `json`, `b64`, `uuid`, `case`, `pipe `, etc. These populate M-x and the radial. - **Project = directory.** "Open folder == open project" (VSCode/Sublime model); no `.kateproject` ceremony. - File / symbol switchers scoped to the opened folder (e.g. `git ls-files` / `fd` for files; LSP or ctags for symbols), all through the M2 palette. ## Build and test ```sh cmake -B build -S . cmake --build build ctest --test-dir build # or directly: QT_QPA_PLATFORM=offscreen ./build/bin/test_fuzzyranker ``` ## Environment facts (verified) - Kate 25.12.3, KDE Frameworks 6, Qt 6.10, cmake 4.2.3, g++, extra-cmake-modules. - KTextEditor + KCoreAddons + KConfigWidgets + KXmlGui + KRunner dev headers present. - No Python/Pâté binding installed; C++ is the only first-class plugin path.