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

Support command completions from installed tools #752

Open
FrederikNJS opened this issue Jun 29, 2020 · 8 comments
Open

Support command completions from installed tools #752

FrederikNJS opened this issue Jun 29, 2020 · 8 comments

Comments

@FrederikNJS
Copy link

Describe the solution you'd like
I recently discovered asdf, and installed it hoping I could manage all my various hand-installed binaries (especially everything I use for Kubernetes). Most of the Kubernetes tools are much less useful without completions, as you often have to look up a pod name and then secondly perform some action on them, in two separate commands. With command completions, I can simply autocomplete the pod names, which saves me running an entire command.

A lot of kubernetes tools, such as kubectl and helm all include completions directly in the binary, but they have to be loaded like this (in zsh):

source <(kubectl completion zsh)
source <(helm completion zsh)

I'm aware that many plugins provide completions in different ways, but pretty much all Kubernetes tools provide them like this.

Would it be possible for asdf to somehow enable the loading of completions from each plugin, such that a plugin author can include some configuration that tells asdf how to load completions.

Describe similar asdf features and why they are not sufficient
asdf already supplies completions for itself, but that doesn't help me look up pods via kubectl.

Describe workarounds you've considered
The simplest workaround would be to simply add all the completions in my .zshrc, like I've always done, but it would be nice if asdf automatically installed and set up the completions.

@endorama
Copy link

Hello, I just stumbled upon this issue as I'm having the same problem.

Running source <(kubectl completion zsh) in the rc file is not possible unless a global version is set, as the command is not "present" (setting a global version is particularly problematic in the case of kubectl as compatibility between local kubectl version and remote Kubernetes cluster has some version compatibility rules that you should respect).

Ideally asdf would provide a way to load completion just in time after enabling a specific version. This may either be a dynamic completion (like in the kubectl case) or loading some static completion from files.

A note: asdf is really useful when you need multiple version of the same tool; as those versions do not guarantee compatibility between each other, is possible that completion change between different version, so each installed version should ideally have it's completion loaded when enabled.

Do you think this would be possible? I'm willing to submit a PR but I'd like to discuss how to implement this first.

(BTW thank you for the awesome project 💪)

@alexandrospanagiotidis
Copy link

alexandrospanagiotidis commented Oct 24, 2020

Here is a quick hack that I came up with:

#!/usr/bin/env bash

_asdf_complete_kubectl() {
    currentVersion="$(asdf current kubectl)"

    if [[ ${_ASDF_COMPLETE_KUBECTL_VER} = "${currentVersion}" ]]
    then
        __start_kubectl "${@}"
    else
        source <(kubectl completion bash)
        complete -F _asdf_complete_kubectl kubectl
        _ASDF_COMPLETE_KUBECTL_VER="${currentVersion}"
    fi
}

complete -F _asdf_complete_kubectl kubectl

This registers _asdf_complete_kubectl as completion handler for kubectl.
That function loads the kubectl completions and "remembers" that it already loaded those for the currently active kubectl verison.
If the version changes (e.g., by calling asdf, or because of a .tool-versions file), then it will load the completions of the now active version.

I did not know how to integrate this into asdf; in particular, it seems that completions are not handled at all (i.e., you need to source them yourself).
Furthermore, this snippet is not perfect, because the actual completion of kubectl is not only determined by __start_kubectl:

> source <(kubectl completion bash)
> complete -p kubectl
complete -o default -F __start_kubectl kubectl

With the limited time I spent on this, I was not able to figure out how to call the original completion, but in this particular case calling __start_kubectl seems good enough to me.

I am curious if this could be generalized for other asdf plugins (e.g., helm), and if something similar could be used for plugins with static completions (e.g., ripgrep).
Maybe someone with more knowledge about asdf could chime in :)

@alexandrospanagiotidis
Copy link

alexandrospanagiotidis commented Oct 24, 2020

FWIW, a general version of this is not so hard (requires Bash 4.3 for the variable references):

# Run a completion, loads it lazily if not already done 
__asdf_complete() {
    local pluginName="${1}"
    shift

    local currentVersion
    currentVersion="$(asdf current "${pluginName}")"

    local loadedCompletionName="_ASDF_COMPLETE_${pluginName}_VER"

    if [[ ${!loadedCompletionName} != "${currentVersion}" ]]
    then
        _asdf_load_completion
        printf -v "${loadedCompletionName}" "%s" "${currentVersion}"
    fi

    _asdf_complete "${@}"
}

