diff --git a/builtins/src/main/java/org/jline/builtins/Builtins.java b/builtins/src/main/java/org/jline/builtins/Builtins.java index 6dccdc0f9..5a3fa4172 100644 --- a/builtins/src/main/java/org/jline/builtins/Builtins.java +++ b/builtins/src/main/java/org/jline/builtins/Builtins.java @@ -137,6 +137,7 @@ public boolean hasCommand(String name) { return false; } + @Override public SystemCompleter compileCompleters() { SystemCompleter out = new SystemCompleter(); for (Map.Entry entry: commandName.entrySet()) { @@ -163,31 +164,18 @@ private Command command(String name) { } @Override - public Object execute(String command, String[] args) throws Exception { - return execute(command, Arrays.asList(args)); - } - - private Object execute(String command, List args) throws Exception { - execute(command, args, System.in, System.out, System.err); - return null; - } - - public void execute(String command, List args, InputStream in, PrintStream out, PrintStream err) throws Exception { - execute(command, args.toArray(new String[0]), in, out, err); - } - - public void execute(String command, String[] args, InputStream in, PrintStream out, PrintStream err) throws Exception { + public Object execute(CommandRegistry.CommandSession session, String command, String[] args) throws Exception { exception = null; - commandExecute.get(command(command)).execute().accept(new CommandInput(args, in, out, err)); + commandExecute.get(command(command)).execute().accept(new CommandInput(args, session)); if (exception != null) { throw exception; } + return null; } private List commandOptions(String command) { - List args = Arrays.asList("--help"); try { - execute(command, args); + execute(new CommandRegistry.CommandSession(), command, new String[] {"--help"}); } catch (HelpException e) { return compileCommandOptions(e.getMessage()); } catch (Exception e) { @@ -196,13 +184,9 @@ private List commandOptions(String command) { return null; } - private Terminal terminal() { - return reader.getTerminal(); - } - private void less(CommandInput input) { try { - Commands.less(terminal(), input.in(), input.out(), input.err(), workDir.get(), input.args()); + Commands.less(input.terminal(), input.in(), input.out(), input.err(), workDir.get(), input.args()); } catch (Exception e) { this.exception = e; } @@ -210,7 +194,7 @@ private void less(CommandInput input) { private void nano(CommandInput input) { try { - Commands.nano(terminal(), input.out(), input.err(), workDir.get(), input.args(), configPath); + Commands.nano(input.terminal(), input.out(), input.err(), workDir.get(), input.args(), configPath); } catch (Exception e) { this.exception = e; } @@ -266,7 +250,7 @@ private void unsetopt(CommandInput input) { private void ttop(CommandInput input) { try { - TTop.ttop(terminal(), input.out(), input.err(), input.args()); + TTop.ttop(input.terminal(), input.out(), input.err(), input.args()); } catch (Exception e) { this.exception = e; } @@ -502,27 +486,32 @@ public static List compileCommandInfo(String helpMessage) { return out; } - public static class CommandInput{ + public static class CommandInput { String[] args; Object[] xargs; + Terminal terminal; InputStream in; PrintStream out; PrintStream err; - public CommandInput(String[] args) { - this(args, null, null, null); + public CommandInput(String[] args, CommandRegistry.CommandSession session) { + this(args, null, session); } - public CommandInput(Object[] xargs, boolean dumb) { - this.xargs = xargs; - this.args = new String[xargs.length]; - for (int i = 0; i < xargs.length; i++) { - args[i] = xargs[i] != null ? xargs[i].toString() : ""; + public CommandInput(String[] args, Object[] xargs, CommandRegistry.CommandSession session) { + this(args, session.terminal(), session.in(), session.out(), session.err()); + if (xargs != null) { + this.xargs = xargs; + this.args = new String[xargs.length]; + for (int i = 0; i < xargs.length; i++) { + this.args[i] = xargs[i] != null ? xargs[i].toString() : ""; + } } } - public CommandInput(String[] args, InputStream in, PrintStream out, PrintStream err) { + public CommandInput(String[] args, Terminal terminal, InputStream in, PrintStream out, PrintStream err) { this.args = args; + this.terminal = terminal; this.in = in; this.out = out; this.err = err; @@ -536,6 +525,10 @@ public Object[] xargs() { return xargs; } + public Terminal terminal() { + return terminal; + } + public InputStream in() { return in; } diff --git a/builtins/src/main/java/org/jline/builtins/CommandRegistry.java b/builtins/src/main/java/org/jline/builtins/CommandRegistry.java index daa48bb16..1492a6107 100644 --- a/builtins/src/main/java/org/jline/builtins/CommandRegistry.java +++ b/builtins/src/main/java/org/jline/builtins/CommandRegistry.java @@ -11,7 +11,10 @@ import org.jline.builtins.Completers; import org.jline.builtins.Widgets; import org.jline.builtins.Options.HelpException; +import org.jline.terminal.Terminal; +import java.io.InputStream; +import java.io.PrintStream; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -26,6 +29,7 @@ public interface CommandRegistry { /** * Aggregate SystemCompleters of commandRegisteries + * @param commandRegistries command registeries which completers is to be aggregated * @return uncompiled SystemCompleter */ static Completers.SystemCompleter aggregateCompleters(CommandRegistry ... commandRegistries) { @@ -38,6 +42,7 @@ static Completers.SystemCompleter aggregateCompleters(CommandRegistry ... comman /** * Aggregate and compile SystemCompleters of commandRegisteries + * @param commandRegistries command registeries which completers is to be aggregated and compile * @return compiled SystemCompleter */ static Completers.SystemCompleter compileCompleters(CommandRegistry ... commandRegistries) { @@ -48,7 +53,7 @@ static Completers.SystemCompleter compileCompleters(CommandRegistry ... commandR /** * Returns the name of this registry. - * @return name + * @return the name of the registry */ default String name() { return this.getClass().getSimpleName(); @@ -71,7 +76,7 @@ default String name() { */ default List commandInfo(String command) { try { - invoke(command, new Object[] {"--help"}); + invoke(new CommandSession(), command, new Object[] {"--help"}); } catch (HelpException e) { return Builtins.compileCommandInfo(e.getMessage()); } catch (Exception e) { @@ -90,7 +95,6 @@ default List commandInfo(String command) { /** * Returns a {@code SystemCompleter} that can provide detailed completion * information for all registered commands. - * * @return a SystemCompleter that can provide command completion for all registered commands */ Completers.SystemCompleter compileCompleters(); @@ -103,7 +107,7 @@ default List commandInfo(String command) { */ default Widgets.CmdDesc commandDescription(String command) { try { - invoke(command, new Object[] {"--help"}); + invoke(new CommandSession(), command, new Object[] {"--help"}); } catch (HelpException e) { return Builtins.compileCommandDescription(e.getMessage()); } catch (Exception e) { @@ -115,24 +119,26 @@ default Widgets.CmdDesc commandDescription(String command) { /** * Execute a command that have only string parameters and options. Implementation of the method is required * when aggregating command registries using SystemRegistry. - * @param command - * @param args - * @return result - * @throws Exception + * @param session the data of the current command session + * @param command the name of the command + * @param args arguments of the command + * @return result of the command execution + * @throws Exception in case of error */ - default Object execute(String command, String[] args) throws Exception { + default Object execute(CommandSession session, String command, String[] args) throws Exception { throw new IllegalArgumentException("CommandRegistry method execute(String command, String[] args) is not implemented!"); } /** * Execute a command. If command has other than string parameters a custom implementation is required. * This method will be called only when we have ConsoleEngine in SystemRegistry. - * @param command - * @param args - * @return result - * @throws Exception + * @param session the data of the current command session + * @param command the name of the command + * @param args arguments of the command + * @return result of the command execution + * @throws Exception in case of error */ - default Object invoke(String command, Object... args) throws Exception { + default Object invoke(CommandSession session, String command, Object... args) throws Exception { String[] _args = new String[args.length]; for (int i = 0; i < args.length; i++) { if (!(args[i] instanceof String)) { @@ -140,7 +146,49 @@ default Object invoke(String command, Object... args) throws Exception { } _args[i] = args[i].toString(); } - return execute(command, _args); + return execute(session, command, _args); + } + + public static class CommandSession { + private final Terminal terminal; + private final InputStream in; + private final PrintStream out; + private final PrintStream err; + + public CommandSession() { + this.in = System.in; + this.out = System.out; + this.err = System.err; + this.terminal = null; + } + + public CommandSession(Terminal terminal) { + this(terminal, terminal.input(), new PrintStream(terminal.output()), new PrintStream(terminal.output())); + } + + public CommandSession(Terminal terminal, InputStream in, PrintStream out, PrintStream err) { + this.terminal = terminal; + this.in = in; + this.out = out; + this.err = err; + } + + public Terminal terminal() { + return terminal; + } + + public InputStream in() { + return in; + } + + public PrintStream out() { + return out; + } + + public PrintStream err() { + return err; + } + } } diff --git a/builtins/src/main/java/org/jline/builtins/ConsoleEngine.java b/builtins/src/main/java/org/jline/builtins/ConsoleEngine.java index 4b3ccd161..f2dda46c1 100644 --- a/builtins/src/main/java/org/jline/builtins/ConsoleEngine.java +++ b/builtins/src/main/java/org/jline/builtins/ConsoleEngine.java @@ -15,7 +15,6 @@ import org.jline.builtins.CommandRegistry; import org.jline.reader.Completer; import org.jline.reader.LineReader; -import org.jline.reader.ParsedLine; import org.jline.reader.Widget; /** @@ -26,9 +25,9 @@ public interface ConsoleEngine extends CommandRegistry { /** - * Removes command first character if it is colon - * @param command name to complete - * @return command name without colon + * Removes the command name first character if it is colon + * @param command the name of the command to complete + * @return command name without starting colon */ static String plainCommand(String command) { return command.startsWith(":") ? command.substring(1) : command; @@ -42,63 +41,65 @@ static String plainCommand(String command) { /** * Sets systemRegistry - * @param systemRegistry + * @param systemRegistry SystemRegistry */ void setSystemRegistry(SystemRegistry systemRegistry); /** * Substituting args references with their values. - * @param args - * @return Substituted args + * @param args the arguments to be expanded + * @return expanded arguments * @throws Exception in case of error */ Object[] expandParameters(String[] args) throws Exception; /** * Returns all scripts found from PATH - * @return script names + * @return script file names */ List scripts(); /** * Sets file name extension used by console scripts - * @param console script file extension + * @param extension console script file extension */ - public void setScriptExtension(String extension); + void setScriptExtension(String extension); /** * Returns true if alias 'name' exists - * @param alias name + * @param name alias name * @return true if alias exists */ boolean hasAlias(String name); /** * Returns alias 'name' value - * @param alias name + * @param name alias name * @return value of alias */ String getAlias(String name); /** * Returns script and variable completers - * @return script completers + * @return script and variable completers */ List scriptCompleters(); /** - * Executes parsed line that does not contain known command by the system registry. - * If parsed line is neither JLine or ScriptEngine script it will be evaluated + * Executes command line that does not contain known command by the system registry. + * If the line is neither JLine or ScriptEngine script it will be evaluated * as ScriptEngine statement. - * @param parsed command line + * @param name parsed command/script name + * @param rawLine raw command line + * @param args parsed arguments of the command * @return command line execution result * @throws Exception in case of error */ - Object execute(ParsedLine parsedLine) throws Exception; + Object execute(String name, String rawLine, String[] args) throws Exception; /** * Executes either JLine or ScriptEngine script. - * @param script file + * @param script script file * @return script execution result * @throws Exception in case of error */ @@ -108,43 +109,56 @@ default Object execute(File script) throws Exception { /** * Executes either JLine or ScriptEngine script. - * @param script file - * @param cmdLine complete command line - * @param script arguments + * @param script script file + * @param rawLine raw command line + * @param args script arguments * @return script execution result * @throws Exception in case of error */ - Object execute(File script, String cmdLine, String[] args) throws Exception; + Object execute(File script, String rawLine, String[] args) throws Exception; /** * Post processes execution result. If result is to be assigned to the console variable * then method will return null. - * @param command line - * @param result to process + * @param line command line + * @param result command result to process + * @param output command redirected output * @return processed result */ - Object postProcess(String line, Object result); + Object postProcess(String line, Object result, String output); /** - * Displays object. - * @param object to print + * Print object. + * @param object object to print */ void println(Object object); /** - * Displays object. + * Print object. * @param options println options - * @param object to print + * @param object object to print */ void println(Map options, Object object); + /** + * Create console variable + * @param name name of the variable + * @param value value of the variable + */ + void putVariable(String name, Object value); + /** * Get variable value - * @param name of variable + * @param name name of the variable * @return variable value */ Object getVariable(String name); + /** + * Delete temporary console variables + */ + void purge(); + /** * Execute widget function * @param function to execute @@ -152,6 +166,12 @@ default Object execute(File script) throws Exception { */ boolean executeWidget(Object function); + /** + * + * @return true if consoleEngine is executing script + */ + boolean isExecuting(); + static class WidgetCreator implements Widget { private ConsoleEngine consoleEngine; private Object function; @@ -167,7 +187,7 @@ public WidgetCreator(ConsoleEngine consoleEngine, String function) { public boolean apply() { return consoleEngine.executeWidget(function); } - + @Override public String toString() { return name; diff --git a/builtins/src/main/java/org/jline/builtins/ConsoleEngineImpl.java b/builtins/src/main/java/org/jline/builtins/ConsoleEngineImpl.java index b2d7d086a..b8aa6e520 100644 --- a/builtins/src/main/java/org/jline/builtins/ConsoleEngineImpl.java +++ b/builtins/src/main/java/org/jline/builtins/ConsoleEngineImpl.java @@ -51,6 +51,7 @@ public enum Command {SHOW , ALIAS , UNALIAS , SLURP}; + private static final String VAR_CONSOLE_OPTIONS = "CONSOLE_OPTIONS"; private static final String VAR_PRNT_OPTIONS = "PRNT_OPTIONS"; private static final String VAR_PATH = "PATH"; private static final String VAR_NANORC = "NANORC"; @@ -71,6 +72,7 @@ public enum Command {SHOW private final Map aliases = new HashMap<>(); private Path aliasFile; private LineReader reader; + private boolean executing = false; @SuppressWarnings("unchecked") public ConsoleEngineImpl(ScriptEngine engine @@ -100,7 +102,7 @@ public ConsoleEngineImpl(ScriptEngine engine @Override public void setLineReader(LineReader reader) { - this.reader= reader; + this.reader = reader; } private Parser parser() { @@ -108,7 +110,11 @@ private Parser parser() { } private Terminal terminal() { - return reader.getTerminal(); + return systemRegistry.terminal(); + } + + public boolean isExecuting() { + return executing; } @Override @@ -210,6 +216,7 @@ public List scriptCompleters() { , this::commandOptions , 1) )); + out.add(new ArgumentCompleter(new StringsCompleter(aliases::keySet), NullCompleter.INSTANCE)); return out; } @@ -234,7 +241,7 @@ public List scripts() { out.add(name.substring(0, name.lastIndexOf("."))); } } catch (Exception e) { - println(e); + systemRegistry.println(e); } return out; } @@ -449,8 +456,9 @@ public boolean execute() throws Exception { private void internalExecute() throws Exception { if (isEngineScript()) { result = engine.execute(script, expandParameters(args)); - result = postProcess(cmdLine, result); + postProcess(cmdLine, result); } else if (isConsoleScript()) { + executing = true; boolean done = false; String line = ""; try (BufferedReader br = new BufferedReader(new FileReader(script))) { @@ -487,16 +495,20 @@ private void internalExecute() throws Exception { throw e; } catch (EndOfFileException e) { done = true; + executing = false; break; } catch (Exception e) { + executing = false; throw new IllegalArgumentException(line + "\n" + e.getMessage()); } } if (!done) { + executing = false; throw new IllegalArgumentException("Incompleted command: \n" + line); } + executing = false; result = engine.get("_return"); - result = postProcess(cmdLine, result); + postProcess(cmdLine, result); } } } @@ -515,18 +527,16 @@ public Object execute(File script, String cmdLine, String[] args) throws Excepti } @Override - public Object execute(ParsedLine pl) throws Exception { - if (pl.line().trim().startsWith("#")) { + public Object execute(String cmd, String line, String[] args) throws Exception { + if (line.trim().startsWith("#")) { return null; } - String[] args = pl.words().subList(1, pl.words().size()).toArray(new String[0]); - String cmd = ConsoleEngine.plainCommand(Parser.getCommand(pl.word())); Object out = null; - ScriptFile file = new ScriptFile(cmd, pl.line(), args); + ScriptFile file = new ScriptFile(cmd, line, args); if (file.execute()) { out = file.getResult(); } else { - String line = pl.line().trim(); + line = line.trim(); if (isCodeBlock(line)) { StringBuilder sb = new StringBuilder(); for (String s: line.split("\n|\n\r")) { @@ -575,6 +585,16 @@ public Object execute(ParsedLine pl) throws Exception { return out; } + @Override + public void purge() { + engine.del("_*"); + } + + @Override + public void putVariable(String name, Object value) { + engine.put(name, value); + } + @Override public Object getVariable(String name) { if (!engine.hasVariable(name)) { @@ -594,31 +614,61 @@ public boolean executeWidget(Object function) { } engine.execute("_widgetFunction()"); } catch (Exception e) { - println(e); + systemRegistry.println(e); return false; } return true; } + @SuppressWarnings("unchecked") + private boolean splitCommandOutput() { + boolean out = true; + try { + if (engine.hasVariable(VAR_CONSOLE_OPTIONS)) { + out = (boolean) ((Map) engine.get(VAR_CONSOLE_OPTIONS)).getOrDefault("splitOutput", true); + } + } catch (Exception e) { + systemRegistry.println(new Exception("Bad CONSOLE_OPTION value: " + e.getMessage())); + } + return out; + } + + @Override - public Object postProcess(String line, Object result) { + public Object postProcess(String line, Object result, String output) { Object out = result; + Object _output = output != null && splitCommandOutput() ? output.split("\n") : output; + if (Parser.getVariable(line) != null && result != null) { + engine.put("output", _output); + } + if (systemRegistry.hasCommand(Parser.getCommand(line))) { + out = postProcess(line, Parser.getVariable(line) != null && result == null ? _output : result); + } else if (Parser.getVariable(line) != null) { + if (result == null) { + engine.put(Parser.getVariable(line), _output); + } + out = null; + } + return out; + } + + private Object postProcess(String line, Object result) { + Object out = result instanceof String && ((String)result).trim().length() == 0 ? null : result; if (Parser.getVariable(line) != null) { engine.put(Parser.getVariable(line), result); out = null; - } else if (!Parser.getCommand(line).equals("show") && systemRegistry.hasCommand(Parser.getCommand(line)) - && result != null) { + } else if (!Parser.getCommand(line).equals("show") && result != null) { engine.put("_", result); } return out; } @Override - public Object invoke(String command, Object... args) throws Exception { + public Object invoke(CommandRegistry.CommandSession session, String command, Object... args) throws Exception { exception = null; Object out = null; if (hasCommand(command)) { - out = commandExecute.get(command(command)).executeFunction().apply(new Builtins.CommandInput(args, true)); + out = commandExecute.get(command(command)).executeFunction().apply(new Builtins.CommandInput(null, args, session)); } else { String[] _args = new String[args.length]; for (int i = 0; i < args.length; i++) { @@ -650,7 +700,6 @@ private Map defaultPrntOptions() { @Override public void println(Object object) { Map options = defaultPrntOptions(); - options.putIfAbsent("exception", "message"); println(options, object); } @@ -667,21 +716,7 @@ public void println(Map options, Object object) { } else if (!style.isEmpty() && object instanceof String) { highlight(width, style, (String) object); } else if (object instanceof Exception) { - if (object instanceof Options.HelpException) { - Options.HelpException.highlight(((Exception)object).getMessage(), Options.HelpException.defaultStyle()).print(terminal()); - } else if (options.getOrDefault("exception", "stack").equals("stack")) { - ((Exception) object).printStackTrace(); - } else { - String message = ((Exception) object).getMessage(); - AttributedStringBuilder asb = new AttributedStringBuilder(); - if (message != null) { - asb.append(message, AttributedStyle.DEFAULT.foreground(AttributedStyle.RED)); - } else { - asb.append("Caught exception: ", AttributedStyle.DEFAULT.foreground(AttributedStyle.RED)); - asb.append(object.getClass().getCanonicalName(), AttributedStyle.DEFAULT.foreground(AttributedStyle.RED)); - } - asb.toAttributedString().println(terminal()); - } + SystemRegistry.println(options.getOrDefault("exception", "stack").equals("stack"), terminal(), (Exception)object); } else if (object instanceof String) { highlight(AttributedStyle.YELLOW + AttributedStyle.BRIGHT, object); } else if (object instanceof Number) { @@ -710,9 +745,9 @@ private void highlight(int width, String style, String object) { } SyntaxHighlighter highlighter = nanorc != null ? SyntaxHighlighter.build(nanorc, style) : null; - for (String s: object.split("\n")) { + for (String s: object.split("\\r?\\n")) { AttributedStringBuilder asb = new AttributedStringBuilder(); - asb.append(s).setLength(width); + asb.append(s).subSequence(0, width); // setLength(width) fill nul-chars at the end of line if (highlighter != null) { highlighter.highlight(asb).println(terminal()); } else { @@ -879,7 +914,7 @@ private Object unalias(Builtins.CommandInput input) { private List commandOptions(String command) { try { - invoke(command, "--help"); + invoke(new CommandSession(), command, "--help"); } catch (HelpException e) { return Builtins.compileCommandOptions(e.getMessage()); } catch (Exception e) { diff --git a/builtins/src/main/java/org/jline/builtins/SystemRegistry.java b/builtins/src/main/java/org/jline/builtins/SystemRegistry.java index 3e43ff188..015000e9a 100644 --- a/builtins/src/main/java/org/jline/builtins/SystemRegistry.java +++ b/builtins/src/main/java/org/jline/builtins/SystemRegistry.java @@ -13,8 +13,9 @@ import java.util.Map; import org.jline.reader.Completer; -import org.jline.reader.ParsedLine; import org.jline.terminal.Terminal; +import org.jline.utils.AttributedStringBuilder; +import org.jline.utils.AttributedStyle; /** * Aggregate command registries and dispatch command executions. @@ -25,38 +26,96 @@ public interface SystemRegistry extends CommandRegistry { /** * Set command registeries - * @param commandRegistries + * @param commandRegistries command registeries used by the application */ void setCommandRegistries(CommandRegistry... commandRegistries); /** * Initialize consoleEngine environment by executing console script - * @param script + * @param script initialization script */ void initialize(File script); /** * Returns command completer that includes also console variable and script completion. - * @return complater + * @return command completer */ Completer completer(); /** * Returns a command, method or syntax description for use in the JLine Widgets framework. - * @param command line whose description to return + * @param line command line whose description to return * @return command description for JLine TailTipWidgets to be displayed * in the terminal status bar. */ Widgets.CmdDesc commandDescription(Widgets.CmdLine line); - + /** * Execute a command, script or evaluate scriptEngine statement - * @param line - * @return result - * @throws Exception + * @param line command line to be executed + * @return execution result + * @throws Exception in case of error */ Object execute(String line) throws Exception; + /** + * Delete temporary console variables and reset output streams + */ + void cleanUp(); + + /** + * Print exception on terminal + * @param exception exception to print on terminal + */ + void println(Exception exception); + + /** + * @return terminal + */ + Terminal terminal(); + + /** + * Execute command with arguments + * @param command command to be executed + * @param args arguments of the command + * @return command execution result + * @throws Exception in case of error + */ + Object execute(String command, String[] args) throws Exception; + + /** + * Execute command with arguments + * @param command command to be executed + * @param args arguments of the command + * @return command execution result + * @throws Exception in case of error + */ + Object invoke(String command, Object... args) throws Exception; + + /** + * Print exception + * @param stack print stack trace if stack true otherwise message + * @param terminal JLine terminal + * @param exception exception to be printed + */ + static void println(boolean stack, Terminal terminal, Exception exception) { + if (exception instanceof Options.HelpException) { + Options.HelpException.highlight((exception).getMessage(), Options.HelpException.defaultStyle()).print(terminal); + } else if (stack) { + exception.printStackTrace(); + } else { + String message = exception.getMessage(); + AttributedStringBuilder asb = new AttributedStringBuilder(); + if (message != null) { + asb.append(message, AttributedStyle.DEFAULT.foreground(AttributedStyle.RED)); + } else { + asb.append("Caught exception: ", AttributedStyle.DEFAULT.foreground(AttributedStyle.RED)); + asb.append(exception.getClass().getCanonicalName(), AttributedStyle.DEFAULT.foreground(AttributedStyle.RED)); + } + asb.toAttributedString().println(terminal); + } + } + /** * @return systemRegistry of the current thread */ diff --git a/builtins/src/main/java/org/jline/builtins/SystemRegistryImpl.java b/builtins/src/main/java/org/jline/builtins/SystemRegistryImpl.java index b4adc4d4d..f7b5ba785 100644 --- a/builtins/src/main/java/org/jline/builtins/SystemRegistryImpl.java +++ b/builtins/src/main/java/org/jline/builtins/SystemRegistryImpl.java @@ -8,7 +8,14 @@ */ package org.jline.builtins; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import java.io.File; +import java.io.FileOutputStream; +import java.io.PrintStream; import java.util.*; import java.util.stream.Collectors; @@ -28,8 +35,12 @@ import org.jline.reader.impl.completer.ArgumentCompleter; import org.jline.reader.impl.completer.NullCompleter; import org.jline.reader.impl.completer.StringsCompleter; +import org.jline.terminal.Attributes; +import org.jline.terminal.Attributes.InputFlag; import org.jline.terminal.Terminal; +import org.jline.terminal.TerminalBuilder; import org.jline.utils.AttributedStringBuilder; +import org.jline.utils.OSUtils; /** * Aggregate command registeries. @@ -37,27 +48,29 @@ * @author Matti Rinta-Nikkola */ public class SystemRegistryImpl implements SystemRegistry { - public enum Command {EXIT - , HELP}; - private static final Class[] BUILTIN_REGISTERIES = {Builtins.class, ConsoleEngineImpl.class}; + public enum Command { + EXIT, HELP + }; + + private static final Class[] BUILTIN_REGISTERIES = { Builtins.class, ConsoleEngineImpl.class }; private CommandRegistry[] commandRegistries; private Integer consoleId = null; - private Terminal terminal; private Parser parser; private ConfigurationPath configPath; - private Map commandName = new HashMap<>(); - private Map nameCommand = new HashMap<>(); - private Map aliasCommand = new HashMap<>(); - private final Map commandExecute = new HashMap<>(); + private Map commandName = new HashMap<>(); + private Map nameCommand = new HashMap<>(); + private Map aliasCommand = new HashMap<>(); + private final Map commandExecute = new HashMap<>(); private Map> commandInfos = new HashMap<>(); private Exception exception; + private CommandOutputStream outputStream; public SystemRegistryImpl(Parser parser, Terminal terminal, ConfigurationPath configPath) { this.parser = parser; - this.terminal = terminal; this.configPath = configPath; + outputStream = new CommandOutputStream(terminal); Set cmds = new HashSet<>(EnumSet.allOf(Command.class)); - for (Command c: cmds) { + for (Command c : cmds) { commandName.put(c, c.name().toLowerCase()); } doNameCommand(); @@ -66,9 +79,7 @@ public SystemRegistryImpl(Parser parser, Terminal terminal, ConfigurationPath co } private void doNameCommand() { - nameCommand = commandName.entrySet() - .stream() - .collect(Collectors.toMap(Map.Entry::getValue, Map.Entry::getKey)); + nameCommand = commandName.entrySet().stream().collect(Collectors.toMap(Map.Entry::getValue, Map.Entry::getKey)); } @Override @@ -142,12 +153,12 @@ private Command command(String name) { private List localCommandInfo(String command) { try { - localExecute(command, new String[] {"--help"}); + localExecute(command, new String[] { "--help" }); } catch (HelpException e) { exception = null; return Builtins.compileCommandInfo(e.getMessage()); } catch (Exception e) { - consoleEngine().println(e); + println(e); } return new ArrayList<>(); } @@ -182,7 +193,7 @@ private boolean isLocalCommand(String command) { public Completers.SystemCompleter compileCompleters() { Completers.SystemCompleter out = CommandRegistry.aggregateCompleters(commandRegistries); Completers.SystemCompleter local = new Completers.SystemCompleter(); - for (Map.Entry entry: commandName.entrySet()) { + for (Map.Entry entry : commandName.entrySet()) { local.add(entry.getValue(), commandExecute.get(entry.getKey()).compileCompleter().apply(entry.getValue())); } local.addAliases(aliasCommand); @@ -206,12 +217,12 @@ private Widgets.CmdDesc localCommandDescription(String command) { throw new IllegalArgumentException(); } try { - localExecute(command, new String[] {"--help"}); + localExecute(command, new String[] { "--help" }); } catch (HelpException e) { exception = null; return Builtins.compileCommandDescription(e.getMessage()); } catch (Exception e) { - consoleEngine().println(e); + println(e); } return null; } @@ -252,7 +263,7 @@ public Object invoke(String command, Object... args) throws Exception { command = ConsoleEngine.plainCommand(command); int id = registryId(command); if (id > -1) { - out = commandRegistries[id].invoke(command, args); + out = commandRegistries[id].invoke(commandSession(), command, args); } else if (isLocalCommand(command)) { String[] _args = new String[args.length]; for (int i = 0; i < args.length; i++) { @@ -263,7 +274,7 @@ public Object invoke(String command, Object... args) throws Exception { } out = localExecute(command, _args); } else if (consoleId != null) { - out = consoleEngine().invoke(command, args); + out = consoleEngine().invoke(commandSession(), command, args); } return out; } @@ -273,7 +284,7 @@ public Object execute(String command, String[] args) throws Exception { Object out = null; int id = registryId(command); if (id > -1) { - out = commandRegistries[id].execute(command, args); + out = commandRegistries[id].execute(commandSession(), command, args); } else if (isLocalCommand(command)) { out = localExecute(command, args); } @@ -284,49 +295,246 @@ public Object localExecute(String command, String[] args) throws Exception { if (!isLocalCommand(command)) { throw new IllegalArgumentException(); } - Object out = commandExecute.get(command(command)).executeFunction().apply(new Builtins.CommandInput(args)); + Object out = commandExecute.get(command(command)).executeFunction() + .apply(new Builtins.CommandInput(args, commandSession())); if (exception != null) { throw exception; } return out; } + public Terminal terminal() { + return commandSession().terminal(); + } + + private CommandSession commandSession() { + return outputStream.getCommandSession(); + } + + private static class CommandOutputStream { + private PrintStream origOut; + private PrintStream origErr; + private Terminal origTerminal; + private ByteArrayOutputStream byteOutputStream; + private FileOutputStream fileOutputStream; + private PrintStream out; + private InputStream in; + private Terminal terminal; + private String output; + private CommandRegistry.CommandSession commandSession; + private boolean redirecting = false; + + public CommandOutputStream(Terminal terminal) { + this.origOut = System.out; + this.origErr = System.err; + this.origTerminal = terminal; + this.terminal = terminal; + PrintStream ps = new PrintStream(terminal.output()); + this.commandSession = new CommandRegistry.CommandSession(terminal, terminal.input(), ps, ps); + } + + public void redirect() throws IOException { + byteOutputStream = new ByteArrayOutputStream(); + } + + public void redirect(File file, boolean append) throws IOException { + if (!file.exists()){ + try { + file.createNewFile(); + } catch(IOException e){ + (new File(file.getParent())).mkdirs(); + file.createNewFile(); + } + } + fileOutputStream = new FileOutputStream(file, append); + } + + public void open() throws IOException { + if (redirecting || (byteOutputStream == null && fileOutputStream == null)) { + return; + } + OutputStream outputStream = byteOutputStream != null ? byteOutputStream : fileOutputStream; + out = new PrintStream(outputStream); + System.setOut(out); + System.setErr(out); + in = new ByteArrayInputStream( "".getBytes() ); + Attributes attrs = new Attributes(); + if (OSUtils.IS_WINDOWS) { + attrs.setInputFlag(InputFlag.IGNCR, true); + } + terminal = TerminalBuilder.builder() + .streams(in, outputStream) + .attributes(attrs) + .type(Terminal.TYPE_DUMB).build(); + this.commandSession = new CommandRegistry.CommandSession(terminal, terminal.input(), out, out); + redirecting = true; + } + + public void flush() { + if (out == null) { + return; + } + try { + out.flush(); + if (byteOutputStream != null) { + byteOutputStream.flush(); + output = byteOutputStream.toString(); + } else if (fileOutputStream != null) { + fileOutputStream.flush(); + } + } catch (Exception e) { + + } + } + + public void close() { + if (out == null) { + return; + } + try { + in.close(); + flush(); + if (byteOutputStream != null) { + byteOutputStream.close(); + byteOutputStream = null; + } else if (fileOutputStream != null) { + fileOutputStream.close(); + fileOutputStream = null; + } + out.close(); + out = null; + } catch (Exception e) { + + } + } + + public CommandRegistry.CommandSession getCommandSession() { + return commandSession; + } + + public String getOutput() { + return output; + } + + public boolean isRedirecting() { + return redirecting; + } + + public boolean isByteStream() { + return redirecting && byteOutputStream != null; + } + + public void reset() { + if (redirecting) { + out = null; + byteOutputStream = null; + fileOutputStream = null; + output = null; + System.setOut(origOut); + System.setErr(origErr); + terminal = origTerminal; + PrintStream ps = new PrintStream(terminal.output()); + this.commandSession = new CommandRegistry.CommandSession(terminal, terminal.input(), ps, ps); + redirecting = false; + } + } + } + @Override public Object execute(String line) throws Exception { ParsedLine pl = parser.parse(line, 0, ParseContext.ACCEPT_LINE); if (pl.line().isEmpty() || pl.line().trim().startsWith("#")) { return null; } + String var = consoleId != null ? Parser.getVariable(line) : null; String cmd = ConsoleEngine.plainCommand(Parser.getCommand(pl.word())); if (consoleId != null && consoleEngine().hasAlias(cmd)) { pl = parser.parse(line.replaceFirst(cmd, consoleEngine().getAlias(cmd)), 0, ParseContext.ACCEPT_LINE); cmd = ConsoleEngine.plainCommand(Parser.getCommand(pl.word())); } - String[] argv = pl.words().subList(1, pl.words().size()).toArray(new String[0]); + File toFile = null; + boolean append = false; + List words = pl.words(); + int lastArg = words.size(); + for (int i = 1; i < words.size() - 1; i++) { + if (words.get(i).equals(">") || words.get(i).equals(">>")) { + lastArg = i; + append = words.get(i).equals(">>"); + toFile = new File(words.get(i + 1)); + break; + } + } + String rawLine = lastArg < words.size() ? words.subList(0, lastArg).stream().collect(Collectors.joining(" ")) : pl.line(); + String[] argv = words.subList(1, lastArg).toArray(new String[0]); Object out = null; exception = null; - if (isLocalCommand(cmd)) { - out = localExecute(cmd, argv); - } else { - int id = registryId(cmd); - try { + boolean statement = false; + try { + if (var != null || toFile != null) { + if (toFile != null) { + outputStream.redirect(toFile, append); + } else if (consoleId != null && !consoleEngine().isExecuting()) { + outputStream.redirect(); + } + outputStream.open(); + } + if (isLocalCommand(cmd)) { + out = localExecute(cmd, argv); + } else { + int id = registryId(cmd); if (id > -1) { if (consoleId != null) { - out = commandRegistries[id].invoke(cmd, consoleEngine().expandParameters(argv)); - out = consoleEngine().postProcess(pl.line(), out); + out = commandRegistries[id].invoke(outputStream.getCommandSession(), cmd, + consoleEngine().expandParameters(argv)); } else { - out = commandRegistries[id].execute(cmd, argv); + out = commandRegistries[id].execute(outputStream.getCommandSession(), cmd, argv); } } else if (consoleId != null) { - out = consoleEngine().execute(pl); + if (outputStream.isByteStream() && !consoleEngine().scripts().contains(cmd)) { + outputStream.close(); + outputStream.reset(); + statement = true; + } + out = consoleEngine().execute(cmd, rawLine, argv); } - } catch (HelpException e) { - consoleEngine().println(e); + } + } catch (HelpException e) { + println(e); + } finally { + if (consoleId != null && !consoleEngine().isExecuting() && !statement) { + outputStream.flush(); + out = consoleEngine().postProcess(pl.line(), out, outputStream.getOutput()); } } return out; } + public void cleanUp() { + if (outputStream.isRedirecting()) { + outputStream.close(); + outputStream.reset(); + } + if (consoleId != null) { + consoleEngine().purge(); + } + } + + @Override + public void println(Exception exception) { + if (outputStream.isRedirecting()) { + outputStream.close(); + outputStream.reset(); + } + if (consoleId != null) { + consoleEngine().putVariable("exception", exception); + Map options = new HashMap<>(); + options.put("exception", "message"); + consoleEngine().println(options, exception); + } else { + SystemRegistry.println(false, terminal(), exception); + } + } + private ConsoleEngine consoleEngine() { return consoleId != null ? (ConsoleEngine) commandRegistries[consoleId] : null; } @@ -345,7 +553,7 @@ private void printHeader(String header) { asb.append("\t"); asb.append(header, HelpException.defaultStyle().resolve(".ti")); asb.append(":"); - asb.toAttributedString().println(terminal); + asb.toAttributedString().println(terminal()); } private void printCommandInfo(String command, String info, int max) { @@ -354,8 +562,8 @@ private void printCommandInfo(String command, String info, int max) { asb.append(command, HelpException.defaultStyle().resolve(".co")); asb.append("\t"); asb.append(info); - asb.setLength(terminal.getWidth()); - asb.toAttributedString().println(terminal); + asb.setLength(terminal().getWidth()); + asb.toAttributedString().println(terminal()); } private void printCommands(Collection commands, int max) { @@ -368,8 +576,8 @@ private void printCommands(Collection commands, int max) { asb.append(c, HelpException.defaultStyle().resolve(".co")); asb.append("\t"); col += max; - if (col + max > terminal.getWidth()) { - asb.toAttributedString().println(terminal); + if (col + max > terminal().getWidth()) { + asb.toAttributedString().println(terminal()); asb = new AttributedStringBuilder().tabs(Arrays.asList(4, max + 4)); col = 0; asb.append("\t"); @@ -380,9 +588,9 @@ private void printCommands(Collection commands, int max) { } } if (!done) { - asb.toAttributedString().println(terminal); + asb.toAttributedString().println(terminal()); } - terminal.flush(); + terminal().flush(); } private String doCommandInfo(List info) { @@ -394,11 +602,8 @@ private boolean isInArgs(List args, String name) { } private Object help(Builtins.CommandInput input) { - final String[] usage = { - "help - command help", - "Usage: help [NAME...]", - " -? --help Displays command help", - }; + final String[] usage = { "help - command help", "Usage: help [NAME...]", + " -? --help Displays command help", }; Options opt = Options.compile(usage).parse(input.args()); if (opt.isSet("help")) { exception = new HelpException(opt.usage()); @@ -409,12 +614,12 @@ private Object help(Builtins.CommandInput input) { if (consoleId > -1) { commands.addAll(consoleEngine().scripts()); } - boolean withInfo = commands.size() < terminal.getHeight() || !opt.args().isEmpty() ? true : false; + boolean withInfo = commands.size() < terminal().getHeight() || !opt.args().isEmpty() ? true : false; int max = Collections.max(commands, Comparator.comparing(String::length)).length() + 1; - TreeMap builtinCommands = new TreeMap<>(); + TreeMap builtinCommands = new TreeMap<>(); for (CommandRegistry r : commandRegistries) { if (isBuiltinRegistry(r)) { - for (String c: r.commandNames()) { + for (String c : r.commandNames()) { builtinCommands.put(c, doCommandInfo(commandInfo(c))); } } @@ -450,22 +655,20 @@ private Object help(Builtins.CommandInput input) { if (consoleId > -1 && isInArgs(opt.args(), "Scripts")) { printHeader("Scripts"); if (withInfo) { - for (String c: consoleEngine().scripts()) { + for (String c : consoleEngine().scripts()) { printCommandInfo(c, doCommandInfo(commandInfo(c)), max); } } else { printCommands(consoleEngine().scripts(), max); } } + terminal().flush(); return null; } private Object exit(Builtins.CommandInput input) { - final String[] usage = { - "exit - exit from app/script", - "Usage: exit", - " -? --help Displays command help", - }; + final String[] usage = { "exit - exit from app/script", "Usage: exit", + " -? --help Displays command help", }; Options opt = Options.compile(usage).parse(input.args()); if (opt.isSet("help")) { exception = new HelpException(opt.usage()); @@ -477,12 +680,12 @@ private Object exit(Builtins.CommandInput input) { private List commandOptions(String command) { try { - localExecute(command, new String[] {"--help"}); + localExecute(command, new String[] { "--help" }); } catch (HelpException e) { exception = null; return Builtins.compileCommandOptions(e.getMessage()); } catch (Exception e) { - consoleEngine().println(e); + println(e); } return null; } @@ -504,21 +707,15 @@ private List registryNames() { private List helpCompleter(String command) { List completers = new ArrayList<>(); - completers.add(new ArgumentCompleter(NullCompleter.INSTANCE - , new OptionCompleter(new StringsCompleter(this::registryNames) - , this::commandOptions - , 1) - )); + completers.add(new ArgumentCompleter(NullCompleter.INSTANCE, + new OptionCompleter(new StringsCompleter(this::registryNames), this::commandOptions, 1))); return completers; } private List exitCompleter(String command) { List completers = new ArrayList<>(); - completers.add(new ArgumentCompleter(NullCompleter.INSTANCE - , new OptionCompleter(NullCompleter.INSTANCE - , this::commandOptions - , 1) - )); + completers.add(new ArgumentCompleter(NullCompleter.INSTANCE, + new OptionCompleter(NullCompleter.INSTANCE, this::commandOptions, 1))); return completers; } diff --git a/builtins/src/test/java/org/jline/example/Example.java b/builtins/src/test/java/org/jline/example/Example.java index eb1df7676..737f61a5d 100644 --- a/builtins/src/test/java/org/jline/example/Example.java +++ b/builtins/src/test/java/org/jline/example/Example.java @@ -295,9 +295,9 @@ public Completers.SystemCompleter compileCompleters() { return out; } - public Object execute(String command, String[] args) throws Exception { + public Object execute(CommandRegistry.CommandSession session, String command, String[] args) throws Exception { exception = null; - commandExecute.get(command(command)).execute().accept(new Builtins.CommandInput(args)); + commandExecute.get(command(command)).execute().accept(new Builtins.CommandInput(args, session)); if (exception != null) { throw exception; } @@ -722,6 +722,7 @@ public void complete(LineReader reader, ParsedLine line, List candida // // REPL-loop // + CommandRegistry.CommandSession session = new CommandRegistry.CommandSession(terminal); while (true) { try { String line = reader.readLine(prompt, rightPrompt, (MaskingCallback) null, null); @@ -752,10 +753,10 @@ public void complete(LineReader reader, ParsedLine line, List candida masterRegistry.help(); } else if (builtins.hasCommand(cmd)) { - builtins.execute(cmd, argv, System.in, System.out, System.err); + builtins.execute(session, cmd, argv); } else if (exampleCommands.hasCommand(cmd)) { - exampleCommands.execute(cmd, argv); + exampleCommands.execute(session, cmd, argv); } } catch (HelpException e) { diff --git a/demo/src/main/java/org/jline/demo/Repl.java b/demo/src/main/java/org/jline/demo/Repl.java index a7bbb0ece..e47bf200b 100644 --- a/demo/src/main/java/org/jline/demo/Repl.java +++ b/demo/src/main/java/org/jline/demo/Repl.java @@ -113,9 +113,9 @@ public Completers.SystemCompleter compileCompleters() { return out; } - public Object execute(String command, String[] args) throws Exception { + public Object execute(CommandRegistry.CommandSession session, String command, String[] args) throws Exception { exception = null; - commandExecute.get(command(command)).execute().accept(new Builtins.CommandInput(args)); + commandExecute.get(command(command)).execute().accept(new Builtins.CommandInput(args, session)); if (exception != null) { throw exception; } @@ -199,7 +199,7 @@ private void clear(Builtins.CommandInput input) { private List commandOptions(String command) { try { - execute(command, new String[] {"--help"}); + execute(new CommandRegistry.CommandSession(), command, new String[] {"--help"}); } catch (HelpException e) { return Builtins.compileCommandOptions(e.getMessage()); } catch (Exception e) { @@ -296,7 +296,7 @@ public static void main(String[] args) { consoleEngine.println(terminal.getName()+": "+terminal.getType()); while (true) { try { - scriptEngine.del("_*"); // delete temporary variables + systemRegistry.cleanUp(); // delete temporary variables and reset output streams String line = reader.readLine("groovy-repl> "); Object result = systemRegistry.execute(line); consoleEngine.println(result); @@ -308,8 +308,7 @@ public static void main(String[] args) { break; } catch (Exception e) { - consoleEngine.println(e); - scriptEngine.put("exception", e); // save exception to console variable + systemRegistry.println(e); // print exception and save it to console variable } } } diff --git a/groovy/src/main/java/org/jline/script/GroovyEngine.java b/groovy/src/main/java/org/jline/script/GroovyEngine.java index c2fe6c93d..d08c1e5b9 100644 --- a/groovy/src/main/java/org/jline/script/GroovyEngine.java +++ b/groovy/src/main/java/org/jline/script/GroovyEngine.java @@ -339,7 +339,7 @@ private List internalHighlight(Map options, Ob asb.append("\t"); } if (asb.columnLength() > width) { - asb.setLength(width); + asb.subSequence(0, width); } out.add(asb.toAttributedString()); Integer row = 0; @@ -356,7 +356,7 @@ private List internalHighlight(Map options, Ob asb2.append("\t"); } if (asb2.columnLength() > width) { - asb2.setLength(width); + asb2.subSequence(0, width); } out.add(asb2.toAttributedString()); } @@ -391,7 +391,7 @@ private List internalHighlight(Map options, Ob asb.append("\t"); } if (asb.columnLength() > width) { - asb.setLength(width); + asb.subSequence(0, width); } out.add(asb.toAttributedString()); } @@ -407,7 +407,7 @@ private List internalHighlight(Map options, Ob } asb.append(Utils.toString(o)); if (asb.columnLength() > width) { - asb.setLength(width); + asb.subSequence(0, width); } out.add(asb.toAttributedString()); } @@ -435,13 +435,16 @@ private List highlightMap(Map map, int width) for (Map.Entry entry : map.entrySet()) { AttributedStringBuilder asb = new AttributedStringBuilder().tabs(Arrays.asList(0, max + 1)); asb.append(entry.getKey(), AttributedStyle.DEFAULT.foreground(AttributedStyle.BLUE + AttributedStyle.BRIGHT)); - for (String v : Utils.toString(entry.getValue()).split("\n")) { + for (String v : Utils.toString(entry.getValue()).split("\\r?\\n")) { asb.append("\t"); asb.append(v, AttributedStyle.DEFAULT.foreground(AttributedStyle.YELLOW)); if (asb.columnLength() > width) { - asb.setLength(width); + asb.subSequence(0, width); } out.add(asb.toAttributedString()); + if (map.size() > 1) { + break; + } asb = new AttributedStringBuilder().tabs(Arrays.asList(0, max + 1)); } }