Fabric icon indicating copy to clipboard operation
Fabric copied to clipboard

[Feature request]: zsh autocompletion plugin

Open samuk10 opened this issue 1 year ago • 4 comments

What do you need?

Can we have zsh_autocompletion for fabric commands?

samuk10 avatar Sep 03 '24 14:09 samuk10

Here is a quick pass. Feel free to enhance. https://gist.github.com/stuartsaunders/ac486fc23b1c97693435cff3d2a819e8

stuartsaunders avatar Sep 05 '24 18:09 stuartsaunders

@stuartsaunders This Bash implementation in #659 may not run as fast as your statically defined zsh functions, but I think auto-completion should utilize fabric's builtin functionality to --listpatterns, --listmodels, --listcontexts, and --listsessions so that the lists don't get "stale" when new items are dynamically added.

erhhung avatar Oct 09 '24 18:10 erhhung

Here's a working completion for ZSH. I put this in ~/.zsh/completions/_fabric and have fpath=(~/.zsh/completions $fpath) in my ~/.zshrc

#compdef fabric

_fabric() {
    local curcontext="$curcontext" state line
    typeset -A opt_args

    _arguments -C \
        '(-p --pattern)'{-p,--pattern}'[Pattern]: :->patterns' \
        '(-m --model)'{-m,--model}'[Model]: :->models' \
        '(-C --context)'{-C,--context}'[Context]: :->contexts' \
        '(--session)--session[Session]: :->sessions' \
        '(-o --output)'{-o,--output}'[Output]: :->output' \
        '*: :->args'

    case $state in
        patterns)
            local -a patterns
            patterns=( ${(f)"$(fabric --listpatterns)"} )
            _describe 'patterns' patterns
            ;;
        models)
            local -a models
            models=( ${(f)"$(fabric --listmodels | sed -En 's/^[[:blank:]]+\[[0-9]+\][[:blank:]]+(.+)$/\1/p')"} )
            _describe 'models' models
            ;;
        contexts)
            local -a contexts
            contexts=( ${(f)"$(fabric --listcontexts)"} )
            [[ "$contexts" != *"No Contexts"* ]] && _describe 'contexts' contexts
            ;;
        sessions)
            local -a sessions
            sessions=( ${(f)"$(fabric --listsessions)"} )
            [[ "$sessions" != *"No Sessions"* ]] && _describe 'sessions' sessions
            ;;
        output)
            _files -/
            ;;
        args)
            local -a opts
            opts=( ${(f)"$(fabric --help | sed -En 's/^[[:blank:]]+((-[a-zA-Z],)?[[:blank:]]--[a-z][-_a-zA-Z]+=?[[:blank:]]).*$/\1/p' | \
                   sed -E 's/[,=]//g;s/[[:blank:]]/\n/g;/^$/d;s/^(-[^-])/-\1/' | sort -f | sed -E 's/^-(-.)$/\1/')"} )
            _describe 'options' opts
            ;;
    esac
}

compdef _fabric fabric

natemccurdy avatar Jan 06 '25 21:01 natemccurdy

Thanks, @natemccurdy! Extended this a bit to show descriptions of the arguments and improve formatting in the completions menu.

#compdef fabric

