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

Added ZSH Completion Shim Script #1643

Merged
merged 2 commits into from Feb 18, 2022

Conversation

JohnnyWombwell
Copy link
Contributor

Added dotnet-suggest ZSH completion shim and associated support.

For original discussion see: #1618

@dnfadmin
Copy link

dnfadmin commented Feb 15, 2022

CLA assistant check
All CLA requirements met.

@baronfel
Copy link
Member

hey @JohnnyWombwell, I haven't been able to get this to work on my setup, maybe you can see something I'm doing wrong after fiddling for a while. I'm on WSL ubuntu 20.04 for context.

zshrc:

# Set up the prompt

autoload -Uz promptinit
promptinit
prompt adam1

setopt histignorealldups sharehistory

# Use emacs keybindings even if our EDITOR is set to vi
bindkey -e

bindkey "^[[1;5C" forward-word
bindkey "^[[1;5D" backward-word

# Keep 1000 lines of history within the shell and save it to ~/.zsh_history:
HISTSIZE=1000
SAVEHIST=1000
HISTFILE=~/.zsh_history

# Use modern completion system
autoload -Uz compinit
compinit

zstyle ':completion:*' auto-description 'specify: %d'
zstyle ':completion:*' completer _expand _complete _correct _approximate
zstyle ':completion:*' format 'Completing %d'
zstyle ':completion:*' group-name ''
zstyle ':completion:*' menu select=2
eval "$(dircolors -b)"
zstyle ':completion:*:default' list-colors ${(s.:.)LS_COLORS}
zstyle ':completion:*' list-colors ''
zstyle ':completion:*' list-prompt %SAt %p: Hit TAB for more, or the character to insert%s
zstyle ':completion:*' matcher-list '' 'm:{a-z}={A-Z}' 'm:{a-zA-Z}={A-Za-z}' 'r:|[._-]=* r:|=* l:|=*'
zstyle ':completion:*' menu select=long
zstyle ':completion:*' select-prompt %SScrolling active: current selection at %p%s
zstyle ':completion:*' use-compctl false
zstyle ':completion:*' verbose true

zstyle ':completion:*:*:kill:*:processes' list-colors '=(#b) #([0-9]#)*=0=01;31'
zstyle ':completion:*:kill:*' command 'ps -u $USER -o pid,%cpu,tty,cputime,cmd'

export PATH="$HOME/.dotnet/tools:$PATH"

