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

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

commit 12e8bdcdf1affa19b3d62786d4a4001db936b164
Author: Luigi De Masi <[email protected]>
AuthorDate: Wed Oct 14 13:41:12 2026 +0200

    CAMEL-25618: Make expert parameter controls visible in the TUI
    
    Place Load params and Clear params beside Run, support their keyboard and
    mouse actions, and preserve the input being assessed. Keep scalar tokens
    beside parameter labels while retaining indentation for longer prose.
    Cover loading, clearing, pending evaluations and narrow layouts.
    
    Co-authored-by: Codex <[email protected]>
---
 .../ROOT/pages/camel-jbang-tui-observe.adoc        |  7 ++
 .../jbang/core/commands/tui/SemanticDetails.java   |  8 +-
 .../core/commands/tui/SemanticPlayground.java      | 83 ++++++++++++++++---
 .../dsl/jbang/core/commands/tui/SemanticTab.java   | 15 ++--
 .../src/main/resources/tui/help/semantic.md        |  6 +-
 .../jbang/core/commands/tui/SemanticTabTest.java   | 92 +++++++++++++++++++++-
 6 files changed, 187 insertions(+), 24 deletions(-)

diff --git a/docs/user-manual/modules/ROOT/pages/camel-jbang-tui-observe.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-jbang-tui-observe.adoc
index da017a6109df..d13bb945ff69 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-jbang-tui-observe.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-jbang-tui-observe.adoc
@@ -371,6 +371,13 @@ Criteria and other parameters stay on the right. 
Operations without instructions
 *Ctrl+l* clears the current field or restores an optional choice to its expert 
default;
 *Esc* leaves the editor while preserving the input and output.
 
+Click *Load params* or press *Ctrl+p* to copy the selected definition's 
parameters, including
+Instructions, into the form. The shortcut works while editing; *p* also works 
outside the editor.
+Click *Clear params* or press *Ctrl+k* to empty Instructions and all other 
parameter fields and rows.
+Both actions preserve the text/state to assess and change only the editable 
draft. The definition's state
+selector is not applied. Unset optional parameters use expert defaults; 
required values must be filled before Run.
+These controls are disabled while an evaluation is running.
+
 While editing text/state, use *Ctrl+t* to switch between literal text and JSON 
state when the operation supports both.
 Instructions remain plain text.
 Structured-only operations open in JSON mode. JSON state must be an object or 
