diff options
Diffstat (limited to 'docs')
| -rw-r--r-- | docs/config.md | 11 | ||||
| -rw-r--r-- | docs/fs.md | 10 | ||||
| -rw-r--r-- | docs/helix-keys.md | 16 | ||||
| -rw-r--r-- | docs/tags.md | 3 | ||||
| -rw-r--r-- | docs/v9fs.md | 107 |
5 files changed, 138 insertions, 9 deletions
diff --git a/docs/config.md b/docs/config.md index ff960aac..99da2fdd 100644 --- a/docs/config.md +++ b/docs/config.md @@ -252,6 +252,17 @@ the constant to `1.0` to accept every projected color, collapses included. already open keep the shell they are running. A bare name is resolved against the handful of directories a shell actually lives in, not `$PATH`. +On Linux, `Tty9p` (`SPC n 9`) starts that shell with a private kernel 9P mount, +asking sudo inside the new terminal. `$PARDES_MOUNT` names the mountpoint. +The installed `pardes-v9fs` helper lives beside the editor; development builds +can set `PARDES_V9FS_HELPER` to its absolute path. See [v9fs.md](v9fs.md). + +Ctrl-B switches between raw TTY and editor mode. Plain Escape at a detected +shell prompt hops back to the previous pane. Other keys, including Ctrl-O, Ctrl-W, +paste shortcuts, and modified Escape belong +to the child. Use `Togglettymode` in the pane tag to return to editor +mode in place. Desktop paste events still feed the terminal. + `Font` and `FontSel` exist ONLY in the SDL GUI and native macOS builds — a terminal's font belongs to its emulator and a browser's to the page — so a `Font` line is one of the silently-ignored ones everywhere else. Both builds @@ -50,11 +50,15 @@ drive Unix or TCP without a kernel mount: For [Linux v9fs](https://www.kernel.org/doc/html/latest/filesystems/9p.html), use `version=9p2000,cache=none,access=any` and `trans=unix`, or `trans=tcp` with `port=5640`. Set `uname`, `dfltuid`, and `dfltgid` for the local user. -Leave `aname` empty: the mount root contains `os` and `self`. Kernel mounts -are not part of the test suite. Neither 9P2000.u nor 9P2000.L is implemented. +Leave `aname` empty: the mount root contains `os` and `self`. The opt-in +[Linux v9fs experiment](v9fs.md) tests a kernel mount in a separate subprocess +namespace (`zig build v9fs-test`, requiring explicit mount authorization). +Neither 9P2000.u nor 9P2000.L is implemented. Existing Plan9port/v9fs clients need a userspace bridge for QUIC. The server root contains `os` and `self`. Under `self`, `index` lists panes, +`README` explains the interface. `new/` lists its creation endpoints and another +`README`; listing, walking and statting entries do not create panes. Opening `new/ctl` creates a pane and returns its serial, and `pane/<serial>` contains `body`, `tag`, `ctl`, `addr`, `data`, `event`, and selection files. Terminal panes additionally have `pty/{ctl,status,data}`. @@ -82,6 +86,8 @@ This is a control filesystem, not a complete POSIX export. Native filenames may contain up to 255 bytes. Existing regular OS files support read, write, and truncation to zero; protocol create, remove, rename, and other metadata changes are refused. Ownership, permissions, and timestamps are synthetic. +Zero-length truncation accepts the accompanying `mtime` hint sent by Linux +v9fs; the hint is not stored. Standalone timestamp changes remain refused. `zig build fs-test` drives real sessions using the independent Python client in `test/ninep.py`. `zig build 9p-test` checks the freestanding wire protocol; diff --git a/docs/helix-keys.md b/docs/helix-keys.md index b999a25c..68eae0e1 100644 --- a/docs/helix-keys.md +++ b/docs/helix-keys.md @@ -100,7 +100,7 @@ language-backend queries, and the shell pipe. | `gh` / `gl` | line start / line end (last char, not the newline) | | helix-verified | | `Ctrl-d` / `Ctrl-u` | half page down/up, cursor follows | matches `page_cursor_half_down/up` | helix-verified | | `Ctrl-f` | full page down | matches `page_down`; file panes only get `Ctrl-b` (see next row) | helix-verified | -| `Ctrl-b` | file panes: full page up. Terminal panes: **raw tty mode toggle** (`opts.tty_toggle`, configurable; `Shift-Esc` is a second, fixed binding for the same toggle) | tty toggle is pardes-specific and wins on terminals; harness `pane:"tty"` cases avoid `Ctrl-b` | helix-verified (file) / pardes-specific (tty) | +| `Ctrl-b` | file panes: full page up. Terminal panes: **toggle raw tty/editor mode** (`opts.tty_toggle`, configurable; `Shift-Esc` also enters). In raw tty Shift-Esc goes to the child | tty toggle is pardes-specific and wins on terminals; harness `pane:"tty"` cases avoid `Ctrl-b` | helix-verified (file) / pardes-specific (tty) | | `PageUp` / `PageDown` | full page | matches helix `page_up`/`page_down` (view scroll + cursor snap to the scrolloff edge) | helix-verified | | `zt` / `zz` / `zb` | scroll current line to top / center / bottom | matches `align_view_top/center/bottom` (helix harness pins scrolloff to pardes' 3) | helix-verified | | `i` `a` | insert at selection start / after selection end | helix semantics: `i` before the selection, `a` selects and appends after it | helix-verified | @@ -113,15 +113,15 @@ language-backend queries, and the shell pipe. | `y` | yank selection; bare cursor yanks the 1-wide selection (char under cursor) | line-yank vim-ism removed (phase 5); yank keeps selection AND cursor (helix). Writes the DEFAULT REGISTER and nothing else — the system clipboard is `SPC y`, which is helix's own split and so moves this row TOWARDS helix, not away: a `d` of one character can no longer clobber what the desktop was holding | helix-verified | | `u` / `U` | undo / redo | restores the pre-edit selection (helix); snapshot granularity, no `Alt-u`/`Alt-U` history walking (skipped) | helix-verified | | `p` (normal) | paste the core's yank register after the selection | helix default-register semantics, and only the register — nothing on this path reads or writes the system clipboard. `SPC p` is the word that does, and on a tty its read is OSC 52, which most terminals refuse: an honest no-op there rather than a paste of the wrong text | helix-verified | -| `Esc` (body normal) | clear a pending modal prefix / exit select mode, keeping the selection, then run `Last`: hop to the pane you were in before this one, whichever kind it was, exactly like `SPC j j` — so held down it alternates between two panes, two files as readily as a file and its shell. A PDF pane is the ONE exception: there Esc is the document's own cancel (drop the mouse selection and the search overlay, stay where you are reading) and `Shift-Esc` is the hop out, the same chord that leaves a raw tty | Pardes-specific focus binding layered on helix's cleanup. A leader path, tag, topbar or search owns Esc while it is active; raw tty forwards it | pardes-specific (cleanup helix-verified) | +| `Esc` (body normal) | clear a pending modal prefix / exit select mode, keeping the selection, then run `Last`: hop to the pane you were in before this one, whichever kind it was, exactly like `SPC j j` — so held down it alternates between two panes, two files as readily as a file and its shell. A PDF pane is the ONE exception: there Esc is the document's own cancel (drop the mouse selection and the search overlay, stay where you are reading) and `Shift-Esc` is the hop out, while raw tty only intercepts unmodified Esc at a detected shell prompt | Pardes-specific focus binding layered on helix's cleanup. A leader path, tag, topbar or search owns Esc while it is active; raw tty forwards it | pardes-specific (cleanup helix-verified) | | `Esc` (insert) | back to normal mode, cursor right after the insertion (no vim left-step) | | helix-verified | | `Enter` (normal) | acme **look** chord: EXPLICIT selection, else file-ish word under cursor | pardes-specific, keep (helix normal-mode Enter unbound). Covers helix `gf`. Implicit motion residue falls back to the cursor word | pardes-specific | | `Tab` (normal) | acme **execute** chord | pardes-specific, keep; explicit-selection rule as Enter | pardes-specific | | `:` (normal, body) | focuses the pane's OWN tag as a one-line editor in **normal** mode, parked at the first EDITABLE column: motions (`w` `b` `e` `W` `B` `E`, `0` `$` `^`, arrows, `Home`/`End`) walk the whole rendered tag, `y` yanks the selection, `Enter`/`Tab` look/execute it (else the file-ish word under the cursor), `i`/`a`/`I`/`A` enter insert, `Esc` hands the body back. `h`/`j`/`k`/`l` are NOT motion here — a tagline is a place in the LAYOUT, so they run the same `Left`/`Down`/`Up`/`Right` builtins and land on the neighbouring pane's TAGLINE, still in normal mode (nothing that way = stay put, EXCEPT `k` off the topmost tagline — see the next row); the arrows keep the in-tag motion | helix `:` is command mode (section C); pardes' commands are acme words that live in the tag. `tag_col`/`tag_anchor` are columns of the RENDERED tag (prefix ++ tail) — one coordinate space, so the live mode+path prefix is selectable, yankable and executable, while every edit op (typing, `Backspace`, `i`/`a`/`I`/`A`) measures from the first editable column and is inert inside it | pardes-specific | | `k` (tag normal, topmost tagline) | focuses the column tag, then another `k` reaches the workspace tag at row 0 (`Newcol Joincol Find Grep Help Changelog Tutor Dump NextColor Debug Kill`, plus `Restore <path>` once a dump exists). With `ColumnTags` disabled it goes directly to the workspace. `j` walks back down. Header arrows and `h`/`l` move by grapheme; word motions and `0`/`$`/`^` work too. In normal mode `Enter`/`Tab` executes; `i`/`a`/`I`/`A` enter editing, where Enter is Look and Tab is Exec. Left-click, drag selection, typing, and paste edit either header; Esc leaves | Column commands target that column's active pane, or its first pane when coming from elsewhere. Workspace and column text are independently editable and persist in dumps. See [editable tags](tags.md) | pardes-specific | -| `Ctrl-w` + `h/j/k/l`/arrows | directional pane focus prefix — normal/tty modes only | pardes' own window handling (helix window mode skipped, section C). Runs the SAME `Left`/`Down`/`Up`/`Right` builtins `SPC w h/j/k/l` runs; kept alongside the leader because a pane in raw **tty** mode never sees `SPC` (the shell owns it), so this is the only keyboard way out of one. Insert mode owns `Ctrl-w` = delete-word-back, so a tag being TYPED into swallows it; from a tag in normal mode (`:`) it moves focus to the neighbour's BODY, while the bare letters `h/j/k/l` there move to its TAGLINE (next row) | pardes-specific | -| `Alt-n` | new terminal below (any mode) | shadows helix `Alt-n` TS sibling-select — skipped anyway (tree-sitter) | pardes-specific | -| `Alt-c` | move active terminal to a fresh column (any mode) | helix `Alt-c` is change-noyank; the pardes window op wins (do-not-touch contract). `Alt-d` + `i` covers the behavior | waived (`alt-c-window-op`) | +| `Ctrl-w` + `h/j/k/l`/arrows | directional pane focus prefix — editor normal mode only | pardes' own window handling (helix window mode skipped, section C). Runs the SAME `Left`/`Down`/`Up`/`Right` builtins `SPC w h/j/k/l` runs; Raw **tty** mode forwards Ctrl-w to the child. Insert mode owns `Ctrl-w` = delete-word-back, so a tag being TYPED into swallows it; from a tag in normal mode (`:`) it moves focus to the neighbour's BODY, while the bare letters `h/j/k/l` there move to its TAGLINE (next row) | pardes-specific | +| `Alt-n` | new terminal below (outside raw tty) | shadows helix `Alt-n` TS sibling-select — skipped anyway (tree-sitter) | pardes-specific | +| `Alt-c` | move active terminal to a fresh column (outside raw tty) | helix `Alt-c` is change-noyank; the pardes window op wins (do-not-touch contract). `Alt-d` + `i` covers the behavior | waived (`alt-c-window-op`) | | `Space` (normal, body) | starts the pardes LEADER: a key path runs the same builtin words used by tags and the topbar. The main groups are `f` files, `h` docs, `c` columns, `t` toggles/effects, `a` panel animations, `s` session, `j` jumps, `l` language, and `w` directional focus; `SPC ?` lists every path and `<prefix> ?` lists one group in `+Help`. The pending path appears on the active pane's transient body/message row. Esc or an unmapped key abandons it. Paths, Help rows, and dispatch are all generated from the builtin registry at comptime; the leader applies only to a BODY in normal mode because tags and tty programs own their input | pardes-specific; helix spends Space on pickers/LSP (section C), while pardes uses acme-style executable words | | `SPC y` `SPC Y` `SPC p` `SPC P` `SPC R` | helix's clipboard menu on helix's own letters: yank the selection to the system clipboard (`ClipYank`) or the PRIMARY selection alone (`ClipYankMain`), paste the system clipboard after (`ClipPaste`) / before (`ClipPasteBefore`) the selection, replace the selection with it (`ClipReplace`) | the ONLY five words in pardes that touch the desktop's clipboard — `y`/`d`/`c`/`p`/`P`/`R` and the acme cut/paste chords are the internal register alone. Builtins rather than bare chords because a leader path names a builtin: they land in Help's index and are executable words like every other verb. One divergence: pardes keeps a single register VALUE where helix keeps one per range, so `SPC y` at N cursors joins them with newlines (`Pardes.setYank`, the divergence `msel-yank-paste` already waives). On a tty the write is OSC 52 out and the READ is OSC 52 back, which many terminals refuse or gate — so `SPC y` works there and `SPC p` can be a no-op | out of corpus | | a paste from the OUTER terminal | one `Event.paste`, spliced in at the cursor | the tty shell enables bracketed paste and coalesces `paste_start`..`paste_end` into a single event; before that the bytes arrived as individual key presses and normal mode RAN them, which is how a pasted `d` deleted a line. The bytes deliberately never enter the yank register — clipboard and default register are separate stores in both directions | pardes-specific | @@ -134,7 +134,7 @@ language-backend queries, and the shell pipe. | `gd` `gD` `gy` `gi` `gr` | LSP definition / declaration / type-definition / implementation / references. ONE answer jumps straight there; several fill `+Search`, where n/N walk and Enter opens | in-process ZLS (`src/lsp/lsp_zls.zig`), `.zig` only — on a file the backend does not speak these do nothing at all, with no error row. Ctrl+left-click is the mouse spelling of `gd` | out of corpus | | `]d` `[d` / `]D` `[D` | step the diagnostics list / go to its last or first; if no list is up, asking the backend for one is part of the press | | out of corpus | | `=` | `format_selections` — writes a `- old` / `+ new` diff into `+Lsp` | deliberate divergence: the seam returns ROWS, not edits, so this SHOWS the formatting instead of applying it. Not in the corpus, so there is no waiver to name — the query leaves the core as an effect the headless harness has no shell to perform | out of corpus | -| `Ctrl-o` / `Ctrl-i` | jumplist back / forward — the `Back` / `Forward` builtins, also on `SPC j o` / `SPC j i`, with `SPC j l` rendering the stack as a buffer | helix binds both keys (`jump_backward` / `jump_forward`) but to a POSITION jumplist; pardes' stack is over panes and focus, so the keys agree and the semantics do not. `Ctrl-i` and Tab are the same byte under the legacy encoding; there Tab keeps meaning execute, and the pair only separates where the host speaks the kitty keyboard protocol | pardes-specific | +| `Ctrl-o` / `Ctrl-i` | jumplist back / forward; raw tty forwards both to the child — the `Back` / `Forward` builtins, also on `SPC j o` / `SPC j i`, with `SPC j l` rendering the stack as a buffer | helix binds both keys (`jump_backward` / `jump_forward`) but to a POSITION jumplist; pardes' stack is over panes and focus, so the keys agree and the semantics do not. `Ctrl-i` and Tab are the same byte under the legacy encoding; there Tab keeps meaning execute, and the pair only separates where the host speaks the kitty keyboard protocol | pardes-specific | | `\|` | pipe every selection through `/bin/sh -c`: its bytes in on stdin, its stdout replacing them, one undo across all cursors | helix's own key and meaning; the command is typed into the pane's tag after a bare `\|` marker rather than into a popup | out of corpus | | `A-\|` | the same, and the output is DISCARDED — the text is not touched at all | helix `shell_pipe_to`. For a command run for its effect. Marker `\|-` | out of corpus | | `!` | run with NO stdin, insert the output BEFORE each selection | helix `shell_insert_output`. Runs ONCE and every cursor gets that one answer, as helix does — ten cursors and `date` give ten identical stamps. Marker `!` | out of corpus | @@ -425,3 +425,7 @@ completion off, scrolloff pinned to pardes' 3, no-language indent style pinned to Spaces(4), smart-tab off, and the buffer-setup transaction committed as its own undo revision. Build: `cargo build --release -p helix-term --features helix-term/integration --bin hx-harness`. + +Raw TTY input keeps Ctrl-b for `Togglettymode` and unmodified Escape at a detected shell +prompt for `Last`. Other Ctrl/Alt chords, modified Escape, and clipboard shortcut +keys go to the child. Use the `Togglettymode` tag to leave raw input in place. diff --git a/docs/tags.md b/docs/tags.md index 87cee2ff..c159e9cf 100644 --- a/docs/tags.md +++ b/docs/tags.md @@ -60,7 +60,8 @@ available, as do the shortcuts for `PdfTint` (`SPC t i`) and `PdfSections` (`SPC t s`, or `f` on a PDF). Terminal tags include `Togglettymode`, which toggles between normal editor mode and raw -terminal input using the same transition as Ctrl-B. It also works while editing +terminal input. Ctrl-B enters terminal input from editor mode; raw TTY mode +forwards it to the child. The tag command also works while editing the tag: the command leaves tag editing and toggles the parked body mode. Executing it on a non-terminal pane does nothing. diff --git a/docs/v9fs.md b/docs/v9fs.md new file mode 100644 index 00000000..51b35a46 --- /dev/null +++ b/docs/v9fs.md @@ -0,0 +1,107 @@ +# Linux terminals with a kernel 9P mount + +Execute `Tty9p`, or press `SPC n 9` in editor mode, to open a terminal below +this pane with the current Pardes session mounted through Linux v9fs. + +The new pane asks for your sudo password when needed. After mounting, it starts +your configured shell as your normal user, with your account's supplementary +groups. The shell receives `PARDES_MOUNT`, the absolute mountpoint: + +```sh +ls "$PARDES_MOUNT/self/pane" +cat "$PARDES_MOUNT/self/index" +cat "$PARDES_MOUNT/self/README" +ls -l "$PARDES_MOUNT/self/new" +cat "$PARDES_MOUNT/self/pane/$PARDES_PANE/body" +``` + +The mount belongs to that pane's subprocess tree. Other panes and the editor +core keep their original mount namespace. It works in native Linux TTY and SDL +sessions, including detached sessions. A frontend attaching from elsewhere does +not perform the mount; the session host starts the new terminal. + +## Build and setup + +The normal Linux build installs the ordinary `pardes-v9fs` executable beside +`pardes` and `pardes-gui`: + +```sh +zig build +``` + +Start an updated editor to get the builtin. An already running core retains +its old code. The host resolves the helper beside its own executable; +`PARDES_V9FS_HELPER=/absolute/path/to/pardes-v9fs` overrides this for development +builds whose editor and helper live in different build-cache directories. + +Linux must support `9p` and its Unix transport (`9pnet_fd`). v9fs mounting needs +`CAP_SYS_ADMIN` in the initial user namespace, which sudo supplies. The launcher +uses `sudo -E` to retain the shell environment; local sudo policy must allow +that. It restores the caller's PATH after dropping privileges because sudo's +`secure_path` can replace it even with `-E`. + +No setuid installation, passwordless sudo policy, system group, or FUSE is +installed. The helper currently accepts explicit mount paths and a command; +it is not a restricted privilege broker. Do not grant it blanket passwordless +access. A group-authorized helper would require a separate restricted design. + +## Runtime organization + +`src/linux/v9fs.zig` builds `pardes-v9fs` and provides helper discovery to the +native host. `Tty9p` marks the new pane for a mounted shell; `host_io.forkShell` +starts the normal interactive shell and queues a quoted helper command. For +bash and fish, the command waits for the shell's prompt-ready mark. The helper +runs as a foreground shell job, so sudo uses the terminal like a manually run +command. Authentication failure or cancellation returns to the original shell; +exiting the mounted shell also returns there. + +The unprivileged launcher creates a private temporary mountpoint and invokes +sudo inside the new PTY. The elevated helper creates a private mount namespace, +makes propagation recursively private, and mounts the session's Unix socket +with `version=9p2000,cache=none,access=any,nosuid,nodev,noexec`. It restores the +calling user's account groups, drops all real/effective/saved root IDs and +mount capabilities, and executes the shell. Ordinary commands such as sudo +remain available for subsequent explicit authentication. The launcher waits for sudo, +forwards termination signals, and removes its empty temporary directory on exit. +Namespace destruction releases the mount when its last process exits. + +The core stays outside the mount namespace because its event loop serves 9P. +A blocking filesystem operation through its own mount could wait for a request +that the blocked event loop must service. Even pathname resolution may do this. + +Each `Tty9p` currently creates its own mount and consumes a server connection; +the session has four application connection slots across all transports. This +is the explicit per-pane version. A shared launcher for all pane shells remains +future work. Detached sessions retain the running mounted pane when frontends +leave; Dump/Restore does not reconstruct mounted-shell namespaces. Descendants +that deliberately outlive the terminal may retain their namespace until exit. + +## Tests + +```sh +zig build v9fs-terminal-test -Dplatform=tty +zig build v9fs-driver-test -Dplatform=tty +zig build 9p-test -Dplatform=tty +sudo -v +zig build v9fs-test -Dplatform=tty +``` + +`v9fs-terminal-test` exercises the builtin, real launcher, PTY input, session +environment, quoted helper paths, hidden password input, and core responsiveness +using an unprivileged sudo stand-in. It checks that authentication failure and +interruption clean up the temporary mountpoint and leave the original shell usable. It does not claim kernel-mount coverage. + +`v9fs-test` mounts through the same runtime helper. Its driver keeps the editor +unprivileged and uses `sudo -n`, retaining the calling terminal's authorization. +Missing authorization or kernel support fails the test instead of skipping it. +It checks mount isolation, privilege dropping, inherited access, directory +refresh, body reads and truncation, independent wire updates, addressed edits, +shell redirection to ctl, Exec dispatch, rendered screen JSON, and OS-file reads. + +Linux follows `O_TRUNC` with a `Twstat` carrying zero length and an `mtime` hint. +Pardes accepts this truncation without storing caller-selected timestamps; +standalone timestamp, permission, and ownership changes remain unsupported. + +The initial kernel probe passed on this host on 2026-09-14. The broader +`fs-test` has an existing syntax-bold assertion failure at `test/fs.py:459`, +also reproduced on the cached editor binary preceding the truncation fix. |