# dotnet suggest shell complete script start
_dotnet_zsh_complete()
{
    local fullpath=`which ${words[1]}`
    local position line
    read -nl position
    position=$(($position-1))
    read -l line
    line=$(echo "${line}" | sed s/\"/'\\\"'/g)
    local completions=`dotnet suggest get --executable "$fullpath" --position ${position} -- "${line}"`
    reply=( "${(ps:\n:)completions}" )
}
compctl -K _dotnet_zsh_complete + -f `dotnet suggest list`
export DOTNET_SUGGEST_SCRIPT_VERSION="1.0.0"
# dotnet suggest shell complete script end

sample app:

using System.CommandLine;
using System.CommandLine.Builder;
using System.CommandLine.Parsing;

var c = new RootCommand("scratch");
var o = new Option<int>("--age", "how long have you been itchy?");
c.AddOption(o);

return await new CommandLineBuilder(c).UseVersionOption()
                   .UseHelp()
                   .UseEnvironmentVariableDirective()
                   .UseParseDirective()
                   .UseSuggestDirective()
                   .UseTypoCorrections()
                   .UseParseErrorReporting()
                   .UseExceptionHandler()
                   .CancelOnProcessTermination().Build().InvokeAsync(args);

I added the .NET nightly feeds with

dotnet nuget add source -n "ms nightly" "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-libraries/nuget/v3/index.json

and added System.Commandline with

dotnet add package System.CommandLine --version 2.0.0-beta3.22114.1

I manually registered this app (called scratch) in via dotnet suggest register, but when I attempt tab completion from the bin directory on the scratch executable, zsh only completes files.

Do you have any pointers? I've been fiddling with this for a while but nothing's worked for me.

@baronfel
Copy link
Member

wow, I think it was the zstyle ':completion:*' use-compctl false in my .zshrc. I did some zsh debugging by invoking CTRL+x, ? at the point of completion to get a trace file, and found the following lines:

     +_default:5> zstyle -s :completion::complete:scratch:: use-compctl ctl
     +_default:6> [[ false != (no|false|0|off) ]]
     +_default:15> _files

so we can clearly see the _files completion being called there. on a hunch, I compared them with the value of use-compctl set to true:

     +_default:5> zstyle -s :completion::complete:scratch:: use-compctl ctl
     +_default:6> [[ true != (no|false|0|off) ]]
     +_default:7> local opt
     +_default:9> opt=( ) 
     +_default:10> [[ true = *first* ]]
     +_default:11> [[ true = *default* ]]
     +_default:12> compcall
      +_dotnet_zsh_complete:2> which ./scratch

boom, we're in the _dotnet_zsh_complete command!

I don't know why the use-compctl style was set to false, but we should probably document that as a prerequisite.

@baronfel
Copy link
Member

baronfel commented Feb 17, 2022

@JohnnyWombwell please don't hate me. I got mildly obsessed with this and made a version of the script I consider to be more zsh-idiomatic (as well as leaving to door open for a very near future where descriptions for options/arguments will become available!)

Here's what I ended up with:

# dotnet suggest shell complete script start
_dotnet_zsh_complete()
{
    # debug lines, uncomment to get state variables passed to this function
    # echo "\n\n\nstate:\t'$state'"
    # echo "line:\t'$line'"
    # echo "words:\t$words"

    # Get full path to script because dotnet-suggest needs it
    # NOTE: this requires a command registered with dotnet-suggest be
    # on the PATH
    full_path=`which ${words[1]}` # zsh arrays are 1-indexed
    # Get the full line
    # $words array when quoted like this gets expanded out into the full line
    full_line="$words"

    # Get the completion results, will be newline-delimited
    completions=$(dotnet suggest get --executable "$full_path" -- "$full_line")
    # explode the completions by linefeed instead of by spaces into the descriptions for the
    # _values helper function.
    _values 'suggestions' ${(f)completions}
}

# apply this function to each command the dotnet-suggest knows about
compdef _dotnet_zsh_complete $(dotnet-suggest list)

export DOTNET_SUGGEST_SCRIPT_VERSION="1.0.0"
# dotnet suggest shell complete script end

scratch

@baronfel
Copy link
Member

Saving this for later:

# dotnet suggest shell complete script start
_dotnet_zsh_complete()
{
    # debug lines, uncomment to get state variables passed to this function
    # echo "\n\n\nstate:\t'$state'"
    # echo "line:\t'$line'"
    # echo "words:\t$words"

    # Get full path to script because dotnet-suggest needs it
    # NOTE: this requires a command registered with dotnet-suggest be
    # on the PATH
    full_path=`which ${words[1]}` # zsh arrays are 1-indexed
    # Get the full line
    # $words array when quoted like this gets expanded out into the full line
    full_line="$words"

    # Get the completion results, will be newline-delimited
    completions=$(dotnet suggest get --executable "$full_path" -- "$full_line")
    # explode the completions by linefeed instead of by spaces into the descriptions for the
    # _values helper function.
    
    exploded=(${(f)completions})
    # for later - once we have descriptions from dotnet suggest, we can stitch them
    # together like so:
    # described=()
    # for i in {1..$#exploded}; do
    #     argument="${exploded[$i]}"
    #     description="hello description $i"
    #     entry=($argument"["$description"]")
    #     described+=("$entry")
    # done
    _values 'suggestions' $exploded
}

# apply this function to each command the dotnet-suggest knows about
compdef _dotnet_zsh_complete $(dotnet-suggest list)

export DOTNET_SUGGEST_SCRIPT_VERSION="1.0.0"
# dotnet suggest shell complete script end

we don't get descriptions from dotnet-suggest, but when we do we need to construct an array with lines made up of "completion-item[description]" rows, then it all just works:
descriptions

@JohnnyWombwell
Copy link
Contributor Author

@JohnnyWombwell please don't hate me. I got mildly obsessed with this and made a version of the script I consider to be more zsh-idiomatic (as well as leaving to door open for a very near future where descriptions for options/arguments will become available!)

lol no, that's much cleaner, good work 😄

I was a little uncomfortable with the use of the old compctl completions and this sorts that nicely.

How do you want to proceed? I'm happy to update the PR with your script.

@baronfel
Copy link
Member

I want you to have credit here, so if you're ok with it you could take the script content from #1643 (comment) wholesale and apply it in this branch.

@jonsequitur jonsequitur merged commit 4fac9d3 into dotnet:main Feb 18, 2022
@jonsequitur
Copy link
Contributor

Thanks @JohnnyWombwell! This is fantastic.

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

Successfully merging this pull request may close these issues.

None yet

4 participants