Line data Source code
1 : import * as dashql from '@ankoh/dashql-core';
2 :
3 : import { EditorState, StateEffect, StateField, Transaction } from '@codemirror/state';
4 :
5 : import { DashQLProcessorPlugin } from './dashql_processor.js';
6 : import type { ScriptReferenceReport } from '../scripts/script_reference_check.js';
7 :
8 : /// Set the checked script references of the script of the editor
9 1 : export const DashQLScriptReferencesEffect = StateEffect.define<ScriptReferenceReport | null>();
10 :
11 : interface ScriptReferencesState {
12 : /// The report of the script, null if there is none
13 : report: ScriptReferenceReport | null;
14 : /// The analyzed text version the diagnostics were built for, null if the report does not match it
15 : textVersion: bigint | null;
16 : /// The diagnostics of the analysis that `diagnostics` extends
17 : base: dashql.buffers.editor.EditorDiagnosticT[] | null;
18 : /// The diagnostics of the analysis and the errors of the script references, in text order
19 : diagnostics: dashql.buffers.editor.EditorDiagnosticT[] | null;
20 : }
21 :
22 1 : function referenceDiagnostics(report: ScriptReferenceReport): dashql.buffers.editor.EditorDiagnosticT[] {
23 1 : return report.marks.filter(mark => mark.errors.length > 0).map(mark => new dashql.buffers.editor.EditorDiagnosticT(
24 0 : dashql.buffers.editor.EditorDiagnosticSource.ANALYZER,
25 0 : dashql.buffers.editor.EditorDiagnosticSeverity.ERROR,
26 0 : mark.errors.join('; '),
27 0 : new dashql.buffers.editor.EditorTextSpanT(BigInt(mark.from), BigInt(mark.to - mark.from)),
28 1 : ));
29 1 : }
30 :
31 1 : const EMPTY: ScriptReferencesState = { report: null, textVersion: null, base: null, diagnostics: null };
32 :
33 : /// The checked script references of the script of the editor, their errors join the diagnostics of the analysis
34 1 : const ScriptReferencesField = StateField.define<ScriptReferencesState>({
35 1 : create: () => EMPTY,
36 1 : update: (prev: ScriptReferencesState, transaction: Transaction) => {
37 1 : let report = prev.report;
38 1 : for (const effect of transaction.effects) {
39 1 : if (effect.is(DashQLScriptReferencesEffect)) report = effect.value;
40 : }
41 1 : const processor = transaction.state.field(DashQLProcessorPlugin);
42 1 : const base = processor.editorUpdate?.diagnostics ?? null;
43 1 : const analyzed = processor.editorUpdate?.textVersion ?? null;
44 : // The report belongs to the analyzed text, after an edit its errors wait for the next report
45 1 : const textVersion = report != null && analyzed != null && report.textVersion === analyzed ? analyzed : null;
46 1 : if (report === prev.report && base === prev.base && textVersion === prev.textVersion) {
47 1 : return prev;
48 : }
49 1 : const references = report != null && textVersion != null ? referenceDiagnostics(report) : [];
50 1 : const diagnostics = references.length === 0
51 1 : ? base
52 1 : : [...(base ?? []), ...references].sort((l, r) => Number((l.textSpan?.offset ?? 0n) - (r.textSpan?.offset ?? 0n)));
53 1 : return { report, textVersion, base, diagnostics };
54 1 : },
55 1 : });
56 :
57 : /// The diagnostics of the editor: those of the analysis and the errors of the script references. The array keeps
58 : /// its identity while neither changes.
59 1 : export function editorDiagnostics(state: EditorState): dashql.buffers.editor.EditorDiagnosticT[] | null {
60 1 : const references = state.field(ScriptReferencesField, false);
61 1 : return references != null ? references.diagnostics : (state.field(DashQLProcessorPlugin).editorUpdate?.diagnostics ?? null);
62 1 : }
63 :
64 : /// The checked script references that the editor shows
65 1 : export function shownScriptReferences(state: EditorState): ScriptReferenceReport | null {
66 1 : return state.field(ScriptReferencesField, false)?.report ?? null;
67 1 : }
68 :
69 1 : export const DashQLScriptReferencesPlugin = [ScriptReferencesField];
|