Skip to content

Commit 0bde0a8

Browse files
committed
stash
1 parent 780dcda commit 0bde0a8

7 files changed

Lines changed: 112 additions & 1950 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,6 @@ skills-lock.json
88
.DS_Store
99
node_modules/
1010
editor/codemirror/dist/
11+
react/dist/
1112
lsp-wasm_exec.js
1213
wasm_exec.js

editor/codemirror/README.md

Lines changed: 50 additions & 206 deletions
Original file line numberDiff line numberDiff line change
@@ -143,224 +143,68 @@ const schema = JSON.stringify(buildSchema(myInputData))
143143
jsonataFull({ 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,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;')
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

366210
The 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.

editor/codemirror/package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,13 @@
1111
"import": "./dist/index.js",
1212
"require": "./dist/index.cjs",
1313
"types": "./dist/index.d.ts"
14-
}
14+
},
15+
"./styles.css": "./styles.css"
1516
},
1617
"files": [
1718
"dist",
18-
"src"
19+
"src",
20+
"styles.css"
1921
],
2022
"scripts": {
2123
"build": "rollup -c"

editor/codemirror/styles.css

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* @gnata-sqlite/codemirror styles
3+
*
4+
* Import this in your app to style CodeMirror hover tooltips,
5+
* autocomplete dropdowns, and lint diagnostics with Tokyo Night colors.
6+
*
7+
* import '@gnata-sqlite/codemirror/styles.css';
8+
*
9+
* Needed because CodeMirror tooltips render as portals in document.body,
10+
* outside the editor DOM where EditorView.theme() scoping can't reach them.
11+
*/
12+
13+
/* Dark mode (default) */
14+
.cm-tooltip-hover{background:#1f2335!important;border:1px solid #3b4261!important;border-radius:6px;max-width:480px}
15+
.cm-hover-tooltip{padding:10px 14px;font-size:13px;color:#a9b1d6;line-height:1.5}
16+
.cm-hover-tooltip strong{color:#7aa2f7;font-weight:700}
17+
.cm-hover-tooltip code{font-family:'SF Mono','Fira Code','JetBrains Mono','Cascadia Code',monospace;font-size:12px;background:#1a1b26;padding:1px 5px;border-radius:3px;color:#9ece6a}
18+
.cm-hover-tooltip pre{font-family:'SF Mono','Fira Code','JetBrains Mono','Cascadia Code',monospace;font-size:12px;background:#1a1b26;padding:8px 10px;border-radius:4px;margin:6px 0;color:#a9b1d6;overflow-x:auto}
19+
.cm-hover-tooltip pre code{background:none;padding:0}
20+
.cm-tooltip-autocomplete{background:#1f2335!important;border:1px solid #3b4261!important;border-radius:6px}
21+
.cm-tooltip-autocomplete ul li{color:#a9b1d6;font-family:'SF Mono','Fira Code','JetBrains Mono',monospace;font-size:13px;padding:4px 10px}
22+
.cm-tooltip-autocomplete ul li[aria-selected]{background:rgba(122,162,247,0.12)!important;color:#7aa2f7!important}
23+
.cm-tooltip-autocomplete .cm-completionDetail{color:#565f89;font-style:normal;margin-left:8px}
24+
.cm-completionIcon{display:none}
25+
.cm-tooltip-lint{background:#1f2335;border:1px solid #3b4261;border-radius:6px;color:#a9b1d6;font-family:'SF Mono','Fira Code','JetBrains Mono',monospace;font-size:12px}
26+
.cm-lintRange-error{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");background-repeat:repeat-x;background-position:bottom;background-size:6px 3px;padding-bottom:1px}
27+
.cm-diagnostic-error{border-bottom:none}
28+
.cm-lint-marker{display:none}
29+
30+
/* Light mode — when html element lacks .dark class */
31+
html:not(.dark) .cm-tooltip-hover{background:#e8e9ed!important;border:1px solid #b6bfe2!important}
32+
html:not(.dark) .cm-hover-tooltip{color:#3760bf}
33+
html:not(.dark) .cm-hover-tooltip strong{color:#2e7de9}
34+
html:not(.dark) .cm-hover-tooltip code{background:#e1e2e7;color:#587539}
35+
html:not(.dark) .cm-hover-tooltip pre{background:#e1e2e7;color:#3760bf}
36+
html:not(.dark) .cm-tooltip-autocomplete{background:#e8e9ed!important;border:1px solid #b6bfe2!important}
37+
html:not(.dark) .cm-tooltip-autocomplete ul li{color:#3760bf}
38+
html:not(.dark) .cm-tooltip-autocomplete ul li[aria-selected]{background:rgba(46,125,233,0.10)!important;color:#2e7de9!important}
39+
html:not(.dark) .cm-tooltip-autocomplete .cm-completionDetail{color:#848cb5}
40+
html:not(.dark) .cm-tooltip-lint{background:#e8e9ed;border:1px solid #b6bfe2;color:#3760bf}
41+
html:not(.dark) .cm-lintRange-error{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='%23c64343' stroke-width='.7'/%3E%3C/svg%3E")}

react/README.md

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -38,16 +38,16 @@ Peer dependencies: `react >=18`, `react-dom >=18`.
3838
The package is **hooks-first** — every feature is a hook. Components compose hooks. Use the full `<JsonataPlayground>` widget, or pick individual pieces for custom layouts.
3939

4040
```
41-
┌─────────────────────────────────────────────────┐
42-
│ JsonataPlayground │
43-
│ ┌──────────────┬─────────────┬───────────────┐ │
44-
│ │ JsonataEditor│ JsonataInput│ JsonataResult │ │
45-
│ └──────┬───────┴──────┬──────┴───────┬───────┘ │
46-
│ │ │ │ │
41+
┌─────────────────────────────────────────────────────
42+
JsonataPlayground
43+
│ ┌──────────────┬─────────────┬───────────────┐
44+
│ │ JsonataEditor│ JsonataInput│ JsonataResult │
45+
│ └──────┬───────┴──────┬──────┴───────┬───────┘
46+
│ │ │ │
4747
│ useJsonataEditor useJsonataEval useJsonataSchema │
48-
│ │ │
49-
│ useJsonataWasm (loads gnata.wasm + LSP WASM) │
50-
└─────────────────────────────────────────────────┘
48+
│ │
49+
│ useJsonataWasm (loads gnata.wasm + LSP WASM)
50+
└─────────────────────────────────────────────────────
5151
```
5252

5353
## Hooks
@@ -273,20 +273,19 @@ import {
273273

274274
## WASM Files
275275

276-
The package does not bundle WASM files — serve them from the host application. Build from source:
276+
The package ships the **LSP WASM** module (`gnata-lsp.wasm` + `lsp-wasm_exec.js`) in the `wasm/` directory — this powers diagnostics, autocomplete, and hover. A setup script is included to copy these files into your public directory:
277+
278+
```bash
279+
npx @gnata-sqlite/react
280+
```
281+
282+
The **eval WASM** module (expression evaluation) is not bundled — build it from source if needed:
277283

278284
```bash
279-
# Eval module (standard Go WASM)
280285
GOOS=js GOARCH=wasm go build -ldflags="-s -w" -trimpath -o gnata.wasm ./wasm/
281286
cp "$(go env GOROOT)/lib/wasm/wasm_exec.js" wasm_exec.js
282-
283-
# LSP module (TinyGo WASM — 380 KB, 145 KB gzipped)
284-
tinygo build -o gnata-lsp.wasm -no-debug -gc=conservative -target wasm ./editor/
285-
cp "$(tinygo env TINYGOROOT)/targets/wasm_exec.js" lsp-wasm_exec.js
286287
```
287288

288-
Serve all four files from the public/static directory.
289-
290289
## License
291290

292291
MIT

0 commit comments

Comments
 (0)