#![forbid(unsafe_code)]
use angular_switcher::cli::Cli;
use angular_switcher::{
config::Config,
error::SwitcherError,
opener::open_in_zed,
resolver::identify_current,
strategy::{select, Mode},
};
use clap::Parser;
use std::path::{Path, PathBuf};
use std::process::ExitCode;
fn main() -> ExitCode {
let cli = Cli::parse();
match run(&cli) {
Ok(()) => ExitCode::SUCCESS,
Err(err) => {
eprintln!("angular-switcher: {err}");
ExitCode::from(u8::try_from(err.exit_code()).unwrap_or(1))
}
}
}
fn run(cli: &Cli) -> Result<(), SwitcherError> {
let file = resolve_input_file(cli)?;
if file.as_os_str().is_empty() {
return Err(SwitcherError::Input(
"no file path given and $ZED_FILE is unset".into(),
));
}
if has_nul(&file) {
return Err(SwitcherError::Input("file path contains a NUL byte".into()));
}
let project_root = std::env::var_os("ZED_WORKTREE_ROOT").map(PathBuf::from);
let (config, source) = Config::load(cli.config.as_deref(), project_root.as_deref())?;
if cli.verbose {
match source {
Some(p) => eprintln!("config: {}", p.display()),
None => eprintln!("config: built-in defaults"),
}
}
let current = identify_current(&file, &config)?;
if cli.verbose {
eprintln!(
"current: target='{}' basename='{}' parent='{}'",
current.target,
current.basename,
current.parent.display()
);
}
let mode = match (&cli.to, cli.cycle, cli.reverse) {
(Some(_), _, _) => Mode::Direct,
(None, _, true) => Mode::CycleBackward,
_ => Mode::CycleForward,
};
let target_ref = cli.to.as_deref();
let next = select(¤t, mode, target_ref, &config)?;
if cli.print {
println!("{}", next.display());
return Ok(());
}
if cli.verbose {
eprintln!("opening: {}", next.display());
}
if cli.no_launch {
return Ok(());
}
open_in_zed(&next)
}
fn resolve_input_file(cli: &Cli) -> Result<PathBuf, SwitcherError> {
if let Some(p) = &cli.file {
return Ok(p.clone());
}
if let Some(env) = std::env::var_os("ZED_FILE") {
return Ok(PathBuf::from(env));
}
Err(SwitcherError::Input(
"missing input file. Pass a path or set $ZED_FILE (Zed tasks do this automatically)."
.into(),
))
}
fn has_nul(path: &Path) -> bool {
path.as_os_str().to_string_lossy().as_bytes().contains(&0)
}