Skip to content

Conversation

@czpilar
Copy link
Contributor

@czpilar czpilar commented Dec 28, 2025

Improves #1246
Resolves #285

…s should be completed

Signed-off-by: czpilar <david@czpilar.net>
@czpilar
Copy link
Contributor Author

czpilar commented Dec 28, 2025

Hello,

I’ve just improved --key=value completion by parameterizing the Option annotation so you can specify whether the option value should be completed or not. By default, this is set to true, which means option names are completed with a space. When you specify completion=false, an = is appended to the option name instead. This improves #1246.

@Option(longName = "first", shortName = 'f', completion = false) String first

I’ve also improved the completion provider so you can control whether a completion proposal should be completed or not, as mentioned in #285.

See the following examples.
The first example shows two commands where the hello command has options with completion enabled. The second command, hello2, has options with completion disabled.

This means completion will behave as follows:

shell:> hello --first Peter --last Chan

shell:> hello2 --first=Peter --last=Chan
package org.springframework.shell.samples.helloworld.boot;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.shell.core.command.CommandOption;
import org.springframework.shell.core.command.annotation.Command;
import org.springframework.shell.core.command.annotation.Option;
import org.springframework.shell.core.command.completion.CompletionProposal;
import org.springframework.shell.core.command.completion.CompletionProvider;

import java.util.Collections;
import java.util.stream.Stream;

@SpringBootApplication
public class SpringShellApplication {

	public static void main(String[] args) {
		SpringApplication.run(SpringShellApplication.class, args);
	}

	@Command(name = "hello", completionProvider = "helloNameCompletionProvider")
	public void sayHello(@Option(longName = "first", shortName = 'f') String first,
			@Option(longName = "last", shortName = 'l') String last) {
		System.out.println("Hello " + first + " " + last + "!");
	}

	@Command(name = "hello2", completionProvider = "helloNameCompletionProvider")
	public void sayHello2(@Option(longName = "first", shortName = 'f', completion = false) String first,
			@Option(longName = "last", shortName = 'l', completion = false) String last) {
		System.out.println("Hello2 " + first + " " + last + "!");
	}

	@Bean
	public CompletionProvider helloNameCompletionProvider() {
		return completionContext -> {
			CommandOption option = completionContext.getCommandOption();
			if (option == null) {
				return Collections.emptyList();
			}

			String word = completionContext.getWords().get(completionContext.getWords().size() - 1);
			if (word.contains("=")) {
				word = word.substring(0, word.indexOf('='));
			}
			String prefix = word.isEmpty() ? word : word + "=";

			Stream<String> options = Stream.empty();

			if ("first".equals(option.longName())) {
				options = Stream.of("Peter", "Paul", "Mary");
			}
			else if ("last".equals(option.longName())) {
				options = Stream.of("Chan", "Noris");
			}

			return options.map(str -> new CompletionProposal(prefix + str).displayText(str)).toList();
		};
	}

}

The second example shows how to use a completion provider with completion disabled for its completion proposals.

This means completion will behave as follows:

shell:>snake --first=Mary/Paul/Paul/Paul/Paul/Paul/Paul/Paul/Paul/Paul --last=Noris/Chan/Chan/Noris/Noris/Noris

In this case, the completion proposals are appended with /, and the proposals themselves are not completed. This was requested in #285.

package org.springframework.shell.samples.helloworld.boot;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.shell.core.command.CommandOption;
import org.springframework.shell.core.command.annotation.Command;
import org.springframework.shell.core.command.annotation.Option;
import org.springframework.shell.core.command.completion.CompletionProposal;
import org.springframework.shell.core.command.completion.CompletionProvider;

import java.util.Collections;
import java.util.stream.Stream;

@SpringBootApplication
public class SpringShellApplication {

	public static void main(String[] args) {
		SpringApplication.run(SpringShellApplication.class, args);
	}

	@Command(name = "snake", completionProvider = "snakeCompletionProvider")
	public void snake(@Option(longName = "first", shortName = 'f', completion = false) String first,
			@Option(longName = "last", shortName = 'l', completion = false) String last) {
		System.out.println("Snake " + first + " " + last + "!");
	}

	@Bean
	public CompletionProvider snakeCompletionProvider() {
		return completionContext -> {
			CommandOption option = completionContext.getCommandOption();
			if (option == null) {
				return Collections.emptyList();
			}

			String word = completionContext.getWords().get(completionContext.getWords().size() - 1);
			String prefix = word.endsWith("=") || word.endsWith("/") ? word : word + "/";

			Stream<String> options = Stream.empty();

			if ("first".equals(option.longName())) {
				options = Stream.of("Peter", "Paul", "Mary");
			}
			else if ("last".equals(option.longName())) {
				options = Stream.of("Chan", "Noris");
			}

			return options.map(str -> new CompletionProposal(prefix + str).displayText(str).complete(false)).toList();
		};
	}

}

I’ve also added many tests to cover this behavior.

As a next step, it could be useful to allow defining a custom separator character instead of just a space or equals sign, but that would require changes to the command parser as well.

@czpilar
Copy link
Contributor Author

czpilar commented Dec 28, 2025

@fmbenhassine Please take a look at this PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Tab auto-complete will always put a space after selected option

1 participant