use bkmr_lsp::backend::{BkmrLspBackend, BkmrConfig};
use clap::Parser;
use std::panic;
use tower_lsp::{LspService, Server};
use tracing_subscriber::EnvFilter;
#[derive(Parser)]
#[command(name = "bkmr-lsp")]
#[command(about = "Language Server Protocol implementation for bkmr snippet manager")]
#[command(version = env!("CARGO_PKG_VERSION"))]
struct Args {
#[arg(long, help = "Disable bkmr template interpolation (serve raw templates instead of processed content)")]
no_interpolation: bool,
}
#[tokio::main]
async fn main() {
let args = Args::parse();
panic::set_hook(Box::new(|panic_info| {
eprintln!("PANIC in bkmr-lsp: {}", panic_info);
if let Some(location) = panic_info.location() {
eprintln!(
"Panic occurred in file '{}' at line {}",
location.file(),
location.line()
);
}
if let Some(payload) = panic_info.payload().downcast_ref::<&str>() {
eprintln!("Panic payload: {}", payload);
} else if let Some(payload) = panic_info.payload().downcast_ref::<String>() {
eprintln!("Panic payload: {}", payload);
}
std::process::exit(1);
}));
let result = init_logging();
if let Err(e) = result {
eprintln!(
"Failed to initialize logging: {}, continuing without structured logging",
e
);
}
tracing::info!("Starting bkmr-lsp server v{}", env!("CARGO_PKG_VERSION"));
let config = BkmrConfig {
enable_interpolation: !args.no_interpolation,
..Default::default()
};
tracing::info!("Configuration: {:?}", config);
if let Err(e) = validate_environment().await {
tracing::error!("Environment validation failed: {}", e);
eprintln!("Environment validation failed: {}", e);
std::process::exit(1);
}
let (service, socket) = LspService::new(move |client| {
tracing::debug!("Creating new LSP backend instance");
BkmrLspBackend::with_config(client, config.clone())
});
tracing::info!("LSP service created, starting server on stdin/stdout");
let stdin = tokio::io::stdin();
let stdout = tokio::io::stdout();
tracing::info!("Starting LSP server loop");
Server::new(stdin, stdout, socket).serve(service).await;
tracing::info!("Server shutdown gracefully");
}
fn init_logging() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let filter = EnvFilter::try_from_default_env()
.or_else(|_| EnvFilter::try_new("bkmr_lsp=info"))
.unwrap_or_else(|_| EnvFilter::new("info"));
tracing_subscriber::fmt()
.with_writer(std::io::stderr)
.with_ansi(false) .with_target(false) .with_env_filter(filter)
.try_init()?;
Ok(())
}
async fn validate_environment() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
if atty::is(atty::Stream::Stdin) || atty::is(atty::Stream::Stdout) {
eprintln!("Warning: bkmr-lsp is designed to run as an LSP server");
eprintln!("It should be launched by an LSP client, not directly from a terminal");
eprintln!("If you're testing, pipe some LSP messages to stdin");
}
tokio::time::timeout(std::time::Duration::from_millis(100), async {
tokio::task::yield_now().await
})
.await?;
Ok(())
}