g1t/apps/web/app/lib/line-anchor.ts

51 lines1,908 bytesCodeBlame

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

Fast pages, required checks on the branch, self-hosted runners, honest incidents1/**
2 * Line links on a file: `#L12` names one line, `#L10-L20` a run of them.
3 * The view reads the address on arrival, and writes it when a line number
4 * is picked.
5 */
6
7/** The lines picked, first to last, both included and 1-based. */
8export type LineRange = { start: number; end: number };
9
10/**
11 * The lines an address fragment names (`#L12`, `#L10-L20`, with or without
12 * the `#`), in order, or null when it names none. With `lines`, a run that
13 * starts past the end of the file names none, and one that runs past it
14 * stops at the last line.
15 */
16export function parseLineHash(hash: string, lines?: number): LineRange | null {
17 const match = /^#?L(\d+)(?:-L?(\d+))?$/.exec(hash);
18 if (!match) return null;
19 const a = Number(match[1]);
20 const b = match[2] == null ? a : Number(match[2]);
21 let start = Math.min(a, b);
22 let end = Math.max(a, b);
23 if (start < 1) start = 1;
24 if (end < 1) return null;
25 if (lines != null) {
26 if (start > lines) return null;
27 end = Math.min(end, lines);
28 }
29 return { start, end };
30}
31
32/** The fragment for a run of lines: `#L12`, or `#L10-L20`. */
33export function formatLineHash(range: LineRange): string {
34 return range.start === range.end ? `#L${range.start}` : `#L${range.start}-L${range.end}`;
35}
36
37/**
38 * What picking `line` selects. A plain pick is that line alone; a pick
39 * with shift held runs from the line already picked (the first of a run)
40 * to this one, whichever way round they are.
41 */
42export function pickLine(current: LineRange | null, line: number, extend: boolean): LineRange {
43 if (!extend || !current) return { start: line, end: line };
44 const anchor = current.start;
45 return { start: Math.min(anchor, line), end: Math.max(anchor, line) };
46}
47
48/** Whether `line` is in the run picked. */
49export function inRange(range: LineRange | null, line: number): boolean {
50 return range != null && range.start <= line && line <= range.end;
51}