1// jevstrudel: the lines that are playing, tinted. Anything that highlights 2// code as it plays contributes its ranges to `lineHighlights` (Strudel's 3// mini-notation outlines, highlight.mjs; Jev's playing choices, 4// website/src/jev/boothHighlight.mjs), and each line holding one is tinted 5// in the colour of the one that sounded last. The minimap (minimap.mjs) 6// draws exactly these tints, so the two can never disagree. 7import { Facet, RangeSetBuilder } from '@codemirror/state'; 8import { Decoration, EditorView } from '@codemirror/view'; 9 10// A contribution: an array of { from, to, color, at }. `color` is any CSS 11// colour, or null for the editor's text colour; `at` orders them (a hap's 12// begin), the latest winning its line. 13export const lineHighlights = Facet.define(); 14 15// 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} 32 33// How strongly the editor tints a playing line; the minimap uses the same. 34export const TINT = 0.16; 35 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];