Skip to content

Commit 9b36c4d

Browse files
committed
Improved completion by allowing configuration of whether option values should be completed
Signed-off-by: czpilar <david@czpilar.net>
1 parent 8dcfff6 commit 9b36c4d

File tree

5 files changed

+76
-24
lines changed

5 files changed

+76
-24
lines changed

spring-shell-core/src/main/java/org/springframework/shell/core/command/CommandOption.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
* @author Mahmoud Ben Hassine
2727
*/
2828
public record CommandOption(char shortName, @Nullable String longName, @Nullable String description,
29-
@Nullable Boolean required, @Nullable String defaultValue, @Nullable String value) {
29+
@Nullable Boolean required, @Nullable String defaultValue, @Nullable String value, boolean completion) {
3030

3131
public static Builder with() {
3232
return new Builder();
@@ -46,6 +46,8 @@ public static class Builder {
4646

4747
private @Nullable String value;
4848

49+
private boolean completion = true;
50+
4951
public Builder shortName(char shortName) {
5052
this.shortName = shortName;
5153
return this;
@@ -76,8 +78,13 @@ public Builder value(String value) {
7678
return this;
7779
}
7880

81+
public Builder completion(boolean completion) {
82+
this.completion = completion;
83+
return this;
84+
}
85+
7986
public CommandOption build() {
80-
return new CommandOption(shortName, longName, description, required, defaultValue, value);
87+
return new CommandOption(shortName, longName, description, required, defaultValue, value, completion);
8188
}
8289

8390
}

spring-shell-core/src/main/java/org/springframework/shell/core/command/annotation/Option.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,4 +62,10 @@
6262
*/
6363
String defaultValue() default "";
6464

65+
/**
66+
* Indicates whether this option should be completed in the command completer.
67+
* @return true if the option should be completed, defaults to true.
68+
*/
69+
boolean completion() default true;
70+
6571
}

spring-shell-core/src/main/java/org/springframework/shell/core/command/annotation/support/CommandFactoryBean.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ private List<CommandOption> getCommandOptions() {
122122
String description = optionAnnotation.description();
123123
boolean required = optionAnnotation.required();
124124
String defaultValue = optionAnnotation.defaultValue();
125+
boolean completion = optionAnnotation.completion();
125126
if (shortName == ' ' && longName.isEmpty()) {
126127
throw new IllegalArgumentException(
127128
"Either shortName or longName (or both) must be provided for option on parameter '"
@@ -133,6 +134,7 @@ private List<CommandOption> getCommandOptions() {
133134
.description(description)
134135
.required(required)
135136
.defaultValue(defaultValue)
137+
.completion(completion)
136138
.build();
137139
commandOptions.add(commandOption);
138140
}

spring-shell-jline/src/main/java/org/springframework/shell/jline/CommandCompleter.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,14 @@ public void complete(LineReader reader, ParsedLine line, List<Candidate> candida
4646
// add option completions for the command
4747
for (CommandOption option : options) {
4848
boolean present = isOptionPresent(line, option);
49+
String separator = option.completion() ? "" : "=";
4950
if (option.longName() != null && !present) {
50-
candidates.add(new Candidate("--" + option.longName()));
51+
candidates.add(new Candidate("--" + option.longName() + separator, "--" + option.longName(),
52+
null, null, null, null, option.completion(), 0));
5153
}
5254
if (option.shortName() != ' ' && !present) {
53-
candidates.add(new Candidate("-" + option.shortName()));
55+
candidates.add(new Candidate("-" + option.shortName() + separator, "-" + option.shortName(),
56+
null, null, null, null, option.completion(), 0));
5457
}
5558
}
5659
}

spring-shell-jline/src/test/java/org/springframework/shell/jline/CommandCompleterTests.java

Lines changed: 54 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -87,8 +87,8 @@ private List<String> toCandidateDisplayText(List<Candidate> candidates) {
8787
}
8888

8989
@ParameterizedTest
90-
@MethodSource("completeData")
91-
public void testComplete(List<String> words, List<String> expectedValues) {
90+
@MethodSource("completeWithCompletionData")
91+
public void testCompleteWithCompletion(List<String> words, List<String> expectedValues) {
9292
// given
9393
when(command.getOptions())
9494
.thenReturn(List.of(new CommandOption.Builder().longName("first").shortName('f').build(),
@@ -107,51 +107,85 @@ public void testComplete(List<String> words, List<String> expectedValues) {
107107
assertEquals(expectedValues, toCandidateNames(candidates));
108108
}
109109

110-
static Stream<Arguments> completeData() {
110+
static Stream<Arguments> completeWithCompletionData() {
111+
return completeData("");
112+
}
113+
114+
@ParameterizedTest
115+
@MethodSource("completeWithoutCompletionData")
116+
public void testCompleteWithoutCompletion(List<String> words, List<String> expectedValues) {
117+
// given
118+
when(command.getName()).thenReturn("hello");
119+
when(command.getOptions())
120+
.thenReturn(List.of(new CommandOption.Builder().longName("first").shortName('f').completion(false).build(),
121+
new CommandOption.Builder().longName("last").shortName('l').completion(false).build()));
122+
123+
List<Candidate> candidates = new ArrayList<>();
124+
ParsedLine line = mock(ParsedLine.class);
125+
when(line.words()).thenReturn(words);
126+
when(line.word()).thenReturn(words.get(words.size() - 1));
127+
when(line.line()).thenReturn(String.join(" ", words));
128+
129+
// when
130+
completer.complete(mock(LineReader.class), line, candidates);
131+
132+
// then
133+
assertEquals(expectedValues, toCandidateNames(candidates));
134+
}
135+
136+
static Stream<Arguments> completeWithoutCompletionData() {
137+
return completeData("=");
138+
}
139+
140+
static Stream<Arguments> completeData(String sep) {
111141
return Stream.of(Arguments.of(List.of(""), List.of("hello")), Arguments.of(List.of("he"), List.of("hello")),
112142
Arguments.of(List.of("he", ""), List.of()), Arguments.of(List.of("hello"), List.of("hello")),
113143

114-
Arguments.of(List.of("hello", ""), List.of("--first", "--last", "-f", "-l")),
115-
Arguments.of(List.of("hello", "--"), List.of("--first", "--last", "-f", "-l")),
116-
Arguments.of(List.of("hello", "-"), List.of("--first", "--last", "-f", "-l")),
117-
Arguments.of(List.of("hello", "--fi"), List.of("--first", "--last", "-f", "-l")),
118-
Arguments.of(List.of("hello", "--la"), List.of("--first", "--last", "-f", "-l")),
144+
Arguments.of(List.of("hello", ""), List.of("--first" + sep, "--last" + sep, "-f" + sep, "-l" + sep)),
145+
Arguments.of(List.of("hello", "--"), List.of("--first" + sep, "--last" + sep, "-f" + sep, "-l" + sep)),
146+
Arguments.of(List.of("hello", "-"), List.of("--first" + sep, "--last" + sep, "-f" + sep, "-l" + sep)),
147+
Arguments.of(List.of("hello", "--fi"),
148+
List.of("--first" + sep, "--last" + sep, "-f" + sep, "-l" + sep)),
149+
Arguments.of(List.of("hello", "--la"),
150+
List.of("--first" + sep, "--last" + sep, "-f" + sep, "-l" + sep)),
119151

120-
Arguments.of(List.of("hello", "-f"), List.of("--first", "--last", "-f", "-l")),
152+
Arguments.of(List.of("hello", "-f"), List.of("--first" + sep, "--last" + sep, "-f" + sep, "-l" + sep)),
121153
Arguments.of(List.of("hello", "-f="), List.of("-f=Mary", "-f=Paul", "-f=Peter")),
122154
Arguments.of(List.of("hello", "-f=Pe"), List.of("-f=Mary", "-f=Paul", "-f=Peter")),
123-
Arguments.of(List.of("hello", "-f=Pe", ""), List.of("--last", "-l")),
155+
Arguments.of(List.of("hello", "-f=Pe", ""), List.of("--last" + sep, "-l" + sep)),
124156

125-
Arguments.of(List.of("hello", "--first"), List.of("--first", "--last", "-f", "-l")),
157+
Arguments.of(List.of("hello", "--first"),
158+
List.of("--first" + sep, "--last" + sep, "-f" + sep, "-l" + sep)),
126159
Arguments.of(List.of("hello", "--first="), List.of("--first=Mary", "--first=Paul", "--first=Peter")),
127160
Arguments.of(List.of("hello", "--first=Pe"), List.of("--first=Mary", "--first=Paul", "--first=Peter")),
128-
Arguments.of(List.of("hello", "--first=Pe", ""), List.of("--last", "-l")),
161+
Arguments.of(List.of("hello", "--first=Pe", ""), List.of("--last" + sep, "-l" + sep)),
129162

130163
Arguments.of(List.of("hello", "-f", ""), List.of("Mary", "Paul", "Peter")),
131164
Arguments.of(List.of("hello", "--first", ""), List.of("Mary", "Paul", "Peter")),
132165

133166
Arguments.of(List.of("hello", "-f", "Pe"), List.of("Mary", "Paul", "Peter")),
134-
Arguments.of(List.of("hello", "-f", "Pe", ""), List.of("--last", "-l")),
167+
Arguments.of(List.of("hello", "-f", "Pe", ""), List.of("--last" + sep, "-l" + sep)),
135168
Arguments.of(List.of("hello", "--first", "Pe"), List.of("Mary", "Paul", "Peter")),
136-
Arguments.of(List.of("hello", "--first", "Pe", ""), List.of("--last", "-l")),
169+
Arguments.of(List.of("hello", "--first", "Pe", ""), List.of("--last" + sep, "-l" + sep)),
137170

138-
Arguments.of(List.of("hello", "-l"), List.of("--first", "--last", "-f", "-l")),
171+
Arguments.of(List.of("hello", "-l"), List.of("--first" + sep, "--last" + sep, "-f" + sep, "-l" + sep)),
139172
Arguments.of(List.of("hello", "-l="), List.of("-l=Chan", "-l=Noris")),
140173
Arguments.of(List.of("hello", "-l=No"), List.of("-l=Chan", "-l=Noris")),
141-
Arguments.of(List.of("hello", "-l=No", ""), List.of("--first", "-f")),
174+
Arguments.of(List.of("hello", "-l=No", ""), List.of("--first" + sep, "-f" + sep)),
142175

143-
Arguments.of(List.of("hello", "--last"), List.of("--first", "--last", "-f", "-l")),
176+
Arguments.of(List.of("hello", "--last"),
177+
List.of("--first" + sep, "--last" + sep, "-f" + sep, "-l" + sep)),
144178
Arguments.of(List.of("hello", "--last="), List.of("--last=Chan", "--last=Noris")),
145179
Arguments.of(List.of("hello", "--last=No"), List.of("--last=Chan", "--last=Noris")),
146-
Arguments.of(List.of("hello", "--last=No", ""), List.of("--first", "-f")),
180+
Arguments.of(List.of("hello", "--last=No", ""), List.of("--first" + sep, "-f" + sep)),
147181

148182
Arguments.of(List.of("hello", "-l", ""), List.of("Chan", "Noris")),
149183
Arguments.of(List.of("hello", "--last", ""), List.of("Chan", "Noris")),
150184

151185
Arguments.of(List.of("hello", "-l", "No"), List.of("Chan", "Noris")),
152-
Arguments.of(List.of("hello", "-l", "No", ""), List.of("--first", "-f")),
186+
Arguments.of(List.of("hello", "-l", "No", ""), List.of("--first" + sep, "-f" + sep)),
153187
Arguments.of(List.of("hello", "--last", "No"), List.of("Chan", "Noris")),
154-
Arguments.of(List.of("hello", "--last", "No", ""), List.of("--first", "-f")),
188+
Arguments.of(List.of("hello", "--last", "No", ""), List.of("--first" + sep, "-f" + sep)),
155189

156190
Arguments.of(List.of("hello", "--first", "Paul", "--last", "Noris", ""), List.of()),
157191
Arguments.of(List.of("hello", "--first", "Paul", "-l", "Noris", ""), List.of()),

0 commit comments

Comments
 (0)