#![warn(clippy::disallowed_methods)]
use clap::crate_authors;
use std::io;
use std::path::PathBuf;
use std::time::SystemTime;
use clap::{CommandFactory, Parser, Subcommand, ValueEnum};
use clap_complete::generate;
use rand::RngExt;
use starship::context::{Context, Properties, Target};
use starship::module::ALL_MODULES;
use starship::{bug_report, configure, init, logger, num_rayon_threads, print, shadow};
#[derive(Parser, Debug)]
#[clap(
author=crate_authors!(),
version=shadow::PKG_VERSION,
long_version=shadow::CLAP_LONG_VERSION,
about="The cross-shell prompt for astronauts. ☄🌌️",
subcommand_required=true,
arg_required_else_help=true,
)]
struct Cli {
#[clap(subcommand)]
command: Commands,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
enum CompletionShell {
Bash,
Elvish,
Fish,
Nushell,
#[clap(name = "powershell", alias = "pwsh", alias = "power-shell")]
PowerShell,
Zsh,
}
fn generate_shell(shell: impl clap_complete::Generator) {
generate(
shell,
&mut Cli::command(),
"starship",
&mut io::stdout().lock(),
);
}
fn generate_completions(shell: CompletionShell) {
match shell {
CompletionShell::Bash => generate_shell(clap_complete::Shell::Bash),
CompletionShell::Elvish => generate_shell(clap_complete::Shell::Elvish),
CompletionShell::Fish => generate_shell(clap_complete::Shell::Fish),
CompletionShell::PowerShell => generate_shell(clap_complete::Shell::PowerShell),
CompletionShell::Zsh => generate_shell(clap_complete::Shell::Zsh),
CompletionShell::Nushell => generate_shell(clap_complete_nushell::Nushell),
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
enum Statuslines {
#[clap(alias = "claude")]
ClaudeCode,
}
#[derive(Subcommand, Debug)]
enum Commands {
BugReport,
Completions {
#[clap(value_enum)]
shell: CompletionShell,
},
Config {
#[clap(requires = "value")]
name: Option<String>,
value: Option<String>,
},
Explain(Properties),
Init {
shell: String,
#[clap(long)]
print_full_init: bool,
},
Module {
#[clap(required_unless_present("list"))]
name: Option<String>,
#[clap(short, long)]
list: bool,
#[clap(flatten)]
properties: Properties,
},
Preset {
#[clap(required_unless_present("list"), value_enum)]
name: Option<print::Preset>,
#[clap(short, long, conflicts_with = "list")]
output: Option<PathBuf>,
#[clap(short, long)]
list: bool,
},
PrintConfig {
#[clap(short, long)]
default: bool,
name: Vec<String>,
},
Prompt {
#[clap(long)]
right: bool,
#[clap(long, conflicts_with = "right")]
profile: Option<String>,
#[clap(long, conflicts_with = "right", conflicts_with = "profile")]
continuation: bool,
#[clap(flatten)]
properties: Properties,
},
Session,
Statusline {
provider: Statuslines,
#[clap(long)]
profile: Option<String>,
#[clap(flatten)]
properties: Properties,
},
#[clap(hide = true)]
Time,
Timings(Properties),
Toggle {
name: String,
#[clap(default_value = "disabled")]
value: String,
},
#[cfg(feature = "config-schema")]
ConfigSchema,
}
fn main() {
#[cfg(windows)]
let _ = nu_ansi_term::enable_ansi_support();
logger::init();
init_global_threadpool();
rayon::spawn(|| {
let log_dir = logger::get_log_dir();
logger::cleanup_log_files(log_dir);
});
let args = match Cli::try_parse() {
Ok(args) => args,
Err(e) => {
let is_info_only = !e.use_stderr();
let _ = e.print();
let exit_code = if is_info_only {
0
} else {
use io::Write;
let mut stderr = io::stderr();
let _ = writeln!(
stderr,
"\nNOTE:\n passed arguments: {:?}",
std::env::args().skip(1).collect::<Vec<_>>()
);
2
};
std::process::exit(exit_code);
}
};
log::trace!("Parsed arguments: {args:#?}");
match args.command {
Commands::Init {
shell,
print_full_init,
} => {
if print_full_init {
init::init_main(&shell).expect("can't init_main");
} else {
init::init_stub(&shell).expect("can't init_stub");
}
}
Commands::Prompt {
properties,
right,
profile,
continuation,
} => {
let target = match (right, profile, continuation) {
(true, _, _) => Target::Right,
(_, Some(profile_name), _) => Target::Profile(profile_name),
(_, _, true) => Target::Continuation,
(_, _, _) => Target::Main,
};
print::prompt(properties, target);
}
Commands::Module {
name,
list,
properties,
} => {
if list {
println!("Supported modules list");
println!("----------------------");
for modules in ALL_MODULES {
println!("{modules}");
}
}
if let Some(module_name) = name {
print::module(&module_name, properties);
}
}
Commands::Preset { name, list, output } => print::preset_command(name, output, list),
Commands::Config { name, value } => {
let context = Context::default();
if let Some(name) = name {
if let Some(value) = value {
configure::update_configuration(&context, &name, &value);
}
} else if let Err(reason) = configure::edit_configuration(&context, None) {
eprintln!("Could not edit configuration: {reason}");
std::process::exit(1);
}
}
Commands::PrintConfig { default, name } => {
configure::print_configuration(&Context::default(), default, &name);
}
Commands::Toggle { name, value } => {
configure::toggle_configuration(&Context::default(), &name, &value);
}
Commands::BugReport => bug_report::create(),
Commands::Time => {
match SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.ok()
{
Some(time) => println!("{}", time.as_millis()),
None => println!("{}", -1),
}
}
Commands::Explain(props) => print::explain(props),
Commands::Timings(props) => print::timings(props),
Commands::Completions { shell } => generate_completions(shell),
Commands::Session => println!(
"{}",
rand::rng()
.sample_iter(rand::distr::Alphanumeric)
.take(16)
.map(char::from)
.collect::<String>()
),
Commands::Statusline {
provider,
profile,
properties,
} => {
let profile = profile.unwrap_or_else(|| match provider {
Statuslines::ClaudeCode => "claude-code".to_string(),
});
let target = Target::Profile(profile);
print::prompt_with_claude_code(properties, target);
}
#[cfg(feature = "config-schema")]
Commands::ConfigSchema => print::print_schema(),
}
}
fn init_global_threadpool() {
rayon::ThreadPoolBuilder::new()
.num_threads(num_rayon_threads())
.build_global()
.expect("Failed to initialize worker thread pool");
}