use crate::App;
#[cfg(not(target_os = "linux"))]
use crate::shell::setup::{
InteractiveSetup, SetupContext, apply_user_choices, execute_setup, handle_interactive_error,
is_recoverable_interactive_error, post_setup_actions, pre_setup_checks,
};
#[cfg(not(target_os = "linux"))]
use color_eyre::eyre::Context as _;
use yansi::Paint;
#[cfg(not(target_os = "linux"))]
fn print_dir_table_and_ensure(app: &App) -> crate::Result<bool> {
use crate::shell::path_utils::check_dir_in_path;
use std::io::{self, Write};
let paths = &app.paths;
struct Row {
role: &'static str,
path: std::path::PathBuf,
}
let rows = vec![
Row { role: "Data ", path: paths.data_dir.clone() },
Row { role: "Config", path: paths.config_dir.clone() },
Row { role: "Cache ", path: paths.cache_dir.clone() },
];
let pub_bin = paths.public_bin_dir.clone();
let path_width = rows
.iter()
.map(|r| r.path.display().to_string().len())
.chain(pub_bin.iter().map(|p| p.display().to_string().len()))
.max()
.unwrap_or(30)
.max(30);
let sep = "─".repeat(8 + path_width + 14);
println!();
println!("{}", Paint::cyan("zv directory layout (XDG Base Directory Specification)").bold());
println!("{sep}");
println!(" {:<8} {:<path_width$} Status", "Role", "Directory");
println!("{sep}");
let mut dirs_to_create: Vec<std::path::PathBuf> = Vec::new();
for row in &rows {
let status = if row.path.is_dir() {
Paint::green("✓ exists").to_string()
} else {
dirs_to_create.push(row.path.clone());
Paint::yellow("[will create]").to_string()
};
println!(
" {:<8} {:<path_width$} {}",
row.role,
row.path.display(),
status
);
}
if let Some(ref pub_bin_path) = pub_bin {
let in_path = check_dir_in_path(pub_bin_path);
let status = if !pub_bin_path.is_dir() {
dirs_to_create.push(pub_bin_path.clone());
Paint::yellow("[will create]").to_string()
} else if in_path {
Paint::green("✓ in PATH").to_string()
} else {
Paint::yellow("exists, not in PATH").to_string()
};
println!(
" {:<8} {:<path_width$} {}",
"Pub bin",
pub_bin_path.display(),
status
);
}
println!("{sep}");
println!();
if !dirs_to_create.is_empty() {
println!("{}", Paint::yellow("Directories to create:"));
for dir in &dirs_to_create {
println!(" • {}", Paint::cyan(&dir.display().to_string()));
}
println!();
if !crate::tools::supports_interactive_prompts() {
for dir in &dirs_to_create {
std::fs::create_dir_all(dir)?;
}
} else {
print!("Create these directories? [Y/n] ");
io::stdout().flush().ok();
let mut input = String::new();
io::stdin().read_line(&mut input).ok();
let trimmed = input.trim().to_lowercase();
if trimmed == "n" || trimmed == "no" {
println!("{}", Paint::red("Aborted."));
return Ok(false);
}
for dir in &dirs_to_create {
std::fs::create_dir_all(dir)?;
println!(" {} Created {}", Paint::green("✓"), dir.display());
}
}
println!();
}
Ok(true)
}
pub async fn setup_shell(
#[allow(unused_variables)] app: &mut App,
#[allow(unused_variables)] using_env_var: bool,
#[allow(unused_variables)] dry_run: bool,
#[allow(unused_variables)] no_interactive: bool,
) -> crate::Result<()> {
#[cfg(target_os = "linux")]
{
println!(
"{} No setup needed. Your system uses XDG directories. Run {} to initialize.",
Paint::green("✓"),
Paint::blue("zv sync")
);
return Ok(());
}
#[cfg(target_os = "macos")]
if app.paths.tier == 1 && !using_env_var {
println!(
"{} No setup needed. Your system uses XDG directories. Run {} to initialize.",
Paint::green("✓"),
Paint::blue("zv sync")
);
return Ok(());
}
#[cfg(not(target_os = "linux"))]
{
if !dry_run {
let proceed = print_dir_table_and_ensure(app)?;
if !proceed {
return Ok(());
}
}
if app.source_set {
println!(
"{}",
Paint::white("✓ Shell environment PATH already includes path to zv")
);
let context = SetupContext::new_with_interactive(
app.shell.clone().unwrap_or_default(),
app.clone(),
using_env_var,
dry_run,
no_interactive,
);
post_setup_actions(&context).await?;
return Ok(());
}
let shell = app.shell.clone().unwrap_or_default();
let context = SetupContext::new_with_interactive(
shell,
app.clone(),
using_env_var,
dry_run,
no_interactive,
);
if dry_run {
println!(
"{} zv setup for {} shell...",
Paint::yellow("Previewing"),
Paint::cyan(&context.shell.to_string())
);
} else {
println!(
"Setting up zv for {} shell...",
Paint::cyan(&context.shell.to_string())
);
}
let requirements = pre_setup_checks(&context)
.await
.with_context(|| "Pre-setup checks failed")?;
let final_requirements = if should_use_interactive(&context) {
let interactive_setup = InteractiveSetup::new(context.clone(), requirements.clone());
match interactive_setup.run_interactive_flow().await {
Ok(user_choices) => {
apply_user_choices(requirements, user_choices)?
}
Err(e) => {
if let Some(zv_error) = e.downcast_ref::<crate::ZvError>() {
if is_recoverable_interactive_error(zv_error) {
if let Some(message) = handle_interactive_error(zv_error) {
crate::tools::warn(message);
crate::tools::warn("Falling back to non-interactive mode");
}
requirements
} else {
if let Some(suggestion) = handle_interactive_error(zv_error) {
crate::tools::error(suggestion);
}
return Err(e);
}
} else {
return Err(e);
}
}
}
} else {
requirements
};
execute_setup(&context, &final_requirements)
.await
.with_context(|| "Setup execution failed")?;
if dry_run {
println!("{}", Paint::cyan("→ Dry Run Complete"));
println!("Run {} to apply these changes", Paint::green("zv setup"));
} else {
println!("{}", Paint::green("→ Setup Complete"));
println!(
"Restart your shell or run the appropriate source command to apply changes immediately"
);
}
Ok(())
}
}
#[cfg(not(target_os = "linux"))]
fn should_use_interactive(context: &SetupContext) -> bool {
if context.no_interactive {
return false;
}
if std::env::var("CI").is_ok() {
return false;
}
if let Ok(term) = std::env::var("TERM")
&& term == "dumb"
{
return false;
}
crate::tools::supports_interactive_prompts()
}