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

Fix/shell prompt #385

Merged
merged 9 commits into from
Oct 13, 2023
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
179 changes: 96 additions & 83 deletions Cargo.lock

Large diffs are not rendered by default.

20 changes: 10 additions & 10 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ deno_task_shell = "0.13.2"
dirs = "5.0.1"
dunce = "1.0.4"
futures = "0.3.28"
indexmap = { version = "2.0.0", features = ["serde"] }
indexmap = { version = "2.0.2", features = ["serde"] }
indicatif = "0.17.7"
insta = { version = "1.32.0", features = ["yaml"] }
is_executable = "1.0.1"
Expand All @@ -43,7 +43,7 @@ rattler_repodata_gateway = { version = "0.10.0", default-features = false, featu
rattler_shell = { version = "0.10.0", default-features = false, features = ["sysinfo"] }
rattler_solve = { version = "0.10.0", default-features = false, features = ["resolvo"] }
rattler_virtual_packages = { version = "0.10.0", default-features = false }
reqwest = { version = "0.11.20", default-features = false }
reqwest = { version = "0.11.22", default-features = false }
serde = "1.0.188"
serde_json = "1.0.107"
serde_spanned = "0.6.3"
Expand Down Expand Up @@ -71,14 +71,14 @@ tokio = { version = "1.32.0", features = ["rt"] }
toml = "0.8.1"

[patch.crates-io]
#rattler = { git = "https://github.com/mamba-org/rattler", branch = "main" }
#rattler_conda_types = { git = "https://github.com/mamba-org/rattler", branch = "main" }
#rattler_digest = { git = "https://github.com/mamba-org/rattler", branch = "main" }
#rattler_networking = { git = "https://github.com/mamba-org/rattler", branch = "main" }
#rattler_repodata_gateway = { git = "https://github.com/mamba-org/rattler", branch = "main" }
#rattler_shell = { git = "https://github.com/mamba-org/rattler", branch = "main" }
#rattler_solve = { git = "https://github.com/mamba-org/rattler", branch = "main" }
#rattler_virtual_packages = { git = "https://github.com/mamba-org/rattler", branch = "main" }
rattler = { git = "https://github.com/mamba-org/rattler", branch = "main" }
rattler_conda_types = { git = "https://github.com/mamba-org/rattler", branch = "main" }
rattler_digest = { git = "https://github.com/mamba-org/rattler", branch = "main" }
rattler_networking = { git = "https://github.com/mamba-org/rattler", branch = "main" }
rattler_repodata_gateway = { git = "https://github.com/mamba-org/rattler", branch = "main" }
rattler_shell = { git = "https://github.com/mamba-org/rattler", branch = "main" }
rattler_solve = { git = "https://github.com/mamba-org/rattler", branch = "main" }
rattler_virtual_packages = { git = "https://github.com/mamba-org/rattler", branch = "main" }

#rattler = { path = "../rattler/crates/rattler" }
#rattler_conda_types = { path = "../rattler/crates/rattler_conda_types" }
Expand Down
4 changes: 2 additions & 2 deletions src/cli/global/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use rattler_conda_types::{Channel, ChannelConfig, MatchSpec, PackageName, Platfo
use rattler_networking::AuthenticatedClient;
use rattler_repodata_gateway::sparse::SparseRepoData;
use rattler_shell::{
activation::{ActivationVariables, Activator, PathModificationBehaviour},
activation::{ActivationVariables, Activator, PathModificationBehavior},
shell::Shell,
shell::ShellEnum,
};
Expand Down Expand Up @@ -141,7 +141,7 @@ pub(crate) fn create_activation_script(
.activation(ActivationVariables {
conda_prefix: None,
path: None,
path_modification_behaviour: PathModificationBehaviour::Prepend,
path_modification_behaviour: PathModificationBehavior::Prepend,
})
.into_diagnostic()?;

Expand Down
39 changes: 10 additions & 29 deletions src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@ use clap::{CommandFactory, Parser};
use clap_complete;
use clap_verbosity_flag::Verbosity;
use miette::IntoDiagnostic;
use rattler_shell::shell::{Shell, ShellEnum};
use std::io::{IsTerminal, Write};
use std::str::FromStr;
use std::io::IsTerminal;
use tracing_subscriber::{filter::LevelFilter, util::SubscriberInitExt, EnvFilter};

pub mod add;
Expand All @@ -30,7 +28,7 @@ struct Args {
command: Command,

/// The verbosity level
/// (-v for verbose, -vv for debug, -vvv for trace, -q for quiet)
/// (-v for warning, -vv for info, -vvv for debug, -vvvv for trace, -q for quiet)
#[command(flatten)]
verbose: Verbosity,

Expand Down Expand Up @@ -80,29 +78,6 @@ fn completion(args: CompletionCommand) -> miette::Result<()> {
"pixi",
&mut std::io::stdout(),
);

// Create PS1 overwrite command
// TODO: Also make this work for non bourne shells.
let mut script = String::new();
let shell = ShellEnum::from_str(clap_shell.to_string().as_str()).into_diagnostic()?;
// Generate a shell agnostic command to add the PIXI_PROMPT to the PS1 variable.
shell
.set_env_var(
&mut script,
"PS1",
format!(
"{}{}",
shell.format_env_var("PIXI_PROMPT"),
shell.format_env_var("PS1")
)
.as_str(),
)
.unwrap();
// Just like the clap autocompletion code write directly to the stdout
std::io::stdout()
.write_all(script.as_bytes())
.into_diagnostic()?;

Ok(())
}

Expand Down Expand Up @@ -132,14 +107,20 @@ pub async fn execute() -> miette::Result<()> {
clap_verbosity_flag::LevelFilter::Trace => LevelFilter::TRACE,
};

// Default pixi level to warn but overwrite if a lower level is requested.
let pixi_level = if level_filter > LevelFilter::WARN {
level_filter
} else {
LevelFilter::WARN
};
ruben-arts marked this conversation as resolved.
Show resolved Hide resolved

let env_filter = EnvFilter::builder()
.with_default_directive(level_filter.into())
.from_env()
.into_diagnostic()?
// filter logs from apple codesign because they are very noisy
.add_directive("apple_codesign=off".parse().into_diagnostic()?)
// set pixi's tracing level to warn
.add_directive("pixi=warn".parse().into_diagnostic()?);
.add_directive(format!("pixi={}", pixi_level).parse().into_diagnostic()?);

// Setup the tracing subscriber
tracing_subscriber::fmt()
Expand Down
4 changes: 2 additions & 2 deletions src/cli/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use crate::project::environment::get_metadata_env;
use crate::task::{quote_arguments, CmdArgs, Execute, Task};
use crate::{environment::get_up_to_date_prefix, Project};
use rattler_shell::{
activation::{ActivationVariables, Activator, PathModificationBehaviour},
activation::{ActivationVariables, Activator, PathModificationBehavior},
shell::ShellEnum,
};
use tokio::task::JoinHandle;
Expand Down Expand Up @@ -295,7 +295,7 @@ async fn run_activation(
conda_prefix: None,

// Prepending environment paths so they get found first.
path_modification_behaviour: PathModificationBehaviour::Prepend,
path_modification_behaviour: PathModificationBehavior::Prepend,
})
})
.await
Expand Down
84 changes: 67 additions & 17 deletions src/cli/shell.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::Project;
use crate::{prompt, Project};
use clap::Parser;
use miette::IntoDiagnostic;
use rattler_conda_types::Platform;
Expand Down Expand Up @@ -36,6 +36,7 @@ pub struct Args {
fn start_powershell(
pwsh: PowerShell,
env: &HashMap<String, String>,
prompt: String,
) -> miette::Result<Option<i32>> {
// create a tempfile for activation
let mut temp_file = tempfile::Builder::new()
Expand All @@ -47,11 +48,13 @@ fn start_powershell(
for (key, value) in env {
shell_script.set_env_var(key, value);
}
temp_file
.write_all(shell_script.contents.as_bytes())
.into_diagnostic()?;

// Write custom prompt to the env file
temp_file.write(prompt.as_bytes()).into_diagnostic()?;

let mut contents = shell_script.contents;
// TODO: build a better prompt
contents.push_str("\nfunction prompt {\"PS pixi> \"}");
temp_file.write_all(contents.as_bytes()).into_diagnostic()?;
// close the file handle, but keep the path (needed for Windows)
let temp_path = temp_file.into_temp_path();

Expand All @@ -66,7 +69,11 @@ fn start_powershell(
}

#[cfg(target_family = "windows")]
fn start_cmdexe(cmdexe: CmdExe, env: &HashMap<String, String>) -> miette::Result<Option<i32>> {
fn start_cmdexe(
cmdexe: CmdExe,
env: &HashMap<String, String>,
prompt: String,
) -> miette::Result<Option<i32>> {
// create a tempfile for activation
let mut temp_file = tempfile::Builder::new()
.suffix(".cmd")
Expand All @@ -82,6 +89,9 @@ fn start_cmdexe(cmdexe: CmdExe, env: &HashMap<String, String>) -> miette::Result
.write_all(shell_script.contents.as_bytes())
.into_diagnostic()?;

// Write custom prompt to the env file
temp_file.write(prompt.as_bytes()).into_diagnostic()?;

let mut command = std::process::Command::new(cmdexe.executable());
command.arg("/K");
command.arg(temp_file.path());
Expand All @@ -100,6 +110,7 @@ async fn start_unix_shell<T: Shell + Copy>(
shell: T,
args: Vec<&str>,
env: &HashMap<String, String>,
prompt: String,
) -> miette::Result<Option<i32>> {
// create a tempfile for activation
let mut temp_file = tempfile::Builder::new()
Expand All @@ -118,15 +129,27 @@ async fn start_unix_shell<T: Shell + Copy>(
.write_all(shell_script.contents.as_bytes())
.into_diagnostic()?;

// Write custom prompt to the env file
temp_file.write(prompt.as_bytes()).into_diagnostic()?;

let mut command = std::process::Command::new(shell.executable());
command.args(args);

let mut process = PtySession::new(command).into_diagnostic()?;
process
// Space added before `source` to automatically ignore it in history.
.send_line(&format!(" source {}", temp_file.path().display()))
// Space added before `source` to automatically ignore it in history.
let mut source_command = " ".to_string();
shell
.run_script(&mut source_command, temp_file.path())
.into_diagnostic()?;

// Remove automatically added `\n`, if for some reason this fails, just ignore.
let source_command = source_command
.strip_suffix('\n')
.unwrap_or(source_command.as_str());

// Start process and send env activation to the shell.
let mut process = PtySession::new(command).into_diagnostic()?;
process.send_line(source_command).into_diagnostic()?;

process.interact().into_diagnostic()
}

Expand Down Expand Up @@ -165,6 +188,7 @@ pub async fn execute(args: Args) -> miette::Result<()> {

// Get the environment variables we need to set activate the project in the shell.
let env = get_shell_env(&project, args.frozen, args.locked).await?;
tracing::debug!("Pixi environment activation:\n{:?}", env);

// Start the shell as the last part of the activation script based on the default shell.
let interactive_shell: ShellEnum = ShellEnum::from_parent_process()
Expand All @@ -173,20 +197,46 @@ pub async fn execute(args: Args) -> miette::Result<()> {

#[cfg(target_family = "windows")]
let res = match interactive_shell {
ShellEnum::PowerShell(pwsh) => start_powershell(pwsh, &env),
ShellEnum::CmdExe(cmdexe) => start_cmdexe(cmdexe, &env),
ShellEnum::PowerShell(pwsh) => {
start_powershell(pwsh, &env, prompt::get_powershell_prompt(project.name()))
}
ShellEnum::CmdExe(cmdexe) => {
start_cmdexe(cmdexe, &env, prompt::get_cmd_prompt(project.name()))
}
_ => {
miette::bail!("Unsupported shell: {:?}", interactive_shell);
}
};

#[cfg(target_family = "unix")]
let res = match interactive_shell {
ShellEnum::PowerShell(pwsh) => start_powershell(pwsh, &env),
ShellEnum::Bash(bash) => start_unix_shell(bash, vec!["-l", "-i"], &env).await,
ShellEnum::Zsh(zsh) => start_unix_shell(zsh, vec!["-l", "-i"], &env).await,
ShellEnum::Fish(fish) => start_unix_shell(fish, vec![], &env).await,
ShellEnum::Xonsh(xonsh) => start_unix_shell(xonsh, vec![], &env).await,
ShellEnum::PowerShell(pwsh) => {
start_powershell(pwsh, &env, prompt::get_powershell_prompt(project.name()))
}
ShellEnum::Bash(bash) => {
start_unix_shell(
bash,
vec!["-l", "-i"],
&env,
prompt::get_bash_prompt(project.name()),
)
.await
}
ShellEnum::Zsh(zsh) => {
start_unix_shell(
zsh,
vec!["-l", "-i"],
&env,
prompt::get_zsh_prompt(project.name()),
)
.await
}
ShellEnum::Fish(fish) => {
start_unix_shell(fish, vec![], &env, prompt::get_fish_prompt(project.name())).await
}
ShellEnum::Xonsh(xonsh) => {
start_unix_shell(xonsh, vec![], &env, prompt::get_xonsh_prompt()).await
}
_ => {
miette::bail!("Unsupported shell: {:?}", interactive_shell)
}
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ pub mod environment;
pub mod prefix;
pub mod progress;
pub mod project;
mod prompt;
pub mod repodata;
pub mod task;
#[cfg(unix)]
Expand Down
44 changes: 44 additions & 0 deletions src/prompt.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/// Set default pixi prompt for the bash shell
#[cfg(target_family = "unix")]
pub fn get_bash_prompt(env_name: &str) -> String {
format!("export PS1=\"({}) $PS1\"", env_name)
}

/// Set default pixi prompt for the zsh shell
#[cfg(target_family = "unix")]
pub fn get_zsh_prompt(env_name: &str) -> String {
format!("export PS1=\"({}) $PS1\"", env_name)
}

/// Set default pixi prompt for the fish shell
#[cfg(target_family = "unix")]
pub fn get_fish_prompt(env_name: &str) -> String {
format!(
"functions -c fish_prompt old_fish_prompt; \
function fish_prompt; \
echo \"({}) $(old_fish_prompt)\"; \
end;",
env_name
)
}

/// Set default pixi prompt for the xonsh shell
#[cfg(target_family = "unix")]
pub fn get_xonsh_prompt() -> String {
// Xonsh' default prompt can find the environment for some reason.
"".to_string()
}
/// Set default pixi prompt for the powershell
pub fn get_powershell_prompt(env_name: &str) -> String {
format!(
"$old_prompt = & $function:prompt\n\
function prompt {{\"({}) $old_prompt\"}}",
env_name
)
}

/// Set default pixi prompt for the cmd.exe command prompt
#[cfg(target_family = "windows")]
pub fn get_cmd_prompt(env_name: &str) -> String {
format!(r"@PROMPT ({}) $P$G", env_name)
}