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 f387b90e8ab8 camel-jbang - camel get history use jline to make --it 
better (#20360)
f387b90e8ab8 is described below

commit f387b90e8ab85247be827c615b2f183abc0ebe92
Author: Claus Ibsen <[email protected]>
AuthorDate: Thu Dec 11 13:48:31 2025 +0100

    camel-jbang - camel get history use jline to make --it better (#20360)
    
    * camel-jbang - camel get history use jline to make it mode better
---
 .../core/commands/action/CamelHistoryAction.java   | 596 +++++++++------------
 .../core/commands/action/InteractiveTerminal.java  | 138 +++++
 .../core/commands/action/MessageTableHelper.java   |   6 +
 3 files changed, 405 insertions(+), 335 deletions(-)

diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelHistoryAction.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelHistoryAction.java
index 6e180b4fe317..e80bc2bea8c7 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelHistoryAction.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelHistoryAction.java
@@ -16,7 +16,6 @@
  */
 package org.apache.camel.dsl.jbang.core.commands.action;
 
-import java.io.Console;
 import java.io.LineNumberReader;
 import java.nio.file.Files;
 import java.nio.file.Path;
@@ -50,13 +49,19 @@ import org.apache.camel.util.json.JsonArray;
 import org.apache.camel.util.json.JsonObject;
 import org.apache.camel.util.json.Jsoner;
 import org.fusesource.jansi.Ansi;
-import org.fusesource.jansi.AnsiConsole;
+import org.jline.keymap.KeyMap;
+import org.jline.terminal.Size;
+import org.jline.utils.AttributedString;
+import org.jline.utils.AttributedStyle;
+import org.jline.utils.InfoCmp;
 import picocli.CommandLine;
 
 @CommandLine.Command(name = "history",
                      description = "History of latest completed exchange", 
sortOptions = false, showDefaultValues = true)
 public class CamelHistoryAction extends ActionWatchCommand {
 
+    private static final int IT_MAX_ROWS = 10;
+
     @CommandLine.Parameters(description = "Name or pid of running Camel 
integration", arity = "0..1")
     String name = "*";
 
@@ -116,8 +121,6 @@ public class CamelHistoryAction extends ActionWatchCommand {
     boolean loggingColor = true;
 
     private MessageTableHelper tableHelper;
-    private final AtomicBoolean quit = new AtomicBoolean();
-    private final AtomicBoolean waitForUser = new AtomicBoolean();
     private final CamelCatalog camelCatalog = new DefaultCamelCatalog(true);
 
     public CamelHistoryAction(CamelJBangMain main) {
@@ -157,8 +160,9 @@ public class CamelHistoryAction extends ActionWatchCommand {
                 String ago = TimeUtils.printSince(first.timestamp);
                 Row last = rows.get(rows.size() - 1);
                 String status = last.failed ? "failed" : "success";
-                String s = String.format("Message History of last completed 
(id:%s status:%s ago:%s pid:%d name:%s)",
-                        first.exchangeId, status, ago, first.pid, first.name);
+                String elapsed = TimeUtils.printDuration(last.elapsed, true);
+                String s = String.format("Message History of last completed 
(id:%s status:%s elapsed:%s ago:%s pid:%d name:%s)",
+                        first.exchangeId, status, elapsed, ago, first.pid, 
first.name);
                 printer().println(s);
 
                 printer().println(AsciiTable.getTable(AsciiTable.NO_BORDERS, 
rows, Arrays.asList(
@@ -203,40 +207,10 @@ public class CamelHistoryAction extends 
ActionWatchCommand {
         return 0;
     }
 
-    private void doRead(Console c, AtomicBoolean quit, AtomicInteger index, 
AtomicBoolean refresh) {
-        do {
-            String line = c.readLine();
-            if (line != null) {
-                line = line.trim();
-                if ("q".equalsIgnoreCase(line) || 
"quit".equalsIgnoreCase(line) || "exit".equalsIgnoreCase(line)) {
-                    quit.set(true);
-                } else if ("r".equalsIgnoreCase(line)) {
-                    refresh.set(true);
-                    index.set(0);
-                } else if ("p".equalsIgnoreCase(line)) {
-                    if (index.get() > 0) {
-                        index.decrementAndGet();
-                    }
-                } else if (line.isBlank() || "n".equalsIgnoreCase(line)) {
-                    index.incrementAndGet();
-                } else {
-                    int idx = lineIsNumber(line);
-                    if (idx >= 0) {
-                        index.set(idx);
-                    }
-                }
-                waitForUser.set(false);
-            }
-        } while (!quit.get());
-    }
-
     private Integer doInteractiveCall(List<Row> rows) throws Exception {
-        // read CLI input from user
-        final AtomicInteger index = new AtomicInteger();
-        final AtomicBoolean refresh = new AtomicBoolean();
-        final Console c = System.console();
-        Thread t2 = new Thread(() -> doRead(c, quit, index, refresh), 
"ReadCommand");
-        t2.start();
+        AtomicInteger rowIndex = new AtomicInteger();
+        AtomicInteger pageIndex = new AtomicInteger();
+        AtomicBoolean quit = new AtomicBoolean();
 
         tableHelper = new MessageTableHelper();
         tableHelper.setPretty(pretty);
@@ -244,65 +218,225 @@ public class CamelHistoryAction extends 
ActionWatchCommand {
         tableHelper.setShowExchangeProperties(showExchangeProperties);
         tableHelper.setShowExchangeVariables(showExchangeVariables);
 
-        do {
-            if (!waitForUser.get()) {
-                if (refresh.compareAndSet(true, false)) {
-                    var reloaded = loadRows();
-                    if (reloaded.size() == 1) {
-                        rows = reloaded.get(0);
+        try (InteractiveTerminal t = new InteractiveTerminal()) {
+            t.sigint(() -> quit.set(true));
+            t.addKeyBinding("quit", KeyMap.ctrl('c'), "q");
+            t.addKeyBinding("refresh", InfoCmp.Capability.key_f5);
+            t.addKeyBinding("down", InfoCmp.Capability.key_down);
+            t.addKeyBinding("up", InfoCmp.Capability.key_up);
+            t.addKeyBinding("pgdn", InfoCmp.Capability.key_npage);
+            t.addKeyBinding("pgup", InfoCmp.Capability.key_ppage);
+            t.addKeyBinding("home", InfoCmp.Capability.key_home);
+            t.addKeyBinding("end", InfoCmp.Capability.key_end);
+            t.start();
+
+            t.clearDisplay();
+            t.updateDisplay(interactiveContent(rows, rowIndex, pageIndex, 
t.size()));
+            t.flush();
+
+            // how many lines to jump per page
+            int pageSize = t.size().getRows() - IT_MAX_ROWS;
+
+            do {
+                String operation = t.readNextKeyBinding();
+                if (operation != null) {
+                    switch (operation) {
+                        case "quit" -> quit.set(true);
+                        case "up" -> {
+                            if (rowIndex.get() > 0) {
+                                rowIndex.addAndGet(-1);
+                            }
+                        }
+                        case "down" -> {
+                            if (rowIndex.get() < rows.size() - 1) {
+                                rowIndex.addAndGet(1);
+                            }
+                        }
+                        case "pgup" -> {
+                            if (rowIndex.get() > 0) {
+                                rowIndex.addAndGet(-IT_MAX_ROWS);
+                            }
+                            if (rowIndex.get() < 0) {
+                                rowIndex.set(0);
+                            }
+                        }
+                        case "pgdn" -> {
+                            if (rowIndex.get() < rows.size() - 1) {
+                                rowIndex.addAndGet(IT_MAX_ROWS);
+                            }
+                            if (rowIndex.get() > rows.size() - 1) {
+                                rowIndex.set(rows.size() - 1);
+                            }
+                        }
+                        case "end" -> pageIndex.addAndGet(pageSize);
+                        case "home" -> {
+                            pageIndex.addAndGet(-pageSize);
+                            if (pageIndex.get() < 0) {
+                                pageIndex.set(0);
+                            }
+                        }
+                        case "refresh" -> {
+                            var reloaded = loadRows();
+                            if (reloaded.size() == 1) {
+                                rows = reloaded.get(0);
+                            }
+                            rowIndex.set(0);
+                        }
                     }
+                    t.clearDisplay();
+                    t.updateDisplay(interactiveContent(rows, rowIndex, 
pageIndex, t.size()));
+                    t.flush();
                 }
+            } while (!quit.get());
+        }
 
-                clearScreen();
-                Row first = rows.get(0);
-                String ago = TimeUtils.printSince(first.timestamp);
-                Row last = rows.get(rows.size() - 1);
-                String status = last.failed ? "failed" : "success";
-                String s = String.format("Message History of last completed 
(id:%s status:%s ago:%s pid:%d name:%s)",
-                        first.exchangeId, status, ago, first.pid, first.name);
-                printer().println(s);
-                printer().println();
-
-                int i = index.get();
-                if (i > rows.size()) {
-                    i = 0; // start over again
-                    index.set(0);
-                }
-                if (i < rows.size()) {
-                    Row r = rows.get(i);
-                    printSourceAndHistory(r);
-                    printCurrentRow(r);
-                }
-                printer().println();
-
-                int total = rows.size() - 1;
-                int pos = i;
+        return 0;
+    }
 
-                if (pos == total) {
-                    index.set(-1); // start over again
-                }
+    private List<AttributedString> interactiveContent(
+            List<Row> rows, AtomicInteger rowIndex, AtomicInteger pageIndex, 
Size size) {
+        List<AttributedString> answer = new ArrayList<>();
 
-                String msg
-                        = "    Message History (" + pos + "/" + total
-                          + "). Press ENTER to continue (n = next (default), p 
= previous, number = jump to index, r = refresh, q = quit).";
-                if (loggingColor) {
-                    
AnsiConsole.out().println(Ansi.ansi().a(Ansi.Attribute.INTENSITY_BOLD).a(msg).reset());
-                } else {
-                    printer().println(msg);
-                }
-                waitForUser.set(true);
+        // top message
+        Row first = rows.get(0);
+        String ago = TimeUtils.printSince(first.timestamp);
+        Row last = rows.get(rows.size() - 1);
+        String status = last.failed ? "failed" : "success";
+        if (loggingColor) {
+            status = Ansi.ansi().fg(last.failed ? Ansi.Color.RED : 
Ansi.Color.GREEN).a(status).reset().toString();
+        }
+        String elapsed = TimeUtils.printDuration(last.elapsed, true);
+        String s = String.format("    Message History of last completed (id:%s 
status:%s elapsed:%s ago:%s pid:%d name:%s)",
+                first.exchangeId, status, elapsed, ago, first.pid, first.name);
+        answer.add(new AttributedString(s));
+        answer.add(new AttributedString(""));
+
+        // build full table with all data so the table sizing are always the 
same when scrolling
+        String table = AsciiTable.getTable(AsciiTable.NO_BORDERS, rows, 
Arrays.asList(
+                new Column().header("").dataAlign(HorizontalAlign.LEFT)
+                        .minWidth(6).maxWidth(6)
+                        .with(this::getDirection),
+                new Column().header("ID").dataAlign(HorizontalAlign.LEFT)
+                        .minWidth(10).maxWidth(20, 
OverflowBehaviour.ELLIPSIS_RIGHT)
+                        .with(this::getId),
+                new 
Column().header("PROCESSOR").dataAlign(HorizontalAlign.LEFT)
+                        .minWidth(40).maxWidth(55, 
OverflowBehaviour.ELLIPSIS_RIGHT)
+                        .with(this::getProcessor),
+                new Column().header("ELAPSED").dataAlign(HorizontalAlign.RIGHT)
+                        .maxWidth(10, OverflowBehaviour.ELLIPSIS_RIGHT)
+                        .with(this::getElapsed),
+                new 
Column().header("EXCHANGE").headerAlign(HorizontalAlign.RIGHT).dataAlign(HorizontalAlign.RIGHT)
+                        .maxWidth(12, OverflowBehaviour.ELLIPSIS_RIGHT)
+                        .with(this::getExchangeId),
+                new Column().header("").dataAlign(HorizontalAlign.LEFT)
+                        .maxWidth(Integer.MAX_VALUE) // capture all in single 
line
+                        .with(this::getMessage)));
+
+        // styles for highlighting the selected row
+        var faint = AttributedStyle.DEFAULT.faint();
+        var bold = AttributedStyle.DEFAULT.bold();
+        var normal = AttributedStyle.DEFAULT;
+        var select = AttributedStyle.DEFAULT
+                .background(loggingColor ? AttributedStyle.BLUE : 
AttributedStyle.BRIGHT)
+                .bold();
+
+        // calculate the max width from all the message traces
+        int maxLength = 0;
+        String[] lines = table.split(System.lineSeparator());
+        for (int i = 1; i < lines.length; i++) {
+            String line = lines[i];
+            maxLength = Math.max(maxLength, line.length());
+        }
+
+        // calculate top table max size
+        int maxRows = Math.min(IT_MAX_ROWS, rows.size());
+
+        // table header
+        answer.add(new AttributedString(lines[0], bold));
+
+        // slice top table with maximum IT_MAX_ROWS number of rows
+        int rowPos = rowIndex.get();
+        List<AttributedString> pending = new ArrayList<>();
+        for (int i = 1; i < lines.length; i++) {
+            int j = i - 1;
+            String line = lines[i];
+            AttributedStyle style;
+            if (j < rowPos) {
+                style = normal;
+            } else if (j == rowPos) {
+                style = select;
+            } else {
+                style = normal;
             }
-        } while (!quit.get() || waitForUser.get());
+            pending.add(new AttributedString(line, style));
+        }
+        if (rows.size() <= IT_MAX_ROWS) {
+            // show all rows as there are no need to scroll
+            answer.addAll(pending);
+        } else if (rowPos < IT_MAX_ROWS) {
+            // show all rows as we are in the top before scrolling
+            answer.addAll(pending.subList(0, IT_MAX_ROWS));
+        } else {
+            // scroll down to add 1 new row
+            answer.addAll(pending.subList(rowPos - (IT_MAX_ROWS - 1), rowPos + 
1));
+        }
+
+        // detailed data for current selected row
+        // need to pre-calculate how much lines of this data can be visibly 
shown
+        // in the bottom panel
+        int pagePos = pageIndex.get();
+        Row r = rows.get(rowIndex.get());
+        table = getDataAsTable(r);
+        lines = table.split(System.lineSeparator());
+        // how many lines for bottom panel
+        int maxBottom = size.getRows() - Math.min(maxRows, rows.size());
+        if (lines.length < maxBottom) {
+            pagePos = 0;
+            pageIndex.set(pagePos);
+        }
+        if (pagePos > lines.length - maxBottom) {
+            pagePos = Math.max(0, lines.length - maxBottom);
+            pageIndex.set(pagePos);
+        }
+
+        // are there more rows?
+        boolean moreRows = rows.size() > IT_MAX_ROWS && rowPos + 1 < 
rows.size();
+
+        // calculate page index/total
+        int p1 = (int) Math.ceil((double) pagePos / maxBottom) + 1;
+        int p2 = (int) Math.ceil((double) lines.length / maxBottom);
+
+        // status panel in the middle
+        String help = String.format("   row:%d/%d (\u2191\u2193pgup/pgdn)   
page:%d/%d (home/end)    f5=refresh    q=quit",
+                rowPos + 1, rows.size(), p1, p2);
+        String pad = StringHelper.padString(maxLength - help.length(), 1);
+        if (moreRows) {
+            answer.add(new AttributedString("       ...", faint));
+        } else {
+            answer.add(new AttributedString(""));
+        }
+        answer.add(new AttributedString(help + pad, AttributedStyle.INVERSE));
+        answer.add(new AttributedString(""));
 
-        return 0;
-    }
+        // bottom header
+        String header = rowDetailedHeader(r);
+        answer.add(AttributedString.fromAnsi(header));
+        answer.add(new AttributedString(""));
 
-    private static int lineIsNumber(String line) {
-        try {
-            return Integer.parseInt(line);
-        } catch (Exception e) {
-            return -1;
+        // slice bottom panel to show selected page
+        pending.clear();
+        for (String line : lines) {
+            pending.add(AttributedString.fromAnsi(line));
         }
+        if (pagePos > 0 && pagePos < pending.size()) {
+            pending = pending.subList(pagePos, pending.size());
+        }
+        if (pending.size() > maxBottom) {
+            pending = pending.subList(0, maxBottom);
+        }
+        answer.addAll(pending);
+
+        return answer;
     }
 
     private String getDataAsTable(Row r) {
@@ -310,148 +444,9 @@ public class CamelHistoryAction extends 
ActionWatchCommand {
                 r.message, r.exception);
     }
 
-    private void printSourceAndHistory(Row row) {
-        List<Panel> panel = new ArrayList<>();
-        if (!row.code.isEmpty()) {
-            String loc = StringHelper.beforeLast(row.location, ":", 
row.location);
-            if (loc != null && loc.length() < 72) {
-                loc = loc + " ".repeat(72 - loc.length());
-            } else {
-                loc = "";
-            }
-            panel.add(Panel.withCode("Source: " + loc).andHistory("History"));
-            panel.add(Panel.withCode("-".repeat(80))
-                    .andHistory("-".repeat(90)));
-
-            for (int i = 0; i < row.code.size(); i++) {
-                Code code = row.code.get(i);
-                String c = Jsoner.unescape(code.code);
-                String arrow = "    ";
-                if (code.match) {
-                    if (row.first) {
-                        arrow = "*-->";
-                    } else if (row.last) {
-                        arrow = "<--*";
-                    } else {
-                        arrow = "--->";
-                    }
-                }
-                String msg = String.format("%4d: %s %s", code.line, arrow, c);
-                if (msg.length() > 80) {
-                    msg = msg.substring(0, 80);
-                }
-                int length = msg.length();
-                if (loggingColor && code.match) {
-                    Ansi.Color col = Ansi.Color.BLUE;
-                    Ansi.Attribute it = Ansi.Attribute.INTENSITY_BOLD;
-                    if (row.failed && row.last) {
-                        col = Ansi.Color.RED;
-                    } else if (row.last) {
-                        col = Ansi.Color.GREEN;
-                    }
-                    // need to fill out entire line, so fill in spaces
-                    if (length < 80) {
-                        String extra = " ".repeat(80 - length);
-                        msg = msg + extra;
-                        length = 80;
-                    }
-                    msg = Ansi.ansi().bg(col).a(it).a(msg).reset().toString();
-                } else {
-                    // need to fill out entire line, so fill in spaces
-                    if (length < 80) {
-                        String extra = " ".repeat(80 - length);
-                        msg = msg + extra;
-                        length = 80;
-                    }
-                }
-                panel.add(Panel.withCode(msg, length));
-            }
-            for (int i = row.code.size(); i < 11; i++) {
-                // empty lines so source code has same height
-                panel.add(Panel.withCode(" ".repeat(80)));
-            }
-        }
-
-        if (!row.history.isEmpty()) {
-            if (row.history.size() > (panel.size() - 4)) {
-                // cut to only what we can display
-                int pos = row.history.size() - (panel.size() - 4);
-                if (row.history.size() > pos) {
-                    row.history = row.history.subList(pos, row.history.size());
-                }
-            }
-            for (int i = 2; panel.size() > 2 && i < 11; i++) {
-                Panel p = panel.get(i);
-                if (row.history.size() > (i - 2)) {
-                    History h = row.history.get(i - 2);
-                    boolean top = h == row.history.get(row.history.size() - 1);
-
-                    String ids;
-                    if (source) {
-                        ids = locationAndLine(h.location, h.line);
-                    } else {
-                        ids = h.routeId + "/" + h.nodeId;
-                    }
-                    if (ids.length() > 30) {
-                        ids = ids.substring(ids.length() - 30);
-                    }
-
-                    ids = String.format("%-30.30s", ids);
-                    if (loggingColor) {
-                        ids = Ansi.ansi().fgCyan().a(ids).reset().toString();
-                    }
-                    long e = i == 2 ? 0 : h.elapsed; // the pseudo from should 
have 0 as elapsed
-                    String elapsed = "(" + e + "ms)";
-
-                    String c = "";
-                    if (source && h.code != null) {
-                        c = Jsoner.unescape(h.code);
-                        c = c.trim();
-                    } else if (h.nodeLabel != null) {
-                        c = Jsoner.escape(h.nodeLabel);
-                        c = c.trim();
-                    }
-                    // pad with level
-                    String pad = StringHelper.padString(h.level);
-                    c = pad + c;
-
-                    String fids = String.format("%-30.30s", ids);
-                    String msg;
-                    if (top && !row.last) {
-                        msg = String.format("%2d %10.10s %s %4d:   %s", 
h.index, "--->", fids, h.line, c);
-                    } else {
-                        msg = String.format("%2d %10.10s %s %4d:   %s", 
h.index, elapsed, fids, h.line, c);
-                    }
-                    int len = msg.length();
-                    if (loggingColor) {
-                        fids = String.format("%-30.30s", ids);
-                        fids = Ansi.ansi().fgCyan().a(fids).reset().toString();
-                        if (top && !row.last) {
-                            msg = String.format("%2d %10.10s %s %4d:   %s", 
h.index, "--->", fids, h.line, c);
-                        } else {
-                            msg = String.format("%2d %10.10s %s %4d:   %s", 
h.index, elapsed, fids, h.line, c);
-                        }
-                    }
-
-                    p.history = msg;
-                    p.historyLength = len;
-                }
-            }
-        }
-        // the ascii-table does not work well with color cells 
(https://github.com/freva/ascii-table/issues/26)
-        for (Panel p : panel) {
-            String c = p.code;
-            String h = p.history;
-            int len = p.historyLength;
-            if (len > 90) {
-                h = h.substring(0, 90);
-            }
-            String line = c + "    " + h;
-            printer().println(line);
-        }
-    }
+    private String rowDetailedHeader(Row row) {
+        StringBuilder sb = new StringBuilder();
 
-    private void printCurrentRow(Row row) {
         if (timestamp) {
             String ts;
             if (ago) {
@@ -461,20 +456,20 @@ public class CamelHistoryAction extends 
ActionWatchCommand {
                 ts = sdf.format(new Date(row.timestamp));
             }
             if (loggingColor) {
-                
AnsiConsole.out().print(Ansi.ansi().fgBrightDefault().a(Ansi.Attribute.INTENSITY_FAINT).a(ts).reset());
+                
sb.append(Ansi.ansi().fgBrightDefault().a(Ansi.Attribute.INTENSITY_FAINT).a(ts).reset());
             } else {
-                printer().print(ts);
+                sb.append(ts);
             }
-            printer().print("  ");
+            sb.append("  ");
         }
         // pid
         String p = String.format("%5.5s", row.pid);
         if (loggingColor) {
-            AnsiConsole.out().print(Ansi.ansi().fgMagenta().a(p).reset());
-            
AnsiConsole.out().print(Ansi.ansi().fgBrightDefault().a(Ansi.Attribute.INTENSITY_FAINT).a("
 --- ").reset());
+            sb.append(Ansi.ansi().fgMagenta().a(p).reset());
+            
sb.append(Ansi.ansi().fgBrightDefault().a(Ansi.Attribute.INTENSITY_FAINT).a(" 
--- ").reset());
         } else {
-            printer().print(p);
-            printer().print(" --- ");
+            sb.append(p);
+            sb.append(" --- ");
         }
         // thread name
         String tn = row.threadName;
@@ -483,15 +478,15 @@ public class CamelHistoryAction extends 
ActionWatchCommand {
         }
         tn = String.format("[%25.25s]", tn);
         if (loggingColor) {
-            
AnsiConsole.out().print(Ansi.ansi().fgBrightDefault().a(Ansi.Attribute.INTENSITY_FAINT).a(tn).reset());
+            
sb.append(Ansi.ansi().fgBrightDefault().a(Ansi.Attribute.INTENSITY_FAINT).a(tn).reset());
         } else {
-            printer().print(tn);
+            sb.append(tn);
         }
-        printer().print(" ");
+        sb.append(" ");
         // node ids or source location
         String ids;
         if (source) {
-            ids = locationAndLine(row.location, -1);
+            ids = location(row.location);
         } else {
             ids = row.routeId + "/" + getId(row);
         }
@@ -500,37 +495,35 @@ public class CamelHistoryAction extends 
ActionWatchCommand {
         }
         ids = String.format("%40.40s", ids);
         if (loggingColor) {
-            AnsiConsole.out().print(Ansi.ansi().fgCyan().a(ids).reset());
+            sb.append(Ansi.ansi().fgCyan().a(ids).reset());
         } else {
-            printer().print(ids);
+            sb.append(ids);
         }
-        printer().print(" : ");
+        sb.append(" : ");
         // uuid
         String u = String.format("%5.5s", row.uid);
         if (loggingColor) {
-            AnsiConsole.out().print(Ansi.ansi().fgMagenta().a(u).reset());
+            sb.append(Ansi.ansi().fgMagenta().a(u).reset());
         } else {
-            printer().print(u);
+            sb.append(u);
         }
-        printer().print(" - ");
+        sb.append(" - ");
         // status
-        printer().print(getStatus(row));
+        sb.append(getStatus(row));
         // elapsed
         String e = getElapsed(row);
         if (e != null) {
             if (loggingColor) {
-                AnsiConsole.out().print(Ansi.ansi().fgBrightDefault().a(" (" + 
e + ")").reset());
+                sb.append(Ansi.ansi().fgBrightDefault().a(" (" + e + 
")").reset());
             } else {
-                printer().print("(" + e + ")");
+                sb.append("(").append(e).append(")");
             }
         }
-        printer().println();
-        printer().println(getDataAsTable(row));
-        printer().println();
+        return sb.toString();
     }
 
     private String getElapsed(Row r) {
-        if (!r.first) {
+        if (r.exchangeId != null) {
             return TimeUtils.printDuration(r.elapsed, true);
         }
         return null;
@@ -577,10 +570,9 @@ public class CamelHistoryAction extends ActionWatchCommand 
{
         }
     }
 
-    private static String locationAndLine(String loc, int line) {
+    private static String location(String loc) {
         // shorten path as there is no much space (there are no scheme as add 
fake)
-        loc = LoggerHelper.sourceNameOnly("file:" + FileUtil.stripPath(loc));
-        return line == -1 ? loc : loc + ":" + line;
+        return LoggerHelper.sourceNameOnly("file:" + FileUtil.stripPath(loc));
     }
 
     private boolean filterDepth(Row r) {
@@ -600,8 +592,8 @@ public class CamelHistoryAction extends ActionWatchCommand {
         }
         JsonArray arr = r.message.getCollection("exchangeProperties");
         if (arr != null) {
-            for (int i = 0; i < arr.size(); i++) {
-                JsonObject jo = (JsonObject) arr.get(i);
+            for (Object o : arr) {
+                JsonObject jo = (JsonObject) o;
                 if ("CamelSplitIndex".equals(jo.getString("key"))) {
                     long idx = jo.getLongOrDefault("value", Long.MAX_VALUE);
                     return idx < limitSplit;
@@ -641,11 +633,14 @@ public class CamelHistoryAction extends 
ActionWatchCommand {
         } else if (r.last) {
             return "*<--";
         } else {
-            return "";
+            return null;
         }
     }
 
     private String getExchangeId(Row r) {
+        if (r.exchangeId == null) {
+            return null;
+        }
         String id = r.exchangeId.substring(r.exchangeId.length() - 4);
         String cid = r.correlationExchangeId;
         if (cid != null) {
@@ -858,32 +853,6 @@ public class CamelHistoryAction extends ActionWatchCommand 
{
                 }
             }
         }
-        for (int i = 1; i < rows.size() - 1; i++) {
-            Row cur = rows.get(i);
-
-            cur.history = new ArrayList<>();
-            for (int j = 0; j < i; j++) {
-                Row r = rows.get(j);
-                History h = new History();
-                h.index = j;
-                h.nodeId = r.nodeId;
-                h.routeId = r.routeId;
-                h.nodeShortName = r.nodeShortName;
-                h.nodeLabel = r.nodeLabel;
-                h.location = r.location;
-                h.elapsed = r.elapsed;
-                h.level = r.nodeLevel;
-                if (r.code != null) {
-                    for (var c : r.code) {
-                        if (c.match) {
-                            h.code = c.code;
-                            h.line = c.line;
-                        }
-                    }
-                }
-                cur.history.add(h);
-            }
-        }
     }
 
     private Map<String, String> extractComponentModel(String uri, Row r) {
@@ -897,8 +866,8 @@ public class CamelHistoryAction extends ActionWatchCommand {
                     if (eh.isImportant()) {
                         JsonArray arr = r.message.getCollection("headers");
                         if (arr != null) {
-                            for (int i = 0; i < arr.size(); i++) {
-                                JsonObject jo = (JsonObject) arr.get(i);
+                            for (Object o : arr) {
+                                JsonObject jo = (JsonObject) o;
                                 String key = jo.getString("key");
                                 if (key.equals(eh.getName())) {
                                     answer.put(key, jo.getString("value"));
@@ -922,8 +891,8 @@ public class CamelHistoryAction extends ActionWatchCommand {
                 if (ep.isImportant()) {
                     JsonArray arr = 
r.message.getCollection("exchangeProperties");
                     if (arr != null) {
-                        for (int i = 0; i < arr.size(); i++) {
-                            JsonObject jo = (JsonObject) arr.get(i);
+                        for (Object o : arr) {
+                            JsonObject jo = (JsonObject) o;
                             String key = jo.getString("key");
                             if (key.equals(ep.getName())) {
                                 answer.put(key, jo.getString("value"));
@@ -932,8 +901,8 @@ public class CamelHistoryAction extends ActionWatchCommand {
                     }
                     arr = r.message.getCollection("headers");
                     if (arr != null) {
-                        for (int i = 0; i < arr.size(); i++) {
-                            JsonObject jo = (JsonObject) arr.get(i);
+                        for (Object o : arr) {
+                            JsonObject jo = (JsonObject) o;
                             String key = jo.getString("key");
                             if (key.equals(ep.getName())) {
                                 answer.put(key, jo.getString("value"));
@@ -976,20 +945,6 @@ public class CamelHistoryAction extends ActionWatchCommand 
{
         JsonObject exception;
         String summary;
         List<Code> code = new ArrayList<>();
-        List<History> history = new ArrayList<>();
-    }
-
-    private static class History {
-        int index;
-        String routeId;
-        String nodeId;
-        String nodeShortName;
-        String nodeLabel;
-        int level;
-        long elapsed;
-        String location;
-        int line;
-        String code;
     }
 
     private static class Code {
@@ -998,33 +953,4 @@ public class CamelHistoryAction extends ActionWatchCommand 
{
         boolean match;
     }
 
-    private static class Panel {
-        String code = "";
-        String history = "";
-        int codeLength;
-        int historyLength;
-
-        static Panel withCode(String code) {
-            return withCode(code, code.length());
-        }
-
-        static Panel withCode(String code, int length) {
-            Panel p = new Panel();
-            p.code = code;
-            p.codeLength = length;
-            return p;
-        }
-
-        Panel andHistory(String history) {
-            return andHistory(history, history.length());
-        }
-
-        Panel andHistory(String history, int length) {
-            this.history = history;
-            this.historyLength = length;
-            return this;
-        }
-
-    }
-
 }
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/InteractiveTerminal.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/InteractiveTerminal.java
new file mode 100644
index 000000000000..e5a9aea83605
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/InteractiveTerminal.java
@@ -0,0 +1,138 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.dsl.jbang.core.commands.action;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.EnumSet;
+import java.util.List;
+
+import org.apache.camel.util.IOHelper;
+import org.jline.keymap.BindingReader;
+import org.jline.keymap.KeyMap;
+import org.jline.terminal.Attributes;
+import org.jline.terminal.Size;
+import org.jline.terminal.Terminal;
+import org.jline.terminal.TerminalBuilder;
+import org.jline.utils.AttributedString;
+import org.jline.utils.Display;
+import org.jline.utils.InfoCmp;
+import org.jline.utils.Status;
+
+/**
+ * Interactive terminal that runs in full screen and allows a more immersive 
user experience with Camel JBang CLI.
+ */
+public class InteractiveTerminal implements Closeable {
+
+    private final KeyMap<String> keys = new KeyMap<>();
+
+    private Terminal terminal;
+    private Display display;
+    private Size size;
+    private BindingReader bindingReader;
+    private Runnable sigint;
+    private Status status;
+    private Runnable statusTask;
+
+    public InteractiveTerminal() throws Exception {
+        terminal = TerminalBuilder.builder().build();
+    }
+
+    public void start() {
+        Attributes attributes = terminal.getAttributes();
+        int vsusp = attributes.getControlChar(Attributes.ControlChar.VSUSP);
+        if (vsusp > 0) {
+            attributes.setControlChar(Attributes.ControlChar.VSUSP, vsusp);
+        }
+        Attributes newAttr = new Attributes(attributes);
+        newAttr.setLocalFlags(EnumSet.of(Attributes.LocalFlag.ICANON, 
Attributes.LocalFlag.ECHO, Attributes.LocalFlag.IEXTEN,
+                Attributes.LocalFlag.ISIG), false);
+        newAttr.setInputFlags(EnumSet.of(Attributes.InputFlag.IXON, 
Attributes.InputFlag.ICRNL, Attributes.InputFlag.INLCR),
+                false);
+        newAttr.setControlChar(Attributes.ControlChar.VMIN, 1);
+        newAttr.setControlChar(Attributes.ControlChar.VTIME, 0);
+        newAttr.setControlChar(Attributes.ControlChar.VINTR, 0);
+        terminal.setAttributes(newAttr);
+        terminal.puts(InfoCmp.Capability.enter_ca_mode);
+        terminal.puts(InfoCmp.Capability.keypad_xmit);
+
+        // Create a display for managing the screen
+        display = new Display(terminal, true);
+        // Get initial terminal size
+        size = terminal.getSize();
+        // resize display
+        display.resize(size.getRows(), size.getColumns());
+
+        status = Status.getStatus(terminal);
+        bindingReader = new BindingReader(terminal.reader());
+
+        if (sigint != null) {
+            terminal.handle(Terminal.Signal.INT, signal -> {
+                sigint.run();
+            });
+        }
+    }
+
+    public void sigint(Runnable task) {
+        this.sigint = task;
+    }
+
+    public void status(Runnable task) {
+        this.statusTask = task;
+    }
+
+    public void addKeyBinding(String operation, String... key) {
+        keys.bind(operation, key);
+    }
+
+    public void addKeyBinding(String operation, InfoCmp.Capability keySeq) {
+        keys.bind(operation, KeyMap.key(terminal, keySeq));
+    }
+
+    public String readNextKeyBinding() {
+        return bindingReader.readBinding(keys);
+    }
+
+    public void clearDisplay() {
+        display.clear();
+    }
+
+    public void updateDisplay(List<AttributedString> newLines) {
+        display.update(newLines, size.cursorPos(0, 0));
+    }
+
+    public void updateStatus(List<AttributedString> newLines) {
+        status.update(newLines);
+    }
+
+    public Size size() {
+        return size;
+    }
+
+    public void flush() {
+        terminal.flush();
+    }
+
+    public void stop() throws Exception {
+        close();
+    }
+
+    @Override
+    public void close() throws IOException {
+        IOHelper.close(terminal);
+    }
+}
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/MessageTableHelper.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/MessageTableHelper.java
index cfbb4bc55f09..a36f81e7b917 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/MessageTableHelper.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/MessageTableHelper.java
@@ -415,6 +415,9 @@ public class MessageTableHelper {
                 s = "WrappedInputStream";
             } else if (type.startsWith("org.apache.camel.converter.stream.")) {
                 s = type.substring(34);
+            } else if (type
+                    
.equals("org.apache.camel.processor.aggregate.AbstractListAggregationStrategy.GroupedExchangeList"))
 {
+                s = "GroupedExchangeList";
             } else if (type.length() > 34) {
                 // type must not be too long
                 int pos = type.lastIndexOf('.');
@@ -446,6 +449,9 @@ public class MessageTableHelper {
                 s = "WrappedInputStream";
             } else if (type.startsWith("org.apache.camel.converter.stream.")) {
                 s = type.substring(34);
+            } else if (type
+                    
.equals("org.apache.camel.processor.aggregate.AbstractListAggregationStrategy.GroupedExchangeList"))
 {
+                s = "GroupedExchangeList";
             } else {
                 s = type;
             }


Reply via email to