summaryrefslogtreecommitdiff
path: root/test
Commit message (Collapse)AuthorAge
* Registers hold a value per range, and "<reg> names oneGabriel Schneider3 days
| | | | | | | | | | | | | Registers.zig replaces the one yank buffer: every register keeps a value per range, y fills them in document order, and p, P, R and insert Ctrl-r put value i at range i, repeating the last (helix paste_impl). "<reg> names the register for the next command; _ swallows, # numbers the ranges, . is each range's text, % the file's name, / the last s/S pattern, and + and * are the system clipboard through the ClipYank and ClipPaste paths. SPC y now writes + alone, as helix's does. The acme chords and a paste into a terminal take the default register joined by newlines. The msel-yank-paste waiver is gone. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* The two regex waivers are gone: s and S already match helix thereGabriel Schneider3 days
| | | | | | | | | s and S search each line as its own haystack, so ^ and $ hold at every line and . never takes a newline, which is helix's multi_line regex. The waivers for the old difference, sel-regex-caret and sel-regex-dot-newline, had gone stale, and --strict stopped on the first before printing its summary. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* & aligns the selections into columnsGabriel Schneider3 days
| | | | | | | | | | | helix-golf's enumerate_and_align ends on %s |\d+<ret>&. align_selections treats the k-th range of each line as column k and puts spaces before each range until its head reaches the widest head of its column, measured in display cells, which File.rawDisplayCol already gives. A range over several lines refuses, as in helix. The example itself still needs more than 64 ranges, which the selection model does not hold. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Backspace in a line's leading blanks goes back to the previous indent stopGabriel Schneider3 days
| | | | | | | | | | helix's delete_char_backward dedents: when everything before the cursor on its line is blank, it deletes back to the previous multiple of the indent width, a whole unit when already on one (a tab before the cursor still goes alone). pardes deleted one space, so O<backspace> under an indented line left three of four, which is where helix-golf's function_into_class ended up. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Alt-( and Alt-) rotate the text of the selectionsGabriel Schneider3 days
| | | | | | | | | | | helix-golf swaps keys and values with rotate_selection_contents (webS_to_ <ret><A-(>, then per line). Each range's text moves to the next range (the previous one for Alt-(), in one edit; a count rotates by that many ranges, each range comes back over the text it now holds in its own direction, and the primary follows its text. That is the harness's helix; 25.07.1 read the count as a group size and left the primary alone. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Alt-J joins lines and selects the spaces it put inGabriel Schneider3 days
| | | | | | | | | | helix-golf's text_into_array turns lines into an array with %<A-s>ms"<A-J>i,: join_selections_space joins like J and leaves one cursor on each space it inserted. J and Alt-J now make one edit over every range, as helix does, rather than J being replayed per range; that is what lets Alt-J hand back all the spaces as the new selection. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Blank lines open above a selection's first line and below its lastGabriel Schneider3 days
| | | | | | | | | [<space> and ]<space> measured from the cursor's line, so over a selection of several lines whose head was at the bottom, [<space> opened a line INSIDE the selection. helix takes the range's line_range: above its start, below its end. helix-golf's export_from_mod (yp[<space>) is where it showed. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Replacing a selection with the register keeps its directionGabriel Schneider3 days
| | | | | | | | | R always left the head on the pasted text's last char. helix maps the replaced range through the change, so a backward selection comes back backward, its head on the first char: helix-golf's reverse_golf_example (xbR) is where it showed. SPC R shares the path. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Surround keeps the direction of the selection it wrapsGabriel Schneider3 days
| | | | | | | | | ms<ch> always left the wrapped range pointing forward. helix gives each range back with_direction(range.direction()), so a selection made backward (b, or v moving up) keeps its head on the open, which is where the next motion starts from. helix-golf's csv_to_sql (bms`) is where it showed. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Every helix-golf example is a differential case, one per step of its walkthroughGabriel Schneider3 days
| | | | | | | | | | | The ten examples on helix-golf are imported as 118 cases: each numbered step of an example's walkthrough is the case whose keys are the command up to and including that step, so the first mismatch names the step where pardes and helix part. Six more restart at a step that begins with %, from helix's own text there, so a later step is tested even when an earlier one misses. zig build hxgolf compares all 124 with goldens from hx-harness. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* A terminal bound as a language's REPL takes the execs made on that ↵Gabriel Schneider3 days
| | | | | | | | | | | | | | | | | | | | | | language's files Repl python in a terminal's tag or on its ctl binds it (Repl - unbinds, Repl bare says the binding, the same word again changes nothing), with an id, python-a, python-b, in its tag. An exec made by a gesture on the body of a file in that language -- a middle click or the execute key, on any text, a single word too -- or on the REPL's own body is typed into it, bracketed if its program asked for that, else line by line with a once-said warning for Python's blank lines, then Enter; the message row says -> python-a in the tag's name tint, the log send <from> <to> <id>. Several bound for the language: the pane asks which on its notice band, as Del asks, one key answering, Esc none, nothing remembered. The gesture is known where it is dispatched (mouse.zig, the execute key), so the tag's words, Exec <text> run by name and a command word @`cmd` stay commands, and a 9P exec is never sent. A finished command pane is no REPL; bindings are not dumped. The docs spell out the escapes, the pdb and input() caveat and the no-bracketed-paste papercut. 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 Schneider3 days
| | | | | | | | | | | | | | | | 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 terminal's Tty word names its shell, Tty+fish, and a click on it opens anotherGabriel Schneider3 days
| | | | | | | | | | | | | | | 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 Schneider3 days
| | | | | | | | | | | | | | | | | | | | | | | 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]>
* A Restore answers its writer before hanging up, and the log records dumps ↵Gabriel Schneider3 days
| | | | | | | | | | | | | | | | and restores A client that wrote Restore saw its connection cut with no answer, and could not tell a Restore from a crash. The listener now lets the writer's answer out before the cut, and cuts only the old editor's connections, refusing their requests meanwhile; a client that dials during it is the new editor's and stays. Dump logs 'dump <path>' and the restored editor's log 'restore <path>'. Keeping connections across a Restore was weighed and left: the fids name the old editor's panes and opens, so it would mean carrying serials and open records into the new one, where acme's Load only adds windows. tty's Restore also closed its shells' ptys without reaping them; it retires them now. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Restore asks about unsaved text first, as Exit doesGabriel Schneider3 days
| | | | | | | | | | | Restore replaces every pane with a dump's, so a pane edited since its last save lost that text without a word, where Exit asks. acme's Load only adds a dump's windows and so never asks; Restore now asks what Exit asks (acme's rowclean and winclean, wind.c:511-529): each modified pane says so once, and Restore again with nothing edited since goes ahead. Exit and Restore share the check and its warned-at revision, as acme's winclean clears one dirty flag for any asker. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Exit quits and Kill stops commands, as in acmeGabriel Schneider3 days
| | | | | | | | | | | | | | | | 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]>
* Kill written to /ctl is answered before the editor quitsGabriel Schneider3 days
| | | | | | | | | The write got EPIPE: Kill set quit, the request waited for the editor to settle its effects, and the editor quit and cut the connections first. A write that quits the editor is now answered at once, and the listener lets pending answers out (up to 200 ms) before it stops the runner. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* A refused or failed write is an err record in the log, naming the file and ↵Gabriel Schneider3 days
| | | | | | | | | | | | the reason Through a mount every refusal reads as Invalid argument, so a shell user could not tell a missing regexp match from a bad ctl word. Every write or truncation the tree refuses or that fails now adds err <serial|-> <file>: <why> to /log. No per-pane readable error file: acme's errors only takes text, and one stream is simpler to watch. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* A lock another open holds fails at once with file in use instead of parkingGabriel Schneider3 days
| | | | | | | | | | | A contended lock parked until the holder unlocked, but through a kernel or FUSE mount the kernel serialises writes to one file, so the parked lock held up the holder's own unlock and close on that ctl, and the two deadlocked. acme's qlock blocks; here the second lock is refused at once with file in use (EBUSY) and the client retries, and nothing parks on the lock any more. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Addresses search a line at a time as sam does, say why they fail, and a ↵Gabriel Schneider3 days
| | | | | | | | | | | | | | | | | | failed addr leaves no address addr's regexps ran mvzr over text[from..hi]: ^ and $ anchored only at the slice's ends, . matched newlines, the search never wrapped, and every failure read as bad address syntax. The regexps stay mvzr's, called the way sam searches (editors/acme/regx.c): one line per haystack, so ^ and $ fall at line boundaries and . never crosses a newline; a pattern naming \\n runs over the whole text with its . made [^\\n]; /re/ wraps unless limit is set, and ?re? takes the last match before the range. The ceiling (mvzr's first alternative, not sam's longest) is documented. A failed address says why (no match for regexp, address out of range, bad regular expression), and a failed write to addr leaves no address, so data and xdata refuse until the next good one instead of writing at the old range. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Truncating data or xdata deletes only the addressed range, not the whole bodyGabriel Schneider3 days
| | | | | | | | | | | A shell's echo NEW > data opens data with OTRUNC, and truncate() spliced the whole body away before the write replaced the addressed range: a five-byte replacement erased a buffer. data and xdata hold what addr names, so truncating one now deletes that range and nothing else; > on data replaces the range and : > data deletes it, and only truncating body clears the buffer. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Focus gives the keyboard only, reads empty while a header has it, and sits ↵Gabriel Schneider3 days
| | | | | | | | | | | | | in the root listing's natural place A write to /focus leaves a folded pane folded, as rio keeps current apart from unhide; while a column or workspace tag has the keyboard no pane does, so /focus reads empty and every pane's ctl says notcurrent (an empty read also no longer answers stale staged bytes). focus, ctl and commands now list after status, and the discovery test checks the root as a set rather than by position. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* A switch setting clicked with a selection held flips again, unless the ↵Gabriel Schneider3 days
| | | | | | | | | | | | | | | selection is on or off Giving panel and scene effects on/off made every switch a word that takes an argument, so a middle click on Verbose or PanelSlide with other text selected passed that text as its value and failed. The click path now passes a held selection to a switch only when it says on or off, and the word flips as it did bare; a ctl write is still checked strictly. The topbarexec golden had been recorded with that failure (Debug clicked with a sweep opening nothing, the very thing the script says it guards); it now shows the overlay again. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* A root commands file lists every builtin, its argument and which ctl takes itGabriel Schneider3 days
| | | | | | | | | A script could learn the builtin words only from source or the Help window. /commands is generated from the registry: one line per builtin, Word or Word arg, then root or pane for the ctl that takes it, so it is always this build's own list. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Split control messages by scope: the root ctl takes the session's builtins ↵Gabriel Schneider3 days
| | | | | | | | | | | | | | | | | | | | and reads the settings, a pane's ctl its own Every builtin could only be clicked, or written to exec, and the settings could be read only as the Config window's prose. acme keeps window verbs on a window's ctl, and webfs and upas/fs keep session settings on a root ctl. Each builtin now declares its scope (scope = .session; settings are all session, the rest pane), read by the registry. The root /ctl takes session builtins and reads every setting in the words a write takes, so its read written back changes nothing (panel and scene effects now take on/off like the toggles, to make that true); a pane's ctl takes the pane's builtins beside get, lock and unlock. Writes are checked whole and refused in Plan 9's ctl words (unknown control message "X", wrong #args ...), which 9ns now maps to EINVAL (cloud9 re-pinned at a8c7a715). A builtin that would prompt for its argument fails the write instead, and a refusal is answered at once, not after the frame. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* A root focus file names the pane with the keyboard and moves it; a pane's ↵Gabriel Schneider3 days
| | | | | | | | | | | | | | ctl says whether it is current Scripts had no way to ask which pane has the keyboard or to give it one. rio answers both through a window's wctl: its read ends with current or notcurrent, and a write of current takes the keyboard (rio(4)). A pane's ctl line now ends the same way, and since there is one keyboard for the whole tree, /focus reads the focused pane's serial and takes a serial to move the keyboard there, off any header that had it; a serial no pane has fails with "no such window". fs-bench's stale readdir check is fixed too. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Let the keyboard reach a tag's path, read-onlyGabriel Schneider3 days
| | | | | | | | | | | | | | The tag's computed path, dirty marker and page were out of the keyboard's reach: its cursor lived in the text after them. It now lives in the tag as shown, as in acme where the name is ordinary text at the start of the tag, so `0` goes to the line's start and motions select and yank the path. The path stays read-only: an edit that would change it is refused and leaves the cursor where it was, and typing into a file's path drafts a new name as a click on it does. Sync moves the tag's positions when the prefix changes length, so a rename or the dirty marker still never moves the cursor off the text after it. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Let s, S and | pressed in a tag answer for the tag's own textGabriel Schneider3 days
| | | | | | | | | | A tag is a text like the body, so selecting on a regex and piping act on the text they are pressed in: a prompt records the text it answers for, the tag keeps the keyboard while it is typed, and a header's prompt goes on the active pane's band. `/` still searches the body from anywhere, as acme's Look from a tag searches the body. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Scroll a tag taller than its rows to keep its caret in viewGabriel Schneider3 days
| | | | | | | | | | A tag shows at most eight rows, so a longer one (a 9P write of many lines, or `o` in the tag) put its caret on a line that was never drawn. Pane, column and workspace tags now keep a first line on screen that follows the caret while they have the keyboard, and the pointer and the pane text count their rows from it. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* File the tag code into tagline.zig and draw tags beside bodiesGabriel Schneider3 days
| | | | | | | | | | | With tags reduced to Texts, what is left of them is the computed prefix, the default and saved tails, entering and leaving a tag and the headers: that goes to tagline.zig, as acme keeps the tag half of a window in wind.c. Tag and header drawing moves next to body drawing in body_layer.zig, and the tag hit helpers go to tag_layer.zig with the Hit they read, where sameCell now also tells the lines of a taller tag apart. The docs describe the tag as a Text. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Let a pane's tag take a row for each line it holdsGabriel Schneider3 days
| | | | | | | | | | | A tag that is a text can hold a newline, and the tagline, the body under it and the pointer all assumed one row. The pane's tag now takes a row per line (up to eight, and never the body's last row); the body starts below it, the rows a pointer counts start the body after the tag's lines, a click on any line puts the tag's cursor there, and a pixel shell gets a tag layer per line. A tag of one line draws exactly as before. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Make the column and workspace tags Texts with the pane tag's keysGabriel Schneider3 days
| | | | | | | | | | | | | The headers had their own one-line editor with its own keys: Enter always executed, h/l and J/K moved between headers and panes, Ctrl-c/x/v cut and pasted, and TagLine refused a newline that 9P accepted. They are now Texts like a pane's tag, with no prefix and a default until edited, so normal and insert mode, undo and the look and execute keys are the body's. Moving between them is the window keys' job: Up from a pane with nothing above it reaches its column's tag and then the workspace's, Down comes back, and Left and Right walk the column tags. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Make a pane's tag a Text edited with the body's own keysGabriel Schneider3 days
| | | | | | | | | | | | | | The tag was a fixed buffer with its own one-line editor, a cursor measured across the path, and a hijack of the body's mode while it was typed into. It is now the pane's second Text: its own characters after the computed prefix (path, dirty marker, PDF page), which is never stored in it, so a rename or a dirty marker never moves its cursor. Normal and insert mode are the body's, undo included; `:` moves the keyboard between body and tag, the tag remembers its cursor and starts on Save, and executing a word from it hands the keyboard back. The mouse still sees the prefix: a sweep selects across it and a click on the path drafts a new name. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Give prompts their own line to type into instead of the tag's tailGabriel Schneider3 days
| | | | | | | | | | | A search, pipe or Save prompt wrote its marker and answer onto the end of the tag, so the tag's cursor, its selection, its 9P writes and a prompt abandoned by a click all shared one buffer: the abandoned text stayed in the tag and a 9P write landed inside the prompt. The answer is now its own Text on the pane, typed in insert mode and shown on the notice band where it was already drawn, and the tag never holds any of it. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Give a pane's body its own Text holding the cursor, selections, mode and undoGabriel Schneider3 days
| | | | | | | | | | | 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]>
* Give the pipe its own fields in PipeGabriel Schneider3 days
| | | | | | | | | | | | | | Not a pure move: state moves. Pardes's `pipe_seq` and `pipe_wait` become Pipe.zig's own fields `seq` and `wait`, and Pardes embeds one as `pipe: Pipe = .{}`. pipeRequest reads only the request in flight, so it now takes `pipe: *const Pipe` and the four shells call `core.pipe.pipeRequest(id)` (their Pipe imports go away again); the other pipe functions still need the panes and keep `p: *Pardes`, writing `p.pipe.seq/wait`. Field reads in dump.zig, the shells' tests and two test files follow (`core.pipe_wait` becomes `core.pipe.wait`). No behaviour change. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Move saving and loading a whole editor out of pardes.zig into dump.zigGabriel Schneider3 days
| | | | | | | | | | | | | | | | | | | Pure move, no behaviour change (acme keeps dump and load in rows.c): dumpState, restore, initFromDump and initDump go verbatim to the end of dump.zig, after the dump format they read and write. Inside dump.zig the moved code's `dump.` prefix drops, so `Pane` there is the dump record; the one editor pane it names is spelled `pardes.panes.Pane`, and its other `panes.X` references become `pardes.panes.X` because dump.zig's own tests use `panes` as a local name. The methods become free functions taking `p: *Pardes`: `p.dumpState()` becomes `dump.dumpState(p)`, `core.restore(bytes)` becomes `dump.restore(core, bytes)` and `Pardes.initFromDump(..)` becomes `dump.initFromDump(..)`, in pardes.zig, the shells, layout.zig, Terminal.zig, builtins.zig and the tests (38 receiver rewrites plus the initFromDump calls). The tag-tail restore helpers stay with the tag code. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Move exec out of pardes.zig into exec.zigGabriel Schneider3 days
| | | | | | | | | | | | | | | | | | | | Pure move, no behaviour change (acme keeps this in exec.c): execute, commandText, max_exec_depth, executeBuiltinLine, applyStartupConfig, runBuiltin and applySettingBuiltin; the getarg-style operand code (withArg, PointerOperand, pointerOperand, heldSelection, chordEachSel); takesCommandLine and the terminals commands run in (spawnTty, spawnV9fsTty, spawnTtyWithMount, evictLonePristineTty, replaceStillborn, ttyForDir); placeDoc; and the save path (submitSave, saveFile, saveTo, askWrite), with the acme-chords test, go verbatim to exec.zig. The methods become free functions taking `p: *Pardes`. executeBuiltinLine is called from ~170 places as `p.executeBuiltinLine(..)`, so Pardes keeps one declaration alias for it and those call sites stay; the other 104 calls change from `p.execute(..)` to `exec.execute(p, ..)`. ninep/ctl.zig has a local named exec, so it writes `pardes.exec.execute` instead of importing the file. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Move looking out of pardes.zig into look.zigGabriel Schneider3 days
| | | | | | | | | | | | | | | | | | | | | | Pure move, no behaviour change (acme keeps this in look.c): expanding the word under a click (ExpandedWord, expandedWord, expandedSel, cursorWordSel), the n/N walk (LookFrom, lookEdge, lookStand, lookWalkPanes, lookPast, wholeRowSpan, lookSpanIn, lookWalk, landLookSpot, noteLookSource, armLookWalk), search results (Search, SearchStart, submitSearch, lookFirstHit, runSearch, searchStep, jumpResult), the look-hover preview (LookHoverWait, LookHoverPreview, FileWordSpan, PdfWordPreview, invalidateLookHover, cancelLookHover, lookHoverPane, noteLookHover, refreshLookHoverFromRaw, advanceLookHover) and lookAt with its targets (focusPaneLine, selectSpan, openPaneTarget, focusPaneByPath, clearNavigationSelection, resolveLookTarget, locationText, canonicalLookLocation, pdfLinkLocation, followPdfLink), with five tests, go verbatim to the end of look.zig after its word and target resolution. The methods become free functions taking `p: *Pardes`; their 143 call sites change from `p.lookAt(..)` to `look.lookAt(p, ..)` (tests reach them as `pardes.look.x`). Inside look.zig the moved code's `look.` prefix drops. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Look no longer announces itself on the message rowGabriel Schneider3 days
| | | | | | | | | | | | | | | | | | A look's answer is the pane it opens or the place it jumps to; its own name on the message row was only noise over that. Look now declares `pub const quiet = true;`, like Msg, so runBuiltin skips the announcement. A registry test checks that Look is quiet and Del still announces. 23 snapshot goldens drop the "Look" chip from the message row and nothing else: chordcut find hscroll images jumps layout-open look-center look-dir look-file look-file-1col lookloaded lsp lsp-client lspcomplete lsprelpath psearch stepgrain syntax tag tagbottomimage tagnav ttylook ttytaken. Updated with --update --jobs=1 on exactly those scripts; every changed row, compared screen by screen against the old golden, differs only by the blanked chip (and its style runs), and two full snap runs after the update pass. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Move helix normal mode out of pardes.zig into normal.zigGabriel Schneider3 days
| | | | | | | | | | | | | | | Pure move, no behaviour change: the key-to-role matching (isPrefix, roleBindingName, normalInput), multiOnce, the selection regex prompt (startSelRegex, selRegexArmed, applySelRegex), KeyState, Replay and replaySels, handleNormal, executeNormalAction and handlePdfNormal, with two tests, go verbatim to normal.zig. The methods become free functions taking `p: *Pardes`; their 22 call sites (handleKey, the mouse, look walk, lsp, runBuiltin, body drawing and edit.zig) change from `p.handleNormal(..)` to `normal.handleNormal(p, ..)`. test/lspbench.zig's second anchor follows its needle into src/normal.zig. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Move text editing out of pardes.zig into edit.zigGabriel Schneider3 days
| | | | | | | | | | | | | | | | | | | | | | | Pure move, no behaviour change (acme keeps this in text.c): which text a pane edits and how it maps to the screen (editText, editTextEol, setEditText, paneCursorLines, paneByteAtDisplay, pinPaneCursor, flatSurface, paneWrapWidth), insert mode (enterInsert, handleInsert, insertKey, insertTab, exitInsert, clampFileCursor), the d/c/y/p edit operations with replace, case, join, indent, comment, number, textobjects and surround, undo and redo, yank/clipboard/paste (setYank, setClipboard, ClipRequest, clipRequest, typeToTty, applyPaste, clipYank), and the pointer selections as text (PointerTextSelection, pointerTextSelection, capturePointerSelection, paneText, pointerSourceLine, selectionText, spanHas, currentSelText), with four tests, go verbatim to edit.zig. The methods become free functions taking `p: *Pardes`; calls change from `p.insertKey(..)` to `edit.insertKey(p, ..)` (pardes.zig, body_layer.zig, selection_pipe.zig, builtins.zig, test/hxdiff.zig, test/perf.zig). In executeNormalAction the `.edit => |edit|` capture becomes `|op|`, since it would now shadow the edit import. test/lspbench.zig's first anchor follows its needle into src/edit.zig. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Move colour themes out of pardes.zig into colors.zigGabriel Schneider3 days
| | | | | | | | | | | | | | | | | | | Pure move, no behaviour change: the Theme type, the curated palettes, fold and the themes ring with its two comptime checks, themeContrast, mix, ChromeTheme, ChromeAnimation and initial_chrome, the Pardes methods that load theme files and switch themes (nextThemeFileGeneration, requestThemeFile, ThemeFileRequest, themeFileRequest, failThemeFile, loadThemeFile, finishThemeInitialization, invalidateThemeDependentRasters, setThemeIndex), and the ten theme tests go verbatim to colors.zig. The methods become free functions taking `p: *Pardes`; their call sites change from `p.setThemeIndex(i)` to `colors.setThemeIndex(p, i)` (37 of them, in pardes.zig, builtins.zig, file_watch.zig, macos.zig and two test files). pardes.zig keeps `pub const Theme/themes/native_theme_count/ ChromeTheme = colors.X;` for the shells that name them, and sync and enterTagEdit become pub because a moved test calls them. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* A pane's ctl takes acme's lock and unlockGabriel Schneider3 days
| | | | | | | | | | | | A client doing an edit of several writes to addr and data had no way to keep another client's from landing in between. acme's window ctl takes lock and unlock for this (editors/acme/xfid.c:603-611): a qlock that blocks a second locker, owned by the fid that wrote it and given up when that fid is clunked, binding only clients that ask. pardes does the same: the open's record holds it, a second lock parks until unlock, close or the pane closing, and no other write is refused for it. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Keep a pane's filesystem state on the Pane instead of a slot-indexed tableGabriel Schneider3 days
| | | | | | | | | | | addr, limit, the event queue and its readers, pty/data's queue, a waiting run and the /log announcement flag lived in p.fs.panes[slot], reset when the pane went; a reused slot is how the bug class came in. acme keeps the same state on its Window (editors/acme/dat.h:226-266), so now Pane.fs holds it: announced, retired (noteRetire, which still answers a waiting run and drops the listener count first) and freed with the pane itself. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* pty/run answers with the command's output; pin cloud9 with 9ns concurrency ↵Gabriel Schneider3 days
| | | | | | | | | | | | | | | | | | and E.BUSY The answer is now the header line (exit N, or exit N cut M when only the last 64 KiB were kept, or exit N cut when its start scrolled out of the history), then what the command printed: the screen text between its C and D marks, which the marks handler pins so scrolling keeps them. Also from review: a plain /log open honours its offset until follow is written, so tail -n and less work; whitespace-only run lines are refused. cloud9 is pinned at 82d8152c: 9ns keeps up to 32 requests in flight per mount so a waiting read no longer freezes the rest of it, and E.BUSY replaces the errno pardes spelled locally. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Make pty/run and /log streams, and take refused lines back cleanlyGabriel Schneider3 days
| | | | | | | | | | | | | | | | | | | | | | | | | 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 Schneider3 days
| | | | | | | | | | | | | | | | | | | | | | | /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]>
* Wake reads waiting on /log, event and pty/data; one reader per consuming ↵Gabriel Schneider3 days
| | | | | | | | | | | | | | | | | | | | | | file; pty/ctl reads back A read parked on a queue was retried only when some unrelated write had to wait, so a follower of /log (and a reader of event or pty/data) slept until then. Pushing a record now marks the turn parked, and giving the turn up wakes them (measured: stuck past 3 s before, 0 s after). event and pty/data consume what they read, so a second open for reading is refused with rio's "file in use" (EBUSY through 9ns); writers still get in, and pty/data queues output only for an actual reader. pty/ctl reads back "winsize C R", in the words it takes. /log fixes from review: a record longer than a read comes in pieces (a shell read loop failed on long lines), every repeated message is logged, a record bigger than the ring is cut to fit instead of emptying it, the ring is reserved at boot so recording never allocates, and panes present at boot are recorded first. Co-Authored-By: Claude Opus 5.5 <[email protected]>