159 lines
7.2 KiB
Markdown
159 lines
7.2 KiB
Markdown
# Deft
|
|
|
|
A suite of Kate plugins for people who despise menus. The aim is a text editor
|
|
that is driven by the mouse at the cursor and by plain-letter keyboard input —
|
|
never by hunting through nested menubars or memorizing complex chords.
|
|
|
|
## Design constraints
|
|
|
|
These are hard constraints that shape every decision:
|
|
|
|
- **Mouse-heavy.** The fastest input is the mouse, used *locally* at the caret
|
|
(radial / floating surfaces), not a trek to the menubar.
|
|
- **One-handed typing.** Keyboard input must work comfortably with one hand.
|
|
- **No complex chords.** Single modifier at most (`Ctrl`+letter). No leader-key
|
|
chord trees, no multi-cursor keybinds.
|
|
- **Compact keyboard.** F-keys and nav clusters live on a layer toggle, so they
|
|
carry extra cognitive cost and are avoided as primary triggers.
|
|
- **Emacs/Sublime fluency.** `M-x` (`Alt+x`) is comfortable and welcome as a
|
|
primary keyboard entry point, as is a Sublime-style type-to-filter palette.
|
|
|
|
### Unifying principle
|
|
|
|
> **One action registry. Three doors onto it:**
|
|
> **`M-x` (keyboard), a radial caret menu (mouse), and the `:` command line (optional).**
|
|
> **No action requires an F-key or a chord.**
|
|
|
|
## Why not just use Kate's command bar?
|
|
|
|
Kate's `KCommandBar` is a sealed widget: it accepts a list of actions via
|
|
`setActions()` and exposes **no hook** to customize its matcher. Its matcher,
|
|
`KFuzzyMatcher`, is single-needle, strictly in-order subsequence, with **no typo
|
|
tolerance** — by KDE's own documentation, `"gti"` will not match `"git"`. There
|
|
is no "orderless" (space-separated tokens in any order) and no company-style
|
|
live completion.
|
|
|
|
So the project replaces the palette rather than extending it, built on a custom
|
|
matcher (`FuzzyRanker`).
|
|
|
|
## Environment
|
|
|
|
- Kate 25.12.3, KDE Frameworks 6, Qt 6
|
|
- C++20, CMake, extra-cmake-modules
|
|
- C++ is the only first-class plugin path on this install (no Python/Pâté binding present)
|
|
|
|
## Build
|
|
|
|
```sh
|
|
cmake -B build -S .
|
|
cmake --build build
|
|
QT_QPA_PLATFORM=offscreen ./build/bin/test_fuzzyranker # run the matcher tests
|
|
ctest --test-dir build # or via ctest
|
|
```
|
|
|
|
## Install the plugins into Kate
|
|
|
|
Deft is **one `.so` per feature**, not a monolith. The build produces six
|
|
KTextEditor plugins under `build/bin/kf6/ktexteditor/`:
|
|
|
|
| Plugin file | Display name | Feature |
|
|
|------------------|-----------------------|---------|
|
|
| `deft_palette.so`| `[deft:util] palette` | Command palette (`Alt+X` / M-x) over every window action + the `:`-verbs + the switchers, frecency-ranked |
|
|
| `deft_switch.so` | `[deft:util] switch` | Project **Go to File** (`Alt+P`) and **Go to Symbol** (`Alt+G`) |
|
|
| `deft_radial.so` | `[deft:util] radial` | Radial caret menu (`Alt+R` / right-mouse) |
|
|
| `deft_acme.so` | `[deft:nineify] acme` | Acme editing gestures (line keys, pair selection, mouse chords) |
|
|
| `deft_sam.so` | `[deft:nineify] sam` | `sam`-style structural regular expressions |
|
|
| `deft_plumb.so` | `[deft:nineify] plumb`| Plan 9 plumber (open under cursor) |
|
|
|
|
Drop whichever features you want where Kate scans for KTextEditor plugins (a
|
|
user path on `QT_PLUGIN_PATH`):
|
|
|
|
```sh
|
|
for so in build/bin/kf6/ktexteditor/deft_*.so; do
|
|
install -D "$so" ~/.local/lib/x86_64-linux-gnu/qt6/plugins/kf6/ktexteditor/"$(basename "$so")"
|
|
done
|
|
```
|
|
|
|
Then in Kate: Settings → Configure Kate → Plugins → enable each **`[deft:…]`**
|
|
plugin you installed. Press **Alt+X** (M-x) to open the command palette; type to
|
|
filter, Enter or click to run. Open the editing **radial menu** either by
|
|
pressing **Alt+R** (opens at the caret) or by **pressing the right mouse button**
|
|
in the editor (opens at the pointer; the normal context menu is suppressed while
|
|
this is enabled). Move toward a slice and release — or click; a branch slice
|
|
(marked ▸) opens a submenu, the centre hub goes back/cancels.
|
|
|
|
The plugins cooperate but do not depend on one another: palette's "Go to
|
|
File/Symbol" entries and radial's launcher slices resolve `switch`'s actions by
|
|
object name at runtime, so each `.so` works on its own and simply gains those
|
|
entry points when its sibling is also enabled.
|
|
|
|
## Status
|
|
|
|
See [docs/PLAN.md](docs/PLAN.md) for the full roadmap.
|
|
|
|
- **Milestone 1 — FuzzyRanker (keystone): DONE.** Orderless, layered scoring
|
|
(exact / substring / word-initials / subsequence / bounded typo), merged
|
|
highlight ranges. 14 unit tests, all green.
|
|
- **Milestone 2 — custom palette widget: DONE.** `PaletteModel` +
|
|
`PaletteWidget` (frameless popup, live filtering, highlighted matches,
|
|
keyboard + mouse activation). 10 model unit tests, all green.
|
|
- Milestone 3 — KTextEditor plugin + M-x: **DONE.** `Alt+X` opens the palette
|
|
over every action in the window's GUI clients.
|
|
- Milestone 4 — radial caret menu: **DONE (first increment).** Multi-level
|
|
radial menu at the caret (`Alt+R`) or at the pointer via a **right-mouse
|
|
gesture**, mouse-driven selection, config tree with per-radial key + mouse
|
|
triggers. Free-form stroke gestures next.
|
|
- Milestone 5 — command vocabulary + project-as-directory + switchers: **DONE.**
|
|
`:`-verb pack, project file (`Alt+P`) and symbol (`Alt+G`) switchers over an
|
|
async, cached index.
|
|
- Milestone 6 — one `.so` per feature: **DONE.** The monolithic plugin is split
|
|
into six independent KTextEditor plugins (`palette`, `switch`, `radial`,
|
|
`acme`, `sam`, `plumb`), tagged `[deft:util]` / `[deft:nineify]`, cooperating
|
|
by resolving each other's actions by object name at runtime.
|
|
|
|
## Layout
|
|
|
|
Shared building blocks are static libraries (`fuzzy`, `palette`, `project`,
|
|
`commands`, …); each feature is a self-contained KTextEditor plugin that links
|
|
the libraries it needs.
|
|
|
|
```
|
|
CMakeLists.txt top-level KF6/Qt6/ECM project
|
|
src/
|
|
fuzzy/
|
|
fuzzyranker.{h,cpp} orderless + layered fuzzy matcher
|
|
test_fuzzyranker.cpp QTest suite
|
|
palette/
|
|
palettemodel.{h,cpp} ranked, filterable list model (headless)
|
|
palettewidget.{h,cpp} frameless command-palette popup
|
|
frecencystore.{h,cpp} persisted frecency ranking
|
|
paletteplugin.{h,cpp} [deft:util] palette — M-x, :-verbs, switcher doors
|
|
paletteplugin.json plugin metadata
|
|
test_*.cpp QTest suites
|
|
switch/
|
|
switchplugin.{h,cpp} [deft:util] switch — Go to File (Alt+P) / Symbol (Alt+G)
|
|
switchplugin.json plugin metadata
|
|
radial/
|
|
radialmodel.{h,cpp} radial tree + hit-test geometry (headless)
|
|
radialmenu.{h,cpp} frameless multi-level radial widget
|
|
radialconfig.{h,cpp} JSON → radial tree + built-in default
|
|
radialplugin.{h,cpp} [deft:util] radial — caret menu plugin
|
|
radialplugin.json plugin metadata
|
|
test_*.cpp QTest suites
|
|
commands/
|
|
texttransforms.{h,cpp} pure text transforms (headless)
|
|
olliecommands.{h,cpp} ":"-verb KTextEditor::Command pack (used by palette)
|
|
test_*.cpp QTest suites
|
|
project/
|
|
projectindex / symbolindex / projectindexer / kateprojectbridge
|
|
async, cached file & symbol index (used by switch)
|
|
acme/
|
|
acmeplugin.{h,cpp,json} [deft:nineify] acme — editing gestures
|
|
sam/
|
|
sam* + samplugin.{h,cpp,json} [deft:nineify] sam — structural regexps
|
|
plumb/
|
|
plumb* + plumbplugin [deft:nineify] plumb — Plan 9 plumber
|
|
docs/
|
|
PLAN.md goal, constraints, roadmap
|
|
```
|