casey / just

šŸ¤– Just a command runner
https://just.systems
Creative Commons Zero v1.0 Universal
21.17k stars 470 forks source link

Completion confused when using --justfile #936

Open neur0manc opened 3 years ago

neur0manc commented 3 years ago

Hi, great project, thank you so much!

Intro

I laughed at the comment "Iā€™m pretty sure that nobody actually uses this feature" here, because I've been using exactly that for my global Makefile for a while now. I had a shell alias mmake that executes some tasks to manage my macOS workstation. For example upgrading all homebrew packages (mmake upgrade) or save a list of installed homebrew packages (mmake freeze). So I'm glad that this is implemented. šŸ‘

The Actual Issue

The shell completion that is generated with zsh does not take that switch into account it seems. When defining the alias .just as just -f=~/.justfile, .just -l will display the recipes of the justfile in the current directory instead of the recipes defined in ~/.justfile.

Have a great day šŸŒ»

casey commented 3 years ago

I laughed at the comment "Iā€™m pretty sure that nobody actually uses this feature" here, because I've been using exactly that for my global Makefile for a while now. I

Awesome, that's great!

One note, as of version 0.10.0 which was just released, just will look for files named .justfile in addition to justfile, so just may find your global justfile when you don't want it to. If you don't mind this, no change is necessary, but if you want to avoid it, you could name it something like ~/.global.justfile.

The shell completion that is generated with zsh does not take that switch into account it seems. When defining the alias .just as just -f=~/.justfile, .just -l will display the recipes of the justfile in the current directory instead of the recipes defined in ~/.justfile.

Gotcha, that's tricky. The zsh completions are auto-generated by clap, the argument parsing library just uses, with improvements by various contributors. I'll leave this issue open, in case someone wants to dive in, but I don't actually have much experience modifying the completions themselves, or with how zsh completions work in general, so it might be a while before this gets tackled.

neur0manc commented 3 years ago

One note, as of version 0.10.0 which was just released

Oh, thanks for the suggestion. I just did that.

I'll leave this issue open, in case someone wants to dive in, but I don't actually have much experience modifying the completions themselves, or with how zsh completions work in general, so it might be a while before this gets tackled.

Of course, no pressure šŸ™‚

casey commented 3 years ago

One note for anyone who wants to take this on, I think this could be made easier by adding a subcommand, something like --print-justfile-location. The autocomplete script could add --print-justfile-location to any just invocation, in this case one from an alias like just -f ~/justfile, so you could get the justfile location programmatically.

casey commented 2 years ago

Please thumbs-up the original issue instead of commenting, since commenting generates a notification. Thank you!

0xjams commented 4 months ago

Hi everyone

I'm using oh-my-zsh, so this script is in ~/.oh-my-zsh/custom/plugins/just. I wanted completions to work both with the -f and with the -g flags.

Here's the script I've been testing:

#compdef just

autoload -U is-at-least

# Toggle debugging on or off
JUST_DEBUG=0

_just_debug() {
    if [[ $JUST_DEBUG -eq 1 ]]; then
        echo "Debug: $*" >&2
    fi
}

