129 lines
6.7 KiB
Markdown
129 lines
6.7 KiB
Markdown
# 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) — DONE (first increment)
|
|
- `src/radial/radialmodel.{h,cpp}`: pure tree + geometry. Multi-level
|
|
(branch/leaf) `RadialNode` tree, angle hit-testing (0 = up, clockwise), inner
|
|
dead zone, far-flick snaps by angle, descend/ascend navigation. 14 tests.
|
|
- `src/radial/radialmenu.{h,cpp}`: frameless translucent `QWidget`; paints the
|
|
current ring (wedges, labels, branch ▸, central "back" hub), highlights the
|
|
slice under the pointer, release/click activates a leaf, descends a branch, or
|
|
ascends/cancels from the dead zone; Esc ascends/cancels.
|
|
- `src/radial/radialconfig.{h,cpp}`: JSON → radial tree parser (+ built-in
|
|
default) so multiple radials can be bound to different triggers. 8 tests.
|
|
- Wired into the plugin: `OllieView::setupRadials()` registers one trigger
|
|
action per configured radial (default **Alt+R** → editing radial). The radial
|
|
pops at the caret (`View::cursorPositionCoordinates()` → global); selection is
|
|
mouse-driven. A slice may reference any Kate action by objectName, resolved via
|
|
`findActionByName`; the special `ollie_command_palette` slice opens the palette.
|
|
- Honours the agreed model: radials are triggerable by keys (weapon-wheel style),
|
|
and the config format carries a per-radial trigger so mouse-button / gesture
|
|
triggers can be added without code changes.
|
|
- Not yet done: mouse-button and gesture triggers (only key/action triggers so
|
|
far); user-file config loading (built-in default only); frecency.
|
|
|
|
### M5 — Command vocabulary + project model + switchers — NEXT
|
|
- `:`-verb pack via `KTextEditor::Command`: `sort`, `align`, `json`, `b64`,
|
|
`uuid`, `case`, `pipe <shell>`, 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.
|