Line data Source code
1 : import * as dashql from '@ankoh/dashql-core';
2 :
3 : import { Tooltip, showTooltip } from '@codemirror/view';
4 : import { Transaction, StateField } from '@codemirror/state';
5 :
6 : import { DashQLCompletionStatus, DashQLProcessorPlugin } from './dashql_processor.js';
7 : import { editorDiagnostics } from './dashql_script_references.js';
8 :
9 : interface CursorError {
10 : message: string;
11 : from: number;
12 : to: number;
13 : }
14 :
15 1 : function findErrorAtCursor(diagnostics: dashql.buffers.editor.EditorDiagnosticT[] | null, cursor: number): CursorError | null {
16 1 : for (const diagnostic of diagnostics ?? []) {
17 1 : const loc = diagnostic.textSpan;
18 1 : if (loc == null) continue;
19 1 : const from = Number(loc.offset);
20 1 : const to = Number(loc.offset + loc.length);
21 1 : if (from <= cursor && to >= cursor) {
22 1 : return { message: typeof diagnostic.message === 'string' ? diagnostic.message : '', from, to };
23 : }
24 1 : }
25 1 : return null;
26 1 : }
27 :
28 1 : function createTooltip(error: CursorError): Tooltip {
29 1 : return {
30 1 : pos: error.from,
31 1 : end: error.to,
32 1 : arrow: true,
33 1 : create: () => {
34 1 : const dom = document.createElement('div');
35 1 : dom.className = 'cm-tooltip-cursor-diagnostics';
36 1 : dom.textContent = error.message;
37 1 : return { dom };
38 1 : },
39 1 : };
40 1 : }
41 :
42 : interface CursorDiagnosticsSnapshot {
43 : update: dashql.buffers.editor.EditorDiagnosticT[] | null;
44 : pos: number;
45 : tooltip: Tooltip | null;
46 : }
47 :
48 1 : const CursorDiagnosticsField = StateField.define<CursorDiagnosticsSnapshot>({
49 1 : create: () => ({ update: null, pos: -1, tooltip: null }),
50 1 : update: (prev: CursorDiagnosticsSnapshot, transaction: Transaction) => {
51 1 : const pos = transaction.state.selection.main.head;
52 1 : const processor = transaction.state.field(DashQLProcessorPlugin);
53 1 : const update = editorDiagnostics(transaction.state);
54 :
55 : // Nothing changed?
56 1 : if (update === prev.update && pos === prev.pos) {
57 1 : return prev;
58 : }
59 :
60 : // Hide tooltip during active completion
61 1 : if (processor.scriptCompletion?.status == DashQLCompletionStatus.AVAILABLE) {
62 1 : return { update, pos, tooltip: null };
63 : }
64 :
65 1 : const error = findErrorAtCursor(update, pos);
66 1 : return { update, pos, tooltip: error ? createTooltip(error) : null };
67 1 : },
68 1 : provide: f => showTooltip.computeN([f], state => {
69 1 : const tooltip = state.field(f).tooltip;
70 1 : return tooltip ? [tooltip] : [];
71 1 : }),
72 1 : });
73 :
74 1 : export const DashQLCursorDiagnosticsPlugin = [CursorDiagnosticsField];
|