chadtmiller.com/knotless
Git hosted in your atproto account, on any stock PDS, fast enough for agents.
git clone knotless://chadtmiller.com/knotlessneeds the knotless git helper. set it up
// Highlighting, painted with the CSS Custom Highlight API rather than
// spans, so the text stays one node per line and the page stays small.
/** The text in `el`, as one node, or nothing for an empty line. */
function textOf(el: Element): Text | null {
el.normalize();
const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT);
return walker.nextNode() as Text | null;
}
/** The highlight named `name`, made if there is none yet. */
function named(name: string): Highlight {
let h = CSS.highlights.get(name);
if (!h) CSS.highlights.set(name, (h = new Highlight()));
return h;
}
/**
* Paints the ranges `ranges` encodes over the text of the `code`
* elements in `el`, taken as if joined by newlines. `ranges` is
* `name:start,length,start,length…;…`, each start counted from the
* end of the range before it.
*/
function paint(el: Element, ranges: string): [Highlight, Range][] {
const nodes: [number, Text][] = [];
let at = 0;
for (const code of el.querySelectorAll('code')) {
const text = textOf(code);
if (text) nodes.push([at, text]);
at += (text ? text.length : 0) + 1;
}
const painted: [Highlight, Range][] = [];
for (const group of ranges.split(';')) {
if (!group) continue;
const [name, list] = group.split(':');
const h = named(name);
const x = list.split(',');
let o = 0;
let j = 0;
for (let i = 0; i < x.length; i += 2) {
o += +x[i];
const end = o + +x[i + 1];
while (j < nodes.length && nodes[j][0] + nodes[j][1].length <= o) j++;
for (let k = j; k < nodes.length && nodes[k][0] < end; k++) {
const [a, t] = nodes[k];
const r = new Range();
r.setStart(t, Math.max(o, a) - a);
r.setEnd(t, Math.min(end, a + t.length) - a);
h.add(r);
painted.push([h, r]);
}
o = end;
}
}
return painted;
}
/** An action painting `ranges` over the code in its element, again as what it shows changes. */
export function highlight(el: Element, shown: { ranges: string | null; text: unknown }) {
if (!globalThis.CSS?.highlights) return {};
let painted = paint(el, shown.ranges ?? '');
const clear = () => painted.forEach(([h, r]) => h.delete(r));
return {
update(next: typeof shown) {
clear();
painted = paint(el, next.ranges ?? '');
},
destroy: clear
};
}
/** Marks line `line` of the text in `el`, or nothing. */
export function target(el: Element | undefined, line: number | null) {
if (!globalThis.CSS?.highlights) return;
CSS.highlights.delete('target');
const text = el && line ? textOf(el) : null;
if (!text || !line) return;
const data = text.data;
let a = 0;
for (let i = 1; i < line; i++) {
a = data.indexOf('\n', a) + 1;
if (!a) return;
}
const e = data.indexOf('\n', a);
const r = new Range();
r.setStart(text, a);
r.setEnd(text, e < 0 ? data.length : e + 1);
CSS.highlights.set('target', new Highlight(r));
}