Files
bbs-md/src/main.rs
T
ruohki ddf9ebc42f feat(02-03): wire main.rs with highlighter init, vault loading, and renderer
- Add highlighter::init_highlighter() call before render_markdown()
- Load index.md via vault::load_document() with proper VaultDocument matching
- Query terminal size before terminal init for initial render width
- Render markdown via renderer::render_markdown() passing content and width
- Pass raw content string to App::new() for resize re-rendering
- Update App::new() call with all 4 arguments (is_login_shell, config, doc, raw)
- All 7 modules declared: app, config, highlighter, renderer, signals, terminal, vault
2026-02-28 22:24:00 +01:00

121 lines
5.2 KiB
Rust

mod app;
mod config;
mod highlighter;
mod renderer;
mod signals;
mod terminal;
mod vault;
fn main() {
// ── PRE-TERMINAL PHASE ────────────────────────────────────────────────────
// Errors here use normal eprintln! — the terminal is not yet in raw mode.
// 1. Detect login shell BEFORE stripping argv[0]
// POSIX: kernel prefixes argv[0] with '-' for login shells.
let is_login_shell = config::detect_login_shell();
// 2. Parse CLI (strips the leading dash from argv[0] before clap sees it)
let cli = config::parse_cli();
// 3. Resolve config path and load config
let config_path = config::resolve_config_path(cli.config.as_deref());
let app_config = match config::load_config(&config_path) {
Ok(c) => c,
Err(e) => {
config::print_config_error(&e);
std::process::exit(1);
}
};
// 3a. Initialize syntax highlighting (one-time, loads embedded syntax definitions)
// Must be called before render_markdown() — highlighter uses OnceLock statics.
highlighter::init_highlighter();
// 3b. Load initial document (index.md from vault) and render it.
// We query the terminal size here for initial render width.
// On resize, the event loop re-renders with the updated width.
let initial_width = ratatui::crossterm::terminal::size()
.map(|(w, _)| w)
.unwrap_or(80);
let (initial_doc, raw_content) = match vault::load_document(&app_config.vault_path, "index.md") {
vault::VaultDocument::Loaded { path, content } => {
let filename = path
.file_name()
.map(|n| n.to_string_lossy().to_string())
.unwrap_or_else(|| "index.md".to_string());
let lines = renderer::render_markdown(&content, initial_width);
let doc = app::DocumentState::Loaded { filename, lines };
(doc, Some(content))
}
vault::VaultDocument::Missing { path } => {
(app::DocumentState::Missing { path }, None)
}
vault::VaultDocument::ReadError { path, reason } => {
(app::DocumentState::Error { path, reason }, None)
}
};
// ── TERMINAL PHASE ────────────────────────────────────────────────────────
// Install safety envelope BEFORE terminal init so panics during init are caught.
// 4. Install panic hook first — covers panics during terminal init itself
terminal::install_panic_hook();
// 5. Register signal handlers before terminal init — covers early SIGHUP
// (e.g. SSH disconnect between login shell launch and first draw)
let signal_flags = match signals::register_signals() {
Ok(s) => s,
Err(e) => {
eprintln!("SYSTEM ERROR: Cannot register signal handlers: {}", e);
std::process::exit(1);
}
};
// 6. Initialize terminal (enables raw mode, clears screen, sets Viewport::Fullscreen)
let mut term = match terminal::init_terminal() {
Ok(t) => t,
Err(e) => {
eprintln!("SYSTEM ERROR: Cannot initialize terminal: {}", e);
std::process::exit(1);
}
};
// ── EVENT LOOP PHASE ──────────────────────────────────────────────────────
// 7. Create app state and run the event loop.
// raw_content is passed so the event loop can re-render on terminal resize.
let mut app_state = app::App::new(is_login_shell, app_config, initial_doc, raw_content);
let shutdown_reason = app_state.run_event_loop(&mut term, &signal_flags);
// ── SHUTDOWN PHASE ────────────────────────────────────────────────────────
// Terminal must be restored in EVERY exit path below.
// 8. Restore terminal — always, regardless of how we got here.
// Must happen BEFORE show_goodbye() since goodbye prints to stdout.
terminal::restore_terminal();
// 9. Handle the shutdown reason
match shutdown_reason {
Ok(app::ShutdownReason::UserQuit) => {
// User deliberately exited — show BBS goodbye message
app::show_goodbye();
}
Ok(app::ShutdownReason::Signal) => {
// SIGHUP or SIGTERM — SSH disconnect or graceful OS shutdown.
// Exit silently: there may be nobody on the other end to see a message.
}
Err(e) => {
// I/O error from the event loop
if e.kind() == std::io::ErrorKind::BrokenPipe {
// SSH connection closed while we were writing — silent exit (LIFE-04).
// Terminal is already restored above; just exit quietly.
} else {
// Unexpected I/O error — log to stderr after terminal restore
eprintln!("SYSTEM ERROR: {}", e);
std::process::exit(1);
}
}
}
}