rfellows commented on code in PR #10144:
URL: https://github.com/apache/nifi/pull/10144#discussion_r2240616575


##########
nifi-frontend/src/main/frontend/package.json:
##########
@@ -25,7 +25,14 @@
         "@angular/platform-browser": "19.2.14",
         "@angular/platform-browser-dynamic": "19.2.14",
         "@angular/router": "19.2.14",
-        "@ctrl/ngx-codemirror": "^7.0.0",
+        "@codemirror/autocomplete": "^6.8.14",
+        "@codemirror/commands": "^6.8.0",
+        "@codemirror/language": "^6.10.8",
+        "@codemirror/language-data": "^6.5.1",
+        "@codemirror/lang-javascript": "^6.2.2",

Review Comment:
   I see we depend on the javascript language from code mirror but it seems we 
actually import languages from other packages as well that are not in the 
package.json. Is that by design? How does it work (xml, yaml, json, markdown)
   
   do we also need to add these?
   ```
   "@codemirror/lang-json": "^6.0.1",
   "@codemirror/lang-xml": "^6.1.0", 
   "@codemirror/lang-yaml": "^6.1.1",
   "@codemirror/lang-markdown": "^6.2.5"
   ```
   



##########
nifi-frontend/src/main/frontend/libs/shared/src/services/codemirror-nifi-language-package.service.ts:
##########
@@ -676,375 +957,176 @@ export class NfEl {
                     stream.next();
                     return null;
                 }