_just() {
    _just_debug "Entering _just function"
    typeset -A opt_args
    typeset -a _arguments_options
    local ret=1

    if is-at-least 5.2; then
        _arguments_options=(-s -S -C)
    else
        _arguments_options=(-s -C)
    fi

    local context curcontext="$curcontext" state line
    local justfile=""
    local global_justfile=""
    local common=(
        '--chooser=[Override binary invoked by `--choose`]: : ' \
        '--color=[Print colorful output]: :(auto always never)' \
        '--command-color=[Echo recipe lines in <COMMAND-COLOR>]: :(black blue cyan green purple red yellow)' \
        '--dump-format=[Dump justfile as <FORMAT>]:FORMAT:(just json)' \
        '--list-heading=[Print <TEXT> before list]:TEXT: ' \
        '--list-prefix=[Print <TEXT> before each list item]:TEXT: ' \
        '-f+[Use <JUSTFILE> as justfile]:justfile:_files' \
        '--justfile=[Use <JUSTFILE> as justfile]:justfile:_files' \
        '-g+[Use <GLOBAL-JUSTFILE> as global justfile]:global-justfile:_files' \
        '--global-justfile=[Use <GLOBAL-JUSTFILE> as global justfile]:global-justfile:_files' \
        '*--set=[Override <VARIABLE> with <VALUE>]: :(_just_variables)' \
        '--shell=[Invoke <SHELL> to run recipes]: : ' \
        '*--shell-arg=[Invoke shell with <SHELL-ARG> as an argument]: : ' \
        '-d+[Use <WORKING-DIRECTORY> as working directory. --justfile must also be set]: :_files' \
        '--working-directory=[Use <WORKING-DIRECTORY> as working directory. --justfile must also be set]: :_files' \
        '*-c+[Run an arbitrary command with the working directory, `.env`, overrides, and exports set]: : ' \
        '*--command=[Run an arbitrary command with the working directory, `.env`, overrides, and exports set]: : ' \
        '--completions=[Print shell completion script for <SHELL>]:SHELL:(bash elvish fish nushell powershell zsh)' \
        '()-l+[List available recipes]' \
        '()--list=[List available recipes]' \
        '-s+[Show recipe at <PATH>]: :(_just_commands)' \
        '--show=[Show recipe at <PATH>]: :(_just_commands)' \
        '(-E --dotenv-path)--dotenv-filename=[Search for environment file named <DOTENV-FILENAME> instead of `.env`]: : ' \
        '-E+[Load <DOTENV-PATH> as environment file instead of searching for one]: :_files' \
        '--dotenv-path=[Load <DOTENV-PATH> as environment file instead of searching for one]: :_files' \
        '--timestamp-format=[Timestamp format string]: : ' \
        '--check[Run `--fmt` in '\''check'\'' mode. Exits with 0 if justfile is formatted correctly. Exits with 1 and prints a diff if formatting is required.]' \
        '--yes[Automatically confirm all recipes.]' \
        '(-q --quiet)-n[Print what just would do without doing it]' \
        '(-q --quiet)--dry-run[Print what just would do without doing it]' \
        '--highlight[Highlight echoed recipe lines in bold]' \
        '--list-submodules[List recipes in submodules]' \
        '--no-aliases[Don'\''t show aliases in list]' \
        '--no-deps[Don'\''t run recipe dependencies]' \
        '--no-dotenv[Don'\''t load `.env` file]' \
        '--no-highlight[Don'\''t highlight echoed recipe lines in bold]' \
        '(-n --dry-run)-q[Suppress all output]' \
        '(-n --dry-run)--quiet[Suppress all output]' \
        '--shell-command[Invoke <COMMAND> with the shell used to run recipe lines and backticks]' \
        '--clear-shell-args[Clear shell arguments]' \
        '-u[Return list and summary entries in source order]' \
        '--unsorted[Return list and summary entries in source order]' \
        '--unstable[Enable unstable features]' \
        '*-v[Use verbose output]' \
        '*--verbose[Use verbose output]' \
        '--changelog[Print changelog]' \
        '--choose[Select one or more recipes to run using a binary chooser. If `--chooser` is not passed the chooser defaults to the value of $JUST_CHOOSER, falling back to `fzf`]' \
        '--dump[Print justfile]' \
        '-e[Edit justfile with editor given by $VISUAL or $EDITOR, falling back to `vim`]' \
        '--edit[Edit justfile with editor given by $VISUAL or $EDITOR, falling back to `vim`]' \
        '--evaluate[Evaluate and print all variables. If a variable name is given as an argument, only print that variable'\''s value.]' \
        '--fmt[Format and overwrite justfile]' \
        '--init[Initialize new justfile in project root]' \
        '--groups[List recipe groups]' \
        '--man[Print man page]' \
        '--summary[List names of available recipes]' \
        '--variables[List names of variables]' \
        '--timestamp[Print recipe command timestamps]' \
        '-h[Print help]' \
        '--help[Print help]' \
        '-V[Print version]' \
        '--version[Print version]' \
    )

    _just_debug "Calling _arguments with options: ${_arguments_options[@]}"
    _arguments "${_arguments_options[@]}" $common \
        '1: :_just_commands' \
        '*: :->args' \
        && ret=0

    _just_debug "_arguments returned $ret"

    case $state in
        args)
            curcontext="${curcontext%:*}-${words[2]}:"

            local lastarg=${words[${#words}]}
            local recipe

            _just_debug "Current arguments: ${words[*]}"

            local cmds; cmds=(
                ${(s: :)$(_call_program commands just --summary)}
            )

            _just_debug "Retrieved commands: ${cmds[*]}"

            # Find first recipe name
            for ((i = 2; i < $#words; i++ )); do
                if [[ ${cmds[(I)${words[i]}]} -gt 0 ]]; then
                    recipe=${words[i]}
                    break
                fi
            done

            _just_debug "Found recipe: $recipe"

            if [[ $lastarg = */* ]]; then
                _arguments -s -S $common '*:: :_files'
            elif [[ $lastarg = *=* ]]; then
                _message "value"
            elif [[ $recipe ]]; then
                _message "`just --show $recipe`"
            else
                _arguments -s -S $common '*:: :_just_commands'
            fi
        ;;
    esac

    return ret
}

(( $+functions[_just_commands] )) ||
_just_commands() {
    _just_debug "Entering _just_commands function"
    [[ $PREFIX = -* ]] && return 1
    integer ret=1

    local justfile=""
    local global_justfile=""
    for ((i = 1; i < $#words; i++)); do
        if [[ ${words[i]} == "-f" || ${words[i]} == "--justfile" ]]; then
            justfile=${words[i+1]}
        elif [[ ${words[i]} == "-g" || ${words[i]} == "--global-justfile" ]]; then
            global_justfile=${words[i+1]}
        fi
    done

    _just_debug "Using justfile: $justfile"
    _just_debug "Using global justfile: $global_justfile"

    local variables_command="just --variables"
    local list_command="just --list"

    if [[ -n $justfile ]]; then
        variables_command+=" -f $justfile"
        list_command+=" -f $justfile"
    elif [[ -n $global_justfile ]]; then
        variables_command+=" -g $global_justfile"
        list_command+=" -g $global_justfile"
    fi

    _just_debug "Executing command: $variables_command"
    local variables_output
    variables_output=$(eval $variables_command 2>&1)
    _just_debug "Command output: $variables_output"
    local variables; variables=(
        ${(s: :)variables_output}
    )

    _just_debug "Executing command: $list_command"
    local commands_output
    commands_output=$(eval $list_command 2>&1)
    if [[ $commands_output == *"No justfile found"* ]]; then
        commands_output=""
    fi
    _just_debug "Command output: $commands_output"
    local commands; commands=(
        ${${${(M)"${(f)commands_output}":#    *}/ ##/}/ ##/:Args: }
    )

    _just_debug "Variables: ${variables[*]}"
    _just_debug "Commands: ${commands[*]}"

    if compset -P '*='; then
        case "${${words[-1]%=*}#*=}" in
            *) _message 'value' && ret=0 ;;
        esac
    else
        _describe -t variables 'variables' variables -qS "=" && ret=0
        _describe -t commands 'just commands' commands "$@"
    fi

    _just_debug "_describe returned $ret"
    return ret
}

if [ "$funcstack[1]" = "_just" ]; then
    (( $+functions[_just_variables] )) ||
_just_variables() {
    _just_debug "Entering _just_variables function"
    [[ $PREFIX = -* ]] && return 1
    integer ret=1

    local justfile=""
    local global_justfile=""
    for ((i = 1; i < $#words; i++)); do
        if [[ ${words[i]} == "-f" || ${words[i]} == "--justfile" ]]; then
            justfile=${words[i+1]}
        elif [[ ${words[i]} == "-g" || ${words[i]} == "--global-justfile" ]]; then
            global_justfile=${words[i+1]}
        fi
    done

    _just_debug "Using justfile: $justfile"
    _just_debug "Using global justfile: $global_justfile"

    local variables_command="just --variables"
    if [[ -n $justfile ]]; then
        variables_command+=" -f $justfile"
    elif [[ -n $global_justfile ]]; then
        variables_command+=" -g $global_justfile"
    fi

    _just_debug "Executing command: $variables_command"
    local variables_output
    variables_output=$(eval $variables_command 2>&1)
    _just_debug "Command output: $variables_output"
    local variables; variables=(
        ${(s: :)variables_output}
    )

    _just_debug "Variables: ${variables[*]}"

    if compset -P '*='; then
        case "${${words[-1]%=*}#*=}" in
            *) _message 'value' && ret=0 ;;
        esac
    else
        _describe -t variables 'variables' variables && ret=0
    fi

    _just_debug "_describe returned $ret"
    return ret
}

_just "$@"
else
    compdef _just just
fi

It has a debug flag so that it's easier to see what happens when Tab is pressed.

I'd love to hear your opinions and any ideas to improve it. This is not ready for prime time as it hasn't been tested enough.