This is an automated email from the ASF dual-hosted git repository.

davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to refs/heads/main by this push:
     new 8d92667e4807 camel-tui - Replace hand-coded YAML DSL completion with 
tree-driven approach
8d92667e4807 is described below

commit 8d92667e4807ee1a157fa80e7fa2844f76f14475
Author: Claus Ibsen <[email protected]>
AuthorDate: Wed Aug 5 23:56:26 2026 +0200

    camel-tui - Replace hand-coded YAML DSL completion with tree-driven approach
    
    Replace ~1300 lines of per-context completion code (6 context records,
    6 detection methods, 8 provider methods) with a single tree-walking
    approach using the generated camelYamlDsl-completion.json.
    
    SourceViewer now uses findParentYamlKey() to determine the parent YAML
    key by walking up indentation. SourceTab looks up that key's node in
    the completion tree and returns its children. Value completion (boolean,
    enum) works the same way by finding the matching child entry.
    
    Preserves the tree's index order from the model metadata instead of
    sorting alphabetically, so options appear in their natural order.
    
    Component endpoint completion (uri, parameters) stays unchanged as it
    needs runtime catalog queries with consumer/producer filtering.
    
    camel-tui - Fix Esc exiting edit mode when popup is open, and fix sibling 
key detection on blank lines
    
    - cancelEdit() now closes the autocomplete popup first before exiting
      edit mode, so Esc dismisses the popup without leaving edit mode
    - collectExistingSiblingKeys uses the deeper indent from both successor
      and predecessor lines, so blank lines between or after siblings
      correctly detect already-specified keys for filtering
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
---
 .../dsl/jbang/core/commands/tui/SourceTab.java     | 405 +++----------
 .../dsl/jbang/core/commands/tui/SourceViewer.java  | 459 +++------------
 .../core/commands/tui/SourceViewerEditTest.java    |  10 +-
 .../core/commands/tui/YamlCompletionTest.java      | 650 ++-------------------
 4 files changed, 190 insertions(+), 1334 deletions(-)

diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceTab.java
index bb3bb9a465b6..99436a5480b9 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceTab.java
@@ -105,6 +105,10 @@ class SourceTab extends AbstractTab {
     private List<AutocompletePopup.CompletionItem> consumerComponents;
     private List<AutocompletePopup.CompletionItem> producerComponents;
 
+    // YAML DSL completion tree (loaded from generated schema)
+    private JsonObject completionTree;
+    private boolean completionTreeLoaded;
+
     // Spring Boot configuration metadata cache (lazy-loaded on-demand via IPC)
     private Map<String, JsonObject> springBootMetadataCache;
     private boolean springBootMetadataLoaded;
@@ -952,29 +956,9 @@ class SourceTab extends AbstractTab {
             return provideComponentNameCompletions(context.substring(9));
         }
 
-        // EIP option completion
-        if (context.startsWith("yaml-eip:")) {
-            return provideEipKeyCompletions(context.substring(9));
-        }
-
-        // expression language name completion inside expression: block
-        if (context.startsWith("yaml-expr:")) {
-            return provideExpressionLanguageCompletions(context.substring(10));
-        }
-
-        // language option completion inside a language block under expression:
-        if (context.startsWith("yaml-lang-opt:")) {
-            return provideLanguageOptionCompletions(context.substring(14));
-        }
-
-        // data format name completion inside marshal/unmarshal
-        if (context.startsWith("yaml-df:")) {
-            return provideDataFormatNameCompletions(context.substring(8));
-        }
-
-        // data format option completion
-        if (context.startsWith("yaml-df-opt:")) {
-            return provideDataFormatOptionCompletions(context.substring(12));
+        // tree-driven YAML DSL completion (EIPs, expressions, languages, data 
formats, route options, top-level)
+        if (context.startsWith("yaml-tree:")) {
+            return provideTreeCompletions(context.substring(10));
         }
 
         if (!context.startsWith("yaml:")) {
@@ -1086,349 +1070,139 @@ class SourceTab extends AbstractTab {
         return items;
     }
 
-    private static final Set<String> EIP_BOILERPLATE = Set.of("id", "note", 
"description", "disabled",
-            "input", "outputs", "steps");
+    private static final Set<String> TREE_BOILERPLATE = Set.of("id", "note", 
"description", "disabled");
 
-    private List<AutocompletePopup.CompletionItem> 
provideEipKeyCompletions(String contextAfterPrefix) {
-        CamelCatalog catalog = getCatalog();
-        if (catalog == null) {
-            return List.of();
-        }
-
-        // context format: "eipName" or "eipName:existingKey1,existingKey2,..."
-        String[] parts = contextAfterPrefix.split(":", 2);
-        String eipName = parts[0];
-
-        Set<String> existingKeys = Set.of();
-        if (parts.length > 1 && !parts[1].isEmpty()) {
-            existingKeys = new HashSet<>(Arrays.asList(parts[1].split(",")));
-        }
-
-        EipModel model = catalog.eipModel(eipName);
-        if (model == null) {
-            return List.of();
-        }
-
-        List<AutocompletePopup.CompletionItem> items = new ArrayList<>();
-        for (EipModel.EipOptionModel opt : model.getOptions()) {
-            if ("expression".equals(opt.getKind()) || 
"element".equals(opt.getKind())) {
-                if (EIP_BOILERPLATE.contains(opt.getName())) {
-                    continue;
+    private JsonObject getCompletionTree() {
+        if (!completionTreeLoaded) {
+            completionTreeLoaded = true;
+            try (var is = 
getClass().getResourceAsStream("/schema/camelYamlDsl-completion.json")) {
+                if (is != null) {
+                    String json = new String(is.readAllBytes(), 
java.nio.charset.StandardCharsets.UTF_8);
+                    completionTree = (JsonObject) 
org.apache.camel.util.json.Jsoner.deserialize(json);
                 }
-                if (existingKeys.contains(opt.getName())) {
-                    continue;
-                }
-                items.add(new AutocompletePopup.CompletionItem(
-                        opt.getName(), opt.getDescription(), opt.getType(),
-                        opt.getDefaultValue(), opt.isDeprecated(), 
opt.getDeprecationNote(),
-                        opt.getGroup(), opt.isRequired()));
-                continue;
-            }
-            if (!"attribute".equals(opt.getKind())) {
-                continue;
-            }
-            if (EIP_BOILERPLATE.contains(opt.getName())) {
-                continue;
-            }
-            if (existingKeys.contains(opt.getName()) && !opt.isMultiValue()) {
-                continue;
+            } catch (Exception e) {
+                // ignore — tree not available
             }
-            items.add(new AutocompletePopup.CompletionItem(
-                    opt.getName(), opt.getDescription(), opt.getType(),
-                    opt.getDefaultValue(), opt.isDeprecated(), 
opt.getDeprecationNote(),
-                    opt.getGroup(), opt.isRequired()));
         }
-
-        
items.sort(Comparator.comparing(AutocompletePopup.CompletionItem::deprecated)
-                .thenComparing((a, b) -> Boolean.compare(b.required(), 
a.required()))
-                .thenComparing(AutocompletePopup.CompletionItem::key, 
String.CASE_INSENSITIVE_ORDER));
-        return items;
+        return completionTree;
     }
 
-    private List<AutocompletePopup.CompletionItem> 
provideExpressionLanguageCompletions(String contextAfterPrefix) {
-        CamelCatalog catalog = getCatalog();
-        if (catalog == null) {
-            return List.of();
+    private JsonObject getTreeNode(String nodeName) {
+        JsonObject tree = getCompletionTree();
+        if (tree == null) {
+            return null;
+        }
+        JsonObject nodes = (JsonObject) tree.get("nodes");
+        if (nodes == null) {
+            return null;
         }
+        return (JsonObject) nodes.get(nodeName);
+    }
 
-        // context format: "eipName" or "eipName:existingKey1,existingKey2,..."
+    private List<AutocompletePopup.CompletionItem> 
provideTreeCompletions(String contextAfterPrefix) {
+        // context format: "nodeName" or 
"nodeName:existingKey1,existingKey2,..."
         String[] parts = contextAfterPrefix.split(":", 2);
-        String eipName = parts[0];
+        String nodeName = parts[0];
 
         Set<String> existingKeys = Set.of();
         if (parts.length > 1 && !parts[1].isEmpty()) {
             existingKeys = new HashSet<>(Arrays.asList(parts[1].split(",")));
         }
 
-        EipModel model = catalog.eipModel(eipName);
-        if (model == null) {
-            return List.of();
-        }
-
-        List<AutocompletePopup.CompletionItem> items = new ArrayList<>();
-        for (EipModel.EipOptionModel opt : model.getOptions()) {
-            if (!"expression".equals(opt.getKind())) {
-                continue;
-            }
-            List<String> oneOfs = opt.getOneOfs();
-            if (oneOfs == null || oneOfs.isEmpty()) {
-                continue;
-            }
-            if (oneOfs.stream().anyMatch(existingKeys::contains)) {
-                continue;
-            }
-            for (String langName : oneOfs) {
-                LanguageModel langModel = catalog.languageModel(langName);
-                String desc = langModel != null
-                        ? langModel.getTitle() + " - " + 
langModel.getDescription()
-                        : langName;
-                String label = langModel != null ? langModel.getLabel() : 
"language";
-                boolean dep = langModel != null && langModel.isDeprecated();
-                String depNote = langModel != null ? 
langModel.getDeprecationNote() : null;
-                items.add(new AutocompletePopup.CompletionItem(
-                        langName, desc, "language",
-                        null, dep, depNote, label));
-            }
-        }
-
-        
items.sort(Comparator.comparing(AutocompletePopup.CompletionItem::deprecated)
-                .thenComparing(AutocompletePopup.CompletionItem::key, 
String.CASE_INSENSITIVE_ORDER));
-        return items;
-    }
-
-    private List<AutocompletePopup.CompletionItem> 
provideLanguageOptionCompletions(String contextAfterPrefix) {
-        CamelCatalog catalog = getCatalog();
-        if (catalog == null) {
+        JsonObject node = getTreeNode(nodeName);
+        if (node == null) {
             return List.of();
         }
 
-        // context format: "languageName" or 
"languageName:existingKey1,existingKey2,..."
-        String[] parts = contextAfterPrefix.split(":", 2);
-        String langName = parts[0];
-
-        Set<String> existingKeys = Set.of();
-        if (parts.length > 1 && !parts[1].isEmpty()) {
-            existingKeys = new HashSet<>(Arrays.asList(parts[1].split(",")));
-        }
-
-        LanguageModel model = catalog.languageModel(langName);
-        if (model == null) {
+        JsonArray children = (JsonArray) node.get("children");
+        if (children == null) {
             return List.of();
         }
 
         List<AutocompletePopup.CompletionItem> items = new ArrayList<>();
-        for (LanguageModel.LanguageOptionModel opt : model.getOptions()) {
-            if (!"attribute".equals(opt.getKind()) && 
!"value".equals(opt.getKind())) {
+        for (Object obj : children) {
+            JsonObject child = (JsonObject) obj;
+            String name = (String) child.get("name");
+            if (name == null) {
                 continue;
             }
-            if (EIP_BOILERPLATE.contains(opt.getName())) {
+            if (TREE_BOILERPLATE.contains(name)) {
                 continue;
             }
-            if (existingKeys.contains(opt.getName()) && !opt.isMultiValue()) {
+            if (existingKeys.contains(name)) {
                 continue;
             }
+
+            String desc = (String) child.get("description");
+            String type = (String) child.get("type");
+            String group = (String) child.get("group");
+            String label = (String) child.get("label");
+            Object defVal = child.get("default");
+            boolean required = Boolean.TRUE.equals(child.get("required"));
+            boolean deprecated = Boolean.TRUE.equals(child.get("deprecated"));
+            String depNote = (String) child.get("deprecationNote");
+
             items.add(new AutocompletePopup.CompletionItem(
-                    opt.getName(), opt.getDescription(), opt.getType(),
-                    opt.getDefaultValue(), opt.isDeprecated(), 
opt.getDeprecationNote(),
-                    opt.getGroup(), opt.isRequired()));
+                    name, desc, type, defVal, deprecated, depNote, group != 
null ? group : label, required));
         }
 
         
items.sort(Comparator.comparing(AutocompletePopup.CompletionItem::deprecated)
-                .thenComparing((a, b) -> Boolean.compare(b.required(), 
a.required()))
-                .thenComparing(AutocompletePopup.CompletionItem::key, 
String.CASE_INSENSITIVE_ORDER));
+                .thenComparing((a, b) -> Boolean.compare(b.required(), 
a.required())));
         return items;
     }
 
-    private List<AutocompletePopup.CompletionItem> 
provideLanguageValueCompletions(String contextAfterPrefix) {
-        CamelCatalog catalog = getCatalog();
-        if (catalog == null) {
-            return List.of();
-        }
-
-        // context format: "languageName:optionName"
+    private List<AutocompletePopup.CompletionItem> 
provideTreeValueCompletions(String contextAfterPrefix) {
+        // context format: "nodeName:optionName"
         String[] parts = contextAfterPrefix.split(":", 2);
         if (parts.length < 2) {
             return List.of();
         }
-        String langName = parts[0];
+        String nodeName = parts[0];
         String optionName = parts[1];
 
-        LanguageModel model = catalog.languageModel(langName);
-        if (model == null) {
+        JsonObject node = getTreeNode(nodeName);
+        if (node == null) {
             return List.of();
         }
 
-        LanguageModel.LanguageOptionModel opt = null;
-        for (LanguageModel.LanguageOptionModel o : model.getOptions()) {
-            if (o.getName().equals(optionName)) {
-                opt = o;
-                break;
-            }
-        }
-
-        List<AutocompletePopup.CompletionItem> items = new ArrayList<>();
-        if (opt != null) {
-            List<String> enums = opt.getEnums();
-            if (enums != null && !enums.isEmpty()) {
-                for (String value : enums) {
-                    boolean isDefault = 
value.equals(String.valueOf(opt.getDefaultValue()));
-                    items.add(new AutocompletePopup.CompletionItem(
-                            value, opt.getDescription(), opt.getType(),
-                            isDefault ? value : opt.getDefaultValue(),
-                            false, null, opt.getGroup()));
-                }
-            } else if ("boolean".equalsIgnoreCase(opt.getType())
-                    || "java.lang.Boolean".equals(opt.getJavaType())) {
-                items.add(new AutocompletePopup.CompletionItem(
-                        "true", opt.getDescription(), "boolean", 
opt.getDefaultValue(),
-                        false, null, opt.getGroup()));
-                items.add(new AutocompletePopup.CompletionItem(
-                        "false", opt.getDescription(), "boolean", 
opt.getDefaultValue(),
-                        false, null, opt.getGroup()));
-            }
-        }
-        return items;
-    }
-
-    private List<AutocompletePopup.CompletionItem> 
provideDataFormatValueCompletions(String contextAfterPrefix) {
-        CamelCatalog catalog = getCatalog();
-        if (catalog == null) {
-            return List.of();
-        }
-
-        String[] parts = contextAfterPrefix.split(":", 2);
-        if (parts.length < 2) {
+        JsonArray children = (JsonArray) node.get("children");
+        if (children == null) {
             return List.of();
         }
-        String dfName = parts[0];
-        String optionName = parts[1];
 
-        DataFormatModel model = catalog.dataFormatModel(dfName);
-        if (model == null) {
-            return List.of();
-        }
-
-        DataFormatModel.DataFormatOptionModel opt = null;
-        for (DataFormatModel.DataFormatOptionModel o : model.getOptions()) {
-            if (o.getName().equals(optionName)) {
-                opt = o;
+        // find the matching child
+        JsonObject matchedChild = null;
+        for (Object obj : children) {
+            JsonObject child = (JsonObject) obj;
+            if (optionName.equals(child.get("name"))) {
+                matchedChild = child;
                 break;
             }
         }
-
-        List<AutocompletePopup.CompletionItem> items = new ArrayList<>();
-        if (opt != null) {
-            List<String> enums = opt.getEnums();
-            if (enums != null && !enums.isEmpty()) {
-                for (String value : enums) {
-                    boolean isDefault = 
value.equals(String.valueOf(opt.getDefaultValue()));
-                    items.add(new AutocompletePopup.CompletionItem(
-                            value, opt.getDescription(), opt.getType(),
-                            isDefault ? value : opt.getDefaultValue(),
-                            false, null, opt.getGroup()));
-                }
-            } else if ("boolean".equalsIgnoreCase(opt.getType())
-                    || "java.lang.Boolean".equals(opt.getJavaType())) {
-                items.add(new AutocompletePopup.CompletionItem(
-                        "true", opt.getDescription(), "boolean", 
opt.getDefaultValue(),
-                        false, null, opt.getGroup()));
-                items.add(new AutocompletePopup.CompletionItem(
-                        "false", opt.getDescription(), "boolean", 
opt.getDefaultValue(),
-                        false, null, opt.getGroup()));
-            }
-        }
-        return items;
-    }
-
-    private List<AutocompletePopup.CompletionItem> 
provideDataFormatNameCompletions(String contextAfterPrefix) {
-        CamelCatalog catalog = getCatalog();
-        if (catalog == null) {
-            return List.of();
-        }
-
-        // context format: "eipName" or "eipName:existingKey1,existingKey2,..."
-        String[] parts = contextAfterPrefix.split(":", 2);
-        String eipName = parts[0];
-
-        Set<String> existingKeys = Set.of();
-        if (parts.length > 1 && !parts[1].isEmpty()) {
-            existingKeys = new HashSet<>(Arrays.asList(parts[1].split(",")));
-        }
-
-        EipModel model = catalog.eipModel(eipName);
-        if (model == null) {
+        if (matchedChild == null) {
             return List.of();
         }
 
         List<AutocompletePopup.CompletionItem> items = new ArrayList<>();
-        for (EipModel.EipOptionModel opt : model.getOptions()) {
-            List<String> oneOfs = opt.getOneOfs();
-            if (oneOfs == null || oneOfs.isEmpty()) {
-                continue;
-            }
-            // only offer data format names if none is already specified
-            if (oneOfs.stream().anyMatch(existingKeys::contains)) {
-                continue;
-            }
-            for (String dfName : oneOfs) {
-                DataFormatModel dfModel = catalog.dataFormatModel(dfName);
-                String desc = dfModel != null
-                        ? dfModel.getTitle() + " - " + dfModel.getDescription()
-                        : dfName;
-                String label = dfModel != null ? dfModel.getLabel() : 
"dataformat";
-                boolean dep = dfModel != null && dfModel.isDeprecated();
-                String depNote = dfModel != null ? 
dfModel.getDeprecationNote() : null;
+        String type = (String) matchedChild.get("type");
+        String desc = (String) matchedChild.get("description");
+        Object defVal = matchedChild.get("default");
+        String group = (String) matchedChild.get("group");
+
+        JsonArray enumValues = (JsonArray) matchedChild.get("enum");
+        if (enumValues != null && !enumValues.isEmpty()) {
+            for (Object e : enumValues) {
+                String value = String.valueOf(e);
+                boolean isDefault = value.equals(String.valueOf(defVal));
                 items.add(new AutocompletePopup.CompletionItem(
-                        dfName, desc, "dataformat",
-                        null, dep, depNote, label));
-            }
-        }
-
-        
items.sort(Comparator.comparing(AutocompletePopup.CompletionItem::deprecated)
-                .thenComparing(AutocompletePopup.CompletionItem::key, 
String.CASE_INSENSITIVE_ORDER));
-        return items;
-    }
-
-    private List<AutocompletePopup.CompletionItem> 
provideDataFormatOptionCompletions(String contextAfterPrefix) {
-        CamelCatalog catalog = getCatalog();
-        if (catalog == null) {
-            return List.of();
-        }
-
-        // context format: "dataFormatName" or 
"dataFormatName:existingKey1,existingKey2,..."
-        String[] parts = contextAfterPrefix.split(":", 2);
-        String dfName = parts[0];
-
-        Set<String> existingKeys = Set.of();
-        if (parts.length > 1 && !parts[1].isEmpty()) {
-            existingKeys = new HashSet<>(Arrays.asList(parts[1].split(",")));
-        }
-
-        DataFormatModel model = catalog.dataFormatModel(dfName);
-        if (model == null) {
-            return List.of();
-        }
-
-        List<AutocompletePopup.CompletionItem> items = new ArrayList<>();
-        for (DataFormatModel.DataFormatOptionModel opt : model.getOptions()) {
-            if (!"attribute".equals(opt.getKind())) {
-                continue;
-            }
-            if (EIP_BOILERPLATE.contains(opt.getName())) {
-                continue;
-            }
-            if (existingKeys.contains(opt.getName()) && !opt.isMultiValue()) {
-                continue;
+                        value, desc, type, isDefault ? value : defVal, false, 
null, group));
             }
+        } else if ("boolean".equalsIgnoreCase(type)) {
             items.add(new AutocompletePopup.CompletionItem(
-                    opt.getName(), opt.getDescription(), opt.getType(),
-                    opt.getDefaultValue(), opt.isDeprecated(), 
opt.getDeprecationNote(),
-                    opt.getGroup(), opt.isRequired()));
+                    "true", desc, "boolean", defVal, false, null, group));
+            items.add(new AutocompletePopup.CompletionItem(
+                    "false", desc, "boolean", defVal, false, null, group));
         }
-
-        
items.sort(Comparator.comparing(AutocompletePopup.CompletionItem::deprecated)
-                .thenComparing((a, b) -> Boolean.compare(b.required(), 
a.required()))
-                .thenComparing(AutocompletePopup.CompletionItem::key, 
String.CASE_INSENSITIVE_ORDER));
         return items;
     }
 
@@ -1453,18 +1227,9 @@ class SourceTab extends AbstractTab {
         }
 
         // EIP value completion
-        if (context.startsWith("yaml-eip-value:")) {
-            return provideEipValueCompletions(context.substring(15));
-        }
-
-        // Language option value completion
-        if (context.startsWith("yaml-lang-value:")) {
-            return provideLanguageValueCompletions(context.substring(16));
-        }
-
-        // Data format option value completion
-        if (context.startsWith("yaml-df-value:")) {
-            return provideDataFormatValueCompletions(context.substring(14));
+        // tree-driven value completion
+        if (context.startsWith("yaml-tree-value:")) {
+            return provideTreeValueCompletions(context.substring(16));
         }
 
         if (!context.startsWith("yaml:")) {
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceViewer.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceViewer.java
index 1b480c4b631c..1c0d7a60326e 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceViewer.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceViewer.java
@@ -275,6 +275,10 @@ class SourceViewer {
             validationErrors = null;
             return true;
         }
+        if (autocompletePopup != null) {
+            autocompletePopup = null;
+            return true;
+        }
         exitEditMode();
         return true;
     }
@@ -462,9 +466,6 @@ class SourceViewer {
             } else if (result == AutocompletePopup.Result.CURSOR_LEFT) {
                 editState.moveCursorLeft();
             }
-            if (autocompletePopup != null && !autocompletePopup.hasItems()) {
-                autocompletePopup = null;
-            }
             return true;
         }
         if (ke.isCancel()) {
@@ -821,28 +822,53 @@ class SourceViewer {
     record YamlEipContext(String eipName) {
     }
 
-    record YamlDataFormatContext(String eipName) {
-    }
-
-    record YamlDataFormatOptionContext(String dataFormatName) {
-    }
-
-    record YamlEndpointBlockContext(String eipName, boolean consumer, 
java.util.Set<String> existingKeys) {
-    }
+    String findParentYamlKey(int fromRow) {
+        String cursorLine = editState.getLine(fromRow);
+        int cursorIndent = countLeadingSpaces(cursorLine);
 
-    record YamlExpressionContext(String eipName) {
-    }
+        if (cursorLine.isBlank() && cursorIndent == 0) {
+            int lineCount = editState.lineCount();
+            for (int i = fromRow + 1; i < lineCount; i++) {
+                String next = editState.getLine(i);
+                if (!next.isBlank()) {
+                    cursorIndent = countLeadingSpaces(next);
+                    break;
+                }
+            }
+            if (cursorIndent == 0) {
+                for (int i = fromRow - 1; i >= 0; i--) {
+                    String prev = editState.getLine(i);
+                    if (!prev.isBlank()) {
+                        cursorIndent = countLeadingSpaces(prev);
+                        break;
+                    }
+                }
+            }
+        }
 
-    record YamlLanguageOptionContext(String languageName) {
+        // walk up to find parent key at lower indent
+        for (int i = fromRow; i >= 0; i--) {
+            String line = editState.getLine(i);
+            if (line.isBlank()) {
+                continue;
+            }
+            int indent = countLeadingSpaces(line);
+            if (indent < cursorIndent) {
+                String key = extractEipName(line.trim());
+                if (key != null) {
+                    // "steps" in YAML maps to the "steps" node in the tree
+                    return dashToCamelCase(key);
+                }
+                break;
+            }
+        }
+        return "root";
     }
 
     private static final java.util.Set<String> STRUCTURAL_KEYS
             = java.util.Set.of("steps", "uri", "parameters", "from", 
"expression", "routeConfiguration",
                     "routeTemplate", "templatedRoute", "rest", "beans");
 
-    private static final java.util.Set<String> MARSHAL_EIPS
-            = java.util.Set.of("marshal", "unmarshal");
-
     YamlEipContext findEnclosingEip(int fromRow) {
         String cursorLine = editState.getLine(fromRow);
         int cursorIndent = countLeadingSpaces(cursorLine);
@@ -916,259 +942,34 @@ class SourceViewer {
         return null;
     }
 
-    YamlEndpointBlockContext findEndpointBlockContext(int fromRow) {
-        String cursorLine = editState.getLine(fromRow);
-        int cursorIndent = countLeadingSpaces(cursorLine);
-        if (cursorLine.isBlank() && cursorIndent == 0) {
-            int lineCount = editState.lineCount();
-            for (int i = fromRow + 1; i < lineCount; i++) {
-                String next = editState.getLine(i);
-                if (!next.isBlank()) {
-                    cursorIndent = countLeadingSpaces(next);
-                    break;
-                }
-            }
-            if (cursorIndent == 0) {
-                for (int i = fromRow - 1; i >= 0; i--) {
-                    String prev = editState.getLine(i);
-                    if (!prev.isBlank()) {
-                        cursorIndent = countLeadingSpaces(prev);
-                        break;
-                    }
-                }
-            }
-        }
-
-        // walk up to find the parent key
-        for (int i = fromRow; i >= 0; i--) {
-            String line = editState.getLine(i);
-            if (line.isBlank()) {
-                continue;
-            }
-            int indent = countLeadingSpaces(line);
-            if (indent < cursorIndent) {
-                String parentKey = extractEipName(line.trim());
-                if (parentKey != null && (CONSUMER_EIPS.contains(parentKey) || 
PRODUCER_EIPS.contains(parentKey))) {
-                    // check siblings: must have uri: and NOT be inside 
parameters:
-                    java.util.Set<String> siblings = 
collectExistingSiblingKeys(fromRow);
-                    if (siblings.contains("uri") && 
!siblings.contains("parameters")) {
-                        // skip if the cursor line itself has a key with colon 
(not a blank addition)
-                        String ct = cursorLine.trim();
-                        if (ct.startsWith("- ")) {
-                            ct = ct.substring(2).trim();
-                        }
-                        if (ct.indexOf(':') > 0) {
-                            return null;
-                        }
-                        boolean isConsumer = CONSUMER_EIPS.contains(parentKey);
-                        return new YamlEndpointBlockContext(parentKey, 
isConsumer, siblings);
-                    }
-                }
-                break;
-            }
-        }
-        return null;
-    }
-
-    YamlExpressionContext findExpressionContext(int fromRow) {
-        // cursor is inside an expression: block — find the parent EIP
-        String cursorLine = editState.getLine(fromRow);
-        int cursorIndent = countLeadingSpaces(cursorLine);
-        if (cursorLine.isBlank() && cursorIndent == 0) {
-            int lineCount = editState.lineCount();
-            for (int i = fromRow + 1; i < lineCount; i++) {
-                String next = editState.getLine(i);
-                if (!next.isBlank()) {
-                    cursorIndent = countLeadingSpaces(next);
-                    break;
-                }
-            }
-            if (cursorIndent == 0) {
-                for (int i = fromRow - 1; i >= 0; i--) {
-                    String prev = editState.getLine(i);
-                    if (!prev.isBlank()) {
-                        cursorIndent = countLeadingSpaces(prev);
-                        break;
-                    }
-                }
-            }
-        }
-
-        // walk up to find the immediate parent key
-        for (int i = fromRow; i >= 0; i--) {
-            String line = editState.getLine(i);
-            if (line.isBlank()) {
-                continue;
-            }
-            int indent = countLeadingSpaces(line);
-            if (indent < cursorIndent) {
-                String parentKey = extractEipName(line.trim());
-                if ("expression".equals(parentKey)) {
-                    // found expression: — now find the EIP above it
-                    YamlEipContext eipCtx = findEnclosingEip(i);
-                    if (eipCtx != null) {
-                        return new YamlExpressionContext(eipCtx.eipName());
-                    }
-                }
-                break;
-            }
-        }
-        return null;
-    }
-
-    YamlLanguageOptionContext findLanguageOptionContext(int fromRow) {
-        // cursor is inside a language block (e.g., simple:) under expression: 
under an EIP
+    java.util.Set<String> collectExistingSiblingKeys(int fromRow) {
+        java.util.Set<String> keys = new java.util.LinkedHashSet<>();
         String cursorLine = editState.getLine(fromRow);
         int cursorIndent = countLeadingSpaces(cursorLine);
-        if (cursorLine.isBlank() && cursorIndent == 0) {
-            int lineCount = editState.lineCount();
-            for (int i = fromRow + 1; i < lineCount; i++) {
-                String next = editState.getLine(i);
-                if (!next.isBlank()) {
-                    cursorIndent = countLeadingSpaces(next);
-                    break;
-                }
-            }
-            if (cursorIndent == 0) {
-                for (int i = fromRow - 1; i >= 0; i--) {
-                    String prev = editState.getLine(i);
-                    if (!prev.isBlank()) {
-                        cursorIndent = countLeadingSpaces(prev);
-                        break;
-                    }
-                }
-            }
-        }
-
-        // walk up: parent should be a language name, grandparent should be 
expression:
-        String parentKey = null;
-        int parentIndent = -1;
-        for (int i = fromRow; i >= 0; i--) {
-            String line = editState.getLine(i);
-            if (line.isBlank()) {
-                continue;
-            }
-            int indent = countLeadingSpaces(line);
-            if (indent < cursorIndent) {
-                parentKey = extractEipName(line.trim());
-                parentIndent = indent;
-                break;
-            }
-        }
-        if (parentKey == null || parentIndent < 0) {
-            return null;
-        }
 
-        // grandparent should be expression:
-        for (int i = findLineAbove(fromRow, parentIndent); i >= 0; i--) {
-            String line = editState.getLine(i);
-            if (line.isBlank()) {
-                continue;
-            }
-            int indent = countLeadingSpaces(line);
-            if (indent < parentIndent) {
-                String grandparent = extractEipName(line.trim());
-                if ("expression".equals(grandparent)) {
-                    return new YamlLanguageOptionContext(parentKey);
-                }
-                break;
-            }
-        }
-        return null;
-    }
-
-    YamlDataFormatContext findMarshalContext(int fromRow) {
-        YamlEipContext eipCtx = findEnclosingEip(fromRow);
-        if (eipCtx != null && MARSHAL_EIPS.contains(eipCtx.eipName())) {
-            return new YamlDataFormatContext(eipCtx.eipName());
-        }
-        return null;
-    }
-
-    YamlDataFormatOptionContext findDataFormatOptionContext(int fromRow) {
-        String cursorLine = editState.getLine(fromRow);
-        int cursorIndent = countLeadingSpaces(cursorLine);
-        if (cursorLine.isBlank() && cursorIndent == 0) {
-            int lineCount = editState.lineCount();
-            for (int i = fromRow + 1; i < lineCount; i++) {
+        // for blank lines, check both directions and use the deeper indent 
(sibling level)
+        if (cursorLine.isBlank()) {
+            int succIndent = -1;
+            int predIndent = -1;
+            int lineCount2 = editState.lineCount();
+            for (int i = fromRow + 1; i < lineCount2; i++) {
                 String next = editState.getLine(i);
                 if (!next.isBlank()) {
-                    cursorIndent = countLeadingSpaces(next);
+                    succIndent = countLeadingSpaces(next);
                     break;
                 }
             }
-            if (cursorIndent == 0) {
-                for (int i = fromRow - 1; i >= 0; i--) {
-                    String prev = editState.getLine(i);
-                    if (!prev.isBlank()) {
-                        cursorIndent = countLeadingSpaces(prev);
-                        break;
-                    }
-                }
-            }
-        }
-
-        // walk up to find parent key (data format name) then grandparent 
(marshal/unmarshal)
-        String parentKey = null;
-        int parentIndent = -1;
-        for (int i = fromRow; i >= 0; i--) {
-            String line = editState.getLine(i);
-            if (line.isBlank()) {
-                continue;
-            }
-            int indent = countLeadingSpaces(line);
-            if (indent < cursorIndent) {
-                parentKey = extractEipName(line.trim());
-                parentIndent = indent;
-                break;
-            }
-        }
-        if (parentKey == null || parentIndent < 0) {
-            return null;
-        }
-
-        // now find the grandparent — should be marshal or unmarshal
-        for (int i = findLineAbove(fromRow, parentIndent); i >= 0; i--) {
-            String line = editState.getLine(i);
-            if (line.isBlank()) {
-                continue;
-            }
-            int indent = countLeadingSpaces(line);
-            if (indent < parentIndent) {
-                String grandparent = extractEipName(line.trim());
-                if (grandparent != null && MARSHAL_EIPS.contains(grandparent)) 
{
-                    return new YamlDataFormatOptionContext(parentKey);
-                }
-                break;
-            }
-        }
-        return null;
-    }
-
-    private int findLineAbove(int fromRow, int maxIndent) {
-        for (int i = fromRow - 1; i >= 0; i--) {
-            String line = editState.getLine(i);
-            if (!line.isBlank() && countLeadingSpaces(line) < maxIndent) {
-                return i;
-            }
-        }
-        return -1;
-    }
-
-    java.util.Set<String> collectExistingSiblingKeys(int fromRow) {
-        java.util.Set<String> keys = new java.util.LinkedHashSet<>();
-        String cursorLine = editState.getLine(fromRow);
-        int cursorIndent = countLeadingSpaces(cursorLine);
-
-        // for blank lines, derive indent from nearest non-blank sibling
-        if (cursorLine.isBlank()) {
             for (int i = fromRow - 1; i >= 0; i--) {
                 String prev = editState.getLine(i);
                 if (!prev.isBlank()) {
-                    cursorIndent = countLeadingSpaces(prev);
+                    predIndent = countLeadingSpaces(prev);
                     break;
                 }
             }
+            cursorIndent = Math.max(succIndent, predIndent);
+            if (cursorIndent < 0) {
+                cursorIndent = 0;
+            }
         }
 
         // scan upward for siblings at same indent
@@ -1475,29 +1276,10 @@ class SourceViewer {
             return;
         }
 
-        // offer parameters: (and steps: for from:) when inside a from:/to: 
block with uri: but no parameters: yet
-        YamlEndpointBlockContext blockCtx = findEndpointBlockContext(row);
-        if (blockCtx != null && autocompleteProvider != null) {
-            String filter = trimmed;
-            List<AutocompletePopup.CompletionItem> items = new ArrayList<>();
-            items.add(new AutocompletePopup.CompletionItem(
-                    "parameters", "Endpoint configuration options", "object",
-                    null, false, null, "common", true));
-            if (blockCtx.consumer() && 
!blockCtx.existingKeys().contains("steps")) {
-                items.add(new AutocompletePopup.CompletionItem(
-                        "steps", "Processing steps for this route", "array",
-                        null, false, null, "common", true));
-            }
-            autocompletePopup = new AutocompletePopup(items, filter, filter);
-            autocompletePopup.setTitlePrefix(blockCtx.eipName());
-            return;
-        }
-
         YamlEndpointContext ctx = findEnclosingComponent(row);
         if (ctx != null) {
             int colonIdx = trimmed.indexOf(':');
             if (colonIdx > 0) {
-                // value completion — cursor is on a line with key: or key: 
value
                 String optionName = trimmed.substring(0, colonIdx).trim();
                 String valueText = trimmed.substring(colonIdx + 1).trim();
                 if (valueText.startsWith("\"") || valueText.startsWith("'")) {
@@ -1514,7 +1296,6 @@ class SourceViewer {
                     }
                 }
             } else {
-                // key completion — cursor is on an empty or partial key line
                 String filter = colonIdx > 0 ? trimmed.substring(0, 
colonIdx).trim() : trimmed;
                 String role = ctx.consumer() ? "consumer" : "producer";
                 java.util.Set<String> existing = 
collectExistingParameters(row);
@@ -1531,122 +1312,12 @@ class SourceViewer {
             return;
         }
 
-        // Language option completion — cursor inside a language block (e.g., 
simple:) under expression:
-        YamlLanguageOptionContext langOptCtx = findLanguageOptionContext(row);
-        if (langOptCtx != null && autocompleteProvider != null) {
-            int colonIdx = trimmed.indexOf(':');
-            if (colonIdx > 0) {
-                // value completion for language option
-                String optionName = trimmed.substring(0, colonIdx).trim();
-                String valueText = trimmed.substring(colonIdx + 1).trim();
-                if (valueText.startsWith("\"") || valueText.startsWith("'")) {
-                    valueText = valueText.substring(1);
-                }
-                if (valueText.endsWith("\"") || valueText.endsWith("'")) {
-                    valueText = valueText.substring(0, valueText.length() - 1);
-                }
-                if (autocompleteValueProvider != null) {
-                    String context = "yaml-lang-value:" + 
langOptCtx.languageName() + ":" + optionName;
-                    List<AutocompletePopup.CompletionItem> values = 
autocompleteValueProvider.provide(context);
-                    if (values != null && !values.isEmpty()) {
-                        autocompletePopup = new AutocompletePopup(values, "", 
valueText, true);
-                    }
-                }
-            } else {
-                // key completion for language options
-                String filter = trimmed;
-                java.util.Set<String> existing = 
collectExistingSiblingKeys(row);
-                String context = "yaml-lang-opt:" + langOptCtx.languageName();
-                if (!existing.isEmpty()) {
-                    context += ":" + String.join(",", existing);
-                }
-                List<AutocompletePopup.CompletionItem> items = 
autocompleteProvider.provide(context);
-                if (items != null && !items.isEmpty()) {
-                    autocompletePopup = new AutocompletePopup(items, filter, 
filter);
-                    autocompletePopup.setTitlePrefix(langOptCtx.languageName() 
+ " options");
-                }
-            }
-            return;
-        }
-
-        // Expression language name completion — cursor inside an expression: 
block under an EIP
-        YamlExpressionContext exprCtx = findExpressionContext(row);
-        if (exprCtx != null && autocompleteProvider != null) {
-            String filter = trimmed;
-            java.util.Set<String> existing = collectExistingSiblingKeys(row);
-            String context = "yaml-expr:" + exprCtx.eipName();
-            if (!existing.isEmpty()) {
-                context += ":" + String.join(",", existing);
-            }
-            List<AutocompletePopup.CompletionItem> items = 
autocompleteProvider.provide(context);
-            if (items != null && !items.isEmpty()) {
-                autocompletePopup = new AutocompletePopup(items, filter, 
filter);
-                autocompletePopup.setTitlePrefix("Languages");
-            }
-            return;
-        }
-
-        // Data format option completion — cursor inside a data format block 
under marshal/unmarshal
-        YamlDataFormatOptionContext dfOptCtx = 
findDataFormatOptionContext(row);
-        if (dfOptCtx != null && autocompleteProvider != null) {
-            int colonIdx = trimmed.indexOf(':');
-            if (colonIdx > 0) {
-                // value completion for data format option
-                String optionName = trimmed.substring(0, colonIdx).trim();
-                String valueText = trimmed.substring(colonIdx + 1).trim();
-                if (valueText.startsWith("\"") || valueText.startsWith("'")) {
-                    valueText = valueText.substring(1);
-                }
-                if (valueText.endsWith("\"") || valueText.endsWith("'")) {
-                    valueText = valueText.substring(0, valueText.length() - 1);
-                }
-                if (autocompleteValueProvider != null) {
-                    String context = "yaml-df-value:" + 
dfOptCtx.dataFormatName() + ":" + optionName;
-                    List<AutocompletePopup.CompletionItem> values = 
autocompleteValueProvider.provide(context);
-                    if (values != null && !values.isEmpty()) {
-                        autocompletePopup = new AutocompletePopup(values, "", 
valueText, true);
-                    }
-                }
-            } else {
-                // key completion for data format options
-                String filter = trimmed;
-                java.util.Set<String> existing = 
collectExistingSiblingKeys(row);
-                String context = "yaml-df-opt:" + dfOptCtx.dataFormatName();
-                if (!existing.isEmpty()) {
-                    context += ":" + String.join(",", existing);
-                }
-                List<AutocompletePopup.CompletionItem> items = 
autocompleteProvider.provide(context);
-                if (items != null && !items.isEmpty()) {
-                    autocompletePopup = new AutocompletePopup(items, filter, 
filter);
-                    autocompletePopup.setTitlePrefix(dfOptCtx.dataFormatName() 
+ " options");
-                }
-            }
-            return;
-        }
-
-        // Data format name completion — cursor inside a marshal/unmarshal 
block
-        YamlDataFormatContext dfCtx = findMarshalContext(row);
-        if (dfCtx != null && autocompleteProvider != null) {
-            String filter = trimmed;
-            java.util.Set<String> existing = collectExistingSiblingKeys(row);
-            String context = "yaml-df:" + dfCtx.eipName();
-            if (!existing.isEmpty()) {
-                context += ":" + String.join(",", existing);
-            }
-            List<AutocompletePopup.CompletionItem> items = 
autocompleteProvider.provide(context);
-            if (items != null && !items.isEmpty()) {
-                autocompletePopup = new AutocompletePopup(items, filter, 
filter);
-                autocompletePopup.setTitlePrefix("Data Formats");
-            }
-            return;
-        }
-
-        // EIP option completion — cursor is inside an EIP block (not in 
parameters:)
-        YamlEipContext eipCtx = findEnclosingEip(row);
-        if (eipCtx != null && autocompleteProvider != null) {
+        // tree-driven completion — walk up to find parent key, use completion 
tree
+        if (autocompleteProvider != null) {
+            String parentKey = findParentYamlKey(row);
             int colonIdx = trimmed.indexOf(':');
             if (colonIdx > 0) {
-                // value completion for EIP option
+                // value completion
                 String optionName = trimmed.substring(0, colonIdx).trim();
                 String valueText = trimmed.substring(colonIdx + 1).trim();
                 if (valueText.startsWith("\"") || valueText.startsWith("'")) {
@@ -1656,24 +1327,24 @@ class SourceViewer {
                     valueText = valueText.substring(0, valueText.length() - 1);
                 }
                 if (autocompleteValueProvider != null) {
-                    String context = "yaml-eip-value:" + eipCtx.eipName() + 
":" + optionName;
+                    String context = "yaml-tree-value:" + parentKey + ":" + 
optionName;
                     List<AutocompletePopup.CompletionItem> values = 
autocompleteValueProvider.provide(context);
                     if (values != null && !values.isEmpty()) {
                         autocompletePopup = new AutocompletePopup(values, "", 
valueText, true);
                     }
                 }
             } else {
-                // key completion for EIP options
+                // key completion
                 String filter = trimmed;
                 java.util.Set<String> existing = 
collectExistingSiblingKeys(row);
-                String context = "yaml-eip:" + eipCtx.eipName();
+                String context = "yaml-tree:" + parentKey;
                 if (!existing.isEmpty()) {
                     context += ":" + String.join(",", existing);
                 }
                 List<AutocompletePopup.CompletionItem> items = 
autocompleteProvider.provide(context);
                 if (items != null && !items.isEmpty()) {
                     autocompletePopup = new AutocompletePopup(items, filter, 
filter);
-                    autocompletePopup.setTitlePrefix(eipCtx.eipName() + " 
options");
+                    autocompletePopup.setTitlePrefix(parentKey);
                 }
             }
         }
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceViewerEditTest.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceViewerEditTest.java
index 934eb2a56b4d..e4b9c4d01808 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceViewerEditTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceViewerEditTest.java
@@ -83,7 +83,7 @@ class SourceViewerEditTest {
     void eEntersEditModeForLocalFile() {
         viewer.loadFile(sourceFile);
 
-        assertThat(viewer.handleKeyEvent(KeyEvent.ofChar('e', 
KeyModifiers.NONE))).isTrue();
+        assertThat(viewer.handleKeyEvent(KeyEvent.ofKey(KeyCode.F4, 
KeyModifiers.NONE))).isTrue();
 
         assertThat(viewer.isEditMode()).isTrue();
         assertThat(viewer.isTextInputActive()).isTrue();
@@ -92,7 +92,7 @@ class SourceViewerEditTest {
     @Test
     void escCancelsEditModeWithoutClosingViewer() {
         viewer.loadFile(sourceFile);
-        viewer.handleKeyEvent(KeyEvent.ofChar('e', KeyModifiers.NONE));
+        viewer.handleKeyEvent(KeyEvent.ofKey(KeyCode.F4, KeyModifiers.NONE));
         assertThat(viewer.isEditMode()).isTrue();
 
         assertThat(viewer.handleKeyEvent(KeyEvent.ofKey(KeyCode.ESCAPE, 
KeyModifiers.NONE))).isTrue();
@@ -176,7 +176,7 @@ class SourceViewerEditTest {
         assertThat(viewer.isVisible()).isFalse();
         assertThat(viewer.isEditable()).isFalse();
         assertThat(viewer.isEditMode()).isFalse();
-        assertThat(viewer.handleKeyEvent(KeyEvent.ofChar('e', 
KeyModifiers.NONE))).isFalse();
+        assertThat(viewer.handleKeyEvent(KeyEvent.ofKey(KeyCode.F4, 
KeyModifiers.NONE))).isFalse();
     }
 
     @Test
@@ -254,7 +254,7 @@ class SourceViewerEditTest {
         try {
             viewer.loadFile(readOnly);
             assertThat(viewer.isEditable()).isFalse();
-            assertThat(viewer.handleKeyEvent(KeyEvent.ofChar('e', 
KeyModifiers.NONE))).isFalse();
+            assertThat(viewer.handleKeyEvent(KeyEvent.ofKey(KeyCode.F4, 
KeyModifiers.NONE))).isFalse();
             assertThat(viewer.isEditMode()).isFalse();
         } finally {
             readOnly.toFile().setWritable(true);
@@ -386,7 +386,7 @@ class SourceViewerEditTest {
         SourceTab tab = new SourceTab(ctx);
         tab.onTabSelected();
         assertThat(tab.handleKeyEvent(KeyEvent.ofKey(KeyCode.ENTER, 
KeyModifiers.NONE))).isTrue();
-        assertThat(tab.handleKeyEvent(KeyEvent.ofChar('e', 
KeyModifiers.NONE))).isTrue();
+        assertThat(tab.handleKeyEvent(KeyEvent.ofKey(KeyCode.F4, 
KeyModifiers.NONE))).isTrue();
         assertThat(tab.isSourceViewerEditMode()).isTrue();
 
         Rect area = new Rect(0, 0, 80, 24);
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/YamlCompletionTest.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/YamlCompletionTest.java
index 1c3607131703..c6679e959319 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/YamlCompletionTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/YamlCompletionTest.java
@@ -21,16 +21,12 @@ import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.Comparator;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 
 import org.apache.camel.catalog.CamelCatalog;
 import org.apache.camel.catalog.DefaultCamelCatalog;
 import org.apache.camel.tooling.model.ComponentModel;
-import org.apache.camel.tooling.model.DataFormatModel;
-import org.apache.camel.tooling.model.EipModel;
-import org.apache.camel.tooling.model.LanguageModel;
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.io.TempDir;
@@ -787,105 +783,6 @@ class YamlCompletionTest {
         assertThat(ctx.eipName()).isEqualTo("log");
     }
 
-    // --- EIP option completion ---
-
-    @Test
-    void eipCompletionIncludesOnlyAttributes() {
-        List<AutocompletePopup.CompletionItem> items = 
provideEipKeyCompletions("split");
-
-        // streaming is an attribute — should be included
-        assertThat(items).anyMatch(i -> i.key().equals("streaming"));
-        assertThat(items).anyMatch(i -> i.key().equals("parallelProcessing"));
-    }
-
-    @Test
-    void eipCompletionExcludesBoilerplate() {
-        List<AutocompletePopup.CompletionItem> items = 
provideEipKeyCompletions("split");
-
-        assertThat(items).noneMatch(i -> i.key().equals("id"));
-        assertThat(items).noneMatch(i -> i.key().equals("note"));
-        assertThat(items).noneMatch(i -> i.key().equals("description"));
-        assertThat(items).noneMatch(i -> i.key().equals("disabled"));
-    }
-
-    @Test
-    void eipCompletionIncludesExpressionAndElementKinds() {
-        List<AutocompletePopup.CompletionItem> items = 
provideEipKeyCompletions("split");
-
-        // canonical format: expression and element kinds are included as keys
-        assertThat(items).anyMatch(i -> i.key().equals("expression"));
-    }
-
-    @Test
-    void eipCompletionExcludesExistingOptions() {
-        Set<String> existing = Set.of("streaming", "delimiter");
-        List<AutocompletePopup.CompletionItem> items = 
provideEipKeyCompletions("split", existing);
-
-        assertThat(items).noneMatch(i -> i.key().equals("streaming"));
-        assertThat(items).noneMatch(i -> i.key().equals("delimiter"));
-        assertThat(items).isNotEmpty();
-    }
-
-    @Test
-    void eipCompletionForLogEip() {
-        List<AutocompletePopup.CompletionItem> items = 
provideEipKeyCompletions("log");
-
-        assertThat(items).anyMatch(i -> i.key().equals("message"));
-        assertThat(items).anyMatch(i -> i.key().equals("loggingLevel"));
-        assertThat(items).anyMatch(i -> i.key().equals("logName"));
-    }
-
-    @Test
-    void eipValueCompletionForEnum() {
-        List<AutocompletePopup.CompletionItem> items = 
provideEipValueCompletions("log", "loggingLevel");
-
-        assertThat(items).anyMatch(i -> i.key().equals("INFO"));
-        assertThat(items).anyMatch(i -> i.key().equals("ERROR"));
-        assertThat(items).anyMatch(i -> i.key().equals("DEBUG"));
-    }
-
-    @Test
-    void eipValueCompletionForBoolean() {
-        List<AutocompletePopup.CompletionItem> items = 
provideEipValueCompletions("split", "streaming");
-
-        assertThat(items).anyMatch(i -> i.key().equals("true"));
-        assertThat(items).anyMatch(i -> i.key().equals("false"));
-    }
-
-    @Test
-    void eipValueCompletionFiltersIncompatiblePlaceholders() {
-        List<AutocompletePopup.CompletionItem> placeholders = List.of(
-                new AutocompletePopup.CompletionItem(
-                        "{{greeting.message}}", "Hello World", "placeholder",
-                        null, false, null, "application.properties"),
-                new AutocompletePopup.CompletionItem(
-                        "{{log.level}}", "WARN", "placeholder",
-                        null, false, null, "application.properties"));
-
-        // enum option: only placeholders whose value matches a valid enum 
choice should be included
-        List<AutocompletePopup.CompletionItem> items = 
provideEipValueCompletions("log", "loggingLevel", placeholders);
-
-        assertThat(items).anyMatch(i -> i.key().equals("INFO"));
-        assertThat(items).anyMatch(i -> i.key().equals("ERROR"));
-        // {{log.level}} has value "WARN" which IS a valid enum value
-        assertThat(items).anyMatch(i -> i.key().equals("{{log.level}}"));
-        // {{greeting.message}} has value "Hello World" which is NOT a valid 
enum value
-        assertThat(items).noneMatch(i -> 
i.key().equals("{{greeting.message}}"));
-    }
-
-    @Test
-    void eipValueCompletionAllowsPlaceholdersForStringOptions() {
-        List<AutocompletePopup.CompletionItem> placeholders = List.of(
-                new AutocompletePopup.CompletionItem(
-                        "{{greeting.message}}", "Hello World", "placeholder",
-                        null, false, null, "application.properties"));
-
-        // string option (logName): no type filter, all placeholders should be 
included
-        List<AutocompletePopup.CompletionItem> items = 
provideEipValueCompletions("log", "logName", placeholders);
-
-        assertThat(items).anyMatch(i -> 
i.key().equals("{{greeting.message}}"));
-    }
-
     // --- collectExistingSiblingKeys ---
 
     @Test
@@ -1049,180 +946,16 @@ class YamlCompletionTest {
         assertThat(viewer.findScopeLineRow(3)).isEqualTo(3);
     }
 
-    // --- Canonical expression completion (expression: → language → language 
options) ---
-
-    @Test
-    void eipCompletionIncludesExpressionKey() {
-        List<AutocompletePopup.CompletionItem> items = 
provideEipKeyCompletions("split");
-
-        // canonical format: expression is a key, not expanded into language 
names
-        assertThat(items).anyMatch(i -> i.key().equals("expression"));
-        // language names should NOT appear at EIP level
-        assertThat(items).noneMatch(i -> i.key().equals("simple"));
-        assertThat(items).noneMatch(i -> i.key().equals("jsonpath"));
-    }
-
-    @Test
-    void eipCompletionForSetHeaderIncludesExpressionKey() {
-        List<AutocompletePopup.CompletionItem> items = 
provideEipKeyCompletions("setHeader");
-
-        assertThat(items).anyMatch(i -> i.key().equals("expression"));
-        assertThat(items).anyMatch(i -> i.key().equals("name"));
-        // no inline languages at EIP level
-        assertThat(items).noneMatch(i -> i.key().equals("simple"));
-    }
-
-    @Test
-    void eipCompletionExcludesExpressionWhenAlreadySpecified() {
-        Set<String> existing = Set.of("expression");
-        List<AutocompletePopup.CompletionItem> items = 
provideEipKeyCompletions("split", existing);
-
-        assertThat(items).noneMatch(i -> i.key().equals("expression"));
-        assertThat(items).anyMatch(i -> i.key().equals("streaming"));
-    }
-
-    @Test
-    void expressionContextOffersLanguageNames() {
-        List<AutocompletePopup.CompletionItem> items = 
provideExpressionLanguageCompletions("split");
-
-        assertThat(items).anyMatch(i -> i.key().equals("simple"));
-        assertThat(items).anyMatch(i -> i.key().equals("jsonpath"));
-        assertThat(items).anyMatch(i -> i.key().equals("xpath"));
-        assertThat(items).anyMatch(i -> i.key().equals("constant"));
-        var simple = items.stream().filter(i -> 
i.key().equals("simple")).findFirst();
-        assertThat(simple).isPresent();
-        assertThat(simple.get().type()).isEqualTo("language");
-    }
-
-    @Test
-    void expressionContextExcludesAlreadySpecified() {
-        Set<String> existing = Set.of("simple");
-        List<AutocompletePopup.CompletionItem> items = 
provideExpressionLanguageCompletions("split", existing);
-
-        assertThat(items).isEmpty();
-    }
-
-    @Test
-    void languageOptionCompletionForSimple() {
-        List<AutocompletePopup.CompletionItem> items = 
provideLanguageOptionCompletions("simple");
-
-        assertThat(items).anyMatch(i -> i.key().equals("expression"));
-        assertThat(items).anyMatch(i -> i.key().equals("resultType"));
-    }
-
-    @Test
-    void languageOptionCompletionForJsonpath() {
-        List<AutocompletePopup.CompletionItem> items = 
provideLanguageOptionCompletions("jsonpath");
-
-        assertThat(items).anyMatch(i -> i.key().equals("expression"));
-        assertThat(items).anyMatch(i -> i.key().equals("resultType"));
-    }
-
-    @Test
-    void languageOptionCompletionExcludesExisting() {
-        Set<String> existing = Set.of("expression");
-        List<AutocompletePopup.CompletionItem> items = 
provideLanguageOptionCompletions("simple", existing);
-
-        assertThat(items).noneMatch(i -> i.key().equals("expression"));
-        assertThat(items).anyMatch(i -> i.key().equals("resultType"));
-    }
-
-    @Test
-    void languageOptionCompletionExcludesBoilerplate() {
-        List<AutocompletePopup.CompletionItem> items = 
provideLanguageOptionCompletions("simple");
-
-        assertThat(items).noneMatch(i -> i.key().equals("id"));
-        assertThat(items).noneMatch(i -> i.key().equals("description"));
-    }
-
-    @Test
-    void eipCompletionForLogDoesNotIncludeExpression() {
-        List<AutocompletePopup.CompletionItem> items = 
provideEipKeyCompletions("log");
-
-        // log has no expression option
-        assertThat(items).noneMatch(i -> i.key().equals("expression"));
-    }
-
-    // --- Data format name completion ---
-
-    @Test
-    void dataFormatNameCompletionForMarshal() {
-        List<AutocompletePopup.CompletionItem> items = 
provideDataFormatNameCompletions("marshal");
-
-        assertThat(items).isNotEmpty();
-        assertThat(items).anyMatch(i -> i.key().equals("json"));
-        assertThat(items).anyMatch(i -> i.key().equals("csv"));
-        assertThat(items).anyMatch(i -> i.key().equals("avro"));
-        // type should be "dataformat"
-        var json = items.stream().filter(i -> 
i.key().equals("json")).findFirst();
-        assertThat(json).isPresent();
-        assertThat(json.get().type()).isEqualTo("dataformat");
-    }
-
-    @Test
-    void dataFormatNameCompletionForUnmarshal() {
-        List<AutocompletePopup.CompletionItem> items = 
provideDataFormatNameCompletions("unmarshal");
-
-        assertThat(items).isNotEmpty();
-        assertThat(items).anyMatch(i -> i.key().equals("json"));
-    }
-
-    @Test
-    void dataFormatNameCompletionExcludesAlreadySpecified() {
-        Set<String> existing = Set.of("json");
-        List<AutocompletePopup.CompletionItem> items = 
provideDataFormatNameCompletions("marshal", existing);
-
-        // once a data format is specified, no more should appear
-        assertThat(items).isEmpty();
-    }
-
-    // --- Data format option completion ---
-
-    @Test
-    void dataFormatOptionCompletionForJackson() {
-        List<AutocompletePopup.CompletionItem> items = 
provideDataFormatOptionCompletions("jackson");
-
-        assertThat(items).isNotEmpty();
-        assertThat(items).anyMatch(i -> i.key().equals("prettyPrint"));
-        assertThat(items).anyMatch(i -> i.key().equals("unmarshalType"));
-    }
-
-    @Test
-    void dataFormatOptionCompletionExcludesBoilerplate() {
-        List<AutocompletePopup.CompletionItem> items = 
provideDataFormatOptionCompletions("jackson");
-
-        assertThat(items).noneMatch(i -> i.key().equals("id"));
-        assertThat(items).noneMatch(i -> i.key().equals("description"));
-    }
+    // --- findParentYamlKey context detection (tree-driven) ---
 
     @Test
-    void dataFormatOptionCompletionExcludesExisting() {
-        Set<String> existing = Set.of("prettyPrint");
-        List<AutocompletePopup.CompletionItem> items = 
provideDataFormatOptionCompletions("jackson", existing);
-
-        assertThat(items).noneMatch(i -> i.key().equals("prettyPrint"));
-        assertThat(items).anyMatch(i -> i.key().equals("unmarshalType"));
-    }
-
-    @Test
-    void dataFormatOptionCompletionForCsv() {
-        List<AutocompletePopup.CompletionItem> items = 
provideDataFormatOptionCompletions("csv");
-
-        assertThat(items).isNotEmpty();
-        assertThat(items).anyMatch(i -> i.key().equals("delimiter"));
-    }
-
-    // --- Expression context detection ---
-
-    @Test
-    void findExpressionContextInsideExpressionBlock() throws IOException {
+    void findParentYamlKeyInsideSplit() throws IOException {
         String yaml = String.join("\n",
                 "- from:",
                 "    uri: timer:tick",
                 "    steps:",
                 "      - split:",
-                "          expression:",
-                "            ",
+                "          ",
                 "");
 
         Path file = tempDir.resolve("route.camel.yaml");
@@ -1232,20 +965,18 @@ class YamlCompletionTest {
         viewer.loadFile(file);
         viewer.enterEditMode();
 
-        SourceViewer.YamlExpressionContext ctx = 
viewer.findExpressionContext(5);
-        assertThat(ctx).isNotNull();
-        assertThat(ctx.eipName()).isEqualTo("split");
+        assertThat(viewer.findParentYamlKey(4)).isEqualTo("split");
     }
 
     @Test
-    void findExpressionContextReturnsNullOutsideExpression() throws 
IOException {
+    void findParentYamlKeyInsideExpression() throws IOException {
         String yaml = String.join("\n",
                 "- from:",
                 "    uri: timer:tick",
                 "    steps:",
                 "      - split:",
-                "          streaming: true",
-                "          ",
+                "          expression:",
+                "            ",
                 "");
 
         Path file = tempDir.resolve("route.camel.yaml");
@@ -1255,14 +986,11 @@ class YamlCompletionTest {
         viewer.loadFile(file);
         viewer.enterEditMode();
 
-        SourceViewer.YamlExpressionContext ctx = 
viewer.findExpressionContext(5);
-        assertThat(ctx).isNull();
+        assertThat(viewer.findParentYamlKey(5)).isEqualTo("expression");
     }
 
-    // --- Language option context detection ---
-
     @Test
-    void findLanguageOptionContextInsideSimple() throws IOException {
+    void findParentYamlKeyInsideSimpleUnderExpression() throws IOException {
         String yaml = String.join("\n",
                 "- from:",
                 "    uri: timer:tick",
@@ -1280,20 +1008,16 @@ class YamlCompletionTest {
         viewer.loadFile(file);
         viewer.enterEditMode();
 
-        SourceViewer.YamlLanguageOptionContext ctx = 
viewer.findLanguageOptionContext(6);
-        assertThat(ctx).isNotNull();
-        assertThat(ctx.languageName()).isEqualTo("simple");
+        assertThat(viewer.findParentYamlKey(6)).isEqualTo("simple");
     }
 
     @Test
-    void findLanguageOptionContextReturnsNullWhenParentIsNotExpression() 
throws IOException {
+    void findParentYamlKeyInsideFrom() throws IOException {
         String yaml = String.join("\n",
-                "- from:",
-                "    uri: timer:tick",
-                "    steps:",
-                "      - split:",
-                "          streaming:",
-                "            ",
+                "- route:",
+                "    from:",
+                "      uri: timer",
+                "      ",
                 "");
 
         Path file = tempDir.resolve("route.camel.yaml");
@@ -1303,20 +1027,16 @@ class YamlCompletionTest {
         viewer.loadFile(file);
         viewer.enterEditMode();
 
-        SourceViewer.YamlLanguageOptionContext ctx = 
viewer.findLanguageOptionContext(5);
-        assertThat(ctx).isNull();
+        assertThat(viewer.findParentYamlKey(3)).isEqualTo("from");
     }
 
-    // --- Marshal/unmarshal context detection ---
-
     @Test
-    void findMarshalContextInsideMarshal() throws IOException {
+    void findParentYamlKeyInsideSteps() throws IOException {
         String yaml = String.join("\n",
                 "- from:",
                 "    uri: timer:tick",
                 "    steps:",
-                "      - marshal:",
-                "          ",
+                "      ",
                 "");
 
         Path file = tempDir.resolve("route.camel.yaml");
@@ -1326,19 +1046,14 @@ class YamlCompletionTest {
         viewer.loadFile(file);
         viewer.enterEditMode();
 
-        SourceViewer.YamlDataFormatContext ctx = viewer.findMarshalContext(4);
-        assertThat(ctx).isNotNull();
-        assertThat(ctx.eipName()).isEqualTo("marshal");
+        assertThat(viewer.findParentYamlKey(3)).isEqualTo("steps");
     }
 
     @Test
-    void findMarshalContextInsideUnmarshal() throws IOException {
+    void findParentYamlKeyInsideRoute() throws IOException {
         String yaml = String.join("\n",
-                "- from:",
-                "    uri: timer:tick",
-                "    steps:",
-                "      - unmarshal:",
-                "          ",
+                "- route:",
+                "    ",
                 "");
 
         Path file = tempDir.resolve("route.camel.yaml");
@@ -1348,18 +1063,16 @@ class YamlCompletionTest {
         viewer.loadFile(file);
         viewer.enterEditMode();
 
-        SourceViewer.YamlDataFormatContext ctx = viewer.findMarshalContext(4);
-        assertThat(ctx).isNotNull();
-        assertThat(ctx.eipName()).isEqualTo("unmarshal");
+        assertThat(viewer.findParentYamlKey(1)).isEqualTo("route");
     }
 
     @Test
-    void findMarshalContextReturnsNullForSplit() throws IOException {
+    void findParentYamlKeyInsideMarshal() throws IOException {
         String yaml = String.join("\n",
                 "- from:",
                 "    uri: timer:tick",
                 "    steps:",
-                "      - split:",
+                "      - marshal:",
                 "          ",
                 "");
 
@@ -1370,14 +1083,11 @@ class YamlCompletionTest {
         viewer.loadFile(file);
         viewer.enterEditMode();
 
-        SourceViewer.YamlDataFormatContext ctx = viewer.findMarshalContext(4);
-        assertThat(ctx).isNull();
+        assertThat(viewer.findParentYamlKey(4)).isEqualTo("marshal");
     }
 
-    // --- Data format option context detection ---
-
     @Test
-    void findDataFormatOptionContextInsideCsv() throws IOException {
+    void findParentYamlKeyInsideCsvUnderMarshal() throws IOException {
         String yaml = String.join("\n",
                 "- from:",
                 "    uri: timer:tick",
@@ -1394,21 +1104,13 @@ class YamlCompletionTest {
         viewer.loadFile(file);
         viewer.enterEditMode();
 
-        SourceViewer.YamlDataFormatOptionContext ctx = 
viewer.findDataFormatOptionContext(5);
-        assertThat(ctx).isNotNull();
-        assertThat(ctx.dataFormatName()).isEqualTo("csv");
+        assertThat(viewer.findParentYamlKey(5)).isEqualTo("csv");
     }
 
     @Test
-    void findDataFormatOptionContextInsideUnmarshal() throws IOException {
+    void findParentYamlKeyAtRootLevel() throws IOException {
         String yaml = String.join("\n",
-                "- from:",
-                "    uri: timer:tick",
-                "    steps:",
-                "      - unmarshal:",
-                "          csv:",
-                "            delimiter: \";\"",
-                "            ",
+                "",
                 "");
 
         Path file = tempDir.resolve("route.camel.yaml");
@@ -1418,42 +1120,17 @@ class YamlCompletionTest {
         viewer.loadFile(file);
         viewer.enterEditMode();
 
-        SourceViewer.YamlDataFormatOptionContext ctx = 
viewer.findDataFormatOptionContext(6);
-        assertThat(ctx).isNotNull();
-        assertThat(ctx.dataFormatName()).isEqualTo("csv");
+        assertThat(viewer.findParentYamlKey(0)).isEqualTo("root");
     }
 
     @Test
-    void findDataFormatOptionContextReturnsNullInsideSplit() throws 
IOException {
+    void findParentYamlKeyConvertsKebabCase() throws IOException {
         String yaml = String.join("\n",
                 "- from:",
                 "    uri: timer:tick",
                 "    steps:",
-                "      - split:",
-                "          simple:",
-                "            ",
-                "");
-
-        Path file = tempDir.resolve("route.camel.yaml");
-        Files.writeString(file, yaml);
-
-        SourceViewer viewer = new SourceViewer();
-        viewer.loadFile(file);
-        viewer.enterEditMode();
-
-        SourceViewer.YamlDataFormatOptionContext ctx = 
viewer.findDataFormatOptionContext(5);
-        assertThat(ctx).isNull();
-    }
-
-    // --- Route-level option completion ---
-
-    @Test
-    void findEnclosingEipDetectsRoute() throws IOException {
-        String yaml = String.join("\n",
-                "- route:",
-                "    ",
-                "    from:",
-                "      uri: timer:tick",
+                "      - circuit-breaker:",
+                "          ",
                 "");
 
         Path file = tempDir.resolve("route.camel.yaml");
@@ -1463,27 +1140,7 @@ class YamlCompletionTest {
         viewer.loadFile(file);
         viewer.enterEditMode();
 
-        SourceViewer.YamlEipContext ctx = viewer.findEnclosingEip(1);
-        assertThat(ctx).isNotNull();
-        assertThat(ctx.eipName()).isEqualTo("route");
-    }
-
-    @Test
-    void routeEipCompletionIncludesRouteOptions() {
-        List<AutocompletePopup.CompletionItem> items = 
provideEipKeyCompletions("route");
-
-        assertThat(items).anyMatch(i -> i.key().equals("autoStartup"));
-        assertThat(items).anyMatch(i -> i.key().equals("streamCache"));
-        assertThat(items).anyMatch(i -> i.key().equals("logMask"));
-        assertThat(items).anyMatch(i -> i.key().equals("messageHistory"));
-    }
-
-    @Test
-    void routeEipCompletionExcludesStructural() {
-        List<AutocompletePopup.CompletionItem> items = 
provideEipKeyCompletions("route");
-
-        // from and steps are not attribute kind, should not appear
-        assertThat(items).noneMatch(i -> i.key().equals("from"));
+        assertThat(viewer.findParentYamlKey(4)).isEqualTo("circuitBreaker");
     }
 
     // --- Helpers that replicate SourceTab logic for testing ---
@@ -1596,243 +1253,6 @@ class YamlCompletionTest {
         return items;
     }
 
-    private static final Set<String> EIP_BOILERPLATE = Set.of("id", "note", 
"description", "disabled",
-            "input", "outputs", "steps");
-
-    private List<AutocompletePopup.CompletionItem> 
provideEipKeyCompletions(String eipName) {
-        return provideEipKeyCompletions(eipName, Set.of());
-    }
-
-    private List<AutocompletePopup.CompletionItem> 
provideEipKeyCompletions(String eipName, Set<String> existingKeys) {
-        EipModel model = catalog.eipModel(eipName);
-        if (model == null) {
-            return List.of();
-        }
-        List<AutocompletePopup.CompletionItem> items = new ArrayList<>();
-        for (EipModel.EipOptionModel opt : model.getOptions()) {
-            if ("expression".equals(opt.getKind()) || 
"element".equals(opt.getKind())) {
-                if (EIP_BOILERPLATE.contains(opt.getName())) {
-                    continue;
-                }
-                if (existingKeys.contains(opt.getName())) {
-                    continue;
-                }
-                items.add(new AutocompletePopup.CompletionItem(
-                        opt.getName(), opt.getDescription(), opt.getType(),
-                        opt.getDefaultValue(), opt.isDeprecated(), 
opt.getDeprecationNote(),
-                        opt.getGroup(), opt.isRequired()));
-                continue;
-            }
-            if (!"attribute".equals(opt.getKind())) {
-                continue;
-            }
-            if (EIP_BOILERPLATE.contains(opt.getName())) {
-                continue;
-            }
-            if (existingKeys.contains(opt.getName()) && !opt.isMultiValue()) {
-                continue;
-            }
-            items.add(new AutocompletePopup.CompletionItem(
-                    opt.getName(), opt.getDescription(), opt.getType(),
-                    opt.getDefaultValue(), opt.isDeprecated(), 
opt.getDeprecationNote(),
-                    opt.getGroup(), opt.isRequired()));
-        }
-        
items.sort(Comparator.comparing(AutocompletePopup.CompletionItem::deprecated)
-                .thenComparing((a, b) -> Boolean.compare(b.required(), 
a.required()))
-                .thenComparing(AutocompletePopup.CompletionItem::key, 
String.CASE_INSENSITIVE_ORDER));
-        return items;
-    }
-
-    private List<AutocompletePopup.CompletionItem> 
provideExpressionLanguageCompletions(String eipName) {
-        return provideExpressionLanguageCompletions(eipName, Set.of());
-    }
-
-    private List<AutocompletePopup.CompletionItem> 
provideExpressionLanguageCompletions(
-            String eipName, Set<String> existingKeys) {
-        EipModel model = catalog.eipModel(eipName);
-        if (model == null) {
-            return List.of();
-        }
-        List<AutocompletePopup.CompletionItem> items = new ArrayList<>();
-        for (EipModel.EipOptionModel opt : model.getOptions()) {
-            if (!"expression".equals(opt.getKind())) {
-                continue;
-            }
-            List<String> oneOfs = opt.getOneOfs();
-            if (oneOfs == null || oneOfs.isEmpty()) {
-                continue;
-            }
-            if (oneOfs.stream().anyMatch(existingKeys::contains)) {
-                continue;
-            }
-            for (String langName : oneOfs) {
-                LanguageModel langModel = catalog.languageModel(langName);
-                String desc = langModel != null
-                        ? langModel.getTitle() + " - " + 
langModel.getDescription()
-                        : langName;
-                String label = langModel != null ? langModel.getLabel() : 
"language";
-                boolean dep = langModel != null && langModel.isDeprecated();
-                String depNote = langModel != null ? 
langModel.getDeprecationNote() : null;
-                items.add(new AutocompletePopup.CompletionItem(
-                        langName, desc, "language", null, dep, depNote, 
label));
-            }
-        }
-        
items.sort(Comparator.comparing(AutocompletePopup.CompletionItem::deprecated)
-                .thenComparing(AutocompletePopup.CompletionItem::key, 
String.CASE_INSENSITIVE_ORDER));
-        return items;
-    }
-
-    private List<AutocompletePopup.CompletionItem> 
provideLanguageOptionCompletions(String langName) {
-        return provideLanguageOptionCompletions(langName, Set.of());
-    }
-
-    private List<AutocompletePopup.CompletionItem> 
provideLanguageOptionCompletions(
-            String langName, Set<String> existingKeys) {
-        LanguageModel model = catalog.languageModel(langName);
-        if (model == null) {
-            return List.of();
-        }
-        List<AutocompletePopup.CompletionItem> items = new ArrayList<>();
-        for (LanguageModel.LanguageOptionModel opt : model.getOptions()) {
-            if (!"attribute".equals(opt.getKind()) && 
!"value".equals(opt.getKind())) {
-                continue;
-            }
-            if (EIP_BOILERPLATE.contains(opt.getName())) {
-                continue;
-            }
-            if (existingKeys.contains(opt.getName()) && !opt.isMultiValue()) {
-                continue;
-            }
-            items.add(new AutocompletePopup.CompletionItem(
-                    opt.getName(), opt.getDescription(), opt.getType(),
-                    opt.getDefaultValue(), opt.isDeprecated(), 
opt.getDeprecationNote(),
-                    opt.getGroup(), opt.isRequired()));
-        }
-        
items.sort(Comparator.comparing(AutocompletePopup.CompletionItem::deprecated)
-                .thenComparing((a, b) -> Boolean.compare(b.required(), 
a.required()))
-                .thenComparing(AutocompletePopup.CompletionItem::key, 
String.CASE_INSENSITIVE_ORDER));
-        return items;
-    }
-
-    private List<AutocompletePopup.CompletionItem> 
provideEipValueCompletions(String eipName, String optionName) {
-        return provideEipValueCompletions(eipName, optionName, List.of());
-    }
-
-    private List<AutocompletePopup.CompletionItem> provideEipValueCompletions(
-            String eipName, String optionName, 
List<AutocompletePopup.CompletionItem> placeholders) {
-        EipModel model = catalog.eipModel(eipName);
-        if (model == null) {
-            return List.of();
-        }
-        EipModel.EipOptionModel opt = null;
-        for (EipModel.EipOptionModel o : model.getOptions()) {
-            if (o.getName().equals(optionName)) {
-                opt = o;
-                break;
-            }
-        }
-        List<AutocompletePopup.CompletionItem> items = new ArrayList<>();
-        java.util.function.Predicate<String> valueFilter = null;
-        if (opt != null) {
-            List<String> enums = opt.getEnums();
-            if (enums != null && !enums.isEmpty()) {
-                Set<String> validValues = new HashSet<>();
-                for (String value : enums) {
-                    validValues.add(value.toLowerCase());
-                    boolean isDefault = 
value.equals(String.valueOf(opt.getDefaultValue()));
-                    items.add(new AutocompletePopup.CompletionItem(
-                            value, opt.getDescription(), opt.getType(),
-                            isDefault ? value : opt.getDefaultValue(),
-                            false, null, opt.getGroup()));
-                }
-                valueFilter = v -> validValues.contains(v.toLowerCase());
-            } else if ("boolean".equalsIgnoreCase(opt.getType())
-                    || "java.lang.Boolean".equals(opt.getJavaType())) {
-                valueFilter = v -> "true".equalsIgnoreCase(v) || 
"false".equalsIgnoreCase(v);
-                items.add(new AutocompletePopup.CompletionItem(
-                        "true", opt.getDescription(), "boolean", 
opt.getDefaultValue(),
-                        false, null, opt.getGroup()));
-                items.add(new AutocompletePopup.CompletionItem(
-                        "false", opt.getDescription(), "boolean", 
opt.getDefaultValue(),
-                        false, null, opt.getGroup()));
-            }
-        }
-        for (AutocompletePopup.CompletionItem ph : placeholders) {
-            if (valueFilter == null || (ph.description() != null && 
valueFilter.test(ph.description()))) {
-                items.add(ph);
-            }
-        }
-        return items;
-    }
-
-    private List<AutocompletePopup.CompletionItem> 
provideDataFormatNameCompletions(String eipName) {
-        return provideDataFormatNameCompletions(eipName, Set.of());
-    }
-
-    private List<AutocompletePopup.CompletionItem> 
provideDataFormatNameCompletions(
-            String eipName, Set<String> existingKeys) {
-        EipModel model = catalog.eipModel(eipName);
-        if (model == null) {
-            return List.of();
-        }
-        List<AutocompletePopup.CompletionItem> items = new ArrayList<>();
-        for (EipModel.EipOptionModel opt : model.getOptions()) {
-            List<String> oneOfs = opt.getOneOfs();
-            if (oneOfs == null || oneOfs.isEmpty()) {
-                continue;
-            }
-            if (oneOfs.stream().anyMatch(existingKeys::contains)) {
-                continue;
-            }
-            for (String dfName : oneOfs) {
-                DataFormatModel dfModel = catalog.dataFormatModel(dfName);
-                String desc = dfModel != null
-                        ? dfModel.getTitle() + " - " + dfModel.getDescription()
-                        : dfName;
-                String label = dfModel != null ? dfModel.getLabel() : 
"dataformat";
-                boolean dep = dfModel != null && dfModel.isDeprecated();
-                String depNote = dfModel != null ? 
dfModel.getDeprecationNote() : null;
-                items.add(new AutocompletePopup.CompletionItem(
-                        dfName, desc, "dataformat", null, dep, depNote, 
label));
-            }
-        }
-        
items.sort(Comparator.comparing(AutocompletePopup.CompletionItem::deprecated)
-                .thenComparing(AutocompletePopup.CompletionItem::key, 
String.CASE_INSENSITIVE_ORDER));
-        return items;
-    }
-
-    private List<AutocompletePopup.CompletionItem> 
provideDataFormatOptionCompletions(String dfName) {
-        return provideDataFormatOptionCompletions(dfName, Set.of());
-    }
-
-    private List<AutocompletePopup.CompletionItem> 
provideDataFormatOptionCompletions(
-            String dfName, Set<String> existingKeys) {
-        DataFormatModel model = catalog.dataFormatModel(dfName);
-        if (model == null) {
-            return List.of();
-        }
-        List<AutocompletePopup.CompletionItem> items = new ArrayList<>();
-        for (DataFormatModel.DataFormatOptionModel opt : model.getOptions()) {
-            if (!"attribute".equals(opt.getKind())) {
-                continue;
-            }
-            if (EIP_BOILERPLATE.contains(opt.getName())) {
-                continue;
-            }
-            if (existingKeys.contains(opt.getName()) && !opt.isMultiValue()) {
-                continue;
-            }
-            items.add(new AutocompletePopup.CompletionItem(
-                    opt.getName(), opt.getDescription(), opt.getType(),
-                    opt.getDefaultValue(), opt.isDeprecated(), 
opt.getDeprecationNote(),
-                    opt.getGroup(), opt.isRequired()));
-        }
-        
items.sort(Comparator.comparing(AutocompletePopup.CompletionItem::deprecated)
-                .thenComparing(ci -> !ci.required())
-                .thenComparing(AutocompletePopup.CompletionItem::key, 
String.CASE_INSENSITIVE_ORDER));
-        return items;
-    }
-
     private List<AutocompletePopup.CompletionItem> loadPlaceholders(Path dir) {
         List<AutocompletePopup.CompletionItem> items = new ArrayList<>();
         try (var stream = Files.list(dir)) {

Reply via email to