ricecoder_cli/commands/
lsp.rs

1//! LSP command - Start the Language Server Protocol server
2
3use crate::commands::Command;
4use crate::error::{CliError, CliResult};
5use std::sync::atomic::{AtomicBool, Ordering};
6use std::sync::Arc;
7use tracing::{error, info};
8
9/// LSP server configuration
10#[derive(Debug, Clone)]
11pub struct LspConfig {
12    /// Log level (trace, debug, info, warn, error)
13    pub log_level: String,
14    /// Port for TCP transport (future support)
15    pub port: Option<u16>,
16    /// Enable debug mode for verbose logging
17    pub debug: bool,
18}
19
20impl Default for LspConfig {
21    fn default() -> Self {
22        Self {
23            log_level: "info".to_string(),
24            port: None,
25            debug: false,
26        }
27    }
28}
29
30/// LSP command handler
31pub struct LspCommand {
32    log_level: Option<String>,
33    port: Option<u16>,
34    debug: bool,
35}
36
37impl LspCommand {
38    /// Create a new LSP command
39    pub fn new(log_level: Option<String>, port: Option<u16>, debug: bool) -> Self {
40        Self {
41            log_level,
42            port,
43            debug,
44        }
45    }
46
47    /// Get the LSP configuration
48    pub fn get_config(&self) -> LspConfig {
49        LspConfig {
50            log_level: self.log_level.clone().unwrap_or_else(|| {
51                if self.debug {
52                    "debug".to_string()
53                } else {
54                    "info".to_string()
55                }
56            }),
57            port: self.port,
58            debug: self.debug,
59        }
60    }
61}
62
63impl Command for LspCommand {
64    fn execute(&self) -> CliResult<()> {
65        // Build LSP configuration
66        let config = self.get_config();
67
68        // Start the LSP server
69        start_lsp_server(config)
70    }
71}
72
73/// Start the LSP server
74fn start_lsp_server(config: LspConfig) -> CliResult<()> {
75    // Create a runtime for async operations
76    let rt = tokio::runtime::Runtime::new()
77        .map_err(|e| CliError::Internal(format!("Failed to create runtime: {}", e)))?;
78
79    rt.block_on(async {
80        // Initialize logging with configured level
81        init_lsp_logging(&config)?;
82
83        info!("Starting LSP server");
84        info!("Log level: {}", config.log_level);
85        if let Some(port) = config.port {
86            info!("TCP port: {}", port);
87        }
88        info!("Debug mode: {}", config.debug);
89
90        // Create shutdown signal handler
91        let shutdown = Arc::new(AtomicBool::new(false));
92        let shutdown_clone = shutdown.clone();
93
94        // Set up signal handlers for graceful shutdown
95        let _shutdown_handle = tokio::spawn(async move {
96            match tokio::signal::ctrl_c().await {
97                Ok(()) => {
98                    info!("Received shutdown signal (SIGINT)");
99                    shutdown_clone.store(true, Ordering::SeqCst);
100                }
101                Err(e) => {
102                    error!("Failed to listen for shutdown signal: {}", e);
103                }
104            }
105        });
106
107        // Import the LSP server
108        use ricecoder_lsp::LspServer;
109
110        // Create and run the LSP server
111        let mut server = LspServer::new();
112
113        info!("LSP server initialized");
114        info!("Listening on stdio transport");
115
116        // Run the server
117        match server.run().await {
118            Ok(()) => {
119                info!("LSP server shut down gracefully");
120                Ok(())
121            }
122            Err(e) => {
123                error!("LSP server error: {}", e);
124                Err(CliError::Internal(format!("LSP server error: {}", e)))
125            }
126        }
127    })
128}
129
130/// Initialize logging for LSP server
131fn init_lsp_logging(config: &LspConfig) -> CliResult<()> {
132    use tracing_subscriber::fmt;
133
134    // Parse log level
135    let level = match config.log_level.to_lowercase().as_str() {
136        "trace" => tracing::Level::TRACE,
137        "debug" => tracing::Level::DEBUG,
138        "info" => tracing::Level::INFO,
139        "warn" => tracing::Level::WARN,
140        "error" => tracing::Level::ERROR,
141        _ => tracing::Level::INFO,
142    };
143
144    // Initialize tracing subscriber with the specified level
145    fmt()
146        .with_max_level(level)
147        .with_target(config.debug)
148        .with_thread_ids(config.debug)
149        .with_file(config.debug)
150        .with_line_number(config.debug)
151        .with_writer(std::io::stderr)
152        .init();
153
154    Ok(())
155}
156
157#[cfg(test)]
158mod tests {
159    use super::*;
160
161    #[test]
162    fn test_lsp_command_creation() {
163        let cmd = LspCommand::new(Some("debug".to_string()), Some(8080), true);
164        let config = cmd.get_config();
165
166        assert_eq!(config.log_level, "debug");
167        assert_eq!(config.port, Some(8080));
168        assert!(config.debug);
169    }
170
171    #[test]
172    fn test_lsp_command_defaults() {
173        let cmd = LspCommand::new(None, None, false);
174        let config = cmd.get_config();
175
176        assert_eq!(config.log_level, "info");
177        assert_eq!(config.port, None);
178        assert!(!config.debug);
179    }
180
181    #[test]
182    fn test_lsp_command_debug_mode() {
183        let cmd = LspCommand::new(None, None, true);
184        let config = cmd.get_config();
185
186        assert_eq!(config.log_level, "debug");
187        assert!(config.debug);
188    }
189
190    #[test]
191    fn test_lsp_config_default() {
192        let config = LspConfig::default();
193        assert_eq!(config.log_level, "info");
194        assert_eq!(config.port, None);
195        assert!(!config.debug);
196    }
197
198    #[test]
199    fn test_lsp_command_with_port() {
200        let cmd = LspCommand::new(None, Some(9000), false);
201        let config = cmd.get_config();
202
203        assert_eq!(config.port, Some(9000));
204    }
205
206    #[test]
207    fn test_lsp_command_with_log_level() {
208        let cmd = LspCommand::new(Some("trace".to_string()), None, false);
209        let config = cmd.get_config();
210
211        assert_eq!(config.log_level, "trace");
212    }
213}