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::code_graph::FileDelta;
30use crate::lsp_client::{LspClient, LspLocation, LspServerConfig, extension_to_language_id};
31
32#[derive(Debug, Serialize, Deserialize)]
37#[serde(tag = "type", rename_all = "snake_case")]
38enum DaemonRequest {
39 Ping { lang_id: String },
41 OpenFile {
43 lang_id: String,
44 rel_path: String,
45 content: String,
46 },
47 GetDefinition {
49 lang_id: String,
50 rel_path: String,
51 line: u32,
52 character: u32,
53 },
54 GetReferences {
56 lang_id: String,
57 rel_path: String,
58 line: u32,
59 character: u32,
60 include_declaration: bool,
61 },
62 GetImplementations {
64 lang_id: String,
65 rel_path: String,
66 line: u32,
67 character: u32,
68 },
69 CloseFile {
71 lang_id: String,
72 rel_path: String,
73 },
74 ShutdownLang { lang_id: String },
76 ShutdownAll,
78 Status,
80 RefineIncremental {
82 added: Vec<String>,
83 modified: Vec<String>,
84 deleted: Vec<String>,
85 root_dir: String,
86 },
87}
88
89#[derive(Debug, Serialize, Deserialize)]
90#[serde(tag = "type", rename_all = "snake_case")]
91enum DaemonResponse {
92 Ok,
93 Pong {
94 ready: bool,
95 uptime_secs: u64,
96 },
97 Definition {
98 location: Option<LocationDto>,
99 },
100 References {
101 locations: Vec<LocationDto>,
102 },
103 Implementations {
104 locations: Vec<LocationDto>,
105 },
106 Status {
107 servers: Vec<ServerStatus>,
108 },
109 Error {
110 message: String,
111 },
112 Refined {
113 files_processed: usize,
114 },
115}
116
117#[derive(Debug, Clone, Serialize, Deserialize)]
118struct LocationDto {
119 file_path: String,
120 line: u32,
121 character: u32,
122}
123
124impl From<LspLocation> for LocationDto {
125 fn from(loc: LspLocation) -> Self {
126 Self {
127 file_path: loc.file_path,
128 line: loc.line,
129 character: loc.character,
130 }
131 }
132}
133
134impl From<LocationDto> for LspLocation {
135 fn from(dto: LocationDto) -> Self {
136 Self {
137 file_path: dto.file_path,
138 line: dto.line,
139 character: dto.character,
140 }
141 }
142}
143
144#[derive(Debug, Serialize, Deserialize)]
145struct ServerStatus {
146 lang_id: String,
147 ready: bool,
148 uptime_secs: u64,
149 files_opened: usize,
150}
151
152struct ManagedServer {
157 client: LspClient,
158 started_at: Instant,
159 last_used: Instant,
160 files_opened: usize,
161}
162
163pub struct LspDaemon {
165 servers: HashMap<String, ManagedServer>,
166 project_root: PathBuf,
167 socket_path: PathBuf,
168 idle_timeout: Duration,
169}
170
171pub fn daemon_socket_path(project_root: &Path) -> PathBuf {
173 project_root.join(".gid").join("lsp-daemon.sock")
174}
175
176pub fn daemon_pid_path(project_root: &Path) -> PathBuf {
178 project_root.join(".gid").join("lsp-daemon.pid")
179}
180
181impl LspDaemon {
182 pub fn new(project_root: &Path) -> Self {
184 let socket_path = daemon_socket_path(project_root);
185 Self {
186 servers: HashMap::new(),
187 project_root: project_root.to_path_buf(),
188 socket_path,
189 idle_timeout: Duration::from_secs(3600), }
191 }
192
193 pub fn ensure_server(&mut self, lang_id: &str) -> Result<()> {
195 if self.servers.contains_key(lang_id) {
196 return Ok(());
197 }
198
199 let configs = LspServerConfig::detect_available();
200 let config = configs
201 .iter()
202 .find(|c| c.language_id == lang_id)
203 .ok_or_else(|| anyhow::anyhow!("No LSP server available for {}", lang_id))?;
204
205 eprintln!("[LSP daemon] Starting {} server for {}...", lang_id, self.project_root.display());
206 let client = LspClient::start(config, &self.project_root)?;
207
208 self.servers.insert(
209 lang_id.to_string(),
210 ManagedServer {
211 client,
212 started_at: Instant::now(),
213 last_used: Instant::now(),
214 files_opened: 0,
215 },
216 );
217
218 Ok(())
219 }
220
221 pub fn wait_ready(&mut self, lang_id: &str) -> Result<()> {
223 let server = self.servers.get_mut(lang_id)
224 .ok_or_else(|| anyhow::anyhow!("No server for {}", lang_id))?;
225 server.client.wait_until_ready(Duration::from_secs(600))
226 }
227
228 fn handle_request(&mut self, req: DaemonRequest) -> DaemonResponse {
230 match req {
231 DaemonRequest::Ping { lang_id } => {
232 let ready = self.servers.get(&lang_id).is_some();
233 let uptime = self.servers.get(&lang_id)
234 .map(|s| s.started_at.elapsed().as_secs())
235 .unwrap_or(0);
236 DaemonResponse::Pong { ready, uptime_secs: uptime }
237 }
238
239 DaemonRequest::OpenFile { lang_id, rel_path, content } => {
240 if let Err(e) = self.ensure_server(&lang_id) {
241 return DaemonResponse::Error { message: e.to_string() };
242 }
243 let server = self.servers.get_mut(&lang_id).unwrap();
244 server.last_used = Instant::now();
245 match server.client.open_file(&rel_path, &content, &lang_id) {
246 Ok(()) => {
247 server.files_opened += 1;
248 DaemonResponse::Ok
249 }
250 Err(e) => DaemonResponse::Error { message: e.to_string() },
251 }
252 }
253
254 DaemonRequest::GetDefinition { lang_id, rel_path, line, character } => {
255 let server = match self.servers.get_mut(&lang_id) {
256 Some(s) => s,
257 None => return DaemonResponse::Error {
258 message: format!("No server for {}", lang_id),
259 },
260 };
261 server.last_used = Instant::now();
262 match server.client.get_definition(&rel_path, line, character) {
263 Ok(loc) => DaemonResponse::Definition {
264 location: loc.map(LocationDto::from),
265 },
266 Err(e) => DaemonResponse::Error { message: e.to_string() },
267 }
268 }
269
270 DaemonRequest::GetReferences { lang_id, rel_path, line, character, include_declaration } => {
271 let server = match self.servers.get_mut(&lang_id) {
272 Some(s) => s,
273 None => return DaemonResponse::Error {
274 message: format!("No server for {}", lang_id),
275 },
276 };
277 server.last_used = Instant::now();
278 match server.client.get_references(&rel_path, line, character, include_declaration) {
279 Ok(locs) => DaemonResponse::References {
280 locations: locs.into_iter().map(LocationDto::from).collect(),
281 },
282 Err(e) => DaemonResponse::Error { message: e.to_string() },
283 }
284 }
285
286 DaemonRequest::GetImplementations { lang_id, rel_path, line, character } => {
287 let server = match self.servers.get_mut(&lang_id) {
288 Some(s) => s,
289 None => return DaemonResponse::Error {
290 message: format!("No server for {}", lang_id),
291 },
292 };
293 server.last_used = Instant::now();
294 match server.client.get_implementations(&rel_path, line, character) {
295 Ok(locs) => DaemonResponse::Implementations {
296 locations: locs.into_iter().map(LocationDto::from).collect(),
297 },
298 Err(e) => DaemonResponse::Error { message: e.to_string() },
299 }
300 }
301
302 DaemonRequest::CloseFile { lang_id, rel_path } => {
303 if let Some(server) = self.servers.get_mut(&lang_id) {
304 server.last_used = Instant::now();
305 let _ = server.client.close_file(&rel_path);
306 }
307 DaemonResponse::Ok
308 }
309
310 DaemonRequest::ShutdownLang { lang_id } => {
311 if let Some(server) = self.servers.remove(&lang_id) {
312 let _ = server.client.shutdown();
313 }
314 DaemonResponse::Ok
315 }
316
317 DaemonRequest::ShutdownAll => {
318 let keys: Vec<String> = self.servers.keys().cloned().collect();
319 for key in keys {
320 if let Some(server) = self.servers.remove(&key) {
321 let _ = server.client.shutdown();
322 }
323 }
324 DaemonResponse::Ok
325 }
326
327 DaemonRequest::Status => {
328 let servers = self.servers.iter().map(|(lang_id, server)| {
329 ServerStatus {
330 lang_id: lang_id.clone(),
331 ready: true,
332 uptime_secs: server.started_at.elapsed().as_secs(),
333 files_opened: server.files_opened,
334 }
335 }).collect();
336 DaemonResponse::Status { servers }
337 }
338
339 DaemonRequest::RefineIncremental { added, modified, deleted, root_dir } => {
340 let root = PathBuf::from(&root_dir);
341 let mut processed: usize = 0;
342
343 for rel_path in &deleted {
345 for server in self.servers.values_mut() {
346 server.last_used = Instant::now();
347 let _ = server.client.close_file(rel_path);
348 }
349 processed += 1;
350 }
351
352 for rel_path in &modified {
354 let abs_path = root.join(rel_path);
355 let content = match std::fs::read_to_string(&abs_path) {
356 Ok(c) => c,
357 Err(e) => {
358 eprintln!("[LSP daemon] Failed to read modified file {}: {}", rel_path, e);
359 continue;
360 }
361 };
362 let ext = Path::new(rel_path)
363 .extension()
364 .and_then(|e| e.to_str())
365 .unwrap_or("");
366 let lang_id = extension_to_language_id(ext);
367
368 if let Some(server) = self.servers.get_mut(lang_id) {
369 server.last_used = Instant::now();
370 let _ = server.client.close_file(rel_path);
371 match server.client.open_file(rel_path, &content, lang_id) {
372 Ok(()) => { server.files_opened += 1; }
373 Err(e) => {
374 eprintln!("[LSP daemon] Failed to re-open modified file {}: {}", rel_path, e);
375 continue;
376 }
377 }
378 }
379 processed += 1;
380 }
381
382 for rel_path in &added {
384 let abs_path = root.join(rel_path);
385 let content = match std::fs::read_to_string(&abs_path) {
386 Ok(c) => c,
387 Err(e) => {
388 eprintln!("[LSP daemon] Failed to read added file {}: {}", rel_path, e);
389 continue;
390 }
391 };
392 let ext = Path::new(rel_path)
393 .extension()
394 .and_then(|e| e.to_str())
395 .unwrap_or("");
396 let lang_id = extension_to_language_id(ext);
397
398 if let Err(e) = self.ensure_server(lang_id) {
399 eprintln!("[LSP daemon] Failed to ensure server for {}: {}", lang_id, e);
400 continue;
401 }
402
403 if let Some(server) = self.servers.get_mut(lang_id) {
404 server.last_used = Instant::now();
405 match server.client.open_file(rel_path, &content, lang_id) {
406 Ok(()) => { server.files_opened += 1; }
407 Err(e) => {
408 eprintln!("[LSP daemon] Failed to open added file {}: {}", rel_path, e);
409 continue;
410 }
411 }
412 }
413 processed += 1;
414 }
415
416 DaemonResponse::Refined { files_processed: processed }
417 }
418 }
419 }
420
421 pub fn run(&mut self) -> Result<()> {
423 if self.socket_path.exists() {
425 std::fs::remove_file(&self.socket_path)?;
426 }
427
428 if let Some(parent) = self.socket_path.parent() {
430 std::fs::create_dir_all(parent)?;
431 }
432
433 let pid_path = daemon_pid_path(&self.project_root);
435 std::fs::write(&pid_path, std::process::id().to_string())?;
436
437 let listener = UnixListener::bind(&self.socket_path)
438 .with_context(|| format!("bind socket: {}", self.socket_path.display()))?;
439
440 listener.set_nonblocking(true)?;
442
443 eprintln!("[LSP daemon] Listening on {}", self.socket_path.display());
444
445 let mut last_activity = Instant::now();
446
447 loop {
448 if !self.servers.is_empty() {
450 let all_idle = self.servers.values()
451 .all(|s| s.last_used.elapsed() > self.idle_timeout);
452 if all_idle {
453 eprintln!("[LSP daemon] All servers idle for {}s, shutting down",
454 self.idle_timeout.as_secs());
455 break;
456 }
457 } else if last_activity.elapsed() > Duration::from_secs(300) {
458 eprintln!("[LSP daemon] No servers and idle for 5 min, shutting down");
460 break;
461 }
462
463 match listener.accept() {
464 Ok((stream, _)) => {
465 last_activity = Instant::now();
466 if let Err(e) = self.handle_connection(stream) {
467 eprintln!("[LSP daemon] Connection error: {}", e);
468 }
469 }
470 Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => {
471 std::thread::sleep(Duration::from_millis(100));
473 }
474 Err(e) => {
475 eprintln!("[LSP daemon] Accept error: {}", e);
476 break;
477 }
478 }
479 }
480
481 self.handle_request(DaemonRequest::ShutdownAll);
483 let _ = std::fs::remove_file(&self.socket_path);
484 let _ = std::fs::remove_file(&pid_path);
485
486 eprintln!("[LSP daemon] Stopped.");
487 Ok(())
488 }
489
490 fn handle_connection(&mut self, stream: UnixStream) -> Result<()> {
491 stream.set_read_timeout(Some(Duration::from_secs(60)))?;
492 stream.set_write_timeout(Some(Duration::from_secs(60)))?;
493
494 let mut reader = BufReader::new(stream.try_clone()?);
495 let mut writer = stream;
496
497 loop {
499 let mut line = String::new();
500 match reader.read_line(&mut line) {
501 Ok(0) => break, Ok(_) => {}
503 Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock
504 || e.kind() == std::io::ErrorKind::TimedOut => break,
505 Err(e) => return Err(e.into()),
506 }
507
508 let line = line.trim();
509 if line.is_empty() {
510 continue;
511 }
512
513 let req: DaemonRequest = match serde_json::from_str(line) {
514 Ok(r) => r,
515 Err(e) => {
516 let resp = DaemonResponse::Error {
517 message: format!("Invalid request: {}", e),
518 };
519 let mut resp_line = serde_json::to_string(&resp)?;
520 resp_line.push('\n');
521 writer.write_all(resp_line.as_bytes())?;
522 writer.flush()?;
523 continue;
524 }
525 };
526
527 let is_shutdown_all = matches!(req, DaemonRequest::ShutdownAll);
528 let resp = self.handle_request(req);
529
530 let mut resp_line = serde_json::to_string(&resp)?;
531 resp_line.push('\n');
532 writer.write_all(resp_line.as_bytes())?;
533 writer.flush()?;
534
535 if is_shutdown_all {
536 return Ok(());
537 }
538 }
539
540 Ok(())
541 }
542}
543
544pub struct DaemonLspClient {
551 stream: BufReader<UnixStream>,
552 writer: UnixStream,
553 lang_id: String,
554}
555
556impl DaemonLspClient {
557 pub fn connect(project_root: &Path, lang_id: &str) -> Result<Self> {
559 let socket_path = daemon_socket_path(project_root);
560 let stream = UnixStream::connect(&socket_path)
561 .with_context(|| format!("connect to LSP daemon at {}", socket_path.display()))?;
562 stream.set_read_timeout(Some(Duration::from_secs(120)))?;
563 stream.set_write_timeout(Some(Duration::from_secs(30)))?;
564
565 Ok(Self {
566 stream: BufReader::new(stream.try_clone()?),
567 writer: stream,
568 lang_id: lang_id.to_string(),
569 })
570 }
571
572 fn send_recv(&mut self, req: DaemonRequest) -> Result<DaemonResponse> {
573 let mut line = serde_json::to_string(&req)?;
574 line.push('\n');
575 self.writer.write_all(line.as_bytes())?;
576 self.writer.flush()?;
577
578 let mut resp_line = String::new();
579 self.stream.read_line(&mut resp_line)?;
580
581 let resp: DaemonResponse = serde_json::from_str(resp_line.trim())?;
582 Ok(resp)
583 }
584
585 pub fn ping(&mut self) -> Result<bool> {
587 match self.send_recv(DaemonRequest::Ping { lang_id: self.lang_id.clone() })? {
588 DaemonResponse::Pong { ready, .. } => Ok(ready),
589 DaemonResponse::Error { message } => bail!("Daemon error: {}", message),
590 _ => bail!("Unexpected response to ping"),
591 }
592 }
593
594 pub fn open_file(&mut self, rel_path: &str, content: &str) -> Result<()> {
596 match self.send_recv(DaemonRequest::OpenFile {
597 lang_id: self.lang_id.clone(),
598 rel_path: rel_path.to_string(),
599 content: content.to_string(),
600 })? {
601 DaemonResponse::Ok => Ok(()),
602 DaemonResponse::Error { message } => bail!("{}", message),
603 _ => bail!("Unexpected response"),
604 }
605 }
606
607 pub fn get_definition(&mut self, rel_path: &str, line: u32, character: u32) -> Result<Option<LspLocation>> {
609 match self.send_recv(DaemonRequest::GetDefinition {
610 lang_id: self.lang_id.clone(),
611 rel_path: rel_path.to_string(),
612 line,
613 character,
614 })? {
615 DaemonResponse::Definition { location } => Ok(location.map(LspLocation::from)),
616 DaemonResponse::Error { message } => bail!("{}", message),
617 _ => bail!("Unexpected response"),
618 }
619 }
620
621 pub fn get_references(
623 &mut self,
624 rel_path: &str,
625 line: u32,
626 character: u32,
627 include_declaration: bool,
628 ) -> Result<Vec<LspLocation>> {
629 match self.send_recv(DaemonRequest::GetReferences {
630 lang_id: self.lang_id.clone(),
631 rel_path: rel_path.to_string(),
632 line,
633 character,
634 include_declaration,
635 })? {
636 DaemonResponse::References { locations } => {
637 Ok(locations.into_iter().map(LspLocation::from).collect())
638 }
639 DaemonResponse::Error { message } => bail!("{}", message),
640 _ => bail!("Unexpected response"),
641 }
642 }
643
644 pub fn get_implementations(&mut self, rel_path: &str, line: u32, character: u32) -> Result<Vec<LspLocation>> {
646 match self.send_recv(DaemonRequest::GetImplementations {
647 lang_id: self.lang_id.clone(),
648 rel_path: rel_path.to_string(),
649 line,
650 character,
651 })? {
652 DaemonResponse::Implementations { locations } => {
653 Ok(locations.into_iter().map(LspLocation::from).collect())
654 }
655 DaemonResponse::Error { message } => bail!("{}", message),
656 _ => bail!("Unexpected response"),
657 }
658 }
659
660 pub fn close_file(&mut self, rel_path: &str) -> Result<()> {
662 let _ = self.send_recv(DaemonRequest::CloseFile {
663 lang_id: self.lang_id.clone(),
664 rel_path: rel_path.to_string(),
665 });
666 Ok(())
667 }
668
669 pub fn refine_incremental(&mut self, delta: &FileDelta, root_dir: &Path) -> Result<usize> {
674 match self.send_recv(DaemonRequest::RefineIncremental {
675 added: delta.added.clone(),
676 modified: delta.modified.clone(),
677 deleted: delta.deleted.clone(),
678 root_dir: root_dir.to_string_lossy().to_string(),
679 })? {
680 DaemonResponse::Refined { files_processed } => Ok(files_processed),
681 DaemonResponse::Error { message } => bail!("Refine incremental error: {}", message),
682 _ => bail!("Unexpected response to refine_incremental"),
683 }
684 }
685}
686
687pub fn is_daemon_running(project_root: &Path) -> bool {
693 let socket_path = daemon_socket_path(project_root);
694
695 if !socket_path.exists() {
696 return false;
697 }
698
699 if UnixStream::connect(&socket_path).is_ok() {
701 return true;
702 }
703
704 let _ = std::fs::remove_file(&socket_path);
706 let _ = std::fs::remove_file(&daemon_pid_path(project_root));
707 false
708}
709
710pub fn ensure_daemon(project_root: &Path) -> Result<bool> {
713 if is_daemon_running(project_root) {
714 return Ok(false);
715 }
716
717 eprintln!("[LSP] Starting daemon for {}...", project_root.display());
718
719 let root = project_root.to_path_buf();
724 std::thread::spawn(move || {
725 let mut daemon = LspDaemon::new(&root);
726 if let Err(e) = daemon.run() {
727 eprintln!("[LSP daemon] Error: {}", e);
728 }
729 });
730
731 let socket_path = daemon_socket_path(project_root);
733 let deadline = Instant::now() + Duration::from_secs(5);
734 while Instant::now() < deadline {
735 if socket_path.exists() {
736 return Ok(true);
737 }
738 std::thread::sleep(Duration::from_millis(100));
739 }
740
741 bail!("Daemon did not start within 5 seconds")
742}
743
744pub fn get_or_start_daemon_client(
747 project_root: &Path,
748 lang_id: &str,
749) -> Result<DaemonLspClient> {
750 ensure_daemon(project_root)?;
751 DaemonLspClient::connect(project_root, lang_id)
752}
753
754pub fn stop_daemon(project_root: &Path) -> Result<()> {
756 if !is_daemon_running(project_root) {
757 return Ok(());
758 }
759
760 let socket_path = daemon_socket_path(project_root);
761 if let Ok(stream) = UnixStream::connect(&socket_path) {
762 stream.set_write_timeout(Some(Duration::from_secs(5)))?;
763 let mut writer = stream;
764 let req = serde_json::to_string(&DaemonRequest::ShutdownAll)?;
765 let _ = writer.write_all(format!("{}\n", req).as_bytes());
766 let _ = writer.flush();
767 }
768
769 std::thread::sleep(Duration::from_millis(500));
771
772 let _ = std::fs::remove_file(&socket_path);
774 let _ = std::fs::remove_file(&daemon_pid_path(project_root));
775
776 Ok(())
777}