array.
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SemanticDetails.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SemanticDetails.java
index 87f2af974d15..49ab089ffc55 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SemanticDetails.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SemanticDetails.java
@@ -103,7 +103,13 @@ final class SemanticDetails {
                 parameter(lines, indent + "  ", "[" + i + "] ", list.get(i));
             }
         } else if (value instanceof String text) {
-            // Put text under its label so wrapped prose keeps its place in 
the parameter tree.
+            // Keep scalar tokens beside their labels; prose retains its own 
hanging indentation below.
+            if (text.codePoints().noneMatch(Character::isWhitespace)) {
+                String token = text.replaceAll("[\\p{Cntrl}\\p{Cf}]", "");
+                lines.add(Line.from(Span.styled(indent + label, Theme.label()),
+                        Span.raw(token.isEmpty() ? "\"\"" : token)));
+                return;
+            }
             lines.add(Line.from(Span.styled(indent + label.stripTrailing(), 
Theme.label())));
             for (String part : text.split("\\R", -1)) {
                 lines.add(Line.from(indent + "  " + (part.isEmpty() ? "\"\"" : 
part.replaceAll("[\\p{Cntrl}\\p{Cf}]", ""))));
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SemanticPlayground.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SemanticPlayground.java
index 9de134d25299..0636eba05e9c 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SemanticPlayground.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SemanticPlayground.java
@@ -28,7 +28,6 @@ import dev.tamboui.style.Style;
 import dev.tamboui.terminal.Frame;
 import dev.tamboui.text.Line;
 import dev.tamboui.text.Span;
-import dev.tamboui.text.Text;
 import dev.tamboui.tui.event.KeyCode;
 import dev.tamboui.tui.event.KeyEvent;
 import dev.tamboui.tui.event.MouseEvent;
@@ -37,7 +36,6 @@ import dev.tamboui.widgets.block.BorderType;
 import dev.tamboui.widgets.block.Borders;
 import dev.tamboui.widgets.input.TextArea;
 import dev.tamboui.widgets.input.TextAreaState;
-import dev.tamboui.widgets.paragraph.Paragraph;
 import org.apache.camel.util.json.JsonObject;
 import org.apache.camel.util.json.Jsoner;
 
@@ -64,6 +62,7 @@ final class SemanticPlayground {
     private List<String> submittedScoreLevels = List.of();
     private JsonObject submittedInterpretation;
     private String parameterSource;
+    private JsonObject definition;
     private String draftWarning;
     private boolean expanded;
     private long submittedInstructions;
@@ -74,6 +73,8 @@ final class SemanticPlayground {
     private Rect parameterArea;
     private Rect outputArea;
     private Rect runArea;
+    private Rect loadArea;
+    private Rect clearArea;
 
     SemanticPlayground(MonitorContext ctx, String pid, String expert, 
JsonObject operation) {
         this.ctx = ctx;
@@ -103,17 +104,34 @@ final class SemanticPlayground {
                        + "current defaults, expert/model revisions and route 
context may differ. Review before Run.";
     }
 
-    boolean loadParameters(JsonObject definition) {
-        if (pending != null) {
+    void setDefinition(JsonObject definition) {
+        this.definition = definition != null && definition.get("error") == null
+                && Objects.equals(expert, definition.get("expert"))
+                && Objects.equals(operation.get("name"), 
definition.get("operation")) ? definition : null;
+    }
+
+    boolean loadParameters() {
+        if (pending != null || definition == null || !available()) {
             return false;
         }
         Map<?, ?> values = definition.get("parameters") instanceof Map<?, ?> 
map ? map : Map.of();
         instructions.load(values);
         parameters.load(values);
         parameterSource = text(definition, "name");
+        focusFirstField();
         return true;
     }
 
+    private void clearParameters() {
+        if (pending == null && available()) {
+            instructions.load(Map.of());
+            parameters.load(Map.of());
+            parameterSource = null;
+            error = null;
+            focusFirstField();
+        }
+    }
+
     boolean setInputValue(String name, String value) {
         refresh();
         if (pending != null || !available()) {
@@ -197,6 +215,10 @@ final class SemanticPlayground {
         refresh();
         if (key.hasCtrl() && key.isCharIgnoreCase('r')) {
             run();
+        } else if (key.hasCtrl() && key.isCharIgnoreCase('p')) {
+            loadParameters();
+        } else if (key.hasCtrl() && key.isCharIgnoreCase('k')) {
+            clearParameters();
         } else if (key.isKey(KeyCode.TAB)) {
             boolean back = key.hasShift();
             if (field == 1 && parameters.next(back)) {
@@ -259,6 +281,14 @@ final class SemanticPlayground {
             run();
             return true;
         }
+        if (loadArea != null && loadArea.contains(event.x(), event.y())) {
+            loadParameters();
+            return true;
+        }
+        if (clearArea != null && clearArea.contains(event.x(), event.y())) {
+            clearParameters();
+            return true;
+        }
         if (pending == null && instructions.click(event.x(), event.y())) {
             field = -1;
             return true;
@@ -356,6 +386,8 @@ final class SemanticPlayground {
         parameterArea = null;
         outputArea = null;
         runArea = null;
+        loadArea = null;
+        clearArea = null;
         Block block = 
Block.builder().borders(Borders.ALL).borderType(BorderType.ROUNDED)
                 .borderStyle(ctx.paneBorder(focused))
                 .title(" " + truncate("Try expert · " + text(operation, 
"name") + " · direct"
@@ -365,7 +397,24 @@ final class SemanticPlayground {
                 .build();
         frame.renderWidget(block, area);
         Rect inner = block.inner(area);
-        if (inner.height() < (instructions.isEmpty() ? 7 : 11) || 
inner.width() < 30) {
+        String loadLabel = " Ctrl+p Load params ";
+        String clearLabel = " Ctrl+k Clear params ";
+        String runLabel = !connected() ? " Disconnected " : pending == null ? 
" Ctrl+r Run " : " Running… ";
+        boolean hasParameters = !instructions.isEmpty() || 
!parameters.isEmpty();
+        List<String> labels = hasParameters ? List.of(loadLabel, clearLabel, 
runLabel) : List.of(runLabel);
+        List<Rect> actions = new ArrayList<>();
+        int actionX = inner.x() + 1;
+        int actionY = inner.y() + 1;
+        for (String label : labels) {
+            if (actionX > inner.x() + 1 && actionX + label.length() > 
inner.right() - 1) {
+                actionX = inner.x() + 1;
+                actionY++;
+            }
+            actions.add(new Rect(actionX, actionY, label.length(), 1));
+            actionX += label.length() + 1;
+        }
+        int actionRows = actionY - inner.y();
+        if (inner.height() < (instructions.isEmpty() ? 7 : 11) + actionRows || 
inner.width() < 30) {
             paragraph(frame, inner, List.of(Line.from("Enlarge the terminal to 
use the expert playground.")), 0);
             return;
         }
@@ -373,19 +422,23 @@ final class SemanticPlayground {
             paragraph(frame, inset(inner), List.of(Line.from("No supported 
input type is published for this operation.")), 0);
             return;
         }
-        Rect title = new Rect(inner.x() + 1, inner.y(), Math.max(0, 
inner.width() - 20), 1);
+        Rect title = new Rect(inner.x() + 1, inner.y(), Math.max(0, 
inner.width() - 2), 1);
         paragraph(frame, title, List.of(Line.from(Span.styled(expert + " › ", 
Theme.info()),
                 Span.raw(text(operation, "name") + " → " + text(operation, 
"resultType")),
                 Span.styled(" · input: " + (structured ? "JSON" : "text"), 
Theme.muted()))), 0);
-        runArea = new Rect(inner.right() - 17, inner.y(), 16, 1);
-        
frame.renderWidget(Paragraph.builder().text(Text.from(Line.from(Span.styled(
-                !connected() ? " Disconnected " : pending == null ? " Ctrl+r  
Run " : " Running… ",
-                connected() ? Theme.hintKey() : Theme.muted())))).build(), 
runArea);
+        runArea = actions.get(actions.size() - 1);
+        action(frame, runArea, runLabel, connected() && pending == null);
         boolean sideBySide = !parameters.isEmpty();
         int editorHeight = Math.max(instructions.isEmpty() ? 3 : 6,
                 Math.min(expanded ? Integer.MAX_VALUE : sideBySide || 
!instructions.isEmpty() ? 22 : 5,
-                        (inner.height() - (parameterSource == null ? 0 : 1)) * 
2 / 3));
-        int y = inner.y() + 1;
+                        (inner.height() - actionRows - (parameterSource == 
null ? 0 : 1)) * 2 / 3));
+        int y = inner.y() + 1 + actionRows;
+        if (hasParameters) {
+            loadArea = actions.get(0);
+            clearArea = actions.get(1);
+            action(frame, loadArea, loadLabel, pending == null && definition 
!= null);
+            action(frame, clearArea, clearLabel, pending == null);
+        }
         if (parameterSource != null) {
             paragraph(frame, new Rect(inner.x() + 1, y++, inner.width() - 2, 
1),
                     List.of(Line
@@ -419,6 +472,10 @@ final class SemanticPlayground {
         paragraph(frame, outputArea, lines, outputScroll);
     }
 
+    private void action(Frame frame, Rect area, String label, boolean enabled) 
{
+        paragraph(frame, area, List.of(Line.from(Span.styled(label, enabled ? 
Theme.hintKey() : Theme.muted()))), 0);
+    }
+
     private Rect renderEditor(Frame frame, Rect area, TextAreaState state, 
String title, boolean focused, String placeholder) {
         Block block = 
Block.builder().borders(Borders.ALL).borderType(BorderType.ROUNDED)
                 .borderStyle(ctx.paneBorder(focused)).title(" " + 
truncate(title, area.width() - 4) + " ").build();
@@ -471,7 +528,7 @@ final class SemanticPlayground {
         hint(spans, "Esc", "leave");
         hint(spans, "Ctrl+r", !connected() ? "disconnected" : pending == null 
? "run" : "running");
         hint(spans, "Tab/⇧Tab", "field");
-        hint(spans, "Ctrl+l", "clear");
+        hint(spans, "Ctrl+l", "clear field");
         if (field == 1) {
             parameters.renderFooter(spans);
         }
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SemanticTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SemanticTab.java
index cec2acbc64d0..efbc04c2e5e1 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SemanticTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SemanticTab.java
@@ -419,6 +419,9 @@ class SemanticTab extends AbstractTableTab {
             }
             auditDraft = null;
         }
+        if (playground != null) {
+            playground.setDefinition(selectedDefinition());
+        }
     }
 
     private void view(View selected) {
@@ -516,6 +519,12 @@ class SemanticTab extends AbstractTableTab {
             }
             return true;
         }
+        if (view == View.EXPERTS && playground != null && key.hasCtrl()
+                && (key.isCharIgnoreCase('p') || key.isCharIgnoreCase('k'))) {
+            playground.handleKeyEvent(key);
+            focus = Focus.PLAYGROUND;
+            return true;
+        }
         if (focus == Focus.PLAYGROUND && playground != null) {
             if (key.hasCtrl() && key.isCharIgnoreCase('e')) {
                 playgroundExpanded = !playgroundExpanded;
@@ -569,12 +578,8 @@ class SemanticTab extends AbstractTableTab {
             return true;
         }
         if (key.isChar('p') && view == View.EXPERTS) {
-            JsonObject definition = selectedDefinition();
-            if (playground != null && definition != null && 
definition.get("error") == null
-                    && Objects.equals(operationName, 
definition.get("operation"))
-                    && playground.loadParameters(definition)) {
+            if (playground != null && playground.loadParameters()) {
                 focus = Focus.PLAYGROUND;
-                playground.focusFirstField();
             }
             return true;
         }
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/semantic.md
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/semantic.md
index 572c51d8b0e7..da6ee25580d6 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/semantic.md
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/semantic.md
@@ -64,9 +64,13 @@ Failures/timeouts keep your draft and show an inline error; 
`Ctrl+r` retries exp
 Run is disabled when the selected application disconnects. Listing an expert 
does not prove its service is reachable;
 no inference is made just to test availability.
 
-Press `p` outside the editor to load the selected definition's parameters, 
including instructions.
+Click **Load params** or press `Ctrl+p` to load the selected definition's 
parameters, including instructions.
+The shortcut also works while editing; `p` remains available outside the 
editor.
 This replaces the parameter draft with an editable copy labeled with its 
source; it keeps your text/state.
 The definition's state selector is not applied. Without this action the form 
starts with expert defaults.
+Click **Clear params** or press `Ctrl+k` to empty Instructions and all other 
parameter fields and rows,
+keeping your text/state. Unset optional parameters use expert defaults; 
required parameters must be filled before Run.
+Loading and clearing only change the editable draft, and are disabled while an 
evaluation is running.
 
 ## Definition sample evaluation
 
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/SemanticTabTest.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/SemanticTabTest.java
index 5a547a0975eb..f6d03b2d3fe2 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/SemanticTabTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/SemanticTabTest.java
@@ -733,7 +733,7 @@ class SemanticTabTest {
             JsonObject overview = (JsonObject) Jsoner.deserialize(OVERVIEW);
             JsonObject definition = SemanticTab.objects(overview, 
"evaluations").get(0);
             definition.put("parameters", Jsoner.deserialize("""
-                    {"instructions":"Which department should handle this 
support request?",
+                    {"uncertaintyPolicy":"fail","instructions":"Which 
department should handle this support request?",
                      "criteria":{"billing":"Invoices, payments, refunds and 
duplicate charges",
                                  "technical":"Bugs, outages and system 
errors"}}
                     """));
@@ -741,14 +741,16 @@ class SemanticTabTest {
             var tab = loaded(runtime);
             for (int width : List.of(160, 80)) {
                 String rendered = TuiTestHelper.renderToString(tab, width, 48);
-                assertThat(rendered).contains("instructions:", "criteria:", "• 
billing:", "• technical:",
-                        "Invoices, payments, refunds and duplicate charges")
+                assertThat(rendered)
+                        .contains("uncertaintyPolicy: fail", "instructions:", 
"criteria:", "• billing:", "• technical:",
+                                "Invoices, payments, refunds and duplicate 
charges")
                         .doesNotContain("criteria =", "\"billing\"", 
"instructions =");
                 assertThat(rendered.lines().filter(line -> line.contains("• 
billing:")))
                         .allSatisfy(line -> 
assertThat(line).doesNotContain("technical", "Invoices"));
             }
             tab.subViewBar().views().get(2).select().run();
-            assertThat(TuiTestHelper.renderToString(tab, 180, 48)).contains("• 
billing:", "• technical:");
+            assertThat(TuiTestHelper.renderToString(tab, 180, 48))
+                    .contains("uncertaintyPolicy: fail", "• billing:", "• 
technical:");
 
             definition.put("parameters", Jsoner.deserialize("""
                     {"criteria":["Routine request with no urgency","Needs 
attention soon",
@@ -1277,6 +1279,88 @@ class SemanticTabTest {
         }
     }
 
+    @ParameterizedTest
+    @CsvSource({ "180,false", "100,true", "88,true" })
+    void parameterControlsLoadAndClearWithoutReplacingInput(int width, boolean 
mouse) throws Exception {
+        try (var runtime = new Runtime()) {
+            JsonObject overview = (JsonObject) Jsoner.deserialize(OVERVIEW);
+            JsonObject operation = 
SemanticTab.objects(SemanticTab.objects(overview, "experts").get(0), 
"operations").get(0);
+            SemanticDetails.contract(operation).put("parameters", 
Jsoner.deserialize("""
+                    
[{"name":"instructions","type":"String","required":true,"minSize":1},
+                     {"name":"criteria","type":"Map","itemType":"String"},
+                     {"name":"levels","type":"List","itemType":"String"},
+                     {"name":"threshold","type":"Number"},
+                     
{"name":"policy","type":"String","values":["fail","continue"]}]
+                    """));
+            SemanticTab.objects(overview, 
"evaluations").get(0).put("parameters", Map.of(
+                    "instructions", "Choose a department", "criteria", 
Map.of("billing", "Invoices"),
+                    "levels", List.of("Routine", "Urgent"), "threshold", 0.7, 
"policy", "fail"));
+            runtime.overview = overview.toJson();
+            var tab = loaded(runtime);
+            tab.handleKeyEvent(KeyEvent.ofKey(KeyCode.ENTER));
+            tab.setInputValue("input", "Keep this text to assess");
+            String screen = TuiTestHelper.renderToString(tab, width, 44);
+            assertThat(screen).contains("Ctrl+p Load params", "Ctrl+k Clear 
params", "Ctrl+r Run");
+            if (width == 180) {
+                assertThat(screen.lines().filter(line -> line.contains("Load 
params")))
+                        .singleElement().asString().contains("Clear params", 
"Ctrl+r Run");
+            }
+            if (mouse) {
+                clickParameterAction(tab, width, "Load params");
+            } else {
+                tab.handleKeyEvent(KeyEvent.ofChar('p', KeyModifiers.CTRL));
+            }
+            JsonObject draft = 
tab.getTableDataAsJson().getJsonObject("playground");
+            assertThat(draft).containsEntry("parameterSource", "screenPrompt")
+                    .containsEntry("input", "Keep this text to assess");
+            List<JsonObject> loadedParameters = SemanticTab.objects(draft, 
"parameters");
+            assertThat(loadedParameters).extracting(p -> p.get("value"))
+                    .contains("Choose a department", Map.of("billing", 
"Invoices"), List.of("Routine", "Urgent"), "fail");
+            assertThat(runtime.requests).hasSize(1);
+
+            runtime.release = new CountDownLatch(1);
+            tab.handleKeyEvent(KeyEvent.ofChar('r', KeyModifiers.CTRL));
+            
assertThat(tab.getTableDataAsJson().getJsonObject("playground")).containsEntry("pending",
 true);
+            if (mouse) {
+                clickParameterAction(tab, width, "Clear params");
+            } else {
+                tab.handleKeyEvent(KeyEvent.ofChar('k', KeyModifiers.CTRL));
+            }
+            
assertThat(SemanticTab.objects(tab.getTableDataAsJson().getJsonObject("playground"),
 "parameters"))
+                    .isEqualTo(loadedParameters);
+            runtime.release.countDown();
+            await().atMost(5, TimeUnit.SECONDS).untilAsserted(() -> assertThat(
+                    
tab.getTableDataAsJson().getJsonObject("playground")).containsEntry("pending", 
false));
+            if (mouse) {
+                clickParameterAction(tab, width, "Clear params");
+            } else {
+                tab.handleKeyEvent(KeyEvent.ofChar('k', KeyModifiers.CTRL));
+            }
+            draft = tab.getTableDataAsJson().getJsonObject("playground");
+            assertThat(draft).containsEntry("parameterSource", 
null).containsEntry("input", "Keep this text to assess")
+                    .containsEntry("inputChanged", true);
+            for (JsonObject parameter : SemanticTab.objects(draft, 
"parameters")) {
+                if (parameter.containsKey("entries")) {
+                    
assertThat(parameter.get("entries")).isEqualTo(List.of(Map.of("key", "", 
"value", "")));
+                } else {
+                    assertThat(parameter).containsEntry("text", "");
+                }
+            }
+            assertThat(runtime.requests).hasSize(2);
+            tab.handleKeyEvent(KeyEvent.ofChar('p', KeyModifiers.CTRL));
+            
assertThat(SemanticTab.objects(tab.getTableDataAsJson().getJsonObject("playground"),
 "parameters"))
+                    .isEqualTo(loadedParameters);
+        }
+    }
+
+    private static void clickParameterAction(SemanticTab tab, int width, 
String label) {
+        List<String> lines = TuiTestHelper.renderToString(tab, width, 
44).lines().toList();
+        int row = IntStream.range(0, lines.size()).filter(i -> 
lines.get(i).contains(label))
+                .findFirst().orElseThrow();
+        assertThat(tab.handleMouseEvent(MouseEvent.press(MouseButton.LEFT, 
lines.get(row).indexOf(label), row),
+                new Rect(0, 0, width, 44))).isTrue();
+    }
+
     @Test
     void operationDraftsAndExplicitParameterCopiesSurviveSwitching() throws 
Exception {
         try (var runtime = new Runtime()) {

Reply via email to