diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index d137fec62..cf5980e3c 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -1,5 +1,38 @@ # picocli Release Notes +# Picocli 3.3.0 +The picocli community is pleased to announce picocli 3.3.0. + +This release contains a bugfix for the JLine TAB completion support and error message improvements. + +This is the thirty-fourth public release. +Picocli follows [semantic versioning](http://semver.org/). + +## Table of Contents +* [New and noteworthy](#3.3.0-new) +* [Promoted features](#3.3.0-promoted) +* [Fixed issues](#3.3.0-fixes) +* [Deprecations](#3.3.0-deprecated) +* [Potential breaking changes](#3.3.0-breaking-changes) + +## New and Noteworthy +### + +## Promoted Features +Promoted features are features that were incubating in previous versions of picocli but are now supported and subject to backwards compatibility. + +No features have been promoted in this picocli release. + +## Fixed issues +- [#411] Bugfix: Completion candidates were only generated for the first option, not for subsequent options. + +## Deprecations +No features were deprecated in this release. + +## Potential breaking changes +This release has no breaking changes. + + # Picocli 3.2.0 The picocli community is pleased to announce picocli 3.2.0. diff --git a/src/main/java/picocli/AutoComplete.java b/src/main/java/picocli/AutoComplete.java index c82de72f2..0cb7e6834 100644 --- a/src/main/java/picocli/AutoComplete.java +++ b/src/main/java/picocli/AutoComplete.java @@ -579,12 +579,8 @@ public static int complete(CommandSpec spec, String[] args, int argIndex, int po CommandLine parser = new CommandLine(spec); ParseResult parseResult = parser.parseArgs(args); if (argIndex >= parseResult.tentativeMatch.size()) { - int count = parseResult.tentativeMatch.size(); - if (count == 0) { - addCandidatesForArgsFollowing(spec, candidates); - } else { - addCandidatesForArgsFollowing(parseResult.tentativeMatch.get(count - 1), candidates); - } + Object startPoint = findCompletionStartPoint(parseResult); + addCandidatesForArgsFollowing(startPoint, candidates); } else { Object obj = parseResult.tentativeMatch.get(argIndex); if (obj instanceof CommandSpec) { // subcommand @@ -615,7 +611,28 @@ public static int complete(CommandSpec spec, String[] args, int argIndex, int po spec.parser().collectErrors(reset); } } + private static Object findCompletionStartPoint(ParseResult parseResult) { + List tentativeMatches = parseResult.tentativeMatch; + for (int i = 1; i <= tentativeMatches.size(); i++) { + Object found = tentativeMatches.get(tentativeMatches.size() - i); + if (found instanceof CommandSpec) { + return found; + } + if (found instanceof ArgSpec) { + CommandLine.Range arity = ((ArgSpec) found).arity(); + if (i < arity.min) { + return found; // not all parameters have been supplied yet + } else { + return findCommandFor((ArgSpec) found, parseResult.commandSpec()); + } + } + } + return parseResult.commandSpec(); + } + private static CommandSpec findCommandFor(ArgSpec arg, CommandSpec cmd) { + return (arg instanceof OptionSpec) ? findCommandFor((OptionSpec) arg, cmd) : findCommandFor((PositionalParamSpec) arg, cmd); + } private static CommandSpec findCommandFor(OptionSpec option, CommandSpec commandSpec) { OptionSpec found = commandSpec.findOption(option.longestName()); if (found != null) { return commandSpec; } diff --git a/src/test/java/picocli/AutoCompleteTest.java b/src/test/java/picocli/AutoCompleteTest.java index a63c461ca..d4a54f72b 100644 --- a/src/test/java/picocli/AutoCompleteTest.java +++ b/src/test/java/picocli/AutoCompleteTest.java @@ -508,32 +508,55 @@ public void testComplete() { CommandSpec spec = hierarchy.getCommandSpec(); spec.parser().collectErrors(true); - List result = new ArrayList(); - String[] args = new String[] {}; - - test(spec, a(), 0, 0, 500, l("--help", "--version", "-V", "-h", "sub1", "sub2")); - test(spec, a("-"), 0, 0, 500, l("--help", "--version", "-V", "-h", "sub1", "sub2")); - test(spec, a("-"), 0, 1, 500, l("-help", "-version", "V", "h")); - test(spec, a("-h"), 0, 1, 500, l("-help", "-version", "V", "h")); - test(spec, a("-h"), 0, 2, 500, l("")); - test(spec, a("s"), 0, 1, 500, l("ub1", "ub2")); - test(spec, a("sub1"), 1, 0, 500, l("--candidates", "--num", "--str")); - test(spec, a("sub1", "-"), 1, 0, 500, l("--candidates", "--num", "--str")); - test(spec, a("sub1", "-"), 1, 1, 500, l("-candidates", "-num", "-str")); - test(spec, a("sub1", "--"), 1, 1, 500, l("-candidates", "-num", "-str")); - test(spec, a("sub1", "--"), 1, 2, 500, l("candidates", "num", "str")); - test(spec, a("sub1", "--c"), 1, 2, 500, l("candidates", "num", "str")); - test(spec, a("sub1", "--c"), 1, 3, 500, l("andidates")); - test(spec, a("sub1", "--candidates"), 2, 0, 500, l("a", "b", "c")); - test(spec, a("sub1", "--num"), 2, 0, 500, l()); - test(spec, a("sub1", "--str"), 2, 0, 500, l()); - test(spec, a("sub2"), 1, 0, 500, l("--directory", "--num2", "-d", "subsub1", "subsub2")); - test(spec, a("sub2", "-"), 1, 1, 500, l("-directory", "-num2", "d")); - test(spec, a("sub2", "-d"), 2, 0, 500, l()); - test(spec, a("sub2", "subsub1"), 2, 0, 500, l("--host", "-h")); - test(spec, a("sub2", "subsub2"), 2, 0, 500, l("--timeUnit", "--timeout", "-t", "-u", "a", "b", "c")); - test(spec, a("sub2", "subsub2", "-"), 2, 1, 500, l("-timeUnit", "-timeout", "t", "u")); - test(spec, a("sub2", "subsub2", "a"), 2, 1, 500, l("")); + int cur = 500; + + test(spec, a(), 0, 0, cur, l("--help", "--version", "-V", "-h", "sub1", "sub2")); + test(spec, a("-"), 0, 0, cur, l("--help", "--version", "-V", "-h", "sub1", "sub2")); + test(spec, a("-"), 0, 1, cur, l("-help", "-version", "V", "h")); + test(spec, a("-h"), 0, 1, cur, l("-help", "-version", "V", "h")); + test(spec, a("-h"), 0, 2, cur, l("")); + test(spec, a("s"), 0, 1, cur, l("ub1", "ub2")); + test(spec, a("sub1"), 1, 0, cur, l("--candidates", "--num", "--str")); + test(spec, a("sub1", "-"), 1, 0, cur, l("--candidates", "--num", "--str")); + test(spec, a("sub1", "-"), 1, 1, cur, l("-candidates", "-num", "-str")); + test(spec, a("sub1", "--"), 1, 1, cur, l("-candidates", "-num", "-str")); + test(spec, a("sub1", "--"), 1, 2, cur, l("candidates", "num", "str")); + test(spec, a("sub1", "--c"), 1, 2, cur, l("candidates", "num", "str")); + test(spec, a("sub1", "--c"), 1, 3, cur, l("andidates")); + test(spec, a("sub1", "--candidates"), 2, 0, cur, l("a", "b", "c")); + test(spec, a("sub1", "--candidates", "a"), 2, 1, cur, l("")); + test(spec, a("sub1", "--candidates", "a"), 3, 0, cur, l("--candidates", "--num", "--str")); + test(spec, a("sub1", "--candidates", "a", "-"), 3, 1, cur, l("-candidates", "-num", "-str")); + test(spec, a("sub1", "--candidates", "a", "--"), 3, 2, cur, l("candidates", "num", "str")); + test(spec, a("sub1", "--num"), 2, 0, cur, l()); + test(spec, a("sub1", "--str"), 2, 0, cur, l()); + test(spec, a("sub2"), 1, 0, cur, l("--directory", "--num2", "-d", "subsub1", "subsub2")); + test(spec, a("sub2", "-"), 1, 1, cur, l("-directory", "-num2", "d")); + test(spec, a("sub2", "-d"), 2, 0, cur, l()); + test(spec, a("sub2", "-d", "/"), 3, 0, cur, l("--directory", "--num2", "-d", "subsub1", "subsub2")); + test(spec, a("sub2", "-d", "/", "-"), 3, 1, cur, l("-directory", "-num2", "d")); + test(spec, a("sub2", "-d", "/", "--"), 3, 2, cur, l("directory", "num2")); + test(spec, a("sub2", "-d", "/", "--n"), 3, 3, cur, l("um2")); + test(spec, a("sub2", "-d", "/", "--num2"), 3, 6, cur, l("")); + test(spec, a("sub2", "-d", "/", "--num2"), 4, 0, cur, l()); + test(spec, a("sub2", "-d", "/", "--num2", "0"), 4, 1, cur, l()); + test(spec, a("sub2", "-d", "/", "--num2", "0"), 5, 0, cur, l("--directory", "--num2", "-d", "subsub1", "subsub2")); + test(spec, a("sub2", "-d", "/", "--num2", "0", "s"), 5, 1, cur, l("ubsub1", "ubsub2")); + test(spec, a("sub2", "subsub1"), 2, 0, cur, l("--host", "-h")); + test(spec, a("sub2", "subsub2"), 2, 0, cur, l("--timeUnit", "--timeout", "-t", "-u", "a", "b", "c")); + test(spec, a("sub2", "subsub2", "-"), 2, 1, cur, l("-timeUnit", "-timeout", "t", "u")); + test(spec, a("sub2", "subsub2", "-t"), 2, 2, cur, l("")); + test(spec, a("sub2", "subsub2", "-t"), 3, 0, cur, l()); + test(spec, a("sub2", "subsub2", "-t", "0"), 3, 1, cur, l()); + test(spec, a("sub2", "subsub2", "-t", "0"), 4, 0, cur, l("--timeUnit", "--timeout", "-t", "-u", "a", "b", "c")); + test(spec, a("sub2", "subsub2", "-t", "0", "-"), 4, 1, cur, l("-timeUnit", "-timeout", "t", "u")); + test(spec, a("sub2", "subsub2", "-t", "0", "--"), 4, 2, cur, l("timeUnit", "timeout")); + test(spec, a("sub2", "subsub2", "-t", "0", "--t"), 4, 3, cur, l("imeUnit", "imeout")); + test(spec, a("sub2", "subsub2", "-t", "0", "-u"), 4, 2, cur, l("")); + test(spec, a("sub2", "subsub2", "-t", "0", "-u"), 5, 0, cur, timeUnitValues()); + test(spec, a("sub2", "subsub2", "-t", "0", "-u", "M"),5, 1, cur, l("ICROSECONDS", "ILLISECONDS", "INUTES")); + test(spec, a("sub2", "subsub2", "a"), 2, 1, cur, l("")); + test(spec, a("sub2", "subsub2", "a"), 3, 0, cur, l("--timeUnit", "--timeout", "-t", "-u", "a", "b", "c")); } private static void test(CommandSpec spec, String[] args, int argIndex, int positionInArg, int cursor, List expected) { @@ -552,6 +575,12 @@ private static List l(CharSequence... args) { return Arrays.asList(args); } + private static List timeUnitValues() { + List result = new ArrayList(); + for (TimeUnit tu : TimeUnit.values()) { result.add(tu.toString()); } + return result; + } + static class CharSequenceSort implements Comparator { public int compare(CharSequence left, CharSequence right) { return left.toString().compareTo(right.toString());