diff --git a/builtins/pom.xml b/builtins/pom.xml index 94bd6ed97..95c78a433 100644 --- a/builtins/pom.xml +++ b/builtins/pom.xml @@ -60,6 +60,7 @@ **/TTop.java + **/Builtins.java -Xlint:all,-options @@ -77,6 +78,7 @@ **/TTop.java + **/Builtins.java -Xlint:all,-options diff --git a/builtins/src/main/java/org/jline/builtins/Builtins.java b/builtins/src/main/java/org/jline/builtins/Builtins.java new file mode 100644 index 000000000..83eab41e8 --- /dev/null +++ b/builtins/src/main/java/org/jline/builtins/Builtins.java @@ -0,0 +1,441 @@ +/* + * Copyright (c) 2002-2019, the original author or authors. + * + * This software is distributable under the BSD license. See the terms of the + * BSD license in the documentation provided with this software. + * + * https://opensource.org/licenses/BSD-3-Clause + */ +package org.jline.builtins; + +import java.io.InputStream; +import java.io.PrintStream; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +import org.jline.builtins.Commands; +import org.jline.builtins.Completers.FilesCompleter; +import org.jline.builtins.Completers.SystemCompleter; +import org.jline.builtins.TTop; +import org.jline.builtins.Options.HelpException; +import org.jline.builtins.Widgets.ArgDesc; +import org.jline.builtins.Widgets.CmdDesc; +import org.jline.reader.Completer; +import org.jline.reader.ConfigurationPath; +import org.jline.reader.LineReader; +import org.jline.reader.Widget; +import org.jline.reader.LineReader.Option; +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.Terminal; +import org.jline.utils.AttributedString; + +/** + * Builtins: create tab completers, execute and create descriptions for builtins commands. + * + * @author Matti Rinta-Nikkola + */ +public class Builtins { + public enum Command {NANO + , LESS + , HISTORY + , WIDGET + , KEYMAP + , SETOPT + , SETVAR + , UNSETOPT + , TTOP}; + private ConfigurationPath configPath; + private final Function widgetCreator; + private final Supplier workDir; + private Map commandName = new HashMap<>(); + private Map nameCommand = new HashMap<>(); + private Map aliasCommand = new HashMap<>(); + private final Map commandExecute = new HashMap<>(); + private LineReader reader; + private Exception exception; + + public Builtins(Path workDir, ConfigurationPath configPath, Function widgetCreator) { + this(null, () -> workDir, configPath, widgetCreator); + } + + public Builtins(Set commands, Path workDir, ConfigurationPath configpath, Function widgetCreator) { + this(commands, () -> workDir, configpath, widgetCreator); + } + + public Builtins(Supplier workDir, ConfigurationPath configPath, Function widgetCreator) { + this(null, workDir, configPath, widgetCreator); + } + + public Builtins(Set commands, Supplier workDir, ConfigurationPath configpath, Function widgetCreator) { + this.configPath = configpath; + this.widgetCreator = widgetCreator; + this.workDir = workDir; + Set cmds = new HashSet<>(); + if (commands == null) { + cmds = new HashSet<>(EnumSet.allOf(Command.class)); + } else { + cmds = new HashSet<>(commands); + } + for (Command c: cmds) { + commandName.put(c, c.name().toLowerCase()); + } + doNameCommand(); + commandExecute.put(Command.NANO, new CommandMethods(this::nano, this::nanoCompleter)); + commandExecute.put(Command.LESS, new CommandMethods(this::less, this::lessCompleter)); + commandExecute.put(Command.HISTORY, new CommandMethods(this::history, this::historyCompleter)); + commandExecute.put(Command.WIDGET, new CommandMethods(this::widget, this::widgetCompleter)); + commandExecute.put(Command.KEYMAP, new CommandMethods(this::keymap, this::keymapCompleter)); + commandExecute.put(Command.SETOPT, new CommandMethods(this::setopt, this::setoptCompleter)); + commandExecute.put(Command.SETVAR, new CommandMethods(this::setvar, this::setvarCompleter)); + commandExecute.put(Command.UNSETOPT, new CommandMethods(this::unsetopt, this::unsetoptCompleter)); + commandExecute.put(Command.TTOP, new CommandMethods(this::ttop, this::ttopCompleter)); + } + + private void doNameCommand() { + nameCommand = commandName.entrySet() + .stream() + .collect(Collectors.toMap(Map.Entry::getValue, Map.Entry::getKey)); + } + + public void setLineReader(LineReader reader) { + this.reader = reader; + } + + public void rename(Command command, String newName) { + if (nameCommand.containsKey(newName)) { + throw new IllegalArgumentException("Duplicate command name!"); + } else if (!commandName.containsKey(command)) { + throw new IllegalArgumentException("Command does not exists!"); + } + commandName.put(command, newName); + doNameCommand(); + } + + public void alias(String alias, String command) { + if (!nameCommand.keySet().contains(command)) { + throw new IllegalArgumentException("Command does not exists!"); + } + aliasCommand.put(alias, command); + } + + public boolean hasCommand(String name) { + if (nameCommand.containsKey(name) || aliasCommand.containsKey(name)) { + return true; + } + return false; + } + + public SystemCompleter compileCompleters() { + SystemCompleter out = new SystemCompleter(); + for (Map.Entry entry: commandName.entrySet()) { + out.add(entry.getValue(), commandExecute.get(entry.getKey()).compileCompleter().apply(entry.getValue())); + } + out.addAliases(aliasCommand); + return out; + } + + private Command command(String name) { + Command out = null; + if (!hasCommand(name)) { + throw new IllegalArgumentException("Command does not exists!"); + } + if (aliasCommand.containsKey(name)) { + name = aliasCommand.get(name); + } + if (nameCommand.containsKey(name)) { + out = nameCommand.get(name); + } else { + throw new IllegalArgumentException("Command does not exists!"); + } + return out; + } + + private void execute(String command, List args) throws Exception { + execute(command, args, System.in, System.out, System.err); + } + + 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 { + exception = null; + commandExecute.get(command(command)).execute().accept(new CommandInput(args, in, out, err)); + if (exception != null) { + throw exception; + } + } + + public CmdDesc commandDescription(String command) { + CmdDesc out = null; + List args = Arrays.asList("--help"); + try { + execute(command, args); + } catch (HelpException e) { + List main = new ArrayList<>(); + Map> options = new HashMap<>(); + String[] msg = e.getMessage().replaceAll("\r\n", "\n").replaceAll("\r", "\n").split("\n"); + String prevOpt = null; + boolean mainDone = false; + boolean start = false; + for (String s: msg) { + if (!start) { + if (s.trim().startsWith("Usage: ")) { + s = s.split("Usage:")[1]; + start = true; + } else { + continue; + } + } + if (s.matches("^\\s+-.*$")) { + mainDone = true; + int ind = s.lastIndexOf(" "); + if (ind > 0) { + String o = s.substring(0, ind); + String d = s.substring(ind); + if (o.trim().length() > 0) { + prevOpt = o.trim(); + options.put(prevOpt, new ArrayList<>(Arrays.asList(new AttributedString(d.trim())))); + } + } + } else if (s.matches("^[\\s]{20}.*$") && prevOpt != null && options.containsKey(prevOpt)) { + int ind = s.lastIndexOf(" "); + if (ind > 0) { + options.get(prevOpt).add(new AttributedString(s.substring(ind).trim())); + } + } else { + prevOpt = null; + } + if (!mainDone) { + main.add(new AttributedString(s.trim())); + } + } + out = new CmdDesc(main, ArgDesc.doArgNames(Arrays.asList("[pN...]")), options); + } catch (Exception e) { + + } + return out; + } + + 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()); + } catch (Exception e) { + this.exception = e; + } + } + + private void nano(CommandInput input) { + try { + Commands.nano(terminal(), input.out(), input.err(), workDir.get(), input.args(), configPath); + } catch (Exception e) { + this.exception = e; + } + } + + private void history(CommandInput input) { + try { + Commands.history(reader, input.out(), input.err(), workDir.get(), input.args()); + } catch (Exception e) { + this.exception = e; + } + } + + private void widget(CommandInput input) { + try { + Commands.widget(reader, input.out(), input.err(), widgetCreator, input.args()); + } catch (Exception e) { + this.exception = e; + } + } + + private void keymap(CommandInput input) { + try { + Commands.keymap(reader, input.out(), input.err(), input.args()); + } catch (Exception e) { + this.exception = e; + } + } + + private void setopt(CommandInput input) { + try { + Commands.setopt(reader, input.out(), input.err(), input.args()); + } catch (Exception e) { + this.exception = e; + } + } + + private void setvar(CommandInput input) { + try { + Commands.setvar(reader, input.out(), input.err(), input.args()); + } catch (Exception e) { + this.exception = e; + } + } + + private void unsetopt(CommandInput input) { + try { + Commands.unsetopt(reader, input.out(), input.err(), input.args()); + } catch (Exception e) { + this.exception = e; + } + } + + private void ttop(CommandInput input) { + try { + TTop.ttop(terminal(), input.out(), input.err(), input.args()); + } catch (Exception e) { + this.exception = e; + } + } + + private List unsetOptions(boolean set) { + List out = new ArrayList<>(); + for (Option option : Option.values()) { + if (set == (reader.isSet(option) == option.isDef())) { + out.add((option.isDef() ? "no-" : "") + option.toString().toLowerCase().replace('_', '-')); + } + } + return out; + } + + private Set allWidgets() { + Set out = new HashSet<>(); + for (String s: reader.getWidgets().keySet()) { + out.add(s); + out.add(reader.getWidgets().get(s).toString()); + } + return out; + } + + private List nanoCompleter(String name) { + List completers = new ArrayList<>(); + completers.add(new ArgumentCompleter(new StringsCompleter(name), new FilesCompleter(workDir.get(), true))); + return completers; + } + + private List lessCompleter(String name) { + List completers = new ArrayList<>(); + completers.add(new ArgumentCompleter(new StringsCompleter(name), new FilesCompleter(workDir.get(), true))); + return completers; + } + + private List historyCompleter(String name) { + List completers = new ArrayList<>(); + completers.add(new ArgumentCompleter(new StringsCompleter(name), new NullCompleter())); + completers.add(new ArgumentCompleter(new StringsCompleter(name) + , new StringsCompleter(Arrays.asList("-A", "-W", "-R")), new FilesCompleter(workDir.get(), true), new NullCompleter())); + return completers; + } + + private List widgetCompleter(String name) { + List completers = new ArrayList<>(); + completers.add(new ArgumentCompleter(new StringsCompleter(name) + , new StringsCompleter("-A"), new StringsCompleter(() -> allWidgets()) + , new StringsCompleter(() -> reader.getWidgets().keySet()), new NullCompleter())); + completers.add(new ArgumentCompleter(new StringsCompleter(name) + , new StringsCompleter("-D"), new StringsCompleter(() -> reader.getWidgets().keySet()))); + return completers; + } + + private List keymapCompleter(String name) { + List completers = new ArrayList<>(); + completers.add(new ArgumentCompleter(new StringsCompleter(name), new NullCompleter())); + return completers; + } + + private List setvarCompleter(String name) { + List completers = new ArrayList<>(); + completers.add(new ArgumentCompleter(new StringsCompleter(name) + , new StringsCompleter(() -> reader.getVariables().keySet()), new NullCompleter())); + return completers; + } + + private List setoptCompleter(String name) { + List completers = new ArrayList<>(); + completers.add(new ArgumentCompleter(new StringsCompleter(name) + , new StringsCompleter(() -> unsetOptions(true)))); + return completers; + } + + private List unsetoptCompleter(String name) { + List completers = new ArrayList<>(); + completers.add(new ArgumentCompleter(new StringsCompleter(name) + , new StringsCompleter(() -> unsetOptions(false)))); + return completers; + } + + private List ttopCompleter(String name) { + List completers = new ArrayList<>(); + completers.add(new ArgumentCompleter(new StringsCompleter(name), new NullCompleter())); + return completers; + } + + private class CommandInput{ + String[] args; + InputStream in; + PrintStream out; + PrintStream err; + + public CommandInput(String[] args, InputStream in, PrintStream out, PrintStream err) { + this.args = args; + this.in = in; + this.out = out; + this.err = err; + } + + public String[] args() { + return args; + } + + public InputStream in() { + return in; + } + + public PrintStream out() { + return out; + } + + public PrintStream err() { + return err; + } + + } + + private class CommandMethods { + Consumer execute; + Function> compileCompleter; + + public CommandMethods(Consumer execute, Function> compileCompleter) { + this.execute = execute; + this.compileCompleter = compileCompleter; + } + + public Consumer execute() { + return execute; + } + + public Function> compileCompleter() { + return compileCompleter; + } + } + +} + diff --git a/builtins/src/main/java/org/jline/builtins/Completers.java b/builtins/src/main/java/org/jline/builtins/Completers.java index f89bf273f..2a5716116 100644 --- a/builtins/src/main/java/org/jline/builtins/Completers.java +++ b/builtins/src/main/java/org/jline/builtins/Completers.java @@ -20,6 +20,7 @@ import java.util.Collection; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -30,6 +31,9 @@ import org.jline.reader.Candidate; import org.jline.reader.LineReader; import org.jline.reader.LineReader.Option; +import org.jline.reader.impl.completer.AggregateCompleter; +import org.jline.reader.impl.completer.ArgumentCompleter; +import org.jline.reader.impl.completer.StringsCompleter; import org.jline.reader.ParsedLine; import org.jline.terminal.Terminal; import org.jline.utils.AttributedStringBuilder; @@ -562,4 +566,116 @@ public int cursor() { } } } + + public static class SystemCompleter implements org.jline.reader.Completer { + private Map> completers = new HashMap<>(); + private Map aliasCommand = new HashMap<>(); + private StringsCompleter commands; + private boolean compiled = false; + + public SystemCompleter() {} + + @Override + public void complete(LineReader reader, ParsedLine commandLine, List candidates) { + if (!compiled) { + throw new IllegalStateException(); + } + assert commandLine != null; + assert candidates != null; + if (commandLine.words().size() > 0) { + if (commandLine.words().size() == 1) { + commands.complete(reader, commandLine, candidates); + } else if (command(commandLine.words().get(0)) != null) { + completers.get(command(commandLine.words().get(0))).get(0).complete(reader, commandLine, candidates); + } + } + } + + public boolean isCompiled() { + return compiled; + } + + private String command(String cmd) { + String out = null; + if (completers.containsKey(cmd)) { + out = cmd; + } else if (aliasCommand.containsKey(cmd)) { + out = aliasCommand.get(cmd); + } + return out; + } + + public void add(String command, List completers) { + for (org.jline.reader.Completer c : completers) { + add(command, c); + } + } + + public void add(List commands, org.jline.reader.Completer completer) { + for (String c: commands) { + add(c, completer); + } + } + + public void add(String command, org.jline.reader.Completer completer) { + if (compiled) { + throw new IllegalStateException(); + } + if (!completers.containsKey(command)) { + completers.put(command, new ArrayList()); + } + if (completer instanceof ArgumentCompleter) { + ((ArgumentCompleter) completer).setStrictCommand(false); + } + completers.get(command).add(completer); + } + + public void add(SystemCompleter other) { + if (other.isCompiled()) { + throw new IllegalStateException(); + } + for (Map.Entry> entry: other.getCompleters().entrySet()) { + for (org.jline.reader.Completer c: entry.getValue()) { + add(entry.getKey(), c); + } + } + addAliases(other.getAliases()); + } + + public void addAliases(Map aliasCommand) { + if (compiled) { + throw new IllegalStateException(); + } + this.aliasCommand.putAll(aliasCommand); + } + + public Map getAliases() { + return aliasCommand; + } + + public void compile() { + if (compiled) { + return; + } + Map> compiledCompleters = new HashMap<>(); + for (Map.Entry> entry: completers.entrySet()) { + if (entry.getValue().size() == 1) { + compiledCompleters.put(entry.getKey(), entry.getValue()); + } else { + compiledCompleters.put(entry.getKey(), new ArrayList()); + compiledCompleters.get(entry.getKey()).add(new AggregateCompleter(entry.getValue())); + } + } + completers = compiledCompleters; + Set cmds = new HashSet<>(completers.keySet()); + cmds.addAll(aliasCommand.keySet()); + commands = new StringsCompleter(cmds); + compiled = true; + } + + public Map> getCompleters() { + 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 3392d9054..996378447 100644 --- a/builtins/src/test/java/org/jline/example/Example.java +++ b/builtins/src/test/java/org/jline/example/Example.java @@ -22,9 +22,12 @@ import java.util.regex.Pattern; import java.util.function.Supplier; +import org.jline.builtins.Builtins; +import org.jline.builtins.Builtins.Command; import org.jline.builtins.Commands; import org.jline.builtins.Completers; import org.jline.builtins.Completers.TreeCompleter; +import org.jline.builtins.Completers.SystemCompleter; import org.jline.builtins.Options.HelpException; import org.jline.builtins.TTop; import org.jline.builtins.Widgets.AutopairWidgets; @@ -104,17 +107,17 @@ public static void help() { , " less file pager" , " nano nano editor" , " setopt set options" + , " setvar set lineReader variable" , " tmux UNAVAILABLE" , " ttop display and update sorted information about threads" , " unsetopt unset options" - , " widget ~UNAVAILABLE" + , " widget ~UNAVAILABLE, has been created without widgetCreator" , " Example:" , " autopair toggle brackets/quotes autopair key bindings" , " autosuggestion history, completer, tailtip [tailtip|completer|combined] or none" , " cls clear screen" , " help list available commands" , " exit exit from example app" - , " set set lineReader variable" , " sleep sleep 3 seconds" , " testkey display key events" , " tput set terminal capability" @@ -165,70 +168,18 @@ private static Map compileTailTips() { return tailTips; } - private static class Executor { - LineReader reader; - Terminal terminal; + private static class DescriptionGenerator { + Builtins builtins; - public Executor(LineReader reader) { - this.reader = reader; - this.terminal = reader.getTerminal(); - } - - public boolean execute(String command, String[] argv) throws Exception { - boolean out = true; - if ("tmux".equals(command)) { - Commands.tmux(terminal, System.out, System.err, - null, //Supplier getter, - null, //Consumer setter, - null, //Consumer runner, - argv); - } - else if ("nano".equals(command)) { - Commands.nano(terminal, System.out, System.err, - Paths.get(""), - argv); - } - else if ("less".equals(command)) { - Commands.less(terminal, System.in, System.out, System.err, - Paths.get(""), - argv); - } - else if ("history".equals(command)) { - Commands.history(reader, System.out, System.err, Paths.get(""),argv); - } - else if ("complete".equals(command)) { - Commands.complete(reader, System.out, System.err, - null, // Map> completions, - argv); - } - else if ("widget".equals(command)) { - Commands.widget(reader, System.out, System.err, - null, //Function widgetCreator, - argv); - } - else if ("keymap".equals(command)) { - Commands.keymap(reader, System.out, System.err, argv); - } - else if ("setopt".equals(command)) { - Commands.setopt(reader, System.out, System.err, argv); - } - else if ("unsetopt".equals(command)) { - Commands.unsetopt(reader, System.out, System.err, argv); - } - else if ("ttop".equals(command)) { - TTop.ttop(terminal, System.out, System.err, argv); - } - else { - out = false; - } - return out; + public DescriptionGenerator(Builtins builtins) { + this.builtins = builtins; } CmdDesc commandDescription(CmdLine line) { CmdDesc out = null; switch (line.getDescriptionType()) { case COMMAND: - out = commandDescription(line.getArgs().get(0)); + out = builtins.commandDescription(line.getArgs().get(0)); break; case METHOD: out = methodDescription(line); @@ -266,56 +217,6 @@ private CmdDesc methodDescription(CmdLine line) { return new CmdDesc(mainDesc, new ArrayList<>(), new HashMap<>()); } - private CmdDesc commandDescription(String command) { - CmdDesc out = null; - String[] argv = {"--help"}; - try { - execute(command, argv); - } catch (HelpException e) { - List main = new ArrayList<>(); - Map> options = new HashMap<>(); - String[] msg = e.getMessage().replaceAll("\r\n", "\n").replaceAll("\r", "\n").split("\n"); - String prevOpt = null; - boolean mainDone = false; - boolean start = false; - for (String s: msg) { - if (!start) { - if (s.trim().startsWith("Usage: ")) { - s = s.split("Usage:")[1]; - start = true; - } else { - continue; - } - } - if (s.matches("^\\s+-.*$")) { - mainDone = true; - int ind = s.lastIndexOf(" "); - if (ind > 0) { - String o = s.substring(0, ind); - String d = s.substring(ind); - if (o.trim().length() > 0) { - prevOpt = o.trim(); - options.put(prevOpt, new ArrayList<>(Arrays.asList(new AttributedString(d.trim())))); - } - } - } else if (s.matches("^[\\s]{20}.*$") && prevOpt != null && options.containsKey(prevOpt)) { - int ind = s.lastIndexOf(" "); - if (ind > 0) { - options.get(prevOpt).add(new AttributedString(s.substring(ind).trim())); - } - } else { - prevOpt = null; - } - if (!mainDone) { - main.add(new AttributedString(s.trim())); - } - } - out = new CmdDesc(main, ArgDesc.doArgNames(Arrays.asList("[pN...]")), options); - } catch (Exception e) { - - } - return out; - } } private static class ReaderOptions { @@ -539,19 +440,30 @@ public void complete(LineReader reader, ParsedLine line, List candida } } } + Builtins builtins = new Builtins(Paths.get(""), null, null); + builtins.rename(Command.TTOP, "top"); + builtins.alias("zle", "widget"); + builtins.alias("bindkey", "keymap"); + SystemCompleter systemCompleter = builtins.compileCompleters(); + systemCompleter.compile(); + List completers = new ArrayList<>(); + completers.add(systemCompleter); + completers.add(completer); + AggregateCompleter finalCompleter = new AggregateCompleter(completers); Terminal terminal = builder.build(); System.out.println(terminal.getName()+": "+terminal.getType()); System.out.println("\nhelp: list available commands"); LineReader reader = LineReaderBuilder.builder() .terminal(terminal) - .completer(completer) + .completer(finalCompleter) .parser(parser) .variable(LineReader.SECONDARY_PROMPT_PATTERN, "%M%P > ") .variable(LineReader.INDENTATION, 2) .option(Option.INSERT_BRACKET, true) .build(); - Executor executor = new Executor(reader); + builtins.setLineReader(reader); + DescriptionGenerator descritionGenerator = new DescriptionGenerator(builtins); readerOptions.setReader(reader); AutopairWidgets autopairWidgets = new AutopairWidgets(reader); AutosuggestionWidgets autosuggestionWidgets = new AutosuggestionWidgets(reader); @@ -559,7 +471,7 @@ public void complete(LineReader reader, ParsedLine line, List candida if (argument) { tailtipWidgets = new TailTipWidgets(reader, compileTailTips(), 5, TipType.COMPLETER); } else { - tailtipWidgets = new TailTipWidgets(reader, executor::commandDescription, 5, TipType.COMPLETER); + tailtipWidgets = new TailTipWidgets(reader, descritionGenerator::commandDescription, 5, TipType.COMPLETER); } if (timer) { @@ -626,15 +538,15 @@ public void complete(LineReader reader, ParsedLine line, List candida if ("help".equals(pl.word()) || "?".equals(pl.word())) { help(); } - else if (executor.execute(pl.word(), argv)) { - // built-in command has been executed + else if (builtins.hasCommand(pl.word())) { + builtins.execute(pl.word(), argv, System.in, System.out, System.err); } - else if ("set".equals(pl.word())) { - if (argv.length == 2) { - reader.setVariable(argv[0], argv[1]); - } else { - terminal.writer().println("Usage: set "); - } + else if ("tmux".equals(pl.word())) { + Commands.tmux(terminal, System.out, System.err, + null, //Supplier getter, + null, //Consumer setter, + null, //Consumer runner, + argv); } else if ("tput".equals(pl.word())) { if (argv.length == 1) { diff --git a/reader/src/main/java/org/jline/reader/impl/completer/ArgumentCompleter.java b/reader/src/main/java/org/jline/reader/impl/completer/ArgumentCompleter.java index c9c2cec18..653b08823 100644 --- a/reader/src/main/java/org/jline/reader/impl/completer/ArgumentCompleter.java +++ b/reader/src/main/java/org/jline/reader/impl/completer/ArgumentCompleter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2002-2018, the original author or authors. + * Copyright (c) 2002-2019, the original author or authors. * * This software is distributable under the BSD license. See the terms of the * BSD license in the documentation provided with this software. @@ -34,6 +34,7 @@ public class ArgumentCompleter implements Completer private final List completers = new ArrayList<>(); private boolean strict = true; + private boolean strictCommand = true; /** * Create a new completer. @@ -64,6 +65,15 @@ public void setStrict(final boolean strict) { this.strict = strict; } + /** + * If true, a completion at argument index N will only succeed + * if all the completions from 1-(N-1) also succeed. + * + * @param strictCommand the strictCommand flag + */ + public void setStrictCommand(final boolean strictCommand) { + this.strictCommand = strictCommand; + } /** * Returns whether a completion at argument index N will success * if all the completions from arguments 0-(N-1) also succeed. @@ -104,8 +114,12 @@ public void complete(LineReader reader, ParsedLine line, final List c } // ensure that all the previous completers are successful before allowing this completer to pass (only if strict). - for (int i = 0; isStrict() && (i < line.wordIndex()); i++) { - Completer sub = completers.get(i >= completers.size() ? (completers.size() - 1) : i); + for (int i = strictCommand ? 0 : 1; isStrict() && (i < line.wordIndex()); i++) { + int idx = i >= completers.size() ? (completers.size() - 1) : i; + if (idx == 0 && !strictCommand) { + continue; + } + Completer sub = completers.get(idx); List args = line.words(); String arg = (args == null || i >= args.size()) ? "" : args.get(i).toString();