@@ -420,6 +420,53 @@ const Configurator = ({
420420 if ( value === "visual" ) loadVisual ( true ) ;
421421 } ;
422422
423+ // Calculate line count for dynamic optimization
424+ const lineCount = React . useMemo ( ( ) => config . split ( '\n' ) . length , [ config ] ) ;
425+
426+ // Memoize autocomplete functions to prevent recreating options on every render
427+ const completeAfter = React . useCallback ( ( cm , pred ) => {
428+ if ( ! pred || pred ( ) ) {
429+ setTimeout ( ( ) => {
430+ if ( ! cm . state . completionActive ) cm . showHint ( { completeSingle : false } ) ;
431+ } , 100 ) ;
432+ }
433+ return CM . Pass ;
434+ } , [ ] ) ;
435+
436+ const completeIfInTag = React . useCallback ( ( cm ) => {
437+ return completeAfter ( cm , ( ) => {
438+ const token = cm . getTokenAt ( cm . getCursor ( ) ) ;
439+
440+ if ( token . type === "string" && ( ! / [ ' " ] $ / . test ( token . string ) || token . string . length === 1 ) ) return false ;
441+
442+ const inner = CM . innerMode ( cm . getMode ( ) , token . state ) . state ;
443+
444+ return inner . tagName ;
445+ } ) ;
446+ } , [ completeAfter ] ) ;
447+
448+ // Dynamic CodeMirror options based on document size
449+ const dynamicCodeMirrorOptions = React . useMemo ( ( ) => {
450+ const isLargeDocument = lineCount > 50 ;
451+
452+ return {
453+ mode : "xml" ,
454+ theme : "default" ,
455+ lineNumbers : ! isLargeDocument ,
456+ viewportMargin : isLargeDocument ? 10 : Infinity ,
457+ extraKeys : {
458+ "'<'" : completeAfter ,
459+ "' '" : completeIfInTag ,
460+ "'='" : completeIfInTag ,
461+ "Ctrl-Space" : "autocomplete" ,
462+ } ,
463+ hintOptions : { schemaInfo : tags } ,
464+ } ;
465+ } , [ lineCount , completeAfter , completeIfInTag ] ) ;
466+
467+ // Key to force CodeMirror recreation when crossing threshold
468+ const editorKey = React . useMemo ( ( ) => lineCount > 50 ? 'large' : 'small' , [ lineCount ] ) ;
469+
423470 const onChange = React . useCallback (
424471 ( config ) => {
425472 try {
@@ -451,27 +498,6 @@ const Configurator = ({
451498 return res ;
452499 } ;
453500
454- function completeAfter ( cm , pred ) {
455- if ( ! pred || pred ( ) ) {
456- setTimeout ( ( ) => {
457- if ( ! cm . state . completionActive ) cm . showHint ( { completeSingle : false } ) ;
458- } , 100 ) ;
459- }
460- return CM . Pass ;
461- }
462-
463- function completeIfInTag ( cm ) {
464- return completeAfter ( cm , ( ) => {
465- const token = cm . getTokenAt ( cm . getCursor ( ) ) ;
466-
467- if ( token . type === "string" && ( ! / [ ' " ] $ / . test ( token . string ) || token . string . length === 1 ) ) return false ;
468-
469- const inner = CM . innerMode ( cm . getMode ( ) , token . state ) . state ;
470-
471- return inner . tagName ;
472- } ) ;
473- }
474-
475501 const extra = (
476502 < p className = { configClass . elem ( "tags-link" ) } >
477503 Configure the labeling interface with tags.
@@ -504,6 +530,7 @@ const Configurator = ({
504530 { configure === "code" && (
505531 < div className = { configClass . elem ( "code" ) } style = { { display : configure === "code" ? undefined : "none" } } >
506532 < CodeEditor
533+ key = { editorKey }
507534 name = "code"
508535 id = "edit_code"
509536 value = { config }
@@ -512,20 +539,7 @@ const Configurator = ({
512539 detach
513540 border
514541 extensions = { [ "hint" , "xml-hint" ] }
515- options = { {
516- mode : "xml" ,
517- theme : "default" ,
518- lineNumbers : true ,
519- extraKeys : {
520- "'<'" : completeAfter ,
521- // "'/'": completeIfAfterLt,
522- "' '" : completeIfInTag ,
523- "'='" : completeIfInTag ,
524- "Ctrl-Space" : "autocomplete" ,
525- } ,
526- hintOptions : { schemaInfo : tags } ,
527- } }
528- // don't close modal with Escape while editing config
542+ options = { dynamicCodeMirrorOptions }
529543 onKeyDown = { ( editor , e ) => {
530544 if ( e . code === "Escape" ) e . stopPropagation ( ) ;
531545 } }
0 commit comments