_fabric() {
    ###################################################################
    # Enable extendedglob for advanced pattern matching (no PCRE)
    ###################################################################
    setopt extendedglob

    local curcontext="$curcontext" state line
    typeset -A opt_args

    ###################################################################
    # Gather lines from "fabric -h"
    ###################################################################
    local -a options_with_desc
    while IFS= read -r line; do
        options_with_desc+=("$line")
    done < <(
        fabric -h |
        sed -n '/Application Options:/,/Help Options:/p' |
        grep -E '^(  -|      --)' |
        sed 's/^ *//'
    )

    ###################################################################
    # Parse each line to extract short_opt, long_opt, and desc:
    #
    # short+long format:  "-p, --pattern=    Some description"
    # long-only format:   "--session=        Some description"
    ###################################################################
    local -a formatted_options=()
    local -A seen_options
    local short_opt long_opt desc

    for line in "${options_with_desc[@]}"; do

        # short+long
        if [[ "$line" =~ '^-([^[:space:],]+),[[:space:]]*--([^[:space:]]+)[[:space:]]+(.*)$' ]]; then
            short_opt=$match[1]
            long_opt=$match[2]
            desc=$match[3]

            short_opt="-$short_opt"
            long_opt="--$long_opt"

        # long-only
        elif [[ "$line" =~ '^(--[^[:space:]]+)[[:space:]]+(.*)$' ]]; then
            short_opt=""
            long_opt=$match[1]
            desc=$match[2]

        else
            continue
        fi

        # Trim whitespace from description
        desc="${desc## }"
        desc="${desc%% }"

        # Remove trailing '=' from long options if present
        if [[ "$long_opt" == *= ]]; then
            long_opt="${long_opt%=}"
        fi

        local key="$short_opt|$long_opt"
        # Skip duplicate options to avoid redundancy in completions
        [[ -n ${seen_options[$key]} ]] && continue
        seen_options[$key]=1

        ################################################################
        # Map options to sub-completions
        ################################################################
        local argSpec=""
        local base_opt="${long_opt#--}"

        # Short options
        case $short_opt in
            -p) argSpec=":arg:->patterns"  ;;
            -m) argSpec=":arg:->models"    ;;
            -C) argSpec=":arg:->contexts"  ;;
        esac

        # Long options
        case $base_opt in
            pattern)  argSpec=":arg:->patterns"  ;;
            model)    argSpec=":arg:->models"    ;;
            context)  argSpec=":arg:->contexts"  ;;
            session)  argSpec=":arg:->sessions"  ;;
        esac

        # Add formatted options to the completion list
        if [[ -n $short_opt ]]; then
            if [[ -n $argSpec ]]; then
                formatted_options+=( "${short_opt}[${desc}]${argSpec}" )
            else
                formatted_options+=( "${short_opt}[${desc}]" )
            fi
        fi

        if [[ -n $argSpec ]]; then
            formatted_options+=( "${long_opt}[${desc}]${argSpec}" )
        else
            formatted_options+=( "${long_opt}[${desc}]" )
        fi
    done

    ###################################################################
    # _arguments sets up main completions, then sub-completions
    # based on $state in the case statement below
    ###################################################################
    _arguments -S -s \
        "*::arg:->args" \
        ${formatted_options[@]}

    ###################################################################
    # Sub-completion states
    ###################################################################
    case $state in
        patterns)
            local -a patterns
            patterns=( ${(f)"$(fabric --listpatterns 2>/dev/null)"} )
            _describe 'patterns' patterns
            ;;
        models)
            local -a models
            models=( ${(f)"$(fabric --listmodels 2>/dev/null | sed -En 's/^[[:blank:]]+\[[0-9]+\][[:blank:]]+(.+)$/\1/p')"} )
            _describe 'models' models
            ;;
        contexts)
            local -a contexts
            contexts=( ${(f)"$(fabric --listcontexts 2>/dev/null)"} )
            [[ "$contexts" != *"No Contexts"* ]] && _describe 'contexts' contexts
            ;;
        sessions)
            local -a sessions
            sessions=( ${(f)"$(fabric --listsessions 2>/dev/null)"} )
            [[ "$sessions" != *"No Sessions"* ]] && _describe 'sessions' sessions
            ;;
    esac
}

compdef _fabric fabric

stuartsaunders avatar Jan 08 '25 22:01 stuartsaunders

I extended @KenMacD's recent fish completion script and added new updated scripts for zsh, fish and bash in the referenced PR. Comments welcome. Cheers!

ksylvan avatar Apr 25 '25 03:04 ksylvan

Does the zsh auto-complete I just added in the PR close this issue, @samuk10 ?

https://github.com/user-attachments/assets/c322344a-b482-46cb-9089-6a2afc64cbca

ksylvan avatar Apr 25 '25 14:04 ksylvan