oclif / plugin-autocomplete

autocomplete plugin for oclif
MIT License
83 stars 31 forks source link

Add autocompletion for target orgs #620

Open gilgourevitch opened 6 months ago

gilgourevitch commented 6 months ago

As a Salesforce user on multiple orgs, with multiple Sandboxes, the target org aliases and usernames are case-sensitive, and when the CLI is authenticated locally in multiple orgs, it become more difficult to find the correct alias. Adding autocompletion for this flag could ease the usage of the CLI.

mdonnalley commented 6 months ago

@gilgourevitch This is an awesome idea 🏆

We definitely want to support this for sf but plugin-autocomplete isn't the right spot for it, since oclif shouldn't be aware of anything salesforce related.

Ideally, plugin-autocomplete would be able to merge together various autocomplete scripts. So, for instance, sf would ship logic for target-org autocompletion that plugin-autocomplete would merge into it's own generated scripts.

That feature, unfortunately, doesn't exist yet. I'm going to put this on our backlog so we can prioritize it

git2gus[bot] commented 6 months ago

This issue has been linked to a new work item: W-15022864

gilgourevitch commented 6 months ago

Hello @mdonnalley , and thank you for your feedback. I understand the concept. Do you already have an idea of the future code structure and how the custom scripts could be passed to the autocomplete plugin ? Having this in mind, I could start developing a script for this use-case, or if I can help on the core code, do not hesitate.

mdonnalley commented 6 months ago

@gilgourevitch I don't have anything in mind for how this would actually be implemented - just a high level idea of the feature.

@cristiand391 is our resident autocomplete guru so I would defer to him how to implement this

cristiand391 commented 6 months ago

Hey @gilgourevitch 👋🏼

I explored this a while ago but never fully implemented it, here's a few things that would be needed:

1) Expose a command that returns completion candidates: This is the most common approach, we could add a hidden __complete that accepts some args and return the completion candidates. The shell completion functions would pass the current line to it and get back the values to suggest.

2) How args/flags define dynamic completion logic The __complete cmd needs a way to access the completion logic for the current args/flags, to follow the oclif style this could be another prop in a flag.

For the --target-dev-hub flag in some sf commands, this would be added here like the defaultHelp prop: https://github.com/salesforcecli/sf-plugins-core/blob/5413761097259b1d060831b0bcc82f2550fc7c89/src/flags/orgFlags.ts#L175-L189

With those 2 you could get dynamic completions, it would work like this:

sf org create scratch --target-org <TAB>

***
1) completion script passes current line to `__complete`:
sf __complete `sf org create scratch --target-org `

2) `__complete` resolves the command name and flag, access the completion func defined for the flag and executes it

3) return values in a standard format, then pass those down to each shell completion helpers

Alternative to __complete: call node with a script containing the arg/flag comp. logic

This would be a bit tricky because it requires extracting the logic from code and calling it outside the "oclif" context but would be way faster.

I did this in my fuzzy completion script for sf: https://github.com/cristiand391/sf-plugin-fzf-cmp https://github.com/cristiand391/sf-plugin-fzf-cmp/blob/main/src/comp-gen.ts

_fzf_complete_sf() {
  if [[ "$@" == "sf " || "$@" == "sf which " || "$@" == "sf help " ]]; then
    local commands_file preview_command

    commands_file="/Users/cdominguez/Library/Caches/sf/autocomplete/fzf-cmp/commands.json"
    preview_command="jq --arg cmd {} -r '. as \$commands | map(.id == \$cmd) | index(true) | \$commands[.].summary' $commands_file"

    _fzf_complete --reverse --preview "$preview_command" --preview-window wrap,down,1 --prompt="sf> " -- "$@" < <(
      jq -r '.[] | .id' $commands_file
    )
  else
    local comp=$(node /Users/cdominguez/.local/share/sf/node_modules/@cristiand391/sf-plugin-fzf-cmp/lib/shell-completion.js "$@")
    _fzf_complete --select-1 --ansi --prompt="sf> " -- "$@" < <(
      echo $comp
    )
  fi
}

_fzf_complete_sf_post() {
  cut -f 1
}

Considerations

1) UX The hidden complete command approach works great for compiled languages like Go and Rust because it's fast to generate these values, but in JS this could a pain if each dynamic completion takes a more than a few seconds. NOTE: I would be mostly concerted about __complete load performance (it has to resolve the command) rather than each flag logic (these can always be improved later).

As an example you can try the npm completion, it's way slower and I try to never use it.

2) Caching Kinda related to 1, some frameworks/extension implement some caching mechanism so that dynamic completions feel snappy. If __complete it's slow we can explore caching options.

Related: 1) jayree's fork of plugin-autocomplete: https://github.com/jayree/sfdx-autocomplete-plugin

it does org username completion and caching

2) carapace's cache: https://rsteube.github.io/blog/2022/a-pragmatic-approach-to-shell-completion.html#caching It's a completion for the cobra framework (Go), but it's a great blog post for learning about this stuff.

3) spf13/cobra completion: https://github.com/spf13/cobra/blob/main/site/content/completions/_index.md

I'm happy to answer any questions if you wanna give this a try :)

rsteube commented 6 months ago

fyi https://github.com/rsteube/carapace-spec

jbbarth commented 6 months ago

I'm super interested in that as well, as we're building our own CLI at my current company on top of Oclif and not having dynamic completion reduces the value proposal of our CLI.

Thanks @cristiand391 for the extensive notes. I'm not sure I get that one though:

The hidden complete command approach works great for compiled languages like Go and Rust because it's fast to generate these values, but in JS this could a pain if each dynamic completion takes a more than a few seconds.

My own CLI takes 150ms to execute "--help" end-to-end, and the "heroku" CLI, which has arguably a lot of commands, take 0.8s. I think it's not unreasonable to consider a dynamic completion can go over 1s, but I don't think that will take "a few seconds".

I'm willing to spend some time on the topic, though my knowledge of oclif internals are limited. Let me know if there's some valuable work to start in that space.

cristiand391 commented 6 months ago

My own CLI takes 150ms to execute "--help" end-to-end, and the "heroku" CLI, which has arguably a lot of commands, take 0.8s. I think it's not unreasonable to consider a dynamic completion can go over 1s, but I don't think that will take "a few seconds".

I was thinking more about completion that requires doing some HTTP requests, reading local files and similar should be fine. That said, I think caching can be implemented by developers rather having oclif provide a generic way to do it (maybe people want. to store it in json, yml, etc).

I saw the angular CLI puts a ... in the current line while fetching completions, never looked at how it works but it's a nice way to let users know what happens: https://angular.io/generated/images/guide/cli/completion.gif

I'm willing to spend some time on the topic, though my knowledge of oclif internals are limited. Let me know if there's some valuable work to start in that space.

for a POC I think you can go with points 1 & 2 mentioned in my comment above, a __complete command that executes a flag's completion function and print the returned values.

After that you will have to pick either zsh or powershell completion to test it (there's bash support too but it's has a few bugs and I'm not very familiar with it, we may rewrite it in the future to fix some bugs/bring it to feature parity with others).

also check the resources linked in this PR: https://github.com/oclif/plugin-autocomplete/pull/406

and also the GitHub CLI generated completion script, you can see there how it calls its own __complete command and passes the values to zsh utils.