Pure hashline primitives: the ANCHOR vocabulary cat mints, grep echoes and
patch spends.
An anchor is <1-based line>:<3-hex content hash> (Can Bölük's original
hashline shape). The LINE NUMBER locates the line; the CONTENT HASH verifies
it. A write requires BOTH coordinates to match exactly: any contradiction is
REFUSED (:anchor-mismatch) instead of relocating the edit. Only the
non-destructive read path may follow matching content through small line drift.
This namespace is pure — no IO, no tool wiring, no extension envelope. Every surface that addresses a line routes here so the scheme is never recomputed:
split-content-lines / char-offset-at-line blob <-> line/char coordinates
line-hash / line-anchor / anchor->line text -> <line>:<hash>
render-hashline-block [[ln text]…] -> gutter text
anchor-token / parse-anchor rendered line -> bare anchor
indices-matching-hash content-only hash lookup
resolve-one-anchor / resolve-anchor-range exact write resolution
resolve-anchor-range-read tolerant read resolution
resolve-anchor-edit-span anchor span -> char span
Pure hashline primitives: the ANCHOR vocabulary `cat` mints, `grep` echoes and `patch` spends. An anchor is `<1-based line>:<3-hex content hash>` (Can Bölük's original hashline shape). The LINE NUMBER locates the line; the CONTENT HASH verifies it. A write requires BOTH coordinates to match exactly: any contradiction is REFUSED (`:anchor-mismatch`) instead of relocating the edit. Only the non-destructive read path may follow matching content through small line drift. This namespace is pure — no IO, no tool wiring, no extension envelope. Every surface that addresses a line routes here so the scheme is never recomputed: split-content-lines / char-offset-at-line blob <-> line/char coordinates line-hash / line-anchor / anchor->line text -> `<line>:<hash>` render-hashline-block [[ln text]…] -> gutter text anchor-token / parse-anchor rendered line -> bare anchor indices-matching-hash content-only hash lookup resolve-one-anchor / resolve-anchor-range exact write resolution resolve-anchor-range-read tolerant read resolution resolve-anchor-edit-span anchor span -> char span
(anchor->line anchor)Parse the 1-based line number out of a <line>:<hash> anchor, rendered or
bare.
Parse the 1-based line number out of a `<line>:<hash>` anchor, rendered or bare.
(anchor-string? x)True when x ADDRESSES a line: a bare <line>:<hash> token, or a whole
rendered <line>:<hash>│ <text> line, which is that same anchor with the
gutter still attached. cat uses it to tell an anchor endpoint from a bare
line number; anything else is a number.
True when `x` ADDRESSES a line: a bare `<line>:<hash>` token, or a whole rendered `<line>:<hash>│ <text>` line, which is that same anchor with the gutter still attached. `cat` uses it to tell an anchor endpoint from a bare line number; anything else is a number.
(anchor-token x)The bare <line>:<hash> token inside whatever the caller actually passed.
Every anchor a model ever sees is RENDERED — <line>:<hash>│ <text>, because
cat, grep and patch's re-anchored window all print through
render-hashline-block — and the contract those tools advertise is that one
of their lines goes straight back in as an anchor. So everything from
hashline-gutter on is DECORATION and is cut here: the line number locates
and the hash verifies, while the text behind the gutter only SHOWED the line.
Without this cut the text became part of the hash, no line could carry it,
and the refusal handed back the very anchor it had just refused.
What is left is unwrapped from stray whitespace and quote chars — the other
common JSON/LLM mistake, where "4439:a80" arrives WITH its literal quotes
and parse-long chokes on the leading one.
The bare `<line>:<hash>` token inside whatever the caller actually passed. Every anchor a model ever sees is RENDERED — `<line>:<hash>│ <text>`, because `cat`, `grep` and `patch`'s re-anchored window all print through `render-hashline-block` — and the contract those tools advertise is that one of their lines goes straight back in as an anchor. So everything from `hashline-gutter` on is DECORATION and is cut here: the line number locates and the hash verifies, while the text behind the gutter only SHOWED the line. Without this cut the text became part of the hash, no line could carry it, and the refusal handed back the very anchor it had just refused. What is left is unwrapped from stray whitespace and quote chars — the other common JSON/LLM mistake, where `"4439:a80"` arrives WITH its literal quotes and `parse-long` chokes on the leading one.
(bare-anchor-string? x)True when x is ONLY an anchor — the token, with no line text behind it.
patch asks this of its REPLACEMENT slot: a bare anchor there is the span
the caller forgot to finish, while a rendered line carrying TEXT is plausibly
the content it means to write and is written (with a note), not refused.
True when `x` is ONLY an anchor — the token, with no line text behind it. `patch` asks this of its REPLACEMENT slot: a bare anchor there is the span the caller forgot to finish, while a rendered line carrying TEXT is plausibly the content it means to write and is written (with a note), not refused.
(char-offset-at-line content line-idx)Char offset in content where 0-based line line-idx starts. Returns
(count content) when line-idx reaches past the last line. Public so the
edit-span planner can map line indices back to char positions for splicing.
Char offset in `content` where 0-based line `line-idx` starts. Returns `(count content)` when `line-idx` reaches past the last line. Public so the edit-span planner can map line indices back to char positions for splicing.
How far resolve-anchor-range-read may follow content from its stated line.
Reads are non-destructive, so following one nearby hash match is useful; writes
never use this window and require an exact line/hash pair.
How far `resolve-anchor-range-read` may follow content from its stated line. Reads are non-destructive, so following one nearby hash match is useful; writes never use this window and require an exact line/hash pair.
Hex chars in a line's content hash. The line number locates; this hash verifies the exact line for writes. Three hex chars keep an anchor at 4-7 characters; the line coordinate, not a file-wide hash search, disambiguates collisions.
Hex chars in a line's content hash. The line number locates; this hash verifies the exact line for writes. Three hex chars keep an anchor at 4-7 characters; the line coordinate, not a file-wide hash search, disambiguates collisions.
Separator between the line number and the content hash inside an anchor
(<line>:<hash>). A single char so the gutter stays narrow.
Separator between the line number and the content hash inside an anchor (`<line>:<hash>`). A single char so the gutter stays narrow.
Separator between the anchor and the line text in every rendered block.
U+2502 plus a space: it never occurs in source, so line.split("│ ", 1) is
exact and can never be confused with the : inside the anchor.
Separator between the anchor and the line text in every rendered block.
U+2502 plus a space: it never occurs in source, so `line.split("│ ", 1)` is
exact and can never be confused with the `:` inside the anchor.(indices-matching-hash lines h)0-based indices of lines whose line-hash equals the bare hash h. This is
only for tolerant READ resolution; writes verify the hash at the named line.
0-based indices of `lines` whose `line-hash` equals the bare hash `h`. This is only for tolerant READ resolution; writes verify the hash at the named line.
(line-anchor ln text)The editable anchor for a line: <line-number>:<content-hash> (e.g.
4439:a80). The line number LOCATES the line and the hash VERIFIES its exact
current content. patch refuses any mismatch instead of following the hash
to another line.
The editable anchor for a line: `<line-number>:<content-hash>` (e.g. `4439:a80`). The line number LOCATES the line and the hash VERIFIES its exact current content. `patch` refuses any mismatch instead of following the hash to another 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, and a blank line hashes to 000.
Hot path: it runs once per rendered line on every cat, every grep hit and
every patch resolve. Formats with Integer/toHexString plus a left-pad
rather than java.util.Formatter (~1.5x slower), and leans on the trimmed
String/hashCode because that is a JIT intrinsic.
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, and a blank line hashes to `000`. Hot path: it runs once per rendered line on every `cat`, every `grep` hit and every patch resolve. Formats with `Integer/toHexString` plus a left-pad rather than java.util.Formatter (~1.5x slower), and leans on the trimmed `String/hashCode` because that is a JIT intrinsic.
(parse-anchor anchor)Parse a <line>:<hash> anchor into {:line L :hash H} (L a 1-based long, H
the hex content hash). The line number is REQUIRED: an anchor with no :
separator, or a non-numeric line part, parses to {:malformed true :raw S}
and resolve-one-anchor refuses it (:anchor-malformed). Every anchor must
carry BOTH coordinates so the line LOCATES and the hash VERIFIES.
A WHOLE RENDERED LINE parses exactly like a bare token: anchor-token cuts
the gutter and the text behind it first, so the hash is only ever the hex.
Parse a `<line>:<hash>` anchor into `{:line L :hash H}` (L a 1-based long, H
the hex content hash). The line number is REQUIRED: an anchor with no `:`
separator, or a non-numeric line part, parses to `{:malformed true :raw S}`
and `resolve-one-anchor` refuses it (`:anchor-malformed`). Every anchor must
carry BOTH coordinates so the line LOCATES and the hash VERIFIES.
A WHOLE RENDERED LINE parses exactly like a bare token: `anchor-token` cuts
the gutter and the text behind it first, so the hash is only ever the hex.(render-hashline-block tuples)(render-hashline-block tuples indent)Render [[line-number text]…] tuples as the MODEL's addressable gutter —
<line>:<hash>│ <text>, one line per tuple, indent prefixed to each. This
is the single renderer behind cat, grep's hit rows and patch's
re-anchored window, so all three speak one format and any of their lines can
be split on hashline-gutter and fed straight back to patch.
A CRLF file's lines still carry their \r: split-content-lines splits on
\n alone because the char offsets an edit splices at must count every
character the file really has. RENDERING drops that trailing CR — it is
invisible on screen, line-hash never saw it (the hash is of the TRIMMED
line, so the anchor is unchanged), and a model that copies a rendered line
back as a replacement would otherwise write a SECOND carriage return in.
Render `[[line-number text]…]` tuples as the MODEL's addressable gutter — `<line>:<hash>│ <text>`, one line per tuple, `indent` prefixed to each. This is the single renderer behind `cat`, `grep`'s hit rows and `patch`'s re-anchored window, so all three speak one format and any of their lines can be split on `hashline-gutter` and fed straight back to `patch`. A CRLF file's lines still carry their `\r`: `split-content-lines` splits on `\n` alone because the char offsets an edit splices at must count every character the file really has. RENDERING drops that trailing CR — it is invisible on screen, `line-hash` never saw it (the hash is of the TRIMMED line, so the anchor is unchanged), and a model that copies a rendered line back as a replacement would otherwise write a SECOND carriage return in.
(resolve-anchor-edit-span current from-anchor to-anchor replacement)Resolve an exact anchored line range to a CHAR SPAN against current, WITHOUT
building new content: {:start S :end E :replacement R :from-line N :to-line N}
or {:error {:reason KW …}}. to-anchor defaults to from-anchor (a single
line); every supplied endpoint must match its exact current line.
Newline semantics: a replacement need NOT end in \n — the matched region's
terminator is preserved (\r\n stays \r\n on a CRLF file) — and one that
DOES is not doubled: exactly one trailing terminator is dropped, so a block
copied with its own newline REPLACES the span instead of growing a blank line
after it. An EMPTY replacement consumes the trailing newline, so the lines
actually vanish instead of leaving blanks behind.
Resolve an exact anchored line range to a CHAR SPAN against `current`, WITHOUT
building new content: `{:start S :end E :replacement R :from-line N :to-line N}`
or `{:error {:reason KW …}}`. `to-anchor` defaults to `from-anchor` (a single
line); every supplied endpoint must match its exact current line.
Newline semantics: a replacement need NOT end in `\n` — the matched region's
terminator is preserved (`\r\n` stays `\r\n` on a CRLF file) — and one that
DOES is not doubled: exactly one trailing terminator is dropped, so a block
copied with its own newline REPLACES the span instead of growing a blank line
after it. An EMPTY replacement consumes the trailing newline, so the lines
actually vanish instead of leaving blanks behind.(resolve-anchor-range current from-anchor to-anchor)Resolve from-anchor and to-anchor against LIVE current; to defaults to
from for a single-line edit. Every endpoint is a <line>:<hash> pair whose
hash must match that exact line. A mismatch is refused (:anchor-mismatch),
never relocated. Returns {:from-line N :to-line N} (1-based, INCLUSIVE) or
{:error {:reason KW …}}; a range mismatch carries the fresh anchors for both
endpoints.
The WRITE side of the contract. resolve-anchor-range-read is its tolerant,
non-destructive twin.
Resolve `from-anchor` and `to-anchor` against LIVE `current`; `to` defaults to
`from` for a single-line edit. Every endpoint is a `<line>:<hash>` pair whose
hash must match that exact line. A mismatch is refused (`:anchor-mismatch`),
never relocated. Returns `{:from-line N :to-line N}` (1-based, INCLUSIVE) or
`{:error {:reason KW …}}`; a range mismatch carries the fresh anchors for both
endpoints.
The WRITE side of the contract. `resolve-anchor-range-read` is its tolerant,
non-destructive twin.(resolve-anchor-range-read current from-anchor to-anchor)READ-tolerant twin of resolve-anchor-range, for cat. A read is
NON-DESTRUCTIVE, so one nearby hash match may be followed; otherwise an
in-range stale anchor falls back to its stated LINE NUMBER. Returns
{:from-line N :to-line N :stale? BOOL} (1-based, INCLUSIVE; the window never
inverts), or {:error …} only for a genuinely unlocatable anchor
(:anchor-malformed or :anchor-line-out-of-range).
READ-tolerant twin of `resolve-anchor-range`, for `cat`. A read is
NON-DESTRUCTIVE, so one nearby hash match may be followed; otherwise an
in-range stale anchor falls back to its stated LINE NUMBER. Returns
`{:from-line N :to-line N :stale? BOOL}` (1-based, INCLUSIVE; the window never
inverts), or `{:error …}` only for a genuinely unlocatable anchor
(`:anchor-malformed` or `:anchor-line-out-of-range`).(resolve-one-anchor lines which {:keys [line hash malformed raw]})Resolve one PARSED {:line :hash} write anchor to a 0-based index in lines,
or {:error {:reason KW …}}. The line LOCATES and the hash VERIFIES that exact
line. A mismatch is never relocated, even when the same hash occurs nearby;
its error carries the current anchor at the stated line for one-step recovery.
Resolve one PARSED `{:line :hash}` write anchor to a 0-based index in `lines`,
or `{:error {:reason KW …}}`. The line LOCATES and the hash VERIFIES that exact
line. A mismatch is never relocated, even when the same hash occurs nearby;
its error carries the current anchor at the stated line for one-step recovery.(split-content-lines s)Split a file blob into a vec of lines. A trailing empty element (from the file's final newline) is dropped, so the vector's count IS the file's line count and index 0 is line 1.
Split a file blob into a vec of lines. A trailing empty element (from the file's final newline) is dropped, so the vector's count IS the file's line count and index 0 is line 1.
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 |