# Completion for kubectl
_asdf_complete_kubectl() {
    _asdf_complete() {
        __start_kubectl "${@}"
    }

    _asdf_load_completion() {
        source <(kubectl completion bash)
        complete -F _asdf_complete_kubectl kubectl
    }

    __asdf_complete "kubectl" "${@}"
}

complete -F _asdf_complete_kubectl kubectl

# Completion for helm
_asdf_complete_helm() {
    _asdf_complete() {
        __start_helm "${@}"
    }

    _asdf_load_completion() {
        source <(helm completion bash)
        complete -F _asdf_complete_helm helm
    }

    __asdf_complete "helm" "${@}"
}

complete -F _asdf_complete_helm helm

Note that asdf current foo will not only print the version, but also where it was defined (e.g., which .tool-versions file); as such, it could happen that this will load the same completions more than once, if the same version is defined in different .tool-versions files.
While this could be fixed by removing everything except the version, I feel like there should be something in asdf that only returns the version without the "source".

This obviously also works for static completions, e.g.,

# Completion for rg
_asdf_complete_ripgrep() {
    _asdf_complete() {
        _rg "${@}"
    }

    _asdf_load_completion() {
        local completionFile
        completionFile="$(asdf where ripgrep)/complete/rg.bash"
        [[ -f ${completionFile} ]] && source "${completionFile}"
        complete -F _asdf_complete_ripgrep rg
    }

    __asdf_complete "ripgrep" "${@}"
}

complete -F _asdf_complete_ripgrep rg

It seems though that static completions might not be at the same path for different versions of a plugin or even missing for earlier versions.

@itspngu
Copy link

itspngu commented Jan 10, 2021

Adding to this, manually sourcing completion files in your .bashrc (or equivalent file for other shells) - wether it's through kubectl-style indirections or static files provided by the tool - is not only inconvenient, it also adds massive overhead. Right now I'm running something along the lines of _<tool>_dir="$(asdf where <tool>)" along with logic that has to be customized on a per-tool basis to source completions.

Lazily loading completions for kubectl-style tools helps slightly, but even asdf where <tool> itself is slow to the point of being an actual annoyance once you do this for more than one or two tools, though by the looks of it that's not easily fixed. I feel like drafting a standard for plugin authors to follow with regards to the location of completion files would be a great first step towards cleaning this up.

In the case of kubectl for example, you could have an optional bin/completions script which could make use of the lazy-loading mentioned above, leaving the implementation details to the plugin author. For static completions provided by the tool it could perhaps be as simple as a symlink.

Thoughts?

@kamontat
Copy link

I would love to contribute on this feature. If no one working on this.

@hyperupcall
Copy link
Contributor

@kamontat Go ahead! This is a very useful feature

@kamontat
Copy link

@hyperupcall Do we have design review yet?

Sorry for late reply. I didn't saw your message.

@hyperupcall
Copy link
Contributor

hyperupcall commented Jul 28, 2023

@kamontat There is no design review besides just the comments in this issue. I also have some extra notes below.

When implementing, I think it is important to consider:

  1. Whether the tool has a CLI subcommand to show completions, or has a file to source
  2. The shells we intend to support
  3. Version ranges / error handling
  4. An option that opts-in this new behavior
  5. Caching

For [1], it might be worth looking into supporting a new file for plugins to have: bin/completions (along with bin/install, etc.). Only the plugins know how completions are shown for their specific tool, so this file would be a common way to expose that.

For [2], I'd say it's okay for an initial implementation to only support one shell (perhaps Bash). More shells can be supported in future PRs.

[3] isn't as important, because that logic should be placed in each bin/completions file. For example, NodeJS added some --bash-completion like flag relatively recently. You wouldn't want to try to invoke that on an earlier version. So these checks are important

I wrote [4] and [5] for similar reasons. This feature will absolutely kill performance, once implemented. So, an initial implementation will either be opt-in, to allow for incremental improvements without disrupting existing workflows, and/or cache like crazy so things don't slow down even more.

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

No branches or pull requests

7 participants