Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OptionCompleter documentation #502

Closed
mel4tonin4 opened this issue Jan 27, 2020 · 4 comments
Closed

OptionCompleter documentation #502

mel4tonin4 opened this issue Jan 27, 2020 · 4 comments

Comments

@mel4tonin4
Copy link

mel4tonin4 commented Jan 27, 2020

Hello.

I'm trying to understand how to specify completion for the options of my CliktCommand (from Clikt) to jline3, but my code — which is inspired by Picocli Shell — is not working as expected: the resulting shell can complete the nested commands but not the options.
Here's the relevant code, in the form of extension methods for the CliktCommand class:

internal fun CliktCommand.makeJLineTreeCompleter() : Completers.TreeCompleter =
    Completers.TreeCompleter(registeredSubcommands.map { it.makeJLineCompleterNode() })

internal fun CliktCommand.makeJLineCompleterNode() : Completers.TreeCompleter.Node =
    registeredSubcommands.let { subcommands ->
        if (subcommands.isEmpty()) {
            val options = registeredOptions.flatMap {
                (it.completionCandidates.toNameSet() + it.names).toList()
            }
            val optionValues = mapOf<String, List<String>>()
            Completers.TreeCompleter.node(
                ArgumentCompleter(
                    StringsCompleter(commandName),
                    Completers.OptionCompleter(
                        NullCompleter.INSTANCE, optionValues, options, 1
                    )
                )
            )
        } else {
            Completers.TreeCompleter.node(commandName, *subcommands.map { it.makeJLineCompleterNode() }.toTypedArray())
        }
    }

Could you explain what is the meaning of the parameters of this constructor?

OptionCompleter(org.jline.reader.Completer completer, Map<String,List<String>> optionValues, Collection<String> options, int startPos)

What is completer going to complete?

Should startPos be greater if I'm dealing with a nested command?

In this piece of code from the wiki:

completer = new ArgumentCompleter(
    new StringsCompleter("Command1"),
    new OptionCompleter(Arrays.asList(new StringsCompleter("p1", "p11"),
                                      new StringsCompleter("p2", "p22"),
                                      NullCompleter.INSTANCE)),
                        this::commandOptions
                        1));   // OptionCompleter position in ArgumentCompleter List

what is this::commandOptions supposed to be? What are those "p1", "p11", etc.? I could run the TreeCompleter snippet from the same wiki and check what it does, but it's not possible to run this other one.

Thanks for reading.

@mattirn
Copy link
Collaborator

mattirn commented Jan 28, 2020

OptionCompleter completes also command positional parameters.

OptionCompleter(org.jline.reader.Completer completer, Map<String,List<String>> optionValues, Collection<String> options, int startPos)
  1. completer completes command positional parameters
  2. optionValues options that have value. Map key is option and map value possible option values.
  3. options list of options that do not have value
  4. startPos is the position where you put the OptionCompleter in ArgumentCompleter. In case of sub commands your argument completer would look something like:
new ArgumentCompleter(StringsCompleter("command"), StringsCompleter(subCommands), new OptionCompleter(argsCompleter, commandOptions, 2))

The code snippet from wiki:

  1. this::commandOptions is Java functional interface Function<String,Collection<OptDesc>>. The function String parameter is command name and it will return command option descriptions.
  2. "p1", "p11" etc are possible values for command positional parameter.

optionCompleterTest script

I think the fastest way to experiment and test optionCompleter is to use the script. After you have build the JLine project launch the script by entering command

% ./build repl
groovy-repl> optionCompleterTest -v

Screenshot from 2020-01-28 21-08-59

@mel4tonin4
Copy link
Author

mel4tonin4 commented Jan 29, 2020

Thank you for the clarification, mattir!

Now I understand better the code that I attempted to copy from picocli shell, and I'm able to use both TreeCompleters and OptionCompleters inside ArgumentCompleters.

I'm still unable to make the completers generated by my code from the CliktCommand to work. However, I think I understand the problem is how I'm trying to use ArgumentCompleter inside the TreeCompleter.

Here is an example of hand-made completer which exhibits the same kind of malfunction, similar to what is generated by my code:

Completers.TreeCompleter(
    node(
       "Cmd1",
       node("aaa", "bbbbb")
    ),
   node(
       "Cmd2",
       node("xxx", "xxyyyy", "zzz")
   ),
   node(
       "Cmd3",
       node(
           "Cmd3a",
           node("aaa", "bbbbb")
        ),
        node(
            "Cmd3b",
            node("xxx", "xxyyyy", "zzz")
        )
    ),
   node(
        ArgumentCompleter(
            StringsCompleter("Cmd4"),
            Completers.OptionCompleter(
                listOf(
                    StringsCompleter("bar", "rab"),
                    StringsCompleter("foo", "oof"),
                    NullCompleter.INSTANCE
                ),
                listOf(
                    OptDesc("-s", "--sopt", StringsCompleter("val", "lav")),
                    OptDesc(null, "--option", NullCompleter.INSTANCE)
                ),
                1
            )
        )
    )
)

Am I not supposed to used ArgumentCompleter in this manner?

@mattirn
Copy link
Collaborator

mattirn commented Jan 29, 2020

I have not used TreeCompleter in my code but after a few tests it seems to me that every node in TreeCompleter completes at maximum one word. While both ArgumentCompleter and OptionCompleter completes N words. So if you put ArgumentCompleter as a parameter of node it will complete only its first argument.
Maybe RegexCompleter would work better in your case than TreeCompleter. I have not used neither RegexCompleter but it has been used to implement TreeCompleter so probably you have more freedom with it.

@mel4tonin4
Copy link
Author

Thank you for the help, mattirn.

I ended up creating a tree of AggregateCompleter, like this:


internal fun CliktCommand.makeJLineTreeCompleter() : Completer =
    AggregateCompleter(registeredSubcommands.map { it.makeJLineTreeCompleter(emptyList()) })

internal fun CliktCommand.makeJLineTreeCompleter(parentCommandPath : List<CliktCommand>) : Completer =
    registeredSubcommands.let { subcommands ->
        val commandPath = parentCommandPath + listOf(this)
        if (subcommands.isEmpty()) {
            ArgumentCompleter(
                commandPath.map { StringsCompleter(it.commandName) } + listOf(
                    Completers.OptionCompleter(
                        registeredArguments.map { it.makeJLineTreeCompleter() },
                        Function { _ -> registeredOptions.map { it.makeJLineDescription() } },
                        commandPath.size
                    )
                )
            )
        } else {
            AggregateCompleter(subcommands.map { it.makeJLineTreeCompleter(commandPath) })
        }
    }

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

No branches or pull requests

2 participants