jevstrudel.git / packages / codemirror / lineHighlights.mjs

jevstrudel: the lines that are playing, tinted. Anything that highlights code as it plays contributes its ranges to lineHighlights (Strudel's mini-notation outlines, highlight.mjs; Jev's playing choices, website/src/jev/boothHighlight.mjs), and each line holding one is tinted in the colour of the one that sounded last. The minimap (minimap.mjs) draws exactly these tints, so the two can never disagree.

7import { Facet, RangeSetBuilder } from '@codemirror/state';
8import { Decoration, EditorView } from '@codemirror/view';

A contribution: an array of { from, to, color, at }. color is any CSS colour, or null for the editor's text colour; at orders them (a hap's begin), the latest winning its line.

13export const lineHighlights = Facet.define();

Line number → { color, at }, from every contribution.

16export function playingLines(state) {
17  const lines = new Map();
18  const { doc } = state;
19  for (const ranges of state.facet(lineHighlights)) {
20    for (const { from, to, color = null, at = 0 } of ranges) {
21      if (from > doc.length) continue;
22      const first = doc.lineAt(from).number;
23      const last = doc.lineAt(Math.min(Math.max(to, from), doc.length)).number;
24      for (let n = first; n <= last; n++) {
25        const had = lines.get(n);
26        if (!had || at > had.at) lines.set(n, { color, at });
27      }
28    }
29  }
30  return lines;
31}

How strongly the editor tints a playing line; the minimap uses the same.

34export const TINT = 0.16;
36const tinted = new Map();
37const tintFor = (color) => {
38  const key = color ?? '';
39  if (!tinted.has(key)) {
40    const c = color ?? 'var(--foreground)';
41    tinted.set(
42      key,
43      Decoration.line({
44        attributes: { style: `background-color: color-mix(in srgb, ${c} ${TINT * 100}%, transparent)` },
45      }),
46    );
47  }
48  return tinted.get(key);
49};
50
51const lineTints = EditorView.decorations.compute([lineHighlights], (state) => {
52  const builder = new RangeSetBuilder();
53  const lines = [...playingLines(state)].sort((a, b) => a[0] - b[0]);
54  for (const [n, { color }] of lines) {
55    const line = state.doc.line(n);
56    builder.add(line.from, line.from, tintFor(color));
57  }
58  return builder.finish();
59});
60
61export const lineHighlightExtension = [lineTints];