-            };
-        });
-    }
-
-    private parameterKeyRegex = /^[a-zA-Z0-9-_. ]+/;
-
-    private parameters: string[] = [];
-    private parameterRegex = new RegExp('^$');
-
-    private parameterDetails: { [key: string]: Parameter } = {};
-    private parametersSupported = false;
-
-    private subjectlessFunctions: string[] = [];
-    private functions: string[] = [];
-
-    private subjectlessFunctionRegex = new RegExp('^$');
-    private functionRegex = new RegExp('^$');
-
-    private functionDetails: { [key: string]: ElFunction } = {};
-
-    // valid context states
-    private static readonly SUBJECT: string = 'subject';
-    private static readonly FUNCTION: string = 'function';
-    private static readonly SUBJECT_OR_FUNCTION: string = 
'subject-or-function';
-    private static readonly EXPRESSION: string = 'expression';
-    private static readonly ARGUMENTS: string = 'arguments';
-    private static readonly ARGUMENT: string = 'argument';
-    private static readonly PARAMETER: string = 'parameter';
-    private static readonly SINGLE_QUOTE_PARAMETER: string = 
'single-quote-parameter';
-    private static readonly DOUBLE_QUOTE_PARAMETER: string = 
'double-quote-parameter';
-    private static readonly INVALID: string = 'invalid';
-
-    /**
-     * Handles dollars identifies on the stream.
-     *
-     * @param {string} startChar    The start character
-     * @param {string} context      The context to transition to if we match 
on the specified start character
-     * @param {object} stream       The character stream
-     * @param {object} states       The states
-     */
-    private handleStart(startChar: string, context: string, stream: 
StringStream, states: any): null | string {
-        // determine the number of sequential start chars
-        let startCharCount = 0;
-        stream.eatWhile(function (ch: string) {
-            if (ch === startChar) {
-                startCharCount++;
-                return true;
-            }
-            return false;
-        });
-
-        // if there is an even number of consecutive start chars this 
expression is escaped
-        if (startCharCount % 2 === 0) {
-            // do not style an escaped expression
-            return null;
-        }
-
-        // if there was an odd number of consecutive start chars and there was 
more than 1
-        if (startCharCount > 1) {
-            // back up one char so we can process the start sequence next 
iteration
-            stream.backUp(1);
-
-            // do not style the preceding start chars
-            return null;
-        }
-
-        // if the next character isn't the start of an expression
-        if (stream.peek() === '{') {
-            // consume the open curly
-            stream.next();
-
-            if (NfEl.PARAMETER === context) {
-                // there may be an optional single/double quote
-                if (stream.peek() === "'") {
-                    // consume the single quote
-                    stream.next();
-
-                    // new expression start
-                    states.push({
-                        context: NfEl.SINGLE_QUOTE_PARAMETER
-                    });
-                } else if (stream.peek() === '"') {
-                    // consume the double quote
-                    stream.next();
-
-                    // new expression start
-                    states.push({
-                        context: NfEl.DOUBLE_QUOTE_PARAMETER
-                    });
-                } else {
-                    // new expression start
-                    states.push({
-                        context: NfEl.PARAMETER
-                    });
-                }
-            } else {
-                // new expression start
-                states.push({
-                    context: context
-                });
-            }
-
-            // consume any addition whitespace
-            stream.eatSpace();
-
-            return 'bracket';
-        }
-        // not a valid start sequence
-        return null;
-    }
-
-    /**
-     * Handles dollars identifies on the stream.
-     *
-     * @param {StringStream} stream   The character stream
-     * @param {object} state    The current state
-     */
-    private handleStringLiteral(stream: StringStream, state: any): string | 
null {
-        const current: string | null = stream.next();
-        let foundTrailing = false;
-        let foundEscapeChar = false;
-
-        // locate a closing string delimitor
-        const foundStringLiteral: boolean = stream.eatWhile(function (ch: 
string) {
-            // we've just found the trailing delimitor, stop
-            if (foundTrailing) {
-                return false;
-            }
-
-            // if this is the trailing delimitor, only consume
-            // if we did not see the escape character on the
-            // previous iteration
-            if (ch === current) {
-                foundTrailing = !foundEscapeChar;
-            }
-
-            // reset the escape character flag
-            foundEscapeChar = false;
-
-            // if this is the escape character, set the flag
-            if (ch === '\\') {
-                foundEscapeChar = true;
-            }
-
-            // consume this character
-            return true;
-        });
-
-        // if we found the trailing delimitor
-        if (foundStringLiteral) {
-            return 'string';
-        }
-
-        // there is no trailing delimitor... clear the current context
-        state.context = NfEl.INVALID;
-        stream.skipToEnd();
-        return null;
-    }
-
-    private handleParameterEnd(
-        stream: StringStream,
-        state: any,
-        states: any,
-        parameterPredicate: () => boolean
-    ): string | null {
-        if (parameterPredicate()) {
-            // consume the single/double quote
-            stream.next();
-
-            // verify the next character closes the parameter reference
-            if (stream.peek() === '}') {
-                // -----------------
-                // end of expression
-                // -----------------
-
-                // consume the close
-                stream.next();
-
-                // signifies the end of a parameter reference
-                if (typeof states.pop() === 'undefined') {
-                    return null;
-                } else {
-                    // style as expression
-                    return 'bracket';
-                }
-            } else {
-                // ----------
-                // unexpected
-                // ----------
+            },
+            getAutocompletions: (viewContainerRef: ViewContainerRef) => {
+                return (context: CompletionContext): CompletionResult | null 
=> {
+                    if (!context.explicit) {
+                        return null;
+                    }
 
-                // consume and move along
-                stream.skipToEnd();
-                state.context = NfEl.INVALID;
+                    // Analyze the text around the cursor position to 
determine context
+                    const cursorPos = context.pos;
+                    const doc = context.state.doc;
+                    const lineInfo = doc.lineAt(cursorPos);
+                    const lineText = lineInfo.text;
+                    const cursorInLine = cursorPos - lineInfo.from;
+
+                    // Function to determine if cursor is within specific 
syntax
+                    const getCursorContext = (): string => {
+                        // Look backward from cursor to find opening syntax
+                        let parameterStart = -1;
+                        let elStart = -1;
+                        let parameterEnd = -1;
+                        let elEnd = -1;
+
+                        // Find the most recent parameter or EL function start 
before cursor
+                        for (let i = cursorInLine - 1; i >= 0; i--) {
+                            if (lineText.charAt(i) === '{' && i > 0) {
+                                if (lineText.charAt(i - 1) === '#' && 
parameterStart === -1) {
+                                    parameterStart = i - 1;
+                                }
+                                if (lineText.charAt(i - 1) === '$' && elStart 
=== -1) {
+                                    elStart = i - 1;
+                                }
+                            }
+                        }
 
-                // unexpected...
-                return null;
-            }
-        } else {
-            // ----------
-            // unexpected
-            // ----------
+                        // Find the next closing brace after cursor
+                        for (let i = cursorInLine; i < lineText.length; i++) {
+                            if (lineText.charAt(i) === '}') {
+                                if (parameterStart !== -1 && parameterEnd === 
-1) {
+                                    parameterEnd = i;
+                                }
+                                if (elStart !== -1 && elEnd === -1) {
+                                    elEnd = i;
+                                }
+                                break;
+                            }
+                        }
 
-            // consume and move along
-            stream.skipToEnd();
-            state.context = NfEl.INVALID;
+                        // Determine which context we're in based on the most 
recent opening
+                        if (parameterStart !== -1 && parameterEnd !== -1) {
+                            if (elStart === -1 || parameterStart > elStart) {
+                                return CodemirrorNifiLanguagePackage.PARAMETER;
+                            }
+                        }
 
-            // unexpected...
-            return null;
-        }
-    }
+                        if (elStart !== -1 && elEnd !== -1) {
+                            if (parameterStart === -1 || elStart > 
parameterStart) {
+                                return 
CodemirrorNifiLanguagePackage.EXPRESSION;
+                            }
+                        }
 
-    public setViewContainerRef(viewContainerRef: ViewContainerRef, renderer: 
Renderer2): void {
-        this.viewContainerRef = viewContainerRef;
-        this.renderer = renderer;
-    }
+                        return CodemirrorNifiLanguagePackage.INVALID;
+                    };
 
-    public configureAutocomplete(): void {
-        const self: NfEl = this;
-
-        CodeMirror.commands.autocomplete = function (cm: Editor) {
-            CodeMirror.showHint(cm, function (editor: Editor) {
-                // Find the token at the cursor
-                const cursor: CodeMirror.Position = editor.getCursor();
-                const token: CodeMirror.Token = editor.getTokenAt(cursor);
-                let includeAll = false;
-                const state = token.state.get();
-
-                // whether the current context is within a function
-                const isFunction = function (context: string): boolean {
-                    // attempting to match a function name or already 
successfully matched a function name
-                    return context === NfEl.FUNCTION || context === 
NfEl.ARGUMENTS;
-                };
+                    const detectedContext = getCursorContext();
 
-                // whether the current context is within a subject-less 
funciton
-                const isSubjectlessFunction = function (context: string): 
boolean {
-                    // within an expression when no characters are found or 
when the string may be an attribute or a function
-                    return context === NfEl.EXPRESSION || context === 
NfEl.SUBJECT_OR_FUNCTION;
-                };
+                    // whether the current context is within a function
+                    const isFunction = (context: string): boolean => {
+                        // attempting to match a function name or already 
successfully matched a function name
+                        return (
+                            context === CodemirrorNifiLanguagePackage.FUNCTION 
||
+                            context === 
CodemirrorNifiLanguagePackage.ARGUMENTS ||
+                            context === 
CodemirrorNifiLanguagePackage.EXPRESSION ||
+                            context === 
CodemirrorNifiLanguagePackage.SUBJECT_OR_FUNCTION
+                        );
+                    };
+
+                    // whether the current context is within a parameter 
reference
+                    const isParameterReference = (context: string): boolean => 
{
+                        // attempting to match a parameter name or already 
successfully matched a parameter name
+                        return (
+                            context === 
CodemirrorNifiLanguagePackage.PARAMETER ||
+                            context === 
CodemirrorNifiLanguagePackage.SINGLE_QUOTE_PARAMETER ||
+                            context === 
CodemirrorNifiLanguagePackage.DOUBLE_QUOTE_PARAMETER
+                        );
+                    };
 
-                // whether the current context is within a parameter reference
-                const isParameterReference = function (context: string): 
boolean {
-                    // attempting to match a function name or already 
successfully matched a function name
-                    return (
-                        context === NfEl.PARAMETER ||
-                        context === NfEl.SINGLE_QUOTE_PARAMETER ||
-                        context === NfEl.DOUBLE_QUOTE_PARAMETER
-                    );
-                };
+                    let options: string[] = [];
+                    let useFunctionDetails = true;
 
-                // only support suggestion in certain cases
-                const context: string = state.context;
-                if (!isSubjectlessFunction(context) && !isFunction(context) && 
!isParameterReference(context)) {
-                    return null;
-                }
+                    // determine the options based on the detected context
+                    console.log('detectedContext', detectedContext);

Review Comment:
   Probably left here on accident, please remove.



##########
nifi-frontend/src/main/frontend/apps/update-attribute/src/app/pages/update-attribute/ui/ua-editor/ua-editor.component.ts:
##########
@@ -84,44 +157,78 @@ export class UaEditor implements OnDestroy {
         if (this.isRequired) {
             
this.uaEditorForm.get('value')?.setValidators([Validators.required]);
         }
-
-        this.nfel.setViewContainerRef(this.viewContainerRef, this.renderer);
-        this.nfel.configureAutocomplete();
     }
 
-    codeMirrorLoaded(codeEditor: any): void {
-        this.editor = codeEditor.codeMirror;
+    initializeCodeMirror(): void {
+        if (this.valueSet && this.requiredSet) {
+            const setup: Extension[] = [
+                lineNumbers(),
+                history(),
+                indentUnit.of('    '),
+                elFunctionHighlightPlugin,
+                EditorView.lineWrapping,
+                rectangularSelection(),
+                crosshairCursor(),
+                EditorState.allowMultipleSelections.of(true),
+                indentOnInput(),
+                highlightSpecialChars(),
+                syntaxHighlighting(highlightStyle),
+                syntaxHighlighting(defaultHighlightStyle, { fallback: true }),
+                bracketMatching(),
+                closeBrackets(),
+                highlightActiveLine(),
+                highlightSelectionMatches(),
+                [highlightActiveLineGutter(), Prec.highest(lineNumbers())],
+                foldGutter(),
+                autocompletion(),
+                markdown(),
+                xml(),
+                EditorView.contentAttributes.of({ 'aria-label': 'Code Editor' 
}),
+                keymap.of([
+                    { key: 'Mod-Enter', run: () => true }, // ignore Mod-Enter 
in `defaultKeymap` which is handled by `QueryShortcuts.ts`
+                    { key: 'Ctrl-Enter', run: () => true }, // ignore 
Ctrl-Enter in `defaultKeymap` which is handled by `QueryShortcuts.ts`
+                    { key: 'Mod-y', run: redoSelection },
+                    { key: 'Shift-Ctrl-k', run: deleteLine },

Review Comment:
   ```suggestion
                       { key: 'Shift-Mod-k', run: deleteLine },
   ```



##########
nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/feature/jolt-transform-json-ui.component.ts:
##########
@@ -177,33 +237,102 @@ export class JoltTransformJsonUi implements OnDestroy {
         this.store.dispatch(resetJoltTransformJsonPropertyState());
     }
 
-    getJoltSpecOptions(): any {
-        return {
-            theme: 'nifi',
-            lineNumbers: true,
-            gutters: ['CodeMirror-lint-markers'],
-            mode: 'application/json',
-            lint: true,
-            extraKeys: {
-                'Shift-Ctrl-F': () => {
-                    this.formatSpecification();
-                }
-            }
-        };
+    getJoltSpecExtensions(): Extension[] {
+        return [
+            lineNumbers(),
+            history(),
+            indentUnit.of('    '),
+            EditorView.lineWrapping,
+            rectangularSelection(),
+            crosshairCursor(),
+            EditorState.allowMultipleSelections.of(true),
+            indentOnInput(),
+            highlightSpecialChars(),
+            syntaxHighlighting(highlightStyle),
+            syntaxHighlighting(defaultHighlightStyle, { fallback: true }),
+            bracketMatching(),
+            closeBrackets(),
+            highlightActiveLine(),
+            highlightSelectionMatches(),
+            [highlightActiveLineGutter(), Prec.highest(lineNumbers())],
+            foldGutter(),
+            autocompletion(),
+            json(),
+            linter(jsonParseLinter()),
+            EditorView.contentAttributes.of({ 'aria-label': 'Jolt 
Specification Editor' }),
+            keymap.of([
+                {
+                    key: 'Shift-Ctrl-f',
+                    run: () => {
+                        this.formatSpecification();
+                        return true;
+                    }
+                },
+                ...closeBracketsKeymap,
+                ...defaultKeymap,
+                ...historyKeymap,
+                ...foldKeymap,
+                ...searchKeymap,
+                ...completionKeymap
+            ])
+        ];
     }
 
-    getExampleDataOptions(): any {
-        return this.exampleDataOptions;
+    getExampleDataExtensions(): Extension[] {
+        return [
+            lineNumbers(),
+            history(),
+            indentUnit.of('    '),
+            EditorView.lineWrapping,
+            rectangularSelection(),
+            crosshairCursor(),
+            EditorState.allowMultipleSelections.of(true),
+            indentOnInput(),
+            highlightSpecialChars(),
+            syntaxHighlighting(highlightStyle),
+            syntaxHighlighting(defaultHighlightStyle, { fallback: true }),
+            bracketMatching(),
+            closeBrackets(),
+            highlightActiveLine(),
+            highlightSelectionMatches(),
+            [highlightActiveLineGutter(), Prec.highest(lineNumbers())],
+            foldGutter(),
+            autocompletion(),
+            json(),
+            EditorView.contentAttributes.of({ 'aria-label': 'Example Data 
Editor' }),
+            keymap.of([
+                {
+                    key: 'Shift-Ctrl-f',

Review Comment:
   ```suggestion
                       key: 'Shift-Mod-f',
   ```



##########
nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/property-table/editors/nf-editor/nf-editor.component.ts:
##########
@@ -88,40 +129,130 @@ export class NfEditor implements OnDestroy {
     @Output() exit: EventEmitter<void> = new EventEmitter<void>();
 
     itemSet = false;
-    getParametersSet = false;
-
+    parameterConfigSet = false;
+    nfLanguageDefinition: NfLanguageDefinition | null = null;
     nfEditorForm: FormGroup;
     showSensitiveHelperText = false;
     supportsEl = false;
     supportsParameters = false;
     blank = false;
 
-    mode!: string;
     parameters: Parameter[] | null = null;
-
-    editor!: Editor;
+    private _codemirrorConfig: CodeMirrorConfig = {
+        extensions: [],
+        autoFocus: true
+    };
+
+    // Styling configuration
+    editorStyling = {
+        width: '100%',
+        height: '108px'
+    };
+
+    // Language configuration
+    languageConfig = {
+        language: this.nifiLanguagePackage.getLanguageId(),
+        languages: [] as LanguageDescription[]
+    };
+
+    // Dynamic config getter that includes disabled state
+    get codemirrorConfig(): CodeMirrorConfig {
+        return {
+            ...this._codemirrorConfig,
+            viewDisabled: this.readonly || this.blank,
+            readonly: this.readonly || this.blank
+        };
+    }
 
     constructor(
         private formBuilder: FormBuilder,
         private viewContainerRef: ViewContainerRef,
-        private renderer: Renderer2,
-        private nfel: NfEl,
-        private nfpr: NfPr
+        private nifiLanguagePackage: CodemirrorNifiLanguagePackage
     ) {
         this.nfEditorForm = this.formBuilder.group({
             value: new FormControl(''),
             setEmptyString: new FormControl(false)
         });
     }
 
-    codeMirrorLoaded(codeEditor: any): void {
-        this.editor = codeEditor.codeMirror;
+    initializeCodeMirror(): void {
+        if (this.itemSet && this.parameterConfigSet) {
+            const setup: Extension[] = [
+                lineNumbers(),
+                history(),
+                indentUnit.of('    '),
+                parameterHighlightPlugin,
+                elFunctionHighlightPlugin,
+                EditorView.lineWrapping,
+                rectangularSelection(),
+                crosshairCursor(),
+                EditorState.allowMultipleSelections.of(true),
+                indentOnInput(),
+                highlightSpecialChars(),
+                syntaxHighlighting(highlightStyle),
+                syntaxHighlighting(defaultHighlightStyle, { fallback: true }),
+                bracketMatching(),
+                closeBrackets(),
+                highlightActiveLine(),
+                highlightSelectionMatches(),
+                [highlightActiveLineGutter(), Prec.highest(lineNumbers())],
+                foldGutter(),
+                autocompletion(),
+                markdown(),
+                xml(),
+                EditorView.contentAttributes.of({ 'aria-label': 'Code Editor' 
}),
+                keymap.of([
+                    { key: 'Mod-Enter', run: () => true }, // ignore Mod-Enter 
in `defaultKeymap` which is handled by `QueryShortcuts.ts`
+                    { key: 'Ctrl-Enter', run: () => true }, // ignore 
Ctrl-Enter in `defaultKeymap` which is handled by `QueryShortcuts.ts`
+                    { key: 'Mod-y', run: redoSelection },
+                    { key: 'Shift-Ctrl-k', run: deleteLine },

Review Comment:
   ```suggestion
                       { key: 'Shift-Mod-k', run: deleteLine },
   ```



##########
nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/property-table/editors/nf-editor/nf-editor.component.ts:
##########
@@ -88,40 +129,130 @@ export class NfEditor implements OnDestroy {
     @Output() exit: EventEmitter<void> = new EventEmitter<void>();
 
     itemSet = false;
-    getParametersSet = false;
-
+    parameterConfigSet = false;
+    nfLanguageDefinition: NfLanguageDefinition | null = null;
     nfEditorForm: FormGroup;
     showSensitiveHelperText = false;
     supportsEl = false;
     supportsParameters = false;
     blank = false;
 
-    mode!: string;
     parameters: Parameter[] | null = null;
-
-    editor!: Editor;
+    private _codemirrorConfig: CodeMirrorConfig = {
+        extensions: [],
+        autoFocus: true
+    };
+
+    // Styling configuration
+    editorStyling = {
+        width: '100%',
+        height: '108px'
+    };
+
+    // Language configuration
+    languageConfig = {
+        language: this.nifiLanguagePackage.getLanguageId(),
+        languages: [] as LanguageDescription[]
+    };
+
+    // Dynamic config getter that includes disabled state
+    get codemirrorConfig(): CodeMirrorConfig {
+        return {
+            ...this._codemirrorConfig,
+            viewDisabled: this.readonly || this.blank,
+            readonly: this.readonly || this.blank
+        };
+    }
 
     constructor(
         private formBuilder: FormBuilder,
         private viewContainerRef: ViewContainerRef,
-        private renderer: Renderer2,
-        private nfel: NfEl,
-        private nfpr: NfPr
+        private nifiLanguagePackage: CodemirrorNifiLanguagePackage
     ) {
         this.nfEditorForm = this.formBuilder.group({
             value: new FormControl(''),
             setEmptyString: new FormControl(false)
         });
     }
 
-    codeMirrorLoaded(codeEditor: any): void {
-        this.editor = codeEditor.codeMirror;
+    initializeCodeMirror(): void {
+        if (this.itemSet && this.parameterConfigSet) {
+            const setup: Extension[] = [
+                lineNumbers(),
+                history(),
+                indentUnit.of('    '),
+                parameterHighlightPlugin,
+                elFunctionHighlightPlugin,
+                EditorView.lineWrapping,
+                rectangularSelection(),
+                crosshairCursor(),
+                EditorState.allowMultipleSelections.of(true),
+                indentOnInput(),
+                highlightSpecialChars(),
+                syntaxHighlighting(highlightStyle),
+                syntaxHighlighting(defaultHighlightStyle, { fallback: true }),
+                bracketMatching(),
+                closeBrackets(),
+                highlightActiveLine(),
+                highlightSelectionMatches(),
+                [highlightActiveLineGutter(), Prec.highest(lineNumbers())],
+                foldGutter(),

Review Comment:
   Do we want foldgutter here? It adds spacing between the line numbers and the 
right edge of the gutter for the fold arrow but this doesn't seem to offer 
detection of content type to know where the folds need to go... so it seems a 
waste of space:
   
   <img width="1112" height="749" alt="Screenshot 2025-07-29 at 15 08 10" 
src="https://github.com/user-attachments/assets/8fb99f08-862b-4700-b987-cfc969667769";
 />
   



##########
nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/property-table/editors/nf-editor/nf-editor.component.ts:
##########
@@ -88,40 +129,130 @@ export class NfEditor implements OnDestroy {
     @Output() exit: EventEmitter<void> = new EventEmitter<void>();
 
     itemSet = false;
-    getParametersSet = false;
-
+    parameterConfigSet = false;
+    nfLanguageDefinition: NfLanguageDefinition | null = null;
     nfEditorForm: FormGroup;
     showSensitiveHelperText = false;
     supportsEl = false;
     supportsParameters = false;
     blank = false;
 
-    mode!: string;
     parameters: Parameter[] | null = null;
-
-    editor!: Editor;
+    private _codemirrorConfig: CodeMirrorConfig = {
+        extensions: [],
+        autoFocus: true
+    };
+
+    // Styling configuration
+    editorStyling = {
+        width: '100%',
+        height: '108px'
+    };
+
+    // Language configuration
+    languageConfig = {
+        language: this.nifiLanguagePackage.getLanguageId(),
+        languages: [] as LanguageDescription[]
+    };
+
+    // Dynamic config getter that includes disabled state
+    get codemirrorConfig(): CodeMirrorConfig {
+        return {
+            ...this._codemirrorConfig,
+            viewDisabled: this.readonly || this.blank,
+            readonly: this.readonly || this.blank
+        };
+    }
 
     constructor(
         private formBuilder: FormBuilder,
         private viewContainerRef: ViewContainerRef,
-        private renderer: Renderer2,
-        private nfel: NfEl,
-        private nfpr: NfPr
+        private nifiLanguagePackage: CodemirrorNifiLanguagePackage
     ) {
         this.nfEditorForm = this.formBuilder.group({
             value: new FormControl(''),
             setEmptyString: new FormControl(false)
         });
     }
 
-    codeMirrorLoaded(codeEditor: any): void {
-        this.editor = codeEditor.codeMirror;
+    initializeCodeMirror(): void {
+        if (this.itemSet && this.parameterConfigSet) {
+            const setup: Extension[] = [
+                lineNumbers(),
+                history(),
+                indentUnit.of('    '),
+                parameterHighlightPlugin,
+                elFunctionHighlightPlugin,
+                EditorView.lineWrapping,
+                rectangularSelection(),
+                crosshairCursor(),
+                EditorState.allowMultipleSelections.of(true),
+                indentOnInput(),
+                highlightSpecialChars(),
+                syntaxHighlighting(highlightStyle),
+                syntaxHighlighting(defaultHighlightStyle, { fallback: true }),
+                bracketMatching(),
+                closeBrackets(),
+                highlightActiveLine(),
+                highlightSelectionMatches(),
+                [highlightActiveLineGutter(), Prec.highest(lineNumbers())],
+                foldGutter(),
+                autocompletion(),
+                markdown(),
+                xml(),
+                EditorView.contentAttributes.of({ 'aria-label': 'Code Editor' 
}),
+                keymap.of([
+                    { key: 'Mod-Enter', run: () => true }, // ignore Mod-Enter 
in `defaultKeymap` which is handled by `QueryShortcuts.ts`
+                    { key: 'Ctrl-Enter', run: () => true }, // ignore 
Ctrl-Enter in `defaultKeymap` which is handled by `QueryShortcuts.ts`

Review Comment:
   this isn't needed. the `Mod-Enter` covers both CMD+Enter on mac and 
CTRL+Enter on other platforms: https://codemirror.net/docs/ref/#view.KeyBinding
   ```suggestion
   ```



##########
nifi-frontend/src/main/frontend/apps/update-attribute/src/app/pages/update-attribute/ui/ua-editor/ua-editor.component.ts:
##########
@@ -84,44 +157,78 @@ export class UaEditor implements OnDestroy {
         if (this.isRequired) {
             
this.uaEditorForm.get('value')?.setValidators([Validators.required]);
         }
-
-        this.nfel.setViewContainerRef(this.viewContainerRef, this.renderer);
-        this.nfel.configureAutocomplete();
     }
 
-    codeMirrorLoaded(codeEditor: any): void {
-        this.editor = codeEditor.codeMirror;
+    initializeCodeMirror(): void {
+        if (this.valueSet && this.requiredSet) {
+            const setup: Extension[] = [
+                lineNumbers(),
+                history(),
+                indentUnit.of('    '),
+                elFunctionHighlightPlugin,
+                EditorView.lineWrapping,
+                rectangularSelection(),
+                crosshairCursor(),
+                EditorState.allowMultipleSelections.of(true),
+                indentOnInput(),
+                highlightSpecialChars(),
+                syntaxHighlighting(highlightStyle),
+                syntaxHighlighting(defaultHighlightStyle, { fallback: true }),
+                bracketMatching(),
+                closeBrackets(),
+                highlightActiveLine(),
+                highlightSelectionMatches(),
+                [highlightActiveLineGutter(), Prec.highest(lineNumbers())],
+                foldGutter(),
+                autocompletion(),
+                markdown(),
+                xml(),
+                EditorView.contentAttributes.of({ 'aria-label': 'Code Editor' 
}),
+                keymap.of([
+                    { key: 'Mod-Enter', run: () => true }, // ignore Mod-Enter 
in `defaultKeymap` which is handled by `QueryShortcuts.ts`
+                    { key: 'Ctrl-Enter', run: () => true }, // ignore 
Ctrl-Enter in `defaultKeymap` which is handled by `QueryShortcuts.ts`

Review Comment:
   not needed.
   ```suggestion
   ```



##########
nifi-frontend/src/main/frontend/libs/shared/src/components/map-table/editors/text-editor/text-editor.component.ts:
##########
@@ -122,50 +158,68 @@ export class TextEditor {
         // needed to display the 'Set Empty String' checkbox, the action 
buttons,
         // and the resize handle. If the amount of spacing needed for 
additional UX is needed for the `.editor` is
         // changed then this value should also be updated.
-        this.editor.setSize('100%', event.height - 112);
+        this.editorStyling.width = '100%';
+        this.editorStyling.height = `${event.height - 152}px`;
     }
 
     preventDrag(event: MouseEvent): void {
         event.stopPropagation();
     }
 
-    codeMirrorLoaded(codeEditor: any): void {
-        this.editor = codeEditor.codeMirror;
-        // The `.text-editor` minimum height is set to 220px. This is the 
height of the `.editor` overlay. The
-        // height of the codemirror needs to be set in order to handle large 
amounts of text in the codemirror editor.
-        // The height of the codemirror should be the height of the `.editor` 
overlay minus the 112px of spacing
-        // needed to display the 'Set Empty String' checkbox, the action 
buttons,
-        // and the resize handle so the initial height of the codemirror when 
opening should be 108px for a 220px tall
-        // `.editor` overlay. If the initial height of that overlay changes 
then this initial height should also be
-        // updated.
-        this.editor.setSize('100%', 108);
-
-        if (!this.readonly) {
-            this.editor.focus();
-            this.editor.execCommand('selectAll');
-        }
-
+    codeMirrorLoaded(): void {
         // disabling of the input through the form isn't supported until 
codemirror
         // has loaded so we must disable again if the value is an empty string
         if (this.textEditorForm.get('setEmptyString')?.value) {
             this.textEditorForm.get('value')?.disable();
-            this.editor.setOption('readOnly', 'nocursor');
         }
     }
 
-    getOptions(): any {
-        return {
-            readOnly: this.readonly,
-            lineNumbers: true,
-            matchBrackets: true,
-            theme: 'nifi',
-            extraKeys: {
-                Enter: () => {
-                    if (this.textEditorForm.dirty && 
this.textEditorForm.valid) {
-                        this.okClicked();
+    getExtensions(): Extension[] {
+        const setup: Extension[] = [
+            lineNumbers(),
+            history(),
+            indentUnit.of('    '),
+            EditorView.lineWrapping,
+            rectangularSelection(),
+            crosshairCursor(),
+            EditorState.allowMultipleSelections.of(true),
+            indentOnInput(),
+            highlightSpecialChars(),
+            syntaxHighlighting(highlightStyle),
+            syntaxHighlighting(defaultHighlightStyle, { fallback: true }),
+            bracketMatching(),
+            closeBrackets(),
+            highlightActiveLine(),
+            highlightSelectionMatches(),
+            [highlightActiveLineGutter(), Prec.highest(lineNumbers())],
+            foldGutter(),
+            markdown(),
+            xml(),
+            EditorView.contentAttributes.of({ 'aria-label': 'Code Editor' }),
+            keymap.of([
+                { key: 'Mod-Enter', run: () => true }, // ignore Mod-Enter in 
`defaultKeymap` which is handled by `QueryShortcuts.ts`
+                { key: 'Ctrl-Enter', run: () => true }, // ignore Ctrl-Enter 
in `defaultKeymap` which is handled by `QueryShortcuts.ts`

Review Comment:
   not needed
   ```suggestion
   ```



##########
nifi-frontend/src/main/frontend/apps/nifi-jolt-transform-ui/src/app/pages/jolt-transform-json-ui/feature/jolt-transform-json-ui.component.ts:
##########
@@ -177,33 +237,102 @@ export class JoltTransformJsonUi implements OnDestroy {
         this.store.dispatch(resetJoltTransformJsonPropertyState());
     }
 
-    getJoltSpecOptions(): any {
-        return {
-            theme: 'nifi',
-            lineNumbers: true,
-            gutters: ['CodeMirror-lint-markers'],
-            mode: 'application/json',
-            lint: true,
-            extraKeys: {
-                'Shift-Ctrl-F': () => {
-                    this.formatSpecification();
-                }
-            }
-        };
+    getJoltSpecExtensions(): Extension[] {
+        return [
+            lineNumbers(),
+            history(),
+            indentUnit.of('    '),
+            EditorView.lineWrapping,
+            rectangularSelection(),
+            crosshairCursor(),
+            EditorState.allowMultipleSelections.of(true),
+            indentOnInput(),
+            highlightSpecialChars(),
+            syntaxHighlighting(highlightStyle),
+            syntaxHighlighting(defaultHighlightStyle, { fallback: true }),
+            bracketMatching(),
+            closeBrackets(),
+            highlightActiveLine(),
+            highlightSelectionMatches(),
+            [highlightActiveLineGutter(), Prec.highest(lineNumbers())],
+            foldGutter(),
+            autocompletion(),
+            json(),
+            linter(jsonParseLinter()),
+            EditorView.contentAttributes.of({ 'aria-label': 'Jolt 
Specification Editor' }),
+            keymap.of([
+                {
+                    key: 'Shift-Ctrl-f',

Review Comment:
   ```suggestion
                       key: 'Shift-Mod-f',
   ```



##########
nifi-frontend/src/main/frontend/libs/shared/src/components/map-table/editors/text-editor/text-editor.component.ts:
##########
@@ -122,50 +158,68 @@ export class TextEditor {
         // needed to display the 'Set Empty String' checkbox, the action 
buttons,
         // and the resize handle. If the amount of spacing needed for 
additional UX is needed for the `.editor` is
         // changed then this value should also be updated.
-        this.editor.setSize('100%', event.height - 112);
+        this.editorStyling.width = '100%';
+        this.editorStyling.height = `${event.height - 152}px`;
     }
 
     preventDrag(event: MouseEvent): void {
         event.stopPropagation();
     }
 
-    codeMirrorLoaded(codeEditor: any): void {
-        this.editor = codeEditor.codeMirror;
-        // The `.text-editor` minimum height is set to 220px. This is the 
height of the `.editor` overlay. The
-        // height of the codemirror needs to be set in order to handle large 
amounts of text in the codemirror editor.
-        // The height of the codemirror should be the height of the `.editor` 
overlay minus the 112px of spacing
-        // needed to display the 'Set Empty String' checkbox, the action 
buttons,
-        // and the resize handle so the initial height of the codemirror when 
opening should be 108px for a 220px tall
-        // `.editor` overlay. If the initial height of that overlay changes 
then this initial height should also be
-        // updated.
-        this.editor.setSize('100%', 108);
-
-        if (!this.readonly) {
-            this.editor.focus();
-            this.editor.execCommand('selectAll');
-        }
-
+    codeMirrorLoaded(): void {
         // disabling of the input through the form isn't supported until 
codemirror
         // has loaded so we must disable again if the value is an empty string
         if (this.textEditorForm.get('setEmptyString')?.value) {
             this.textEditorForm.get('value')?.disable();
-            this.editor.setOption('readOnly', 'nocursor');
         }
     }
 
-    getOptions(): any {
-        return {
-            readOnly: this.readonly,
-            lineNumbers: true,
-            matchBrackets: true,
-            theme: 'nifi',
-            extraKeys: {
-                Enter: () => {
-                    if (this.textEditorForm.dirty && 
this.textEditorForm.valid) {
-                        this.okClicked();
+    getExtensions(): Extension[] {
+        const setup: Extension[] = [
+            lineNumbers(),
+            history(),
+            indentUnit.of('    '),
+            EditorView.lineWrapping,
+            rectangularSelection(),
+            crosshairCursor(),
+            EditorState.allowMultipleSelections.of(true),
+            indentOnInput(),
+            highlightSpecialChars(),
+            syntaxHighlighting(highlightStyle),
+            syntaxHighlighting(defaultHighlightStyle, { fallback: true }),
+            bracketMatching(),
+            closeBrackets(),
+            highlightActiveLine(),
+            highlightSelectionMatches(),
+            [highlightActiveLineGutter(), Prec.highest(lineNumbers())],
+            foldGutter(),
+            markdown(),
+            xml(),
+            EditorView.contentAttributes.of({ 'aria-label': 'Code Editor' }),
+            keymap.of([
+                { key: 'Mod-Enter', run: () => true }, // ignore Mod-Enter in 
`defaultKeymap` which is handled by `QueryShortcuts.ts`
+                { key: 'Ctrl-Enter', run: () => true }, // ignore Ctrl-Enter 
in `defaultKeymap` which is handled by `QueryShortcuts.ts`
+                { key: 'Mod-y', run: redoSelection },
+                { key: 'Shift-Ctrl-k', run: deleteLine },

Review Comment:
   ```suggestion
                   { key: 'Shift-Mod-k', run: deleteLine },
   ```



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


Reply via email to