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..3136a43 100644 --- a/src/commands/init.rs +++ b/src/commands/init.rs @@ -1,6 +1,11 @@ -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. @@ -14,6 +19,14 @@ 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 + 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..7609e24 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -2,6 +2,9 @@ use assert_cmd::Command; use rstest_reuse::{self, *}; use std::path::{Path, PathBuf}; +// const ZSH_PRELUDE: &str = "autoload -U +X compinit && compinit"; +const ZSH_PRELUDE: &str = "compinit"; + /// Command to run env-select pub fn env_select() -> Command { let mut command = Command::cargo_bin("es").unwrap(); @@ -47,7 +50,15 @@ pub fn execute_script( // Inject the function source into the script let function_source = String::from_utf8(assert.get_output().stdout.clone()) .expect("Function output is not valid UTF-8"); - let script = format!("{function_source} {script}"); + + // Zsh requires an extra command to enable completion. Typically the user + // would have this set in their .zshrc + let mut script_components = Vec::new(); + if shell_kind == "zsh" { + script_components.push(ZSH_PRELUDE); + } + script_components.extend([&function_source, script]); + let script = script_components.join("\n"); let shell = shell_path(shell_kind); let mut command = Command::new(&shell);