@health-samurai/react-components 0.0.0-alpha.18 → 0.0.0-alpha.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/bundle.css +51 -33
- package/dist/src/components/code-editor/fhir-autocomplete.d.ts +70 -0
- package/dist/src/components/code-editor/fhir-autocomplete.d.ts.map +1 -0
- package/dist/src/components/code-editor/fhir-autocomplete.js +1849 -0
- package/dist/src/components/code-editor/fhir-autocomplete.js.map +1 -0
- package/dist/src/components/code-editor/fhir-autocomplete.test.js +1099 -0
- package/dist/src/components/code-editor/fhir-autocomplete.test.js.map +1 -0
- package/dist/src/components/code-editor/http/index.d.ts +9 -1
- package/dist/src/components/code-editor/http/index.d.ts.map +1 -1
- package/dist/src/components/code-editor/http/index.js +423 -3
- package/dist/src/components/code-editor/http/index.js.map +1 -1
- package/dist/src/components/code-editor/index.d.ts +13 -4
- package/dist/src/components/code-editor/index.d.ts.map +1 -1
- package/dist/src/components/code-editor/index.js +505 -96
- package/dist/src/components/code-editor/index.js.map +1 -1
- package/dist/src/components/code-editor/json-ast.d.ts +46 -0
- package/dist/src/components/code-editor/json-ast.d.ts.map +1 -0
- package/dist/src/components/code-editor/json-ast.js +465 -0
- package/dist/src/components/code-editor/json-ast.js.map +1 -0
- package/dist/src/components/code-editor/json-ast.test.js +206 -0
- package/dist/src/components/code-editor/json-ast.test.js.map +1 -0
- package/dist/src/components/code-editor/sql-completion.d.ts +22 -0
- package/dist/src/components/code-editor/sql-completion.d.ts.map +1 -0
- package/dist/src/components/code-editor/sql-completion.js +895 -0
- package/dist/src/components/code-editor/sql-completion.js.map +1 -0
- package/dist/src/components/date-picker-input.d.ts +10 -0
- package/dist/src/components/date-picker-input.d.ts.map +1 -0
- package/dist/src/components/date-picker-input.js +90 -0
- package/dist/src/components/date-picker-input.js.map +1 -0
- package/dist/src/components/date-picker-input.stories.js +76 -0
- package/dist/src/components/date-picker-input.stories.js.map +1 -0
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/shadcn/components/ui/alert-dialog.d.ts +1 -1
- package/dist/src/shadcn/components/ui/calendar.d.ts +1 -1
- package/dist/src/shadcn/components/ui/carousel.d.ts +1 -1
- package/dist/src/shadcn/components/ui/chart.d.ts +3 -3
- package/dist/src/shadcn/components/ui/chart.d.ts.map +1 -1
- package/dist/src/shadcn/components/ui/chart.js +1 -1
- package/dist/src/shadcn/components/ui/chart.js.map +1 -1
- package/dist/src/shadcn/components/ui/command.d.ts +1 -1
- package/dist/src/shadcn/components/ui/pagination.d.ts +1 -1
- package/dist/src/shadcn/components/ui/resizable.stories.js +2 -2
- package/dist/src/shadcn/components/ui/resizable.stories.js.map +1 -1
- package/dist/src/shadcn/components/ui/sidebar.d.ts +4 -4
- package/dist/src/shadcn/components/ui/tabs.d.ts +3 -1
- package/dist/src/shadcn/components/ui/tabs.d.ts.map +1 -1
- package/dist/src/shadcn/components/ui/tabs.js +129 -2
- package/dist/src/shadcn/components/ui/tabs.js.map +1 -1
- package/dist/src/shadcn/components/ui/tabs.stories.js +1 -1
- package/dist/src/shadcn/components/ui/tabs.stories.js.map +1 -1
- package/dist/src/shadcn/components/ui/toggle-group.d.ts +1 -1
- package/dist/src/typography.css +1 -1
- package/package.json +24 -19
- package/src/components/code-editor/fhir-autocomplete.test.ts +993 -0
- package/src/components/code-editor/fhir-autocomplete.ts +2321 -0
- package/src/components/code-editor/http/index.ts +339 -2
- package/src/components/code-editor/index.tsx +593 -102
- package/src/components/code-editor/json-ast.test.ts +230 -0
- package/src/components/code-editor/json-ast.ts +590 -0
- package/src/components/code-editor/sql-completion.ts +1105 -0
- package/src/components/date-picker-input.stories.tsx +79 -0
- package/src/components/date-picker-input.tsx +104 -0
- package/src/index.tsx +1 -0
- package/src/shadcn/components/ui/chart.tsx +6 -3
- package/src/shadcn/components/ui/resizable.stories.tsx +2 -2
- package/src/shadcn/components/ui/tabs.stories.tsx +1 -1
- package/src/shadcn/components/ui/tabs.tsx +160 -2
- package/src/typography.css +1 -1
- package/dist/src/components/code-editor/http/grammar/http.test.d.ts +0 -2
- package/dist/src/components/code-editor/http/grammar/http.test.d.ts.map +0 -1
|
@@ -5,6 +5,8 @@ import {
|
|
|
5
5
|
closeBrackets,
|
|
6
6
|
closeBracketsKeymap,
|
|
7
7
|
completionKeymap,
|
|
8
|
+
completionStatus,
|
|
9
|
+
moveCompletionSelection,
|
|
8
10
|
} from "@codemirror/autocomplete";
|
|
9
11
|
import { defaultKeymap, history, historyKeymap } from "@codemirror/commands";
|
|
10
12
|
import { json, jsonParseLinter } from "@codemirror/lang-json";
|
|
@@ -17,8 +19,9 @@ import {
|
|
|
17
19
|
HighlightStyle,
|
|
18
20
|
indentOnInput,
|
|
19
21
|
syntaxHighlighting,
|
|
22
|
+
syntaxTree,
|
|
20
23
|
} from "@codemirror/language";
|
|
21
|
-
import { linter,
|
|
24
|
+
import { linter, lintKeymap } from "@codemirror/lint";
|
|
22
25
|
import {
|
|
23
26
|
closeSearchPanel,
|
|
24
27
|
findNext,
|
|
@@ -34,6 +37,7 @@ import {
|
|
|
34
37
|
Compartment,
|
|
35
38
|
EditorState,
|
|
36
39
|
type Extension,
|
|
40
|
+
Prec,
|
|
37
41
|
RangeSet,
|
|
38
42
|
StateEffect,
|
|
39
43
|
StateField,
|
|
@@ -46,8 +50,6 @@ import {
|
|
|
46
50
|
EditorView,
|
|
47
51
|
GutterMarker,
|
|
48
52
|
gutterLineClass,
|
|
49
|
-
highlightActiveLine,
|
|
50
|
-
highlightActiveLineGutter,
|
|
51
53
|
highlightSpecialChars,
|
|
52
54
|
keymap,
|
|
53
55
|
lineNumbers,
|
|
@@ -55,13 +57,37 @@ import {
|
|
|
55
57
|
type ViewUpdate,
|
|
56
58
|
} from "@codemirror/view";
|
|
57
59
|
import { tags } from "@lezer/highlight";
|
|
58
|
-
import {
|
|
60
|
+
import { vim } from "@replit/codemirror-vim";
|
|
61
|
+
import {
|
|
62
|
+
ChevronDown,
|
|
63
|
+
ChevronsRight,
|
|
64
|
+
ChevronUp,
|
|
65
|
+
Heading,
|
|
66
|
+
Table2,
|
|
67
|
+
Terminal,
|
|
68
|
+
X,
|
|
69
|
+
} from "lucide-react";
|
|
59
70
|
import * as React from "react";
|
|
60
71
|
import { flushSync } from "react-dom";
|
|
61
72
|
import { createRoot } from "react-dom/client";
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
73
|
+
import {
|
|
74
|
+
ComplexTypeIcon,
|
|
75
|
+
ResourceIcon,
|
|
76
|
+
SquareFunctionIcon,
|
|
77
|
+
TypCodeIcon,
|
|
78
|
+
} from "../../icons";
|
|
79
|
+
import {
|
|
80
|
+
buildFhirCompletionExtension,
|
|
81
|
+
type ExpandValueSet,
|
|
82
|
+
fhirDiagnosticsField,
|
|
83
|
+
type GetStructureDefinitions,
|
|
84
|
+
} from "./fhir-autocomplete";
|
|
85
|
+
import { type GetUrlSuggestions, http } from "./http";
|
|
86
|
+
import {
|
|
87
|
+
buildSqlCompletionExtensions,
|
|
88
|
+
fetchSqlMetadata,
|
|
89
|
+
type SqlConfig,
|
|
90
|
+
} from "./sql-completion";
|
|
65
91
|
|
|
66
92
|
// --- Issue lines: gutter highlighting, line background, hover tooltip ---
|
|
67
93
|
|
|
@@ -77,37 +103,89 @@ const setIssueLinesEffect = StateEffect.define<IssueLine[]>();
|
|
|
77
103
|
|
|
78
104
|
let errorTooltipEl: HTMLDivElement | null = null;
|
|
79
105
|
|
|
80
|
-
function
|
|
81
|
-
|
|
106
|
+
function formatErrorTypeTitle(code: string): string {
|
|
107
|
+
return code
|
|
108
|
+
.split("-")
|
|
109
|
+
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
|
|
110
|
+
.join(" ");
|
|
111
|
+
}
|
|
82
112
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
Object.assign(
|
|
86
|
-
position: "fixed",
|
|
113
|
+
function renderErrorCard(msg: string): HTMLElement {
|
|
114
|
+
const card = document.createElement("div");
|
|
115
|
+
Object.assign(card.style, {
|
|
87
116
|
backgroundColor: "var(--color-bg-primary)",
|
|
88
117
|
border: "1px solid var(--color-border-primary)",
|
|
89
118
|
borderRadius: "var(--radius-md)",
|
|
90
119
|
padding: "6px 10px",
|
|
120
|
+
boxShadow: "0 2px 6px rgba(0, 0, 0, 0.08)",
|
|
121
|
+
});
|
|
122
|
+
const newlineIdx = msg.indexOf("\n");
|
|
123
|
+
if (newlineIdx !== -1) {
|
|
124
|
+
const title = msg.slice(0, newlineIdx);
|
|
125
|
+
const body = msg.slice(newlineIdx + 1);
|
|
126
|
+
|
|
127
|
+
const titleEl = document.createElement("div");
|
|
128
|
+
titleEl.textContent = formatErrorTypeTitle(title);
|
|
129
|
+
Object.assign(titleEl.style, { fontWeight: "600" });
|
|
130
|
+
|
|
131
|
+
const hr = document.createElement("div");
|
|
132
|
+
Object.assign(hr.style, {
|
|
133
|
+
borderTop: "1px solid var(--color-border-primary)",
|
|
134
|
+
margin: "4px 0",
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
const bodyEl = document.createElement("div");
|
|
138
|
+
bodyEl.textContent = body;
|
|
139
|
+
Object.assign(bodyEl.style, { whiteSpace: "pre-wrap" });
|
|
140
|
+
|
|
141
|
+
card.append(titleEl, hr, bodyEl);
|
|
142
|
+
} else {
|
|
143
|
+
card.textContent = msg;
|
|
144
|
+
card.style.whiteSpace = "pre-wrap";
|
|
145
|
+
}
|
|
146
|
+
return card;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function showErrorTooltip(message: string, x: number, y: number) {
|
|
150
|
+
hideErrorTooltip();
|
|
151
|
+
|
|
152
|
+
const tooltip = document.createElement("div");
|
|
153
|
+
Object.assign(tooltip.style, {
|
|
154
|
+
position: "fixed",
|
|
91
155
|
fontSize: "12px",
|
|
92
156
|
lineHeight: "1.4",
|
|
93
157
|
color: "var(--color-text-error-primary)",
|
|
94
158
|
fontFamily: "var(--font-family-sans)",
|
|
95
|
-
boxShadow: "0 4px 12px rgba(0, 0, 0, 0.1)",
|
|
96
159
|
zIndex: "1000",
|
|
97
160
|
pointerEvents: "none",
|
|
98
161
|
maxWidth: "400px",
|
|
99
|
-
|
|
162
|
+
display: "flex",
|
|
163
|
+
flexDirection: "column",
|
|
164
|
+
gap: "6px",
|
|
100
165
|
});
|
|
166
|
+
|
|
167
|
+
const parts = message.split("\n\x00\n");
|
|
168
|
+
for (const part of parts) {
|
|
169
|
+
tooltip.append(renderErrorCard(part ?? ""));
|
|
170
|
+
}
|
|
171
|
+
|
|
101
172
|
document.body.appendChild(tooltip);
|
|
102
173
|
errorTooltipEl = tooltip;
|
|
103
174
|
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
175
|
+
const tooltipRect = tooltip.getBoundingClientRect();
|
|
176
|
+
let top = y - tooltipRect.height - 8;
|
|
177
|
+
// If tooltip goes above viewport, show below cursor instead
|
|
178
|
+
if (top < 4) {
|
|
179
|
+
top = y + 20;
|
|
180
|
+
}
|
|
181
|
+
// If it still goes below viewport, clamp to bottom
|
|
182
|
+
if (top + tooltipRect.height > window.innerHeight - 4) {
|
|
183
|
+
top = window.innerHeight - tooltipRect.height - 4;
|
|
184
|
+
}
|
|
185
|
+
// Final clamp to top
|
|
186
|
+
if (top < 4) top = 4;
|
|
187
|
+
tooltip.style.left = `${x}px`;
|
|
188
|
+
tooltip.style.top = `${top}px`;
|
|
111
189
|
}
|
|
112
190
|
|
|
113
191
|
function hideErrorTooltip() {
|
|
@@ -157,6 +235,21 @@ const issueLinesField = StateField.define<{
|
|
|
157
235
|
};
|
|
158
236
|
}
|
|
159
237
|
}
|
|
238
|
+
if (tr.docChanged) {
|
|
239
|
+
try {
|
|
240
|
+
return {
|
|
241
|
+
gutterMarkers: state.gutterMarkers.map(tr.changes),
|
|
242
|
+
lineDecorations: state.lineDecorations.map(tr.changes),
|
|
243
|
+
messages: state.messages,
|
|
244
|
+
};
|
|
245
|
+
} catch {
|
|
246
|
+
return {
|
|
247
|
+
gutterMarkers: RangeSet.empty,
|
|
248
|
+
lineDecorations: Decoration.none,
|
|
249
|
+
messages: new Map(),
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
}
|
|
160
253
|
return state;
|
|
161
254
|
},
|
|
162
255
|
provide(field) {
|
|
@@ -167,33 +260,59 @@ const issueLinesField = StateField.define<{
|
|
|
167
260
|
},
|
|
168
261
|
});
|
|
169
262
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
263
|
+
function getErrorMessageForLine(
|
|
264
|
+
view: EditorView,
|
|
265
|
+
lineNo: number,
|
|
266
|
+
): string | undefined {
|
|
267
|
+
const issueMsg = view.state.field(issueLinesField).messages.get(lineNo);
|
|
268
|
+
if (issueMsg) return issueMsg;
|
|
269
|
+
try {
|
|
270
|
+
return view.state.field(fhirDiagnosticsField).messages.get(lineNo);
|
|
271
|
+
} catch {
|
|
272
|
+
return undefined;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function handleErrorTooltipMove(event: Event, view: EditorView) {
|
|
277
|
+
const target = event.target as HTMLElement;
|
|
278
|
+
const mouseEvent = event as MouseEvent;
|
|
180
279
|
|
|
280
|
+
// Check gutter line number
|
|
281
|
+
const gutterEl = target.closest(
|
|
282
|
+
".cm-lineNumbers .cm-gutterElement",
|
|
283
|
+
) as HTMLElement | null;
|
|
284
|
+
if (gutterEl) {
|
|
181
285
|
const lineNo = Number.parseInt(gutterEl.textContent ?? "", 10);
|
|
182
|
-
if (Number.isNaN(lineNo)) {
|
|
183
|
-
|
|
184
|
-
|
|
286
|
+
if (!Number.isNaN(lineNo)) {
|
|
287
|
+
const message = getErrorMessageForLine(view, lineNo);
|
|
288
|
+
if (message) {
|
|
289
|
+
showErrorTooltip(message, mouseEvent.clientX, mouseEvent.clientY);
|
|
290
|
+
return false;
|
|
291
|
+
}
|
|
185
292
|
}
|
|
293
|
+
hideErrorTooltip();
|
|
294
|
+
return false;
|
|
295
|
+
}
|
|
186
296
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
297
|
+
// Check content line (cm-line) — follow cursor
|
|
298
|
+
const lineEl = target.closest(".cm-line") as HTMLElement | null;
|
|
299
|
+
if (lineEl) {
|
|
300
|
+
const pos = view.posAtDOM(lineEl);
|
|
301
|
+
const lineNo = view.state.doc.lineAt(pos).number;
|
|
302
|
+
const message = getErrorMessageForLine(view, lineNo);
|
|
303
|
+
if (message) {
|
|
304
|
+
showErrorTooltip(message, mouseEvent.clientX, mouseEvent.clientY);
|
|
191
305
|
return false;
|
|
192
306
|
}
|
|
307
|
+
}
|
|
193
308
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
309
|
+
hideErrorTooltip();
|
|
310
|
+
return false;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const errorTooltipHandler = EditorView.domEventHandlers({
|
|
314
|
+
mouseover: handleErrorTooltipMove,
|
|
315
|
+
mousemove: handleErrorTooltipMove,
|
|
197
316
|
mouseleave() {
|
|
198
317
|
hideErrorTooltip();
|
|
199
318
|
return false;
|
|
@@ -230,24 +349,40 @@ const baseTheme = EditorView.theme({
|
|
|
230
349
|
fontFamily: "var(--font-family-mono)",
|
|
231
350
|
},
|
|
232
351
|
".cm-gutters": {
|
|
233
|
-
backgroundColor: "
|
|
352
|
+
backgroundColor: "transparent",
|
|
234
353
|
border: "none",
|
|
235
354
|
},
|
|
236
355
|
".cm-lineNumbers": {
|
|
237
|
-
|
|
356
|
+
minWidth: "3.5ch",
|
|
238
357
|
},
|
|
239
|
-
".cm-
|
|
358
|
+
".cm-lineNumbers .cm-gutterElement": {
|
|
359
|
+
minWidth: "3.5ch",
|
|
360
|
+
paddingRight: "4px",
|
|
361
|
+
color: "var(--color-text-quaternary)",
|
|
362
|
+
},
|
|
363
|
+
".cm-lineNumbers .cm-gutterElement.cm-activeLineGutter": {
|
|
240
364
|
backgroundColor: "var(--color-bg-primary)",
|
|
241
|
-
color: "var(--color-text-
|
|
365
|
+
color: "var(--color-text-secondary)",
|
|
366
|
+
},
|
|
367
|
+
".cm-activeLineGutter": {
|
|
368
|
+
backgroundColor: "transparent !important",
|
|
242
369
|
},
|
|
243
370
|
".cm-activeLine": {
|
|
244
|
-
backgroundColor: "
|
|
371
|
+
backgroundColor: "transparent !important",
|
|
245
372
|
},
|
|
246
|
-
".cm-errorLineGutter": {
|
|
373
|
+
".cm-lineNumbers .cm-gutterElement.cm-errorLineGutter": {
|
|
247
374
|
color: "var(--color-text-error-primary)",
|
|
248
375
|
backgroundColor:
|
|
249
376
|
"color-mix(in srgb, var(--color-text-error-primary) 7%, transparent)",
|
|
250
377
|
},
|
|
378
|
+
".cm-foldGutter .cm-gutterElement.cm-errorLineGutter": {
|
|
379
|
+
color: "var(--color-text-error-primary)",
|
|
380
|
+
backgroundColor:
|
|
381
|
+
"color-mix(in srgb, var(--color-text-error-primary) 7%, transparent)",
|
|
382
|
+
display: "flex",
|
|
383
|
+
alignItems: "center",
|
|
384
|
+
justifyContent: "center",
|
|
385
|
+
},
|
|
251
386
|
".cm-errorLine": {
|
|
252
387
|
backgroundColor:
|
|
253
388
|
"color-mix(in srgb, var(--color-text-error-primary) 7%, transparent)",
|
|
@@ -266,6 +401,14 @@ const completionTheme = EditorView.theme({
|
|
|
266
401
|
".cm-completionLabel": {
|
|
267
402
|
flex: "1",
|
|
268
403
|
minWidth: "0",
|
|
404
|
+
fontFamily: "var(--font-family-mono)",
|
|
405
|
+
fontSize: "var(--font-size-sm)",
|
|
406
|
+
lineHeight: "var(--font-leading-5)",
|
|
407
|
+
},
|
|
408
|
+
".cm-completionMatchedText": {
|
|
409
|
+
textDecoration: "none",
|
|
410
|
+
fontWeight: "600",
|
|
411
|
+
color: "var(--color-text-link)",
|
|
269
412
|
},
|
|
270
413
|
".cm-completionDetail": {
|
|
271
414
|
color: "var(--color-text-tertiary)",
|
|
@@ -278,9 +421,10 @@ const completionTheme = EditorView.theme({
|
|
|
278
421
|
border: "1px solid var(--color-border-primary)",
|
|
279
422
|
borderRadius: "var(--radius-md)",
|
|
280
423
|
color: "var(--color-text-secondary)",
|
|
281
|
-
fontFamily: "var(--font-family-
|
|
282
|
-
fontSize: "
|
|
424
|
+
fontFamily: "var(--font-family-mono)",
|
|
425
|
+
fontSize: "14px",
|
|
283
426
|
padding: "8px 12px",
|
|
427
|
+
marginLeft: "8px",
|
|
284
428
|
lineHeight: "1.4",
|
|
285
429
|
whiteSpace: "normal",
|
|
286
430
|
maxWidth: "300px",
|
|
@@ -326,20 +470,36 @@ const readOnlyTheme = EditorView.theme({
|
|
|
326
470
|
border: "none",
|
|
327
471
|
},
|
|
328
472
|
".cm-lineNumbers": {
|
|
329
|
-
|
|
473
|
+
minWidth: "3.5ch",
|
|
330
474
|
},
|
|
331
|
-
".cm-
|
|
475
|
+
".cm-lineNumbers .cm-gutterElement": {
|
|
476
|
+
minWidth: "3.5ch",
|
|
477
|
+
paddingRight: "4px",
|
|
478
|
+
color: "var(--color-text-quaternary)",
|
|
479
|
+
},
|
|
480
|
+
".cm-lineNumbers .cm-gutterElement.cm-activeLineGutter": {
|
|
332
481
|
backgroundColor: "var(--color-bg-secondary)",
|
|
333
|
-
color: "var(--color-text-
|
|
482
|
+
color: "var(--color-text-secondary)",
|
|
483
|
+
},
|
|
484
|
+
".cm-activeLineGutter": {
|
|
485
|
+
backgroundColor: "transparent !important",
|
|
334
486
|
},
|
|
335
487
|
".cm-activeLine": {
|
|
336
|
-
backgroundColor: "
|
|
488
|
+
backgroundColor: "transparent !important",
|
|
337
489
|
},
|
|
338
|
-
".cm-errorLineGutter": {
|
|
490
|
+
".cm-lineNumbers .cm-gutterElement.cm-errorLineGutter": {
|
|
339
491
|
color: "var(--color-text-error-primary)",
|
|
340
492
|
backgroundColor:
|
|
341
493
|
"color-mix(in srgb, var(--color-text-error-primary) 7%, transparent)",
|
|
342
494
|
},
|
|
495
|
+
".cm-foldGutter .cm-gutterElement.cm-errorLineGutter": {
|
|
496
|
+
color: "var(--color-text-error-primary)",
|
|
497
|
+
backgroundColor:
|
|
498
|
+
"color-mix(in srgb, var(--color-text-error-primary) 7%, transparent)",
|
|
499
|
+
display: "flex",
|
|
500
|
+
alignItems: "center",
|
|
501
|
+
justifyContent: "center",
|
|
502
|
+
},
|
|
343
503
|
".cm-errorLine": {
|
|
344
504
|
backgroundColor:
|
|
345
505
|
"color-mix(in srgb, var(--color-text-error-primary) 7%, transparent)",
|
|
@@ -426,6 +586,7 @@ function createSearchPanel(view: EditorView) {
|
|
|
426
586
|
alignItems: "center",
|
|
427
587
|
gap: "2px",
|
|
428
588
|
padding: "6px 8px",
|
|
589
|
+
marginTop: "4px",
|
|
429
590
|
backgroundColor: "var(--color-bg-primary)",
|
|
430
591
|
border: "1px solid var(--color-border-primary)",
|
|
431
592
|
borderRadius: "var(--radius-md)",
|
|
@@ -562,10 +723,10 @@ function createSearchPanel(view: EditorView) {
|
|
|
562
723
|
};
|
|
563
724
|
}
|
|
564
725
|
|
|
565
|
-
const searchPanelTheme = EditorView.
|
|
566
|
-
".cm-panels-top": {
|
|
726
|
+
const searchPanelTheme = EditorView.theme({
|
|
727
|
+
"& .cm-panels-top": {
|
|
567
728
|
position: "absolute",
|
|
568
|
-
top: "
|
|
729
|
+
top: "8px",
|
|
569
730
|
right: "4px",
|
|
570
731
|
left: "auto",
|
|
571
732
|
zIndex: "10",
|
|
@@ -573,10 +734,13 @@ const searchPanelTheme = EditorView.baseTheme({
|
|
|
573
734
|
border: "none",
|
|
574
735
|
},
|
|
575
736
|
".cm-searchMatch": {
|
|
576
|
-
backgroundColor: "
|
|
737
|
+
backgroundColor: "var(--color-blue-200) !important",
|
|
577
738
|
},
|
|
578
739
|
".cm-searchMatch-selected": {
|
|
579
|
-
backgroundColor: "
|
|
740
|
+
backgroundColor: "var(--color-blue-400) !important",
|
|
741
|
+
},
|
|
742
|
+
".cm-selectionMatch": {
|
|
743
|
+
backgroundColor: "var(--color-blue-100) !important",
|
|
580
744
|
},
|
|
581
745
|
});
|
|
582
746
|
|
|
@@ -690,23 +854,107 @@ const customSQLDialect = SQLDialect.define({
|
|
|
690
854
|
builtin: SQL_BUILTIN.join(" "),
|
|
691
855
|
});
|
|
692
856
|
|
|
857
|
+
function computeYamlNewlineIndent(lineText: string): string {
|
|
858
|
+
const indent = lineText.match(/^(\s*)/)?.[1] ?? "";
|
|
859
|
+
const trimmed = lineText.trimEnd();
|
|
860
|
+
|
|
861
|
+
if (trimmed.endsWith(":")) {
|
|
862
|
+
// After "key:" with no value — increase indent
|
|
863
|
+
// For " - key:", base indent is at the dash content level
|
|
864
|
+
const dashMatch = trimmed.match(/^(\s*-\s+)/);
|
|
865
|
+
const baseIndent = dashMatch?.[1]
|
|
866
|
+
? " ".repeat(dashMatch[1].length)
|
|
867
|
+
: indent;
|
|
868
|
+
return `${baseIndent} `;
|
|
869
|
+
}
|
|
870
|
+
if (/^\s*-\s*$/.test(trimmed)) {
|
|
871
|
+
// After bare "- " (array item marker only) — align to content after dash
|
|
872
|
+
const dashMatch = trimmed.match(/^(\s*-\s*)/);
|
|
873
|
+
return dashMatch?.[1] ? " ".repeat(dashMatch[1].length) : indent;
|
|
874
|
+
}
|
|
875
|
+
// Preserve current indent; for " - key: val" align to key level
|
|
876
|
+
const dashKeyMatch = trimmed.match(/^(\s*-\s+)\S/);
|
|
877
|
+
return dashKeyMatch?.[1] ? " ".repeat(dashKeyMatch[1].length) : indent;
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
function yamlEnterKeymap(): Extension {
|
|
881
|
+
return keymap.of([
|
|
882
|
+
{
|
|
883
|
+
key: "Enter",
|
|
884
|
+
run: (view) => {
|
|
885
|
+
const { state } = view;
|
|
886
|
+
const pos = state.selection.main.head;
|
|
887
|
+
const line = state.doc.lineAt(pos);
|
|
888
|
+
const newIndent = computeYamlNewlineIndent(line.text);
|
|
889
|
+
|
|
890
|
+
view.dispatch({
|
|
891
|
+
changes: { from: pos, insert: `\n${newIndent}` },
|
|
892
|
+
selection: { anchor: pos + 1 + newIndent.length },
|
|
893
|
+
});
|
|
894
|
+
return true;
|
|
895
|
+
},
|
|
896
|
+
},
|
|
897
|
+
]);
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
function httpYamlEnterKeymap(): Extension {
|
|
901
|
+
return keymap.of([
|
|
902
|
+
{
|
|
903
|
+
key: "Enter",
|
|
904
|
+
run: (view) => {
|
|
905
|
+
const { state } = view;
|
|
906
|
+
const pos = state.selection.main.head;
|
|
907
|
+
const doc = state.doc.toString();
|
|
908
|
+
|
|
909
|
+
// Only handle if cursor is in YAML body (after blank line separator)
|
|
910
|
+
const textBeforeCursor = doc.slice(0, pos);
|
|
911
|
+
const blankLineIdx = textBeforeCursor.indexOf("\n\n");
|
|
912
|
+
if (blankLineIdx === -1 || pos <= blankLineIdx + 1) return false;
|
|
913
|
+
|
|
914
|
+
// Check if the body looks like YAML (not JSON)
|
|
915
|
+
const bodyStart = blankLineIdx + 2;
|
|
916
|
+
const bodyPrefix = doc.slice(bodyStart, bodyStart + 20).trimStart();
|
|
917
|
+
if (bodyPrefix.startsWith("{") || bodyPrefix.startsWith("["))
|
|
918
|
+
return false;
|
|
919
|
+
|
|
920
|
+
const line = state.doc.lineAt(pos);
|
|
921
|
+
const newIndent = computeYamlNewlineIndent(line.text);
|
|
922
|
+
|
|
923
|
+
view.dispatch({
|
|
924
|
+
changes: { from: pos, insert: `\n${newIndent}` },
|
|
925
|
+
selection: { anchor: pos + 1 + newIndent.length },
|
|
926
|
+
});
|
|
927
|
+
return true;
|
|
928
|
+
},
|
|
929
|
+
},
|
|
930
|
+
]);
|
|
931
|
+
}
|
|
932
|
+
|
|
693
933
|
type LanguageMode = "json" | "http" | "sql" | "yaml";
|
|
694
934
|
|
|
695
|
-
function languageExtensions(
|
|
935
|
+
function languageExtensions(
|
|
936
|
+
mode: LanguageMode,
|
|
937
|
+
sqlExtraBuiltins?: string[],
|
|
938
|
+
getUrlSuggestions?: GetUrlSuggestions,
|
|
939
|
+
) {
|
|
696
940
|
if (mode === "http") {
|
|
697
941
|
const jsonLang = json();
|
|
698
942
|
const yamlLang = yaml();
|
|
699
943
|
return [
|
|
700
|
-
http(
|
|
701
|
-
ct
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
944
|
+
http(
|
|
945
|
+
(ct) =>
|
|
946
|
+
ct === "application/json"
|
|
947
|
+
? jsonLang.language
|
|
948
|
+
: ct === "text/yaml" ||
|
|
949
|
+
ct === "application/yaml" ||
|
|
950
|
+
ct === "application/x-yaml"
|
|
951
|
+
? yamlLang.language
|
|
952
|
+
: null,
|
|
953
|
+
getUrlSuggestions,
|
|
708
954
|
),
|
|
709
955
|
syntaxHighlighting(customHighlightStyle),
|
|
956
|
+
jsonAutoExpandBraces(),
|
|
957
|
+
httpYamlEnterKeymap(),
|
|
710
958
|
];
|
|
711
959
|
} else if (mode === "sql") {
|
|
712
960
|
let dialect = customSQLDialect;
|
|
@@ -718,16 +966,81 @@ function languageExtensions(mode: LanguageMode, sqlExtraBuiltins?: string[]) {
|
|
|
718
966
|
}
|
|
719
967
|
return [sql({ dialect }), syntaxHighlighting(customHighlightStyle)];
|
|
720
968
|
} else if (mode === "yaml") {
|
|
721
|
-
return [
|
|
969
|
+
return [
|
|
970
|
+
yaml(),
|
|
971
|
+
syntaxHighlighting(customHighlightStyle),
|
|
972
|
+
yamlEnterKeymap(),
|
|
973
|
+
];
|
|
722
974
|
} else {
|
|
723
975
|
return [
|
|
724
976
|
json(),
|
|
725
|
-
linter(
|
|
977
|
+
linter(
|
|
978
|
+
(view) => {
|
|
979
|
+
if (!view.state.doc.toString().trim()) return [];
|
|
980
|
+
return jsonParseLinter()(view);
|
|
981
|
+
},
|
|
982
|
+
{ delay: 300 },
|
|
983
|
+
),
|
|
726
984
|
syntaxHighlighting(customHighlightStyle),
|
|
985
|
+
jsonAutoExpandBraces(),
|
|
727
986
|
];
|
|
728
987
|
}
|
|
729
988
|
}
|
|
730
989
|
|
|
990
|
+
function jsonAutoExpandBraces(): Extension {
|
|
991
|
+
return EditorState.transactionFilter.of((tr) => {
|
|
992
|
+
if (!tr.docChanged) return tr;
|
|
993
|
+
|
|
994
|
+
let braceFrom = -1;
|
|
995
|
+
let braceTo = -1;
|
|
996
|
+
let changeCount = 0;
|
|
997
|
+
|
|
998
|
+
tr.changes.iterChanges((fromA, toA, _fromB, _toB, inserted) => {
|
|
999
|
+
changeCount++;
|
|
1000
|
+
if (inserted.toString() === "{}") {
|
|
1001
|
+
braceFrom = fromA;
|
|
1002
|
+
braceTo = toA;
|
|
1003
|
+
}
|
|
1004
|
+
});
|
|
1005
|
+
|
|
1006
|
+
if (changeCount !== 1 || braceFrom === -1) return tr;
|
|
1007
|
+
|
|
1008
|
+
const tree = syntaxTree(tr.startState);
|
|
1009
|
+
const nodeBefore = tree.resolveInner(braceFrom, -1);
|
|
1010
|
+
if (nodeBefore.name === "String" || nodeBefore.parent?.name === "String") {
|
|
1011
|
+
return tr;
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
const line = tr.startState.doc.lineAt(braceFrom);
|
|
1015
|
+
const indent = line.text.match(/^(\s*)/)?.[1] ?? "";
|
|
1016
|
+
const inner = `${indent} `;
|
|
1017
|
+
|
|
1018
|
+
// Check if { is inside an extension array — insert {"url": ""} snippet
|
|
1019
|
+
const docText = tr.startState.doc.toString();
|
|
1020
|
+
const textBefore = docText.slice(0, braceFrom);
|
|
1021
|
+
const isInExtArray =
|
|
1022
|
+
/"(?:extension|modifierExtension)"\s*:\s*\[\s*(?:\{[\s\S]*?\}\s*,?\s*)*$/s.test(
|
|
1023
|
+
textBefore,
|
|
1024
|
+
);
|
|
1025
|
+
if (isInExtArray) {
|
|
1026
|
+
const insert = `{\n${inner}"url": ""\n${indent}}`;
|
|
1027
|
+
return {
|
|
1028
|
+
changes: { from: braceFrom, to: braceTo, insert },
|
|
1029
|
+
selection: { anchor: braceFrom + insert.lastIndexOf('""') + 1 },
|
|
1030
|
+
};
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
return {
|
|
1034
|
+
changes: {
|
|
1035
|
+
from: braceFrom,
|
|
1036
|
+
to: braceTo,
|
|
1037
|
+
insert: `{\n${inner}\n${indent}}`,
|
|
1038
|
+
},
|
|
1039
|
+
selection: { anchor: braceFrom + 2 + inner.length },
|
|
1040
|
+
};
|
|
1041
|
+
});
|
|
1042
|
+
}
|
|
1043
|
+
|
|
731
1044
|
type CodeEditorProps = {
|
|
732
1045
|
readOnly?: boolean;
|
|
733
1046
|
isReadOnlyTheme?: boolean;
|
|
@@ -741,13 +1054,28 @@ type CodeEditorProps = {
|
|
|
741
1054
|
additionalExtensions?: Extension[];
|
|
742
1055
|
issueLineNumbers?: { line: number; message?: string }[];
|
|
743
1056
|
foldGutter?: boolean;
|
|
744
|
-
lintGutter?: boolean;
|
|
745
1057
|
lineNumbers?: boolean;
|
|
746
|
-
|
|
1058
|
+
sql?: SqlConfig;
|
|
1059
|
+
getStructureDefinitions?: GetStructureDefinitions;
|
|
1060
|
+
resourceTypeHint?: string;
|
|
1061
|
+
expandValueSet?: ExpandValueSet;
|
|
1062
|
+
getUrlSuggestions?: GetUrlSuggestions;
|
|
1063
|
+
vimMode?: boolean;
|
|
747
1064
|
};
|
|
748
1065
|
|
|
749
1066
|
export type CodeEditorView = EditorView;
|
|
750
1067
|
|
|
1068
|
+
export type {
|
|
1069
|
+
ExpandValueSet,
|
|
1070
|
+
GetStructureDefinitions,
|
|
1071
|
+
} from "./fhir-autocomplete";
|
|
1072
|
+
export type { GetUrlSuggestions } from "./http";
|
|
1073
|
+
export type {
|
|
1074
|
+
SqlConfig,
|
|
1075
|
+
SqlMetadata,
|
|
1076
|
+
SqlQueryType,
|
|
1077
|
+
} from "./sql-completion";
|
|
1078
|
+
|
|
751
1079
|
export function CodeEditor({
|
|
752
1080
|
defaultValue,
|
|
753
1081
|
currentValue,
|
|
@@ -761,13 +1089,28 @@ export function CodeEditor({
|
|
|
761
1089
|
additionalExtensions,
|
|
762
1090
|
issueLineNumbers,
|
|
763
1091
|
foldGutter: enableFoldGutter = true,
|
|
764
|
-
lintGutter: enableLintGutter = true,
|
|
765
1092
|
lineNumbers: enableLineNumbers = true,
|
|
766
|
-
|
|
1093
|
+
sql,
|
|
1094
|
+
getStructureDefinitions,
|
|
1095
|
+
resourceTypeHint,
|
|
1096
|
+
expandValueSet,
|
|
1097
|
+
getUrlSuggestions,
|
|
1098
|
+
vimMode = false,
|
|
767
1099
|
}: CodeEditorProps) {
|
|
768
1100
|
const domRef = React.useRef(null);
|
|
769
1101
|
const [view, setView] = React.useState<EditorView | null>(null);
|
|
770
1102
|
|
|
1103
|
+
const safeDispatch = React.useCallback(
|
|
1104
|
+
(spec: Parameters<EditorView["dispatch"]>[0]) => {
|
|
1105
|
+
try {
|
|
1106
|
+
view?.dispatch(spec);
|
|
1107
|
+
} catch {
|
|
1108
|
+
// Ignore RangeError from stale decoration positions during reconfigure
|
|
1109
|
+
}
|
|
1110
|
+
},
|
|
1111
|
+
[view],
|
|
1112
|
+
);
|
|
1113
|
+
|
|
771
1114
|
const initialValue = React.useRef(defaultValue ?? "");
|
|
772
1115
|
|
|
773
1116
|
const onChangeComparment = React.useRef(new Compartment());
|
|
@@ -776,6 +1119,13 @@ export function CodeEditor({
|
|
|
776
1119
|
const readOnlyCompartment = React.useRef(new Compartment());
|
|
777
1120
|
const themeCompartment = React.useRef(new Compartment());
|
|
778
1121
|
const additionalExtensionsCompartment = React.useRef(new Compartment());
|
|
1122
|
+
const sqlCompletionCompartment = React.useRef(new Compartment());
|
|
1123
|
+
const fhirCompletionCompartment = React.useRef(new Compartment());
|
|
1124
|
+
const vimCompartment = React.useRef(new Compartment());
|
|
1125
|
+
const [sqlFunctions, setSqlFunctions] = React.useState<
|
|
1126
|
+
string[] | undefined
|
|
1127
|
+
>();
|
|
1128
|
+
const executeSqlRef = React.useRef(sql?.executeSql);
|
|
779
1129
|
|
|
780
1130
|
React.useEffect(() => {
|
|
781
1131
|
if (!domRef.current) {
|
|
@@ -787,10 +1137,28 @@ export function CodeEditor({
|
|
|
787
1137
|
state: EditorState.create({
|
|
788
1138
|
doc: initialValue.current,
|
|
789
1139
|
extensions: [
|
|
1140
|
+
vimCompartment.current.of(vimMode ? vim() : []),
|
|
790
1141
|
EditorView.contentAttributes.of({ "data-gramm": "false" }),
|
|
791
1142
|
readOnlyCompartment.current.of(EditorState.readOnly.of(false)),
|
|
792
1143
|
...(enableLineNumbers ? [lineNumbers()] : []),
|
|
793
|
-
...(enableFoldGutter
|
|
1144
|
+
...(enableFoldGutter
|
|
1145
|
+
? [
|
|
1146
|
+
foldGutter({
|
|
1147
|
+
markerDOM: (open) => {
|
|
1148
|
+
const el = document.createElement("span");
|
|
1149
|
+
el.style.display = "flex";
|
|
1150
|
+
el.style.alignItems = "center";
|
|
1151
|
+
el.style.justifyContent = "center";
|
|
1152
|
+
el.style.width = "100%";
|
|
1153
|
+
el.style.height = "100%";
|
|
1154
|
+
el.innerHTML = open
|
|
1155
|
+
? '<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6"/></svg>'
|
|
1156
|
+
: '<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m9 18 6-6-6-6"/></svg>';
|
|
1157
|
+
return el;
|
|
1158
|
+
},
|
|
1159
|
+
}),
|
|
1160
|
+
]
|
|
1161
|
+
: []),
|
|
794
1162
|
highlightSpecialChars(),
|
|
795
1163
|
history(),
|
|
796
1164
|
drawSelection(),
|
|
@@ -803,6 +1171,7 @@ export function CodeEditor({
|
|
|
803
1171
|
autocompletion({
|
|
804
1172
|
icons: false,
|
|
805
1173
|
maxRenderedOptions: 1000,
|
|
1174
|
+
defaultKeymap: false,
|
|
806
1175
|
addToOptions: [{ render: renderCompletionIcon, position: 20 }],
|
|
807
1176
|
optionClass: (_completion) =>
|
|
808
1177
|
"!px-2 !py-1 rounded-md aria-selected:!bg-bg-quaternary aria-selected:!text-text-primary hover:!bg-bg-secondary flex items-center gap-2",
|
|
@@ -816,27 +1185,58 @@ export function CodeEditor({
|
|
|
816
1185
|
}),
|
|
817
1186
|
rectangularSelection(),
|
|
818
1187
|
crosshairCursor(),
|
|
819
|
-
highlightActiveLine(),
|
|
820
|
-
highlightActiveLineGutter(),
|
|
821
1188
|
highlightSelectionMatches(),
|
|
1189
|
+
Prec.highest(
|
|
1190
|
+
keymap.of([
|
|
1191
|
+
{
|
|
1192
|
+
key: "Tab",
|
|
1193
|
+
run: (v) => {
|
|
1194
|
+
if (completionStatus(v.state) === "active") {
|
|
1195
|
+
return moveCompletionSelection(true)(v);
|
|
1196
|
+
}
|
|
1197
|
+
return false;
|
|
1198
|
+
},
|
|
1199
|
+
},
|
|
1200
|
+
{
|
|
1201
|
+
key: "Shift-Tab",
|
|
1202
|
+
run: (v) => {
|
|
1203
|
+
if (completionStatus(v.state) === "active") {
|
|
1204
|
+
return moveCompletionSelection(false)(v);
|
|
1205
|
+
}
|
|
1206
|
+
return false;
|
|
1207
|
+
},
|
|
1208
|
+
},
|
|
1209
|
+
{
|
|
1210
|
+
key: "Enter",
|
|
1211
|
+
run: (v) => {
|
|
1212
|
+
if (completionStatus(v.state) === "active") {
|
|
1213
|
+
return acceptCompletion(v);
|
|
1214
|
+
}
|
|
1215
|
+
return false;
|
|
1216
|
+
},
|
|
1217
|
+
},
|
|
1218
|
+
]),
|
|
1219
|
+
),
|
|
822
1220
|
themeCompartment.current.of(baseTheme),
|
|
823
1221
|
completionTheme,
|
|
824
1222
|
keymap.of([
|
|
825
1223
|
...closeBracketsKeymap,
|
|
1224
|
+
...completionKeymap.filter((b) => b.key !== "Enter"),
|
|
826
1225
|
...defaultKeymap,
|
|
827
1226
|
...searchKeymap,
|
|
828
1227
|
...historyKeymap,
|
|
829
1228
|
...foldKeymap,
|
|
830
|
-
...completionKeymap,
|
|
831
1229
|
...lintKeymap,
|
|
832
1230
|
]),
|
|
833
|
-
...(enableLintGutter ? [lintGutter()] : []),
|
|
834
1231
|
issueLinesField,
|
|
835
1232
|
errorTooltipHandler,
|
|
1233
|
+
EditorView.exceptionSink.of(() => {}),
|
|
836
1234
|
...customSearchExtension,
|
|
837
1235
|
onChangeComparment.current.of([]),
|
|
838
1236
|
onUpdateComparment.current.of([]),
|
|
839
1237
|
additionalExtensionsCompartment.current.of([]),
|
|
1238
|
+
sqlCompletionCompartment.current.of([]),
|
|
1239
|
+
fhirCompletionCompartment.current.of([]),
|
|
840
1240
|
],
|
|
841
1241
|
}),
|
|
842
1242
|
});
|
|
@@ -847,7 +1247,69 @@ export function CodeEditor({
|
|
|
847
1247
|
view.destroy();
|
|
848
1248
|
setView(() => null);
|
|
849
1249
|
};
|
|
850
|
-
}, [enableFoldGutter, enableLineNumbers,
|
|
1250
|
+
}, [enableFoldGutter, enableLineNumbers, vimMode]);
|
|
1251
|
+
|
|
1252
|
+
React.useEffect(() => {
|
|
1253
|
+
executeSqlRef.current = sql?.executeSql;
|
|
1254
|
+
});
|
|
1255
|
+
|
|
1256
|
+
React.useEffect(() => {
|
|
1257
|
+
if (!view || !sql) {
|
|
1258
|
+
if (view) {
|
|
1259
|
+
safeDispatch({
|
|
1260
|
+
effects: sqlCompletionCompartment.current.reconfigure([]),
|
|
1261
|
+
});
|
|
1262
|
+
}
|
|
1263
|
+
setSqlFunctions(undefined);
|
|
1264
|
+
return;
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
let cancelled = false;
|
|
1268
|
+
|
|
1269
|
+
fetchSqlMetadata(sql.executeSql)
|
|
1270
|
+
.then((metadata) => {
|
|
1271
|
+
if (cancelled) return;
|
|
1272
|
+
setSqlFunctions(metadata.functions);
|
|
1273
|
+
const extensions = buildSqlCompletionExtensions(
|
|
1274
|
+
metadata,
|
|
1275
|
+
(query, type) =>
|
|
1276
|
+
executeSqlRef.current?.(query, type) ?? Promise.resolve([]),
|
|
1277
|
+
);
|
|
1278
|
+
safeDispatch({
|
|
1279
|
+
effects: sqlCompletionCompartment.current.reconfigure(extensions),
|
|
1280
|
+
});
|
|
1281
|
+
})
|
|
1282
|
+
.catch(() => {});
|
|
1283
|
+
|
|
1284
|
+
return () => {
|
|
1285
|
+
cancelled = true;
|
|
1286
|
+
};
|
|
1287
|
+
}, [view, sql, safeDispatch]);
|
|
1288
|
+
|
|
1289
|
+
React.useEffect(() => {
|
|
1290
|
+
if (!view) return;
|
|
1291
|
+
if (getStructureDefinitions) {
|
|
1292
|
+
safeDispatch({
|
|
1293
|
+
effects: fhirCompletionCompartment.current.reconfigure(
|
|
1294
|
+
buildFhirCompletionExtension(
|
|
1295
|
+
getStructureDefinitions,
|
|
1296
|
+
resourceTypeHint,
|
|
1297
|
+
expandValueSet,
|
|
1298
|
+
),
|
|
1299
|
+
),
|
|
1300
|
+
});
|
|
1301
|
+
} else {
|
|
1302
|
+
safeDispatch({
|
|
1303
|
+
effects: fhirCompletionCompartment.current.reconfigure([]),
|
|
1304
|
+
});
|
|
1305
|
+
}
|
|
1306
|
+
}, [
|
|
1307
|
+
view,
|
|
1308
|
+
getStructureDefinitions,
|
|
1309
|
+
resourceTypeHint,
|
|
1310
|
+
expandValueSet,
|
|
1311
|
+
safeDispatch,
|
|
1312
|
+
]);
|
|
851
1313
|
|
|
852
1314
|
React.useEffect(() => {
|
|
853
1315
|
if (viewCallback && view) {
|
|
@@ -856,7 +1318,7 @@ export function CodeEditor({
|
|
|
856
1318
|
}, [view, viewCallback]);
|
|
857
1319
|
|
|
858
1320
|
React.useEffect(() => {
|
|
859
|
-
|
|
1321
|
+
safeDispatch({
|
|
860
1322
|
effects: onChangeComparment.current.reconfigure([
|
|
861
1323
|
EditorView.updateListener.of((update) => {
|
|
862
1324
|
if (update.docChanged && onChange) {
|
|
@@ -865,10 +1327,10 @@ export function CodeEditor({
|
|
|
865
1327
|
}),
|
|
866
1328
|
]),
|
|
867
1329
|
});
|
|
868
|
-
}, [
|
|
1330
|
+
}, [onChange, safeDispatch]);
|
|
869
1331
|
|
|
870
1332
|
React.useEffect(() => {
|
|
871
|
-
|
|
1333
|
+
safeDispatch({
|
|
872
1334
|
effects: onUpdateComparment.current.reconfigure([
|
|
873
1335
|
EditorView.updateListener.of((update) => {
|
|
874
1336
|
if (onUpdate) {
|
|
@@ -877,7 +1339,7 @@ export function CodeEditor({
|
|
|
877
1339
|
}),
|
|
878
1340
|
]),
|
|
879
1341
|
});
|
|
880
|
-
}, [
|
|
1342
|
+
}, [onUpdate, safeDispatch]);
|
|
881
1343
|
|
|
882
1344
|
// FIXME: it is probably better to have CM manage its state.
|
|
883
1345
|
React.useEffect(() => {
|
|
@@ -887,7 +1349,7 @@ export function CodeEditor({
|
|
|
887
1349
|
|
|
888
1350
|
const currentDoc = view.state.doc.toString();
|
|
889
1351
|
if (currentDoc !== currentValue) {
|
|
890
|
-
|
|
1352
|
+
safeDispatch({
|
|
891
1353
|
changes: {
|
|
892
1354
|
from: 0,
|
|
893
1355
|
to: currentDoc.length,
|
|
@@ -895,66 +1357,84 @@ export function CodeEditor({
|
|
|
895
1357
|
},
|
|
896
1358
|
});
|
|
897
1359
|
}
|
|
898
|
-
}, [currentValue, view]);
|
|
1360
|
+
}, [currentValue, view, safeDispatch]);
|
|
1361
|
+
|
|
1362
|
+
const getUrlSuggestionsRef = React.useRef(getUrlSuggestions);
|
|
1363
|
+
getUrlSuggestionsRef.current = getUrlSuggestions;
|
|
1364
|
+
|
|
1365
|
+
const stableGetUrlSuggestions = React.useMemo(() => {
|
|
1366
|
+
if (!getUrlSuggestions) return undefined;
|
|
1367
|
+
return ((path: string, method: string) =>
|
|
1368
|
+
getUrlSuggestionsRef.current?.(path, method) ?? []) as GetUrlSuggestions;
|
|
1369
|
+
}, [getUrlSuggestions]);
|
|
899
1370
|
|
|
900
1371
|
React.useEffect(() => {
|
|
901
1372
|
if (view === null) {
|
|
902
1373
|
return;
|
|
903
1374
|
}
|
|
904
|
-
|
|
1375
|
+
safeDispatch({
|
|
905
1376
|
effects: languageCompartment.current.reconfigure(
|
|
906
|
-
languageExtensions(mode,
|
|
1377
|
+
languageExtensions(mode, sqlFunctions, stableGetUrlSuggestions),
|
|
907
1378
|
),
|
|
908
1379
|
});
|
|
909
|
-
}, [mode, view,
|
|
1380
|
+
}, [mode, view, sqlFunctions, stableGetUrlSuggestions, safeDispatch]);
|
|
910
1381
|
|
|
911
1382
|
React.useEffect(() => {
|
|
912
1383
|
if (view === null) {
|
|
913
1384
|
return;
|
|
914
1385
|
}
|
|
915
|
-
|
|
1386
|
+
safeDispatch({
|
|
916
1387
|
effects: [
|
|
917
1388
|
readOnlyCompartment.current.reconfigure(
|
|
918
1389
|
EditorState.readOnly.of(readOnly),
|
|
919
1390
|
),
|
|
920
1391
|
],
|
|
921
1392
|
});
|
|
922
|
-
}, [readOnly, view]);
|
|
1393
|
+
}, [readOnly, view, safeDispatch]);
|
|
923
1394
|
|
|
924
1395
|
React.useEffect(() => {
|
|
925
1396
|
if (view === null) {
|
|
926
1397
|
return;
|
|
927
1398
|
}
|
|
928
|
-
|
|
1399
|
+
safeDispatch({
|
|
929
1400
|
effects: [
|
|
930
1401
|
themeCompartment.current.reconfigure(
|
|
931
1402
|
isReadOnlyTheme ? readOnlyTheme : baseTheme,
|
|
932
1403
|
),
|
|
933
1404
|
],
|
|
934
1405
|
});
|
|
935
|
-
}, [isReadOnlyTheme, view]);
|
|
1406
|
+
}, [isReadOnlyTheme, view, safeDispatch]);
|
|
936
1407
|
|
|
937
1408
|
React.useEffect(() => {
|
|
938
1409
|
if (view === null) {
|
|
939
1410
|
return;
|
|
940
1411
|
}
|
|
941
|
-
|
|
1412
|
+
safeDispatch({
|
|
1413
|
+
effects: [vimCompartment.current.reconfigure(vimMode ? vim() : [])],
|
|
1414
|
+
});
|
|
1415
|
+
}, [vimMode, view, safeDispatch]);
|
|
1416
|
+
|
|
1417
|
+
React.useEffect(() => {
|
|
1418
|
+
if (view === null) {
|
|
1419
|
+
return;
|
|
1420
|
+
}
|
|
1421
|
+
safeDispatch({
|
|
942
1422
|
effects: [
|
|
943
1423
|
additionalExtensionsCompartment.current.reconfigure(
|
|
944
1424
|
additionalExtensions ?? [],
|
|
945
1425
|
),
|
|
946
1426
|
],
|
|
947
1427
|
});
|
|
948
|
-
}, [additionalExtensions, view]);
|
|
1428
|
+
}, [additionalExtensions, view, safeDispatch]);
|
|
949
1429
|
|
|
950
1430
|
React.useEffect(() => {
|
|
951
1431
|
if (view === null) {
|
|
952
1432
|
return;
|
|
953
1433
|
}
|
|
954
|
-
|
|
1434
|
+
safeDispatch({
|
|
955
1435
|
effects: setIssueLinesEffect.of(issueLineNumbers ?? []),
|
|
956
1436
|
});
|
|
957
|
-
}, [issueLineNumbers, view]);
|
|
1437
|
+
}, [issueLineNumbers, view, safeDispatch]);
|
|
958
1438
|
|
|
959
1439
|
return <div className="h-full w-full" ref={domRef} id={id} />;
|
|
960
1440
|
}
|
|
@@ -1012,21 +1492,30 @@ const editorInputTheme = EditorView.theme({
|
|
|
1012
1492
|
});
|
|
1013
1493
|
|
|
1014
1494
|
const KeywordIcon = () => <Terminal size={16} color="#717684" />;
|
|
1015
|
-
const OperatorIcon = () => <
|
|
1495
|
+
const OperatorIcon = () => <ChevronsRight size={16} color="#717684" />;
|
|
1496
|
+
const TableIcon = () => <Table2 size={16} color="#717684" />;
|
|
1497
|
+
const HeaderIcon = () => <Heading size={16} color="#717684" />;
|
|
1016
1498
|
|
|
1017
1499
|
function getCompletionIcon(completion: Completion): React.FC | null {
|
|
1018
1500
|
if (completion.type === "function") return SquareFunctionIcon;
|
|
1019
1501
|
if (completion.type === "keyword") return KeywordIcon;
|
|
1020
1502
|
if (completion.type === "operator") return OperatorIcon;
|
|
1503
|
+
if (completion.type === "table") return TableIcon;
|
|
1504
|
+
if (completion.type === "header") return HeaderIcon;
|
|
1505
|
+
if (completion.type === "text") return TypCodeIcon;
|
|
1506
|
+
if (completion.type === "type") return ResourceIcon;
|
|
1507
|
+
if (completion.type === "search-param") return null;
|
|
1021
1508
|
const detail = completion.detail;
|
|
1022
1509
|
if (!detail) {
|
|
1023
1510
|
if (completion.type === "variable") return SquareFunctionIcon;
|
|
1024
|
-
return
|
|
1511
|
+
return TypCodeIcon;
|
|
1025
1512
|
}
|
|
1026
1513
|
const typeName = detail.replace(/\[\]$/, "");
|
|
1027
|
-
if (!typeName) return
|
|
1514
|
+
if (!typeName) return TypCodeIcon;
|
|
1515
|
+
// Search param types (TOKEN, REFERENCE) — no icon
|
|
1516
|
+
if (typeName === typeName.toUpperCase()) return null;
|
|
1028
1517
|
const firstChar = typeName[0];
|
|
1029
|
-
if (!firstChar) return
|
|
1518
|
+
if (!firstChar) return TypCodeIcon;
|
|
1030
1519
|
const isComplex = firstChar === firstChar.toUpperCase();
|
|
1031
1520
|
return isComplex ? ComplexTypeIcon : TypCodeIcon;
|
|
1032
1521
|
}
|
|
@@ -1039,6 +1528,8 @@ function renderCompletionIcon(completion: Completion): Node {
|
|
|
1039
1528
|
flushSync(() => {
|
|
1040
1529
|
createRoot(container).render(<Icon />);
|
|
1041
1530
|
});
|
|
1531
|
+
} else {
|
|
1532
|
+
container.style.display = "none";
|
|
1042
1533
|
}
|
|
1043
1534
|
return container;
|
|
1044
1535
|
}
|