diff --git a/web/apps/labelstudio/src/pages/CreateProject/Config/Config.jsx b/web/apps/labelstudio/src/pages/CreateProject/Config/Config.jsx index 08a87990b7a2..bca587aab5d4 100644 --- a/web/apps/labelstudio/src/pages/CreateProject/Config/Config.jsx +++ b/web/apps/labelstudio/src/pages/CreateProject/Config/Config.jsx @@ -420,6 +420,53 @@ const Configurator = ({ if (value === "visual") loadVisual(true); }; + // Calculate line count for dynamic optimization + const lineCount = React.useMemo(() => config.split('\n').length, [config]); + + // Memoize autocomplete functions to prevent recreating options on every render + const completeAfter = React.useCallback((cm, pred) => { + if (!pred || pred()) { + setTimeout(() => { + if (!cm.state.completionActive) cm.showHint({ completeSingle: false }); + }, 100); + } + return CM.Pass; + }, []); + + const completeIfInTag = React.useCallback((cm) => { + return completeAfter(cm, () => { + const token = cm.getTokenAt(cm.getCursor()); + + if (token.type === "string" && (!/['"]$/.test(token.string) || token.string.length === 1)) return false; + + const inner = CM.innerMode(cm.getMode(), token.state).state; + + return inner.tagName; + }); + }, [completeAfter]); + + // Dynamic CodeMirror options based on document size + const dynamicCodeMirrorOptions = React.useMemo(() => { + const isLargeDocument = lineCount > 50; + + return { + mode: "xml", + theme: "default", + lineNumbers: !isLargeDocument, + viewportMargin: isLargeDocument ? 10 : Infinity, + extraKeys: { + "'<'": completeAfter, + "' '": completeIfInTag, + "'='": completeIfInTag, + "Ctrl-Space": "autocomplete", + }, + hintOptions: { schemaInfo: tags }, + }; + }, [lineCount, completeAfter, completeIfInTag]); + + // Key to force CodeMirror recreation when crossing threshold + const editorKey = React.useMemo(() => lineCount > 50 ? 'large' : 'small', [lineCount]); + const onChange = React.useCallback( (config) => { try { @@ -451,27 +498,6 @@ const Configurator = ({ return res; }; - function completeAfter(cm, pred) { - if (!pred || pred()) { - setTimeout(() => { - if (!cm.state.completionActive) cm.showHint({ completeSingle: false }); - }, 100); - } - return CM.Pass; - } - - function completeIfInTag(cm) { - return completeAfter(cm, () => { - const token = cm.getTokenAt(cm.getCursor()); - - if (token.type === "string" && (!/['"]$/.test(token.string) || token.string.length === 1)) return false; - - const inner = CM.innerMode(cm.getMode(), token.state).state; - - return inner.tagName; - }); - } - const extra = (

Configure the labeling interface with tags. @@ -504,6 +530,7 @@ const Configurator = ({ {configure === "code" && (

{ if (e.code === "Escape") e.stopPropagation(); }}