Skip to content

tui: row marks for semantic zones; platform: readMachineId() - #75

Merged
christianparpart merged 4 commits into
masterfrom
feature/semantic-row-marks
Oct 20, 2026
Merged

christianparpart merged 4 commits into
masterfrom
feature/semantic-row-marks

Conversation

@christianparpart

Copy link
Copy Markdown
Member

What

  • Row marks: Canvas::mark(row, column, sequence) anchors a zero-width raw sequence (for example an OSC 133 zone mark) to a canvas cell. Marks live in the Buffer (addMark(), marks(), clearMarks()) and are cleared with the frame, like images.
  • Emission rule: the inline screen writes a mark through writeRaw at its own column before repainting any cell of its segment, from the mark's column up to the next mark's on that row. Marks are diffed like hyperlinks: a row whose marks (column and sequence, in order) differ from the previous frame repaints whole; an otherwise unchanged row emits nothing. A mark on a wide character's continuation column is dropped, image rows emit no marks, and the fullscreen and fixed viewports ignore marks. When two marks share a column, the later one wins.
  • Column-0 contract: terminals carry zone state across rows, so a component that needs per-cell correctness marks column 0 of every row it can repaint. This is documented on Canvas::mark.
  • TerminalOutput::deleteLines(n) writes DL (CSI n M).
  • MockTerminalOutput::rawWrites() / deletedLines(): the mock records every writeRaw with the cursor position it was made at, and every deleteLines.
  • core::platform::readMachineId(FileSystem const&) (<core/platform/MachineId.hpp>): reads /etc/machine-id through an injected FileSystem, returns it lowercased only if it is exactly 32 hexadecimal digits, and std::nullopt for a missing file, uninitialized or other content. Not part of the WebAssembly subset.

Why

endo's shell integration places OSC 133 zone marks on exact prompt rows, which needs marks that survive partial repaints and a way to delete lines. It also needs the machine id for OSC 3008. Neither is endo-specific, so they belong here.

Consumer impact

All additions; no existing signature changes. TerminalOutput::deleteLines is a new virtual with a default implementation, so an existing TerminalOutput subclass keeps compiling.

  • contour (vendored): none
  • endo: moves its pin to use row marks, deleteLines and readMachineId
  • fastcached: none
  • tuidu: none
  • Lightweight dbtool: none
  • morph (including its WebAssembly build): none; readMachineId is outside the WebAssembly subset

Tests

New cases: Screen.rowMarks_* (first frame emits at the column, unchanged row emits nothing, segment re-established on repaint, left-segment repaint, reservation scroll, full redraw, changed sequence or column over identical cells re-emits, image row, continuation-column mark), TerminalOutput.deleteLines_writesDL, and MachineId.* (valid, missing, uninitialized, wrong length, non-hex digits).

Run locally on the rebased branch: clang-debug build and ctest (76/76), gcc-release build and ctest (76/76, canary cases skipped as designed), clang-tidy preset build clean, clang-format.py --all --check, mkdocs build --strict.

  • CHANGELOG entry under [Unreleased] (or not user-visible)
  • Documentation updated (or nothing it describes changed)

… emitted by the inline screen

Canvas::mark() stores a RowMark (row, column, raw sequence) in the Buffer,
cleared with the frame like images. Screen::flushInline() splits each row
at its marks into segments [mark.column, next.column) and, before writing
a repainted cell, writes the mark of the cell's segment at the mark's own
column unless it was the last mark written on that row. A terminal that
tags each written cell with the zone in force (OSC 133 in Ghostty and
WezTerm) therefore stays correct across partial repaints, while an
unchanged row emits nothing. Fullscreen and fixed viewports ignore marks.

TerminalOutput::deleteLines(n) writes DL (CSI n M). MockTerminalOutput
records deleteLines() and every writeRaw() with its cursor position.

Signed-off-by: Christian Parpart <christian@parpart.family>
…emitted

A row whose marks (column and sequence, in order) differ from the previous
frame's now repaints whole, so a mark that changes over unchanged cells is
re-emitted instead of silently left stale. Marks on a wide character's
continuation column are dropped, since no cell write starts there.

Canvas::mark documents that terminals carry zone state across rows (so a
component needing per-cell correctness marks column 0 of every row it can
repaint), that the later of two marks on one column wins, and which marks
are never written.

Tests: changed sequence and changed column over identical cells re-emit;
an image row emits nothing; a continuation-column mark emits nothing; the
repainted cell lands at its own column after a mark is written.

Signed-off-by: Christian Parpart <christian@parpart.family>
Reads /etc/machine-id through a FileSystem, trims it, and returns it
lowercased only if it is exactly 32 hexadecimal digits; a missing file,
"uninitialized" or any other content yields std::nullopt.

Signed-off-by: Christian Parpart <christian@parpart.family>
fastcached v0.6.0 (2026-10-18) renamed the project to Pace: the launcher is
now pace-cc, the daemon pace-kv, the CMake options and environment variables
PACE_* (PACE_ADDR, PACE_AUTO_INSTALL, PACE_AUTO_START, PACE_VERBOSE, ...),
and the release assets pace-<ver>-<os>-<arch>.*. The repository moved to
LASTRADA-Software/pace (GitHub redirects the old name).

Our verbatim copy of CompileCache.cmake still looked for
fastcached-0.6.0-Linux-x86_64.tar.gz and fastcache-cc, so the compile-cache
job's auto-install found nothing ("release 0.6.0 publishes no
fastcached-0.6.0-Linux-x86_64.tar.gz"), the module fell back to no launcher,
and "Assert fastcache-cc is the launcher" failed -- on master as well as
here.

Re-synced verbatim from Pace, both byte-identical to its master 0a3a049c:
- cmake/portable/CompileCache.cmake at 46b563b064fcdc1650f547b52f083bdb5089f871
- cmake/FetchTransferBound.cmake at 892df2b1b79134a02bf3925cb7a2cc251eb35822
  (FASTCACHED_FETCH_* became PACE_FETCH_*)

Consumers follow: the compile-cache CI job (launcher, options, PACE_CC,
PACE_VERBOSE, the "pace-cc: HIT" greps; the libyaml-cpp0.8 install is gone,
since Pace's binaries need only glibc and libstdc++), the nightly drift job's
upstream, CPM.cmake's bound (PACE_FETCH_SILENCE_SECONDS, still honouring a
parent's FASTCACHED_FETCH_SILENCE_SECONDS), the provenance rows, NOTICE, the
CHANGELOG Imported table, and the docs and agent rules that name the
launcher or its options. Historical references (fastcached#NNNN, origins,
the ca8dfc32 note about old fastcache-cc binaries) stay as they are.

Verified locally with ccache, sccache and fastcache-cc hidden: a fresh
gcc-release configure with -DPACE_AUTO_INSTALL=ON -DPACE_AUTO_START=ON
auto-installs pace-cc 0.6.0, starts pace-kv, records pace-cc in
CORE_CPP_CXX_COMPILER_LAUNCHER and 552 build.ninja LAUNCHER bindings; the
rebuild after deleting the objects served 552 of 552 compiles as HITs, and
all 76 tests pass.

Signed-off-by: Christian Parpart <christian@parpart.family>
@christianparpart
christianparpart merged commit 68a4eea into master Oct 20, 2026
33 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant