use crate::commands::Command;
use crate::error::{CliError, CliResult};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use tracing::{error, info};
#[derive(Debug, Clone)]
pub struct LspConfig {
pub log_level: String,
pub port: Option<u16>,
pub debug: bool,
}
impl Default for LspConfig {
fn default() -> Self {
Self {
log_level: "info".to_string(),
port: None,
debug: false,
}
}
}
pub struct LspCommand {
log_level: Option<String>,
port: Option<u16>,
debug: bool,
}
impl LspCommand {
pub fn new(log_level: Option<String>, port: Option<u16>, debug: bool) -> Self {
Self {
log_level,
port,
debug,
}
}
pub fn get_config(&self) -> LspConfig {
LspConfig {
log_level: self.log_level.clone().unwrap_or_else(|| {
if self.debug {
"debug".to_string()
} else {
"info".to_string()
}
}),
port: self.port,
debug: self.debug,
}
}
}
impl Command for LspCommand {
fn execute(&self) -> CliResult<()> {
let config = self.get_config();
start_lsp_server(config)
}
}
fn start_lsp_server(config: LspConfig) -> CliResult<()> {
let rt = tokio::runtime::Runtime::new()
.map_err(|e| CliError::Internal(format!("Failed to create runtime: {}", e)))?;
rt.block_on(async {
init_lsp_logging(&config)?;
info!("Starting LSP server");
info!("Log level: {}", config.log_level);
if let Some(port) = config.port {
info!("TCP port: {}", port);
}
info!("Debug mode: {}", config.debug);
let shutdown = Arc::new(AtomicBool::new(false));
let shutdown_clone = shutdown.clone();
let _shutdown_handle = tokio::spawn(async move {
match tokio::signal::ctrl_c().await {
Ok(()) => {
info!("Received shutdown signal (SIGINT)");
shutdown_clone.store(true, Ordering::SeqCst);
}
Err(e) => {
error!("Failed to listen for shutdown signal: {}", e);
}
}
});
use ricecoder_lsp::LspServer;
let mut server = LspServer::new();
info!("LSP server initialized");
info!("Listening on stdio transport");
match server.run().await {
Ok(()) => {
info!("LSP server shut down gracefully");
Ok(())
}
Err(e) => {
error!("LSP server error: {}", e);
Err(CliError::Internal(format!("LSP server error: {}", e)))
}
}
})
}
fn init_lsp_logging(config: &LspConfig) -> CliResult<()> {
use tracing_subscriber::fmt;
let level = match config.log_level.to_lowercase().as_str() {
"trace" => tracing::Level::TRACE,
"debug" => tracing::Level::DEBUG,
"info" => tracing::Level::INFO,
"warn" => tracing::Level::WARN,
"error" => tracing::Level::ERROR,
_ => tracing::Level::INFO,
};
fmt()
.with_max_level(level)
.with_target(config.debug)
.with_thread_ids(config.debug)
.with_file(config.debug)
.with_line_number(config.debug)
.with_writer(std::io::stderr)
.init();
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_lsp_command_creation() {
let cmd = LspCommand::new(Some("debug".to_string()), Some(8080), true);
let config = cmd.get_config();
assert_eq!(config.log_level, "debug");
assert_eq!(config.port, Some(8080));
assert!(config.debug);
}
#[test]
fn test_lsp_command_defaults() {
let cmd = LspCommand::new(None, None, false);
let config = cmd.get_config();
assert_eq!(config.log_level, "info");
assert_eq!(config.port, None);
assert!(!config.debug);
}
#[test]
fn test_lsp_command_debug_mode() {
let cmd = LspCommand::new(None, None, true);
let config = cmd.get_config();
assert_eq!(config.log_level, "debug");
assert!(config.debug);
}
#[test]
fn test_lsp_config_default() {
let config = LspConfig::default();
assert_eq!(config.log_level, "info");
assert_eq!(config.port, None);
assert!(!config.debug);
}
#[test]
fn test_lsp_command_with_port() {
let cmd = LspCommand::new(None, Some(9000), false);
let config = cmd.get_config();
assert_eq!(config.port, Some(9000));
}
#[test]
fn test_lsp_command_with_log_level() {
let cmd = LspCommand::new(Some("trace".to_string()), None, false);
let config = cmd.get_config();
assert_eq!(config.log_level, "trace");
}
}