ricecoder_cli/commands/
lsp.rs1use crate::commands::Command;
4use crate::error::{CliError, CliResult};
5use std::sync::atomic::{AtomicBool, Ordering};
6use std::sync::Arc;
7use tracing::{error, info};
8
9#[derive(Debug, Clone)]
11pub struct LspConfig {
12 pub log_level: String,
14 pub port: Option<u16>,
16 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
30pub struct LspCommand {
32 log_level: Option<String>,
33 port: Option<u16>,
34 debug: bool,
35}
36
37impl LspCommand {
38 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 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 let config = self.get_config();
67
68 start_lsp_server(config)
70 }
71}
72
73fn start_lsp_server(config: LspConfig) -> CliResult<()> {
75 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 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 let shutdown = Arc::new(AtomicBool::new(false));
92 let shutdown_clone = shutdown.clone();
93
94 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 use ricecoder_lsp::LspServer;
109
110 let mut server = LspServer::new();
112
113 info!("LSP server initialized");
114 info!("Listening on stdio transport");
115
116 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
130fn init_lsp_logging(config: &LspConfig) -> CliResult<()> {
132 use tracing_subscriber::fmt;
133
134 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 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}