Pure hashline primitives for content-addressed reading and editing.
Public surface:
split-content-lines / char-offset-at-line
line-hash / lines->anchors text -> lineno:hash / {ln anchor}
render-hashline-block / -range-block tuples -> <hash>| text gutter
indices-matching-hash / resolve-anchor-edit-span self-locating range replace
Pure hashline primitives for content-addressed reading and editing.
Public surface:
split-content-lines / char-offset-at-line
line-hash / lines->anchors text -> lineno:hash / {ln anchor}
render-hashline-block / -range-block tuples -> `<hash>| text` gutter
indices-matching-hash / resolve-anchor-edit-span self-locating range replace(anchor->line anchor)Parse the line number out of a <lineno>:<hash> anchor.
Parse the line number out of a `<lineno>:<hash>` anchor.
(anchor-map->tuples m)Inverse of lines->anchor-map: {anchor {"text" text}} → [[ln text]…]
tuples, the line number parsed from each <lineno>:<hash> key and the text
pulled from the value's "text", sorted by line. For the channel/human
gutter and internal consumers that need tuples.
Inverse of `lines->anchor-map`: `{anchor {"text" text}}` → `[[ln text]…]`
tuples, the line number parsed from each `<lineno>:<hash>` key and the text
pulled from the value's `"text"`, sorted by line. For the channel/human
gutter and internal consumers that need tuples.(anchor-value-text v)Extract the line text from an :anchors / before / after map VALUE.
Every model-facing anchor value is a {"text" <line>} map (see
lines->anchor-map).
Extract the line text from an `:anchors` / `before` / `after` map VALUE.
Every model-facing anchor value is a `{"text" <line>}` map (see
`lines->anchor-map`).(char-offset-at-line content line-idx)Char offset in content where 0-based line line-idx starts.
Returns (count content) if line-idx reaches past the last line.
Public so the anchor patch planner can map line indices back to char
positions for substring splicing.
Char offset in `content` where 0-based line `line-idx` starts. Returns `(count content)` if `line-idx` reaches past the last line. Public so the anchor patch planner can map line indices back to char positions for substring splicing.
How far (in lines) a content hash may sit from its stated line number
before resolve-one-anchor calls the anchor MISPLACED and refuses. The
common path never needs it — within one patch batch every hunk resolves
against the file's ORIGINAL snapshot, so the stated line number is exact and
the fast path fires. This window only forgives small drift when the model
reuses anchors across patch calls without re-reading; anything larger (the
~200-line gap of a genuinely wrong/stale anchor — the corruption this whole
scheme exists to stop) is refused so the model re-reads. Deliberately tight:
a false refuse costs one re-read, a false accept corrupts the file.
How far (in lines) a content hash may sit from its stated line number before `resolve-one-anchor` calls the anchor MISPLACED and refuses. The common path never needs it — within one patch batch every hunk resolves against the file's ORIGINAL snapshot, so the stated line number is exact and the fast path fires. This window only forgives small drift when the model reuses anchors across patch calls without re-reading; anything larger (the ~200-line gap of a genuinely wrong/stale anchor — the corruption this whole scheme exists to stop) is refused so the model re-reads. Deliberately tight: a false refuse costs one re-read, a false accept corrupts the file.
Hex chars in a line's content hash. The line number locates; this hash
verifies within hash-line-drift-tolerance. Three hex chars keep anchors
compact while the line coordinate disambiguates collisions.
Hex chars in a line's content hash. The line number locates; this hash verifies within `hash-line-drift-tolerance`. Three hex chars keep anchors compact while the line coordinate disambiguates collisions.
Separator between the line number and the content hash inside an anchor
(<lineno>:<hash>). A single char so the gutter stays narrow.
Separator between the line number and the content hash inside an anchor (`<lineno>:<hash>`). A single char so the gutter stays narrow.
Separator between the anchor and the line text in rendered output.
Separator between the anchor and the line text in rendered output.
(indices-matching-hash lines h)0-based indices of lines whose content line-hash equals the bare hash
h. Pure content match — the line-number coordinate is applied separately
by resolve-one-anchor, so this just answers "which lines carry this
content".
0-based indices of `lines` whose content `line-hash` equals the bare hash `h`. Pure content match — the line-number coordinate is applied separately by `resolve-one-anchor`, so this just answers "which lines carry this content".
(line-anchor ln text)The editable anchor for a line: <line-number>:<content-hash> (e.g.
325:0e3). The line number LOCATES the line; the hash VERIFIES its
content. patch :from_anchor parses this back via resolve-anchor-range,
matching the line number against live content and refusing if the hash no
longer agrees (the line changed) or that content now lives far from the
stated line (a misattributed / stale anchor). Two coordinates, so a single
reused hash can no longer silently land an edit on the wrong line.
The editable anchor for a line: `<line-number>:<content-hash>` (e.g. `325:0e3`). The line number LOCATES the line; the hash VERIFIES its content. `patch :from_anchor` parses this back via `resolve-anchor-range`, matching the line number against live content and refusing if the hash no longer agrees (the line changed) or that content now lives far from the stated line (a misattributed / stale anchor). Two coordinates, so a single reused hash can no longer silently land an edit on the wrong line.
(line-hash line)Stable hash-width-hex-char content hash of line (trimmed). Folds
the spec'd String/hashCode algorithm over the whitespace-trimmed
line, so it is deterministic across JVM runs. The line coordinate
disambiguates identical hashes.
Hot path: runs once per line on every cat render AND every patch
resolve. Formats with Integer/toHexString + a left-pad rather than
java.util.Formatter, which benches ~1.5x slower; the trimmed
String/hashCode is a JIT intrinsic so we lean on it instead of a
hand loop.
Stable `hash-width`-hex-char content hash of `line` (trimmed). Folds the spec'd `String/hashCode` algorithm over the whitespace-trimmed line, so it is deterministic across JVM runs. The line coordinate disambiguates identical hashes. Hot path: runs once per line on every `cat` render AND every patch resolve. Formats with `Integer/toHexString` + a left-pad rather than java.util.Formatter, which benches ~1.5x slower; the trimmed `String/hashCode` is a JIT intrinsic so we lean on it instead of a hand loop.
(lines->anchor-map tuples)Ordered {anchor {"text" line}} map for [[ln text]…] tuples: a REAL
java.util.LinkedHashMap (natively insertion-ordered), built in line order
— each KEY is the line's <lineno>:<hash> line-anchor, each VALUE a
{"text" <line>} map. This MIRRORS rg's hit value, so a consumer reads
v["text"] uniformly across cat and rg (the whole point of the shape).
Being an ordered hashmap (not a Clojure map), it stays in file order across
the Clojure → JSON/charred → GraalPy dict boundary at ANY size, with NO
comparator. EVERY line is keyed (blanks included, so the read stays
gap-free); duplicate text differs by line number, so keys are unique. THE
single model-facing line payload cat returns; the key IS the
patch :from_anchor.
Ordered `{anchor {"text" line}}` map for `[[ln text]…]` tuples: a REAL
`java.util.LinkedHashMap` (natively insertion-ordered), built in line order
— each KEY is the line's `<lineno>:<hash>` `line-anchor`, each VALUE a
`{"text" <line>}` map. This MIRRORS rg's hit value, so a consumer reads
`v["text"]` uniformly across `cat` and `rg` (the whole point of the shape).
Being an ordered hashmap (not a Clojure map), it stays in file order across
the Clojure → JSON/charred → GraalPy dict boundary at ANY size, with NO
comparator. EVERY line is keyed (blanks included, so the read stays
gap-free); duplicate text differs by line number, so keys are unique. THE
single model-facing line payload `cat` returns; the key IS the
`patch :from_anchor`.(lines->anchors tuples){line-number anchor} map of every non-blank line in tuples, where each
anchor is <line-number>:<content-hash> (line-anchor). The canonical
:anchors payload cat returns — the SINGLE place it is built (read-file /
read-file-ranges / tail-file / rg all route here). Blank lines are omitted:
the model only ever sees anchors it can actually edit by. Line numbers come
straight from the [ln text] tuples, so a windowed read (range / tail /
by-hash) carries the file's real line numbers with NO second full-file pass
— the #N file-wide-ordinal scheme (and its whole-file rescan) is gone now
that the line number, not the hash, disambiguates duplicate lines.
`{line-number anchor}` map of every non-blank line in `tuples`, where each
anchor is `<line-number>:<content-hash>` (`line-anchor`). The canonical
`:anchors` payload `cat` returns — the SINGLE place it is built (read-file /
read-file-ranges / tail-file / rg all route here). Blank lines are omitted:
the model only ever sees anchors it can actually edit by. Line numbers come
straight from the `[ln text]` tuples, so a windowed read (range / tail /
by-hash) carries the file's real line numbers with NO second full-file pass
— the `#N` file-wide-ordinal scheme (and its whole-file rescan) is gone now
that the line number, not the hash, disambiguates duplicate lines.(render-lineno-block tuples)Render [line-number text] tuples as a HUMAN line-number gutter
<ln>│ <text>, line numbers right-aligned to the widest number in
the block. Unlike render-hashline-block (the MODEL surface, whose
gutter is the editable :from_anchor anchor), this is the channel/TUI
display surface: humans navigate by line number, not by content hash.
Render `[line-number text]` tuples as a HUMAN line-number gutter `<ln>│ <text>`, line numbers right-aligned to the widest number in the block. Unlike `render-hashline-block` (the MODEL surface, whose gutter is the editable `:from_anchor` anchor), this is the channel/TUI display surface: humans navigate by line number, not by content hash.
(render-lineno-range-block ranges)render-lineno-block analogue for :ranges windows — -- range S-E --
headers followed by the human line-number gutter for each window.
`render-lineno-block` analogue for `:ranges` windows — `-- range S-E --` headers followed by the human line-number gutter for each window.
(resolve-anchor-edit-span current from_anchor to_anchor replace)Resolve a content-addressed line-range edit to a CHAR SPAN against current,
WITHOUT building new content: {:start S :end E :replacement R :applied-line N}
or {:error {:reason KW …}}. Lets a multi-edit batch resolve every anchor
against the ORIGINAL snapshot and splice all spans together atomically, so an
earlier edit can't drift a later edit's hash/ordinal. to_anchor defaults to
from_anchor (single line). The stated line is tried first and only then
small drift is considered; duplicate hashes at other lines do not make an
exact lineno:hash anchor ambiguous.
Resolve a content-addressed line-range edit to a CHAR SPAN against `current`,
WITHOUT building new content: `{:start S :end E :replacement R :applied-line N}`
or `{:error {:reason KW …}}`. Lets a multi-edit batch resolve every anchor
against the ORIGINAL snapshot and splice all spans together atomically, so an
earlier edit can't drift a later edit's hash/ordinal. `to_anchor` defaults to
`from_anchor` (single line). The stated line is tried first and only then
small drift is considered; duplicate hashes at other lines do not make an
exact `lineno:hash` anchor ambiguous.(resolve-anchor-range current from_anchor to_anchor)Resolve from_anchor (and to_anchor, defaulting to from_anchor for a single
line) against LIVE current. Each is a <line-number>:<hash> anchor: the
line number LOCATES it, the hash VERIFIES the content still matches AND sits
near the stated line (else :hashline-misplaced — the wrong-line guard). Both
coordinates are REQUIRED; a bare hash with no line number is refused
(:hashline-malformed). Returns {:from-line N :to-line N} (1-based,
INCLUSIVE) or {:error {:reason KW …}}.
Shared by resolve-anchor-edit-span (WRITE — patch :from_anchor) and the cat
:anchor READ path so both address lines identically.
Resolve `from_anchor` (and `to_anchor`, defaulting to `from_anchor` for a single
line) against LIVE `current`. Each is a `<line-number>:<hash>` anchor: the
line number LOCATES it, the hash VERIFIES the content still matches AND sits
near the stated line (else `:hashline-misplaced` — the wrong-line guard). Both
coordinates are REQUIRED; a bare hash with no line number is refused
(`:hashline-malformed`). Returns `{:from-line N :to-line N}` (1-based,
INCLUSIVE) or `{:error {:reason KW …}}`.
Shared by `resolve-anchor-edit-span` (WRITE — patch :from_anchor) and the cat
`:anchor` READ path so both address lines identically.(resolve-anchor-range-read current from_anchor to_anchor)READ-tolerant twin of resolve-anchor-range for the cat :anchor path. A read
is NON-DESTRUCTIVE, so a stale/missing hash must not block the look the way it
(correctly) blocks a WRITE. Each anchor still resolves by CONTENT first —
following small drift exactly like the write path — but when its hash matches no
live line the anchor's LINE NUMBER is used as a fallback (a read can safely show
whatever now sits at that line). Returns {:from-line N :to-line N :stale? BOOL}
(1-based, INCLUSIVE; lines are ordered so the window never inverts; :stale? is
true when any hash was missing/misplaced and its line number was used) — or
{:error …} ONLY for a genuinely unlocatable anchor (:hashline-malformed — no
line number — or :hashline-line-out-of-range — a line outside the file).
READ-tolerant twin of `resolve-anchor-range` for the `cat :anchor` path. A read
is NON-DESTRUCTIVE, so a stale/missing hash must not block the look the way it
(correctly) blocks a WRITE. Each anchor still resolves by CONTENT first —
following small drift exactly like the write path — but when its hash matches no
live line the anchor's LINE NUMBER is used as a fallback (a read can safely show
whatever now sits at that line). Returns `{:from-line N :to-line N :stale? BOOL}`
(1-based, INCLUSIVE; lines are ordered so the window never inverts; `:stale?` is
true when any hash was missing/misplaced and its line number was used) — or
`{:error …}` ONLY for a genuinely unlocatable anchor (`:hashline-malformed` — no
line number — or `:hashline-line-out-of-range` — a line outside the file).(split-content-lines s)Split a file blob into a vec of lines. Trailing empty element (from a final newline) is dropped, matching the convention used by the anchor patch planner.
Split a file blob into a vec of lines. Trailing empty element (from a final newline) is dropped, matching the convention used by the anchor patch planner.
(tuples->ranges tuples)Split flat [[ln text]…] tuples into contiguous :ranges windows
[{:range [start end] :lines [[ln text]…]}…], breaking the run whenever
the line number jumps by more than 1. Produces exactly the shape
render-lineno-range-block / render-hashline-range-block consume, so a
flat tuple list (e.g. grouped grep hits) renders with the same
-- range S-E -- gap headers as a native multi-range read.
Split flat `[[ln text]…]` tuples into contiguous `:ranges` windows
`[{:range [start end] :lines [[ln text]…]}…]`, breaking the run whenever
the line number jumps by more than 1. Produces exactly the shape
`render-lineno-range-block` / `render-hashline-range-block` consume, so a
flat tuple list (e.g. grouped grep hits) renders with the same
`-- range S-E --` gap headers as a native multi-range read.cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |