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

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

commit 7bc54a75bc3a874b2d02bfd0bec9859603c9c315
Author: Claus Ibsen <[email protected]>
AuthorDate: Tue Dec 9 20:36:25 2025 +0100

    camel-jbang - camel get history use jline to make it mode better
---
 .../core/commands/action/CamelHistoryAction.java   | 418 +++++++--------------
 .../core/commands/action/InteractiveTerminal.java  | 109 ++++++
 2 files changed, 253 insertions(+), 274 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..9d366d565daf 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;
@@ -30,6 +29,7 @@ import java.util.Map;
 import java.util.StringJoiner;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Function;
 
 import com.github.freva.asciitable.AsciiTable;
 import com.github.freva.asciitable.Column;
@@ -51,73 +51,74 @@ 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.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)
+        description = "History of latest completed exchange", sortOptions = 
false, showDefaultValues = true)
 public class CamelHistoryAction extends ActionWatchCommand {
 
     @CommandLine.Parameters(description = "Name or pid of running Camel 
integration", arity = "0..1")
     String name = "*";
 
-    @CommandLine.Option(names = { "--it" },
-                        description = "Interactive mode for enhanced history 
information")
+    @CommandLine.Option(names = {"--it"},
+            description = "Interactive mode for enhanced history information")
     boolean it;
 
-    @CommandLine.Option(names = { "--source" },
-                        description = "Prefer to display source filename/code 
instead of IDs")
+    @CommandLine.Option(names = {"--source"},
+            description = "Prefer to display source filename/code instead of 
IDs")
     boolean source;
 
-    @CommandLine.Option(names = { "--mask" },
-                        description = "Whether to mask endpoint URIs to avoid 
printing sensitive information such as password or access keys")
+    @CommandLine.Option(names = {"--mask"},
+            description = "Whether to mask endpoint URIs to avoid printing 
sensitive information such as password or access keys")
     boolean mask;
 
-    @CommandLine.Option(names = { "--depth" }, defaultValue = "9",
-                        description = "Depth of tracing. 0=Created+Completed. 
1=All events on 1st route, 2=All events on 1st+2nd depth, and so on. 9 = all 
events on every depth.")
+    @CommandLine.Option(names = {"--depth"}, defaultValue = "9",
+            description = "Depth of tracing. 0=Created+Completed. 1=All events 
on 1st route, 2=All events on 1st+2nd depth, and so on. 9 = all events on every 
depth.")
     int depth;
 
-    @CommandLine.Option(names = { "--limit-split" },
-                        description = "Limit Split to a maximum number of 
entries to be displayed")
+    @CommandLine.Option(names = {"--limit-split"},
+            description = "Limit Split to a maximum number of entries to be 
displayed")
     int limitSplit;
 
-    @CommandLine.Option(names = { "--timestamp" }, defaultValue = "true",
-                        description = "Print timestamp.")
+    @CommandLine.Option(names = {"--timestamp"}, defaultValue = "true",
+            description = "Print timestamp.")
     boolean timestamp = true;
 
-    @CommandLine.Option(names = { "--ago" },
-                        description = "Use ago instead of yyyy-MM-dd HH:mm:ss 
in timestamp.")
+    @CommandLine.Option(names = {"--ago"},
+            description = "Use ago instead of yyyy-MM-dd HH:mm:ss in 
timestamp.")
     boolean ago;
 
-    @CommandLine.Option(names = { "--show-exchange-properties" }, defaultValue 
= "false",
-                        description = "Show exchange properties in debug 
messages")
+    @CommandLine.Option(names = {"--show-exchange-properties"}, defaultValue = 
"false",
+            description = "Show exchange properties in debug messages")
     boolean showExchangeProperties;
 
-    @CommandLine.Option(names = { "--show-exchange-variables" }, defaultValue 
= "true",
-                        description = "Show exchange variables in debug 
messages")
+    @CommandLine.Option(names = {"--show-exchange-variables"}, defaultValue = 
"true",
+            description = "Show exchange variables in debug messages")
     boolean showExchangeVariables = true;
 
-    @CommandLine.Option(names = { "--show-headers" }, defaultValue = "true",
-                        description = "Show message headers in debug messages")
+    @CommandLine.Option(names = {"--show-headers"}, defaultValue = "true",
+            description = "Show message headers in debug messages")
     boolean showHeaders = true;
 
-    @CommandLine.Option(names = { "--show-body" }, defaultValue = "true",
-                        description = "Show message body in debug messages")
+    @CommandLine.Option(names = {"--show-body"}, defaultValue = "true",
+            description = "Show message body in debug messages")
     boolean showBody = true;
 
-    @CommandLine.Option(names = { "--show-exception" }, defaultValue = "true",
-                        description = "Show exception and stacktrace for 
failed messages")
+    @CommandLine.Option(names = {"--show-exception"}, defaultValue = "true",
+            description = "Show exception and stacktrace for failed messages")
     boolean showException = true;
 
-    @CommandLine.Option(names = { "--pretty" },
-                        description = "Pretty print message body when using 
JSon or XML format")
+    @CommandLine.Option(names = {"--pretty"},
+            description = "Pretty print message body when using JSon or XML 
format")
     boolean pretty;
 
-    @CommandLine.Option(names = { "--logging-color" }, defaultValue = "true", 
description = "Use colored logging")
+    @CommandLine.Option(names = {"--logging-color"}, defaultValue = "true", 
description = "Use colored logging")
     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) {
@@ -203,40 +204,9 @@ 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 index = new AtomicInteger();
+        AtomicBoolean quit = new AtomicBoolean();
 
         tableHelper = new MessageTableHelper();
         tableHelper.setPretty(pretty);
@@ -244,65 +214,106 @@ public class CamelHistoryAction extends 
ActionWatchCommand {
         tableHelper.setShowExchangeProperties(showExchangeProperties);
         tableHelper.setShowExchangeVariables(showExchangeVariables);
 
+        InteractiveTerminal t = new InteractiveTerminal();
+        t.start();
+        t.addKeyBinding("quit", "q");
+        t.addKeyBinding("up", InfoCmp.Capability.key_up);
+        t.addKeyBinding("down", InfoCmp.Capability.key_down);
+        t.addKeyBinding("refresh", InfoCmp.Capability.key_f5);
+
+        t.clearDisplay();
+        t.updateDisplay(interactiveContent(rows, index));
+        t.flush();
+
         do {
-            if (!waitForUser.get()) {
-                if (refresh.compareAndSet(true, false)) {
+            String operation = t.readNextKeyBinding();
+            if (operation != null) {
+                if ("quit".equals(operation)) {
+                    quit.set(true);
+                } else if ("up".equals(operation)) {
+                    if (index.get() > 0) {
+                        index.addAndGet(-1);
+                    }
+                } else if ("down".equals(operation)) {
+                    if (index.get() < rows.size() - 1) {
+                        index.addAndGet(1);
+                    }
+                } else if ("refresh".equals(operation)) {
                     var reloaded = loadRows();
                     if (reloaded.size() == 1) {
                         rows = reloaded.get(0);
                     }
-                }
-
-                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;
-
-                if (pos == total) {
-                    index.set(-1); // start over again
-                }
-
-                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);
+                t.clearDisplay();
+                t.updateDisplay(interactiveContent(rows, index));
+                t.flush();
             }
-        } while (!quit.get() || waitForUser.get());
+        } while (!quit.get());
 
         return 0;
     }
 
-    private static int lineIsNumber(String line) {
-        try {
-            return Integer.parseInt(line);
-        } catch (Exception e) {
-            return -1;
-        }
+    private List<AttributedString> interactiveContent(List<Row> rows, 
AtomicInteger index) {
+        List<AttributedString> answer = new ArrayList<>();
+
+        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);
+        answer.add(new AttributedString(""));
+        answer.add(new AttributedString(s));
+        answer.add(new AttributedString(""));
+
+        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(r -> "" + r.elapsed),
+                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(60, OverflowBehaviour.NEWLINE)
+                        .with(this::getMessage)));
+
+        var normal = AttributedStyle.DEFAULT;
+        var select = AttributedStyle.DEFAULT
+                .background(AttributedStyle.YELLOW)
+                .bold();
+        String[] lines = table.split(System.lineSeparator());
+        int pos = index.get() + 1;
+        for (int i = 0; i < lines.length; i++) {
+            String line = lines[i];
+            answer.add(new AttributedString(line, i == pos ? select : normal));
+        }
+        answer.add(new AttributedString(""));
+
+        // load data for current pos
+        Row r = rows.get(index.get());
+        String header = rowDetailedHeader(r);
+        answer.add(AttributedString.fromAnsi(header));
+        answer.add(new AttributedString(""));
+        // table with message details
+        table = getDataAsTable(r);
+        lines = table.split(System.lineSeparator());
+        for (int i = 0; i < lines.length; i++) {
+            String line = lines[i];
+            answer.add(AttributedString.fromAnsi(line));
+        }
+        answer.add(new AttributedString(""));
+
+        return answer;
     }
 
     private String getDataAsTable(Row r) {
@@ -310,148 +321,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 +333,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,11 +355,11 @@ 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) {
@@ -500,33 +372,31 @@ 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("(" + e + ")");
             }
         }
-        printer().println();
-        printer().println(getDataAsTable(row));
-        printer().println();
+        return sb.toString();
     }
 
     private String getElapsed(Row r) {
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..f9bcb3269801
--- /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,109 @@
+/*
+ * 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;
+
+/**
+ * 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;
+
+    public void start() throws Exception {
+        terminal = TerminalBuilder.builder().build();
+
+        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());
+
+        bindingReader = new BindingReader(terminal.reader());
+    }
+
+    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 flush() {
+        terminal.flush();
+    }
+
+    public void stop() throws Exception {
+        close();
+    }
+
+    @Override
+    public void close() throws IOException {
+        IOHelper.close(terminal);
+    }
+}

Reply via email to