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
commit 663ad7e646ccbb54c819c84ba85f2bbae30ad0bb Author: Claus Ibsen <[email protected]> AuthorDate: Fri Aug 14 14:49:54 2026 +0200 camel-tui: Fix completion indent for nested EIPs and add list prefix Fix deriveInsertionIndent to use the parent EIP scope for correct nesting when inserting keys via Tab completion. Keys like steps are now placed at the circuitBreaker child level, not under configuration blocks like resilience4jConfiguration. Array-type completions (steps) now auto-insert the - list item prefix on the child line. Breadcrumb skips Configuration keys to match the scope highlighter. Co-Authored-By: Claude Opus 4.6 <[email protected]> Signed-off-by: Claus Ibsen <[email protected]> --- .../dsl/jbang/core/commands/tui/SourceViewer.java | 44 +++++++++++++- .../core/commands/tui/YamlCompletionTest.java | 69 ++++++++++++++++++++++ 2 files changed, 111 insertions(+), 2 deletions(-) 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 b25415b06382..8cdafd29f84d 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 @@ -1185,6 +1185,41 @@ class SourceViewer { } private int deriveInsertionIndent(int fromRow) { + // use the scope line (parent EIP) to derive indent for correct nesting + int scopeRow = findScopeLineRow(fromRow); + if (scopeRow >= 0) { + String scopeLine = editState.getLine(scopeRow); + int scopeIndent = countLeadingSpaces(scopeLine); + String scopeTrimmed = scopeLine.trim(); + if (scopeTrimmed.startsWith("- ")) { + return scopeIndent + 4; + } + return scopeIndent + 2; + } + // on a blank line with whitespace, walk up to find the parent EIP at lower indent + String cursorLine = editState.getLine(fromRow); + if (cursorLine.isBlank()) { + int wsIndent = cursorLine.length(); + if (wsIndent > 0) { + for (int i = fromRow - 1; i >= 0; i--) { + String line = editState.getLine(i); + if (line.isBlank()) { + continue; + } + int indent = countLeadingSpaces(line); + if (indent < wsIndent) { + String t = line.trim(); + if (t.startsWith("- ")) { + t = t.substring(2).trim(); + } + if (t.endsWith(":") && !STRUCTURAL_KEYS.contains(extractEipName(t))) { + return indent + (line.trim().startsWith("- ") ? 4 : 2); + } + wsIndent = indent; + } + } + } + } return deriveIndentFromPredecessor(fromRow); } @@ -1492,7 +1527,8 @@ class SourceViewer { } String key = extractEipName(line.trim()); if (key != null) { - if (!BREADCRUMB_SKIP_KEYS.contains(key)) { + if (!BREADCRUMB_SKIP_KEYS.contains(key) + && !key.endsWith("Configuration")) { parts.add(key); } if ("route".equals(key)) { @@ -1899,7 +1935,11 @@ class SourceViewer { } else { String prefix = listItem ? "- " : ""; editState.insert(indentStr + prefix + item.key() + ":"); - if ("object".equals(item.type()) || "array".equals(item.type())) { + if ("array".equals(item.type())) { + editState.insert('\n'); + int childIndent = indent + (listItem ? 4 : 2); + editState.insert(" ".repeat(childIndent) + "- "); + } else if ("object".equals(item.type())) { editState.insert('\n'); editState.insert(indentStr + (listItem ? " " : " ")); } else { 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 fa636d202aab..222c9650c875 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 @@ -1227,6 +1227,75 @@ class YamlCompletionTest { assertThat(result).doesNotContain("streaming:\n"); } + @Test + void insertStepsUnderCircuitBreakerNotUnderConfiguration() throws IOException { + String yaml = String.join("\n", + "- route:", + " from:", + " uri: timer:tick", + " steps:", + " - circuitBreaker:", + " resilience4jConfiguration:", + " failureRateThreshold: 123", + " ", + ""); + + Path file = tempDir.resolve("route.camel.yaml"); + Files.writeString(file, yaml); + + SourceViewer viewer = new SourceViewer(); + viewer.loadFile(file); + viewer.enterEditMode(); + // move cursor to the blank line (line 7) after failureRateThreshold + viewer.editState().moveCursorToStart(); + for (int i = 0; i < 7; i++) { + viewer.editState().moveCursorDown(); + } + + AutocompletePopup.CompletionItem item = new AutocompletePopup.CompletionItem( + "steps", "Steps", "array", null, false, null, "common"); + viewer.insertYamlCompletion(item, false, " "); + + String result = viewer.editState().text(); + // steps: should be at same indent as resilience4jConfiguration (child of circuitBreaker) + assertThat(result).contains(" steps:"); + // NOT at deeper indent under resilience4jConfiguration + assertThat(result).doesNotContain(" steps:"); + } + + @Test + void insertStepsAfterEnterAddsListItemPrefix() throws IOException { + String yaml = String.join("\n", + "- route:", + " from:", + " uri: timer:tick", + " steps:", + " - circuitBreaker:", + " steps:", + " ", + ""); + + Path file = tempDir.resolve("route.camel.yaml"); + Files.writeString(file, yaml); + + SourceViewer viewer = new SourceViewer(); + viewer.loadFile(file); + viewer.enterEditMode(); + viewer.setListItemNodeChecker(key -> "steps".equals(key) || "root".equals(key)); + // move cursor to line 5 (steps:) and press Enter + viewer.editState().moveCursorToStart(); + for (int i = 0; i < 5; i++) { + viewer.editState().moveCursorDown(); + } + viewer.editState().moveCursorToLineEnd(); + viewer.handleKeyEvent(dev.tamboui.tui.event.KeyEvent.ofKey( + dev.tamboui.tui.event.KeyCode.ENTER, dev.tamboui.tui.event.KeyModifiers.NONE)); + + String result = viewer.editState().text(); + // after steps:, the new line should have "- " list item prefix + assertThat(result).contains(" steps:\n - "); + } + // --- Helpers that replicate SourceTab logic for testing --- private List<AutocompletePopup.CompletionItem> provideKeyCompletions(String componentName, String role) {
