use clap::{Arg, Command};
use crossterm::event::{self, Event, KeyCode, KeyModifiers};
use crossterm::terminal::{
disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen,
};
use crossterm::ExecutableCommand;
use ratatui::backend::{Backend, CrosstermBackend};
use ratatui::Terminal;
use std::io::{self, stdout};
use std::time::Duration;
use ros2_tui::common::*;
use ros2_tui::topics::*;
fn main() -> io::Result<()> {
let matches = Command::new("topics")
.version(env!("CARGO_PKG_VERSION"))
.author("Till Beemelmanns")
.about("A TUI for monitoring ROS2 topics")
.arg(
Arg::new("refresh")
.long("refresh")
.value_name("SECONDS")
.help("Refresh rate for topic list in seconds")
.default_value("5"),
)
.arg(
Arg::new("detail-refresh")
.long("detail-refresh")
.value_name("SECONDS")
.help("Refresh rate for topic details in seconds")
.default_value("30"),
)
.arg(
Arg::new("no-initial-fetch")
.long("no-initial-fetch")
.help("Skip the initial topic list fetch (for debugging)")
.action(clap::ArgAction::SetTrue),
)
.arg(
Arg::new("verbose")
.short('v')
.long("verbose")
.help("Enable debug logging to topics_debug.log (written in executable directory)")
.action(clap::ArgAction::SetTrue),
)
.get_matches();
let refresh_rate: u64 = matches
.get_one::<String>("refresh")
.unwrap()
.parse()
.unwrap_or(5);
let detail_refresh_rate: u64 = matches
.get_one::<String>("detail-refresh")
.unwrap()
.parse()
.unwrap_or(30);
let _skip_initial_fetch = matches.get_flag("no-initial-fetch");
let verbose = matches.get_flag("verbose");
if verbose {
enable_debug_logging("topics_debug.log")?;
debug_log("=== TOPICS STARTING ===");
}
debug_log(&format!(
"Configuration: refresh_rate={}s, detail_refresh_rate={}s",
refresh_rate, detail_refresh_rate
));
debug_log("Checking if ros2 command is available...");
if let Err(e) = std::process::Command::new("ros2").arg("--help").output() {
debug_log(&format!("Error: ros2 command not found: {:?}", e));
eprintln!("Error: ros2 command not found. Please ensure ROS2 is installed and sourced.");
std::process::exit(1);
}
debug_log("ros2 command is available");
enable_raw_mode()?;
stdout().execute(EnterAlternateScreen)?;
let mut terminal = Terminal::new(CrosstermBackend::new(stdout()))?;
debug_log("Creating App instance...");
let mut app = App::new(
Duration::from_secs(refresh_rate),
Duration::from_secs(detail_refresh_rate),
);
debug_log("App created successfully");
let result = run_app(&mut terminal, &mut app);
disable_raw_mode()?;
stdout().execute(LeaveAlternateScreen)?;
result
}
fn run_app<B: Backend>(terminal: &mut Terminal<B>, app: &mut App) -> io::Result<()>
where
io::Error: From<B::Error>,
{
loop {
app.try_receive_messages();
app.update_loading_animation();
terminal.draw(|f| ui(f, app))?;
if event::poll(Duration::from_millis(50))? {
match event::read()? {
Event::Key(key) => match key.code {
KeyCode::Char('q') => app.on_key('q'),
KeyCode::Char('c') if key.modifiers.contains(KeyModifiers::CONTROL) => {
app.should_quit = true
}
KeyCode::Char('c') if !app.is_in_search_mode() => app.toggle_collapse_all(),
KeyCode::Up => app.on_up(),
KeyCode::Down => app.on_down(),
KeyCode::Left => app.on_left(),
KeyCode::Right => app.on_right(),
KeyCode::Char('k') if !app.is_in_search_mode() => app.on_up(),
KeyCode::Char('j') if !app.is_in_search_mode() => app.on_down(),
KeyCode::Char('h') if !app.is_in_search_mode() => app.on_left(),
KeyCode::Char('l') if !app.is_in_search_mode() => app.on_right(),
KeyCode::Enter => app.on_enter(),
KeyCode::Tab => app.on_tab(),
KeyCode::Char(' ') => app.on_space(),
KeyCode::Char('r') | KeyCode::F(5) => app.on_key('r'),
KeyCode::Char('e') if !app.is_in_search_mode() => app.toggle_echo(),
KeyCode::PageUp => app.echo_page_up(),
KeyCode::PageDown => app.echo_page_down(),
KeyCode::Home => app.echo_home(),
KeyCode::End => app.echo_end(),
KeyCode::F(4) => app.on_f4(),
KeyCode::Esc => app.on_escape(),
KeyCode::Backspace => app.on_backspace(),
KeyCode::Char(c) => app.on_key(c),
_ => {}
},
Event::Resize(_, _) => {
}
_ => {}
}
}
if app.should_quit {
debug_log("Application quitting - shutting down background tasks");
app.shutdown();
return Ok(());
}
}
}