clap-rs / clap

A full featured, fast Command Line Argument Parser for Rust
docs.rs/clap
Apache License 2.0
14.25k stars 1.04k forks source link

clap_complete: Allow access to previous args in completion function #5784

Open LucasPickering opened 5 days ago

LucasPickering commented 5 days ago

Please complete the following tasks

Clap Version

clap 4.4.2, clap_complete 4.5.29

Describe your use case

In a ArgValueCompleter function, it would be nice to have access to what has been parsed so far when generating completions. My use case is that I'm loading the arguments from a YAML file, but the file to load from can be overridden by a prior -f argument. Another identical example is docker-compose run: You can run docker-compose run <service> in which case it will load services from docker-compose.yml, or you can do docker-compose -f different/compose/file.yml run <service>, in which case it should load from different/compose/file.yml.

Describe the solution you'd like

Access to what has already been parsed as an argument to the ArgValueCompleter function. This would most likely be a breaking change because it requires adding an argument to ValueCompleter::complete. The user impact could be mitigated though by keeping the blanket impl on fn(&OsStr) -> Vec<CompletionCandidate>. I imagine most users are not implementing ValueCompleter themselves.

The best I can think of is the additional argument is just a &[&OsStr] containing what's already been parsed. The completer would then be responsible for iterating over that and figuring out the semantic meaning. It'd be great if we could get the prior arguments in a more structured form, but doing that without the full command present is probably not possible. So an example completion function for something like docker-compose run would look like:

fn complete_service(current: &OsStr, previous: &[&OsStr]) -> Vec<CompletionCandidate> {
    let file_arg_index = previous.iter().position(|arg| arg == "-f" || arg == "--file");
    let path = if let Some(file_index) = file_arg_index {
        previous[file_arg_index + 1]
    } else {
        "docker-compose.yml"
    };
    let Ok(config) = load_config(path) else {return vec![]};
    config
        .services
        .into_iter()
        .map(|service| CompletionCandidate::new(service.name))
        .collect()
}

(this is pseudocode, I'm sure it doesn't actually compile but it should get the idea across)

Alternatives, if applicable

Additional Context

epage commented 5 days ago

Huh, I thought we had an issue for this but can't find one. Passing in an OsStr slice wouldn't be ideal as people would have to hack-in their own parsing. We'd likely want ‘ArgMatchesor something like it. The challenge withArgMatches` is we've not exposed the API for making one. More generally, #5515 is a good intermediate step.

LucasPickering commented 5 days ago

Yes ArgMatches seems perfect. Is there a need to expose the construction of it? I would think the completion engine could construct the ArgMatches then pass it into the user's completer functions.

epage commented 4 days ago

ArgMatches lives in clap and so the constructor would need to be exposed so clap_complete could make one and pass it to the users completer.

LucasPickering commented 3 days ago

Ah of course. What can I do to help with this? I don't have a ton of time to dedicate to it but I'd like to help if possible.

epage commented 3 days ago

This is one of the lower priority tasks as we are focusing on

As such, I'm not going to be setting aside the time to help drive the design of this forward for this to be resolved atm.

Previously, I said that #5784 might be initial step but I just realized that there is some extra complexity to that that I had overlooked.