From 12dff36d8be3e2734a39bc278a43514fd9a7a1c0 Mon Sep 17 00:00:00 2001 From: Eric Bottard Date: Sat, 16 Sep 2017 13:43:04 +0200 Subject: [PATCH] Fix completion in multi word commands Fixes #150 --- .../java/org/springframework/shell/Shell.java | 8 ++- .../org/springframework/shell/ShellTest.java | 50 +++++++++++++++++++ 2 files changed, 56 insertions(+), 2 deletions(-) diff --git a/spring-shell-core/src/main/java/org/springframework/shell/Shell.java b/spring-shell-core/src/main/java/org/springframework/shell/Shell.java index cff76b12..c35bc617 100644 --- a/spring-shell-core/src/main/java/org/springframework/shell/Shell.java +++ b/spring-shell-core/src/main/java/org/springframework/shell/Shell.java @@ -58,7 +58,8 @@ public class Shell implements CommandRegistry { private final ResultHandler resultHandler; /** - * Marker object returned to signify that there was no input to turn into a command execution. + * Marker object returned to signify that there was no input to turn into a command + * execution. */ public static final Object NO_INPUT = new Object(); @@ -243,9 +244,12 @@ public class Shell implements CommandRegistry { } private List commandsStartingWith(String prefix) { + // Workaround for https://github.com/spring-projects/spring-shell/issues/150 + // (sadly, this ties this class to JLine somehow) + int lastWordStart = prefix.lastIndexOf(' ') + 1; return methodTargets.entrySet().stream() .filter(e -> e.getKey().startsWith(prefix)) - .map(e -> toCommandProposal(e.getKey(), e.getValue())) + .map(e -> toCommandProposal(e.getKey().substring(lastWordStart), e.getValue())) .collect(Collectors.toList()); } diff --git a/spring-shell-core/src/test/java/org/springframework/shell/ShellTest.java b/spring-shell-core/src/test/java/org/springframework/shell/ShellTest.java index ca4591b1..755782bc 100644 --- a/spring-shell-core/src/test/java/org/springframework/shell/ShellTest.java +++ b/spring-shell-core/src/test/java/org/springframework/shell/ShellTest.java @@ -19,6 +19,8 @@ package org.springframework.shell; import java.io.IOException; import java.util.Arrays; import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; import org.junit.Assert; import org.junit.Before; @@ -31,6 +33,7 @@ import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; import org.springframework.context.ApplicationContext; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; import static org.mockito.Matchers.any; import static org.mockito.Matchers.isA; @@ -189,6 +192,53 @@ public class ShellTest { shell.gatherMethodTargets(); } + @Test + public void commandNameCompletion() throws Exception { + shell.applicationContext = mock(ApplicationContext.class); + when(parameterResolver.supports(any())).thenReturn(true); + when(shell.applicationContext.getBeansOfType(MethodTargetRegistrar.class)) + .thenReturn(Collections.singletonMap("foo", r -> { + r.register("hello world", MethodTarget.of("helloWorld", this, "hellow world")); + r.register("another command", MethodTarget.of("helloWorld", this, "another command")); + })); + shell.gatherMethodTargets(); + + // Invoke at very start + List proposals = shell.complete(new CompletionContext(Arrays.asList(""), 0, "".length())) + .stream().map(CompletionProposal::value).collect(Collectors.toList()); + assertThat(proposals).containsExactly("another command", "hello world"); + + // Invoke in middle of first word + proposals = shell.complete(new CompletionContext(Arrays.asList("hel"), 0, "hel".length())) + .stream().map(CompletionProposal::value).collect(Collectors.toList()); + assertThat(proposals).containsExactly("hello world"); + + // Invoke at end of first word (no space after yet) + proposals = shell.complete(new CompletionContext(Arrays.asList("hello"), 0, "hello".length())) + .stream().map(CompletionProposal::value).collect(Collectors.toList()); + assertThat(proposals).containsExactly("hello world"); + + // Invoke after first word / start of second word + proposals = shell.complete(new CompletionContext(Arrays.asList("hello", ""), 1, "".length())) + .stream().map(CompletionProposal::value).collect(Collectors.toList()); + assertThat(proposals).containsExactly("world"); + + // Invoke in middle of second word + proposals = shell.complete(new CompletionContext(Arrays.asList("hello", "wo"), 1, "wo".length())) + .stream().map(CompletionProposal::value).collect(Collectors.toList()); + assertThat(proposals).containsExactly("world"); + + // Invoke at end of whole command (no space after yet) + proposals = shell.complete(new CompletionContext(Arrays.asList("hello", "world"), 1, "world".length())) + .stream().map(CompletionProposal::value).collect(Collectors.toList()); + assertThat(proposals).containsExactly("world"); + + // Invoke in middle of second word + proposals = shell.complete(new CompletionContext(Arrays.asList("hello", "world", ""), 2, "".length())) + .stream().map(CompletionProposal::value).collect(Collectors.toList()); + assertThat(proposals).isEmpty(); + } + private void helloWorld(String a) { invoked = true; }