summaryrefslogtreecommitdiff
path: root/src/host_io.zig
Commit message (Collapse)AuthorAge
* Edit's w to a file changed on disk since read is refused once, as Save is (w ↵Gabriel Schneider4 hours
| | | | | | again to overwrite), the rest of an X/'/w written and the refused named; Edit filters get a pane command's environment ($PARDES_MOUNT, $PARDES_9P, $PARDES_PID, $winid); an Edit with no commands runs beside one whose commands run unless it touches a file that one changes, refused busy (EBUSY) as is a second Edit with commands Co-Authored-By: Claude Opus 5.5 <[email protected]>
* A look at a directory opens a pane listing it, as acme's directory window: ↵Gabriel Schneider9 hours
| | | | | | named dir/, its entries in acme's columns (bytewise, dotfiles kept, a directory's marked /), a look at an entry from there, Get or a look again reads it again, never dirty, Save refused; DirLook terminal keeps ls in a terminal Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Saving the init file in a pane (Config) applies its settings at once; its ↵Gabriel Schneider17 hours
| | | | | | | | | | | | commands are not run a second time A setting changed in the init file took effect only at the next start. The host's Save now hands a written init file's text back to the core, which applies its setting lines again (each idempotent), with the same notice and err for one that fails; a command line in it (`Newcol`) belongs to the start and is left alone. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* A Rename the server answers for this file alone, while other open files of ↵Gabriel Schneider18 hours
| | | | | | | | | | | | | | | | | its language say the name, is previewed, never applied; Lspwhy shows the files synced first zls renaming at a declaration answers with that file's edits only, cold or warm, whatever it was told of (checked against zls directly: from a call site in main.zig it renames both files, from the declaration in util.zig it never does), so the first Rename applied in util.zig and left main.zig broken. The client now looks for the old name in the other open files of the language; when one says it and the answer left it alone, the edits are a preview with a row naming each such file, and the message row says the server renamed in this file only. Lspwhy carries the open files too, so its trace shows `synced X first` for the query it explains. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* A Rename, references and the like tell the server of every open file of its ↵Gabriel Schneider20 hours
| | | | | | | | | | | | | | | | language first, so a rename reaches files it had not seen and the preview lists them zls renames only in the documents in its store, and syncDoc told it of a file only when a query was asked from it, so renaming in progress.zig left main.zig unchanged and broken, with no preview. For a query whose answer can reach other files (rename, references, implementation, workspace symbols, calls and type hierarchy), the host snapshots every other open file and the client syncs those its server answers for first. The mock server answers a rename in each document it was told of; lsp-rename-files opens main.rs by a look, renames from lib.rs, and its preview lists both. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* With Pager pardes, a terminal's shell also gets SYSTEMD_PAGER and ↵Gabriel Schneider26 hours
| | | | | | | | | | | | | | SYSTEMD_PAGERSECURE=0, and the pager's path is quoted only when it needs it journalctl and systemctl read SYSTEMD_PAGER, not PAGER, and in their secure mode run only less, so a pty/run of either still waited in it; and systemd, splitting a pager on blanks and exec'ing it, never ran the quoted path. Where the user's environment sets neither, a terminal now gets SYSTEMD_PAGER as PAGER is set and secure mode off (this pager has no shell to escape to), and the path is single-quoted only when it holds a blank or shell character. Command panes keep cat. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* A terminal pane draws the kitty graphics its program places, so yazi's image ↵Gabriel Schneider26 hours
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | and PDF previews show yazi asks the pane XTVERSION, and ghostty-vt answers "libghostty": yazi takes it for Ghostty and sends its previews by the kitty graphics protocol with Unicode placeholders (a=T,U=1, then U+10EEEE cells). ghostty-vt stored the images, but nothing drew them: the placeholders showed as tofu in the GUI and as raw codepoints in the tty, in every build since terminal panes answered queries (vkznsvzs, 2026-07-01). Before that no query was answered and yazi fell back to chafa's text art. Each frame, TerminalImages.zig reads the active screen's images and placements (virtual ones through their placeholder runs, and ones placed on a cell), cuts each into the cell rows it covers, maps those rows through the body's own row walk, clips them to the pane, and hands them on as ImagePlaces with exact geometry (NativePlacement.exact). The GUI draws them as textures; the tty passes them to an outer terminal with kitty graphics as one placement per row, its source cropped to whole cells. Placeholder cells draw no text. Scrolling, a delete, a resize, the alternate screen and a closed pane all fall out of reading the emulator's state every frame. A pane now knows its size in pixels: CSI 14/16/18 t are answered, the emulator's width_px/height_px follow the shell's cells, and the pty's winsize carries pixels (host_io.ptyWinsize), so yazi and a nested pardes size their images right. ghostty-vt's image store is 64 MiB per screen (its library default, 10 MB, refused a large preview), PNG transmissions are decoded with stb_image, and RGB images are converted only while shown. test/yazi_preview.py runs yazi in a hidden window on a PNG and a PDF and checks the captures, also through a tty pardes nested in the pane. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* pardes - reads stdin into a +Pager pane, and is a terminal's PAGER and ↵Gabriel Schneider27 hours
| | | | | | | | | | | | | | | | | GIT_PAGER, Pager pardes|off to opt out; command panes page through cat A paging command in a terminal, or run through its pty/run, sat in less, and a pty/run never answered. `pardes -` now reads stdin to its end, strips terminal escapes (colour, OSC, overstrike), and shows it: inside a session as a clean <cwd>/+Pager pane made through pane/new and back at once, outside one as a new editor whose first pane it is, as `vim -` does. A terminal's shell gets PAGER and GIT_PAGER set to this pardes's own path and `-` where the user's environment sets neither; `Pager off`, a setting in /ctl, init and DumpConfig, leaves terminals started after it to the environment. A command pane's one-shot shell keeps cat, its output being a pane already. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* A command run from a pane gets acme's $winid, the serial of the pane it was ↵Gabriel Schneider27 hours
| | | | | | | | | | | | run from; one from a column's or the workspace's tag gets none PARDES_PANE names the command pane itself, so a script a click ran had no way to address the pane it was run in, as acme's win scripts do with $winid. The command pane now remembers that pane and its child gets winid set to it; a header tag's command, which no pane owns, gets winid unset. PARDES_PANE is unchanged. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* A pane's child runs with PAGER=cat and GIT_PAGER=cat where the user's ↵Gabriel Schneider27 hours
| | | | | | | | | | | | environment sets neither, so a pty/run or a command pane never hangs in a pager git log or man run through pty/run, or in a command pane, sat in less at its first screen: no one presses q, and the run never answers. The child's environment now names cat as the pager when the inherited one names none; a user's own PAGER or GIT_PAGER is kept. It is every pane child's, a terminal's shell too, since a pty/run's line runs in it. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* An editor's private pardes-osc133 prompt files go at Exit and at a fatal ↵Gabriel Schneider27 hours
| | | | | | | | | | | | | | | signal, and a start sweeps the ones a dead editor left /tmp had gathered thousands of private prompt files: Exit removed its own, but an editor killed by a signal, or a crashed one, left them for ever. They now carry their editor's pid in the name, a SIGTERM or SIGHUP removes them before the signal's death, and each start removes those whose pid kill(pid, 0) answers ESRCH for. Files of the older shape carry no pid and are left alone, as a running older editor may still read them; the shared ones in the runtime directory are kept, being every session's. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* A background job killed while the tty probe walks it is not the tty's owner, ↵Gabriel Schneider27 hours
| | | | | | | | | | | | | | | | | however far into exiting it is host_io's "a background job is not the tty's owner" failed now and then, 2 runs in 20 under load, always just after `kill %1`. The probe walks the shell's children and reads each one's exe. A killed background sleep, in the moment it is exiting (its exe link gone, not yet a zombie), has no exe to read and is not a zombie to skip, so the probe called the tty taken. A process outside the foreground process group holds nothing whatever state it is in, so one whose exe cannot be read is now skipped when its group is not the foreground one. Under the same load the test passed 50 runs in a row. The unit-test step's perf gate trips under that load, which is not this test. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* A detached session no client watches draws no frames: yes | head -c 50M ↵Gabriel Schneider27 hours
| | | | | | | | | | | | | | | | | | | | through pty/run goes from over 30 s to 11 s Profiling the 50 MB run showed 70% of its time in draw.render, recolorAnsi alone 43%, walking cells through PageList.pin. The detached session drew a frame after every pty read, with no client attached to see it. A host now says whether anyone watches (Host.VTable.watched). The detached server answers whether a client is attached, and while none is the core keeps the frame owed instead of drawing it. A /screen read draws its own, and a client attaching gets one (hello sets needs_frame). Debug, 80x24: 10 MB went from 7.36 s to 2.10 s, and 50 MB from over the 30 s client timeout (about 37 s by rate) to 11.08 s. What is left is the terminal's own parsing and scrollback growth. A session a frontend shows still draws after every read. Pacing those frames is the next step if that case matters. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* The hosts' Save, Dump and DumpThemes writes and their shell reaper are ↵Gabriel Schneider27 hours
| | | | | | | | host_io's, once, where the tty, the gui and the detached session each had a copy writeFile, writeDump and dumpThemes were byte-for-byte the same in tty.zig, gui.zig and detached/server.zig but for how each reaches its core, and the gui's and the session's reapers the same but for a pid <= 0 against == 0 (a pid is never negative there). Each vtable entry is now a line calling host_io's. ttyTaken, fgName, killJob and ptyResize stay each shell's: they are already one call to host_io each, and what differs is how a shell reaches its pty (an optional with a file, an optional with an fd, a bounds-checked array). macos.zig keeps its copies (no macOS build here to check it). No behaviour changes. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Merge: Terminal rename + Kill signal orderGabriel Schneider27 hours
|\
| * Kill stops a command's shell before its jobs, so the line never runs on to ↵Gabriel Schneider27 hours
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | its next command in between signalTty signalled the running job's group first, then scanned all of /proc for the session's & jobs, then the shell. A job dead of the signal lets a job-control shell run the line's next command, and once pids wrap below the host's older processes the scan takes long enough for it to: "sleep 0.4; touch killed" killed at 150ms touched the file. The host_io test caught it every run after the pids wrapped (pid_max 4194304, new pids near 17000, 229 older processes above them to scan). The shell's group now goes first, then the session's groups, then the foreground one. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* | Terminal.zig is terminal.zig: a file of functions and no fields takes a ↵Gabriel Schneider27 hours
|/ | | | | | | | namespace's lowercase name The last of the deferred renames, now that the tty and theme agents have landed: panes.terminal at its importers, the alias lines in Text.zig and File.zig and panes.zig's own uses following. dump.zig's Terminal struct, a dump record, is not this. The served sources list and docs/design.typ name the new file. test/perf.zig's references change, so the three perf baselines take its new harness id with their numbers as recorded. No behaviour changes. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Not-found failures say it once, in plain words: grep: pattern not found, ↵Gabriel Schneider27 hours
| | | | | | | | callers not found The ENOENT that 9ns reads from a failure's words came from a tacked-on suffix, grep: no match, not found, definition: nothing found, not found, no shell "x", not found. They now say it once: grep: pattern not found, find: name not found, Callers: callers not found, definition: not found, shell "x" not found (...). Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Every child starts with its signals at their defaults and none blockedGabriel Schneider27 hours
| | | | | | Shells, command panes, language servers, the link opener and the v9fs mount inherited the editor's signal mask (the tty's blocked SIGWINCH) and anything it ignored (a SIGHUP nohup ignored), since only handlers reset at exec. Each fork now resets every disposition and clears the mask before its exec (resetChildSignals); std's spawn for selection pipes runs with the mask cleared across it. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* A path with a part over 255 bytes is refused, never a panicGabriel Schneider27 hours
| | | | | | std's statFile takes the kernel's ENAMETOOLONG for a bug (errnoBug), so a name, look, DumpDir or Save path with a part over 255 bytes panicked the editor (exec.kindOf via writeName, recentKeeps, dumpFailed). Every non-test statFile now goes through fs.statPath, which refuses such a name as NameTooLong first; fs.py drives long, looping, not-a-directory and not-ours paths through name, look, DumpDir, Dump and Save. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* A bash or fish under another name gets its prompt marksGabriel Schneider27 hours
| | | | | | The shell's kind was read from its file name alone, so a renamed or differently linked bash got no marks and read busy for ever. A name that says nothing now resolves its links, then looks in the program for bash's or fish's own strings; a POSIX shell's name (sh, even when it is bash) is taken at its word. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Tty or Shell naming a file that is there but not executable says soGabriel Schneider27 hours
| | | | | | It said no shell "x", not found, as for a name that is nowhere. A file that exists without its execute bit now says not a shell: x is not executable. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* A script whose interpreter is not there is told apart from a missing shell, ↵Gabriel Schneider27 hours
| | | | | | | | and Tty refuses it up front Tty's up-front check found the script and let it through, so the pane was made, its exec failed ENOENT, and the log read new, msg shell: shell not found, del, then the err. The check now reads a script's #! line and refuses it, interpreter /no/such/interp not found, and an exec that fails ENOENT on a file that is there says the same. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Errnos that fit: a Dump into a missing DumpDir and a shell that is not there ↵Gabriel Schneider27 hours
| | | | | | | | | | | | are ENOENT, a GUI-only setting EINVAL A Dump into a DumpDir not there failed EIO `file not found`; `no shell "x"` read as EIO through a mount; and a GUI-only setting on a terminal, EINVAL in the reply, had no word 9ns reads as EINVAL. Each says so in the words that give its errno: `no such directory`, `not found`, `invalid here`. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* A shell that cannot start fails Tty and pty/ctl exec with why, before either ↵Gabriel Schneider27 hours
| | | | | | | | | | | | | | answers A shell whose exec failed (a script's missing interpreter) was reported started: Tty and pty/ctl exec succeeded, then the pane died. The child now reports a failed chdir or exec through a close-on-exec pipe the parent reads before acknowledging the shell; the host's spawn fails with the reason (`shell not found`, ENOENT), which fails the waiting write. A Tty whose first shell never ran leaves no pane; a terminal restarted by pty/ctl exec keeps its pane. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* pty/ctl exec fails the write when the shell cannot start; a directory ↵Gabriel Schneider27 hours
| | | | | | | | | | | | | removed under a shell leaves its name `exec` on pty/ctl restarted a shell in a directory that was gone, the write succeeding and the pane dead; and the pane was then renamed `<dir> (deleted)` from /proc, so a restart failed even once the directory was back. `exec` is now refused up front, ENOENT, where the directory is gone; a shell the host cannot start fails the waiting write (late failure, as a Save's); and a `(deleted)` cwd is no name to take. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* pty/run's busy names the program holding the terminalGabriel Schneider27 hours
| | | | | | | | | busy said only that something ran. Where the host can tell (Linux, the tty's foreground group's /proc comm, a new fg_name host call), it says busy: <program> is running; elsewhere, busy as before. The GUI's host gains the one small callback (render agent's file: gui.zig, fgName). Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Tty and Shell refuse a directory: not a shellGabriel Schneider27 hours
| | | | | | | | | | A directory passes access(X_OK), so Tty /etc made a pane whose shell exited 127 and Shell /etc was taken. The shell lookup now refuses a directory, and both say "not a shell: /etc is a directory" (Tty and Shell share one refusal, host_io.Shell.refusal). shellset's golden takes the shared wording (re-recorded by name). Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Kill stops a command pane's whole command, its & jobs includedGabriel Schneider27 hours
| | | | | | | | | | With job control on, a command's & jobs get process groups of their own, so Kill's signal to the foreground group and the shell's group left them running, orphaned. A stop of a command pane now signals every process group in its shell's session (Linux; a /proc scan). Jobs still outlive a command that exits on its own. Docs and the skill say it per route. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Kill stops a command pane's whole line again, now it runs with job controlGabriel Schneider27 hours
| | | | | | | | | | | | With the line run under job control (the change before), the job running has a group of its own, and Kill's SIGTERM to the tty's foreground group stopped only that job: of `sleep 30; touch x` the touch still ran. For a command pane the hosts now signal the shell's group as well, so the whole line stops, as it did; a line typed at a prompt still loses only its foreground job. The test forks a real pty for both halves: a background job outliving its command and the pty's hangup, and a killed line not running on. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* A command pane's background job outlives its commandGabriel Schneider27 hours
| | | | | | | | | | | | | | A dogfood agent's background jobs died when the command that started them finished, even under nohup. The command's shell leads its terminal's session, and on its exit the kernel hangs up the terminal's foreground group, which with job control off is the shell's and every job's. The line now runs with job control on (bash, sh, dash, zsh, ksh -m; fish status job-control full), so a job has a group of its own and lives on, printing below exit N, and survives the pane closing. While a job holds the pty the pane is not reused, so the directory's next command gets a second pane (cmdexit's golden, re-recorded by name). Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Shell prompt files are one per content under $XDG_RUNTIME_DIR, so a killed ↵Gabriel Schneider27 hours
| | | | | | | | pardes leaves none behind; tests remove their /tmp dirs Every pardes host wrote its own pardes-osc133-bash-* and -fish-* files to /tmp and removed them only at a clean teardown, so each killed session, test and crash left two: 72K of them had piled up. They are now written once per content, named by its hash, in the user's private runtime directory, renamed into place whole and shared by every pardes; without that directory the old private /tmp files remain. fish's -C source is quoted. The 9p_io tests remove their runtime dirs with what the listener left in them, and the snapshot runner removes its retry captures when every retry passed. Co-Authored-By: Claude Opus 5.5 <[email protected]>
*-. Trial merge: 9P + helix + renderGabriel Schneider27 hours
|\ \
| | * Step core animation by the shell's clock and sleep to the next wakeGabriel Schneider27 hours
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | A shell now answers Host.now, its monotonic clock in ns, and the pump advances core animation to it: one .tick per whole 16 ms frame since core time last stood still. The same animation therefore takes the same time at 60, 120 and 144 Hz, over ssh and after a slow frame (the GUI's re-armed clock ran them ~25% slow at 144 Hz; the tty's post-frame sleep drifted). nextWake() lists every core animation in one place: a frame from now while anything moves, the end of the wait while something only waits (a message lingering, the look-hover delay), null when idle. Shells sleep exactly that long, and a wait is jumped to its end in one step, so an 800 ms linger costs one frame instead of fifty. An overshoot under 1.5 ms after a step is let go: at 60 and 120 Hz every display frame takes exactly one step (a 16 ms frame against a 16.67 ms vsync would otherwise double-step every 24th). The six tick drivers are gone: tty's timer thread only times the wait, interruptibly (a newer, shorter request cuts short a sleep still timing a longer one); the GUI's AnimationClock, web's JS tick bank (pardes_tick now takes rAF's timestamp), the detached server's and the board's ticks; grid mode steps a virtual clock straight to each wake. PARDES_TEST_CLOCK, set by the snapshot harness, gives tty and the detached server the same virtual clock. Every stepped frame is drawn. The old pump never drew the last frame of a fade (a .tick asks for no frame, and the fade was over by the check), so a theme switch stopped at 9/10 of the way until the next input: theme.golden, themesel.golden and the GUI's acme-light scene move to the theme's true colours, and nothing else changes. The frozen previous grid is captured only while a panel transition is chosen, not on every idle frame.
* | | A command's exit is told once its output is in, by the pty's state, not a timerGabriel Schneider27 hours
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | The watcher woke the host a second time 60 ms after the exit by its own clock while the grace was counted from the reap: a host busy for 10 ms missed it, and the tag said running for ever and Kill did nothing; and under load exit 0 could land before the last output. As decided, no timer: the exit is told once it is reaped and the pty says nothing is left (poll: no POLLIN, and no POLLHUP, which means the end of file is on its way behind the output), else at that end of file, checked after each chunk of output. The tty host checks inside its step so the frame shows it. The four hosts' copies are one host_io.takeExits/commandEof, which close a told command's pty at its end of file (the fd and the GUI's reader leaked when the exit came first). A finished command pane whose pty a job it left still holds is not reused, so that job is not hung up; and the reset before a reuse is SGR 0, not DECSTR, which ghostty's stream does not implement. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* | | Tty with a shell that is not there says so, and never sets the caller's shellGabriel Schneider27 hours
|/ / | | | | | | | | | | | | | | | | | | | | | | | | Tty /nonexistent or Tty fsh started the host's fallback shell without a word, and when spawnTty made no pane, the argument became the calling pane's shell. Tty now looks the shell up first and fails 'Tty: no shell "fsh"' when it is neither a name on the usual paths nor a path to one; spawnTty answers the pane it made, the one given the shell, and says why when it made none. The lookup (host_io Shell.find) gives the turn up around its access() calls, since a typed path may be under a mount this editor serves, and forkShell copies the pane's shell before looking it up and checks the pane is still its own after. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* | A command pane's command is over when its process exits, not when its pty closesGabriel Schneider27 hours
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | A job left in the background (sleep 100 &) held the pty open, so the pane stayed running and the child a zombie until the job ended; a command that closed its terminal and ran on got its end of file at once, the host waited 100 ms for an exit, reported exit ? and hung it up. Now each command's child is watched on a thread (waitid with WNOWAIT, so its pid stays its own until the host reaps it), and the host tells the core the exit from that: after the pty's end of file, so the output before the exit is in, or 50 ms after the exit without one, a job holding the pty. The pty stays open until both, so a command that let go of its terminal is never hung up by it. All four front ends; a host that cannot start the watcher reads the exit at end of file as before. Tests: host_io's for both cases, and cmdexit.snap end to end. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* | A spawn names the pane it was made for, so a pane closed and its slot ↵Gabriel Schneider27 hours
| | | | | | | | | | | | | | | | | | | | | | | | | | | | retaken spawns nothing The spawn effect carried only a slot, and the host read the command and shell off whatever pane held the slot as it forked: a command pane closed and its slot taken by the next one before the effects ran forked the new pane's command twice. The effect now carries the pane's serial and is dropped when the slot holds another; forkShell checks it again after giving up the turn for its directory's stat, the one moment a 9P client can change the panes under it. Every front end performs spawns with the turn, from its step. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* | A terminal's Tty word names its shell, Tty+fish, and a click on it opens anotherGabriel Schneider27 hours
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | A terminal's tag said Tty whatever shell it ran. The host's acknowledgement of the shell it forked now sets the word to Tty+<shell>, tinted like the tag's name. Word+arg is taken as Word arg, one word a tag can hold and a click can take whole (+ is a word character), so Tty+fish opens a terminal on fish, as Tty fish does; Tty takes the shell as its argument, which the host forks for that pane instead of the configured one. Every snapshot whose screen shows a terminal's tag changes: 69 goldens, re-recorded by name, whose 297 changed lines are all Tty becoming Tty+bash (the harness runs bash), the cursor columns and style spans that moved with it, or that text cut at a pane's width. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* | A command line runs as its own command pane unless it is clicked at a ↵Gabriel Schneider27 hours
|/ | | | | | | | | | | | | | | | | | | | | | | shell's prompt A middle click, an exec write or a tag word that no builtin knows was typed into some terminal for the pane's directory, sharing whatever state that shell was in and answering nothing, so a misspelling vanished into a shell. Now only a line clicked at an interactive terminal's prompt is typed there. From anywhere else it runs as a command pane: a terminal whose child is $SHELL -c the line in the pane's directory, full emulation, which shows its output and then exit N from the host's reaping of the child, and stays. A finished command pane is the next command's for its directory, which runs below what it showed after a '% line' line (acme appends to +Errors and never clears it, util.c:213); a running one gets a second pane. Kill ends a command pane's whole process group, the log records run and exit, exec reads back the command pane's serial, and a line is at most 1 KB, read off the pane as the host forks rather than carried in every spawn effect. ttyForDir's search for a free shell is gone. The goldens of chordcut, cmdword and layout-open change where a file's exec now opens a command pane, and ttytaken is rewritten to exec from the terminal itself; docs/open-questions.md records the decision. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Kill asks the host to signal at once and says when a shell has no job to signalGabriel Schneider27 hours
| | | | | | | | | | | Kill queued a signal effect for later, so the job it saw running by its marks could have ended, and another started, before the signal went; and with job control off the job shares the shell's process group, so the host skipped it and Kill reported nothing. Kill now calls the host's kill_job while it holds the turn, and when the only job is the shell's own group says 'Kill: no job to signal', which also fails a write of Kill to ctl. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* A closed terminal's shell is reaped on tty and macOS, killed if it ignores ↵Gabriel Schneider27 hours
| | | | | | | | | | | | | | | the hangup tty and macOS hung the pty up and called waitpid once without waiting, so a shell still exiting, or one that ignores SIGHUP, stayed a zombie or ran on with nobody reading it; tty also never reaped a shell that exited by itself, and its spawn into an occupied slot closed the old pty without ending the shell. host_io's retireShell says hangup, waits 100 ms on a thread (macOS has no host timer to poll from), then kills and reaps. The gui's own retired list, when full, left the slot holding the old shell and refused the next spawn into that pane; it now hands that shell to retireShell instead. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Exit quits and Kill stops commands, as in acmeGabriel Schneider27 hours
| | | | | | | | | | | | | | | | Kill quit the editor, which in acme is Exit; acme's Kill stops the commands it started. Exit now quits as acme's does (exec.c, rowclean): it refuses once, naming each pane with unsaved text, and quits when asked again with nothing edited since (a small scratch is not asked about). Kill, bare or with names, stops the commands pardes typed into a terminal (an exec, a middle click, a pty/run) while their shell's marks say they run, by SIGTERM to the terminal's foreground job, never to the shell (acme posts the kill note, which terminates). Both are session builtins; the topbar's Kill becomes Exit, same width, and every golden's topbar row changed by exactly that word (checked line by line); the builtins script scrolls one more row for the index's new line. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* A terminal pane that closes takes its shell and pty with it, in every front endGabriel Schneider27 hours
| | | | | | | | | | | | | The detached server closed a pane's pty only when the shell was respawned, hit EOF or the session shut down, so rmdir, Del or Delcol on a terminal left its shell running with nobody to read it; the tty front end and macOS did the same. Retiring a terminal pane now emits a close_pty effect, which each host that runs shells answers by hanging the pty up and ending the shell (the detached server's and the GUI's existing retire-and-reap path, and a close plus SIGHUP in the tty and macOS shells). Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Give a pane's body its own Text holding the cursor, selections, mode and undoGabriel Schneider27 hours
| | | | | | | | | | | acme keeps what edits a text in its Text (dat.h:171-190) and the window holds a body and a tag of that type. The cursor, the selections, the modal state and the edit-buffer undo move off Pane into Text.zig, Pane holds them as its body, and the edit and normal-mode operations take the Text they edit. Nothing changes in behaviour; this is the step that lets the tag become a second Text. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Point docs and comments at the files code moved toGabriel Schneider27 hours
| | | | | | | | | | | | | Documentation only, no code change: README's reading order and layout, docs/design.typ's paragraph on where pane kinds and editor parts live, docs/helix-keys.md's code map (normalInput and the executors now in normal.zig and edit.zig, insertTab in edit.zig), docs/open-questions.md (execute and ttyForDir in exec.zig), and the comments that named pardes.zig for fold, Cell, takesCommandLine and runBuiltin (tools/gen_themes.zig, themes/helix.zig, detached/wire.zig, host_io.zig, lsp_zls.zig). docs/design.pdf is a retained fixture and is not regenerated. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Make pty/run and /log streams, and take refused lines back cleanlyGabriel Schneider27 hours
| | | | | | | | | | | | | | | | | | | | | | | | | Review fixes to pty/run and /log: - Both are streams with a per-open cursor. A shell's exec 3<>file shares one offset between write and read, so cat <&3 after echo make >&3 asked for offset 5 and got "0" instead of "exit 0"; log after follow lost its first bytes the same way. - A run is accepted only in pardes's own tagged input phase, so a nested shell's prompt (ssh, a shell with its own integration) is never taken for this shell's. - A line the shell refused is taken back so the next finds the prompt clear: bash's continuation prompt (now tagged k=c) gets Ctrl-C and the answer waits for the fresh prompt; a bash syntax error (no C, but a D) is answered at once; fish's kept line gets Ctrl-U (a Ctrl-C sent while fish redraws is lost). fish's right prompt no longer reads as typed input. - bash marks a command's start from PS0 on bash 4.4+, not a DEBUG trap, so a user's own DEBUG trap (bash-preexec, atuin) survives. bash's own job notices now show as in plain bash (ttyfollow golden). - 32 run slots; a new line on an open starts a fresh answer. Checked live in bash, fish, and fish with a right prompt. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Run a line at a terminal's prompt through pty/run and read how it endedGabriel Schneider27 hours
| | | | | | | | | | | | | | | | | | | | | | | /pane/<n>/pty/run takes factotum's rpc shape: write one line on an open, read the answer on that same open. The answer is exit N once the command ended and the shell is back at a prompt; busy at once when a command runs, text is typed at the prompt, or the shell has not drawn its first prompt; error not run when the shell refused the line without running it (fish on a syntax error keeps it on the prompt, so it is taken back with Ctrl-U); error shell gone when the pane closes or its shell is replaced; error no prompt marks for a shell pardes could not instrument. The end comes from the shell's OSC 133 marks. ghostty parses D's exit status and drops it, so the stream now runs through a handler that wraps ghostty's and follows prompt -> input -> running -> done. The marks pardes injects into bash and fish carry aid=pardes and only those count, so fish 4's own marks (which doubled ours), a nested shell's, and a stray 133;D in printed output are ignored. Checked end to end against real bash and fish: false, exit 7, a syntax error, sleep, busy while running, and the pane closing mid-command. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Report each command's exit status in the shell's end-of-command markGabriel Schneider27 hours
| | | | | | | | bash and fish now print OSC 133;D;<status>, which ghostty already parses; the planned /pane/<n>/run file reads it to answer when a command finished and how. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Answer 9P on the connection's task, so a session can open its own treeGabriel Schneider27 hours
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | The editor's loop was the only thing that could answer a 9P request, which made the editor's own syscalls through a mount of its own tree -- a Look at /mnt/9p/pardes/<me>/anything under a `9ns --mntgen` view, a Save into it -- requests only the blocked loop could serve. The name-based refusal that followed (ownMountSuffix) and the in-process routing of a mount of oneself (Client.sameSession) were patches over that, and both are gone, with the mailbox that shipped every request to the editor's thread. One rule replaces them, `pardes.turn`: the core is single-threaded, the editor's thread has the turn by default and gives it up in two kinds of gap -- while it waits for input and while a step of it is out in a host syscall -- and a cloud9 connection task takes it in those gaps to answer. `out` counts the steps that are out, from any thread: while one is, the core reads consistently but that step still holds pointers into it, so a request that would change a pane (a write, a truncation, an rmdir) is parked in the engine and retried when the turn is next given up with nothing out, and the editor's own wake waits for the count to reach zero. It is never a write of its own that a step waits on out there -- writes come from a shell performing a save between steps -- so a parked request is never the syscall's own, and making a pane or rendering a screen need not park: every yield sits before its step's mutation, so the layout and the surface are whole under it. A changing request that queued effects is answered once the editor has performed them (`echo Save > exec` returns with the file written, as acme's `put` does), and it settles the way a step does, because without that a /log reader waited for the user's next keystroke. Every host syscall on a user path has to give the turn up, not fs.zig's alone: the first end-to-end run hung in `inotify_add_watch` performing the new pane's watch effect. PDFs and images are read whole at open, so no draw goes out into the host. The core's allocator takes its fixed buffer through the lock-free interface, since a connection task allocates while the editor's thread is out in a syscall that allocates too. A Restore puts the replacement in first and releases every task waiting on the old core. cloud9 (pinned at eb1a104) parks an open, a truncating wstat, a clunk and a remove on `again`, not only reads and writes, and answers a parked job whose fid was clunked without asking the backend. Verified: test/selfmount.py runs the editor under `9ns --mntgen` and Looks at, reads and Saves its own tree through the mount; a unit test pins that a change parks while the editor is out mid-step and lands when it rests, while a read is answered in the window. 9P over the Unix socket against a tty session, same machine, Debug builds: a read of /index 278us -> 61us, a truncating body write 1184us -> 609us, exec Save 718us -> 583us; the gesture benchmark is unchanged (geometric mean 0.997 over 53 cells). Also from the reviews: a notice chip over an image or PDF pane was painted out by the picture drawn after the cells, so pictures give up the rows; in the GUI a tree-sitter context band painted over the chip, so body layers are emitted first; a message is one row of printable text, its 256-byte cut never leaves half a glyph, and one wider than its pane keeps its tail (the file name, the reason) rather than its head. Co-Authored-By: Claude Fable 5.1 <[email protected]>