Liking cljdoc? Tell your friends :D

com.blockether.vis.internal.foundation.editing.hashline

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. Two coordinates, so a stale or reused anchor can no longer silently land an edit on the wrong line: when the content sits far from the stated line the write is REFUSED (:anchor-misplaced) instead of corrupting the file.

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 anchor -> live line, or refusal resolve-anchor-range-read the READ-tolerant twin 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. Two coordinates, so a stale or reused anchor can no longer silently land
an edit on the wrong line: when the content sits far from the stated line the
write is REFUSED (`:anchor-misplaced`) instead of corrupting the file.

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   anchor -> live line, or refusal
  resolve-anchor-range-read                   the READ-tolerant twin
  resolve-anchor-edit-span                    anchor span -> char span
raw docstring

anchor->lineclj

(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.
sourceraw docstring

anchor-string?clj

(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.
sourceraw docstring

anchor-tokenclj

(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.
sourceraw docstring

bare-anchor-string?clj

(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.
sourceraw docstring

char-offset-at-lineclj

(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.
sourceraw docstring

hash-line-drift-toleranceclj

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: an anchor spent right after the cat/grep that minted it resolves exactly. This window only forgives small drift when anchors are reused across edits without re-reading; anything larger — the ~200-line gap of a genuinely wrong or stale anchor, the corruption this whole scheme exists to stop — is refused so the caller 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: an anchor spent right after the `cat`/`grep` that minted it
resolves exactly. This window only forgives small drift when anchors are
reused across edits without re-reading; anything larger — the ~200-line gap
of a genuinely wrong or stale anchor, the corruption this whole scheme exists
to stop — is refused so the caller re-reads. Deliberately tight: a false
refuse costs one re-read, a false accept corrupts the file.
sourceraw docstring

hash-widthclj

Hex chars in a line's content hash. The line number locates; this hash verifies within hash-line-drift-tolerance. Three hex chars keep an anchor at 4-7 characters 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 an anchor
at 4-7 characters while the line coordinate disambiguates collisions.
sourceraw docstring

hashline-anchor-sepclj

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.
sourceraw docstring

hashline-gutterclj

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.
sourceraw docstring

indices-matching-hashclj

(indices-matching-hash lines h)

0-based indices of lines whose line-hash equals the bare hash h. Pure content match — the line-number coordinate is applied separately by resolve-one-anchor, so this only answers "which lines carry this content".

0-based indices of `lines` whose `line-hash` equals the bare hash `h`. Pure
content match — the line-number coordinate is applied separately by
`resolve-one-anchor`, so this only answers "which lines carry this content".
sourceraw docstring

line-anchorclj

(line-anchor ln text)

The editable anchor for a line: <line-number>:<content-hash> (e.g. 4439:a80). The line number LOCATES the line, the hash VERIFIES its content. patch parses this back through resolve-anchor-range and refuses when the hash no longer agrees (the line changed) or when that content now lives far from the stated line (a stale / misattributed anchor).

The editable anchor for a line: `<line-number>:<content-hash>` (e.g.
`4439:a80`). The line number LOCATES the line, the hash VERIFIES its content.
`patch` parses this back through `resolve-anchor-range` and refuses when the
hash no longer agrees (the line changed) or when that content now lives far
from the stated line (a stale / misattributed anchor).
sourceraw docstring

line-hashclj

(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.
sourceraw docstring

parse-anchorclj

(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.
sourceraw docstring

render-hashline-blockclj

(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.
sourceraw docstring

resolve-anchor-edit-spanclj

(resolve-anchor-edit-span current from-anchor to-anchor replacement)

Resolve an 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). The stated line is tried first and only then small drift, so duplicate hashes elsewhere never make an exact line:hash anchor ambiguous.

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 an EMPTY replacement consumes the trailing newline, so the lines actually vanish instead of leaving blanks behind.

Resolve an 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). The stated line is tried first and only then small drift, so duplicate
hashes elsewhere never make an exact `line:hash` anchor ambiguous.

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 an EMPTY
replacement consumes the trailing newline, so the lines actually vanish
instead of leaving blanks behind.
sourceraw docstring

resolve-anchor-rangeclj

(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>:<hash> anchor: the line number LOCATES it, the hash VERIFIES the content still matches AND still sits near the stated line (else :anchor-misplaced — the wrong-line guard). BOTH coordinates are required; a bare hash with no line number is refused (:anchor-malformed). Returns {:from-line N :to-line N} (1-based, INCLUSIVE) or {:error {:reason KW …}}.

The WRITE side of the contract. resolve-anchor-range-read is its tolerant twin, so a read and a write address lines identically but only the write refuses.

Resolve `from-anchor` (and `to-anchor`, defaulting to `from-anchor` for a
single line) against LIVE `current`. Each is a `<line>:<hash>` anchor: the
line number LOCATES it, the hash VERIFIES the content still matches AND still
sits near the stated line (else `:anchor-misplaced` — the wrong-line guard).
BOTH coordinates are required; a bare hash with no line number is refused
(`:anchor-malformed`). Returns `{:from-line N :to-line N}` (1-based,
INCLUSIVE) or `{:error {:reason KW …}}`.

The WRITE side of the contract. `resolve-anchor-range-read` is its tolerant
twin, so a read and a write address lines identically but only the write
refuses.
sourceraw docstring

resolve-anchor-range-readclj

(resolve-anchor-range-read current from-anchor to-anchor)

READ-tolerant twin of resolve-anchor-range, for cat. A read is NON-DESTRUCTIVE, so a stale 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 the fallback: a read can safely show whatever now sits there. 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, no line number, or :anchor-line-out-of-range, a line outside the file).

READ-tolerant twin of `resolve-anchor-range`, for `cat`. A read is
NON-DESTRUCTIVE, so a stale 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 the fallback: a read can safely show
whatever now sits there. 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`, no line number, or
`:anchor-line-out-of-range`, a line outside the file).
sourceraw docstring

resolve-one-anchorclj

(resolve-one-anchor lines which {:keys [line hash malformed raw]})

Resolve a single PARSED {:line :hash} anchor to a 0-based index in lines, or {:error {:reason KW …}}. The LINE locates; the hash VERIFIES — but a NON-UNIQUE hash never blocks a well-located edit:

  1. exact - the stated line still hashes to hash -> use it.
  2. drifted - the line moved a little and hash sits at EXACTLY one line within hash-line-drift-tolerance -> follow it.
  3. line wins - hash is AMBIGUOUS (several lines, at least one near the stated line): the hash cannot choose, but the caller named an EXPLICIT line, so use it. Duplicate hashes do NOT make a line:hash anchor ambiguous.
  4. misplaced - hash matches only line(s) FAR from the stated line: a strong line-vs-content contradiction -> REFUSE. This is the WRONG-LINE guard that stops an edit landing off target.
  5. not-found - hash matches no live line (the content is gone) -> REFUSE, handing back the anchor that IS at the stated line so the caller recovers in ONE call instead of a second read. Plus: a malformed anchor (no <line>: prefix) or a line outside the file is refused — those genuinely cannot be located.
Resolve a single PARSED `{:line :hash}` anchor to a 0-based index in `lines`,
or `{:error {:reason KW …}}`. The LINE locates; the hash VERIFIES — but a
NON-UNIQUE hash never blocks a well-located edit:
  1. exact     - the stated line still hashes to `hash`            -> use it.
  2. drifted   - the line moved a little and `hash` sits at EXACTLY one line
                 within `hash-line-drift-tolerance`                -> follow it.
  3. line wins - `hash` is AMBIGUOUS (several lines, at least one near the
                 stated line): the hash cannot choose, but the caller named an
                 EXPLICIT line, so use it. Duplicate hashes do NOT make a
                 `line:hash` anchor ambiguous.
  4. misplaced - `hash` matches only line(s) FAR from the stated line: a
                 strong line-vs-content contradiction -> REFUSE. This is the
                 WRONG-LINE guard that stops an edit landing off target.
  5. not-found - `hash` matches no live line (the content is gone) -> REFUSE,
                 handing back the anchor that IS at the stated line so the
                 caller recovers in ONE call instead of a second read.
Plus: a malformed anchor (no `<line>:` prefix) or a line outside the file is
refused — those genuinely cannot be located.
sourceraw docstring

split-content-linesclj

(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.
sourceraw docstring

cljdoc builds & hosts documentation for Clojure/Script libraries

Keyboard shortcuts
Ctrl+kJump to recent docs
Move to previous article
Move to next article
Ctrl+/Jump to the search field
× close