kate-deft/README.md

345 lines
16 KiB
Markdown

# Deft
Deft is my attempt to turn [Kate](https://kate-editor.org/) into a daily driver
I actually want to use. I live in Plan 9's [acme](http://acme.cat-v.org/) most
of the time, but I like a lot of what Kate offers — and I don't like
menu-driven workflows. So this is a
suite of Kate / KTextEditor plugins that pulls in ideas from the tools I reach
for — acme, sam, and the plumber from Plan 9; `M-x` from Emacs; a Sublime-style
fuzzy palette; and the radial/pie menus that video games have used for years to
put actions a flick away from the cursor — and bends Kate toward them.
The deeper reason is ergonomic: I've moved to typing one-handed, and that
reshapes everything. A workflow built on two-handed chords and reaching across
the keyboard simply doesn't work anymore. The mouse does the reaching, the
keyboard stays under one hand, and no action is allowed to require a chord or an
F-key. This isn't a preference knob — it's the constraint the whole design is
built around.
The aim is a text editor 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, in the spirit of a game's weapon wheel), not a
trek to the menubar.
- **One-handed typing (the driving constraint).** Input must work comfortably
with a single hand. This is not a convenience toggle; it is why the mouse
carries the reaching and why chords are off the table.
- **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.**
## 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)
- [plan9port](https://9fans.github.io/plan9port/) — the `plumb` plugin is a
plumb client and talks to plan9port's plumber at runtime (see
[docs/PLUMBING.md](docs/PLUMBING.md)). Only needed if you enable `plumb`
(highly recommended!).
## 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 seven
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_sloppyfocus.so` | `[deft:util] sloppyfocus` | Focus follows the mouse into the view/pane under the pointer |
| `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. The
simplest way is `cmake --install`, which already targets the right place:
```sh
cmake --install build
```
**By default this installs into your home directory, not system-wide.** The
build sets `CMAKE_INSTALL_PREFIX` to `~/.local` when you don't pass one (these
are personal plugins, so installing them under `/usr/local` — the stock CMake
default — would need `root` and is the wrong place), and it points the plugin
install dir at Qt's own plugin path. So the plugins land at:
```
~/.local/lib/<arch>/qt6/plugins/kf6/ktexteditor/deft_*.so
```
which is exactly the directory Kate scans (`QT_PLUGIN_PATH`). To install
system-wide instead, pass an explicit prefix (and expect to need `root`):
```sh
cmake --install build --prefix /usr
```
### Making Kate find a user-local install
Qt only loads plugins from directories on **`QT_PLUGIN_PATH`**, and that does
*not* include `~/.local` by default — so after a home-directory install Kate
will not list the plugins until that path is on the search list. This is a
one-time, per-user setup step.
The install **automates it for you**: when the prefix is inside your home, it
writes a login snippet to
`~/.config/plasma-workspace/env/99-deft.sh` that adds the plugin directory to
`QT_PLUGIN_PATH`. Plasma sources it at login, so after the next **log out / log
in** Kate finds the plugins. To use them in the *current* session without
logging out, run the export the installer prints, e.g.:
```sh
export QT_PLUGIN_PATH="$HOME/.local/lib/x86_64-linux-gnu/qt6/plugins:$QT_PLUGIN_PATH"
```
(For a non-Plasma session, put that same line in your shell profile. For a
system-prefix install this step is unnecessary; the installer prints the ready
snippet under the build dir if you still want it.)
Then in Kate: Settings → Configure Kate → Plugins → enable each **`[deft:…]`**
plugin you installed. See [The plugins](#the-plugins) below for what each one
does and how to drive it.
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.
## The plugins
Each plugin is independent — enable only what you want. Keys shown below are the
defaults.
> ⚠️ **These plugins are opinionated.** They are built around one person's
> workflow (mouse-at-the-caret, one-handed typing, no chords, Plan 9 / Emacs
> habits), and several *reshape or suppress Kate's defaults* rather than just
> adding features:
>
> - `radial` hijacks the **right mouse button** in the editor, suppressing the
> normal context menu while enabled.
> - `acme` rebinds `Ctrl+A/E/H/U/W`, repurposes **double-click**, turns
> left-button **mouse chords** into Cut/Paste, and kills X11 middle-click
> primary paste.
> - `palette` and `switch` claim `Alt+X/P/G`; `plumb` claims `F2`.
>
> This is deliberate, not a bug. If you want a conventional Kate, don't enable
> the ones whose gestures you aren't ready for — try them one at a time.
### `[deft:util] palette` — command palette (M-x)
![The Deft command palette open over a Kate window](docs/img/deft-mx.png)
A Sublime/Emacs-style launcher over **every action currently offered by the
window**, matched with the orderless `FuzzyRanker` (space-separated tokens in
any order, with typo tolerance) and ranked by *frecency* (frequency + recency of
your past choices).
- **Open:** `Alt+X`. Type to filter, `Enter` or click to run, `Esc` to dismiss.
- Entries include all live menu/toolbar actions, the `:`-verbs below, and — when
`switch` is enabled — **Go to File** and **Go to Symbol**.
- Usage is remembered in `~/.config/deft/palette.frecency.json`.
It also registers a small pack of **`:` command-line verbs** (type `:` in Kate,
or pick them from the palette). Each transforms the selection in place, or the
whole document when nothing is selected:
| Verb | Effect |
|------|--------|
| `osort [u] [i]` | sort selected lines (`u` = unique, `i` = case-insensitive) |
| `rsort` | reverse the order of selected lines |
| `case <style>` | recase: `upper` / `lower` / `title` / `snake` / `camel` / `kebab` |
| `b64enc` / `b64dec` | Base64 encode / decode |
| `rot13` | ROT13 |
| `uuid` | insert a random UUID at the cursor |
| `pipe <shell>` | replace the selection with the output of `sh -c <shell>` (selection on stdin) |
> `sort` is registered as `osort` because Kate already ships a built-in `sort`;
> the KTextEditor registry drops a whole command object if any one name
> collides.
### `[deft:util] switch` — project file & symbol switchers
Jump within the active project without the file tree. The project scope is
Kate's loaded project when present (via the Project plugin), otherwise it is
discovered from the active document (VCS root, else its directory).
- **Go to File** — `Alt+P`. Fuzzy-match the full project-relative path (files
come from VCS/discovery). `Enter`/click opens.
- **Go to Symbol** — `Alt+G`. Fuzzy-match symbols indexed with **ctags**
(requires `ctags` on `PATH`); jumps to the definition.
- The index is built asynchronously and cached; the palette opens instantly and
fills in (showing "Indexing…" on a cold project). It is invalidated when Kate
switches projects.
- Rankings are remembered in `~/.config/deft/switch.frecency.json`.
### `[deft:util] radial` — radial caret menu (mouse)
![The Deft radial caret menu](docs/img/deft-radial.png)
A multi-level pie menu for actions that **complete in place** (cut, copy, paste,
comment, case changes…), so the mouse stays at the text. The model is the
weapon/emote wheel from games: a quick flick toward a direction, not a hunt
through a list.
- **Open:** `Alt+R` (at the caret) **or press the right mouse button** in the
editor (at the pointer; the normal context menu is suppressed while enabled).
- **Choose:** move toward a slice and release, or click it. A branch slice
(marked ▸) opens a submenu; the centre hub goes back / cancels.
- Slices can trigger any action by name, including `palette`/`switch` doors.
- **LSP branch (conditional).** When Kate's **LSP Client** plugin is active for
the document, the right-button radial grows an **LSP** submenu — Go to
Definition / Declaration / Type, Find References / Implementations, Rename,
Format, Code Action, Symbol Info, Hover. It is decided at open time (resolving
the actions live), so it appears only when a language server is backing the
buffer and is hidden entirely otherwise.
- **Ollie branch (conditional).** When the **Ollie** Kate plugin (the sibling
AI-agent plugin) is enabled, the right-button radial grows an **Ollie**
submenu — Ask, Explain, Fix, Refactor, Tests, Doc, Verbatim, Start — invoking
Ollie's context actions. Shown only while that plugin is loaded.
- **Pointer follows focus.** When a slice changes the active pane (split, close,
or focus-move), the pointer is warped to the centre of the new pane so the
mouse stays where your attention is — the same cursor-follows-focus feel as
acme. On X11 this is a direct `QCursor::setPos`; on Wayland (which forbids
that) it uses the pointer-constraints protocol, a technique sourced from
[eaburns's Wayland devdraw/acme](https://github.com/eaburns/plan9port/tree/wayland).
- **Configure:** drop a `radials.json` at `~/.config/deft/radials.json` to
replace the built-in tree; when it is absent, empty, or invalid, the built-in
default is used. See [docs/radials.example.json](docs/radials.example.json)
for the format.
### `[deft:util] sloppyfocus` — focus follows mouse
Keyboard focus follows the pointer: move the mouse into an editor view or tool
pane and, after a short hover delay, that pane gets keyboard focus — no click.
The acme way of working, where the mouse and the keyboard target the same place.
- Works for editor views and focusable tool panes (terminal, file tree, etc.).
- **Focus-stealing prevention:** a configurable hover **delay** and a **minimum
stay time** stop rapid bouncing; focus is suppressed while a modal dialog is
open or when a floating widget in another window holds focus.
- Configure it in Settings → Configure Kate → Plugins → **Sloppy Focus**
(delay, minimum stay time, raise-on-focus, dialog/floating guards).
- Pairs naturally with `radial`'s pointer-follows-focus warping — one moves the
pointer to the focused pane, the other moves focus to the pointed-at pane.
### `[deft:nineify] acme` — acme editing gestures
![Acme-style bracket/quote pair selection in Kate](docs/img/deft-acme-select.png)
Plan 9 *acme* muscle memory inside Kate. Installed as a filter so it never
clashes with Kate's own bindings.
- **Line keys:** `Ctrl+A` beginning of line, `Ctrl+E` end of line, `Ctrl+H`
erase char, `Ctrl+U` erase to line start, `Ctrl+W` erase previous word.
- **Double-click** inside a bracket/quote pair selects the enclosed text.
- **Mouse chords** (hold left, tap the other): left+middle = **Cut**,
left+right = **Paste**. X11 middle-click primary-paste is suppressed.
### `[deft:nineify] sam` — structural regular expressions
![The Sam structural-regexp tool view in Kate](docs/img/deft-sam.png)
Run Plan 9 *sam* structural-regexp programs against your text — the expressive
`x/re/`, `g/re/`, `s/re/txt/`, `,` dot-addressing model, not line-at-a-time
find/replace.
- Opens a dockable **Sam** tool view: a program editor, a **Run** button
(`Ctrl+Return`), and an output log.
- Runs against the **active document**, or across open buffers with the
multi-file `X`/`Y` loop commands.
- Edits are applied in a single undo transaction.
### `[deft:nineify] plumb` — Plan 9 plumber
![Plumbing a path from Kate to the Plan 9 plumber](docs/img/deft-ollie-plumb.png)
Make Kate a first-class *plumb* client.
- **Plumb under the caret:** `F2`. Sends the file-path token under the cursor
(or the selection, so `foo.cpp:42` works) through the plumber's **send** port,
resolved relative to the active document's directory.
- Kate also holds the **edit** port open, so files/addresses plumbed from
elsewhere open here.
- If no plumber is running, it falls back to opening the target directly (URLs
via the system handler, `path[:line]` in Kate).
## 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
sloppyfocus/
sloppyfocusplugin.{h,cpp,json} [deft:util] sloppyfocus — focus follows mouse
commands/
texttransforms.{h,cpp} pure text transforms (headless)
deftcommands.{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/
PLUMBING.md configuring the Plan 9 plumber for Kate
SAM.md the sam structural-regexp dialect
radials.example.json example radial-menu config
img/ screenshots used in this README
```
For contributing or working on the code, see [AGENTS.md](AGENTS.md).