From d75dfb6ec1769ddcf03cbb62ed567f0ef8b12333 Mon Sep 17 00:00:00 2001 From: Simon Bernard Date: Thu, 29 Jul 2021 18:25:20 +0200 Subject: [PATCH] Implement the InteractiveCLI using Jline3 and picocli --- leshan-client-demo/pom.xml | 2 +- .../demo/cli/interactive/InteractiveCLI.java | 97 ++++++++++++------- .../cli/interactive/InteractiveCommands.java | 10 +- .../cli/interactive/TerminalAppender.java | 60 +++++------- pom.xml | 12 ++- 5 files changed, 105 insertions(+), 76 deletions(-) diff --git a/leshan-client-demo/pom.xml b/leshan-client-demo/pom.xml index dfda1829d7..169a7bf821 100644 --- a/leshan-client-demo/pom.xml +++ b/leshan-client-demo/pom.xml @@ -37,7 +37,7 @@ Contributors: info.picocli - picocli-shell-jline2 + picocli-shell-jline3 ch.qos.logback diff --git a/leshan-client-demo/src/main/java/org/eclipse/leshan/client/demo/cli/interactive/InteractiveCLI.java b/leshan-client-demo/src/main/java/org/eclipse/leshan/client/demo/cli/interactive/InteractiveCLI.java index e470d8d1b7..dd2020383e 100644 --- a/leshan-client-demo/src/main/java/org/eclipse/leshan/client/demo/cli/interactive/InteractiveCLI.java +++ b/leshan-client-demo/src/main/java/org/eclipse/leshan/client/demo/cli/interactive/InteractiveCLI.java @@ -16,52 +16,72 @@ package org.eclipse.leshan.client.demo.cli.interactive; import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.function.Supplier; import org.eclipse.leshan.client.californium.LeshanClient; import org.eclipse.leshan.core.model.LwM2mModel; +import org.jline.console.impl.SystemRegistryImpl; +import org.jline.keymap.KeyMap; +import org.jline.reader.Binding; +import org.jline.reader.EndOfFileException; +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.DefaultParser; +import org.jline.terminal.Terminal; +import org.jline.terminal.TerminalBuilder; +import org.jline.widget.TailTipWidgets; import org.slf4j.LoggerFactory; -import ch.qos.logback.classic.Logger; import ch.qos.logback.core.Appender; -import jline.TerminalFactory; -import jline.TerminalFactory.Type; -import jline.console.ConsoleReader; -import jline.console.completer.ArgumentCompleter.ArgumentList; -import jline.console.completer.ArgumentCompleter.WhitespaceArgumentDelimiter; -import jline.internal.Configuration; import picocli.CommandLine; -import picocli.CommandLine.Help; -import picocli.shell.jline2.PicocliJLineCompleter; +import picocli.shell.jline3.PicocliCommands; +import picocli.shell.jline3.PicocliCommands.PicocliCommandsFactory; public class InteractiveCLI { - private ConsoleReader console; private CommandLine commandLine; + private LineReader reader; + private SystemRegistryImpl systemRegistry; + private String prompt; public InteractiveCLI(LeshanClient client, LwM2mModel model) throws IOException { + Supplier workDir = () -> Paths.get(System.getProperty("user.dir")); + // set up picocli commands + InteractiveCommands commands = new InteractiveCommands(client, model); + PicocliCommandsFactory factory = new PicocliCommandsFactory(); + commandLine = new CommandLine(commands, factory); + PicocliCommands picocliCommands = new PicocliCommands(commandLine); - // JLine 2 does not detect some terminal as not ANSI compatible, like Eclipse Console - // see : https://github.com/jline/jline2/issues/185 - // So use picocli heuristic instead : - if (!Help.Ansi.AUTO.enabled() && // - Configuration.getString(TerminalFactory.JLINE_TERMINAL, TerminalFactory.AUTO).toLowerCase() - .equals(TerminalFactory.AUTO)) { - TerminalFactory.configure(Type.NONE); - } + Parser parser = new DefaultParser(); + try (Terminal terminal = TerminalBuilder.builder().dumb(true).build()) { + systemRegistry = new SystemRegistryImpl(parser, terminal, workDir, null); + systemRegistry.setCommandRegistries(picocliCommands); + systemRegistry.register("help", picocliCommands); - // Create Interactive Shell - console = new ConsoleReader(); + reader = LineReaderBuilder.builder().terminal(terminal).completer(systemRegistry.completer()).parser(parser) + .variable(LineReader.LIST_MAX, 50) // max tab completion candidates + .build(); + factory.setTerminal(terminal); + TailTipWidgets widgets = new TailTipWidgets(reader, systemRegistry::commandDescription, 5, + TailTipWidgets.TipType.COMPLETER); + widgets.enable(); + KeyMap keyMap = reader.getKeyMaps().get("main"); + keyMap.bind(new Reference("tailtip-toggle"), KeyMap.alt("s")); - // set up the completion - InteractiveCommands commands = new InteractiveCommands(console, client, model); - commandLine = new CommandLine(commands); - console.addCompleter(new PicocliJLineCompleter(commandLine.getCommandSpec())); + prompt = "prompt> "; - // Configure Terminal appender if it is present. - Appender appender = ((Logger) LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME)) - .getAppender("TERMINAL"); - if (appender instanceof TerminalAppender) { - ((TerminalAppender) appender).setConsole(console); + // Configure Terminal appender if it is present. + Appender appender = ((ch.qos.logback.classic.Logger) LoggerFactory + .getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME)).getAppender("TERMINAL"); + if (appender instanceof TerminalAppender) { + ((TerminalAppender) appender).setReader(reader); + } } } @@ -70,13 +90,20 @@ public void showHelp() { } public void start() throws IOException { - - // start the shell and process input until the user quits with Ctl-D + // start the shell and process input until the user quits with Ctrl-D String line; - while ((line = console.readLine()) != null) { - ArgumentList list = new WhitespaceArgumentDelimiter().delimit(line, line.length()); - commandLine.execute(list.getArguments()); - console.killLine(); + while (true) { + try { + systemRegistry.cleanUp(); + line = reader.readLine(prompt, null, (MaskingCallback) null, null); + systemRegistry.execute(line); + } catch (UserInterruptException e) { + // Ignore + } catch (EndOfFileException e) { + return; + } catch (Exception e) { + systemRegistry.trace(e); + } } } } diff --git a/leshan-client-demo/src/main/java/org/eclipse/leshan/client/demo/cli/interactive/InteractiveCommands.java b/leshan-client-demo/src/main/java/org/eclipse/leshan/client/demo/cli/interactive/InteractiveCommands.java index a3f80dc5a2..a1d46a23f6 100644 --- a/leshan-client-demo/src/main/java/org/eclipse/leshan/client/demo/cli/interactive/InteractiveCommands.java +++ b/leshan-client-demo/src/main/java/org/eclipse/leshan/client/demo/cli/interactive/InteractiveCommands.java @@ -15,7 +15,6 @@ import org.eclipse.leshan.core.LwM2mId; import org.eclipse.leshan.core.model.LwM2mModel; -import jline.console.ConsoleReader; import picocli.CommandLine; import picocli.CommandLine.Command; import picocli.CommandLine.HelpCommand; @@ -36,20 +35,21 @@ public class InteractiveCommands implements Runnable { private PrintWriter out; + private CommandLine commandLine; private LeshanClient client; private LwM2mModel model; - public InteractiveCommands(ConsoleReader reader, LeshanClient client, LwM2mModel model) { - out = new PrintWriter(reader.getOutput()); - + public InteractiveCommands(LeshanClient client, LwM2mModel model) { + commandLine = new CommandLine(this); + out = commandLine.getOut(); this.client = client; this.model = model; } @Override public void run() { - out.print(new CommandLine(this).getUsageMessage()); + out.print(commandLine.getUsageMessage()); out.flush(); } diff --git a/leshan-client-demo/src/main/java/org/eclipse/leshan/client/demo/cli/interactive/TerminalAppender.java b/leshan-client-demo/src/main/java/org/eclipse/leshan/client/demo/cli/interactive/TerminalAppender.java index 4b2d08aec4..e53e3bac78 100644 --- a/leshan-client-demo/src/main/java/org/eclipse/leshan/client/demo/cli/interactive/TerminalAppender.java +++ b/leshan-client-demo/src/main/java/org/eclipse/leshan/client/demo/cli/interactive/TerminalAppender.java @@ -1,46 +1,38 @@ +/******************************************************************************* + * Copyright (c) 2021 Sierra Wireless and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v20.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.html. + * + * Contributors: + * Sierra Wireless - initial API and implementation + *******************************************************************************/ package org.eclipse.leshan.client.demo.cli.interactive; -import java.io.IOException; +import org.jline.reader.LineReader; import ch.qos.logback.core.ConsoleAppender; -import jline.console.ConsoleReader; -/** - * A logback Console appender compatible with a Jline 2 Console reader. - */ public class TerminalAppender extends ConsoleAppender { - private ConsoleReader console; - private String prompt; + private LineReader reader; - @Override - protected void subAppend(E event) { - if (console == null || !console.getTerminal().isAnsiSupported()) - super.subAppend(event); - else { - // stash prompt - String stashed = ""; - try { - stashed = console.getCursorBuffer().copy().toString(); - console.resetPromptLine("", "", -1); - } catch (IOException e) { - e.printStackTrace(); - } - - // Display logs - super.subAppend(event); - - // unstash prompt - try { - console.resetPromptLine(prompt, stashed, -1); - } catch (IOException e) { - e.printStackTrace(); - } - } + public void setReader(LineReader reader) { + this.reader = reader; } - public void setConsole(ConsoleReader console) { - this.console = console; - this.prompt = console.getPrompt(); + @Override + protected void append(E eventObject) { + if (reader == null) { + super.append(eventObject); + } else { + reader.printAbove(new String(getEncoder().encode(eventObject))); + } } } diff --git a/pom.xml b/pom.xml index 06f0801924..305f57bb65 100644 --- a/pom.xml +++ b/pom.xml @@ -751,9 +751,19 @@ Contributors: info.picocli - picocli-shell-jline2 + picocli-shell-jline3 4.6.1 + + org.jline + jline + 3.20.0 + + + org.jline + jline-console + 3.20.0 + commons-cli commons-cli