summaryrefslogtreecommitdiff
path: root/src/File.zig
Commit message (Collapse)AuthorAge
* A +Pager keeps its program's colours: the session parses what /pager is ↵Gabriel Schneider20 hours
| | | | written after its directory with ghostty-vt, SGR becomes spans drawn over the plain text, every other escape is dropped, and PagerColor off pages it plain
* An unsaved pane restored from a dump that says nothing of its disk (no ↵Gabriel Schneider20 hours
| | | | | | | | | disk_hash, an older dump) asks at its first Save, as for a file changed on disk What its text was read against is unknown, so a Save could write over a newer file without a word; it now asks once first. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Restore reads a clean file from disk, and an unsaved one whose file changed ↵Gabriel Schneider22 hours
| | | | | | | | | | | | | | | | | | | | | since the Dump is said changed, its first Save asking before overwriting it A Restore put back the dump's copy of every file pane and armed the watch with the disk as it now was, so a file changed on disk after the Dump came back as its old text, marked clean, and the next Save wrote the old text over the new without a word. A clean file pane's text is now not in the dump: Restore reads its file (an older dump's copy is read past too, and used only when the file is gone). An unsaved pane keeps its text once, base64, the plain copy beside it dropped, with the hash of the file it was read against (`disk_hash`); a Restore that finds another there says the file changed on disk, and Save asks once before overwriting it, as for a change seen while it was open. file_watch.zig's own tests were never collected (no test root imported it): main.zig's test block does now, and the restored watch test says what a clean restore now reads. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* A +Pager pane whose text is a diff is drawn and looked at as a diff, as a ↵Gabriel Schneider29 hours
| | | | | | | | | | | | diff command pane is: git diff, show and log -p page into review With git paging into +Pager, a diff arrived as plain text: no colour for its sides and a right click on a hunk line searched the word. A +Pager pane now notes, as its text is set, whether its first 64 KiB holds a diff header or a unified hunk, and such a pane takes the diff highlighting and the diff look every diff pane has. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* A Restore brings back clean a pane whose file was never on disk, such as ↵Gabriel Schneider31 hours
| | | | | | | | | | | | Config's new init; only a file that existed when dumped is said deleted A restored pane whose path was not on disk was marked deleted on disk, so Config's not-yet-written init came back dirty and asked before Del, Exit or Restore. The dump now records whether the file was on disk (older dumps read as yes), and only one that was and is gone is said deleted. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* A selection deep in a long line costs a render what one at its start does: 5 ↵Gabriel Schneider31 hours
| | | | | | | | | | | | | | | | | | | | | | | | | MB in, /screen goes from 14.7 s to 5 ms Every render measured a selection's and the cursor's columns from the start of their line (lineDisplayOffset walked the whole prefix twice), so a selection near the end of a 5 MB line stalled each frame, 3 s at 1 MB in and 14.7 s at 5 MB. The cost grew with the offset, with Wrap on or off. Three changes make it independent of where the selection is: - display width adds (a tab is tab_width wherever it stands), so the offset between two columns is the width of the text between them, never the prefix; - the selection's rows are clamped to what is shown: a row wholly before the selection is skipped, a start before the row's first column starts there, and an end past its last column ends there; - a head or cursor whose column lies past its visible row (the last visible row of a line owns every column after its start) is not measured, since it is not drawn. Measured on a Debug build, 5 MB line: offset 10, 1 MB and 5 MB now take 11, 6 and 5 ms per /screen. A new perf gate case, deep-sel, renders with the selection 2 MB into a line: 83 us on Debug. The baselines were re-recorded for the new harness. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* A restored pane whose file is gone from disk comes back dirty, and Del asks ↵Gabriel Schneider31 hours
| | | | | | | | | | | | | | before throwing its text away A dump keeps a pane's text. Restored after its file was deleted, the pane came back clean: the watch's baseline read of the missing file failed quietly, and Del closed it without a word. Its text was then nowhere at all. Restore now checks the file: a local path with nothing on disk comes back as one deleted on disk, as the watch would have said. It says so once, logs `changed N deleted`, and is dirty, so Del, Exit and Restore ask first. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* A diff reads as the code it changes: hunks in their file's language, added ↵Gabriel Schneider31 hours
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | and removed rows tinted A .diff or .patch pane, and a command pane whose output is a diff (`git diff`, `git show`, `diff -u`; told by a `diff --git` line, or `---`/`+++` with a `@@` under them, in its first rows), colour each hunk's code in the language of the file its section names (`+++ b/<path>`, `--- a/<path>` for a deleted file), with tree-sitter, as highlightLocations does for results. src/diff.zig walks a unified diff by its `@@` counts, so a removed line that reads `--- x` is no header. Each side of a hunk is parsed as one text (context and removed lines the old side, context and added lines the new), so a string or comment across lines colours as it does in the file; a side no line takes its colours from is not parsed. A section's hunks share a parse, in pieces of about 40 lines (a hunk past 80 is cut), only those in view, and each piece's colours are kept by its bytes, so scrolling back and a terminal's every repaint parse nothing again. Added and removed lines carry a flag in their style byte; the painter tints their rows to the pane's edge, a little way from the page toward the theme's ANSI green or red, and draws the prefix in that hue pushed to read on it. A file in no known language keeps the old line colours. A command pane is read once its command has finished: its rows are copied once (File.DiffOutput, dropped when the pane runs again), so no frame dumps the scrollback, and painted over what git printed. A running command is shown as it prints. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Save makes the directories its file goes in, always: for the pane's own name ↵Gabriel Schneider31 hours
| | | | | | | | | | | | | | | | and Save path alike Whether a Save made missing directories depended on how the name had come about. Config's pane did, a name written into a missing directory did (since kkrqxtsp), and `Save path` and every other name did not, failing ENOENT instead. There is now one rule: a Save makes the parent directories of the file it writes, a pane's own or a terminal's scrollback included, and the make_dirs flag is gone. A directory it cannot make still fails the write with why, as fs.py's /nonexistent-pardes-dir case shows. fs.py now also saves into missing directories by Save path and by a written name. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* An append to a body through 9P costs its own bytes, not five passes over the ↵Gabriel Schneider31 hours
| | | | | | | | whole body: 60 KB appends go from 35.7 to 2.1 ms each Round 25 measured bulk body writes at about 8 ms a write. There is no frame wait in it: a profile of open, write, clunk in a loop found the flush of each close walking the whole body five times. dotOf, setDot and showOffset turned the cursor between offsets and rows by counting every newline from the top; setContent found the line index's changed span by comparing old and new byte for byte, and hashed the new text to see whether it was back to the saved one. The rows now come from the file's line index by binary search (checked against the counting at every offset), a splice tells setContent the span it changed, and the text is hashed only when its length is the saved text's. Measured over 9P on a Debug build: 60 KB appends 35.7 to 2.1 ms, 8 KB 5.3 to 0.8 ms, 1 KB 1.3 to 0.9 ms. What is left is two copies of the text an edit (the splice's and the undo snapshot's). The perf gate gains body-appends, 64 appends of 8 KB on the 50k-line file, and its three baselines are recorded again. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Config opens the startup file, DumpConfig the settings report, and the ↵Gabriel Schneider31 hours
| | | | | | | | | | | | | | | | | | | | | | | | | | workspace tag reads Dump Themes Config Debug Exit Config (SPC f c) opens init, found as the settings report finds it (opts.startup_config_path: $XDG_CONFIG_HOME/pardes/init or ~/.config/pardes/init, and the macOS and Windows paths config.md gives). A pane that already holds it is gone to instead; a file on disk is looked at; with none, an empty pane is named for it, and Save makes its directory and any above it first (File.make_dirs, fs.makeDirs) before writing. Without a configuration directory it says so and opens nothing. The report Config used to open, the startup path and every live setting, is DumpConfig, in a +DumpConfig pane. No alias keeps the old meaning. The workspace tag's default words put Themes where NextColor was, NextColor staying a builtin, and Config beside it. A tag the user edited is kept as it was: a dump stores only an edited one (topbar_custom), and a restore puts back only that. theme.snap types NextColor onto the workspace tag to click it, and tagnav's walk back to Dump takes five long-word steps. The rest of the re-recorded goldens differ in the workspace words and their widths only. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Terminal.zig is terminal.zig: a file of functions and no fields takes a ↵Gabriel Schneider31 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]>
* Mini.zig is mini.zig: a file of functions and no fields takes a namespace's ↵Gabriel Schneider31 hours
| | | | | | | | lowercase name The naming the split agreed on, deferred until now; panes.mini is its name at its importers. File.zig keeps its alias line (const Mini = panes.mini) so the file another agent is working in changes by that line alone. The served sources list and docs/design.typ name the new file. No behaviour changes; test/perf.zig's two references change, and since the perf harness is keyed by its own text, its three baselines are recorded again (under other agents' builds, so a little slower). Co-Authored-By: Claude Opus 5.5 <[email protected]>
* The grapheme width test expects two cells for two invalid bytes, as qsltkuom ↵Gabriel Schneider31 hours
| | | | | | | | draws them Its reference list compared \xe4\xb8 with vaxis's own gwidth, which counts the pair one cell; since qsltkuom each invalid byte is a cell, one U+FFFD each, as surface.zig draws it, so the pair is two. The unit suite failed on it. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* An invalid byte in a grapheme is one cell, and never reaches vaxis's widthGabriel Schneider31 hours
| | | | | | uucode joins a stray lead byte (as U+FFFD) to a following e and combining mark into one grapheme; vaxis's gwidth then counts back the replacement rune's three bytes over the one byte it stood for and overflows (gwidth.zig:62), panicking the next frame after a write of \xee e \xcc\x81 to xdata (the 9P monkey's crash-b2417c49). graphemeDisplayWidth now gives each invalid byte one cell, as surface.zig draws it, and measures the valid runs between alone. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* The theme's chrome is worked out once, not per grapheme; still post passes ↵Gabriel Schneider31 hours
| | | | | | | | | | | | | | | | | | | | | | | | | | | | let the GUI rest; a perf gate somtrmsz's contrast floors made ChromeTheme.fromTheme run its focus-tint and separator searches (pow calls each), and Output's RowDecoration.styleAt called it for every grapheme of every highlighted row: the 50k-line file's render went from 0.11 ms to 13 ms. Pardes.bodyChrome now keeps the theme's chrome, worked out again only when the theme differs; recolorSyntax asks for it once a decorated row, and a plain row (nearly every row of a file) never asks. ReleaseFast, medium fixture, median us, before -> after (main): render 13150 -> 76 (111), key-down 14658 -> 79 (110), wheel 13589 -> 76 (109), open 14891 -> 2095 (2055), edit-char 16703 -> 3006 (1251; the rest of that gap is editing and tree-sitter, not this). Post.animating asked for frames whenever the window had focus and any pass was ready, so a still pass kept the GUI drawing at the display's rate (Bloom: 49% of a core idle, in a hidden test window). A pass now says whether it moves on its own: the CRT (its hum and dither) and a Shadertoy file whose source reads iTime, iFrame or iDate (shader_build.readsTime; the flag rides the wire's post message); Bloom, Vignette and Grain are still. Bloom idle: 49% -> 1.2%, as with no pass. zig build perf-gate: the 50k-line file's gestures, each's fastest sample within 3x of the recorded baseline's (test/perf-baseline-<platform>- <optimize>.json, recorded from this build), run with every unit-test. On the regressed code it fails at 180x for render.
* get and a deleted file say what happened: the path, changed <serial> ↵Gabriel Schneider31 hours
| | | | | | | | | | | | | | deleted, reloaded get on a file gone from disk failed with a bare ENOENT; it now says get: <path>: no such file. A watched file deleted on disk was silent; the pane keeps its text and says <file> deleted on disk (Save writes it again) once, logged changed <serial> deleted (only a path the watch has read: a pane renamed to a name never on disk is not deleted). A get that throws a dirty buffer's state away logs changed <serial> reloaded, as the watch's reload does. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Refusals and records say more: get quotes its line, a bare answer lists the ↵Gabriel Schneider31 hours
| | | | | | | | | | | | choices, a clean reload is logged get's refusal now quotes the line as a builtin's (Save's) does; a bare answer while a question stands says what it takes (answer takes k j or -); and a clean buffer reloaded from disk logs changed <serial> reloaded, as the kept dirty case logs changed <serial>. (A failed Save already says why: Save <path>: <the error in words>.) Co-Authored-By: Claude Opus 5.5 <[email protected]>
* A file changed on disk never reloads over unsaved editsGabriel Schneider31 hours
| | | | | | | | | | | | | | | A watched file rewritten on disk was loaded over a buffer's unsaved edits and its dirty flag cleared, so Exit and Del no longer asked about them. A clean buffer still reloads; one with edits keeps its text and stays dirty, says <file> changed on disk (get reloads it, Save overwrites it), logs changed <serial>, and its Save warns once before overwriting the newer file, as acme's Put refuses a file modified since read (exec.c:577); get asks as ever (winclean, exec.c:513). The restored-watch test's dirty case now keeps the snapshot, and the filewatch snapshot script (re-recorded by name) checks both halves: the edited pane keeps its edit and says so, the clean one reloads. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* A pane restored with unsaved text is asked about before it is thrown awayGabriel Schneider31 hours
| | | | | | | | | Exit, Restore, Del and get warn only about a pane edited since it was opened, which its saved hash says (set at its first edit). A pane a Dump kept dirty came back with no hash, so a restored session quit at once over it. Restoring one dirty now gives it a hash its text does not match. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Restore keeps each pane's dot, as acme's dump doesGabriel Schneider31 hours
| | | | | | | | | | A restored text pane came back with its caret at its view's top, whatever had been selected. The dump now keeps each text pane's dot, q0 and q1 as acme's keeps a window's, and Restore puts it back without moving the view it kept. Two look-walk tests that restored and walked on now start from the match they were on. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Text brought back to what was saved is clean againGabriel Schneider31 hours
| | | | | | dirty compared revisions only, so undoing to the save point, or typing the text back, left the pane dirty. The saved text's hash is kept from the first edit after a save or load, and an edit that makes the text equal to it marks the pane saved again, as undo to the save point does in acme. Co-Authored-By: Claude Opus 5.5 <[email protected]>
*-. Trial merge: 9P + helix + renderGabriel Schneider31 hours
|\ \
| | * Paint into a target Surface instead of swapping the frame's outGabriel Schneider31 hours
| | | | | | | | | | | | | | | | | | | | | | | | | | | The painters of a pane's body and tag (renderBody and its selection, gutter, syntax, ANSI and wrap-marker passes; paintPaneTag; paintHeaders; renderPane; the image and PDF grid paths) take the Surface they paint as a parameter. The body layer and the tag layers paint straight into their own temporary surfaces, and the std.mem.swap of p.surface around those calls is gone. No behaviour change.
| * | Alt-o, Alt-i and the other tree-sitter keys walk the file's syntax treeGabriel Schneider31 hours
|/ / | | | | | | | | | | | | | | | | | | | | | | | | | | helix's expand/shrink, previous/next sibling, all siblings, all children and parent-node end/start (Alt-o/up, Alt-i/down, Alt-p/left, Alt-right, Alt-a, Alt-I, Alt-e, Alt-b; Alt-n stays the new terminal) walk a whole-file parse the file keeps (File.node_tree), made on the first such key after an edit and reused until the next, never per key. syntax.walkNodes follows helix's TreeCursor over every node, anonymous ones included, and Alt-i goes back to what Alt-o grew from while the selection still holds it. The harness runs without grammars, so the cases prove the plain-text no-op; a test replays eleven JSON walks whose results were taken from the installed hx. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* / A terminal bound as a language's REPL takes the execs made on that ↵Gabriel Schneider31 hours
|/ | | | | | | | | | | | | | | | | | | | | | 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]>
* Have Text.pane return nothing for a header instead of trappingGabriel Schneider31 hours
| | | | | | | | | A column's or the workspace's tag has no pane, and asking one for it hit unreachable; every caller guarded against that by checking `what` first. An optional makes each caller say what it does without a pane, so a forgotten guard no longer compiles into a crash. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Let a pane's tag take a row for each line it holdsGabriel Schneider31 hours
| | | | | | | | | | | 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 Schneider31 hours
| | | | | | | | | | | | | 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 Schneider31 hours
| | | | | | | | | | | | | | 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 Schneider31 hours
| | | | | | | | | | | 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 Schneider31 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]>
* Move the File namespace out of panes.zig into File.zigGabriel Schneider31 hours
Pure move: the body of `pub const File = struct { ... }` is now the file File.zig, dedented, with the imports it uses; panes.zig keeps `pub const File = @import("File.zig");`, so `panes.File.x` callers and the `File.State` field of Pane are unchanged. File.zig joins the embedded source list. Co-Authored-By: Claude Opus 5.5 <[email protected]>