Line data Source code
1 : import * as React from 'react';
2 : import * as dashql from '@ankoh/dashql-core';
3 :
4 : import { EditorView } from '@codemirror/view';
5 :
6 : import { CodeMirror, createReadonlyCodeMirrorExtensions } from './codemirror.js';
7 : import { DashQLUpdateEffect } from './dashql_processor.js';
8 : import { useDashQLCoreSetup } from '../../runtime/core_provider.js';
9 : import { formatSqlText } from '../scripts/script_format.js';
10 :
11 : /// Analyze a SQL text in a throwaway script session, the result highlights the text in a read-only view
12 1 : export function projectSqlText(
13 1 : core: dashql.DashQL,
14 1 : catalog: dashql.DashQLCatalog,
15 1 : text: string,
16 1 : ): dashql.buffers.editor.EditorUpdateT {
17 1 : const session = core.createScriptSession(catalog);
18 1 : try {
19 1 : session.replaceText(0n, text);
20 1 : return session.analyze();
21 1 : } finally {
22 1 : session.destroy();
23 1 : }
24 1 : }
25 :
26 : /// A SQL text with the analysis that highlights it
27 : export interface ProjectedSqlText {
28 : /// The text, formatted if a formatting was requested and the formatter can format all of it
29 : text: string;
30 : /// The analysis of the text, null until the core is set up
31 : editorUpdate: dashql.buffers.editor.EditorUpdateT | null;
32 : }
33 :
34 : /// Format and analyze a SQL text against an empty catalog once the core is set up
35 1 : export function useProjectedSqlText(
36 1 : text: string,
37 1 : context: string,
38 1 : format: dashql.buffers.formatting.FormattingConfigT | null = null,
39 1 : ): ProjectedSqlText {
40 1 : const coreSetup = useDashQLCoreSetup();
41 1 : const [core, setCore] = React.useState<dashql.DashQL | null>(null);
42 1 : React.useEffect(() => {
43 1 : if (coreSetup == null) return;
44 1 : let cancelled = false;
45 1 : coreSetup(context).then(c => {
46 1 : if (!cancelled) setCore(c);
47 1 : });
48 1 : return () => { cancelled = true; };
49 1 : }, [coreSetup, context]);
50 1 : return React.useMemo(() => {
51 1 : if (core == null) return { text, editorUpdate: null };
52 1 : const formatted = format == null ? text : formatSqlText(core, text, format) ?? text;
53 1 : const catalog = core.createCatalog();
54 1 : try {
55 1 : return { text: formatted, editorUpdate: projectSqlText(core, catalog, formatted) };
56 1 : } catch {
57 0 : return { text: formatted, editorUpdate: null };
58 1 : } finally {
59 1 : catalog.ptr.destroy();
60 1 : }
61 1 : }, [core, text, format]);
62 1 : }
63 :
64 : export interface ReadonlySqlViewProps {
65 : /// The SQL text
66 : text: string;
67 : /// The analysis of the text that highlights it, null for plain text
68 : editorUpdate: dashql.buffers.editor.EditorUpdateT | null;
69 : /// Wrap long lines instead of scrolling horizontally
70 : lineWrapping?: boolean;
71 : /// Grow with the text instead of filling the container
72 : autoHeight?: boolean;
73 : /// Called with the view once it is created and whenever its size or font metrics change, e.g. to fit a text to its width
74 : onResize?: (view: EditorView) => void;
75 : }
76 :
77 : /// A read-only CodeMirror view of a SQL text, highlighted by DashQL
78 1 : export function ReadonlySqlView(props: ReadonlySqlViewProps) {
79 1 : const [view, setView] = React.useState<EditorView | null>(null);
80 1 : const onResize = React.useRef(props.onResize);
81 1 : React.useLayoutEffect(() => { onResize.current = props.onResize; });
82 1 : const extensions = React.useMemo(() => {
83 1 : const base = createReadonlyCodeMirrorExtensions();
84 : // CodeMirror reports changes of the size and of the character width, e.g. once the font loaded
85 1 : const geometry = EditorView.updateListener.of(update => {
86 1 : if (update.geometryChanged) onResize.current?.(update.view);
87 1 : });
88 1 : return props.lineWrapping ? [...base, EditorView.lineWrapping, geometry] : [...base, geometry];
89 1 : }, [props.lineWrapping]);
90 :
91 1 : React.useEffect(() => {
92 1 : if (view != null) onResize.current?.(view);
93 1 : }, [view]);
94 :
95 1 : React.useEffect(() => {
96 1 : if (view == null) return;
97 1 : view.dispatch({
98 1 : changes: { from: 0, to: view.state.doc.length, insert: props.text },
99 1 : effects: props.editorUpdate == null ? [] : [
100 1 : DashQLUpdateEffect.of({
101 1 : scriptKey: 0,
102 1 : scriptSession: null,
103 1 : editorUpdate: props.editorUpdate,
104 1 : scriptBuffers: null,
105 1 : scriptCompletion: null,
106 1 : scriptPendingDiff: null,
107 1 : derivedFocus: null,
108 1 : onUpdate: () => { },
109 1 : }),
110 1 : ],
111 1 : });
112 1 : }, [view, props.text, props.editorUpdate]);
113 :
114 1 : return <CodeMirror ref={setView} extensions={extensions} style={props.autoHeight ? { height: 'auto' } : undefined} />;
115 1 : }
|