| Commit message (Collapse) | Author | Age |
| ... | |
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Pure move, no behaviour change: handleMouse with dragUpdate, dragRelease,
seamIdxAt, reportGesture, gestureRange, dispatchPointerBuiltin,
mirrorTtySelection and chordCutPaste, the pointer hit helpers
(bodyHitForPane, sameTagCell, tagColumn, sameBodyCell), chromeTarget with
ChromeTarget, paneAt, the Drag state type with clampBorderCol/Row, the seam
test fixtures and thirteen pointer and drag tests go verbatim to mouse.zig.
The methods become free functions taking `p: *Pardes`; their 36 call sites
change from `p.handleMouse(..)` to `mouse.handleMouse(p, ..)`, including
web.zig's chromeTarget and macos.zig's paneAt. The Mouse event type stays
in pardes.zig with the rest of the event vocabulary.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Pure move, no behaviour change: renderBody, renderBodyLayer,
paintPointerSelection, paintSourceSelection, paintTerminalSelection and
bodyText go verbatim to the end of body_layer.zig, which already held the
body's layer geometry and hit-testing, so the pane body as the surface shows
it is one file. The file doc now says so.
The methods become free functions taking `p: *Pardes`; their 16 call sites
(render and renderPane, the tag painter, selection and dump code) change
from `p.renderBody(..)` to `body_layer.renderBody(p, ..)`. pardes.zig gains
`pub const body_layer = @import("body_layer.zig");`; pointerSourceLine,
selRegexArmed and cellBounds become pub because the moved code calls them.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Pure move, no behaviour change: the Message stamp helpers, LoggedMessage,
setStatus, setMessage, showMessage, dismissMessage, dismissLine,
advanceMessages, advanceLine, messagesAnimating, MessageMotion,
messageFrames, messageMotion, noticeText, noticeLife, blendRgb,
logMessage, messageLog, reportError, the notice painters (leaderText,
noticeCols, Printed, printRight), collectNotices, and the six message
tests go verbatim to Messages.zig.
The methods become free functions taking `p: *Pardes`. setStatus,
setMessage and reportError are called from ~170 places as `p.setMessage(..)`,
so Pardes keeps three declaration aliases (`pub const setMessage =
Messages.setMessage;`) and those call sites stay as they are; every other
call changes from `p.x(..)` to `Messages.x(p, ..)` (46 of them). The five
shells' `pardes.Pardes.Message` become `pardes.Messages.Message`. The
message ring's fields stay on Pardes for now; moving them into Messages is
a separate change.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Pure move, no behaviour change: submitPipe, pipeRequest, pipeFailed,
pipeCut, pipeOutput and pipeResponse, the PendingPipe they share,
pipeMarker, and the eight pipe tests with their nextPipeEffect helper go
verbatim to the end of selection_pipe.zig, so the whole of `|` (runner,
boundary values, prompt, request and atomic edit) is one file. Inside that
file the `selection_pipe.` prefix drops; the file doc now says it holds both
halves.
The methods become free functions taking `p: *Pardes`: the four shells'
`core.pipeRequest(id)` become `selection_pipe.pipeRequest(core, id)`, and
pardes.zig's two calls change the same way. pushUndo becomes pub because
the pipe's edit calls it; PendingPipe.deinit becomes pub for Pardes.deinit.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
| |
Pure move: Color, FontRole, CellStyle, Cell, AsciiDiff, PanelCellDiff,
ImageCacheKey, NativePlacement, ImagePatch, ImagePlace and Surface, with
their eight tests, go verbatim to the new surface.zig with the imports they
use. pardes.zig keeps one `pub const X = @import("surface.zig").X;` line
per type, as it already does for Pane, Rect and the tag layers, so the
shells' `pardes.Cell` / `pardes.Surface` spellings do not change.
Surface.print, clearRect and overlayDash become pub because render calls
them from pardes.zig. surface.zig is added to pardes.zig's test block and
to the embedded source list.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
| |
Pure move: the body of `pub const Pdf = struct { ... }` is now the file
pdf_view.zig, dedented, with the imports it uses; panes.zig keeps
`pub const Pdf = @import("pdf_view.zig");`, so `panes.Pdf.x` callers are
unchanged. The file is not Pdf.zig because pdf.zig (the MuPDF module) sits
beside it and the two names are one file on a case-insensitive disk.
pdf_view.zig joins the embedded source list.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
| |
Pure move: the body of `pub const Terminal = struct { ... }` is now the file
Terminal.zig, dedented, with the imports it uses; panes.zig keeps
`pub const Terminal = @import("Terminal.zig");`, so `panes.Terminal.x`
callers and Pane's `Terminal.State` field are unchanged. Terminal.zig
joins the embedded source list.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
| |
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]>
|
| |
|
|
|
|
|
|
|
|
|
| |
Pure move: the body of `pub const Output = struct { ... }` is now the file
Output.zig, dedented, with the imports it uses; panes.zig keeps
`pub const Output = @import("Output.zig");`, so `panes.Output.x` callers
are unchanged. decorateRow and RowDecoration.styleAt become pub because
File calls them from another file now. Output.zig joins the embedded
source list.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
| |
Pure move: the body of `pub const Image = struct { ... }` now sits at the
end of image.zig, dedented, under a "the image pane" banner, so the image
thing (decoding, glyph art, and the pane that shows it) is one file. Its
nine `image.X` self-references lose the prefix; image.zig gains the four
imports the pane code uses. panes.zig keeps `pub const Image =
@import("image.zig");`, so `panes.Image.x` callers are unchanged.
image.zig joins the embedded source list, since the pane code left
panes.zig which was on it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
| |
Pure move: the body of `pub const Mini = struct { ... }` is now the file
Mini.zig, dedented, with the imports it uses; panes.zig keeps
`pub const Mini = @import("Mini.zig");` so every `panes.Mini.x` caller is
unchanged. Output.resetBody becomes pub because Mini calls it from another
file now. Mini.zig joins the embedded source list in fs.zig.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
shut down
The held read is now kept by the Ticket cloud9's Conn.hold() gives its
park and answered through Conn.answerWith(), which makes the answer only
while that very park still waits, instead of walking the engine's slots
by tag; pardes no longer reaches into the engine for it. A second read on
an open whose read is held fails with file in use rather than sitting
parked where nothing answers it, and a read that waits with no open
record to hold it is logged and asserted on. A read on an event or
pty/data open whose pane closed answers acme's "window shut down"
(editors/acme/xfid.c:1005). The pane keeps its run's and its lock's open
handles, checked through openOf on use, not record indices. Docs: lock
from a shell needs a held fd, and a command that clears the screen may
read as cut.
Needs cloud9 zvuqvnzy (cca47d63), which adds Conn.hold, waiting and
answerWith; build.zig.zon still pins 82d8152c until that is pushed and
re-pinned.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
| |
A clear or reset while the command ran, a start or end mark that came on
the alternate screen, or a garbage end pin read as a complete answer;
now any of them, like a start that scrolled out, answers exit N cut.
The output is read from at most output_rows rows above its end instead
of the whole history on the editor's thread (cut if that clips it), a D
with no status answers exit ? rather than exit 0, and running out of
memory answers error out of memory rather than cut.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
| |
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]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
| |
Snapshots (and /log's cursor), runs, and the reader_handle constants for
event and pty/data each reused the open handle and each validated
handle and node on its own. Now p.fs.opens is one table of 64 records,
each the node it was opened on and a tagged union of what it holds, like
lib9p's per-fid aux and acme's Fid (editors/acme/dat.h:373-385): one
lookup (openOf), one release, ENFILE when full. A held read lives in
its open's record, so it goes with the release. Opens that hold nothing
answer handle 0 and take no record.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
| |
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]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
| |
A following log, event, pty/data or a pty/run before its answer used to
answer .again and wait for a wakeAll, which only the parked-write path
asked for, so the band-aid had every queue push set turn.parked. Now the
core keeps such a read (ctlfs.hold) and, as the turn is given up after
anything that queued a record, ran a command out or closed a pane,
answers it on its own connection, the way factotum answers the log reads
it keeps and acme an event read. Only a read the engine still holds
parked is answered, because cloud9 tells the backend nothing of a
Tflush, so a flushed read spends no record.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
/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]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
/log is now one ring (64 KiB, 4 KiB on the board) that records new, del,
rename and save, and a msg line for everything the editor says. An open
freezes it, so cat log shows what happened lately and ends; writing
follow to that same open makes reads past it wait for newer records, and
a follower the ring outran reads lost N first.
The message log keeps the pane's serial, not its reusable slot.
A test now fails when the tree serves a file /README never mentions; it
caught pty/status, now documented along with typing through pty/data.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
| |
|
|
|
|
|
|
| |
bash and fish now print OSC 133;D;<status>, which ghostty already parses;
the planned /pane/<n>/run file reads it to answer when a command finished
and how.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
| |
the pane a directional Del grows
A pane that joins a column whose only other pane is an untouched empty
scratch (Newcol's, or the stand-in a closed column leaves) takes the column
whole. Checked at the end of the step, since the pane that asked for the
joiner is often the placeholder itself.
Del k|j focuses the pane that took the rows.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
| |
|
|
|
|
|
|
| |
The five arena buffers are zeroed globals instead of undefined fields, so a
Debug build keeps them in .bss rather than writing 160 MB of 0xAA filler
into every binary (Debug pardes 839 MB -> 246 MB).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Any new glyph re-uploaded the whole 2048x2048 atlas: 4 MB copied into the
transfer buffer and then to the texture, about half a millisecond of CPU on
the frame, and the GPU's copy on top. Glyphs fill the atlas pen-style, a band
of rows at a time, so a frame's new glyphs sit in a band a cell or two tall.
The atlas now keeps the rows drawn into since the last upload (cachedGlyph
widens them by each raster's rows: plain glyphs, ligature strips, tagline and
grip glyphs alike), and uploadAtlas copies and uploads just those rows, at the
same offset in the transfer buffer as in the stage; the texture keeps the
rest. A reset (a font, size or tagline change) marks every row, as
the texture's first upload does, so those still send the whole atlas. A test
replays uploads over glyphs of every kind and a reset, and checks that no row
outside the dirty rows ever differs from what the texture holds.
Hidden captures of 27 layouts, including new glyphs typed and printed, font
and size switches, and Ligatures off and on, are byte-identical to before.
Over 10 interleaved rounds (160x50 cells), a frame that draws new glyphs
spends 12-50 us uploading instead of about 0.5 ms: a terminal printing twelve
new glyphs a line goes from 1.04 to 0.55 ms of renderFrame CPU on such frames
(-34% over the scenario's frames, 9 of 10 rounds faster, -20% to the GPU
fence), a typed glyph's frame from 0.89 to 0.38 ms, and opening a file of 600
new glyphs from 9.1 to 8.7 ms. The first frame and a font size change still
upload everything and cost what they did; other frames are unchanged.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
for each cell
renderFrame skips the grid cells a body or tag layer paints itself, and it
asked which those are cell by cell: for every cell, twice a frame (counting
instances, then emitting them), bodyLayerAt and tagLayerIn walked all 16 body
layers and all 119 tag layers. In a ReleaseFast profile of a full screen of
src/pardes.zig those walks were about a third of the gui's CPU samples.
coverLayers now fills each live layer's rectangle into a per-cell map once a
frame, and the loops read one byte. The loops only ever asked whether some
layer covers a cell, never which, so overlap order does not enter into it; the
map marks exactly the cells the walks found: a tag layer only its viewport's
first row, and a viewport past the grid only the grid's part of it. The loops
over the layers a panel transition leaves get a second map, built only while
one runs.
A test holds the map to the old walk over a core's layouts (columns, stacked
and collapsed panes, column tags, Tagbottom, message and leader chips) and
3000 random layer sets, some past the grid's edges or at the top of u16.
Hidden captures of 27 layouts (message and prompt chips, a column grip rail,
a PDF, an image, font and tagline changes), with and without leftover edge
pixels, are byte-identical to before.
Over 10 interleaved rounds (160x50 cells), renderFrame's CPU a frame falls
from about 2.0 ms to 0.32-0.37 ms idle, scrolling, typing, under terminal
spew and over a PDF (-82 to -84%, every round), and cell emission from about
1.07 ms to 0.27 ms. The first frame's CPU goes from 6.2 to 4.6 ms.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The SDL shell shapes words with HarfBuzz, so a font's `->` and `!=` draw as
ligatures, and there was no way to have the plain glyphs back short of
changing font. `Ligatures` is a toggle, on by default; off, no cell goes to
the shaper and every cell draws its own glyph, exactly as a font without
ligatures does.
It exists only where it means something. A new `ligatures` capability, true
for the gui shell alone, gates it like Font and WindowOpacity are gated: it is
not a builtin elsewhere, has no leader path, and Config does not list it,
rather than print a row the TTY could never change. macOS draws CoreText
ligatures of its own, but nothing there reads the setting, so it stays off
there. The table check that kept every toggle unconditional now lets the
ligatures toggle, and only it, carry a capability, and requires that it
carry `ligatures`; every other setting's rule is as it was.
The gui keeps the setting beside its text caches, which were resolved under
it: when the core's value changes, the per-codepoint cells (which record
whether a cell is shaped) and the shaped words are dropped, and the frame the
toggle asked for draws every cell again. The atlas keeps its glyphs: plain
ones draw either way, and a ligature's strip is reused when it comes back.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
| |
The GUI's ligature tests shape `->` and `==` with Maple Mono NF, the only
ligature font in assets/, but the font was ignored, so a checkout without a
private copy skipped the tests or ran them without their Maple half, and
passed. The font is SIL OFL 1.1, whose licence is already tracked beside it,
so it is committed and the tests now fail when it is missing instead of
quietly passing.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
build too
The tty, gui and detached shells allocate from std.start's gpa, which is a
DebugAllocator in Debug and, because we link libc, libc's malloc otherwise.
The macOS shell hardcoded std.heap.smp_allocator in every mode, so its Debug
build checked nothing it allocated outside memory.zig's subsystems.
Which allocator a release build should use was measured rather than
assumed, in ReleaseFast, with an experimental gui that chose at startup
between glibc's malloc and smp_allocator, with and without memory.zig's
stack-fallback buffers, plus an A/A pair; ten interleaved rounds, compared
round by round. The gui frame-cost harness (idle, scroll and typing in
src/pardes.zig, terminal spew, wheel-scrolling docs/design.pdf, first paint)
saw every candidate within the A/A pair's noise. Twelve rounds of the
allocation-heavy paths themselves, highlighting all of src/pardes.zig (305k
tree-sitter allocations) and pardes-pdf-bench's MuPDF renders, split them:
smp_allocator was 2 to 6% slower on MuPDF's page-sized rasters (slower in 9
to 11 of 12 rounds), no better on tree-sitter, and the stack-fallback buffers
changed nothing either way. So glibc's malloc it is, with the buffers kept.
macos.zig now takes a DebugAllocator in Debug, deinitialized in
pardes_deinit with leaks logged the way std.start treats the others', and
libc's malloc otherwise. main.zig says why init.gpa is kept.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
In a Debug build memory.zig put each subsystem allocator (core, frame, lsp,
tree-sitter, image, PDF) under a DebugAllocator of its own, which is what
reports that subsystem's leaks and makes memory.deinit panic on one. Beneath
it, past the fixed buffer, was the allocator the shell passed in: init.gpa,
which std.process.Init already makes a DebugAllocator in Debug. Every block
that spilled out of a fixed buffer was tracked, checked and given stack
traces twice, and in a test both layers sat on std.testing.allocator.
A Debug build now puts the page allocator beneath the subsystem
DebugAllocators, so each allocation passes through exactly one. The shells'
own gpa is still std.process.Init's DebugAllocator for everything else, and
a release build is unchanged: the subsystems sit on the gpa, no
DebugAllocator anywhere.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
the way out
SDL allocated with libc malloc. runNative now installs the shared C heap over
the gui's gpa with SDL_SetMemoryFunctions before SDL's first call: SDL frees
with whichever functions are current, so none of its memory may predate
them. SDL allocates from its own threads too, and the gpa is thread-safe in
every build (a DebugAllocator in Debug, libc's malloc in release).
That put SDL in a Debug build's leak report, and the report filled up: 1769
blocks at exit, because runNative never released its GPU objects, its window
or SDL itself. The process was about to exit, but a report that long hides
any leak that matters. runNative now releases every pipeline, texture,
sampler and buffer the Gui holds, closes the gamepad, destroys the GPU device
and the window, and calls SDL_Quit, all through defers. Eight 32-byte blocks
are left, and they are SDL's: 3.4.4's VULKAN_INTERNAL_DestroyCommandPool
never frees two arrays per command buffer (buffersUsedInPendingTransfers and
texturesUsedInPendingTransfers), which SDL's main branch now frees. They go
away with the next SDL; a comment at the SDL_Quit says so.
The frame-cost harness and a software-present run (PARDES_SOFT_PRESENT) both
run and exit cleanly in Debug.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
runtime
syntax.zig points the tree-sitter runtime at a Zig allocator, but the
grammars' external scanners did not follow it. A grammar's
tree_sitter/alloc.h maps ts_malloc to the runtime's current allocator only
when TREE_SITTER_REUSE_ALLOCATOR is defined, and plain malloc otherwise, so
every scanner's state and its arrays came from libc. Five scanners (bash,
markdown, markdown_inline, python, typst) also call malloc, calloc, realloc
and free by name.
build.zig now compiles every grammar with TREE_SITTER_REUSE_ALLOCATOR and
forces in src/tree_sitter_heap.h, which includes stdlib.h and then defines
the four names as calls through ts_current_malloc and friends. A header
forced in from the build rather than a patch to the grammars keeps zig-pkg
pristine. No grammar archive refers to libc's allocator any more; only the
runtime does, for its defaults. A scanner frees only what it allocated
itself, and the syntax tests that create and destroy a parser for every
grammar under a leak-checking allocator pass.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
C's malloc, calloc, realloc and free over a Zig allocator were written three
times: syntax.zig's for tree-sitter, pdf.zig's for MuPDF, and gui.zig's for
FreeType and HarfBuzz. They are now one, src/c_heap.zig's Heap. Each hook
names the allocator variable its heap reads; gui.zig exports its heap under
the ui_malloc names font.c and HarfBuzz call. It is a module of its own
because pdf.zig is the MuPDF module, and a file cannot belong to that module
and the core's at once.
A block starts with a 16-byte header holding its size and the allocator that
made it, so it goes back where it came from even after the heap is repointed,
as the fonts' copy did. The fonts' copy carried the 16-byte Allocator itself
in a 32-byte header; here the header holds the allocator's index in a table of
every allocator a heap has used (entries are published once and never change,
and a heap remembers where its current one was last found). The difference is
measurable: with the 32-byte header, pardes-pdf-bench's filtered page render
at 96 dpi took 1.1% longer than before this change, slower in 15 of 16 pinned
rounds in two separate runs, and highlighting all of src/pardes.zig (305k
tree-sitter blocks) up to 0.6% longer, against an A/A pair within 0.7%. With
the 16-byte header both are back within the A/A pair's noise.
Two other things change. For tree-sitter and MuPDF, a block made before the
allocator was repointed now goes back to the one that made it rather than
through the current one. For the fonts, realloc(block, 0) now frees and
returns null, as glibc's does and as the other two copies already did;
neither FreeType nor HarfBuzz asks for it. The heap's test replaces the
tests of the old copies.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
first
A message fell in on t*t, an ease-in: it hung near its start for the first
frames and was still moving at its fastest when it hit the row, a sixth of a
row in its last frame. It now decelerates into place with an ease-out cubic,
slide = -(1-t)^3: a quarter of the way in the first frame, and the last
frames only settle it. There is no overshoot. At a row's height and the
dozen frames of the 180 ms fall, a follow-through is a pixel or two, which
reads as a jitter rather than a bounce. MessageFall keeps its 180 ms: the
ease-out puts the chip where it is going within about 100 ms.
The colour leads the motion: alpha = 1 - (1 - min(1, 2t))^2, whole by half
way, so what lands is already legible. The SDL shell used to show the chip
solid while it slid and fade it only on the way out; it now fades it up with
the same alpha, and the rule under a chip carries the chip's fade (a new
TagLayer.fade) instead of standing solid while the chip comes or goes.
The dissolve was an ease-out, (1-t)^2: it lost a tenth of its colour in the
first frame and spent its last frames nearly invisible. An exit eases in
instead, alpha = 1 - t^2 over the same 300 ms: it lets go gently and leaves
faster and faster. t is now counted to the last frame, so that frame shows
it gone rather than it blinking out from 0.3%. 1 - t^3 was the other
candidate; in captures it held the chip nearly untouched for half the
dissolve after the linger had already held it, then dropped a sixth of its
colour a frame, which reads as a pop. It stays in place: the lines stacked
around it do not move, and a drift would read as it leaving its row.
The test pins the shapes: a fast first step into the row, never past it,
solid by half way; a dissolve whose first drop is smaller than its last and
which ends at exactly nothing.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Del takes a side: `Del k` gives the closed pane's rows to the nearest
expanded pane above it, `Del j` to the one below, each falling back to the
other side when it has none. DelAbove and DelBelow are those two lines, with
no path of their own under SPC.
A bare Del started from a key -- SPC d, Enter on the tag word, a row run from
an output buffer -- on a pane with expanded panes both above and below asks
instead of guessing. The question is a prompt like Save's or a search's
(Pane.Prompt.del_side), so it is painted on the pane's notice band by the
same path, and the next key answers it before any mode sees it: k or Up,
j or Down, anything else keeps the pane, as does a click. Only a key press
sets Pardes.can_ask, so a click, a 9P ctl or event write, a startup line, a
restore and a shell exiting all close the pane at once, the rows going
where layout.absorbVWeight has always sent them. A collapsed pane is not
asked about (it has only a tag row to give), and collapsed neighbours are
passed over (layout.expandedNeighbor, which Collapse now uses too).
removePane and absorbVWeight take the recipient; every other caller passes
null. Three scripts that closed a middle pane with SPC d answer k, which
is where the rows went before, and their goldens are unchanged. delask.snap
covers the question, Esc, j, a clicked DelBelow and a clicked Del.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
A notice chip -- Msg output, an older message, the leader chord, a prompt --
is a piece of tagline hung over the body's top rows, but in the SDL shell it
ended in a bare edge while the tagline above it ends in a rule. It now has
the tagline's rule along its bottom: the chrome border colour, one pixel,
running on to the window edge when the chip does. A chip still falling into
place brings its rule with it, cut at its row's top like the chip.
The macOS shell already rules every non-workspace tag layer, notices
included; a terminal draws no rule under its tagline, so its chips get none
either.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
A builtin that asks for input (Save's path, a search, a pipe) shows its
prompt as a notice chip, like Msg output and the leader chord. Messages and
the chord went through a tag layer, which is what gives a chip the tagline's
height, pitch and band offset; the prompt alone stayed on the grid so its
caret could sit on a cell. A pixel shell therefore drew its tagline glyphs
one to a BODY cell, spaced out like a banner, while the chips around it were
set tight.
The prompt is now a band like the others and carries its caret on the layer,
at the same place in the text the grid pass puts it. The grid still paints
it, for terminal clients. What a band says comes from one place,
noticeText, which the grid pass and the layer pass both read, and what a
prompt says from one place, Pane.promptText, which collectNotices, the
painters and submitSearch share.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
shows it
The column grip worked in the GUI but showed nothing anyone could see.
Driving the real SDL window with pixel mouse events, press, drag and
release all reach the core and reorder or resize the column; the feedback
is what failed. The grip kept its muted color while held, and a reorder's
insertion rail was drawn in the gutter cell at the destination edge. The
GUI paints that cell as the column's scroll rail, and at the window's left
edge, the common case of moving the right column first, the dashes were
lost in it. From the leftmost column, a drag short of a neighbour's middle
shows no rail, rightly, since nothing would move, but nothing said the
grip was even held.
A held grip now lights in column_box, the accent its muted color is mixed
from, and the rail takes that color. The rail runs in the seam cell left of
where the column's rule will land, where a border drag's rail runs, for a
reorder and a left-edge move alike; at the window's left edge, which has no
seam, it runs just past the scroll rail, leaving column 0's grip and pane
boxes whole. A carried column's pointer aims at a place, not a word, so the
column tags no longer light the word under it, and a release snaps the
layout as a border drag's does rather than animating it. The preview and the release both
ask layout.columnDrop where the column goes, so the rail stands where the
release puts the edge, and the drag no longer carries its unused column
index.
The GUI test feed takes ESC]777;mouse;<down|up|motion>;<button>;<x>;<y> in
window pixels and dispatches it as an SDL event. test/column_grip.py uses
it to drive the grip through the same pixel-to-cell and tag hit path as a
hand on the mouse, and checks the grip, the rail and the result against
GPU captures and the 9P grid.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
FreeType and HarfBuzz allocated with libc malloc, out of sight of the Zig
allocator the rest of the gui uses, so its Debug leak report and a test's
std.testing.allocator never saw them. Now every allocation the font code
makes goes through ui_malloc, ui_calloc, ui_realloc and ui_free, exported
from gui.zig over `font_allocator`:
- HarfBuzz is compiled with hb_malloc_impl and friends pointing at them.
- Each UIFont gives its FreeType library its own memory manager
(FT_New_Library with an FT_MemoryRec_ over the same functions, then
FT_Add_Default_Modules and FT_Set_Default_Properties: exactly what
FT_Init_FreeType does over its malloc manager), torn down with
FT_Done_Library.
- The shim's own UIFont and scratch arrays use them too.
C's free and realloc pass no size, so a block starts with a 32-byte header
(the caller's pointer stays 16-byte, max_align_t, aligned) holding its size
and the allocator that made it, so a block goes back where it came from
even after font_allocator is repointed. runNative points font_allocator at
the gui's gpa. HarfBuzz makes a few process-wide objects on first use and
never frees them (the default Unicode functions, the locale's language,
hb-ft's font functions); ui_font_prime makes them from the default heap
before that, so a Debug build's leak report stays about fonts. Tests do the
same: the shaping tests, and a new test that creates, rasterizes and shapes
fonts under std.testing.allocator, report any leak. Only the render thread
calls into fonts, and every allocator used is thread-safe.
It costs nothing measurable: against the previous change, 10 interleaved
rounds with an A/A pair show every scenario as fast or faster with Adwaita
Mono and Maple Mono alike, and first paint over 30 launches each matches
main.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
their cells
The SDL shell rasterized every cell by its own codepoint, so a font's `->`,
`!=` or `<=` ligature never appeared. Text is now shaped with HarfBuzz 11.0.0,
built from source like FreeType: a lazy build.zig.zon dependency (the tarball
Ghostty pins) compiled only for the gui shell, its FreeType integration
linked against the freetype package, so the binary carries no system
HarfBuzz or FreeType and a tty build never compiles it.
Shaping turns on only the font's ligature features (liga, calt, clig, rlig,
rclt); composition, local forms and fractions stay off, so a cell draws either
its nominal glyph, as it always did, or part of a ligature. A glyph no lookup
of those features covers cannot change, so its cell is resolved alone, and
that resolution is cached by codepoint: in a font without ligatures (Adwaita
Mono) no cell is ever shaped, and the pipeline draws pixel for pixel as
before.
A cell whose glyph a ligature could replace is shaped with its word: the
cells between spaces that share a decoration and a role. A word ends either
side of the cursor, block or bar, so the cell being edited shows its own
character (Ghostty's rule), and inside f|i, f|l and s|t, whose typographic
ligatures do not belong on a grid (Ghostty again). Colours never end a word:
a ligature spans a syntax colour change or a selection edge. A codepoint the
primary font lacks shapes in the fallback face that has it. Taglines are not
shaped: they are drawn at two pitches, and a strip would not line up in one.
HarfBuzz only chooses glyphs; the grid places them. A substituted glyph whose
ink reaches over neighbouring cells that draw nothing of their own (the
spacer glyphs of Fira Code-style fonts such as Maple Mono, or the cells a
merged ligature cluster swallowed) claims them: it is rasterized once as a
strip that many cells wide, and each cell samples its own slice, so
selection, the cursor and per-cell colours work unchanged. Anything the grid
cannot place (several glyphs in one cell, a positioned mark) keeps its
nominal glyphs.
A frame must not pay for this. A shaped word's atlas slots are cached by its
text (checked against the stored text, not just its hash), so a frame costs
one lookup per word and a compare for the rest of the word being walked.
The atlas is keyed by (face, glyph, role, decoration, lead, span), packed into
64 bits so a lookup hashes one word; ASCII glyph ids are a table, and a
face's HarfBuzz state is made when it is first asked about a glyph. Measured
with the latency trace, whose F line now also carries the submit time (CPU =
submit - present entry), in 10 interleaved rounds against main with an A/A
pair, at 160x50 cells: with Adwaita Mono every scenario (idle, scrolling,
typing, terminal output) is as fast or 1-3% faster, and the first frame and
time to first paint are unchanged. With Maple Mono, ligatures cost the first
frame about 0.8 ms (HarfBuzz setup and shaping the screen) and scrolling new
text about 1-2%.
With it the codepoint raster path is gone: grips look up their glyph and
stay centered, since a grip is not text. ui_font_scale_for_height, an
identity kept as a "size token", is removed (px is the size everywhere),
ui_font_has_glyph becomes ui_font_glyph, and the space slot is simply the
first, cleared cell of the atlas rather than a rasterized space. CellStyle.ul
gets an explicit u3 tag so a decoration fits in the packed keys.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
stb_truetype sources
Bold in the SDL shell was a smear of the finished glyph: every pixel took
the larger of itself and three quarters of its left neighbour. That added a
grey fringe on the right of each stem instead of a thicker stem, so bold read
as blurred regular text, and at large sizes it barely read as bold at all.
The rasterizer now does what Ghostty's FreeType face does: it loads the
hinted glyph, thickens the outline with FT_Outline_Embolden, and renders
that. The stems widen as one shape with hinted edges, and the advance is
untouched, so the cell grid, caret and selection do not move. The strength
is Ghostty's heuristic, 1/32 of the line height rounded up (one pixel at a
32 px line). A bitmap strike has no outline; it is widened by one whole
pixel with FT_Bitmap_Embolden, the classic overstrike. Underline and
strikethrough are still drawn into the cached cell mask (decorateLine).
vendor/stb (stb_truetype.h and its implementation unit) was left over from
the rasterizer FreeType replaced; nothing built or referenced it.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
resize columns from their grip
Every tag band (workspace, column, pane) is centred in its row, so text sits
at one baseline offset. Anchors are inset from the column rule by that same
margin, so they are square and the band frames them on the left, top and bottom;
the pane mark moves to stay centred. When the window is not a whole number of
cells, the bands, rules, spines and scroll thumbs at the right and bottom edges
run on through the leftover pixels.
A column grip dropped short of another column's place now moves the column's
left edge, with a dashed rail preview, sharing setColumnPairWidth with the
border drag. A release still on the grip changes nothing, and a pair too
narrow for two MINW columns is left alone.
PARDES_TEST_PAD adds leftover pixels to a test-mode window and capture.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
messages
PDF highlights (hover preview, search, selection) are baked into page rasters,
and any change re-rendered the whole page with MuPDF; the TTY then re-sent it
as base64 (4.7 MB a page), the GUI as a new texture. Worse, a pointer motion
over a PDF invalidated the page even when no preview was shown, so every
motion paid that. Now:
- A raster whose baked highlight set equals the wanted one is left alone.
- A highlighted page keeps its clean rows (before highlights and tint); a
change repaints only the rows of quads that differ, running MuPDF's
highlight pass (pardes_pdf_paint_highlights) over those clean rows and
tinting them: the operations a full render performs, so the pixels are
identical. MuPDF band renders are NOT bit-identical to a whole page (edge
rows, resampled images), so they are never used to patch; the comment
claiming otherwise is corrected.
- ImagePlace.patch hands shells the changed rows; the GUI uploads just those
rows into the texture it holds.
- The TTY probes kitty shared memory (t=s) with an id vaxis never reaches and
sends rasters as a /dev/shm name when the terminal reads it; direct base64
otherwise (ssh).
- Shells that take row patches (GUI, TTY with shm) repaint a selection while
it is dragged instead of only on release.
Latency elsewhere:
- TTY: an animating frame no longer sleeps 16 ms blind; a tick thread posts
into the input queue, so input inside the frame is handled at once.
- TTY and GUI: queued pointer motions coalesce to the last.
- GUI: a skipped swapchain image re-arms the frame (3 retries); animations
still tick while nothing presents.
- Editing: the line index is carried across an edit instead of rebuilt from
a scan of the whole file per keystroke.
Messages fall into their row (ease-in; the GUI slides the band out from under
the tagline, a terminal fades it), stay until the next input as before, linger
MessageLinger ms (default 800), and dissolve (ease-out). MessageAnimation
toggles it; both are settings, in Config and startup files. The snapshot
harness pins the old behaviour. The detached server now ticks animations.
A restored terminal comes back live: the old screen and scrollback (dumped
as clean VT by ghostty's formatter, replayed at the new size; older dumps
fall back to their rendered text), a dim
"restored history" marker, then a new shell in the directory it was in.
Right-click on a line number in a file pane looks at that line (a sticky
context header's number included).
Measured with an external pty driver (TTY), an in-process fence trace
(GUI, PARDES_TEST_LATENCY), and test/pdf_pointer_bench.zig (pixel identity
against the baseline and a whole-page oracle); balanced A/A/B rounds, paired
per-round statistics.
Messages stack: each event gets its own row and its own fall, linger and
dissolve; a line keeps its row until it leaves and a new one fills the first
free row. Announcements and statuses are replaced in place, not stacked.
MessageFall, MessageDissolve and DumpDir are settings Config reports.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
|
| | |
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The editor's loop was the only thing that could answer a 9P request, which
made the editor's own syscalls through a mount of its own tree -- a Look at
/mnt/9p/pardes/<me>/anything under a `9ns --mntgen` view, a Save into it --
requests only the blocked loop could serve. The name-based refusal that
followed (ownMountSuffix) and the in-process routing of a mount of oneself
(Client.sameSession) were patches over that, and both are gone, with the
mailbox that shipped every request to the editor's thread.
One rule replaces them, `pardes.turn`: the core is single-threaded, the
editor's thread has the turn by default and gives it up in two kinds of gap
-- while it waits for input and while a step of it is out in a host syscall
-- and a cloud9 connection task takes it in those gaps to answer. `out`
counts the steps that are out, from any thread: while one is, the core reads
consistently but that step still holds pointers into it, so a request that
would change a pane (a write, a truncation, an rmdir) is parked in the
engine and retried when the turn is next given up with nothing out, and the
editor's own wake waits for the count to reach zero. It is never a write of
its own that a step waits on out there -- writes come from a shell
performing a save between steps -- so a parked request is never the
syscall's own, and making a pane or rendering a screen need not park:
every yield sits before its step's mutation, so the layout and the surface
are whole under it. A changing request that queued effects is answered
once the editor has performed them (`echo Save > exec` returns with the
file written, as acme's `put` does), and it settles the way a step does,
because without that a /log reader waited for the user's next keystroke.
Every host syscall on a user path has to give the turn up, not fs.zig's
alone: the first end-to-end run hung in `inotify_add_watch` performing the
new pane's watch effect. PDFs and images are read whole at open, so no
draw goes out into the host. The core's allocator takes its fixed buffer
through the lock-free interface, since a connection task allocates while
the editor's thread is out in a syscall that allocates too. A Restore puts
the replacement in first and releases every task waiting on the old core.
cloud9 (pinned at eb1a104) parks an open, a truncating wstat, a clunk and a
remove on `again`, not only reads and writes, and answers a parked job
whose fid was clunked without asking the backend.
Verified: test/selfmount.py runs the editor under `9ns --mntgen` and
Looks at, reads and Saves its own tree through the mount; a unit test pins
that a change parks while the editor is out mid-step and lands when it
rests, while a read is answered in the window. 9P over the Unix socket
against a tty session, same machine, Debug builds: a read of /index 278us
-> 61us, a truncating body write 1184us -> 609us, exec Save 718us -> 583us;
the gesture benchmark is unchanged (geometric mean 0.997 over 53 cells).
Also from the reviews: a notice chip over an image or PDF pane was painted
out by the picture drawn after the cells, so pictures give up the rows; in
the GUI a tree-sitter context band painted over the chip, so body layers
are emitted first; a message is one row of printable text, its 256-byte
cut never leaves half a glyph, and one wider than its pane keeps its tail
(the file name, the reason) rather than its head.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
|