Files
Triple-C/app/src/viewer/highlightLine.ts
T
jknappandClaude Opus 5.5 8305c96e20
Build App / compute-version (push) Successful in 7s
Secret Scan / scan (push) Successful in 8s
Build App / build-macos (push) Successful in 2m53s
Build App / build-linux (push) Successful in 5m12s
Build App / build-windows (push) Successful in 5m15s
Build App / create-tag (push) Successful in 3s
Build App / sync-to-github (push) Successful in 1m5s
Terminal file viewer/editor + per-window app-command lockdown (#60)
Clicking a file path in Claude's terminal output now opens the file in its own window with a CodeMirror 6 editor. The editor highlights the target line, live-reloads while the file changes, and saves explicitly with hash-based conflict detection. The viewer commands are gated by window label.

Every app command is now ACL-gated per window through a Tauri AppManifest. build.rs checks the handler list against the capability files and fails the build on any mismatch.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-23 17:05:50 +00:00

41 lines
1.5 KiB
TypeScript

import { StateEffect, StateField, type Extension, type Text } from "@codemirror/state";
import { Decoration, EditorView, type DecorationSet } from "@codemirror/view";
export const setHighlight = StateEffect.define<{ from: number; to: number } | null>();
const lineMark = Decoration.line({ class: "cm-triple-c-target" });
export function lineRangeToPositions(doc: Text, from: number, to: number): { from: number; to: number } | null {
const clamp = (n: number) => Math.min(Math.max(1, Math.floor(n)), doc.lines);
const a = clamp(from);
const b = Math.max(a, clamp(to));
return { from: doc.line(a).from, to: doc.line(b).from };
}
export const highlightLineField = StateField.define<DecorationSet>({
create: () => Decoration.none,
update(value, tr) {
let next = value.map(tr.changes);
for (const e of tr.effects) {
if (!e.is(setHighlight)) continue;
if (e.value === null) { next = Decoration.none; continue; }
const range = lineRangeToPositions(tr.state.doc, e.value.from, e.value.to);
if (!range) { next = Decoration.none; continue; }
const marks = [];
for (let pos = range.from; pos <= range.to; ) {
const line = tr.state.doc.lineAt(pos);
marks.push(lineMark.range(line.from));
if (line.to + 1 > tr.state.doc.length) break;
pos = line.to + 1;
}
next = Decoration.set(marks, true);
}
return next;
},
provide: (f) => EditorView.decorations.from(f),
});
export function highlightExtension(): Extension {
return [highlightLineField];
}