summaryrefslogtreecommitdiff
path: root/src/look.zig
Commit message (Collapse)AuthorAge
* A look at a ./ or ../ name that is not there names the file, not the pane's ↵Gabriel Schneider4 days
| | | | | | | | directory Any relative name that opened nothing was blamed on the pane's directory (look: .../work/many: no such directory) though it exists; the directory is named only when it is gone. A plain name is still looked for as text, its miss logged as any look's. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* A Find or Grep that finds nothing empties its directory's +SearchGabriel Schneider4 days
| | | | | | It failed with no match but left the last search's rows in the +Search, where they read as this one's. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* A remembered place is clamped to the file as it is now, its selection's two ↵Gabriel Schneider4 days
| | | | | | | | ends kept A Recent row or a jumplist entry for a line the file no longer has failed the look; now it opens at the last line. The selection's anchor is remembered too, so a reopen restores both ends where they fit, and Recent and Jumplist share the (closed) marker. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Merge: 9P rounds 18-20 + Recent + renames + render G11-G14, tty track, ↵Gabriel Schneider4 days
|\ | | | | | | cursor + faithful themes
| * GUI goldens settle on the window's idle, not two captures that agreeGabriel Schneider4 days
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | test/gui_golden.py hashed a scene once two captures 400 ms apart agreed; on a loaded machine a slow frame agreed with itself before the scene's change landed and every later hash moved up a scene. A test window now writes a status file into its capture directory each loop step (gui.zig writeTestStatus: step, frames captured, idle — nothing owes a frame, nothing moves, nothing waits on the core's clock), and the harness waits for an idle step two past the one current when it asks: a 9P write has been served by then, so the frame is the scene's. Two things kept a window from ever going idle, both fixed: - Look-hover re-armed forever: resting on a word with nothing to open, the hover wait ran its two frames, found nothing, cancelled, and the next present's pointer refresh started it again: a frame every other frame for as long as the pointer rested there, live too (CPU). The place last refused is remembered (Pardes.look_hover_refused) until the pointer moves off it. - The virtual clock moved only on a wait that timed out, so a steady trickle of events held it still; it now moves to the core's wake too when an event cut the wait short and nothing owes a frame. Captures go to a directory of their own, outside the session's (a file written there every frame is a change in the directory its panes look at). All 17 goldens match the file as recorded at oooopqyv; eight runs, five alongside a ReleaseFast build and three with all 16 cores saturated (load 13-19), were byte-identical, 14 s a run unloaded (was ~30). Tests: the resting pointer lets the core go idle.
* | A served-tree pane spoils no Grep, and `look .` from a gone directory is ENOENTGabriel Schneider4 days
| | | | | | | | | | | | | | | | | | | | | | A pane named `/virtual` gave Grep the root `/`, which swallowed every other pane's directory and ran out of walk before any file: every Grep missed. Grep passes over panes of the served tree. And `.` from a pane whose directory was gone resolved to the served tree's root (/virtual/), or through the jump list to another pane's directory: a dot path is the looking pane's own, and gone it fails, `look: <dir>: no such directory`. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* | Grep and Find count the rows they found, so a hit opens its +Search and a ↵Gabriel Schneider4 days
| | | | | | | | | | | | | | | | | | | | | | | | miss fails Round 18 made a search that found nothing fail, but grep and find never counted their rows (only a pane search did), so every Grep read as a miss. Both count the lines they wrote now; a Find or Grep that finds nothing fails alike, `find: no match, not found` / `grep: ...`, ENOENT, and opens no empty +Search. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* | Grep with no hit says grep: no match, and a pane directory that is not there ↵Gabriel Schneider4 days
| | | | | | | | | | | | | | | | | | | | | | is passed over Grep walks every pane's directory; one not there (a scratch named into a directory not made yet) failed the whole search with `file not found`. Such a root is passed over now, and a search that finds nothing says `grep: no match` and opens no empty +Search. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* | `@p<terminal>:N` past the terminal's last line is a miss, as in a fileGabriel Schneider4 days
|/ | | | | | | | The line-past-end check read only a file pane's lines, so a terminal's look went nowhere and said nothing. A terminal's lines are its logical lines, as a +Search of it lists them. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Mount dials its peer at once and fails if none answers; a peer out of reach ↵Gabriel Schneider4 days
| | | | | | | | | | | | | is said as `dial failed: <reason>` Mount took any well-formed dial and nothing was found out until the first look through it, which then said `look: look: dial`. Mount now probes the peer (version, attach, its root) with the turn out, and fails its write, mounting nothing, when none answers; the dial errors read as words, `no answer`, `timed out`, `hung up`, and a look through a peer gone since names its path: `look: /n/peer/f: dial failed: no answer`. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* A look that goes to another pane leaves the looking pane's dot aloneGabriel Schneider4 days
| | | | | | | | | | Every look that found something cleared the looker's selection first, so `@p5:2` or `f.txt:2` written to a pane's look collapsed its dot to q1-1. Only a look that stays in the pane starts from a clean selection now; one that goes elsewhere takes away just the click's highlight, and a miss changes nothing, as acme's look does. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* A look line takes `?re?` after its colon, as addr doesGabriel Schneider4 days
| | | | | | | | `file:?foo?`, `@p5:?foo?` and `:?foo?` were not taken for addresses (the first character had to be one of `0-9+-/$.#,;`), so they went the path way and found nothing. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* `@p<terminal>:<address>` takes any address over the terminal's logical linesGabriel Schneider4 days
| | | | | | | | | Only a line number reached a terminal (the line way); any other address went to lookAddress, which wanted a file and said the terminal was no text. The body is now the lines a +Search of it lists, addressed from the cursor, and the match selected as a +Search row's range is. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* A line address to no open pane, `@p77:3`, is a miss said as one, not the ↵Gabriel Schneider4 days
| | | | | | | | | active pane `@p<serial>:<line>` goes the line way (parsePathLine), where a serial naming no pane quietly did nothing and look read back the active pane. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* @p<serial>:<address> takes any sam address, as file:<address> doesGabriel Schneider4 days
| | | | | | | | | | @p<serial> took a line spot but an address after it (/re/, #n, $, 0/re/, a range) was dropped, the name failing the file check, and an unknown serial fell through to a search. The pane is now addressed like a file; a miss and a serial no pane has each log an err, and look reads back empty. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* A word written to a raw terminal's look is listed, as in normal modeGabriel Schneider4 days
| | | | | | | | | A new Tty is in raw mode, where a right click is its program's, and a look written to it over 9P did nothing for that reason. The look file is no keyboard input: over 9P a plain word is listed in a +Search pane and read back whatever the terminal's key mode. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* One rule for failing builtins: the write fails, one err, no msgGabriel Schneider4 days
| | | | | | | | | | | | | | | | A builtin that failed through a ctl failed the write and logged its err, but through look, exec, tagexec or a column's exec it only said so on the message row, logged as a msg, and the write succeeded. Now every click write runs its builtin as a ctl line does (ctl.captured): a failure fails the write, with its words (EINVAL for malformed input, else EIO or what the words name), logs one err and no msg or announcement. get's Modified and a look miss are shown but logged once, as their err. A control character's refusal names its reason, a failed click reads back nothing, and look never reads back a pane closed since. The special case for a cut-short Edit through exec goes (the rule covers it); fs.md's table of exceptions becomes the one rule, as does the skill. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* @p names a pane by its serial, as /index and the log doGabriel Schneider4 days
| | | | | | | | | | | | A +Search row for a pane with no path (a terminal's), a jump list row and a line-number look said @p<slot>, the pane's place in the core's table, which no client can see. They now say @p<serial>, and @p resolves a serial. Re-recorded by name, their diffs checked: jumps, ttylook and psearch (whose waits now say @p1) for @p<serial>, and with them argv, nested, reflow, tinywin, ttyhelp and ttyonly for the boot scratch now named <cwd>/+New (the change before). Co-Authored-By: Claude Opus 5.5 <[email protected]>
* A looked word selects its next place in its own pane, as acme's look3Gabriel Schneider4 days
| | | | | | | | | | | | | | A plain word looked at (a look write, a right click, an ML record written back) opened another identically named +Search pane each time, until the panes ran out, and never wrapped. As acme's look3 searches the text for it (look.c:210), it now selects the word's next place in that pane after the dot, wrapping at the end, and opens nothing. LookWord list, a new root setting, keeps the +Search listing (search is the default); the rsearch snapshot and an output test that walk the listing say LookWord list. (The leader-path check over every builtin gets a comptime branch quota, as one more builtin took the GUI build past the default.) Co-Authored-By: Claude Opus 5.5 <[email protected]>
* A look's miss repeats the path whole, up to 256 bytesGabriel Schneider4 days
| | | | | | | | | The miss message clipped what was looked at to 48 bytes, mid-word, and a path past 160 bytes turned the has-no-line message into a bare no match. Both now carry up to 256 bytes, cut on a character boundary with an ellipsis past that. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* A look at file:/re/ on an open pane goes on from its dot, and a miss leaves itGabriel Schneider4 days
| | | | | | | | | Look's clean start cleared the active pane's selection before the address read the dot, so the root look's file:/re/ always found the same match, a miss collapsed the dot to a caret, and dumps kept the collapsed dot. A pane already open on the path is now addressed where it stands. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* The look walk goes on from a restored dot both waysGabriel Schneider4 days
| | | | | | | | | | | After a Restore the dot came back on the match it was on, but N stayed on that match instead of wrapping to the last as it does live: the walk remembers the spot it landed on (look_at), and a restored pane has none, so it stood at the cursor, the selection's end, and found the match it was in. A one-row selection now counts as where the walk stands, from its start and past it either way, as a landed spot does. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* A tag that exactly fills its row stays one row, as acme's doesGabriel Schneider4 days
| | | | | | | | | | | | | | | Tags wrapped one cell early to keep a cell for the caret past a full row, so a tag that exactly filled its column broke as De / l. acme's frame wraps a box only when it is wider than what the row has left, and frptofchar clamps the tick past a full line's end to the frame's edge. Tags now wrap at their full width; the caret past a full row sits at the tag layer's edge, and on the pane's last cell in a terminal grid, which has none past it. Two tiny-screen tests collapse their tags, as the others do, and the 41 goldens that moved were re-recorded by name after the same check: rows shift back up under the shorter tags and nothing else changes. chordcut's file-body clicks moved back with them. Co-Authored-By: Claude Opus 5.5 <[email protected]>
*-. Merge: 9P round 6 + empty columns + render (acme theme, grips, Lift, Motion)Gabriel Schneider4 days
|\ \
| * | A pane whose whole text fits in its rows never scrolls to keep a marginGabriel Schneider4 days
| | | | | | | | | | | | | | | | | | | | | | | | The scroll margin reveals text; with none off screen there is nothing to reveal. ensureCursorVisible and a centred look both leave such a pane at its top. A longer file keeps helix's clamp (hxdiff and hxparity unchanged). Co-Authored-By: Claude Opus 5.5 <[email protected]>
| * | A column can be empty, as acme's can; new panes go where acme's ↵Gabriel Schneider4 days
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | makenewwindow puts them Newcol makes an empty column; closing a column's last pane leaves it empty with the keyboard on its tag; Delcol and Joincol alone take a column away; the session's last pane closing quits. The +New stand-in and replaceStillborn / stillborn_joiner are gone. Every new pane goes through exec.placeNew, which follows acme's makenewwindow (active column, empty column whole, blank space, halving the biggest) or, with Placement pardes, the old rules. ColumnTags is gone: column tags are always shown. A grip drag shows acme's box cursor. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* | | A pane tag wraps onto rows and can collapse to one, as acme's doesGabriel Schneider4 days
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | acme's frame wraps a tag at its right edge and the window grows to show every row (wind.c wintaglines); Tagup/Tagdown shrink it to one row and back. Pane tags used to be one row, scrolled sideways to the caret. Now a tag's lines wrap at its width (one cell kept for the caret past a full row) and it starts expanded; insert-mode Up on the first row collapses it, Down on the last row expands it, and Alt-Up/Alt-Down do so in either mode. A word the wrap breaks across rows is still one word to a click and a selection. Column and workspace tags keep scrolling sideways. Tests read body rows relative to the body's first row; the 41 snapshot goldens that moved were re-recorded by name after a programmatic check that each differs only by body rows shifting under the taller tags, and twelve scripts whose clicks targeted fixed rows now click the same text. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* | | A look with a blank in its pattern addresses, file:N selects the line, and a ↵Gabriel Schneider4 days
|/ / | | | | | | | | | | | | | | miss is said, logged and reads back empty Round 6: look calc.py:/return a/ was a word search, since a blank ended the address; calc.py:99 past the end focused the file and said nothing; and /look still read back the pane before a miss. A pattern after the colon may now hold blanks; file:N selects the line, newline included, as acme's does; a line past the end opens and focuses nothing; and every miss (no match, a bad address, a line past the end) says so, logs err <serial> look: why, keeps the selection, and leaves look reading back empty. Documented, with file:0/re/ for the first match, since acme evaluates the address from the file's dot. Five goldens change: a look at file:N now selects the line. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* / A look takes acme's file:addr and :addr, and one that finds nothing changes ↵Gabriel Schneider4 days
|/ | | | | | | | nothing A look at `file:/re/` or `:/re/` searched for the words instead of addressing, and a miss left an empty +Search pane and a cleared selection, while a hit answered the +Search pane on /look. Now an address after a colon (acme look.c:450) is evaluated in the file from its dot; a miss says no match and leaves the panes and the selection alone; a hit reads back as the pane the text is selected in. Both live in lookAt's one flow, so they are one change. Co-Authored-By: Claude Opus 5.5 <[email protected]>
*-. Trial merge: 9P + helix + renderGabriel Schneider4 days
|\ \
| | * One Layer for tags, notices, headers and bodies; a taller tag is one layer ↵Gabriel Schneider4 days
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | of rows; wire v8 src/Layer.zig merges TagLayer and BodyLayer. `rows` (0 = no layer) and a cursor at {x, y}. A tag of N rows is ONE layer of N grid rows: tagHit answers the row as `line`, bodyHit keeps its meaning, and the per-line layer bases (TAG_LINE_LAYER_BASE, HEADER_LINE_LAYER_BASE) are gone, not aliased. Wire v8, the one bump: tag layers carry rows and cursor y, and the frame carries the placed region list. v7 and v9 peers are refused in both directions (server test over both, a new frontend test over both). web: tag_layer_value 11 = rows, 12 = cursor y; app.mjs lays every row. macOS: the Zig side compiles against Layer; pardes.h still sees one row per tag layer (accepted, the macOS shell is ignored for now). No visual change: snapshot goldens and the 17 GUI goldens byte-identical. Shared files touched: pardes.zig, Messages.zig, gui.zig, macos.zig, detached/client.zig, detached/server.zig, detached/wire.zig (plus web.zig, app.mjs, edit.zig, look.zig). Not touched: mouse.zig, tagline.zig, colors.zig, tty.zig, dump.zig, exec.zig, host_io.zig, panes.zig.
| | * Move the frame into draw.zig and the transitions out of layout.zigGabriel Schneider4 days
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Pure moves, no behaviour change. draw.zig holds the whole core frame in order: render, the pane, tag, header and notice painters it calls, and the character-effect composition (Pardes.render stays a declaration alias). Presentation.zig is the panel presentation state as a file struct, and animation.zig the easing curves, transition kinds, tracks and boxes, the character effects' sources and the generic displayed-value transition, all of which lived in layout.zig. layout.zig keeps only layout.
| * | K and Alt-K keep or drop the ranges a regex matches in; Alt-: turns every ↵Gabriel Schneider4 days
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | range forward K and Alt-K share the s/S prompt, which now carries a mode rather than a split flag: a range stays when a match starts inside it (K) or when none does (Alt-K), the primary becomes the first, and keeping none leaves the selection as it was (helix keep_or_remove_matches). Alt-: is ensure_selections_forward. Co-Authored-By: Claude Opus 5.5 <[email protected]>
| * | Every range lives through insert mode, carried through each editGabriel Schneider4 days
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Insert mode dropped the selection on the first key and restored only the primary's appended-over span on Esc. Now each insert-mode edit is made at the bare cursor as before, and the range is then carried through it with helix's Range::map rules: typing slides an i range and stretches an a range, Enter (which now also drops the blanks before the cursor, as helix does) slides or stretches by what the cursor moved, and an arrow collapses. The replay gives every other range the same. Esc after a pulls each range's end back by one (helix restore_cursor), which replaces Text.append_at. The wiX-edit-drops-sel and msel-append waivers are gone. Co-Authored-By: Claude Opus 5.5 <[email protected]>
| * | Registers hold a value per range, and "<reg> names oneGabriel Schneider4 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]>
* / Errors are said in words, and a refusal names what it takesGabriel Schneider4 days
|/ | | | | | A failure read `Del k|j: InvalidArgument`, `Kill: NoSuchCommand`, `ill-formed control message`, or nothing at all. reportError now spells an error's name as words, and the refusals name their accepted forms: pty/ctl lists its verbs, a flag file takes 1 or 0, a setting word takes on or off, Del takes k, j or nothing, Repl takes - or a language. Kill with nothing to stop says so, /focus says pane, a Look that found nothing says no match, and pty/run on a command pane says it is not a shell. A ctl write's failure has room for the Repl list and is cut between words, the Repl word at a character. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Let s, S and | pressed in a tag answer for the tag's own textGabriel Schneider4 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]>
* File the tag code into tagline.zig and draw tags beside bodiesGabriel Schneider4 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 Schneider4 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 a pane's tag a Text edited with the body's own keysGabriel Schneider4 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 a pane's body its own Text holding the cursor, selections, mode and undoGabriel Schneider4 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]>
* Move exec out of pardes.zig into exec.zigGabriel Schneider4 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 Schneider4 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]>
* Ignore slash-only comment markers in general Look detectionGabriel Schneider2026-09-15
|
* Refactor panes and filesystem; replace FUSE with 9PGabriel Schneider2026-09-07
| | | | | | Consolidate pane, layout, memory and host code. Serve 9P by default over Unix sockets, with runtime mounts and optional TCP/QUIC transports. Remove FUSE and obsolete proof-of-concept examples. Fix highlighting and terminal-history performance, expand differential and stress-test infrastructure, sort navigation results while preserving the next occurrence, add syntax-colored Braille minimaps, remove SPC-k, and document 9P interaction as a repository skill.
* grep: one file it cannot read is not the end of the searchGabriel Schneider2026-09-03
| | | | | | | | | | | | | | | | `look.grep` aborted the whole walk and returned `OpenFailed` the moment any file refused to open. One root-owned 0600 file in the tree — or one deleted between the walk and the read, which is routine in a build tree — turned a search of ten thousand files into zero results and a word on the message row that explains nothing. A grep is a question about the files you can read, and the ones you cannot are not an answer to it: they are skipped now, along with a read that fails partway. Opened NONBLOCK for the reason readFile has it, so a FIFO in the tree cannot stop the search until somebody writes to it. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_016Q4RATpafkwahrovHQLKRf
* errors: a mistyped flag is a sentence, and a pipe is not a documentGabriel Schneider2026-09-03
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Every argv refusal in main.zig was `return error.BadArgs` out of `main`, which std prints as `error: BadArgs` with a return trace under it — the same shape a real crash has, for the most ordinary thing a person can do. It also said `BadArgs` and nothing about which argument, at sites that knew exactly: pardes: no such option: --hepl pardes: -n takes 1 or 3, not 'abc' pardes: one file or directory at a time, and 'b' is the second pardes: --detach and --attach are opposites: one runs the session, the other joins one Try 'pardes --help'. stderr rather than the `+Errors` pane one function down, because argv is read before a core exists and the person who mistyped a flag is looking at the prompt they typed it into. `--attach`'s refusal already answered this way; now all eleven do. `getcwd` failing is no longer reported as an argument problem, and a `.url` or `@pN` positional says why a LAUNCH cannot act on it rather than being swept into the same word as a typo. AND `Look` ON A FIFO NO LONGER FREEZES THE EDITOR. `readFile` opened with a plain blocking `open`, so a named pipe with no writer waited forever — inside the keystroke that asked, with no frame, no message row and, in the tty shell, no Ctrl-C either, because the terminal is in raw mode. It is `O_NONBLOCK` now, the read loops answer `EAGAIN` rather than waiting, and `lseek` answering ESPIPE — a pipe, a socket, a terminal — is refused as `NotAFile`, which the boot pane spells "that is a pipe or a device, not a document". Without that last part an unwritten FIFO read as EOF and opened a silent empty pane, which says less than the hang did. The zero-size files worth streaming (procfs and its kin) seek fine and are untouched. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_016Q4RATpafkwahrovHQLKRf
* errors: a save that could not happen, and two panics on an ordinary clickGabriel Schneider2026-09-03
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | A review of what this program does when the environment says no. The finding that reframes it: there were almost NO panics on ordinary paths — the rule already held — but there was a great deal of silence, and one case worse than any panic. SILENT DATA LOSS ON SAVE. `saveFile` marked the pane saved the moment it QUEUED the effect, before any host had tried; `host_io.writeFd` returned void, so a short or failed write was indistinguishable from a complete one; and `writeFileBytes` returned true regardless. A save to a read-only file, or into a directory removed under the pane, therefore cleared the tag's ` *` and posted nothing — and `Del` makes no dirty check, so the next click threw the edits away with the screen saying they were safe. On a full disk it was worse: the file is already `O_TRUNC`'d when `write` fails, so the message row said `saved` over a file that had just been emptied. Now: `writeFd` reports, `writeFileBytes` returns WHY (`PermissionDenied`, `NoSpaceLeft`, `ReadOnlyFilesystem`, …) including a failed `close`, which is where write-back filesystems report at all; the core marks the pane saved around `perform` rather than at emit, which is also where the bytes are read; and a host that could not write calls `Pardes.saveFailed`, which puts the reason on the message row and takes the clean mark back. That is a CALL and not a return value because host.zig enforces, at comptime, that a `push_` method reaching every host in a fan-out cannot have one answer — the first attempt at this changed the signature and the compiler was right to refuse it. TWO PANICS ON AN ORDINARY KEYSTROKE, in look.zig's number scans. `v = v * 10 + d` over caller-supplied digits, reached from `parsePathLine` and the `@pN` scan — which every Look, every right-click and every n/N motion runs on whatever word is under the pointer. A hash in a log, a CSV column, any output shaped `foo:99999999999999999999`, and the editor died with "integer overflow". Both saturate now, the same way acmefs.zig's address parser already did; a saturated line is refused by `file_pane.open`'s `line <= total` and a saturated pane id by `focusPaneLine`'s `id < MAX_PANES`, so nothing addressable changes. A BOOT FILE THAT WILL NOT OPEN joins the missing-name case in the `+Errors` pane instead of taking the launch down: `pardes /root` resolves as a `.file`, could not be read, and left `error: PermissionDenied` and a return trace. `look.readFile` now says which errno it was, so the pane can say "permission denied" rather than a word from the source code. The tag-marker test drained no effects and passed anyway, which is exactly the defect; it drains now. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_016Q4RATpafkwahrovHQLKRf
* lsp: a protocol client for every other language, narrated on the message rowGabriel Schneider2026-09-01
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | The seam grows a second backend: src/lsp/lsp_client.zig speaks JSON-RPC to child language servers — rust-analyzer, clangd, gopls, tsserver, pyright are rows in a spec table — while the in-process ZLS analyser keeps .zig. One reader thread per server owns the socket, routes responses to a mailbox under the conn mutex (monotonic condvar), answers server-to-client requests, feeds the diagnostics store, and narrates $/progress and state changes through a status sink both native shells post to the transient message row: "rust-analyzer: cargo check 88% 955/1083" lands where a save narrates, with the same clock. Chatty progress is throttled and deduplicated; settled states always land, which is also what makes the goldens deterministic. Nothing wedges and nothing healthy dies: waits are deadline-bounded, a timeout cancels and returns no rows, three consecutive timeouts restart the server ONLY while it is idle (an indexing server is narrating its own excuse), spawn and handshake failures back off 10s to 2min, a crash shortly after ready counts as a failure, and only a missing binary disables a spec. PARDES_LSP_{RS,C,GO,TS,PY} override binaries; empty disables; the snapshot harness pins RS to test/lspmock.zig and empties the rest. Mutating answers really mutate now: the @put record beside rename @edit carries per-range text, so = applies the formatter (both backends) and a same-file WorkspaceEdit rename applies atomically, one undo step, narrated ("renamed 2 range(s)"); a multi-file rename previews as rows instead of half-applying. Malformed responses fail closed: coordinates validated not clamped, one bad TextEdit poisons the whole edit set, poison frames kill the connection instead of buffering forever, decoded control bytes reject a uri, hierarchy items too deep to reserialize are skipped. Four kinds helix does not have, on SPC l: c/C incoming/outgoing calls (rows are call sites), t/T super/subtypes. Pull diagnostics (3.17) preferred when advertised. Help gains a language-keys footer for the motions no builtin row could carry; lsp.rel and look.grep now share one path-shortening rule. zig build lspprobe drives the seam from the CLI (comma-separated kinds share one server); measured against a 1083-crate workspace warm: gd 26ms, gr 213 rows 165ms, incoming calls 212 sites 197ms, document symbols 670 rows 347ms. docs/lsp.md tells the whole story; lsp-evaluation.md gets an addendum.
* acmefs: a pane's terminal gets pty/data, pty/ctl and pty/statusGabriel Schneider2026-08-27
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Step 3 of the 9P chain (docs/9p.typ 12.3, docs/registry.typ 9P-8). Nothing here is about 9P: it lands in the FUSE-served tree and any later transport inherits it. A script could write into a terminal that already existed and read its rendered scrollback. It could not START one, RESIZE one or SIGNAL one. Two of those were already effects the core emits, so `exec` and `winsize` are existing capabilities acquiring a name; only `sig` is new, and it brings the one new host method, `push_pty_signal`. pty/ctl winsize <cols> <rows> | sig INT|TERM|HUP|QUIT|KILL | exec one verb per line, validate-all then apply-all, EINVAL applies nothing -- `writeCtl`'s shape and `writeCtl`'s reason pty/status cols, rows, tty-taken as three %11d fields pty/data write is input to the process; read is the RAW output stream, gated on a reader count so a pane nobody reads costs one branch A pane that is not a terminal has no pty/ at all: the lookup is ENOENT and readdir does not list it. `PaneFile` is an enum(u4) and this takes it from 11 values to 15. ONE REMAINS. That is also why pty/ is a DIRECTORY and not three more flat names -- a subdirectory costs one value and buys its own namespace, so `ctl` and `data` did not have to be renamed. Two things the core does not know, and which are therefore not invented: a child's EXIT STATUS (a shell's death is `Event.eof`, which removes the pane, so there is no directory left to read it in) and RAW/COOKED (the core never sets a termios; the mode belongs to the program on the far side). Verified live against a daemon: pty/ appears only on the terminal pane; a `winsize 0 24` and a `sig SIGINT` are refused; a bad verb beside a good one applies neither; `echo pty-works` written to pty/data runs in the shell and its output reaches the body; and a blocking read of pty/data returns the raw stream, OSC 133 marks and all. fs-bench unchanged and still zero allocations.