1use std::collections::HashMap;
21use std::io::{BufRead, BufReader, Write};
22use std::os::unix::net::{UnixListener, UnixStream};
23use std::path::{Path, PathBuf};
24use std::time::{Duration, Instant};
25
26use anyhow::{bail, Context, Result};
27use serde::{Deserialize, Serialize};
28
29use crate::lsp_client::{LspClient, LspLocation, LspServerConfig};
30
31#[derive(Debug, Serialize, Deserialize)]
36#[serde(tag = "type", rename_all = "snake_case")]
37enum DaemonRequest {
38 Ping { lang_id: String },
40 OpenFile {
42 lang_id: String,
43 rel_path: String,
44 content: String,
45 },
46 GetDefinition {
48 lang_id: String,
49 rel_path: String,
50 line: u32,
51 character: u32,
52 },
53 GetReferences {
55 lang_id: String,
56 rel_path: String,
57 line: u32,
58 character: u32,
59 include_declaration: bool,
60 },
61 GetImplementations {
63 lang_id: String,
64 rel_path: String,
65 line: u32,
66 character: u32,
67 },
68 CloseFile {
70 lang_id: String,
71 rel_path: String,
72 },
73 ShutdownLang { lang_id: String },
75 ShutdownAll,
77 Status,
79}
80
81#[derive(Debug, Serialize, Deserialize)]
82#[serde(tag = "type", rename_all = "snake_case")]
83enum DaemonResponse {
84 Ok,
85 Pong {
86 ready: bool,
87 uptime_secs: u64,
88 },
89 Definition {
90 location: Option<LocationDto>,
91 },
92 References {
93 locations: Vec<LocationDto>,
94 },
95 Implementations {
96 locations: Vec<LocationDto>,
97 },
98 Status {
99 servers: Vec<ServerStatus>,
100 },
101 Error {
102 message: String,
103 },
104}
105
106#[derive(Debug, Clone, Serialize, Deserialize)]
107struct LocationDto {
108 file_path: String,
109 line: u32,
110 character: u32,
111}
112
113impl From<LspLocation> for LocationDto {
114 fn from(loc: LspLocation) -> Self {
115 Self {
116 file_path: loc.file_path,
117 line: loc.line,
118 character: loc.character,
119 }
120 }
121}
122
123impl From<LocationDto> for LspLocation {
124 fn from(dto: LocationDto) -> Self {
125 Self {
126 file_path: dto.file_path,
127 line: dto.line,
128 character: dto.character,
129 }
130 }
131}
132
133#[derive(Debug, Serialize, Deserialize)]
134struct ServerStatus {
135 lang_id: String,
136 ready: bool,
137 uptime_secs: u64,
138 files_opened: usize,
139}
140
141struct ManagedServer {
146 client: LspClient,
147 started_at: Instant,
148 last_used: Instant,
149 files_opened: usize,
150}
151
152pub struct LspDaemon {
154 servers: HashMap<String, ManagedServer>,
155 project_root: PathBuf,
156 socket_path: PathBuf,
157 idle_timeout: Duration,
158}
159
160pub fn daemon_socket_path(project_root: &Path) -> PathBuf {
162 project_root.join(".gid").join("lsp-daemon.sock")
163}
164
165pub fn daemon_pid_path(project_root: &Path) -> PathBuf {
167 project_root.join(".gid").join("lsp-daemon.pid")
168}
169
170impl LspDaemon {
171 pub fn new(project_root: &Path) -> Self {
173 let socket_path = daemon_socket_path(project_root);
174 Self {
175 servers: HashMap::new(),
176 project_root: project_root.to_path_buf(),
177 socket_path,
178 idle_timeout: Duration::from_secs(3600), }
180 }
181
182 pub fn ensure_server(&mut self, lang_id: &str) -> Result<()> {
184 if self.servers.contains_key(lang_id) {
185 return Ok(());
186 }
187
188 let configs = LspServerConfig::detect_available();
189 let config = configs
190 .iter()
191 .find(|c| c.language_id == lang_id)
192 .ok_or_else(|| anyhow::anyhow!("No LSP server available for {}", lang_id))?;
193
194 eprintln!("[LSP daemon] Starting {} server for {}...", lang_id, self.project_root.display());
195 let client = LspClient::start(config, &self.project_root)?;
196
197 self.servers.insert(
198 lang_id.to_string(),
199 ManagedServer {
200 client,
201 started_at: Instant::now(),
202 last_used: Instant::now(),
203 files_opened: 0,
204 },
205 );
206
207 Ok(())
208 }
209
210 pub fn wait_ready(&mut self, lang_id: &str) -> Result<()> {
212 let server = self.servers.get_mut(lang_id)
213 .ok_or_else(|| anyhow::anyhow!("No server for {}", lang_id))?;
214 server.client.wait_until_ready(Duration::from_secs(600))
215 }
216
217 fn handle_request(&mut self, req: DaemonRequest) -> DaemonResponse {
219 match req {
220 DaemonRequest::Ping { lang_id } => {
221 let ready = self.servers.get(&lang_id).is_some();
222 let uptime = self.servers.get(&lang_id)
223 .map(|s| s.started_at.elapsed().as_secs())
224 .unwrap_or(0);
225 DaemonResponse::Pong { ready, uptime_secs: uptime }
226 }
227
228 DaemonRequest::OpenFile { lang_id, rel_path, content } => {
229 if let Err(e) = self.ensure_server(&lang_id) {
230 return DaemonResponse::Error { message: e.to_string() };
231 }
232 let server = self.servers.get_mut(&lang_id).unwrap();
233 server.last_used = Instant::now();
234 match server.client.open_file(&rel_path, &content, &lang_id) {
235 Ok(()) => {
236 server.files_opened += 1;
237 DaemonResponse::Ok
238 }
239 Err(e) => DaemonResponse::Error { message: e.to_string() },
240 }
241 }
242
243 DaemonRequest::GetDefinition { lang_id, rel_path, line, character } => {
244 let server = match self.servers.get_mut(&lang_id) {
245 Some(s) => s,
246 None => return DaemonResponse::Error {
247 message: format!("No server for {}", lang_id),
248 },
249 };
250 server.last_used = Instant::now();
251 match server.client.get_definition(&rel_path, line, character) {
252 Ok(loc) => DaemonResponse::Definition {
253 location: loc.map(LocationDto::from),
254 },
255 Err(e) => DaemonResponse::Error { message: e.to_string() },
256 }
257 }
258
259 DaemonRequest::GetReferences { lang_id, rel_path, line, character, include_declaration } => {
260 let server = match self.servers.get_mut(&lang_id) {
261 Some(s) => s,
262 None => return DaemonResponse::Error {
263 message: format!("No server for {}", lang_id),
264 },
265 };
266 server.last_used = Instant::now();
267 match server.client.get_references(&rel_path, line, character, include_declaration) {
268 Ok(locs) => DaemonResponse::References {
269 locations: locs.into_iter().map(LocationDto::from).collect(),
270 },
271 Err(e) => DaemonResponse::Error { message: e.to_string() },
272 }
273 }
274
275 DaemonRequest::GetImplementations { lang_id, rel_path, line, character } => {
276 let server = match self.servers.get_mut(&lang_id) {
277 Some(s) => s,
278 None => return DaemonResponse::Error {
279 message: format!("No server for {}", lang_id),
280 },
281 };
282 server.last_used = Instant::now();
283 match server.client.get_implementations(&rel_path, line, character) {
284 Ok(locs) => DaemonResponse::Implementations {
285 locations: locs.into_iter().map(LocationDto::from).collect(),
286 },
287 Err(e) => DaemonResponse::Error { message: e.to_string() },
288 }
289 }
290
291 DaemonRequest::CloseFile { lang_id, rel_path } => {
292 if let Some(server) = self.servers.get_mut(&lang_id) {
293 server.last_used = Instant::now();
294 let _ = server.client.close_file(&rel_path);
295 }
296 DaemonResponse::Ok
297 }
298
299 DaemonRequest::ShutdownLang { lang_id } => {
300 if let Some(server) = self.servers.remove(&lang_id) {
301 let _ = server.client.shutdown();
302 }
303 DaemonResponse::Ok
304 }
305
306 DaemonRequest::ShutdownAll => {
307 let keys: Vec<String> = self.servers.keys().cloned().collect();
308 for key in keys {
309 if let Some(server) = self.servers.remove(&key) {
310 let _ = server.client.shutdown();
311 }
312 }
313 DaemonResponse::Ok
314 }
315
316 DaemonRequest::Status => {
317 let servers = self.servers.iter().map(|(lang_id, server)| {
318 ServerStatus {
319 lang_id: lang_id.clone(),
320 ready: true,
321 uptime_secs: server.started_at.elapsed().as_secs(),
322 files_opened: server.files_opened,
323 }
324 }).collect();
325 DaemonResponse::Status { servers }
326 }
327 }
328 }
329
330 pub fn run(&mut self) -> Result<()> {
332 if self.socket_path.exists() {
334 std::fs::remove_file(&self.socket_path)?;
335 }
336
337 if let Some(parent) = self.socket_path.parent() {
339 std::fs::create_dir_all(parent)?;
340 }
341
342 let pid_path = daemon_pid_path(&self.project_root);
344 std::fs::write(&pid_path, std::process::id().to_string())?;
345
346 let listener = UnixListener::bind(&self.socket_path)
347 .with_context(|| format!("bind socket: {}", self.socket_path.display()))?;
348
349 listener.set_nonblocking(true)?;
351
352 eprintln!("[LSP daemon] Listening on {}", self.socket_path.display());
353
354 let mut last_activity = Instant::now();
355
356 loop {
357 if !self.servers.is_empty() {
359 let all_idle = self.servers.values()
360 .all(|s| s.last_used.elapsed() > self.idle_timeout);
361 if all_idle {
362 eprintln!("[LSP daemon] All servers idle for {}s, shutting down",
363 self.idle_timeout.as_secs());
364 break;
365 }
366 } else if last_activity.elapsed() > Duration::from_secs(300) {
367 eprintln!("[LSP daemon] No servers and idle for 5 min, shutting down");
369 break;
370 }
371
372 match listener.accept() {
373 Ok((stream, _)) => {
374 last_activity = Instant::now();
375 if let Err(e) = self.handle_connection(stream) {
376 eprintln!("[LSP daemon] Connection error: {}", e);
377 }
378 }
379 Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => {
380 std::thread::sleep(Duration::from_millis(100));
382 }
383 Err(e) => {
384 eprintln!("[LSP daemon] Accept error: {}", e);
385 break;
386 }
387 }
388 }
389
390 self.handle_request(DaemonRequest::ShutdownAll);
392 let _ = std::fs::remove_file(&self.socket_path);
393 let _ = std::fs::remove_file(&pid_path);
394
395 eprintln!("[LSP daemon] Stopped.");
396 Ok(())
397 }
398
399 fn handle_connection(&mut self, stream: UnixStream) -> Result<()> {
400 stream.set_read_timeout(Some(Duration::from_secs(60)))?;
401 stream.set_write_timeout(Some(Duration::from_secs(60)))?;
402
403 let mut reader = BufReader::new(stream.try_clone()?);
404 let mut writer = stream;
405
406 loop {
408 let mut line = String::new();
409 match reader.read_line(&mut line) {
410 Ok(0) => break, Ok(_) => {}
412 Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock
413 || e.kind() == std::io::ErrorKind::TimedOut => break,
414 Err(e) => return Err(e.into()),
415 }
416
417 let line = line.trim();
418 if line.is_empty() {
419 continue;
420 }
421
422 let req: DaemonRequest = match serde_json::from_str(line) {
423 Ok(r) => r,
424 Err(e) => {
425 let resp = DaemonResponse::Error {
426 message: format!("Invalid request: {}", e),
427 };
428 let mut resp_line = serde_json::to_string(&resp)?;
429 resp_line.push('\n');
430 writer.write_all(resp_line.as_bytes())?;
431 writer.flush()?;
432 continue;
433 }
434 };
435
436 let is_shutdown_all = matches!(req, DaemonRequest::ShutdownAll);
437 let resp = self.handle_request(req);
438
439 let mut resp_line = serde_json::to_string(&resp)?;
440 resp_line.push('\n');
441 writer.write_all(resp_line.as_bytes())?;
442 writer.flush()?;
443
444 if is_shutdown_all {
445 return Ok(());
446 }
447 }
448
449 Ok(())
450 }
451}
452
453pub struct DaemonLspClient {
460 stream: BufReader<UnixStream>,
461 writer: UnixStream,
462 lang_id: String,
463}
464
465impl DaemonLspClient {
466 pub fn connect(project_root: &Path, lang_id: &str) -> Result<Self> {
468 let socket_path = daemon_socket_path(project_root);
469 let stream = UnixStream::connect(&socket_path)
470 .with_context(|| format!("connect to LSP daemon at {}", socket_path.display()))?;
471 stream.set_read_timeout(Some(Duration::from_secs(120)))?;
472 stream.set_write_timeout(Some(Duration::from_secs(30)))?;
473
474 Ok(Self {
475 stream: BufReader::new(stream.try_clone()?),
476 writer: stream,
477 lang_id: lang_id.to_string(),
478 })
479 }
480
481 fn send_recv(&mut self, req: DaemonRequest) -> Result<DaemonResponse> {
482 let mut line = serde_json::to_string(&req)?;
483 line.push('\n');
484 self.writer.write_all(line.as_bytes())?;
485 self.writer.flush()?;
486
487 let mut resp_line = String::new();
488 self.stream.read_line(&mut resp_line)?;
489
490 let resp: DaemonResponse = serde_json::from_str(resp_line.trim())?;
491 Ok(resp)
492 }
493
494 pub fn ping(&mut self) -> Result<bool> {
496 match self.send_recv(DaemonRequest::Ping { lang_id: self.lang_id.clone() })? {
497 DaemonResponse::Pong { ready, .. } => Ok(ready),
498 DaemonResponse::Error { message } => bail!("Daemon error: {}", message),
499 _ => bail!("Unexpected response to ping"),
500 }
501 }
502
503 pub fn open_file(&mut self, rel_path: &str, content: &str) -> Result<()> {
505 match self.send_recv(DaemonRequest::OpenFile {
506 lang_id: self.lang_id.clone(),
507 rel_path: rel_path.to_string(),
508 content: content.to_string(),
509 })? {
510 DaemonResponse::Ok => Ok(()),
511 DaemonResponse::Error { message } => bail!("{}", message),
512 _ => bail!("Unexpected response"),
513 }
514 }
515
516 pub fn get_definition(&mut self, rel_path: &str, line: u32, character: u32) -> Result<Option<LspLocation>> {
518 match self.send_recv(DaemonRequest::GetDefinition {
519 lang_id: self.lang_id.clone(),
520 rel_path: rel_path.to_string(),
521 line,
522 character,
523 })? {
524 DaemonResponse::Definition { location } => Ok(location.map(LspLocation::from)),
525 DaemonResponse::Error { message } => bail!("{}", message),
526 _ => bail!("Unexpected response"),
527 }
528 }
529
530 pub fn get_references(
532 &mut self,
533 rel_path: &str,
534 line: u32,
535 character: u32,
536 include_declaration: bool,
537 ) -> Result<Vec<LspLocation>> {
538 match self.send_recv(DaemonRequest::GetReferences {
539 lang_id: self.lang_id.clone(),
540 rel_path: rel_path.to_string(),
541 line,
542 character,
543 include_declaration,
544 })? {
545 DaemonResponse::References { locations } => {
546 Ok(locations.into_iter().map(LspLocation::from).collect())
547 }
548 DaemonResponse::Error { message } => bail!("{}", message),
549 _ => bail!("Unexpected response"),
550 }
551 }
552
553 pub fn get_implementations(&mut self, rel_path: &str, line: u32, character: u32) -> Result<Vec<LspLocation>> {
555 match self.send_recv(DaemonRequest::GetImplementations {
556 lang_id: self.lang_id.clone(),
557 rel_path: rel_path.to_string(),
558 line,
559 character,
560 })? {
561 DaemonResponse::Implementations { locations } => {
562 Ok(locations.into_iter().map(LspLocation::from).collect())
563 }
564 DaemonResponse::Error { message } => bail!("{}", message),
565 _ => bail!("Unexpected response"),
566 }
567 }
568
569 pub fn close_file(&mut self, rel_path: &str) -> Result<()> {
571 let _ = self.send_recv(DaemonRequest::CloseFile {
572 lang_id: self.lang_id.clone(),
573 rel_path: rel_path.to_string(),
574 });
575 Ok(())
576 }
577}
578
579pub fn is_daemon_running(project_root: &Path) -> bool {
585 let socket_path = daemon_socket_path(project_root);
586
587 if !socket_path.exists() {
588 return false;
589 }
590
591 if UnixStream::connect(&socket_path).is_ok() {
593 return true;
594 }
595
596 let _ = std::fs::remove_file(&socket_path);
598 let _ = std::fs::remove_file(&daemon_pid_path(project_root));
599 false
600}
601
602pub fn ensure_daemon(project_root: &Path) -> Result<bool> {
605 if is_daemon_running(project_root) {
606 return Ok(false);
607 }
608
609 eprintln!("[LSP] Starting daemon for {}...", project_root.display());
610
611 let root = project_root.to_path_buf();
616 std::thread::spawn(move || {
617 let mut daemon = LspDaemon::new(&root);
618 if let Err(e) = daemon.run() {
619 eprintln!("[LSP daemon] Error: {}", e);
620 }
621 });
622
623 let socket_path = daemon_socket_path(project_root);
625 let deadline = Instant::now() + Duration::from_secs(5);
626 while Instant::now() < deadline {
627 if socket_path.exists() {
628 return Ok(true);
629 }
630 std::thread::sleep(Duration::from_millis(100));
631 }
632
633 bail!("Daemon did not start within 5 seconds")
634}
635
636pub fn get_or_start_daemon_client(
639 project_root: &Path,
640 lang_id: &str,
641) -> Result<DaemonLspClient> {
642 ensure_daemon(project_root)?;
643 DaemonLspClient::connect(project_root, lang_id)
644}
645
646pub fn stop_daemon(project_root: &Path) -> Result<()> {
648 if !is_daemon_running(project_root) {
649 return Ok(());
650 }
651
652 let socket_path = daemon_socket_path(project_root);
653 if let Ok(stream) = UnixStream::connect(&socket_path) {
654 stream.set_write_timeout(Some(Duration::from_secs(5)))?;
655 let mut writer = stream;
656 let req = serde_json::to_string(&DaemonRequest::ShutdownAll)?;
657 let _ = writer.write_all(format!("{}\n", req).as_bytes());
658 let _ = writer.flush();
659 }
660
661 std::thread::sleep(Duration::from_millis(500));
663
664 let _ = std::fs::remove_file(&socket_path);
666 let _ = std::fs::remove_file(&daemon_pid_path(project_root));
667
668 Ok(())
669}