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 9f3f1afd0677 CAMEL-23226: Enhance Shell with rich prompt, banner, and 
auto-suggestions (#22170)
9f3f1afd0677 is described below

commit 9f3f1afd067736341276e96b7e59b616d2bdc847
Author: Guillaume Nodet <[email protected]>
AuthorDate: Thu Apr 9 11:15:09 2026 +0200

    CAMEL-23226: Enhance Shell with rich prompt, banner, and auto-suggestions 
(#22170)
    
    * CAMEL-23226: Migrate Shell to JLine 4 Shell API
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
    
    * CAMEL-23226: Add POSIX, interactive, and script commands to JBang shell
    
    Add PosixCommandGroup (cd, ls, grep, cat, head, tail, etc.),
    InteractiveCommandGroup (nano, less, ttop), and enable
    script/variable commands for a richer shell experience.
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
    
    * CAMEL-23226: Enhance Shell with alias persistence and init script support
    
    - Add DefaultAliasManager with persistence at ~/.camel-jbang-aliases
      for persistent alias/unalias commands
    - Add init script support via ~/.camel-jbang-init file
    - Remove scriptCommands(true) which requires a scriptRunner to function
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
    
    * CAMEL-23226: Enhance Shell with rich prompt, banner, and auto-suggestions
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
    
    * CAMEL-23226: Bump JLine to 4.0.8 for picocli argument completion
    
    JLine 4.0.8 includes jline/jline3#1708 which fixes PicocliCommandRegistry
    to use picocli's AutoComplete for proper tab completion of options, option
    values, positional parameters, and nested subcommands.
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
    
    * CAMEL-23226: Restore help colors and completion styling in Shell
    
    - Restore HELP_COLORS variable for colored help output
    - Restore OTHERS_GROUP_NAME and COMPLETION_STYLE_GROUP variables
    - Use EnvironmentHelper.isColorEnabled() instead of duplicated logic
    - Use ShellBuilder import instead of FQCN where possible
    - Add TODO for replacing AutosuggestionWidgets with CommandTailTipWidgets
      (needs ShellBuilder to expose CommandDispatcher in onReaderReady callback)
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
    
    * CAMEL-23226: Use CommandTailTipWidgets for unified autosuggestion and 
tooltips
    
    Replace AutosuggestionWidgets with CommandTailTipWidgets which provides
    both fish-style autosuggestion AND command description tooltips below
    the cursor line. This requires the JLine ShellBuilder.onReaderReady
    BiConsumer<LineReader, CommandDispatcher> API enhancement.
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
    
    * CAMEL-23226: Bump JLine to 4.0.9
    
    Includes:
    - BiConsumer<LineReader, CommandDispatcher> overload for 
ShellBuilder.onReaderReady
    - Deprecated no-op jansi()/jna() on TerminalBuilder for JLine 3 
compatibility
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
    
    ---------
    
    Co-authored-by: Claude Opus 4.6 <[email protected]>
---
 dsl/camel-jbang/camel-jbang-core/pom.xml           |   8 +-
 .../camel/dsl/jbang/core/commands/Shell.java       | 204 +++++++++++----------
 2 files changed, 112 insertions(+), 100 deletions(-)

diff --git a/dsl/camel-jbang/camel-jbang-core/pom.xml 
b/dsl/camel-jbang/camel-jbang-core/pom.xml
index 31dccd48d50d..f236054ebe75 100644
--- a/dsl/camel-jbang/camel-jbang-core/pom.xml
+++ b/dsl/camel-jbang/camel-jbang-core/pom.xml
@@ -89,13 +89,13 @@
             <version>${picocli-version}</version>
         </dependency>
         <dependency>
-            <groupId>info.picocli</groupId>
-            <artifactId>picocli-shell-jline3</artifactId>
-            <version>${picocli-version}</version>
+            <groupId>org.jline</groupId>
+            <artifactId>jline</artifactId>
+            <version>${jline-version}</version>
         </dependency>
         <dependency>
             <groupId>org.jline</groupId>
-            <artifactId>jline</artifactId>
+            <artifactId>jline-picocli</artifactId>
             <version>${jline-version}</version>
         </dependency>
         <dependency>
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Shell.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Shell.java
index eeffd993ed50..8cb436ccbde7 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Shell.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Shell.java
@@ -16,35 +16,23 @@
  */
 package org.apache.camel.dsl.jbang.core.commands;
 
+import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
-import java.util.function.Supplier;
 
+import org.apache.camel.dsl.jbang.core.common.EnvironmentHelper;
+import org.apache.camel.dsl.jbang.core.common.VersionHelper;
 import org.apache.camel.util.HomeHelper;
-import org.jline.builtins.ClasspathResourceUtil;
-import org.jline.builtins.ConfigurationPath;
-import org.jline.console.SystemRegistry;
-import org.jline.console.impl.Builtins;
-import org.jline.console.impl.SystemRegistryImpl;
-import org.jline.keymap.KeyMap;
-import org.jline.reader.Binding;
-import org.jline.reader.EndOfFileException;
+import org.jline.builtins.InteractiveCommandGroup;
+import org.jline.builtins.PosixCommandGroup;
+import org.jline.picocli.PicocliCommandRegistry;
 import org.jline.reader.LineReader;
-import org.jline.reader.LineReaderBuilder;
-import org.jline.reader.MaskingCallback;
-import org.jline.reader.Parser;
-import org.jline.reader.Reference;
-import org.jline.reader.UserInterruptException;
-import org.jline.reader.impl.DefaultHighlighter;
-import org.jline.reader.impl.DefaultParser;
-import org.jline.terminal.Terminal;
-import org.jline.terminal.TerminalBuilder;
+import org.jline.shell.ShellBuilder;
+import org.jline.shell.impl.DefaultAliasManager;
+import org.jline.shell.widget.CommandTailTipWidgets;
 import org.jline.utils.AttributedStringBuilder;
 import org.jline.utils.AttributedStyle;
-import org.jline.utils.InfoCmp;
-import org.jline.widget.TailTipWidgets;
 import picocli.CommandLine;
-import picocli.shell.jline3.PicocliCommands;
 
 @CommandLine.Command(name = "shell",
                      description = "Interactive Camel JBang shell.",
@@ -55,92 +43,116 @@ import picocli.shell.jline3.PicocliCommands;
                              "Press Ctrl-C to exit." })
 public class Shell extends CamelCommand {
 
+    // Camel orange color (packed RGB)
+    private static final int CAMEL_ORANGE = 0xF69123;
+
+    // Help colors: title=bold blue, command=38, args=italic, options=yellow, 
description=dark gray
+    private static final String HELP_COLORS = "ti=1;34:co=38:ar=3:op=33:de=90";
+
     public Shell(CamelJBangMain main) {
         super(main);
     }
 
     @Override
     public Integer doCall() throws Exception {
-        Supplier<Path> workDir = () -> 
Paths.get(System.getProperty("user.dir"));
-        // set up JLine built-in commands
-        Path appConfig = 
ClasspathResourceUtil.getResourcePath("/nano/jnanorc", getClass()).getParent();
-        Builtins builtins = new Builtins(workDir, new 
ConfigurationPath(appConfig, workDir.get()), null) {
-            @Override
-            public String name() {
-                return "built-in";
-            }
-        };
-
-        PicocliCommands.PicocliCommandsFactory factory = new 
PicocliCommands.PicocliCommandsFactory();
-        PicocliCommands commands = new 
PicocliCommands(CamelJBangMain.getCommandLine());
-        commands.name("Camel");
-
-        try (Terminal terminal = TerminalBuilder.builder().build()) {
-            Parser parser = new DefaultParser();
-            SystemRegistry systemRegistry = new SystemRegistryImpl(parser, 
terminal, workDir, null);
-            systemRegistry.setCommandRegistries(builtins, commands);
-            systemRegistry.register("help", commands);
-
-            String history = Paths.get(HomeHelper.resolveHomeDir(), 
".camel-jbang-history").toString();
-            LineReader reader = LineReaderBuilder.builder()
-                    .terminal(terminal)
-                    .completer(systemRegistry.completer())
-                    .parser(parser)
-                    .highlighter(new ReplHighlighter())
-                    .variable(LineReader.LIST_MAX, 50)   // max tab completion 
candidates
-                    .variable(LineReader.HISTORY_FILE, history)
-                    .variable(LineReader.OTHERS_GROUP_NAME, "Others")
-                    .variable(LineReader.COMPLETION_STYLE_GROUP, 
"fg:blue,bold")
-                    .variable("HELP_COLORS", "ti=1;34:co=38:ar=3:op=33:de=90")
-                    .option(LineReader.Option.GROUP_PERSIST, true)
-                    .build();
-            builtins.setLineReader(reader);
-            factory.setTerminal(terminal);
-            TailTipWidgets widgets
-                    = new TailTipWidgets(reader, 
systemRegistry::commandDescription, 5, TailTipWidgets.TipType.COMPLETER);
-            widgets.enable();
-            KeyMap<Binding> keyMap = reader.getKeyMaps().get("main");
-            keyMap.bind(new Reference("tailtip-toggle"), KeyMap.alt("s"));
-            String prompt = "camel> ";
-            String rightPrompt = null;
-
-            // start the shell and process input until the user quits with 
Ctrl-C or Ctrl-D
-            String line;
-            boolean run = true;
-            TerminalBuilder.setTerminalOverride(terminal);
-            while (run) {
-                try {
-                    systemRegistry.cleanUp();
-                    line = reader.readLine(prompt, rightPrompt, 
(MaskingCallback) null, null);
-                    systemRegistry.execute(line);
-                } catch (UserInterruptException e) {
-                    // ctrl + c is pressed so exit
-                    run = false;
-                } catch (EndOfFileException e) {
-                    // ctrl + d is pressed so exit
-                    run = false;
-                } catch (Exception e) {
-                    systemRegistry.trace(e);
-                }
-            }
-        } finally {
-            TerminalBuilder.setTerminalOverride(null);
+        PicocliCommandRegistry registry = new 
PicocliCommandRegistry(CamelJBangMain.getCommandLine());
+
+        String homeDir = HomeHelper.resolveHomeDir();
+        Path history = Paths.get(homeDir, ".camel-jbang-history");
+
+        // Alias persistence: aliases are stored in ~/.camel-jbang-aliases
+        Path aliasFile = Paths.get(homeDir, ".camel-jbang-aliases");
+        DefaultAliasManager aliasManager = new DefaultAliasManager(aliasFile);
+
+        // Init script: if ~/.camel-jbang-init exists, it will be executed on 
shell startup
+        Path initScript = Paths.get(homeDir, ".camel-jbang-init");
+
+        String camelVersion = VersionHelper.extractCamelVersion();
+        boolean colorEnabled = EnvironmentHelper.isColorEnabled();
+
+        // org.jline.shell.Shell is used via FQCN to avoid clash with this 
class name
+        ShellBuilder builder = org.jline.shell.Shell.builder()
+                .prompt(() -> buildPrompt(camelVersion, colorEnabled))
+                .rightPrompt(() -> buildRightPrompt(colorEnabled))
+                .groups(registry, new PosixCommandGroup(), new 
InteractiveCommandGroup())
+                .historyFile(history)
+                .historyCommands(true)
+                .helpCommands(true)
+                .variableCommands(true)
+                .commandHighlighter(true)
+                .aliasManager(aliasManager)
+                .onReaderReady((reader, dispatcher) -> {
+                    // CommandTailTipWidgets provides both fish-style 
autosuggestion
+                    // and command description tooltips below the cursor line
+                    new CommandTailTipWidgets(reader, dispatcher, 5).enable();
+                })
+                .variable(LineReader.LIST_MAX, 50)
+                .variable(LineReader.OTHERS_GROUP_NAME, "Others")
+                .variable(LineReader.COMPLETION_STYLE_GROUP, "fg:blue,bold")
+                .variable("HELP_COLORS", HELP_COLORS)
+                .option(LineReader.Option.GROUP_PERSIST, true);
+
+        if (Files.exists(initScript)) {
+            builder.initScript(initScript.toFile());
+        }
+
+        try (org.jline.shell.Shell shell = builder.build()) {
+            printBanner(shell, camelVersion, colorEnabled);
+            shell.run();
         }
         return 0;
     }
 
-    private static class ReplHighlighter extends DefaultHighlighter {
-        @Override
-        protected void commandStyle(LineReader reader, AttributedStringBuilder 
sb, boolean enable) {
-            if (enable) {
-                if 
(reader.getTerminal().getNumericCapability(InfoCmp.Capability.max_colors) >= 
256) {
-                    sb.style(AttributedStyle.DEFAULT.bold().foreground(69));
-                } else {
-                    
sb.style(AttributedStyle.DEFAULT.foreground(AttributedStyle.CYAN));
-                }
-            } else {
-                sb.style(AttributedStyle.DEFAULT.boldOff().foregroundOff());
+    private static String buildPrompt(String camelVersion, boolean 
colorEnabled) {
+        if (!colorEnabled) {
+            return camelVersion != null ? "camel " + camelVersion + "> " : 
"camel> ";
+        }
+        AttributedStringBuilder sb = new AttributedStringBuilder();
+        sb.append("camel", 
AttributedStyle.DEFAULT.bold().foregroundRgb(CAMEL_ORANGE));
+        if (camelVersion != null) {
+            sb.append(" ");
+            sb.append(camelVersion, 
AttributedStyle.DEFAULT.foreground(AttributedStyle.GREEN));
+        }
+        sb.append("> ", AttributedStyle.DEFAULT);
+        return sb.toAnsi();
+    }
+
+    private static String buildRightPrompt(boolean colorEnabled) {
+        String cwd = System.getProperty("user.dir");
+        String home = System.getProperty("user.home");
+        if (cwd != null && home != null && cwd.startsWith(home)) {
+            cwd = "~" + cwd.substring(home.length());
+        }
+        if (cwd == null) {
+            return null;
+        }
+        if (!colorEnabled) {
+            return cwd;
+        }
+        return new AttributedStringBuilder()
+                .append(cwd, AttributedStyle.DEFAULT.faint())
+                .toAnsi();
+    }
+
+    private static void printBanner(org.jline.shell.Shell shell, String 
camelVersion, boolean colorEnabled) {
+        var writer = shell.terminal().writer();
+        if (colorEnabled) {
+            AttributedStringBuilder sb = new AttributedStringBuilder();
+            sb.append("Apache Camel", 
AttributedStyle.DEFAULT.bold().foregroundRgb(CAMEL_ORANGE));
+            sb.append(" JBang Shell", AttributedStyle.DEFAULT.bold());
+            if (camelVersion != null) {
+                sb.append(" v" + camelVersion, 
AttributedStyle.DEFAULT.foreground(AttributedStyle.GREEN));
+            }
+            writer.println(sb.toAnsi(shell.terminal()));
+        } else {
+            String banner = "Apache Camel JBang Shell";
+            if (camelVersion != null) {
+                banner += " v" + camelVersion;
             }
+            writer.println(banner);
         }
+        writer.println("Type 'help' for available commands, 'exit' to quit.");
+        writer.println();
+        writer.flush();
     }
 }

Reply via email to