LCOV - code coverage report
Current view: top level - dashql-app/src/session/editor - dashql_gutters.ts Coverage Total Hit
Test: DashQL 915828b Lines: 95.9 % 73 70
Test Date: 2026-11-06 19:30:32

            Line data    Source code
       1              : import * as dashql from '@ankoh/dashql-core';
       2              : 
       3              : import { foldedRanges } from '@codemirror/language';
       4              : import { gutter, GutterMarker } from '@codemirror/view';
       5              : import { Transaction, StateField, RangeSet, RangeSetBuilder } from '@codemirror/state';
       6              : 
       7              : import { editorDiagnostics } from './dashql_script_references.js';
       8              : import { DashQLProcessorPlugin, DashQLScriptKey } from './dashql_processor.js';
       9              : import { findVegaLiteSpecs, isFolded, toggleVegaLiteSpecAtLine, VegaLiteFoldMarker } from './dashql_vis_folding.js';
      10              : 
      11              : import icons from '@ankoh/dashql-svg-symbols';
      12              : 
      13              : import './dashql_gutters.css';
      14              : 
      15            1 : class ErrorMarker extends GutterMarker {
      16            0 :     eq(other: GutterMarker): boolean {
      17            0 :         return other instanceof ErrorMarker;
      18            0 :     }
      19            1 :     toDOM() {
      20            1 :         const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
      21            1 :         svg.setAttribute('class', 'dashql-gutter-error');
      22            1 :         svg.setAttribute('width', '14px');
      23            1 :         svg.setAttribute('height', '14px');
      24            1 :         const use = document.createElementNS('http://www.w3.org/2000/svg', 'use');
      25            1 :         use.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', `${icons}#close_circle`);
      26            1 :         svg.appendChild(use);
      27            1 :         return svg;
      28            1 :     }
      29            1 : }
      30              : 
      31            1 : const ERROR_MARKER = new ErrorMarker();
      32              : 
      33              : interface State {
      34              :     scriptKey: DashQLScriptKey | null;
      35              :     /// The diagnostics the error lines were derived from, an update that omits an unchanged analysis keeps them
      36              :     diagnostics: dashql.buffers.editor.EditorDiagnosticT[] | null;
      37              :     /// The Vega-Lite specs and folds the fold markers were derived from
      38              :     vegaliteSpecs: dashql.buffers.editor.EditorTextSpanT[] | null;
      39              :     folds: ReturnType<typeof foldedRanges> | null;
      40              :     /// The error and fold markers share one gutter to keep it narrow
      41              :     markers: RangeSet<GutterMarker>;
      42              : }
      43              : 
      44            1 : const GutterState: StateField<State> = StateField.define<State>({
      45              :     // Create the initial state
      46            1 :     create: () => ({
      47            1 :         scriptKey: null,
      48            1 :         diagnostics: null,
      49            1 :         vegaliteSpecs: null,
      50            1 :         folds: null,
      51            1 :         markers: RangeSet.empty,
      52            1 :     }),
      53            1 :     update: (state: State, transaction: Transaction) => {
      54              :         // Program and folds untouched?
      55            1 :         const processor = transaction.state.field(DashQLProcessorPlugin);
      56            1 :         const diagnostics = editorDiagnostics(transaction.state);
      57            1 :         const vegaliteSpecs = processor.editorUpdate?.vegaliteSpecs ?? null;
      58            1 :         const folds = foldedRanges(transaction.state);
      59            1 :         if (
      60            1 :             processor.scriptKey === state.scriptKey &&
      61            1 :             diagnostics === state.diagnostics &&
      62            1 :             vegaliteSpecs === state.vegaliteSpecs &&
      63            1 :             folds === state.folds
      64              :         ) {
      65            1 :             return state;
      66              :         }
      67              : 
      68              :         // The analysis may lag behind the text
      69            1 :         const doc = transaction.state.doc;
      70            1 :         const lineAt = (offset: number) => doc.lineAt(Math.min(offset, doc.length)).from;
      71            1 :         const errorLines: Set<number> = new Set();
      72            1 :         for (const diagnostic of diagnostics ?? []) {
      73            1 :             if (diagnostic.severity !== dashql.buffers.editor.EditorDiagnosticSeverity.ERROR) continue;
      74            1 :             const loc = diagnostic.textSpan;
      75            1 :             if (loc == null) continue;
      76            1 :             errorLines.add(lineAt(Number(loc.offset)));
      77            1 :         }
      78            1 :         const foldLines: Map<number, boolean> = new Map();
      79            1 :         for (const spec of findVegaLiteSpecs(processor.editorUpdate)) {
      80            1 :             foldLines.set(lineAt(spec.from), !isFolded(transaction.state, spec));
      81              :         }
      82            1 :         const builder = new RangeSetBuilder<GutterMarker>();
      83            1 :         for (const line of [...new Set([...errorLines, ...foldLines.keys()])].sort((l, r) => l - r)) {
      84            1 :             const open = foldLines.get(line);
      85            1 :             const error = errorLines.has(line);
      86            1 :             builder.add(line, line, open == null ? ERROR_MARKER : new VegaLiteFoldMarker(open, error));
      87            1 :         }
      88            1 :         return {
      89            1 :             scriptKey: processor.scriptKey,
      90            1 :             diagnostics,
      91            1 :             vegaliteSpecs,
      92            1 :             folds,
      93            1 :             markers: builder.finish(),
      94            1 :         };
      95            1 :     },
      96            1 : });
      97              : 
      98            1 : const GutterExtension = gutter({
      99            1 :     class: 'dashql-gutter-markers',
     100            1 :     markers: view => view.state.field(GutterState).markers,
     101            1 :     initialSpacer() {
     102            1 :         return ERROR_MARKER;
     103            1 :     },
     104            1 :     domEventHandlers: {
     105            1 :         click: (view, line) => toggleVegaLiteSpecAtLine(view, line.from, line.to),
     106            1 :     },
     107            1 : });
     108              : 
     109            1 : export const DashQLGutterPlugin = [GutterState, GutterExtension];
        

Generated by: LCOV version 2.0-1