Skip to content
58 linesCodeBlameRaw
1/**
2 * Drawing a long log a screenful at a time. A step's log past a few
3 * thousand rows draws only the rows on screen and a few around them, with
4 * space above and below standing in for the rest, so a 50,000-line log
5 * opens, grows and scrolls as fast as a short one. Rows are one line high
6 * until they are drawn and measured (a long line wraps), and the space
7 * kept for them is corrected then.
8 */
9
10/** Past this many rows, a step's log draws only what is on screen. */
11export const WINDOW_FROM = 2000;
12/** A row's height before it is measured: one line of `text-xs leading-5`. */
13export const ROW_HEIGHT = 20;
14/** How far past the screen rows are drawn, above and below, in pixels. */
15export const OVERSCAN = 800;
16
17/**
18 * Where each row starts, from the top of the log: `starts[i]` for row `i`,
19 * and `starts[rows]` for the log's whole height. `height(i)` is a row's
20 * measured height, or undefined for the estimate.
21 */
22export function rowStarts(rows: number, height: (index: number) => number | undefined): Float64Array {
23 const starts = new Float64Array(rows + 1);
24 for (let i = 0; i < rows; i++) starts[i + 1] = starts[i]! + (height(i) ?? ROW_HEIGHT);
25 return starts;
26}
27
28/** The first row that ends below `y`: the row at `y`, or the last. */
29export function rowAt(starts: Float64Array, y: number): number {
30 const rows = starts.length - 1;
31 let low = 0;
32 let high = rows;
33 while (low < high) {
34 const middle = (low + high) >> 1;
35 if (starts[middle + 1]! <= y) low = middle + 1;
36 else high = middle;
37 }
38 return Math.min(low, Math.max(0, rows - 1));
39}
40
41/**
42 * The rows to draw, `[from, to)`, for a view from `top` to `bottom`
43 * (pixels from the top of the log; either may be past its ends), with
44 * `overscan` pixels more either side.
45 */
46export function windowOf(starts: Float64Array, top: number, bottom: number, overscan = OVERSCAN): [number, number] {
47 const rows = starts.length - 1;
48 if (rows === 0) return [0, 0];
49 const total = starts[rows]!;
50 const from = top - overscan;
51 const to = bottom + overscan;
52 if (to <= 0 || from >= total) {
53 // Off screen: draw a few rows at the end nearest the view, so the
54 // log has something to measure and its end can be followed.
55 return from >= total ? [Math.max(0, rows - 20), rows] : [0, Math.min(rows, 20)];
56 }
57 return [rowAt(starts, Math.max(0, from)), rowAt(starts, to) + 1];
58}