@@ -143,224 +143,68 @@ const schema = JSON.stringify(buildSchema(myInputData))
143143jsonataFull ({ schema })
144144```
145145
146- ## Hover Tooltip Styling
146+ ## Styles
147147
148- Add CSS for the hover tooltips:
148+ Import the bundled stylesheet for Tokyo Night-themed tooltips, autocomplete, and lint styling :
149149
150- ``` css
151- .cm-tooltip-hover {
152- background : #1f2335 ;
153- border : 1px solid #3b4261 ;
154- border-radius : 6px ;
155- max-width : 480px ;
156- }
157- .cm-hover-doc {
158- padding : 10px 14px ;
159- font-size : 13px ;
160- line-height : 1.5 ;
161- }
162- .cm-hover-doc strong { color : #7aa2f7 ; }
163- .cm-hover-doc code {
164- font-family : monospace ;
165- font-size : 12px ;
166- background : #1a1b26 ;
167- padding : 1px 5px ;
168- border-radius : 3px ;
169- }
170- .cm-hover-doc pre {
171- font-family : monospace ;
172- font-size : 12px ;
173- background : #1a1b26 ;
174- padding : 8px 10px ;
175- border-radius : 4px ;
176- margin : 6px 0 ;
177- overflow-x : auto ;
178- }
150+ ``` ts
151+ import ' @gnata-sqlite/codemirror/styles.css'
179152```
180153
181- ## Minimal HTML Example
182-
183- A complete working example using ESM imports (no build step):
184-
185- ``` html
186- <!DOCTYPE html>
187- <html >
188- <head >
189- <style >
190- #editor { border : 1px solid #3b4261 ; border-radius : 6px ; }
191- /* hover tooltip styles */
192- .cm-tooltip-hover { background : #1f2335 !important ; border : 1px solid #3b4261 !important ; border-radius : 6px ; max-width : 480px ; }
193- .cm-hover-doc { padding : 10px 14px ; font-size : 13px ; color : #a9b1d6 ; line-height : 1.5 ; }
194- .cm-hover-doc strong { color : #7aa2f7 ; }
195- .cm-hover-doc code { font-size : 12px ; background : #1a1b26 ; padding : 1px 5px ; border-radius : 3px ; color : #9ece6a ; }
196- .cm-hover-doc pre { font-size : 12px ; background : #1a1b26 ; padding : 8px 10px ; border-radius : 4px ; margin : 6px 0 ; }
197- /* autocomplete styles */
198- .cm-tooltip-autocomplete { background : #1f2335 !important ; border : 1px solid #3b4261 !important ; border-radius : 6px ; }
199- .cm-tooltip-autocomplete ul li [aria-selected ] { background : rgba (122 ,162 ,247 ,0.12 ) !important ; color : #7aa2f7 !important ; }
200- /* lint squiggly underlines */
201- .cm-lintRange-error {
202- background-image : url (" data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='6' height='3'%3E%3Cpath d='m0 3 l2 -2 l1 0 l2 2 l1 0' fill='none' stroke='%23f7768e' stroke-width='.7'/%3E%3C/svg%3E" );
203- background-repeat : repeat-x ; background-position : bottom ; background-size : 6px 3px ; padding-bottom : 1px ;
204- }
205- </style >
206- </head >
207- <body >
208- <div id =" editor" ></div >
209- <script type =" module" >
210- import { EditorView , keymap } from ' https://esm.sh/@codemirror/view@6'
211- import { EditorState } from ' https://esm.sh/@codemirror/state@6'
212- import { defaultKeymap , history , historyKeymap } from ' https://esm.sh/@codemirror/commands@6'
213- import { syntaxHighlighting , HighlightStyle , StreamLanguage } from ' https://esm.sh/@codemirror/language@6'
214- import { autocompletion } from ' https://esm.sh/@codemirror/autocomplete@6'
215- import { linter } from ' https://esm.sh/@codemirror/lint@6'
216- import { hoverTooltip } from ' https://esm.sh/@codemirror/view@6'
217- import { tags as t } from ' https://esm.sh/@lezer/highlight@1'
218-
219- // ── 1. Load WASM ──
220- await new Promise ((res , rej ) => {
221- const s = document .createElement (' script' )
222- s .src = ' lsp-wasm_exec.js'
223- s .onload = res; s .onerror = rej
224- document .head .appendChild (s)
225- })
226- const go = new Go ()
227- const result = await WebAssembly .instantiateStreaming (
228- fetch (' gnata-lsp.wasm' ), go .importObject
229- )
230- go .run (result .instance )
231-
232- // ── 2. JSONata stream tokenizer ──
233- const KW = new Set ([' and' ,' or' ,' in' ,' true' ,' false' ,' null' ,' function' ])
234- const OP = new Set ([' +' ,' -' ,' *' ,' /' ,' %' ,' &' ,' |' ,' =' ,' <' ,' >' ,' !' ,' ~' ,' ^' ,' ?' ,' :' ,' .' ,' ,' ,' ;' ,' @' ,' #' ])
235- const BR = new Set ([' (' ,' )' ,' {' ,' }' ,' [' ,' ]' ])
236- const jsonataLang = StreamLanguage .define ({
237- token (stream ) {
238- if (stream .eatSpace ()) return null
239- if (stream .match (' /*' )) { while (! stream .match (' */' ) && ! stream .eol ()) stream .next (); return ' blockComment' }
240- const ch = stream .peek ()
241- if (ch === ' "' || ch === " '" ) { const q = stream .next (); while (! stream .eol ()) { const c = stream .next (); if (c === q) return ' string' ; if (c === ' \\ ' ) stream .next () } return ' string' }
242- if (/ [0-9 ] / .test (ch)) { stream .match (/ ^ [0-9 ] * \. ? [0-9 ] * ([eE][+-] ? [0-9 ] + )? / ); return ' number' }
243- if (ch === ' $' ) { stream .next (); stream .match (/ ^ [a-zA-Z _$][a-zA-Z0-9 _] * / ); return stream .peek () === ' (' ? ' function(variableName)' : ' special(variableName)' }
244- if (BR .has (ch)) { stream .next (); return ' paren' }
245- if (stream .match (' ~>' )|| stream .match (' :=' )|| stream .match (' !=' )|| stream .match (' >=' )|| stream .match (' <=' )|| stream .match (' **' )|| stream .match (' ..' )|| stream .match (' ?:' )|| stream .match (' ??' )) return ' operator'
246- if (OP .has (ch)) { stream .next (); return ' operator' }
247- if (/ [a-zA-Z _`] / .test (ch)) {
248- if (ch === ' `' ) { stream .next (); while (! stream .eol () && stream .peek () !== ' `' ) stream .next (); if (stream .peek ()=== ' `' ) stream .next (); return ' variableName' }
249- stream .match (/ ^ [a-zA-Z _][a-zA-Z0-9 _] * / ); const w = stream .current ()
250- if (KW .has (w)) { if (w=== ' true' || w=== ' false' ) return ' bool' ; if (w=== ' null' ) return ' null' ; return ' keyword' }
251- return ' variableName'
252- }
253- stream .next (); return null
254- },
255- })
256-
257- // ── 3. Theme ──
258- const hl = HighlightStyle .define ([
259- { tag: t .keyword , color: ' #bb9af7' },
260- { tag: t .operator , color: ' #89ddff' },
261- { tag: t .special (t .variableName ), color: ' #B5E600' },
262- { tag: t .variableName , color: ' #73daca' },
263- { tag: t .function (t .variableName ), color: ' #7aa2f7' },
264- { tag: t .string , color: ' #9ece6a' },
265- { tag: t .number , color: ' #ff9e64' },
266- { tag: t .bool , color: ' #ff9e64' },
267- { tag: t .null , color: ' #565f89' },
268- { tag: t .blockComment , color: ' #565f89' },
269- { tag: t .paren , color: ' #698098' },
270- { tag: t .squareBracket , color: ' #698098' },
271- { tag: t .brace , color: ' #698098' },
272- ])
273-
274- // ── 4. Wire up WASM features ──
275- const gnataLinter = linter (view => {
276- if (typeof _gnataDiagnostics !== ' function' ) return []
277- const doc = view .state .doc .toString ()
278- if (! doc .trim ()) return []
279- try {
280- const r = _gnataDiagnostics (doc)
281- if (r instanceof Error ) return []
282- return JSON .parse (r)
283- } catch { return [] }
284- }, { delay: 200 })
285-
286- function gnataComplete (ctx ) {
287- if (typeof _gnataCompletions !== ' function' ) return null
288- const doc = ctx .state .doc .toString ()
289- let from = ctx .pos
290- while (from > 0 ) {
291- const ch = doc .charCodeAt (from - 1 )
292- if ((ch>= 65 && ch<= 90 )|| (ch>= 97 && ch<= 122 )|| (ch>= 48 && ch<= 57 )|| ch=== 95 || ch=== 36 ) from--
293- else break
294- }
295- try {
296- const r = _gnataCompletions (doc, ctx .pos , ' ' )
297- if (r instanceof Error ) return null
298- const items = JSON .parse (r)
299- return items .length ? { from, options: items } : null
300- } catch { return null }
301- }
154+ Includes dark mode (default) and light mode (when ` <html> ` lacks a ` .dark ` class). Override any class in your own CSS to customize.
302155
303- function renderMd ( md ) {
304- return md . replace ( / & / g , ' & ' ). replace ( / < / g , ' < ' ). replace ( / > / g , ' > ' )
305- . replace ( / ```\n ( [ \s\S ] *? )``` / g , ' <pre>$1</pre> ' )
306- . replace ( / `( [ ^ `] + )` / g , ' <code>$1</code> ' )
307- . replace ( / \*\* ( [ ^ *] + ) \*\* / g , ' <strong>$1</strong> ' )
308- . replace ( / \n\n / g , ' <br><br> ' ). replace ( / \n / g , ' <br> ' )
309- }
156+ ## Minimal Example
157+
158+ ``` ts
159+ import { EditorView , basicSetup } from " codemirror "
160+ import { initWasm , jsonataFull } from " @gnata-sqlite/codemirror "
161+
162+ await initWasm ( " /gnata-lsp.wasm " , " /lsp-wasm_exec.js " )
310163
311- const gnataHover = hoverTooltip (function (view , pos ) {
312- if (typeof _gnataHover !== ' function' ) return null
313- const doc = view .state .doc .toString ()
314- try {
315- const r = _gnataHover (doc, pos)
316- if (! r) return null
317- const info = JSON .parse (r)
318- return { pos: info .from , end: info .to , above: true , create () {
319- const dom = document .createElement (' div' )
320- dom .className = ' cm-hover-doc'
321- dom .innerHTML = renderMd (info .text )
322- return { dom }
164+ new EditorView ({
165+ doc: ' $sum(Account.Order.Product.(Price * Quantity))' ,
166+ extensions: [basicSetup , jsonataFull ()],
167+ parent: document .getElementById (" editor" )! ,
168+ })
169+ ```
170+
171+ With a schema for field-aware autocomplete and hover:
172+
173+ ``` ts
174+ const schema = JSON .stringify ({
175+ fields: {
176+ Account: {
177+ type: " object" ,
178+ fields: {
179+ Order: { type: " array" , fields: {
180+ Product: { type: " array" , fields: {
181+ Price: { type: " number" },
182+ Quantity: { type: " number" },
183+ }}
323184 }}
324- } catch { return null }
325- })
326-
327- // ── 5. Create editor ──
328- new EditorView ({
329- doc: ' $sum(Account.Order.Product.(Price * Quantity))' ,
330- extensions: [
331- keymap .of ([... defaultKeymap, ... historyKeymap]),
332- history (),
333- jsonataLang,
334- syntaxHighlighting (hl),
335- autocompletion ({ override: [gnataComplete], activateOnTyping: true , icons: false }),
336- gnataLinter,
337- gnataHover,
338- EditorView .theme ({
339- ' &' : { backgroundColor: ' #1a1b26' , color: ' #c0caf5' },
340- ' .cm-content' : { caretColor: ' #c0caf5' },
341- ' .cm-cursor' : { borderLeftColor: ' #c0caf5' },
342- ' .cm-gutters' : { display: ' none' },
343- ' .cm-activeLine' : { background: ' transparent' },
344- }, { dark: true }),
345- ],
346- parent: document .getElementById (' editor' ),
347- })
348- </script >
349- </body >
350- </html >
185+ }
186+ }
187+ }
188+ })
189+
190+ new EditorView ({
191+ doc: ' Account.Order.' ,
192+ extensions: [basicSetup , jsonataFull ({ schema })],
193+ parent: document .getElementById (" editor" )! ,
194+ })
351195```
352196
353197## Architecture
354198
355199```
356- ┌─────────────────────────┐ ┌──────────────────────────┐
357- │ Your App (browser) │ │ gnata-lsp.wasm (145 KB) │
358- │ │ │ TinyGo WASM module │
359- │ CodeMirror Editor │────▶│ │
360- │ + @gnata-sqlite/codemirror │ │ _gnataDiagnostics(expr) │
361- │ │◀────│ _gnataCompletions(...) │
362- │ │ │ _gnataHover(expr, pos) │
363- └─────────────────────────┘ └──────────────────────────┘
200+ ┌───────────────────────────── ┐ ┌───── ──────────────────────────┐
201+ │ Your App (browser) │ │ gnata-lsp.wasm (145 KB) │
202+ │ │ │ TinyGo WASM module │
203+ │ CodeMirror Editor │────▶│ │
204+ │ + @gnata-sqlite/codemirror │ │ _gnataDiagnostics(expr) │
205+ │ │◀────│ _gnataCompletions(...) │
206+ │ │ │ _gnataHover(expr, pos) │
207+ └───────────────────────────── ┘ └───── ──────────────────────────┘
364208```
365209
366210The WASM module contains the gnata parser and a catalog of 89 built-in JSONata functions with full documentation. It runs entirely in the browser — no server calls needed.
0 commit comments