1- import { useMemo } from 'react' ;
1+ import { useMemo , useRef , useCallback } from 'react' ;
22import CodeMirror from '@uiw/react-codemirror' ;
33import { json } from '@codemirror/lang-json' ;
44import { keymap , EditorView } from '@codemirror/view' ;
55import { HighlightStyle , syntaxHighlighting } from '@codemirror/language' ;
6+ import { autocompletion } from '@codemirror/autocomplete' ;
7+ import type { CompletionContext } from '@codemirror/autocomplete' ;
68import { tags } from '@lezer/highlight' ;
79import { useTheme } from '../../hooks/useTheme' ;
10+ import type { SchemaObject , OpenApiSpec } from '../../openapi' ;
11+ import { jsonSchemaComplete } from '../../utils/jsonSchemaCompletion' ;
812
913interface JsonEditorProps {
1014 value : string ;
1115 onChange : ( value : string ) => void ;
16+ schema ?: SchemaObject | null ;
17+ spec ?: OpenApiSpec | null ;
1218}
1319
1420const ctrlEnterPassthrough = keymap . of ( [
@@ -41,6 +47,32 @@ const lightTheme = EditorView.theme({
4147 borderRight : '1px solid rgb(226 232 240)' , // slate-200
4248 color : 'rgb(148 163 184)' , // slate-400
4349 } ,
50+ '.cm-tooltip-autocomplete' : {
51+ backgroundColor : 'rgb(255 255 255)' ,
52+ border : '1px solid rgb(226 232 240)' ,
53+ borderRadius : '6px' ,
54+ boxShadow : '0 4px 6px -1px rgb(0 0 0 / 0.1)' ,
55+ fontSize : '11px' ,
56+ } ,
57+ '.cm-tooltip-autocomplete ul li' : {
58+ padding : '2px 8px' ,
59+ } ,
60+ '.cm-tooltip-autocomplete ul li[aria-selected]' : {
61+ backgroundColor : 'rgb(224 231 255)' , // primary-100
62+ color : 'rgb(67 56 202)' , // primary-700
63+ } ,
64+ '.cm-completionDetail' : {
65+ color : 'rgb(148 163 184)' , // slate-400
66+ fontStyle : 'normal' ,
67+ marginLeft : '8px' ,
68+ } ,
69+ '.cm-completionInfo' : {
70+ fontSize : '11px' ,
71+ padding : '4px 8px' ,
72+ backgroundColor : 'rgb(255 255 255)' ,
73+ border : '1px solid rgb(226 232 240)' ,
74+ borderRadius : '6px' ,
75+ } ,
4476} , { dark : false } ) ;
4577
4678const lightHighlight = HighlightStyle . define ( [
@@ -75,6 +107,32 @@ const darkTheme = EditorView.theme({
75107 borderRight : '1px solid rgb(51 65 85 / 0.5)' , // slate-700/50
76108 color : 'rgb(71 85 105)' , // slate-600
77109 } ,
110+ '.cm-tooltip-autocomplete' : {
111+ backgroundColor : 'rgb(30 41 59)' , // slate-800
112+ border : '1px solid rgb(51 65 85)' , // slate-700
113+ borderRadius : '6px' ,
114+ boxShadow : '0 4px 6px -1px rgb(0 0 0 / 0.3)' ,
115+ fontSize : '11px' ,
116+ } ,
117+ '.cm-tooltip-autocomplete ul li' : {
118+ padding : '2px 8px' ,
119+ } ,
120+ '.cm-tooltip-autocomplete ul li[aria-selected]' : {
121+ backgroundColor : 'rgb(55 48 163 / 0.4)' , // primary-800/40
122+ color : 'rgb(165 180 252)' , // primary-300
123+ } ,
124+ '.cm-completionDetail' : {
125+ color : 'rgb(100 116 139)' , // slate-500
126+ fontStyle : 'normal' ,
127+ marginLeft : '8px' ,
128+ } ,
129+ '.cm-completionInfo' : {
130+ fontSize : '11px' ,
131+ padding : '4px 8px' ,
132+ backgroundColor : 'rgb(30 41 59)' , // slate-800
133+ border : '1px solid rgb(51 65 85)' , // slate-700
134+ borderRadius : '6px' ,
135+ } ,
78136} , { dark : true } ) ;
79137
80138const darkHighlight = HighlightStyle . define ( [
@@ -86,15 +144,25 @@ const darkHighlight = HighlightStyle.define([
86144 { tag : tags . punctuation , color : 'rgb(148 163 184)' } , // slate-400
87145] ) ;
88146
89- export function JsonEditor ( { value, onChange } : JsonEditorProps ) {
147+ export function JsonEditor ( { value, onChange, schema , spec } : JsonEditorProps ) {
90148 const { isDark } = useTheme ( ) ;
91149
150+ const schemaRef = useRef ( schema ) ;
151+ const specRef = useRef ( spec ) ;
152+ schemaRef . current = schema ;
153+ specRef . current = spec ;
154+
155+ const completionSource = useCallback ( ( ctx : CompletionContext ) => {
156+ return jsonSchemaComplete ( ctx , schemaRef . current , specRef . current ) ;
157+ } , [ ] ) ;
158+
92159 const extensions = useMemo ( ( ) => [
93160 json ( ) ,
94161 ctrlEnterPassthrough ,
95162 isDark ? darkTheme : lightTheme ,
96163 syntaxHighlighting ( isDark ? darkHighlight : lightHighlight ) ,
97- ] , [ isDark ] ) ;
164+ autocompletion ( { override : [ completionSource ] , activateOnTyping : true } ) ,
165+ ] , [ isDark , completionSource ] ) ;
98166
99167 return (
100168 < div className = "h-full [&_.cm-editor]:!h-full [&_.cm-editor]:!outline-none [&_.cm-editor]:!text-xs [&_.cm-scroller]:!font-mono" >
@@ -108,6 +176,8 @@ export function JsonEditor({ value, onChange }: JsonEditorProps) {
108176 lineNumbers : false ,
109177 foldGutter : false ,
110178 highlightActiveLine : false ,
179+ completionKeymap : false ,
180+ autocompletion : false ,
111181 } }
112182 />
113183 </ div >
0 commit comments