diff --git a/CHANGELOG.md b/CHANGELOG.md index 9798977..c0abecf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,9 +4,8 @@ ### Added -- Add shell completions, accessed by enabling the `COMPLETE` environment variable [#6](https://github.com/LucasPickering/env-select/issues/6) - - For example, adding `COMPLETE=fish es | source` to your `fish.config` will enable completions for fish - - [See docs](https://env-select.lucaspickering.me/book/user_guide/shell_completions.html) for more info and a list of supported shells +- Add shell completions [#6](https://github.com/LucasPickering/env-select/issues/6) + - These will be automatically loaded as part of the existing shell integrations, so if you already have `es init | source` or similar in your shell init file, you don't need to change anything - Add command aliases - `es r` alias for `es run`, `es s` for `es set` diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index 1502663..e5f35a3 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -15,7 +15,6 @@ - [Inheritance & Cascading Configs](./user_guide/inheritance.md) - [Side Effects](./user_guide/side_effects.md) - [`es run` and Shell Interactions](./user_guide/run_advanced.md) -- [Shell Completions](./user_guide/shell_completions.md) # API Reference diff --git a/docs/src/install.md b/docs/src/install.md index 00c2d15..7e7b9b8 100644 --- a/docs/src/install.md +++ b/docs/src/install.md @@ -6,7 +6,7 @@ See [the artifacts page](/artifacts) to download and install `es` using your pre While not strictly required, it's highly recommended to install the `es` shell function. This wraps the `es` binary command, allowing it to automatically modify your current shell environment with the `es set` subcommand. Otherwise, you'll have to manually pipe the output of `es set` to `source`. -> If you only plan to use the `es run` command, this is **not relevant**. +> If you only plan to use the `es run` command, and don't care about shell tab completions, this is **not relevant**. This is necessary because a child process is not allowed to modify its parent's environment. That means the `es` process cannot modify the environment of the invoking shell. The wrapping shell function takes the output of `es` and runs it in that shell session to update the environment. diff --git a/docs/src/user_guide/shell_completions.md b/docs/src/user_guide/shell_completions.md deleted file mode 100644 index e90417f..0000000 --- a/docs/src/user_guide/shell_completions.md +++ /dev/null @@ -1,43 +0,0 @@ -# Shell Completions - -Env-select provides tab completions for most shells. For the full list of supported shells, [see the clap docs](https://docs.rs/clap_complete/latest/clap_complete/aot/enum.Shell.html). - -> Note: Env-select uses clap's native shell completions, which are still experimental. [This issue](https://github.com/clap-rs/clap/issues/3166) outlines the remaining work to be done. - -To source your completions: - -**WARNING:** We recommend re-sourcing your completions on upgrade. -These completions work by generating shell code that calls into `your_program` while completing. That interface is unstable and a mismatch between the shell code and `your_program` may result in either invalid completions or no completions being generated. - -For this reason, we recommend generating the shell code anew on shell startup so that it is "self-correcting" on shell launch, rather than writing the generated completions to a file. - -## Bash - -```bash -echo "source <(COMPLETE=bash es)" >> ~/.bashrc -``` - -## Elvish - -```elvish -echo "eval (E:COMPLETE=elvish es | slurp)" >> ~/.elvish/rc.elv -``` - -## Fish - -```fish -echo "source (COMPLETE=fish es | psub)" >> ~/.config/fish/config.fish -``` - -## Powershell - -```powershell -echo "COMPLETE=powershell es | Invoke-Expression" >> $PROFILE -``` - -## Zsh - -````zsh -echo "source <(COMPLETE=zsh es)" >> ~/.zshrc -``` -```` diff --git a/src/commands/init.rs b/src/commands/init.rs index a992d64..1496a65 100644 --- a/src/commands/init.rs +++ b/src/commands/init.rs @@ -1,11 +1,20 @@ -use crate::commands::{CommandContext, SubcommandTrait}; +use crate::{ + commands::{CommandContext, SubcommandTrait}, + Args, COMMAND_NAME, +}; use anyhow::Context; -use clap::Parser; +use clap::{CommandFactory, Parser}; +use clap_complete::CompleteEnv; +use std::env; /// Configure the shell environment for env-select. Intended to be piped /// to `source` as part of your shell startup. #[derive(Clone, Debug, Parser)] -pub struct InitCommand; +pub struct InitCommand { + /// Don't include completion script in output + #[clap(long, hide = true)] // Only for testing + no_completions: bool, +} impl SubcommandTrait for InitCommand { fn execute(self, context: CommandContext) -> anyhow::Result<()> { @@ -14,6 +23,16 @@ impl SubcommandTrait for InitCommand { .init_script() .context("Error generating shell init script")?; print!("{script}"); + + // Print the command to enable shell completions as well. CompleteEnv + // doesn't expose the inner machinery that would allow us to print the + // line directly, so we have to enable the env var that triggers it + if !self.no_completions { + env::set_var("COMPLETE", context.shell.kind.to_string()); + CompleteEnv::with_factory(Args::command) + .try_complete([COMMAND_NAME], None)?; + } + Ok(()) } } diff --git a/src/main.rs b/src/main.rs index a360c28..820015f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -17,10 +17,12 @@ use log::{error, LevelFilter}; use clap_complete::CompleteEnv; use std::{path::PathBuf, process::ExitCode}; +const COMMAND_NAME: &str = "es"; + /// A utility to select between predefined values or sets of environment /// variables. #[derive(Debug, Parser)] -#[clap(bin_name = "es", author, version, about, long_about = None)] +#[clap(bin_name = COMMAND_NAME, author, version, about, long_about = None)] pub struct Args { #[command(subcommand)] command: Commands, diff --git a/tests/common/mod.rs b/tests/common/mod.rs index b5ee742..c312f51 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -42,6 +42,13 @@ pub fn execute_script( es.args(["-s", shell_kind]); } es.arg("init"); + if shell_kind == "zsh" { + // Completion script doesn't work with zsh because the compdef command + // isn't present by default. I tried loading it with compinit but that + // has other issues. This is a shortcut. We aren't trying to test + // completion anyway, it's just a bonus. + es.arg("--no-completions"); + } let assert = es.assert().success(); // Inject the function source into the script