From e82e0076708a1664323b99b0358586b98cb0c58c Mon Sep 17 00:00:00 2001 From: Wolf Vollprecht Date: Wed, 26 Feb 2025 17:50:12 +0100 Subject: [PATCH 01/17] implement shell completion for `bash`, `zsh`, and `fish` --- Cargo.lock | 9 ++++----- Cargo.toml | 1 - src/cli/shell.rs | 22 +++++++++++++++++----- 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0a9a3d1e0c..8b33c3fbac 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1008,21 +1008,20 @@ dependencies = [ [[package]] name = "bzip2" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75b89e7c29231c673a61a46e722602bcd138298f6b9e81e71119693534585f5c" +checksum = "49ecfb22d906f800d4fe833b6282cf4dc1c298f5057ca0b5445e5c209735ca47" dependencies = [ "bzip2-sys", ] [[package]] name = "bzip2-sys" -version = "0.1.12+1.0.8" +version = "0.1.13+1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72ebc2f1a417f01e1da30ef264ee86ae31d2dcd2d603ea283d3c244a883ca2a9" +checksum = "225bff33b2141874fe80d71e07d6eec4f85c5c216453dd96388240f96e1acc14" dependencies = [ "cc", - "libc", "pkg-config", ] diff --git a/Cargo.toml b/Cargo.toml index 1ac91225ca..db625f7617 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -131,7 +131,6 @@ rattler_shell = { version = "0.22.22", default-features = false } rattler_solve = { version = "1.3.11", default-features = false } rattler_virtual_packages = { version = "2.0.6", default-features = false } - # Bumping this to a higher version breaks the Windows path handling. url = "2.5.4" uv-auth = { git = "https://github.com/astral-sh/uv", tag = "0.6.1" } diff --git a/src/cli/shell.rs b/src/cli/shell.rs index 69be713d07..81587b036e 100644 --- a/src/cli/shell.rs +++ b/src/cli/shell.rs @@ -127,6 +127,7 @@ fn start_cmdexe( let mut process = command.spawn().into_diagnostic()?; Ok(process.wait().into_diagnostic()?.code()) } +use crate::prefix::Prefix; /// Starts a UNIX shell. /// # Arguments @@ -139,6 +140,7 @@ async fn start_unix_shell( args: Vec<&str>, env: &HashMap, prompt: String, + prefix: &Prefix, ) -> miette::Result> { // create a tempfile for activation let mut temp_file = tempfile::Builder::new() @@ -153,6 +155,12 @@ async fn start_unix_shell( shell_script.set_env_var(key, value).into_diagnostic()?; } + if let Some(completions_dir) = shell.completion_script_location() { + shell_script + .source_completions(&prefix.root().join(completions_dir)) + .into_diagnostic()?; + } + const DONE_STR: &str = "=== DONE ==="; shell_script.echo(DONE_STR).into_diagnostic()?; @@ -254,7 +262,7 @@ pub async fn execute(args: Args) -> miette::Result<()> { let environment = workspace.environment_from_name_or_env_var(args.environment)?; // Make sure environment is up-to-date, default to install, users can avoid this with frozen or locked. - let (lock_file_data, _prefix) = get_update_lock_file_and_prefix( + let (lock_file_data, prefix) = get_update_lock_file_and_prefix( &environment, UpdateMode::QuickValidate, UpdateLockFileOptions { @@ -312,10 +320,14 @@ pub async fn execute(args: Args) -> miette::Result<()> { let res = match interactive_shell { ShellEnum::NuShell(nushell) => start_nu_shell(nushell, env, prompt_hook).await, ShellEnum::PowerShell(pwsh) => start_powershell(pwsh, env, prompt_hook), - ShellEnum::Bash(bash) => start_unix_shell(bash, vec!["-l", "-i"], env, prompt_hook).await, - ShellEnum::Zsh(zsh) => start_unix_shell(zsh, vec!["-l", "-i"], env, prompt_hook).await, - ShellEnum::Fish(fish) => start_unix_shell(fish, vec![], env, prompt_hook).await, - ShellEnum::Xonsh(xonsh) => start_unix_shell(xonsh, vec![], env, prompt_hook).await, + ShellEnum::Bash(bash) => { + start_unix_shell(bash, vec!["-l", "-i"], env, prompt_hook, &prefix).await + } + ShellEnum::Zsh(zsh) => { + start_unix_shell(zsh, vec!["-l", "-i"], env, prompt_hook, &prefix).await + } + ShellEnum::Fish(fish) => start_unix_shell(fish, vec![], env, prompt_hook, &prefix).await, + ShellEnum::Xonsh(xonsh) => start_unix_shell(xonsh, vec![], env, prompt_hook, &prefix).await, _ => { miette::bail!("Unsupported shell: {:?}", interactive_shell) } From e7c714f8ae0c1020082042aa31780129eb03e112 Mon Sep 17 00:00:00 2001 From: Wolf Vollprecht Date: Fri, 28 Feb 2025 17:24:27 +0100 Subject: [PATCH 02/17] use `[shell]` table in config --- crates/pixi_config/src/lib.rs | 159 ++++++++++++++++++++++++++++------ src/cli/config.rs | 3 +- 2 files changed, 133 insertions(+), 29 deletions(-) diff --git a/crates/pixi_config/src/lib.rs b/crates/pixi_config/src/lib.rs index c8e481beee..0bbf1d09e9 100644 --- a/crates/pixi_config/src/lib.rs +++ b/crates/pixi_config/src/lib.rs @@ -121,10 +121,14 @@ pub struct ConfigCliPrompt { #[arg(long)] change_ps1: Option, } + impl From for Config { fn from(cli: ConfigCliPrompt) -> Self { Self { - change_ps1: cli.change_ps1, + shell: ShellConfig { + change_ps1: cli.change_ps1, + ..Default::default() + }, ..Default::default() } } @@ -133,7 +137,7 @@ impl From for Config { impl ConfigCliPrompt { pub fn merge_config(self, config: Config) -> Config { let mut config = config; - config.change_ps1 = self.change_ps1.or(config.change_ps1); + config.shell.change_ps1 = self.change_ps1.or(config.shell.change_ps1); config } } @@ -181,12 +185,18 @@ pub struct ConfigCliActivation { /// Do not use the environment activation cache. (default: true except in experimental mode) #[arg(long)] force_activate: bool, + + #[arg(long)] + no_completion: Option, } impl ConfigCliActivation { pub fn merge_config(self, config: Config) -> Config { let mut config = config; - config.force_activate = Some(self.force_activate); + config.shell.force_activate = Some(self.force_activate); + if let Some(no_completion) = self.no_completion { + config.shell.source_completion_scripts = Some(!no_completion); + } config } } @@ -194,7 +204,11 @@ impl ConfigCliActivation { impl From for Config { fn from(cli: ConfigCliActivation) -> Self { Self { - force_activate: Some(cli.force_activate), + shell: ShellConfig { + force_activate: Some(cli.force_activate), + source_completion_scripts: None, + change_ps1: None, + }, ..Default::default() } } @@ -566,13 +580,6 @@ pub struct Config { #[serde(skip_serializing_if = "Vec::is_empty")] pub default_channels: Vec, - /// If set to true, pixi will set the PS1 environment variable to a custom - /// value. - #[serde(default)] - #[serde(alias = "change_ps1")] // BREAK: remove to stop supporting snake_case alias - #[serde(skip_serializing_if = "Option::is_none")] - pub change_ps1: Option, - /// Path to the file containing the authentication token. #[serde(default)] #[serde(alias = "authentication_override_file")] // BREAK: remove to stop supporting snake_case alias @@ -624,10 +631,10 @@ pub struct Config { #[serde(skip_serializing_if = "Option::is_none")] pub detached_environments: Option, - /// The option to disable the environment activation cache + /// Shell-specific configuration #[serde(default)] - #[serde(skip_serializing_if = "Option::is_none")] - pub force_activate: Option, + #[serde(skip_serializing_if = "ShellConfig::is_default")] + pub shell: ShellConfig, /// Experimental features that can be enabled. #[serde(default)] @@ -638,13 +645,25 @@ pub struct Config { #[serde(default)] #[serde(skip_serializing_if = "ConcurrencyConfig::is_default")] pub concurrency: ConcurrencyConfig, + + ////////////////////// + // Deprecated fields // + ////////////////////// + + #[serde(default)] + #[serde(alias = "change_ps1")] // BREAK: remove to stop supporting snake_case alias + #[serde(skip_serializing_if = "Option::is_none")] + pub change_ps1: Option, + + #[serde(default)] + #[serde(skip_serializing_if = "Option::is_none")] + pub force_activate: Option, } impl Default for Config { fn default() -> Self { Self { default_channels: Vec::new(), - change_ps1: None, authentication_override_file: None, tls_no_verify: None, mirrors: HashMap::new(), @@ -655,9 +674,13 @@ impl Default for Config { s3_options: HashMap::new(), detached_environments: None, pinning_strategy: None, - force_activate: None, + shell: ShellConfig::default(), experimental: ExperimentalConfig::default(), concurrency: ConcurrencyConfig::default(), + + // Deprecated fields + change_ps1: None, + force_activate: None, } } } @@ -714,6 +737,44 @@ impl From<&Config> for rattler_repodata_gateway::ChannelConfig { } } +#[derive(Clone, Default, Debug, Deserialize, Serialize, PartialEq, Eq)] +#[serde(rename_all = "kebab-case")] +pub struct ShellConfig { + /// The option to disable the environment activation cache + #[serde(default)] + #[serde(skip_serializing_if = "Option::is_none")] + pub force_activate: Option, + + /// Whether to source completion scripts from the environment or not. + #[serde(default)] + #[serde(skip_serializing_if = "Option::is_none")] + pub source_completion_scripts: Option, + + /// If set to true, pixi will set the PS1 environment variable to a custom + /// value. + #[serde(default)] + #[serde(skip_serializing_if = "Option::is_none")] + pub change_ps1: Option, +} + +impl ShellConfig { + pub fn merge(self, other: Self) -> Self { + Self { + force_activate: other.force_activate.or(self.force_activate), + source_completion_scripts: other + .source_completion_scripts + .or(self.source_completion_scripts), + change_ps1: other.change_ps1.or(self.change_ps1), + } + } + + pub fn is_default(&self) -> bool { + self.force_activate.is_none() + && self.source_completion_scripts.is_none() + && self.change_ps1.is_none() + } +} + #[derive(thiserror::Error, Debug)] pub enum ConfigError { #[error("no file was found at {0}")] @@ -759,11 +820,21 @@ impl Config { // Deserialize the config and collect unused keys let mut unused_keys = Set::new(); - let config: Config = serde_ignored::deserialize(de, |path| { + let mut config: Config = serde_ignored::deserialize(de, |path| { unused_keys.insert(path.to_string()); }) .into_diagnostic()?; + if config.change_ps1.is_some() { + tracing::warn!("The `change_ps1` field is deprecated and will be removed in a future release. Please use the `shell.change-ps1` field instead."); + config.shell.change_ps1 = config.change_ps1; + } + + if config.force_activate.is_some() { + tracing::warn!("The `force_activate` field is deprecated and will be removed in a future release. Please use the `shell.force-activate` field instead."); + config.shell.force_activate = config.force_activate; + } + Ok((config, unused_keys)) } @@ -927,7 +998,6 @@ impl Config { pub fn get_keys(&self) -> &[&str] { &[ "default-channels", - "change-ps1", "authentication-override-file", "tls-no-verify", "mirrors", @@ -943,6 +1013,10 @@ impl Config { "pypi-config.index-url", "pypi-config.extra-index-urls", "pypi-config.keyring-provider", + "shell", + "shell.force-activate", + "shell.source-completion-scripts", + "shell.change-ps1", "s3-options", "s3-options.", "s3-options..endpoint-url", @@ -966,7 +1040,6 @@ impl Config { other.default_channels }, tls_no_verify: other.tls_no_verify.or(self.tls_no_verify), - change_ps1: other.change_ps1.or(self.change_ps1), authentication_override_file: other .authentication_override_file .or(self.authentication_override_file), @@ -985,10 +1058,14 @@ impl Config { }, detached_environments: other.detached_environments.or(self.detached_environments), pinning_strategy: other.pinning_strategy.or(self.pinning_strategy), - force_activate: other.force_activate, + shell: self.shell.merge(other.shell), experimental: self.experimental.merge(other.experimental), // Make other take precedence over self to allow for setting the value through the CLI concurrency: self.concurrency.merge(other.concurrency), + + // Deprecated fields that we can ignore as we handle them inside `shell.` field + change_ps1: None, + force_activate: None, } } @@ -1009,7 +1086,7 @@ impl Config { /// Retrieve the value for the change_ps1 field (defaults to true). pub fn change_ps1(&self) -> bool { - self.change_ps1.unwrap_or(true) + self.shell.change_ps1.unwrap_or(true) } /// Retrieve the value for the auth_file field. @@ -1044,7 +1121,7 @@ impl Config { } pub fn force_activate(&self) -> bool { - self.force_activate.unwrap_or(false) + self.shell.force_activate.unwrap_or(false) } pub fn experimental_activation_cache_usage(&self) -> bool { @@ -1083,9 +1160,6 @@ impl Config { .into_diagnostic()? .unwrap_or_default(); } - "change-ps1" => { - self.change_ps1 = value.map(|v| v.parse()).transpose().into_diagnostic()?; - } "authentication-override-file" => { self.authentication_override_file = value.map(PathBuf::from); } @@ -1294,6 +1368,34 @@ impl Config { _ => return Err(err), } } + key if key.starts_with("shell") => { + if key == "shell" { + if let Some(value) = value { + self.shell = serde_json::de::from_str(&value).into_diagnostic()?; + } else { + self.shell = ShellConfig::default(); + } + return Ok(()); + } else if !key.starts_with("shell.") { + return Err(err); + } + let subkey = key.strip_prefix("shell.").unwrap(); + match subkey { + "force-activate" => { + self.shell.force_activate = + value.map(|v| v.parse()).transpose().into_diagnostic()?; + } + "source-completion-scripts" => { + self.shell.source_completion_scripts = + value.map(|v| v.parse()).transpose().into_diagnostic()?; + } + "change-ps1" => { + self.shell.change_ps1 = + value.map(|v| v.parse()).transpose().into_diagnostic()?; + } + _ => return Err(err), + } + } _ => return Err(err), } @@ -1555,7 +1657,6 @@ UNUSED = "unused" solves: 5, ..ConcurrencyConfig::default() }, - change_ps1: Some(false), authentication_override_file: Some(PathBuf::default()), mirrors: HashMap::from([( Url::parse("https://conda.anaconda.org/conda-forge").unwrap(), @@ -1566,7 +1667,11 @@ UNUSED = "unused" use_environment_activation_cache: Some(true), }, loaded_from: Vec::from([PathBuf::from_str("test").unwrap()]), - force_activate: Some(true), + shell: ShellConfig { + force_activate: Some(true), + source_completion_scripts: None, + change_ps1: Some(false), + }, pypi_config: PyPIConfig { allow_insecure_host: Vec::from(["test".to_string()]), extra_index_urls: Vec::from([ diff --git a/src/cli/config.rs b/src/cli/config.rs index db99b1a497..3d5ffb8d42 100644 --- a/src/cli/config.rs +++ b/src/cli/config.rs @@ -328,7 +328,7 @@ fn partial_config(config: &mut Config, key: &str) -> miette::Result<()> { match key { "default-channels" => new.default_channels = config.default_channels.clone(), - "change-ps1" => new.change_ps1 = config.change_ps1, + "shell" => new.shell = config.shell.clone(), "tls-no-verify" => new.tls_no_verify = config.tls_no_verify, "authentication-override-file" => { new.authentication_override_file = config.authentication_override_file.clone() @@ -339,7 +339,6 @@ fn partial_config(config: &mut Config, key: &str) -> miette::Result<()> { _ => { let keys = [ "default-channels", - "change-ps1", "tls-no-verify", "authentication-override-file", "mirrors", From db8dd9a1346fd377bf6bbf80902980bfed3fc64a Mon Sep 17 00:00:00 2001 From: Wolf Vollprecht Date: Fri, 28 Feb 2025 17:31:26 +0100 Subject: [PATCH 03/17] hook up the shell hook --- crates/pixi_config/src/lib.rs | 1 - src/cli/shell_hook.rs | 18 ++++++++++++++++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/crates/pixi_config/src/lib.rs b/crates/pixi_config/src/lib.rs index 0bbf1d09e9..b9f41c2505 100644 --- a/crates/pixi_config/src/lib.rs +++ b/crates/pixi_config/src/lib.rs @@ -649,7 +649,6 @@ pub struct Config { ////////////////////// // Deprecated fields // ////////////////////// - #[serde(default)] #[serde(alias = "change_ps1")] // BREAK: remove to stop supporting snake_case alias #[serde(skip_serializing_if = "Option::is_none")] diff --git a/src/cli/shell_hook.rs b/src/cli/shell_hook.rs index a1391b98d7..aeb8880ff9 100644 --- a/src/cli/shell_hook.rs +++ b/src/cli/shell_hook.rs @@ -6,7 +6,7 @@ use pixi_config::{ConfigCliActivation, ConfigCliPrompt}; use rattler_lock::LockFile; use rattler_shell::{ activation::{ActivationVariables, PathModificationBehavior}, - shell::ShellEnum, + shell::{Shell, ShellEnum}, }; use serde::Serialize; use serde_json; @@ -79,7 +79,7 @@ async fn generate_activation_script( // If we are in a conda environment, we need to deactivate it before activating // the host / build prefix let conda_prefix = std::env::var("CONDA_PREFIX").ok().map(|p| p.into()); - let result = activator + let mut result = activator .activation(ActivationVariables { conda_prefix, path, @@ -87,6 +87,20 @@ async fn generate_activation_script( }) .into_diagnostic()?; + if project + .config() + .shell + .source_completion_scripts + .unwrap_or(true) + { + if let Some(completions_dir) = shell.completion_script_location() { + result + .script + .source_completions(&environment.dir().join(completions_dir)) + .into_diagnostic()?; + } + } + let script = result.script.contents().into_diagnostic()?; let hook = prompt::shell_hook(&shell).unwrap_or_default().to_owned(); From 5afbe703560b8134f5c3fbbac7bb644e4b1cdfdc Mon Sep 17 00:00:00 2001 From: Wolf Vollprecht Date: Fri, 28 Feb 2025 17:56:50 +0100 Subject: [PATCH 04/17] wire up config for shell completions --- crates/pixi_config/src/lib.rs | 6 +++- src/cli/shell.rs | 57 +++++++++++++++++++++++++++++------ src/cli/shell_hook.rs | 7 +---- 3 files changed, 54 insertions(+), 16 deletions(-) diff --git a/crates/pixi_config/src/lib.rs b/crates/pixi_config/src/lib.rs index b9f41c2505..0d5fd970a3 100644 --- a/crates/pixi_config/src/lib.rs +++ b/crates/pixi_config/src/lib.rs @@ -747,7 +747,7 @@ pub struct ShellConfig { /// Whether to source completion scripts from the environment or not. #[serde(default)] #[serde(skip_serializing_if = "Option::is_none")] - pub source_completion_scripts: Option, + source_completion_scripts: Option, /// If set to true, pixi will set the PS1 environment variable to a custom /// value. @@ -772,6 +772,10 @@ impl ShellConfig { && self.source_completion_scripts.is_none() && self.change_ps1.is_none() } + + pub fn source_completion_scripts(&self) -> bool { + self.source_completion_scripts.unwrap_or(true) + } } #[derive(thiserror::Error, Debug)] diff --git a/src/cli/shell.rs b/src/cli/shell.rs index 81587b036e..1f36bd3584 100644 --- a/src/cli/shell.rs +++ b/src/cli/shell.rs @@ -141,6 +141,7 @@ async fn start_unix_shell( env: &HashMap, prompt: String, prefix: &Prefix, + source_shell_completions: bool, ) -> miette::Result> { // create a tempfile for activation let mut temp_file = tempfile::Builder::new() @@ -155,12 +156,13 @@ async fn start_unix_shell( shell_script.set_env_var(key, value).into_diagnostic()?; } - if let Some(completions_dir) = shell.completion_script_location() { - shell_script - .source_completions(&prefix.root().join(completions_dir)) - .into_diagnostic()?; + if source_shell_completions { + if let Some(completions_dir) = shell.completion_script_location() { + shell_script + .source_completions(&prefix.root().join(completions_dir)) + .into_diagnostic()?; + } } - const DONE_STR: &str = "=== DONE ==="; shell_script.echo(DONE_STR).into_diagnostic()?; @@ -316,18 +318,55 @@ pub async fn execute(args: Args) -> miette::Result<()> { } }; + let source_shell_completions = workspace.config().shell.source_completion_scripts(); #[cfg(target_family = "unix")] let res = match interactive_shell { ShellEnum::NuShell(nushell) => start_nu_shell(nushell, env, prompt_hook).await, ShellEnum::PowerShell(pwsh) => start_powershell(pwsh, env, prompt_hook), ShellEnum::Bash(bash) => { - start_unix_shell(bash, vec!["-l", "-i"], env, prompt_hook, &prefix).await + start_unix_shell( + bash, + vec!["-l", "-i"], + env, + prompt_hook, + &prefix, + source_shell_completions, + ) + .await } ShellEnum::Zsh(zsh) => { - start_unix_shell(zsh, vec!["-l", "-i"], env, prompt_hook, &prefix).await + start_unix_shell( + zsh, + vec!["-l", "-i"], + env, + prompt_hook, + &prefix, + source_shell_completions, + ) + .await + } + ShellEnum::Fish(fish) => { + start_unix_shell( + fish, + vec![], + env, + prompt_hook, + &prefix, + source_shell_completions, + ) + .await + } + ShellEnum::Xonsh(xonsh) => { + start_unix_shell( + xonsh, + vec![], + env, + prompt_hook, + &prefix, + source_shell_completions, + ) + .await } - ShellEnum::Fish(fish) => start_unix_shell(fish, vec![], env, prompt_hook, &prefix).await, - ShellEnum::Xonsh(xonsh) => start_unix_shell(xonsh, vec![], env, prompt_hook, &prefix).await, _ => { miette::bail!("Unsupported shell: {:?}", interactive_shell) } diff --git a/src/cli/shell_hook.rs b/src/cli/shell_hook.rs index aeb8880ff9..7f3c921066 100644 --- a/src/cli/shell_hook.rs +++ b/src/cli/shell_hook.rs @@ -87,12 +87,7 @@ async fn generate_activation_script( }) .into_diagnostic()?; - if project - .config() - .shell - .source_completion_scripts - .unwrap_or(true) - { + if project.config().shell.source_completion_scripts() { if let Some(completions_dir) = shell.completion_script_location() { result .script From 0dadb3207f2c3259e6a4a049d88d549f6dd1d63e Mon Sep 17 00:00:00 2001 From: Wolf Vollprecht Date: Fri, 28 Feb 2025 18:59:00 +0100 Subject: [PATCH 05/17] fix tests --- crates/pixi_config/src/lib.rs | 3 +++ .../pixi_config__tests__config_merge_multiple.snap | 14 +++++++++----- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/crates/pixi_config/src/lib.rs b/crates/pixi_config/src/lib.rs index 0d5fd970a3..054aa58669 100644 --- a/crates/pixi_config/src/lib.rs +++ b/crates/pixi_config/src/lib.rs @@ -1703,6 +1703,9 @@ UNUSED = "unused" RepodataChannelConfig::default(), )]), }, + // Deprecated keys + change_ps1: None, + force_activate: None, }; let original_other = other.clone(); config = config.merge_config(other); diff --git a/crates/pixi_config/src/snapshots/pixi_config__tests__config_merge_multiple.snap b/crates/pixi_config/src/snapshots/pixi_config__tests__config_merge_multiple.snap index 823d2bcad4..cc7430ecd2 100644 --- a/crates/pixi_config/src/snapshots/pixi_config__tests__config_merge_multiple.snap +++ b/crates/pixi_config/src/snapshots/pixi_config__tests__config_merge_multiple.snap @@ -1,6 +1,5 @@ --- source: crates/pixi_config/src/lib.rs -assertion_line: 1698 expression: debug --- Config { @@ -15,9 +14,6 @@ Config { "defaults", ), ], - change_ps1: Some( - true, - ), authentication_override_file: None, tls_no_verify: Some( false, @@ -123,7 +119,13 @@ Config { true, ), ), - force_activate: None, + shell: ShellConfig { + force_activate: None, + source_completion_scripts: None, + change_ps1: Some( + true, + ), + }, experimental: ExperimentalConfig { use_environment_activation_cache: None, }, @@ -131,4 +133,6 @@ Config { solves: 1, downloads: 50, }, + change_ps1: None, + force_activate: None, } From 31024c332f29877e05542456e0bf4766e382cf50 Mon Sep 17 00:00:00 2001 From: Wolf Vollprecht Date: Wed, 5 Mar 2025 08:22:49 +0000 Subject: [PATCH 06/17] fix tests and add deprecation warnings --- crates/pixi_config/src/lib.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/crates/pixi_config/src/lib.rs b/crates/pixi_config/src/lib.rs index 054aa58669..72e5dc04f6 100644 --- a/crates/pixi_config/src/lib.rs +++ b/crates/pixi_config/src/lib.rs @@ -1189,6 +1189,12 @@ impl Config { .transpose() .into_diagnostic()? } + "change-ps1" => { + return Err(miette::miette!("The `change-ps1` field is deprecated. Please use the `shell.change-ps1` field instead.")); + } + "force-activate" => { + return Err(miette::miette!("The `force-activate` field is deprecated. Please use the `shell.force-activate` field instead.")); + } key if key.starts_with("repodata-config") => { if key == "repodata-config" { self.repodata_config = value @@ -1963,7 +1969,11 @@ UNUSED = "unused" Some(KeyringProvider::Subprocess) ); - config.set("change-ps1", None).unwrap(); + let deprecated = config.set("change-ps1", None); + assert!(deprecated.is_err()); + assert!(deprecated.unwrap_err().to_string().contains("deprecated")); + + config.set("shell.change-ps1", None).unwrap(); assert_eq!(config.change_ps1, None); config From 6880e987aa3497e453ffb83169fc7890fade76b6 Mon Sep 17 00:00:00 2001 From: Wolf Vollprecht Date: Wed, 5 Mar 2025 08:40:30 +0000 Subject: [PATCH 07/17] make things build on windows --- src/cli/shell.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/cli/shell.rs b/src/cli/shell.rs index 1f36bd3584..831b1ad68a 100644 --- a/src/cli/shell.rs +++ b/src/cli/shell.rs @@ -19,6 +19,9 @@ use pixi_config::{ConfigCliActivation, ConfigCliPrompt}; #[cfg(target_family = "unix")] use pixi_pty::unix::PtySession; +#[cfg(target_family = "unix")] +use crate::prefix::Prefix; + /// Start a shell in the pixi environment of the project #[derive(Parser, Debug)] pub struct Args { @@ -127,7 +130,6 @@ fn start_cmdexe( let mut process = command.spawn().into_diagnostic()?; Ok(process.wait().into_diagnostic()?.code()) } -use crate::prefix::Prefix; /// Starts a UNIX shell. /// # Arguments @@ -264,6 +266,7 @@ pub async fn execute(args: Args) -> miette::Result<()> { let environment = workspace.environment_from_name_or_env_var(args.environment)?; // Make sure environment is up-to-date, default to install, users can avoid this with frozen or locked. + #[allow(unused_variables)] let (lock_file_data, prefix) = get_update_lock_file_and_prefix( &environment, UpdateMode::QuickValidate, From ee0452789348c35ea85ad6aff2ca4c4520700f30 Mon Sep 17 00:00:00 2001 From: Wolf Vollprecht Date: Wed, 5 Mar 2025 09:36:52 +0000 Subject: [PATCH 08/17] one more --- src/cli/shell.rs | 102 ++++++++++++++++++++++++----------------------- 1 file changed, 52 insertions(+), 50 deletions(-) diff --git a/src/cli/shell.rs b/src/cli/shell.rs index 831b1ad68a..2247ed0cbe 100644 --- a/src/cli/shell.rs +++ b/src/cli/shell.rs @@ -321,57 +321,59 @@ pub async fn execute(args: Args) -> miette::Result<()> { } }; - let source_shell_completions = workspace.config().shell.source_completion_scripts(); #[cfg(target_family = "unix")] - let res = match interactive_shell { - ShellEnum::NuShell(nushell) => start_nu_shell(nushell, env, prompt_hook).await, - ShellEnum::PowerShell(pwsh) => start_powershell(pwsh, env, prompt_hook), - ShellEnum::Bash(bash) => { - start_unix_shell( - bash, - vec!["-l", "-i"], - env, - prompt_hook, - &prefix, - source_shell_completions, - ) - .await - } - ShellEnum::Zsh(zsh) => { - start_unix_shell( - zsh, - vec!["-l", "-i"], - env, - prompt_hook, - &prefix, - source_shell_completions, - ) - .await - } - ShellEnum::Fish(fish) => { - start_unix_shell( - fish, - vec![], - env, - prompt_hook, - &prefix, - source_shell_completions, - ) - .await - } - ShellEnum::Xonsh(xonsh) => { - start_unix_shell( - xonsh, - vec![], - env, - prompt_hook, - &prefix, - source_shell_completions, - ) - .await - } - _ => { - miette::bail!("Unsupported shell: {:?}", interactive_shell) + let res = { + let source_shell_completions = workspace.config().shell.source_completion_scripts(); + match interactive_shell { + ShellEnum::NuShell(nushell) => start_nu_shell(nushell, env, prompt_hook).await, + ShellEnum::PowerShell(pwsh) => start_powershell(pwsh, env, prompt_hook), + ShellEnum::Bash(bash) => { + start_unix_shell( + bash, + vec!["-l", "-i"], + env, + prompt_hook, + &prefix, + source_shell_completions, + ) + .await + } + ShellEnum::Zsh(zsh) => { + start_unix_shell( + zsh, + vec!["-l", "-i"], + env, + prompt_hook, + &prefix, + source_shell_completions, + ) + .await + } + ShellEnum::Fish(fish) => { + start_unix_shell( + fish, + vec![], + env, + prompt_hook, + &prefix, + source_shell_completions, + ) + .await + } + ShellEnum::Xonsh(xonsh) => { + start_unix_shell( + xonsh, + vec![], + env, + prompt_hook, + &prefix, + source_shell_completions, + ) + .await + } + _ => { + miette::bail!("Unsupported shell: {:?}", interactive_shell) + } } }; From 504ed11ff9459c15ad8779d1255ab868ac0da23b Mon Sep 17 00:00:00 2001 From: Ruben Arts Date: Wed, 5 Mar 2025 14:50:24 +0100 Subject: [PATCH 09/17] misc: improve warning --- crates/pixi_config/src/lib.rs | 48 ++++++++++++++++--------- tests/integration_rust/project_tests.rs | 2 +- 2 files changed, 33 insertions(+), 17 deletions(-) diff --git a/crates/pixi_config/src/lib.rs b/crates/pixi_config/src/lib.rs index 72e5dc04f6..99459e4cb9 100644 --- a/crates/pixi_config/src/lib.rs +++ b/crates/pixi_config/src/lib.rs @@ -818,7 +818,10 @@ impl Config { /// /// Parsing errors #[inline] - pub fn from_toml(toml: &str) -> miette::Result<(Config, Set)> { + pub fn from_toml( + toml: &str, + source_path: Option<&Path>, + ) -> miette::Result<(Config, Set)> { let de = toml_edit::de::Deserializer::from_str(toml).into_diagnostic()?; // Deserialize the config and collect unused keys @@ -828,13 +831,26 @@ impl Config { }) .into_diagnostic()?; + fn create_deprecation_warning(old: &str, new: &str, source_path: Option<&Path>) { + let msg = format!( + "Please replace '{}' with '{}', the field is deprecated and will be removed in a future release.", + console::style(old).red(), console::style(new).green() + ); + match source_path { + Some(path) => { + tracing::warn!("In '{}': {}", console::style(path.display()).bold(), msg,) + } + None => tracing::warn!("{}", msg), + } + } + if config.change_ps1.is_some() { - tracing::warn!("The `change_ps1` field is deprecated and will be removed in a future release. Please use the `shell.change-ps1` field instead."); + create_deprecation_warning("change_ps1", "shell.change-ps1", source_path); config.shell.change_ps1 = config.change_ps1; } if config.force_activate.is_some() { - tracing::warn!("The `force_activate` field is deprecated and will be removed in a future release. Please use the `shell.force-activate` field instead."); + create_deprecation_warning("force_activate", "shell.force-activate", source_path); config.shell.force_activate = config.force_activate; } @@ -863,8 +879,8 @@ impl Config { Err(e) => return Err(ConfigError::ReadError(e)), }; - let (mut config, unused_keys) = - Config::from_toml(&s).map_err(|e| ConfigError::ParseError(e, path.to_path_buf()))?; + let (mut config, unused_keys) = Config::from_toml(&s, Some(path)) + .map_err(|e| ConfigError::ParseError(e, path.to_path_buf()))?; if !unused_keys.is_empty() { tracing::warn!( @@ -1504,7 +1520,7 @@ UNUSED = "unused" "#, env!("CARGO_MANIFEST_DIR").replace('\\', "\\\\").as_str() ); - let (config, unused) = Config::from_toml(toml.as_str()).unwrap(); + let (config, unused) = Config::from_toml(toml.as_str(), None).unwrap(); assert_eq!( config.default_channels, vec![NamedChannelOrUrl::from_str("conda-forge").unwrap()] @@ -1518,7 +1534,7 @@ UNUSED = "unused" assert!(unused.contains("UNUSED")); let toml = r"detached-environments = true"; - let (config, _) = Config::from_toml(toml).unwrap(); + let (config, _) = Config::from_toml(toml, None).unwrap(); assert_eq!( config.detached_environments().path().unwrap().unwrap(), get_cache_dir() @@ -1537,7 +1553,7 @@ UNUSED = "unused" #[case("no-pin", PinningStrategy::NoPin)] fn test_config_parse_pinning_strategy(#[case] input: &str, #[case] expected: PinningStrategy) { let toml = format!("pinning-strategy = \"{}\"", input); - let (config, _) = Config::from_toml(&toml).unwrap(); + let (config, _) = Config::from_toml(&toml, None).unwrap(); assert_eq!(config.pinning_strategy, Some(expected)); } @@ -1582,12 +1598,12 @@ UNUSED = "unused" extra-index-urls = ["https://pypi.org/simple2"] keyring-provider = "subprocess" "#; - let (config, _) = Config::from_toml(toml).unwrap(); + let (config, _) = Config::from_toml(toml, None).unwrap(); assert_eq!( config.pypi_config().index_url, Some(Url::parse("https://pypi.org/simple").unwrap()) ); - assert!(config.pypi_config().extra_index_urls.len() == 1); + assert_eq!(config.pypi_config().extra_index_urls.len(), 1); assert_eq!( config.pypi_config().keyring_provider, Some(KeyringProvider::Subprocess) @@ -1603,7 +1619,7 @@ UNUSED = "unused" keyring-provider = "subprocess" allow-insecure-host = ["https://localhost:1234", "*"] "#; - let (config, _) = Config::from_toml(toml).unwrap(); + let (config, _) = Config::from_toml(toml, None).unwrap(); assert_eq!( config.pypi_config().allow_insecure_host, vec!["https://localhost:1234", "*",] @@ -1618,7 +1634,7 @@ UNUSED = "unused" region = "us-east-1" force-path-style = false "#; - let (config, _) = Config::from_toml(toml).unwrap(); + let (config, _) = Config::from_toml(toml, None).unwrap(); let s3_options = config.s3_options; assert_eq!( s3_options["bucket1"].endpoint_url, @@ -1636,7 +1652,7 @@ UNUSED = "unused" region = "us-east-1" # force-path-style = false "#; - let result = Config::from_toml(toml); + let result = Config::from_toml(toml, None); assert!(result.is_err()); assert!(result .err() @@ -1836,7 +1852,7 @@ UNUSED = "unused" disable_bzip2 = true disable_zstd = true "#; - let (config, _) = Config::from_toml(toml).unwrap(); + let (config, _) = Config::from_toml(toml, None).unwrap(); assert_eq!( config.default_channels, vec![NamedChannelOrUrl::from_str("conda-forge").unwrap()] @@ -1874,7 +1890,7 @@ UNUSED = "unused" disable-zstd = true disable-sharded = true "#; - Config::from_toml(toml).unwrap(); + Config::from_toml(toml, None).unwrap(); } #[test] @@ -2103,7 +2119,7 @@ UNUSED = "unused" disable-bzip2 = false disable-zstd = false "#; - let (config, _) = Config::from_toml(toml).unwrap(); + let (config, _) = Config::from_toml(toml, None).unwrap(); let repodata_config = config.repodata_config(); assert_eq!(repodata_config.default.disable_jlap, Some(true)); assert_eq!(repodata_config.default.disable_bzip2, Some(true)); diff --git a/tests/integration_rust/project_tests.rs b/tests/integration_rust/project_tests.rs index 850425dad9..3c6e648978 100644 --- a/tests/integration_rust/project_tests.rs +++ b/tests/integration_rust/project_tests.rs @@ -157,7 +157,7 @@ fn parse_valid_docs_configs() { let path = entry.path(); if path.extension().map(|ext| ext == "toml").unwrap_or(false) { let toml = fs_err::read_to_string(&path).unwrap(); - let (_config, unused_keys) = Config::from_toml(&toml).unwrap(); + let (_config, unused_keys) = Config::from_toml(&toml, None).unwrap(); assert_eq!( unused_keys, BTreeSet::::new(), From 50e0bbcbc39caf423345d2efbc36c18dac7c204f Mon Sep 17 00:00:00 2001 From: Ruben Arts Date: Wed, 5 Mar 2025 14:58:29 +0100 Subject: [PATCH 10/17] docs: replace the change-ps1 to shell.ps1 --- docs/reference/pixi_configuration.md | 12 +++++++----- docs/source_files/pixi_config_tomls/main_config.toml | 6 ++++-- tests/integration_python/conftest.py | 2 +- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/docs/reference/pixi_configuration.md b/docs/reference/pixi_configuration.md index 568fb28fcd..c10e533ae7 100644 --- a/docs/reference/pixi_configuration.md +++ b/docs/reference/pixi_configuration.md @@ -73,14 +73,16 @@ This defaults to only conda-forge. !!! note The `default-channels` are only used when initializing a new project. Once initialized the `channels` are used from the project manifest. -### `change-ps1` +### `shell` -When set to false, the `(pixi)` prefix in the shell prompt is removed. -This applies to the `pixi shell` subcommand. -You can override this from the CLI with `--change-ps1`. +- `change-ps1`: When set to false, the `(pixi)` prefix in the shell prompt is removed. + This applies to the `pixi shell` subcommand. + You can override this from the CLI with `--change-ps1`. +- `force-activate`: When set to true the re-activation of the environment will always happen. +This is used in combination with the [`experimental`](#experimental) feature `use-environment-activation-cache`. ```toml title="config.toml" ---8<-- "docs/source_files/pixi_config_tomls/main_config.toml:change-ps1" +--8<-- "docs/source_files/pixi_config_tomls/main_config.toml:shell" ``` ### `tls-no-verify` diff --git a/docs/source_files/pixi_config_tomls/main_config.toml b/docs/source_files/pixi_config_tomls/main_config.toml index e78c76149f..106a8e985d 100644 --- a/docs/source_files/pixi_config_tomls/main_config.toml +++ b/docs/source_files/pixi_config_tomls/main_config.toml @@ -3,9 +3,11 @@ default-channels = ["conda-forge"] # --8<-- [end:default-channels] -# --8<-- [start:change-ps1] +# --8<-- [start:shell] +[shell] change-ps1 = true -# --8<-- [end:change-ps1] +force-activation = true +# --8<-- [end:shell] # --8<-- [start:tls-no-verify] tls-no-verify = false diff --git a/tests/integration_python/conftest.py b/tests/integration_python/conftest.py index fca7879cdc..e4b4e568a6 100644 --- a/tests/integration_python/conftest.py +++ b/tests/integration_python/conftest.py @@ -23,7 +23,7 @@ def tmp_pixi_workspace(tmp_path: Path) -> Path: pixi_config = """ # Reset to defaults default-channels = ["conda-forge"] -change-ps1 = true +shell.change-ps1 = true tls-no-verify = false detached-environments = false pinning-strategy = "semver" From f32699c1dd3c793dfb3d1331fd6a921517e55837 Mon Sep 17 00:00:00 2001 From: Ruben Arts Date: Wed, 5 Mar 2025 15:00:07 +0100 Subject: [PATCH 11/17] docs: replace the change-ps1 to shell.ps1 --- docs/source_files/pixi_config_tomls/main_config.toml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/source_files/pixi_config_tomls/main_config.toml b/docs/source_files/pixi_config_tomls/main_config.toml index 106a8e985d..6715203c50 100644 --- a/docs/source_files/pixi_config_tomls/main_config.toml +++ b/docs/source_files/pixi_config_tomls/main_config.toml @@ -3,11 +3,6 @@ default-channels = ["conda-forge"] # --8<-- [end:default-channels] -# --8<-- [start:shell] -[shell] -change-ps1 = true -force-activation = true -# --8<-- [end:shell] # --8<-- [start:tls-no-verify] tls-no-verify = false @@ -92,3 +87,9 @@ use-environment-activation-cache = true "https://prefix.dev/bioconda", ] # --8<-- [end:mirrors] + +# --8<-- [start:shell] +[shell] +change-ps1 = true +force-activation = true +# --8<-- [end:shell] From e42fe91f693c3798f368e41d09813dcbb0191fb1 Mon Sep 17 00:00:00 2001 From: Ruben Arts Date: Wed, 5 Mar 2025 15:05:49 +0100 Subject: [PATCH 12/17] docs: add source completion scripts config --- docs/reference/pixi_configuration.md | 5 +++-- docs/source_files/pixi_config_tomls/main_config.toml | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/reference/pixi_configuration.md b/docs/reference/pixi_configuration.md index c10e533ae7..f638d2cb27 100644 --- a/docs/reference/pixi_configuration.md +++ b/docs/reference/pixi_configuration.md @@ -75,11 +75,12 @@ This defaults to only conda-forge. ### `shell` -- `change-ps1`: When set to false, the `(pixi)` prefix in the shell prompt is removed. +- `change-ps1`: When set to `false`, the `(pixi)` prefix in the shell prompt is removed. This applies to the `pixi shell` subcommand. You can override this from the CLI with `--change-ps1`. -- `force-activate`: When set to true the re-activation of the environment will always happen. +- `force-activate`: When set to `true` the re-activation of the environment will always happen. This is used in combination with the [`experimental`](#experimental) feature `use-environment-activation-cache`. +- `source-completion-scripts`: When set to `false`, pixi will not source the autocompletion scripts of the environment when going into the shell. ```toml title="config.toml" --8<-- "docs/source_files/pixi_config_tomls/main_config.toml:shell" diff --git a/docs/source_files/pixi_config_tomls/main_config.toml b/docs/source_files/pixi_config_tomls/main_config.toml index 6715203c50..fd2866a492 100644 --- a/docs/source_files/pixi_config_tomls/main_config.toml +++ b/docs/source_files/pixi_config_tomls/main_config.toml @@ -90,6 +90,7 @@ use-environment-activation-cache = true # --8<-- [start:shell] [shell] -change-ps1 = true +change-ps1 = false force-activation = true +source-completion-scripts = false # --8<-- [end:shell] From e1e55199f719f064139e8a393973b4e3564ea0a5 Mon Sep 17 00:00:00 2001 From: Ruben Arts Date: Wed, 5 Mar 2025 15:08:02 +0100 Subject: [PATCH 13/17] docs: no-completion flag --- crates/pixi_config/src/lib.rs | 1 + docs/reference/cli.md | 2 ++ 2 files changed, 3 insertions(+) diff --git a/crates/pixi_config/src/lib.rs b/crates/pixi_config/src/lib.rs index 99459e4cb9..c2bcc2669e 100644 --- a/crates/pixi_config/src/lib.rs +++ b/crates/pixi_config/src/lib.rs @@ -186,6 +186,7 @@ pub struct ConfigCliActivation { #[arg(long)] force_activate: bool, + /// Do not source the autocompletion scripts from the environment. #[arg(long)] no_completion: Option, } diff --git a/docs/reference/cli.md b/docs/reference/cli.md index 1ac817a343..26347b809b 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -754,6 +754,7 @@ To exit the pixi shell, simply run `exit`. - `--revalidate`: Revalidate the full environment, instead of checking lock file hash. [more info](../features/environment.md#environment-installation-metadata) - `--concurrent-downloads`: The number of concurrent downloads to use when installing packages. Defaults to 50. - `--concurrent-solves`: The number of concurrent solves to use when installing packages. Defaults to the number of cpu threads. +- `--no-completion`: Do not source the autocompletion scripts from the environment. ```shell pixi shell @@ -786,6 +787,7 @@ This command prints the activation script of an environment. - `--revalidate`: Revalidate the full environment, instead of checking lock file hash. [more info](../features/environment.md#environment-installation-metadata) - `--concurrent-downloads`: The number of concurrent downloads to use when installing packages. Defaults to 50. - `--concurrent-solves`: The number of concurrent solves to use when installing packages. Defaults to the number of cpu threads. +- `--no-completion`: Do not source the autocompletion scripts from the environment. ```shell pixi shell-hook From 77b3ea493927f8bb79b1a0d4524cb1fe00a8af50 Mon Sep 17 00:00:00 2001 From: Ruben Arts Date: Wed, 5 Mar 2025 15:57:02 +0100 Subject: [PATCH 14/17] fix: typo --- docs/source_files/pixi_config_tomls/main_config.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source_files/pixi_config_tomls/main_config.toml b/docs/source_files/pixi_config_tomls/main_config.toml index fd2866a492..2bd1bf06f5 100644 --- a/docs/source_files/pixi_config_tomls/main_config.toml +++ b/docs/source_files/pixi_config_tomls/main_config.toml @@ -91,6 +91,6 @@ use-environment-activation-cache = true # --8<-- [start:shell] [shell] change-ps1 = false -force-activation = true +force-activate = true source-completion-scripts = false # --8<-- [end:shell] From e1a0c65fc493228430ed1df61d1dcb27a024c515 Mon Sep 17 00:00:00 2001 From: Ruben Arts Date: Thu, 6 Mar 2025 09:34:34 +0100 Subject: [PATCH 15/17] test: check if the shell completion scripts are added in shell-hook --- tests/integration_python/test_main_cli.py | 33 +++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/tests/integration_python/test_main_cli.py b/tests/integration_python/test_main_cli.py index 2594f83336..2b0b234719 100644 --- a/tests/integration_python/test_main_cli.py +++ b/tests/integration_python/test_main_cli.py @@ -1157,3 +1157,36 @@ def test_dont_error_on_missing_platform(pixi: Path, tmp_pixi_workspace: Path) -> [pixi, "install", "--manifest-path", manifest], stderr_contains=["pixi project platform add zos-z"], ) + + +def test_shell_hook_autocompletion(pixi: Path, tmp_pixi_workspace: Path) -> None: + manifest = tmp_pixi_workspace.joinpath("pixi.toml") + toml = f""" + {EMPTY_BOILERPLATE_PROJECT} + """ + manifest.write_text(toml) + + bash_comp_dir = ".pixi/envs/default/share/bash-completion/completions" + tmp_pixi_workspace.joinpath(bash_comp_dir).mkdir(parents=True, exist_ok=True) + tmp_pixi_workspace.joinpath(bash_comp_dir, "pixi.sh").touch() + verify_cli_command( + [pixi, "shell-hook", "--manifest-path", manifest, "--shell", "bash"], + stdout_contains=["source", "share/bash-completion/completions/*"], + ) + + zsh_comp_dir = ".pixi/envs/default/share/zsh/site-functions" + tmp_pixi_workspace.joinpath(zsh_comp_dir).mkdir(parents=True, exist_ok=True) + tmp_pixi_workspace.joinpath(zsh_comp_dir, "_pixi").touch() + verify_cli_command( + [pixi, "shell-hook", "--manifest-path", manifest, "--shell", "zsh"], + stdout_contains=["fpath+=", "share/zsh/site-functions", "autoload -Uz compinit"], + ) + + fish_comp_dir = ".pixi/envs/default/share/fish/vendor_completions.d" + tmp_pixi_workspace.joinpath(fish_comp_dir).mkdir(parents=True, exist_ok=True) + tmp_pixi_workspace.joinpath(fish_comp_dir, "pixi.fish").touch() + + verify_cli_command( + [pixi, "shell-hook", "--manifest-path", manifest, "--shell", "fish"], + stdout_contains=["for file in", "source", "share/fish/vendor_completions.d/*"], + ) From 54e0e9029d1f81f1c69d7f839f1cff49bcdfc0ad Mon Sep 17 00:00:00 2001 From: Ruben Arts Date: Thu, 6 Mar 2025 10:01:55 +0100 Subject: [PATCH 16/17] Apply suggestions from code review --- crates/pixi_config/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/pixi_config/src/lib.rs b/crates/pixi_config/src/lib.rs index c2bcc2669e..6798c3364e 100644 --- a/crates/pixi_config/src/lib.rs +++ b/crates/pixi_config/src/lib.rs @@ -846,12 +846,12 @@ impl Config { } if config.change_ps1.is_some() { - create_deprecation_warning("change_ps1", "shell.change-ps1", source_path); + create_deprecation_warning("change-ps1", "shell.change-ps1", source_path); config.shell.change_ps1 = config.change_ps1; } if config.force_activate.is_some() { - create_deprecation_warning("force_activate", "shell.force-activate", source_path); + create_deprecation_warning("force-activate", "shell.force-activate", source_path); config.shell.force_activate = config.force_activate; } From d359c9440cd0b65812b41a199ccff77f6c09c87d Mon Sep 17 00:00:00 2001 From: Ruben Arts Date: Fri, 7 Mar 2025 09:21:46 +0100 Subject: [PATCH 17/17] fix: windows test --- tests/integration_python/test_main_cli.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/integration_python/test_main_cli.py b/tests/integration_python/test_main_cli.py index 2b0b234719..2361bf531e 100644 --- a/tests/integration_python/test_main_cli.py +++ b/tests/integration_python/test_main_cli.py @@ -1171,7 +1171,7 @@ def test_shell_hook_autocompletion(pixi: Path, tmp_pixi_workspace: Path) -> None tmp_pixi_workspace.joinpath(bash_comp_dir, "pixi.sh").touch() verify_cli_command( [pixi, "shell-hook", "--manifest-path", manifest, "--shell", "bash"], - stdout_contains=["source", "share/bash-completion/completions/*"], + stdout_contains=["source", "share/bash-completion/completions"], ) zsh_comp_dir = ".pixi/envs/default/share/zsh/site-functions" @@ -1188,5 +1188,5 @@ def test_shell_hook_autocompletion(pixi: Path, tmp_pixi_workspace: Path) -> None verify_cli_command( [pixi, "shell-hook", "--manifest-path", manifest, "--shell", "fish"], - stdout_contains=["for file in", "source", "share/fish/vendor_completions.d/*"], + stdout_contains=["for file in", "source", "share/fish/vendor_completions.d"], )