@@ -13,7 +13,7 @@ import CodeMirror, {
1313 WidgetType
1414} from "@uiw/react-codemirror"
1515import { json , jsonParseLinter } from "@codemirror/lang-json"
16- import { StreamLanguage , syntaxTree } from "@codemirror/language"
16+ import { StreamLanguage , syntaxTree , type TagStyle } from "@codemirror/language"
1717import { Diagnostic , linter } from "@codemirror/lint"
1818import prettier from "prettier/standalone"
1919import parserBabel from "prettier/plugins/babel"
@@ -68,6 +68,7 @@ export interface EditorInputProps extends Omit<Code0Component<HTMLDivElement>, '
6868 showValidation ?: boolean ,
6969 formatter ?: ( value : string ) => Promise < string >
7070 basicSetup ?: BasicSetupOptions
71+ tokenStyles ?: TagStyle [ ]
7172}
7273
7374class ReactAnchorWidget extends WidgetType {
@@ -98,31 +99,6 @@ class ReactAnchorWidget extends WidgetType {
9899 }
99100}
100101
101- const myTheme = createTheme ( {
102- theme : 'light' ,
103- settings : {
104- background : 'transparent' ,
105- backgroundImage : '' ,
106- foreground : 'rgba(255,255,255, 0.75)' ,
107- caret : 'gray' ,
108- selection : 'rgba(112,179,255,0.25)' ,
109- selectionMatch : 'rgba(112,179,255,0.1)' ,
110- fontSize : "0.8rem" ,
111- gutterBackground : 'transparent' ,
112- gutterForeground : 'rgba(255,255,255, 0.5)' ,
113- gutterBorder : 'transparent' ,
114- gutterActiveForeground : 'rgba(255,255,255, 1)' ,
115- lineHighlight : 'rgba(255,255,255, 0.1)' ,
116- } ,
117- styles : [
118- { tag : t . squareBracket , color : hashToColor ( "squareBracket" ) } ,
119- { tag : t . bracket , color : hashToColor ( "bracket" ) } ,
120- { tag : t . string , color : hashToColor ( "Text" ) } ,
121- { tag : t . bool , color : hashToColor ( "Boolean" ) } ,
122- { tag : t . number , color : hashToColor ( "Number" ) } ,
123- ]
124- } )
125-
126102
127103// Helper function to check if value is a ReactNode
128104const isReactNode = ( value : any ) : value is React . ReactNode => {
@@ -134,6 +110,7 @@ export const Editor: React.FC<EditorInputProps> = (props) => {
134110 language,
135111 tokenizer,
136112 tokenHighlights,
113+ tokenStyles = [ ] ,
137114 suggestions,
138115 onChange,
139116 extensions = [ ] ,
@@ -149,34 +126,31 @@ export const Editor: React.FC<EditorInputProps> = (props) => {
149126 ...rest
150127 } = props
151128
152- const [ formatted , setFormatted ] = React . useState ( "" )
129+ const [ formatted , setFormatted ] = React . useState < string > ( )
153130 const ref = React . useRef < HTMLDivElement > ( null )
154131 const [ anchors , setAnchors ] = React . useState < Map < HTMLElement , { type : string , value : string } > > ( new Map ( ) )
155132 const [ diagnostics , setDiagnostics ] = React . useState < readonly Diagnostic [ ] > ( [ ] )
133+ const [ selection , setSelection ] = React . useState < { from : number , to : number } | null > ( null )
156134 const [ customSuggestion , setCustomSuggestion ] = React . useState < {
157135 component : React . ReactNode ;
158136 position : { top : number ; left : number } ;
159137 } | null > ( null )
160138 const containerRef = React . useRef < HTMLDivElement > ( null )
161139
162- const jsonFormatter = ( value : string ) => {
163- return prettier . format ( JSON . stringify ( value ) , {
164- parser : "json" ,
165- plugins : [ parserBabel , parserEstree ] ,
166- printWidth : 1
167- } )
168- }
169-
170140 React . useEffect ( ( ) => {
171141 ( async ( ) => {
172142 try {
173- const pretty = formatter ? await formatter ( initialValue ) : language === "json" ? await jsonFormatter ( initialValue ) : initialValue
143+ const pretty = formatter ? await formatter ( initialValue ) : language === "json" ? await prettier . format ( JSON . stringify ( initialValue ) , {
144+ parser : "json" ,
145+ plugins : [ parserBabel , parserEstree ] ,
146+ printWidth : 1
147+ } ) : initialValue
174148 setFormatted ( pretty )
175149 } catch ( e ) {
176- setFormatted ( JSON . stringify ( initialValue ) ?? "" )
150+ setFormatted ( language == "json" ? JSON . stringify ( initialValue ) : initialValue )
177151 }
178152 } ) ( )
179- } , [ initialValue ] )
153+ } , [ ] )
180154
181155 const internalExtensions = React . useMemo ( ( ) => {
182156 const internExtensions : Extension [ ] = [ ...extensions ]
@@ -287,10 +261,13 @@ export const Editor: React.FC<EditorInputProps> = (props) => {
287261 } )
288262
289263 if ( foundKey && ! userTag ) {
290- builder . add ( node . from , node . to , Decoration . replace ( {
291- widget : new ReactAnchorWidget ( foundKey , rawText ) ,
292- point : true
293- } ) )
264+ const renderFn = tokenHighlights ?. [ foundKey ] ( { content : rawText } )
265+ if ( ! ! renderFn ) {
266+ builder . add ( node . from , node . to , Decoration . replace ( {
267+ widget : new ReactAnchorWidget ( foundKey , rawText ) ,
268+ point : true
269+ } ) )
270+ }
294271 } else if ( userTag ) {
295272 builder . add ( node . from , node . to , Decoration . replace ( {
296273 widget : new ReactAnchorWidget ( userTag , rawText ) ,
@@ -310,9 +287,8 @@ export const Editor: React.FC<EditorInputProps> = (props) => {
310287 } ) )
311288
312289 return internExtensions
313- } , [ language , tokenizer , tokenHighlights , extensions , suggestions , ref . current ] )
290+ } , [ ref ] )
314291
315- const [ selection , setSelection ] = React . useState < { from : number , to : number } | null > ( null )
316292
317293 const handleUpdate = React . useCallback ( ( viewUpdate : any ) => {
318294 if ( viewUpdate . docChanged || viewUpdate . viewportChanged || viewUpdate . selectionSet ) {
@@ -350,34 +326,48 @@ export const Editor: React.FC<EditorInputProps> = (props) => {
350326 }
351327
352328
353- window . requestAnimationFrame ( ( ) => {
354- const foundNodes = containerRef . current ?. querySelectorAll ( '.cm-react-anchor' )
355- const newAnchors = new Map ( )
329+ const foundNodes = containerRef . current ?. querySelectorAll ( '.cm-react-anchor' )
330+ const newAnchors = new Map ( )
356331
357- foundNodes ?. forEach ( ( node : any ) => {
358- newAnchors . set ( node , {
359- type : node . dataset . type ,
360- value : node . dataset . value
361- } )
332+ foundNodes ?. forEach ( ( node : any ) => {
333+ newAnchors . set ( node , {
334+ type : node . dataset . type ,
335+ value : node . dataset . value
362336 } )
363-
364- setAnchors ( newAnchors )
365337 } )
338+
339+ setAnchors ( newAnchors )
366340 }
367341 } , [ selection ] )
368342
369- React . useEffect ( ( ) => {
370- if ( containerRef . current ) {
371- const timer = window . setTimeout ( ( ) => {
372- const event = new CustomEvent ( 'scroll' ) ;
373- containerRef . current ?. dispatchEvent ( event ) ;
374- handleUpdate ( { docChanged : true , viewportChanged : true , selectionSet : false } ) ;
375- } , 50 ) ;
376- return ( ) => clearTimeout ( timer ) ;
377- }
378- return ( ) => {
379- }
380- } , [ handleUpdate ] ) ;
343+ const myTheme = React . useMemo (
344+ ( ) => createTheme ( {
345+ theme : 'light' ,
346+ settings : {
347+ background : 'transparent' ,
348+ backgroundImage : '' ,
349+ foreground : 'rgba(255,255,255, 0.75)' ,
350+ caret : 'gray' ,
351+ selection : 'rgba(112,179,255,0.25)' ,
352+ selectionMatch : 'rgba(112,179,255,0.1)' ,
353+ fontSize : "0.8rem" ,
354+ gutterBackground : 'transparent' ,
355+ gutterForeground : 'rgba(255,255,255, 0.5)' ,
356+ gutterBorder : 'transparent' ,
357+ gutterActiveForeground : 'rgba(255,255,255, 1)' ,
358+ lineHighlight : 'rgba(255,255,255, 0.1)' ,
359+ } ,
360+ styles : [
361+ { tag : t . squareBracket , color : hashToColor ( "squareBracket" ) } ,
362+ { tag : t . bracket , color : hashToColor ( "bracket" ) } ,
363+ { tag : t . string , color : hashToColor ( "Text" ) } ,
364+ { tag : t . bool , color : hashToColor ( "Boolean" ) } ,
365+ { tag : t . number , color : hashToColor ( "Number" ) } ,
366+ ...tokenStyles
367+ ]
368+ } ) ,
369+ [ tokenStyles ]
370+ )
381371
382372 return (
383373 < ScrollArea h = { "100%" } type = { "scroll" } >
@@ -458,6 +448,7 @@ export const Editor: React.FC<EditorInputProps> = (props) => {
458448 ) : null }
459449 </ Flex >
460450 </ div >
451+
461452 ) }
462453 { showTooltips && (
463454 < div className = { "editor__tools" } >
@@ -488,7 +479,7 @@ export const Editor: React.FC<EditorInputProps> = (props) => {
488479 < CodeMirror
489480 width = "100%"
490481 height = "100%"
491- value = { language === "json" ? formatted : initialValue }
482+ value = { formatted }
492483 theme = { myTheme }
493484 readOnly = { disabled || readonly }
494485 editable = { ! disabled }
@@ -526,12 +517,13 @@ export const Editor: React.FC<EditorInputProps> = (props) => {
526517 } ) }
527518
528519 { customSuggestion && createPortal (
529- < div key = { customSuggestion . position . top + '-' + customSuggestion . position . left } ref = { ref } style = { {
530- position : 'fixed' ,
531- top : customSuggestion . position . top ,
532- left : customSuggestion . position . left ,
533- zIndex : 9999 ,
534- } }
520+ < div key = { customSuggestion . position . top + '-' + customSuggestion . position . left } ref = { ref }
521+ style = { {
522+ position : 'fixed' ,
523+ top : customSuggestion . position . top ,
524+ left : customSuggestion . position . left ,
525+ zIndex : 9999 ,
526+ } }
535527 >
536528 { customSuggestion . component }
537529 </ div > ,
0 commit comments