ddf9ebc42f
- 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
121 lines
5.2 KiB
Rust
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);
|
|
}
|
|
}
|
|
}
|
|
}
|