summaryrefslogtreecommitdiff
path: root/docs
diff options
context:
space:
mode:
Diffstat (limited to 'docs')
-rw-r--r--docs/config.md11
-rw-r--r--docs/fs.md10
-rw-r--r--docs/helix-keys.md16
-rw-r--r--docs/tags.md3
-rw-r--r--docs/v9fs.md107
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
diff --git a/docs/fs.md b/docs/fs.md
index 8805ffde..942a26a3 100644
--- a/docs/fs.md
+++ b/docs/fs.md
@@ -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.