use std::env;
use std::fs;
use std::io::{self, BufRead, BufWriter, StdoutLock, Write};
use std::path::{Path, PathBuf};
use crate::cli::{Args, ColorWhen};
use crate::executor::{ExecutableCheck, SearchResult};
use crate::history::{HistoryContext, HistoryScope};
use crate::output::OutputFormatter;
use crate::path::PathSearcher;
use crate::path_resolver;
use crate::shell_integration;
use crate::system;
use crate::venv_manager;
fn get_session_pid() -> Result<u32, std::io::Error> {
if let Ok(pid_str) = env::var("WHI_SESSION_PID") {
pid_str.parse::<u32>().map_err(|_| {
std::io::Error::new(
std::io::ErrorKind::InvalidData,
"Invalid WHI_SESSION_PID value",
)
})
} else {
system::get_parent_pid()
}
}
fn write_snapshot_safe(new_path: &str, args: &Args) {
match history_for_current_scope() {
Ok(history) => {
if let Err(e) = history.write_snapshot(new_path) {
if !args.quiet && !args.silent {
eprintln!("Warning: Failed to write snapshot: {e}");
}
}
}
Err(e) => {
if !args.quiet && !args.silent {
eprintln!("Warning: Failed to acquire history: {e}");
}
}
}
}
fn history_for_current_scope() -> Result<HistoryContext, String> {
let pid = get_session_pid().map_err(|e| e.to_string())?;
if venv_manager::is_in_venv() {
if let Ok(dir) = env::var("WHI_VENV_DIR") {
if !dir.is_empty() {
let path = PathBuf::from(dir);
return HistoryContext::venv(pid, path.as_path());
}
}
}
HistoryContext::global(pid)
}
fn output_path(out: &mut BufWriter<StdoutLock>, new_path: &str) -> i32 {
writeln!(out, "{new_path}").ok();
out.flush().ok();
0
}
fn handle_path_result(
result: Result<String, String>,
args: &Args,
out: &mut BufWriter<StdoutLock>,
) -> i32 {
match result {
Ok(new_path) => {
write_snapshot_safe(&new_path, args);
output_path(out, &new_path)
}
Err(e) => {
if !args.silent {
eprintln!("Error: {e}");
}
2
}
}
}
#[allow(clippy::too_many_lines)]
#[must_use]
pub fn run(args: &Args) -> i32 {
if let Err(e) = crate::config::ensure_config_exists() {
eprintln!("Error: {e}");
return 2;
}
if let Some(ref shell) = args.init_shell {
match shell_integration::generate_init_script(shell) {
Ok(script) => {
print!("{script}");
return 0;
}
Err(err) => {
eprintln!("Error: {err}");
return 2;
}
}
}
if let Some(shell_opt) = &args.apply_shell {
return handle_apply(shell_opt.as_ref(), args.no_protect, args.apply_force);
}
if let Some(profile_name) = &args.save_profile {
return handle_save_profile(profile_name);
}
if let Some(profile_name) = &args.load_profile {
return handle_load_profile(profile_name);
}
if let Some(profile_name) = &args.remove_profile {
return handle_remove_profile(profile_name);
}
if args.reset {
return handle_reset();
}
if let Some(count) = args.undo_count {
return handle_undo(count);
}
if let Some(count) = args.redo_count {
return handle_redo(count);
}
if args.diff {
return handle_diff(args.diff_full);
}
let path_var = match &args.path_override {
Some(p) => p.clone(),
None => env::var("PATH").unwrap_or_default(),
};
let searcher = PathSearcher::new(&path_var);
let stdout = io::stdout();
let mut out = BufWriter::new(stdout.lock());
if args.clean {
let (new_path, _removed_indices) = searcher.clean_duplicates();
write_snapshot_safe(&new_path, args);
return output_path(&mut out, &new_path);
}
if !args.delete_targets.is_empty() {
return handle_delete(&searcher, &args.delete_targets, args, &mut out);
}
if let Some((from, to)) = args.move_indices {
return handle_path_result(searcher.move_entry(from, to), args, &mut out);
}
if let Some((idx1, idx2)) = args.swap_indices {
return handle_path_result(searcher.swap_entries(idx1, idx2), args, &mut out);
}
if let Some(ref target) = args.prefer_target {
return handle_prefer(&searcher, target, args, &mut out);
}
let names = get_names(args);
if names.is_empty() {
let num_dirs = searcher.dirs().len();
if num_dirs > 999 {
if !args.silent {
eprintln!("Error: PATH has {num_dirs} entries (max 999 supported)");
}
return 3;
}
for (idx, dir) in searcher.dirs().iter().enumerate() {
if args.no_index {
writeln!(out, "{}", dir.display()).ok();
} else {
writeln!(out, "{:>4} {}", format!("[{}]", idx + 1), dir.display()).ok();
}
}
out.flush().ok();
return 0;
}
let mut all_found = true;
if names.is_empty() {
eprintln!("Usage: whi [OPTIONS] [NAME]...\n whi <COMMAND>\n\nTry 'whi --help' for more information.");
return 2;
}
let stderr = io::stderr();
let mut err = BufWriter::new(stderr.lock());
let use_color = should_use_color(args);
let mut formatter = OutputFormatter::new(use_color, args.print0);
for name in names {
let results = search_name(&searcher, &name, args);
if results.is_empty() {
all_found = false;
if !args.silent && !args.quiet {
writeln!(err, "{name}: not found").ok();
}
continue;
}
let max_index = results.iter().map(|r| r.path_index).max().unwrap_or(0);
if max_index > 999 {
if !args.silent {
eprintln!("Error: PATH index {max_index} exceeds max 999");
}
return 3;
}
for (i, result) in results.iter().enumerate() {
let is_winner = i == 0;
formatter
.write_result(
&mut out,
result,
is_winner,
args.follow_symlinks,
!args.no_index,
3, )
.ok();
if (!args.all && !args.full) || args.one {
break;
}
}
if args.full {
writeln!(out).ok();
let match_indices: std::collections::HashSet<usize> =
results.iter().map(|r| r.path_index).collect();
for (idx, dir) in searcher.dirs().iter().enumerate() {
let path_index = idx + 1;
let has_match = match_indices.contains(&path_index);
if !args.no_index {
write!(out, "{:>4} ", format!("[{}]", path_index)).ok();
}
if use_color && has_match {
writeln!(out, "\x1b[33m{}\x1b[0m", dir.display()).ok();
} else {
writeln!(out, "{}", dir.display()).ok();
}
}
}
}
out.flush().ok();
err.flush().ok();
i32::from(!all_found)
}
fn get_names(args: &Args) -> Vec<String> {
if !args.names.is_empty() {
return args.names.clone();
}
if !atty::is(atty::Stream::Stdin) {
let stdin = io::stdin();
let mut names = Vec::new();
for line in stdin.lock().lines().map_while(Result::ok) {
let trimmed = line.trim();
if !trimmed.is_empty() && !trimmed.starts_with('#') {
names.push(trimmed.to_string());
}
}
return names;
}
Vec::new()
}
fn search_name(searcher: &PathSearcher, name: &str, args: &Args) -> Vec<SearchResult> {
if name.contains('/') {
let path = PathBuf::from(name);
if let Some(result) = check_path(&path, args, 0) {
return vec![result];
}
return vec![];
}
let mut results = Vec::new();
let search_all = args.all || args.full;
for (idx, dir) in searcher.dirs().iter().enumerate() {
let candidate = dir.join(name);
if let Some(result) = check_path(&candidate, args, idx + 1) {
results.push(result);
if !search_all {
break;
}
}
}
results
}
fn check_path(path: &Path, args: &Args, path_index: usize) -> Option<SearchResult> {
let checker = ExecutableCheck::new(path);
if !checker.exists() {
return None;
}
let is_executable = checker.is_executable();
if !is_executable && !args.show_nonexec {
return None;
}
let canonical_path = if args.follow_symlinks {
fs::canonicalize(path).ok()
} else {
None
};
let metadata = if args.stat {
checker.get_file_metadata()
} else {
None
};
Some(SearchResult {
path: path.to_path_buf(),
canonical_path,
metadata,
path_index,
})
}
fn should_use_color(args: &Args) -> bool {
match args.color {
ColorWhen::Always => true,
ColorWhen::Never => false,
ColorWhen::Auto => atty::is(atty::Stream::Stdout),
}
}
fn get_current_exe_dir() -> Option<PathBuf> {
env::current_exe()
.ok()
.and_then(|exe_path| exe_path.parent().map(std::path::Path::to_path_buf))
}
fn handle_prefer<W: Write>(
searcher: &PathSearcher,
target: &crate::cli::PreferTarget,
args: &Args,
out: &mut W,
) -> i32 {
use crate::cli::PreferTarget;
match target {
PreferTarget::IndexBased { name, index } => {
handle_prefer_index(searcher, name, *index, args, out)
}
PreferTarget::PathBased { name, path } => {
handle_prefer_path(searcher, name, path, args, out)
}
PreferTarget::PathOnly { path } => handle_prefer_path_only(searcher, path, args, out),
}
}
fn handle_prefer_index<W: Write>(
searcher: &PathSearcher,
name: &str,
target_idx: usize,
args: &Args,
out: &mut W,
) -> i32 {
let mut search_args = args.clone();
search_args.all = true;
let results = search_name(searcher, name, &search_args);
if results.is_empty() {
if !args.silent {
eprintln!("Error: {name}: not found");
}
return 1;
}
let winner_idx = results[0].path_index;
let target_result = results.iter().find(|r| r.path_index == target_idx);
if target_result.is_none() {
if !args.silent {
eprintln!("Error: {name} not found at index {target_idx}");
}
return 2;
}
let new_position = if target_idx > winner_idx {
winner_idx
} else {
if !args.silent {
eprintln!(
"Error: {name} at index {target_idx} is already preferred over index {winner_idx}"
);
}
return 2;
};
match searcher.move_entry(target_idx, new_position) {
Ok(new_path) => {
write_snapshot_safe(&new_path, args);
writeln!(out, "{new_path}").ok();
out.flush().ok();
0
}
Err(e) => {
if !args.silent {
eprintln!("Error: {e}");
}
2
}
}
}
fn handle_prefer_path<W: Write>(
searcher: &PathSearcher,
name: &str,
path_str: &str,
args: &Args,
out: &mut W,
) -> i32 {
use path_resolver::{looks_like_exact_path, resolve_path};
let cwd = env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
if looks_like_exact_path(path_str) {
match resolve_path(path_str, &cwd) {
Ok(resolved_path) => {
handle_prefer_exact_path(searcher, name, &resolved_path, args, out)
}
Err(e) => {
if !args.silent {
eprintln!("Error resolving path: {e}");
}
2
}
}
} else {
handle_prefer_fuzzy(searcher, name, path_str, args, out)
}
}
fn handle_prefer_exact_path<W: Write>(
searcher: &PathSearcher,
name: &str,
path: &Path,
args: &Args,
out: &mut W,
) -> i32 {
if !path.exists() {
if !args.silent {
eprintln!("Error: Directory does not exist: {}", path.display());
}
return 2;
}
if let Some(idx) = searcher.find_path_index(path) {
return handle_prefer_index(searcher, name, idx, args, out);
}
if !searcher.has_executable(path, name) {
if !args.silent {
eprintln!("Error: {} not found in {}", name, path.display());
}
return 2;
}
let results = search_name(searcher, name, args);
let insert_position = if results.is_empty() {
1
} else {
results[0].path_index
};
match searcher.add_path_at_position(path, insert_position) {
Ok(new_path) => {
if !args.silent {
eprintln!(
"Added {} to PATH at index {}",
path.display(),
insert_position
);
}
write_snapshot_safe(&new_path, args);
writeln!(out, "{new_path}").ok();
out.flush().ok();
0
}
Err(e) => {
if !args.silent {
eprintln!("Error adding to PATH: {e}");
}
2
}
}
}
fn handle_prefer_path_only<W: Write>(
searcher: &PathSearcher,
path_str: &str,
args: &Args,
out: &mut W,
) -> i32 {
use path_resolver::{looks_like_exact_path, resolve_path};
let cwd = env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
let resolved_path = if looks_like_exact_path(path_str) {
match resolve_path(path_str, &cwd) {
Ok(path) => path,
Err(e) => {
if !args.silent {
eprintln!("Error resolving path: {e}");
}
return 2;
}
}
} else {
cwd.join(path_str)
};
if let Some(_idx) = searcher.find_path_index(&resolved_path) {
if !args.silent {
eprintln!("{} is already in PATH", resolved_path.display());
}
writeln!(out, "{}", searcher.to_path_string()).ok();
out.flush().ok();
return 0;
}
match searcher.add_path(&resolved_path) {
Ok((new_path, idx)) => {
if !args.silent {
eprintln!("Added {} to PATH at index {}", resolved_path.display(), idx);
}
write_snapshot_safe(&new_path, args);
writeln!(out, "{new_path}").ok();
out.flush().ok();
0
}
Err(e) => {
if !args.silent {
eprintln!("Error adding to PATH: {e}");
}
2
}
}
}
fn handle_prefer_fuzzy<W: Write>(
searcher: &PathSearcher,
name: &str,
pattern: &str,
args: &Args,
out: &mut W,
) -> i32 {
let matches = searcher.find_fuzzy_indices(pattern, Some(name));
if matches.is_empty() {
if !args.silent {
eprintln!("Error: No PATH entries match pattern '{pattern}' containing '{name}'");
}
return 1;
}
if matches.len() > 1 {
if !args.silent {
eprintln!("Error: Multiple PATH entries match pattern '{pattern}':");
for (idx, path) in &matches {
eprintln!(" [{}] {}", idx, path.display());
}
eprintln!("Please be more specific or use an index directly.");
}
return 2;
}
let (index, _) = matches[0];
handle_prefer_index(searcher, name, index, args, out)
}
fn handle_delete<W: Write>(
searcher: &PathSearcher,
targets: &[crate::cli::DeleteTarget],
args: &Args,
out: &mut W,
) -> i32 {
use crate::cli::DeleteTarget;
use crate::path_resolver::{looks_like_exact_path, resolve_path};
let cwd = env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
let mut indices_to_delete = Vec::new();
for target in targets {
match target {
DeleteTarget::Index(idx) => {
indices_to_delete.push(*idx);
}
DeleteTarget::Path(path_str) => {
if looks_like_exact_path(path_str) {
match resolve_path(path_str, &cwd) {
Ok(resolved) => {
if let Some(idx) = searcher.find_path_index(&resolved) {
indices_to_delete.push(idx);
} else {
if !args.silent {
eprintln!(
"Error: Path not found in PATH: {}",
resolved.display()
);
}
return 1;
}
}
Err(e) => {
if !args.silent {
eprintln!("Error resolving path: {e}");
}
return 2;
}
}
} else {
let matches = searcher.find_fuzzy_indices(path_str, None);
if matches.is_empty() {
if !args.silent {
eprintln!("Error: No PATH entries match pattern '{path_str}'");
}
return 1;
}
for (idx, _) in &matches {
indices_to_delete.push(*idx);
}
}
}
}
}
let dirs = searcher.dirs();
if let Some(exe_dir) = get_current_exe_dir() {
let canonical_exe_dir = fs::canonicalize(&exe_dir).unwrap_or_else(|_| exe_dir.clone());
indices_to_delete.retain(|&idx| {
if idx > 0 && idx <= dirs.len() {
let path = &dirs[idx - 1];
let canonical_path = fs::canonicalize(path).unwrap_or_else(|_| path.clone());
path != &exe_dir
&& path != &canonical_exe_dir
&& canonical_path != exe_dir
&& canonical_path != canonical_exe_dir
} else {
true
}
});
}
indices_to_delete.sort_unstable();
indices_to_delete.dedup();
if !args.silent && indices_to_delete.len() > 1 {
for &idx in &indices_to_delete {
if idx > 0 && idx <= dirs.len() {
eprintln!("{:>4} {}", format!("[{}]", idx), dirs[idx - 1].display());
}
}
}
let result = if indices_to_delete.len() == 1 {
searcher.delete_entry(indices_to_delete[0])
} else {
searcher.delete_entries(&indices_to_delete)
};
match result {
Ok(new_path) => {
write_snapshot_safe(&new_path, args);
writeln!(out, "{new_path}").ok();
out.flush().ok();
0
}
Err(e) => {
if !args.silent {
eprintln!("Error: {e}");
}
2
}
}
}
#[allow(clippy::too_many_lines)]
fn handle_apply(shell_opt: Option<&String>, no_protect: bool, force: bool) -> i32 {
use crate::config::load_config;
use crate::config_manager::save_path;
use crate::session_tracker::cleanup_old_sessions;
use crate::shell_detect::{detect_current_shell, Shell};
use std::collections::HashSet;
if venv_manager::is_in_venv() && !force {
eprintln!("Error: Refusing to run 'whi apply' inside an active PATH environment. Exit the venv or re-run with '--force' (optionally with '--no-protect').");
return 2;
}
let mut path_var = env::var("PATH").unwrap_or_default();
if !no_protect {
if let Ok(config) = load_config() {
let current_paths: HashSet<String> = path_var
.split(':')
.filter(|s| !s.is_empty())
.map(std::string::ToString::to_string)
.collect();
let protected_paths: Vec<String> = config
.protected
.paths
.iter()
.filter_map(|p| {
let path_str = p.to_string_lossy().to_string();
if current_paths.contains(&path_str) {
None
} else {
Some(path_str)
}
})
.collect();
if !protected_paths.is_empty() {
let protected_count = protected_paths.len();
path_var = format!("{}:{}", protected_paths.join(":"), path_var);
eprintln!(
"Protected {} system path{}: {}",
protected_count,
if protected_count == 1 { "" } else { "s" },
protected_paths.join(", ")
);
}
}
}
let result = match shell_opt {
None => {
let shell = match detect_current_shell() {
Ok(s) => s,
Err(e) => {
eprintln!("Error: {e}");
return 2;
}
};
if let Err(e) = save_path(&shell, &path_var) {
eprintln!("Error: {e}");
return 2;
}
let num_entries = path_var.split(':').filter(|s| !s.is_empty()).count();
println!(
"Applied PATH to {} ({} entries)",
shell.as_str(),
num_entries
);
0
}
Some(shell_str) => {
if shell_str.to_lowercase() == "all" {
let shells = [Shell::Bash, Shell::Zsh, Shell::Fish];
let mut all_ok = true;
for shell in &shells {
if let Err(e) = save_path(shell, &path_var) {
eprintln!("Error applying to {}: {e}", shell.as_str());
all_ok = false;
} else {
let num_entries = path_var.split(':').filter(|s| !s.is_empty()).count();
println!(
"Applied PATH to {} ({} entries)",
shell.as_str(),
num_entries
);
}
}
if all_ok {
0
} else {
2
}
} else {
let shell = match shell_str.parse::<Shell>() {
Ok(s) => s,
Err(e) => {
eprintln!("Error: {e}");
return 2;
}
};
if let Err(e) = save_path(&shell, &path_var) {
eprintln!("Error: {e}");
return 2;
}
let num_entries = path_var.split(':').filter(|s| !s.is_empty()).count();
println!(
"Applied PATH to {} ({} entries)",
shell.as_str(),
num_entries
);
0
}
}
};
if result == 0 {
match history_for_current_scope() {
Ok(history) => {
if let Err(e) = history.reset_with_initial(&path_var) {
eprintln!("Warning: Failed to reinitialize history: {e}");
}
if history.scope() == HistoryScope::Global {
let _ = cleanup_old_sessions();
}
}
Err(e) => {
eprintln!("Warning: Failed to update history: {e}");
}
}
}
result
}
fn handle_diff(full: bool) -> i32 {
use crate::path_diff::{compute_diff, format_diff_with_limit};
let current_path = env::var("PATH").unwrap_or_default();
let use_color = atty::is(atty::Stream::Stdout);
let baseline_path = history_for_current_scope()
.ok()
.and_then(|history| history.initial_snapshot().ok().flatten())
.unwrap_or_else(|| current_path.clone());
let diff = compute_diff(¤t_path, &baseline_path, full);
let formatted = format_diff_with_limit(&diff, use_color, full);
println!("{formatted}");
0
}
fn handle_reset() -> i32 {
use std::io::Write;
match history_for_current_scope() {
Ok(history) => match history.initial_snapshot() {
Ok(Some(initial_path)) => {
if let Err(e) = history.truncate(1) {
eprintln!("Warning: Failed to truncate snapshot history: {e}");
}
if let Err(e) = history.clear_cursor() {
eprintln!("Warning: Failed to reset history cursor: {e}");
}
let stdout = io::stdout();
let mut out = BufWriter::new(stdout.lock());
writeln!(out, "{initial_path}").ok();
out.flush().ok();
0
}
Ok(None) => {
eprintln!(
"Error: No initial PATH found. No operations have been performed in this session."
);
1
}
Err(e) => {
eprintln!("Error: {e}");
2
}
},
Err(e) => {
eprintln!("Error: {e}");
2
}
}
}
fn handle_undo(count: usize) -> i32 {
use std::io::Write;
if count == 0 {
eprintln!("Error: Count must be at least 1");
return 2;
}
match history_for_current_scope() {
Ok(history) => match history.read_snapshots() {
Ok(snapshots) => {
if snapshots.is_empty() {
eprintln!(
"Error: No PATH history found. No operations have been performed in this session."
);
return 1;
}
let current_pos = match history.get_cursor() {
Ok(Some(pos)) => pos,
Ok(None) => snapshots.len() - 1,
Err(e) => {
eprintln!("Error: {e}");
return 2;
}
};
if current_pos < count {
if current_pos == 0 {
eprintln!("Error: Cannot undo further. Already at initial PATH state.");
} else {
eprintln!(
"Error: Can only undo {current_pos} more step(s). Use 'whi reset' to go back to the initial state."
);
}
return 1;
}
let target_index = current_pos - count;
let target_snapshot = &snapshots[target_index];
if let Err(e) = history.set_cursor(target_index) {
eprintln!("Error: Failed to set cursor: {e}");
return 2;
}
let stdout = io::stdout();
let mut out = BufWriter::new(stdout.lock());
writeln!(out, "{target_snapshot}").ok();
out.flush().ok();
0
}
Err(e) => {
eprintln!("Error: {e}");
2
}
},
Err(e) => {
eprintln!("Error: {e}");
2
}
}
}
fn handle_redo(count: usize) -> i32 {
use std::io::Write;
if count == 0 {
eprintln!("Error: Count must be at least 1");
return 2;
}
match history_for_current_scope() {
Ok(history) => match history.read_snapshots() {
Ok(snapshots) => {
if snapshots.is_empty() {
eprintln!("Error: No PATH history found. No operations have been performed in this session.");
return 1;
}
let current_pos = match history.get_cursor() {
Ok(Some(pos)) => pos,
Ok(None) => {
eprintln!("Error: Already at the latest state. Nothing to redo.");
return 1;
}
Err(e) => {
eprintln!("Error: {e}");
return 2;
}
};
let max_pos = snapshots.len() - 1;
if current_pos + count > max_pos {
let available = max_pos - current_pos;
if available == 0 {
eprintln!("Error: Already at the latest state. Nothing to redo.");
} else {
eprintln!("Error: Can only redo {available} more step(s).");
}
return 1;
}
let target_index = current_pos + count;
let target_snapshot = &snapshots[target_index];
if target_index == max_pos {
if let Err(e) = history.clear_cursor() {
eprintln!("Error: Failed to clear cursor: {e}");
return 2;
}
} else if let Err(e) = history.set_cursor(target_index) {
eprintln!("Error: Failed to set cursor: {e}");
return 2;
}
let stdout = io::stdout();
let mut out = BufWriter::new(stdout.lock());
writeln!(out, "{target_snapshot}").ok();
out.flush().ok();
0
}
Err(e) => {
eprintln!("Error: {e}");
2
}
},
Err(e) => {
eprintln!("Error: {e}");
2
}
}
}
fn handle_save_profile(profile_name: &str) -> i32 {
use crate::config_manager::save_profile;
let path_var = env::var("PATH").unwrap_or_default();
match save_profile(profile_name, &path_var) {
Ok(()) => {
let num_entries = path_var.split(':').filter(|s| !s.is_empty()).count();
println!("Saved profile '{profile_name}' ({num_entries} entries)");
0
}
Err(e) => {
eprintln!("Error: {e}");
2
}
}
}
fn handle_load_profile(profile_name: &str) -> i32 {
use crate::config_manager::load_profile;
use std::io::Write;
match load_profile(profile_name) {
Ok(mut path_string) => {
if let Some(exe_dir) = get_current_exe_dir() {
let canonical_exe_dir =
fs::canonicalize(&exe_dir).unwrap_or_else(|_| exe_dir.clone());
let path_entries: Vec<&str> = path_string.split(':').collect();
let mut found = false;
for entry in &path_entries {
let entry_path = PathBuf::from(entry);
let canonical_entry =
fs::canonicalize(&entry_path).unwrap_or_else(|_| entry_path.clone());
if entry_path == exe_dir
|| entry_path == canonical_exe_dir
|| canonical_entry == exe_dir
|| canonical_entry == canonical_exe_dir
{
found = true;
break;
}
}
if !found {
if !path_string.is_empty() {
path_string.push(':');
}
path_string.push_str(&exe_dir.display().to_string());
}
}
match history_for_current_scope() {
Ok(history) => {
if let Err(e) = history.write_snapshot(&path_string) {
eprintln!("Warning: Failed to write snapshot for loaded profile: {e}");
}
}
Err(e) => {
eprintln!("Warning: Failed to acquire history for loaded profile: {e}");
}
}
let stdout = io::stdout();
let mut out = BufWriter::new(stdout.lock());
writeln!(out, "{path_string}").ok();
out.flush().ok();
0
}
Err(e) => {
eprintln!("Error: {e}");
1
}
}
}
fn handle_remove_profile(profile_name: &str) -> i32 {
use crate::config_manager::delete_profile;
match delete_profile(profile_name) {
Ok(()) => {
println!("Removed profile '{profile_name}'");
0
}
Err(e) => {
eprintln!("Error: {e}");
1
}
}
}
mod atty {
use std::os::unix::io::AsRawFd;
pub fn is(stream: Stream) -> bool {
let fd = match stream {
Stream::Stdout => std::io::stdout().as_raw_fd(),
Stream::Stdin => std::io::stdin().as_raw_fd(),
};
crate::system::is_tty(fd)
}
#[derive(Copy, Clone)]
pub enum Stream {
Stdout,
Stdin,
}
}