Skip to content

Commit

Permalink
Fixes #1754
Browse files Browse the repository at this point in the history
 - Both positional params and option candidates are now stored in the autocomplete script as an array.
 - By setting `IFS=$'\n'` for those scenarios, each array entry is treated as a single candidate,
allowing to honor any spaces in the original suggestions coded by the user.
  • Loading branch information
jsotuyod authored and remkop committed Oct 31, 2022
1 parent 0207de4 commit 8c471fe
Show file tree
Hide file tree
Showing 6 changed files with 144 additions and 101 deletions.
16 changes: 10 additions & 6 deletions src/main/java/picocli/AutoComplete.java
Expand Up @@ -716,16 +716,16 @@ private static String generateFunctionForCommand(String functionName, String com

private static void generatePositionParamCompletionCandidates(StringBuilder buff, PositionalParamSpec f) {
String paramName = bashify(f.paramLabel());
buff.append(format(" local %s_pos_param_args=\"%s\" # %d-%d values\n",
buff.append(format(" local %s_pos_param_args=(\"%s\") # %d-%d values\n",
paramName,
concat(" ", extract(f.completionCandidates())).trim(),
concat("\" \"", extract(f.completionCandidates())).trim(),
f.index().min(), f.index().max()));
}

private static void generateCompletionCandidates(StringBuilder buff, OptionSpec f) {
buff.append(format(" local %s_option_args=\"%s\" # %s values\n",
buff.append(format(" local %s_option_args=(\"%s\") # %s values\n",
bashify(f.paramLabel()),
concat(" ", extract(f.completionCandidates())).trim(),
concat("\" \"", extract(f.completionCandidates())).trim(),
f.longestName()));
}
private static List<String> extract(Iterable<String> generator) {
Expand All @@ -750,7 +750,9 @@ private static String generatePositionalParamsCases(List<PositionalParamSpec> po
int max = param.index().max();
if (param.completionCandidates() != null) {
buff.append(format("%s %s (( currIndex >= %d && currIndex <= %d )); then\n", indent, ifOrElif, min, max));
buff.append(format("%s positionals=$( compgen -W \"$%s_pos_param_args\" -- \"%s\" )\n", indent, paramName, currWord));
buff.append(format("%s local IFS=$'\\n'\n", indent));
buff.append(format("%s positionals=$( compgen -W \"${%s_pos_param_args[*]}\" -- \"%s\" )\n",
indent, paramName, currWord));
} else if (type.equals(File.class) || "java.nio.file.Path".equals(type.getName())) {
buff.append(format("%s %s (( currIndex >= %d && currIndex <= %d )); then\n", indent, ifOrElif, min, max));
buff.append(format("%s local IFS=$'\\n'\n", indent));
Expand Down Expand Up @@ -793,7 +795,9 @@ private static String generateOptionsCases(List<OptionSpec> argOptionFields, Str
}
if (option.completionCandidates() != null) {
buff.append(format("%s %s)\n", indent, concat("|", option.names()))); // " -u|--timeUnit)\n"
buff.append(format("%s COMPREPLY=( $( compgen -W \"${%s_option_args}\" -- \"%s\" ) )\n", indent, bashify(option.paramLabel()), currWord));
buff.append(format("%s local IFS=$'\\n'\n", indent));
buff.append(format("%s COMPREPLY=( $( compgen -W \"${%s_option_args[*]}\" -- \"%s\" ) )\n", indent,
bashify(option.paramLabel()), currWord));
buff.append(format("%s return $?\n", indent));
buff.append(format("%s ;;\n", indent));
} else if (type.equals(File.class) || "java.nio.file.Path".equals(type.getName())) {
Expand Down
59 changes: 32 additions & 27 deletions src/test/java/picocli/AutoCompleteTest.java
Expand Up @@ -15,22 +15,15 @@
*/
package picocli;

import org.hamcrest.MatcherAssert;
import org.junit.Rule;
import org.junit.Test;
import org.junit.contrib.java.lang.system.Assertion;
import org.junit.contrib.java.lang.system.ExpectedSystemExit;
import org.junit.contrib.java.lang.system.ProvideSystemProperty;
import org.junit.contrib.java.lang.system.RestoreSystemProperties;
import org.junit.contrib.java.lang.system.SystemErrRule;
import org.junit.contrib.java.lang.system.SystemOutRule;
import org.junit.rules.TestRule;
import picocli.CommandLine.Command;
import picocli.CommandLine.Model.CommandSpec;
import picocli.CommandLine.Model.OptionSpec;
import picocli.CommandLine.Model.PositionalParamSpec;
import picocli.CommandLine.Option;
import picocli.CommandLine.Parameters;
import static java.lang.String.format;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import java.io.BufferedReader;
import java.io.File;
Expand All @@ -53,11 +46,23 @@
import java.util.Scanner;
import java.util.concurrent.TimeUnit;

import static java.lang.String.format;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.*;
import org.hamcrest.MatcherAssert;
import org.junit.Rule;
import org.junit.Test;
import org.junit.contrib.java.lang.system.Assertion;
import org.junit.contrib.java.lang.system.ExpectedSystemExit;
import org.junit.contrib.java.lang.system.ProvideSystemProperty;
import org.junit.contrib.java.lang.system.RestoreSystemProperties;
import org.junit.contrib.java.lang.system.SystemErrRule;
import org.junit.contrib.java.lang.system.SystemOutRule;
import org.junit.rules.TestRule;

import picocli.CommandLine.Command;
import picocli.CommandLine.Model.CommandSpec;
import picocli.CommandLine.Model.OptionSpec;
import picocli.CommandLine.Model.PositionalParamSpec;
import picocli.CommandLine.Option;
import picocli.CommandLine.Parameters;

/**
* Tests the scripts generated by AutoComplete.
Expand Down Expand Up @@ -93,7 +98,7 @@ public void run() {
public void basic() throws Exception {
String script = AutoComplete.bash("basicExample", new CommandLine(new BasicExample()));
String expected = format(loadTextFromClasspath("/basic.bash"),
CommandLine.VERSION, spaced(TimeUnit.values()));
CommandLine.VERSION, concat("\" \"", TimeUnit.values()));
assertEquals(expected, script);
}

Expand Down Expand Up @@ -188,7 +193,7 @@ public void nestedSubcommands() throws Exception {
);
String script = AutoComplete.bash("picocompletion-demo", hierarchy);
String expected = format(loadTextFromClasspath("/picocompletion-demo_completion.bash"),
CommandLine.VERSION, spaced(TimeUnit.values()));
CommandLine.VERSION, concat("\" \"", TimeUnit.values()));
assertEquals(expected, script);
}

Expand All @@ -204,16 +209,16 @@ public void helpCommand() {
.addSubcommand(new CommandLine.HelpCommand());
String script = AutoComplete.bash("picocompletion-demo-help", hierarchy);
String expected = format(loadTextFromClasspath("/picocompletion-demo-help_completion.bash"),
CommandLine.VERSION, spaced(TimeUnit.values()));
CommandLine.VERSION, concat("\" \"", TimeUnit.values()));
assertEquals(expected, script);
}

private static String spaced(Object[] values) {
private static String concat(String infix, Object[] values) {
StringBuilder result = new StringBuilder();
for (Object value : values) {
result.append(value).append(' ');
result.append(value).append(infix);
}
return result.toString().substring(0, result.length() - 1);
return result.toString().substring(0, result.length() - infix.length());
}

static String loadTextFromClasspath(String path) {
Expand Down
5 changes: 3 additions & 2 deletions src/test/resources/bashify_completion.bash
Expand Up @@ -136,13 +136,14 @@ function _picocli_bashify() {
local commands=""
local flag_opts=""
local arg_opts="-x"
local _AB_C_option_args="1" # -x values
local _AB_C_option_args=("1") # -x values

type compopt &>/dev/null && compopt +o default

case ${prev_word} in
-x)
COMPREPLY=( $( compgen -W "${_AB_C_option_args}" -- "${curr_word}" ) )
local IFS=$'\n'
COMPREPLY=( $( compgen -W "${_AB_C_option_args[*]}" -- "${curr_word}" ) )
return $?
;;
esac
Expand Down
5 changes: 3 additions & 2 deletions src/test/resources/basic.bash
Expand Up @@ -136,13 +136,14 @@ function _picocli_basicExample() {
local commands=""
local flag_opts=""
local arg_opts="-u --timeUnit -t --timeout"
local timeUnit_option_args="%2$s" # --timeUnit values
local timeUnit_option_args=("%2$s") # --timeUnit values

type compopt &>/dev/null && compopt +o default

case ${prev_word} in
-u|--timeUnit)
COMPREPLY=( $( compgen -W "${timeUnit_option_args}" -- "${curr_word}" ) )
local IFS=$'\n'
COMPREPLY=( $( compgen -W "${timeUnit_option_args[*]}" -- "${curr_word}" ) )
return $?
;;
-t|--timeout)
Expand Down

0 comments on commit 8c471fe

Please sign in to comment.