diff --git a/spring-shell-core/src/main/java/org/springframework/shell/command/annotation/OptionValues.java b/spring-shell-core/src/main/java/org/springframework/shell/command/annotation/OptionValues.java index ebbc272f..3fc3b582 100644 --- a/spring-shell-core/src/main/java/org/springframework/shell/command/annotation/OptionValues.java +++ b/spring-shell-core/src/main/java/org/springframework/shell/command/annotation/OptionValues.java @@ -33,8 +33,9 @@ import java.lang.annotation.Target; public @interface OptionValues { /** - * Reference to a bean name - * @return a bean name + * Names of beans for {@link CompletionProvider}. + * + * @return names of CompletionProvider beans */ - String ref() default ""; + String[] provider() default {}; } diff --git a/spring-shell-core/src/main/java/org/springframework/shell/command/annotation/support/CommandRegistrationFactoryBean.java b/spring-shell-core/src/main/java/org/springframework/shell/command/annotation/support/CommandRegistrationFactoryBean.java index c692a082..0c2caa56 100644 --- a/spring-shell-core/src/main/java/org/springframework/shell/command/annotation/support/CommandRegistrationFactoryBean.java +++ b/spring-shell-core/src/main/java/org/springframework/shell/command/annotation/support/CommandRegistrationFactoryBean.java @@ -17,6 +17,7 @@ package org.springframework.shell.command.annotation.support; import java.lang.reflect.Method; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -38,6 +39,8 @@ import org.springframework.core.annotation.MergedAnnotations.SearchStrategy; import org.springframework.messaging.handler.invocation.InvocableHandlerMethod; import org.springframework.shell.Availability; import org.springframework.shell.AvailabilityProvider; +import org.springframework.shell.CompletionContext; +import org.springframework.shell.CompletionProposal; import org.springframework.shell.Utils; import org.springframework.shell.command.CommandExceptionResolver; import org.springframework.shell.command.CommandHandlingResult; @@ -52,6 +55,7 @@ import org.springframework.shell.command.annotation.Option; import org.springframework.shell.command.annotation.OptionValues; import org.springframework.shell.command.invocation.InvocableShellMethod; import org.springframework.shell.completion.CompletionProvider; +import org.springframework.shell.completion.CompletionResolver; import org.springframework.shell.context.InteractionMode; import org.springframework.util.ClassUtils; import org.springframework.util.ObjectUtils; @@ -277,12 +281,25 @@ class CommandRegistrationFactoryBean implements FactoryBean } OptionValues ovAnn = mp.getParameterAnnotation(OptionValues.class); - if (ovAnn != null && StringUtils.hasText(ovAnn.ref())) { - CompletionProvider cr = this.applicationContext.getBean(ovAnn.ref(), CompletionProvider.class); - if (cr != null) { - optionSpec.completion(ctx -> cr.apply(ctx)); + if (ovAnn != null) { + String[] providerBeanNames = ovAnn.provider(); + if (providerBeanNames.length > 0) { + final List resolvers = Arrays.stream(providerBeanNames) + .map(beanName -> this.applicationContext.getBean(beanName, CompletionProvider.class)) + .collect(Collectors.toList()); + optionSpec.completion(ctx -> { + return resolvers.stream() + .flatMap(resolver -> resolver.apply(ctx).stream()) + .collect(Collectors.toList()); + }); } } + // if (ovAnn != null && StringUtils.hasText(ovAnn.ref())) { + // CompletionProvider cr = this.applicationContext.getBean(ovAnn.ref(), CompletionProvider.class); + // if (cr != null) { + // optionSpec.completion(ctx -> cr.apply(ctx)); + // } + // } } } diff --git a/spring-shell-core/src/test/java/org/springframework/shell/command/annotation/support/CommandRegistrationFactoryBeanTests.java b/spring-shell-core/src/test/java/org/springframework/shell/command/annotation/support/CommandRegistrationFactoryBeanTests.java index 4d18823c..c537dfc5 100644 --- a/spring-shell-core/src/test/java/org/springframework/shell/command/annotation/support/CommandRegistrationFactoryBeanTests.java +++ b/spring-shell-core/src/test/java/org/springframework/shell/command/annotation/support/CommandRegistrationFactoryBeanTests.java @@ -15,6 +15,8 @@ */ package org.springframework.shell.command.annotation.support; +import java.util.Collections; + import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.runner.ApplicationContextRunner; @@ -25,6 +27,8 @@ import org.springframework.shell.command.CommandRegistration; import org.springframework.shell.command.annotation.Command; import org.springframework.shell.command.annotation.CommandAvailability; import org.springframework.shell.command.annotation.Option; +import org.springframework.shell.command.annotation.OptionValues; +import org.springframework.shell.completion.CompletionProvider; import static org.assertj.core.api.Assertions.assertThat; @@ -209,6 +213,35 @@ class CommandRegistrationFactoryBeanTests { } } + @Test + void setsOptionWithCompletion() { + configCommon(OptionWithCompletion.class, new OptionWithCompletion(), "command1", new Class[] { String.class }) + .run((context) -> { + CommandRegistrationFactoryBean fb = context.getBean(FACTORYBEANREF, + CommandRegistrationFactoryBean.class); + assertThat(fb).isNotNull(); + CommandRegistration registration = fb.getObject(); + assertThat(registration).isNotNull(); + assertThat(registration.getOptions().get(0).getCompletion()).isNotNull(); + }); + } + + @Command + private static class OptionWithCompletion { + + @Command + void command1(@Option(longNames = "arg") @OptionValues(provider = "completionProvider") String arg) { + } + + @Bean + CompletionProvider completionProvider() { + return ctx -> { + return Collections.emptyList(); + }; + } + + } + private ApplicationContextRunner configCommon(Class type, T bean) { return configCommon(type, bean, "command", new Class[0]); } diff --git a/spring-shell-samples/src/main/java/org/springframework/shell/samples/e2e/InteractiveCompletionCommands.java b/spring-shell-samples/src/main/java/org/springframework/shell/samples/e2e/InteractiveCompletionCommands.java index 98ee1114..bb465835 100644 --- a/spring-shell-samples/src/main/java/org/springframework/shell/samples/e2e/InteractiveCompletionCommands.java +++ b/spring-shell-samples/src/main/java/org/springframework/shell/samples/e2e/InteractiveCompletionCommands.java @@ -62,8 +62,8 @@ public class InteractiveCompletionCommands { @Command(command = "interactive-completion-1") public String testRequiredValueAnnotation( - @Option(longNames = "arg1", required = true) @OptionValues(ref = "test1CompletionProvider") String arg1, - @Option(longNames = "arg2", required = true) @OptionValues(ref = "test2CompletionProvider") String arg2 + @Option(longNames = "arg1", required = true) @OptionValues(provider = "test1CompletionProvider") String arg1, + @Option(longNames = "arg2", required = true) @OptionValues(provider = "test2CompletionProvider") String arg2 ) { return "Hello " + arg1; } @@ -79,8 +79,8 @@ public class InteractiveCompletionCommands { @Bean CompletionProvider test2CompletionProvider() { return ctx -> { - Test1ValuesProvider test1ValuesProvider = new Test1ValuesProvider(); - return test1ValuesProvider.complete(ctx); + Test2ValuesProvider test2ValuesProvider = new Test2ValuesProvider(); + return test2ValuesProvider.complete(ctx); }; } }