use std::io;
use std::io::Stdout;
#[cfg(unix)]
use std::os::unix::process::CommandExt;
use std::process::ExitCode;
use std::sync::Arc;
use std::sync::Mutex;
use crossterm::event::DisableFocusChange;
use crossterm::event::DisableMouseCapture;
use crossterm::event::EnableFocusChange;
use crossterm::event::EnableMouseCapture;
use crossterm::execute;
use crossterm::terminal::EnterAlternateScreen;
use crossterm::terminal::LeaveAlternateScreen;
use crossterm::terminal::disable_raw_mode;
use crossterm::terminal::enable_raw_mode;
use ratatui::Terminal;
use ratatui::backend::CrosstermBackend;
use terminal_colorsaurus::QueryOptions;
use terminal_colorsaurus::ThemeMode;
use tui_pane::Appearance;
use super::event_loop;
use super::tree_state;
use crate::config;
use crate::http::HttpClient;
use crate::project::AbsolutePath;
use crate::project::RootItem;
use crate::project::WorkspaceMetadataStore;
use crate::scan;
use crate::scan::BackgroundMsg;
use crate::tui::app::App;
use crate::tui::constants::PERF_LOG_FILE;
use crate::tui::constants::PREVIOUS_PERF_LOG_FILE;
use crate::tui::settings;
fn setup_terminal() -> io::Result<Terminal<CrosstermBackend<Stdout>>> {
enable_raw_mode()?;
let mut stdout = io::stdout();
execute!(stdout, EnterAlternateScreen)?;
rearm_input_modes()?;
let backend = CrosstermBackend::new(stdout);
Terminal::new(backend)
}
pub(super) fn rearm_input_modes() -> io::Result<()> {
execute!(io::stdout(), EnableMouseCapture, EnableFocusChange)
}
fn detect_terminal_appearance() -> Option<Appearance> {
match terminal_colorsaurus::theme_mode(QueryOptions::default()) {
Ok(ThemeMode::Dark) => Some(Appearance::Dark),
Ok(ThemeMode::Light) => Some(Appearance::Light),
Err(err) => {
tracing::debug!(error = %err, "terminal background detection unavailable");
None
},
}
}
fn restore_terminal(terminal: &mut Terminal<CrosstermBackend<Stdout>>) -> io::Result<()> {
disable_raw_mode()?;
execute!(
terminal.backend_mut(),
LeaveAlternateScreen,
DisableMouseCapture,
DisableFocusChange
)?;
Ok(())
}
fn report_fatal(message: &str) {
tracing::error!("{message}");
eprintln!("cargo-port: {message}");
}
pub fn run() -> ExitCode {
let startup_settings = match settings::load_cargo_port_settings_for_startup() {
Ok(settings) => settings,
Err(err) => {
report_fatal(&err);
return ExitCode::FAILURE;
},
};
let cargo_port_config = startup_settings.config.clone();
config::set_active_config(&cargo_port_config);
let perf_log_path = std::env::temp_dir().join(PERF_LOG_FILE);
let previous_perf_log_path = std::env::temp_dir().join(PREVIOUS_PERF_LOG_FILE);
tui_pane::init_perf_log(&perf_log_path, &previous_perf_log_path);
let Ok(rt) = tokio::runtime::Runtime::new() else {
report_fatal("failed to create async runtime");
return ExitCode::FAILURE;
};
let Some(http_client) = HttpClient::new(rt.handle().clone()) else {
report_fatal("failed to create HTTP client");
return ExitCode::FAILURE;
};
let scan_started_at = std::time::Instant::now();
tracing::trace!(
target: tui_pane::PERF_LOG_TARGET,
kind = "initial",
run = 1,
"scan_start"
);
let scan_dirs = scan::resolve_include_dirs(&cargo_port_config.tui.include_dirs);
let metadata_store = Arc::new(Mutex::new(WorkspaceMetadataStore::new()));
let (background_tx, background_rx) = scan::spawn_streaming_scan(
scan_dirs,
&cargo_port_config.tui.inline_dirs,
cargo_port_config.tui.include_non_rust,
http_client.clone(),
Arc::clone(&metadata_store),
);
let appearance_tx = background_tx.clone();
tui_pane::spawn_appearance_poller(rt.handle(), move |appearance| {
let _ = appearance_tx.send(BackgroundMsg::AppearanceChanged(appearance));
});
let projects: Vec<RootItem> = Vec::new();
let original_hook = std::panic::take_hook();
std::panic::set_hook(Box::new(move |panic_info| {
let _ = disable_raw_mode();
let _ = execute!(
io::stdout(),
LeaveAlternateScreen,
DisableMouseCapture,
DisableFocusChange
);
original_hook(panic_info);
}));
let mut terminal = match setup_terminal() {
Ok(t) => t,
Err(e) => {
report_fatal(&format!("failed to initialize terminal: {e}"));
return ExitCode::FAILURE;
},
};
let mut app = match App::new(
&projects,
background_tx,
background_rx,
startup_settings,
http_client,
scan_started_at,
metadata_store,
) {
Ok(app) => app,
Err(e) => {
let _ = restore_terminal(&mut terminal);
report_fatal(&format!("failed to initialize app: {e:#}"));
return ExitCode::FAILURE;
},
};
tracing::info!(perf_log = %perf_log_path.display(), "tui_ready");
app.set_terminal_appearance(detect_terminal_appearance());
let input_rx = event_loop::spawn_input_thread();
let result = event_loop::event_loop(&mut terminal, &mut app, &input_rx);
tree_state::save_tree_state(&app);
let should_restart = app.framework.restart_requested();
let _ = restore_terminal(&mut terminal);
if should_restart {
restart_self();
}
let status = match result {
Ok(()) => 0,
Err(e) => {
report_fatal(&format!("{e}"));
1
},
};
std::process::exit(status);
}
fn restart_self() {
let exe = AbsolutePath::from(
std::env::current_exe().unwrap_or_else(|_| std::path::PathBuf::from("cargo-port")),
);
let args: Vec<String> = std::env::args().skip(1).collect();
#[cfg(unix)]
{
let err = std::process::Command::new(exe.as_path()).args(&args).exec();
tracing::error!("Failed to restart: {err}");
}
#[cfg(windows)]
{
match std::process::Command::new(exe.as_path())
.args(&args)
.spawn()
{
Ok(_) => std::process::exit(0),
Err(err) => tracing::error!("Failed to restart: {err}"),
}
}
}