1use std::{collections::VecDeque, sync::Arc};
2
3use collections::HashMap;
4use futures::{StreamExt, channel::mpsc};
5use gpui::{App, AppContext as _, Context, Entity, EventEmitter, Global, Subscription, WeakEntity};
6use lsp::{
7 IoKind, LanguageServer, LanguageServerId, LanguageServerName, LanguageServerSelector,
8 MessageType, TraceValue,
9};
10use rpc::proto;
11use settings::WorktreeId;
12
13use crate::{LanguageServerLogType, LspStore, Project, ProjectItem as _};
14
15const SEND_LINE: &str = "\n// Send:";
16const RECEIVE_LINE: &str = "\n// Receive:";
17const MAX_STORED_LOG_ENTRIES: usize = 2000;
18
19pub fn init(on_headless_host: bool, cx: &mut App) -> Entity<LogStore> {
20 let log_store = cx.new(|cx| LogStore::new(on_headless_host, cx));
21 cx.set_global(GlobalLogStore(log_store.clone()));
22 log_store
23}
24
25pub struct GlobalLogStore(pub Entity<LogStore>);
26
27impl Global for GlobalLogStore {}
28
29#[derive(Debug)]
30pub enum Event {
31 NewServerLogEntry {
32 id: LanguageServerId,
33 kind: LanguageServerLogType,
34 text: String,
35 },
36}
37
38impl EventEmitter<Event> for LogStore {}
39
40pub struct LogStore {
41 on_headless_host: bool,
42 projects: HashMap<WeakEntity<Project>, ProjectState>,
43 pub copilot_log_subscription: Option<lsp::Subscription>,
44 pub language_servers: HashMap<LanguageServerId, LanguageServerState>,
45 io_tx: mpsc::UnboundedSender<(LanguageServerId, IoKind, String)>,
46}
47
48struct ProjectState {
49 _subscriptions: [Subscription; 2],
50}
51
52pub trait Message: AsRef<str> {
53 type Level: Copy + std::fmt::Debug;
54 fn should_include(&self, _: Self::Level) -> bool {
55 true
56 }
57}
58
59#[derive(Debug)]
60pub struct LogMessage {
61 message: String,
62 typ: MessageType,
63}
64
65impl AsRef<str> for LogMessage {
66 fn as_ref(&self) -> &str {
67 &self.message
68 }
69}
70
71impl Message for LogMessage {
72 type Level = MessageType;
73
74 fn should_include(&self, level: Self::Level) -> bool {
75 match (self.typ, level) {
76 (MessageType::ERROR, _) => true,
77 (_, MessageType::ERROR) => false,
78 (MessageType::WARNING, _) => true,
79 (_, MessageType::WARNING) => false,
80 (MessageType::INFO, _) => true,
81 (_, MessageType::INFO) => false,
82 _ => true,
83 }
84 }
85}
86
87#[derive(Debug)]
88pub struct TraceMessage {
89 message: String,
90 is_verbose: bool,
91}
92
93impl AsRef<str> for TraceMessage {
94 fn as_ref(&self) -> &str {
95 &self.message
96 }
97}
98
99impl Message for TraceMessage {
100 type Level = TraceValue;
101
102 fn should_include(&self, level: Self::Level) -> bool {
103 match level {
104 TraceValue::Off => false,
105 TraceValue::Messages => !self.is_verbose,
106 TraceValue::Verbose => true,
107 }
108 }
109}
110
111#[derive(Debug)]
112pub struct RpcMessage {
113 message: String,
114}
115
116impl AsRef<str> for RpcMessage {
117 fn as_ref(&self) -> &str {
118 &self.message
119 }
120}
121
122impl Message for RpcMessage {
123 type Level = ();
124}
125
126pub struct LanguageServerState {
127 pub name: Option<LanguageServerName>,
128 pub worktree_id: Option<WorktreeId>,
129 pub kind: LanguageServerKind,
130 log_messages: VecDeque<LogMessage>,
131 trace_messages: VecDeque<TraceMessage>,
132 pub rpc_state: Option<LanguageServerRpcState>,
133 pub trace_level: TraceValue,
134 pub log_level: MessageType,
135 io_logs_subscription: Option<lsp::Subscription>,
136 pub toggled_log_kind: Option<LogKind>,
137}
138
139impl std::fmt::Debug for LanguageServerState {
140 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
141 f.debug_struct("LanguageServerState")
142 .field("name", &self.name)
143 .field("worktree_id", &self.worktree_id)
144 .field("kind", &self.kind)
145 .field("log_messages", &self.log_messages)
146 .field("trace_messages", &self.trace_messages)
147 .field("rpc_state", &self.rpc_state)
148 .field("trace_level", &self.trace_level)
149 .field("log_level", &self.log_level)
150 .field("toggled_log_kind", &self.toggled_log_kind)
151 .finish_non_exhaustive()
152 }
153}
154
155#[derive(PartialEq, Clone)]
156pub enum LanguageServerKind {
157 Local { project: WeakEntity<Project> },
158 Remote { project: WeakEntity<Project> },
159 LocalSsh { lsp_store: WeakEntity<LspStore> },
160 Global,
161}
162
163impl std::fmt::Debug for LanguageServerKind {
164 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
165 match self {
166 LanguageServerKind::Local { .. } => write!(f, "LanguageServerKind::Local"),
167 LanguageServerKind::Remote { .. } => write!(f, "LanguageServerKind::Remote"),
168 LanguageServerKind::LocalSsh { .. } => write!(f, "LanguageServerKind::LocalSsh"),
169 LanguageServerKind::Global => write!(f, "LanguageServerKind::Global"),
170 }
171 }
172}
173
174impl LanguageServerKind {
175 pub const fn project(&self) -> Option<&WeakEntity<Project>> {
176 match self {
177 Self::Local { project } => Some(project),
178 Self::Remote { project } => Some(project),
179 Self::LocalSsh { .. } => None,
180 Self::Global { .. } => None,
181 }
182 }
183}
184
185#[derive(Debug)]
186pub struct LanguageServerRpcState {
187 pub rpc_messages: VecDeque<RpcMessage>,
188 last_message_kind: Option<MessageKind>,
189}
190
191#[derive(Debug, Copy, Clone, PartialEq, Eq)]
192enum MessageKind {
193 Send,
194 Receive,
195}
196
197#[derive(Clone, Copy, Debug, Default, PartialEq)]
198pub enum LogKind {
199 Rpc,
200 Trace,
201 #[default]
202 Logs,
203 ServerInfo,
204}
205
206impl LogKind {
207 pub const fn from_server_log_type(log_type: &LanguageServerLogType) -> Self {
208 match log_type {
209 LanguageServerLogType::Log(_) => Self::Logs,
210 LanguageServerLogType::Trace { .. } => Self::Trace,
211 LanguageServerLogType::Rpc { .. } => Self::Rpc,
212 }
213 }
214}
215
216impl LogStore {
217 pub fn new(on_headless_host: bool, cx: &mut Context<Self>) -> Self {
218 let (io_tx, mut io_rx) = mpsc::unbounded();
219
220 let log_store = Self {
221 projects: HashMap::default(),
222 language_servers: HashMap::default(),
223 copilot_log_subscription: None,
224 on_headless_host,
225 io_tx,
226 };
227 cx.spawn(async move |log_store, cx| {
228 while let Some((server_id, io_kind, message)) = io_rx.next().await {
229 if let Some(log_store) = log_store.upgrade() {
230 log_store.update(cx, |log_store, cx| {
231 log_store.on_io(server_id, io_kind, &message, cx);
232 })?;
233 }
234 }
235 anyhow::Ok(())
236 })
237 .detach_and_log_err(cx);
238
239 log_store
240 }
241
242 pub fn add_project(&mut self, project: &Entity<Project>, cx: &mut Context<Self>) {
243 let weak_project = project.downgrade();
244 self.projects.insert(
245 project.downgrade(),
246 ProjectState {
247 _subscriptions: [
248 cx.observe_release(project, move |this, _, _| {
249 this.projects.remove(&weak_project);
250 this.language_servers
251 .retain(|_, state| state.kind.project() != Some(&weak_project));
252 }),
253 cx.subscribe(project, move |log_store, project, event, cx| {
254 let server_kind = if project.read(cx).is_local() {
255 LanguageServerKind::Local {
256 project: project.downgrade(),
257 }
258 } else {
259 LanguageServerKind::Remote {
260 project: project.downgrade(),
261 }
262 };
263 match event {
264 crate::Event::LanguageServerAdded(id, name, worktree_id) => {
265 log_store.add_language_server(
266 server_kind,
267 *id,
268 Some(name.clone()),
269 *worktree_id,
270 project
271 .read(cx)
272 .lsp_store()
273 .read(cx)
274 .language_server_for_id(*id),
275 cx,
276 );
277 }
278 crate::Event::LanguageServerBufferRegistered {
279 server_id,
280 buffer_id,
281 name,
282 ..
283 } => {
284 let worktree_id = project
285 .read(cx)
286 .buffer_for_id(*buffer_id, cx)
287 .and_then(|buffer| {
288 Some(buffer.read(cx).project_path(cx)?.worktree_id)
289 });
290 let name = name.clone().or_else(|| {
291 project
292 .read(cx)
293 .lsp_store()
294 .read(cx)
295 .language_server_statuses
296 .get(server_id)
297 .map(|status| status.name.clone())
298 });
299 log_store.add_language_server(
300 server_kind,
301 *server_id,
302 name,
303 worktree_id,
304 None,
305 cx,
306 );
307 }
308 crate::Event::LanguageServerRemoved(id) => {
309 log_store.remove_language_server(*id, cx);
310 }
311 crate::Event::LanguageServerLog(id, typ, message) => {
312 log_store.add_language_server(
313 server_kind,
314 *id,
315 None,
316 None,
317 None,
318 cx,
319 );
320 match typ {
321 crate::LanguageServerLogType::Log(typ) => {
322 log_store.add_language_server_log(*id, *typ, message, cx);
323 }
324 crate::LanguageServerLogType::Trace { verbose_info } => {
325 log_store.add_language_server_trace(
326 *id,
327 message,
328 verbose_info.clone(),
329 cx,
330 );
331 }
332 crate::LanguageServerLogType::Rpc { received } => {
333 let kind = if *received {
334 MessageKind::Receive
335 } else {
336 MessageKind::Send
337 };
338 log_store.add_language_server_rpc(*id, kind, message, cx);
339 }
340 }
341 }
342 crate::Event::ToggleLspLogs {
343 server_id,
344 enabled,
345 toggled_log_kind,
346 } => {
347 if let Some(server_state) =
348 log_store.get_language_server_state(*server_id)
349 {
350 if *enabled {
351 server_state.toggled_log_kind = Some(*toggled_log_kind);
352 } else {
353 server_state.toggled_log_kind = None;
354 }
355 }
356 if LogKind::Rpc == *toggled_log_kind {
357 if *enabled {
358 log_store.enable_rpc_trace_for_language_server(*server_id);
359 } else {
360 log_store.disable_rpc_trace_for_language_server(*server_id);
361 }
362 }
363 }
364 _ => {}
365 }
366 }),
367 ],
368 },
369 );
370 }
371
372 pub fn get_language_server_state(
373 &mut self,
374 id: LanguageServerId,
375 ) -> Option<&mut LanguageServerState> {
376 self.language_servers.get_mut(&id)
377 }
378
379 pub fn add_language_server(
380 &mut self,
381 kind: LanguageServerKind,
382 server_id: LanguageServerId,
383 name: Option<LanguageServerName>,
384 worktree_id: Option<WorktreeId>,
385 server: Option<Arc<LanguageServer>>,
386 cx: &mut Context<Self>,
387 ) -> Option<&mut LanguageServerState> {
388 let server_state = self.language_servers.entry(server_id).or_insert_with(|| {
389 cx.notify();
390 LanguageServerState {
391 name: None,
392 worktree_id: None,
393 kind,
394 rpc_state: None,
395 log_messages: VecDeque::with_capacity(MAX_STORED_LOG_ENTRIES),
396 trace_messages: VecDeque::with_capacity(MAX_STORED_LOG_ENTRIES),
397 trace_level: TraceValue::Off,
398 log_level: MessageType::LOG,
399 io_logs_subscription: None,
400 toggled_log_kind: None,
401 }
402 });
403
404 if let Some(name) = name {
405 server_state.name = Some(name);
406 }
407 if let Some(worktree_id) = worktree_id {
408 server_state.worktree_id = Some(worktree_id);
409 }
410
411 if let Some(server) = server.filter(|_| server_state.io_logs_subscription.is_none()) {
412 let io_tx = self.io_tx.clone();
413 let server_id = server.server_id();
414 server_state.io_logs_subscription = Some(server.on_io(move |io_kind, message| {
415 io_tx
416 .unbounded_send((server_id, io_kind, message.to_string()))
417 .ok();
418 }));
419 }
420
421 Some(server_state)
422 }
423
424 pub fn add_language_server_log(
425 &mut self,
426 id: LanguageServerId,
427 typ: MessageType,
428 message: &str,
429 cx: &mut Context<Self>,
430 ) -> Option<()> {
431 let store_logs = !self.on_headless_host;
432 let language_server_state = self.get_language_server_state(id)?;
433
434 let log_lines = &mut language_server_state.log_messages;
435 let message = message.trim_end().to_string();
436 if !store_logs {
437 // Send all messages regardless of the visibility in case of not storing, to notify the receiver anyway
438 self.emit_event(
439 Event::NewServerLogEntry {
440 id,
441 kind: LanguageServerLogType::Log(typ),
442 text: message,
443 },
444 cx,
445 );
446 } else if let Some(new_message) = Self::push_new_message(
447 log_lines,
448 LogMessage { message, typ },
449 language_server_state.log_level,
450 ) {
451 self.emit_event(
452 Event::NewServerLogEntry {
453 id,
454 kind: LanguageServerLogType::Log(typ),
455 text: new_message,
456 },
457 cx,
458 );
459 }
460 Some(())
461 }
462
463 fn add_language_server_trace(
464 &mut self,
465 id: LanguageServerId,
466 message: &str,
467 verbose_info: Option<String>,
468 cx: &mut Context<Self>,
469 ) -> Option<()> {
470 let store_logs = !self.on_headless_host;
471 let language_server_state = self.get_language_server_state(id)?;
472
473 let log_lines = &mut language_server_state.trace_messages;
474 if !store_logs {
475 // Send all messages regardless of the visibility in case of not storing, to notify the receiver anyway
476 self.emit_event(
477 Event::NewServerLogEntry {
478 id,
479 kind: LanguageServerLogType::Trace { verbose_info },
480 text: message.trim().to_string(),
481 },
482 cx,
483 );
484 } else if let Some(new_message) = Self::push_new_message(
485 log_lines,
486 TraceMessage {
487 message: message.trim().to_string(),
488 is_verbose: false,
489 },
490 TraceValue::Messages,
491 ) {
492 if let Some(verbose_message) = verbose_info.as_ref() {
493 Self::push_new_message(
494 log_lines,
495 TraceMessage {
496 message: verbose_message.clone(),
497 is_verbose: true,
498 },
499 TraceValue::Verbose,
500 );
501 }
502 self.emit_event(
503 Event::NewServerLogEntry {
504 id,
505 kind: LanguageServerLogType::Trace { verbose_info },
506 text: new_message,
507 },
508 cx,
509 );
510 }
511 Some(())
512 }
513
514 fn push_new_message<T: Message>(
515 log_lines: &mut VecDeque<T>,
516 message: T,
517 current_severity: <T as Message>::Level,
518 ) -> Option<String> {
519 while log_lines.len() + 1 >= MAX_STORED_LOG_ENTRIES {
520 log_lines.pop_front();
521 }
522 let visible = message.should_include(current_severity);
523
524 let visible_message = visible.then(|| message.as_ref().to_string());
525 log_lines.push_back(message);
526 visible_message
527 }
528
529 fn add_language_server_rpc(
530 &mut self,
531 language_server_id: LanguageServerId,
532 kind: MessageKind,
533 message: &str,
534 cx: &mut Context<'_, Self>,
535 ) {
536 let store_logs = !self.on_headless_host;
537 let Some(state) = self
538 .get_language_server_state(language_server_id)
539 .and_then(|state| state.rpc_state.as_mut())
540 else {
541 return;
542 };
543
544 let received = kind == MessageKind::Receive;
545 let rpc_log_lines = &mut state.rpc_messages;
546 if state.last_message_kind != Some(kind) {
547 while rpc_log_lines.len() + 1 >= MAX_STORED_LOG_ENTRIES {
548 rpc_log_lines.pop_front();
549 }
550 let line_before_message = match kind {
551 MessageKind::Send => SEND_LINE,
552 MessageKind::Receive => RECEIVE_LINE,
553 };
554 if store_logs {
555 rpc_log_lines.push_back(RpcMessage {
556 message: line_before_message.to_string(),
557 });
558 }
559 // Do not send a synthetic message over the wire, it will be derived from the actual RPC message
560 cx.emit(Event::NewServerLogEntry {
561 id: language_server_id,
562 kind: LanguageServerLogType::Rpc { received },
563 text: line_before_message.to_string(),
564 });
565 }
566
567 while rpc_log_lines.len() + 1 >= MAX_STORED_LOG_ENTRIES {
568 rpc_log_lines.pop_front();
569 }
570
571 if store_logs {
572 rpc_log_lines.push_back(RpcMessage {
573 message: message.trim().to_owned(),
574 });
575 }
576
577 self.emit_event(
578 Event::NewServerLogEntry {
579 id: language_server_id,
580 kind: LanguageServerLogType::Rpc { received },
581 text: message.to_owned(),
582 },
583 cx,
584 );
585 }
586
587 pub fn remove_language_server(&mut self, id: LanguageServerId, cx: &mut Context<Self>) {
588 self.language_servers.remove(&id);
589 cx.notify();
590 }
591
592 pub fn server_logs(&self, server_id: LanguageServerId) -> Option<&VecDeque<LogMessage>> {
593 Some(&self.language_servers.get(&server_id)?.log_messages)
594 }
595
596 pub fn server_trace(&self, server_id: LanguageServerId) -> Option<&VecDeque<TraceMessage>> {
597 Some(&self.language_servers.get(&server_id)?.trace_messages)
598 }
599
600 pub fn server_ids_for_project<'a>(
601 &'a self,
602 lookup_project: &'a WeakEntity<Project>,
603 ) -> impl Iterator<Item = LanguageServerId> + 'a {
604 self.language_servers
605 .iter()
606 .filter_map(move |(id, state)| match &state.kind {
607 LanguageServerKind::Local { project } | LanguageServerKind::Remote { project } => {
608 if project == lookup_project {
609 Some(*id)
610 } else {
611 None
612 }
613 }
614 LanguageServerKind::Global | LanguageServerKind::LocalSsh { .. } => Some(*id),
615 })
616 }
617
618 pub fn enable_rpc_trace_for_language_server(
619 &mut self,
620 server_id: LanguageServerId,
621 ) -> Option<&mut LanguageServerRpcState> {
622 let rpc_state = self
623 .language_servers
624 .get_mut(&server_id)?
625 .rpc_state
626 .get_or_insert_with(|| LanguageServerRpcState {
627 rpc_messages: VecDeque::with_capacity(MAX_STORED_LOG_ENTRIES),
628 last_message_kind: None,
629 });
630 Some(rpc_state)
631 }
632
633 pub fn disable_rpc_trace_for_language_server(
634 &mut self,
635 server_id: LanguageServerId,
636 ) -> Option<()> {
637 self.language_servers.get_mut(&server_id)?.rpc_state.take();
638 Some(())
639 }
640
641 pub fn has_server_logs(&self, server: &LanguageServerSelector) -> bool {
642 match server {
643 LanguageServerSelector::Id(id) => self.language_servers.contains_key(id),
644 LanguageServerSelector::Name(name) => self
645 .language_servers
646 .iter()
647 .any(|(_, state)| state.name.as_ref() == Some(name)),
648 }
649 }
650
651 fn on_io(
652 &mut self,
653 language_server_id: LanguageServerId,
654 io_kind: IoKind,
655 message: &str,
656 cx: &mut Context<Self>,
657 ) -> Option<()> {
658 let is_received = match io_kind {
659 IoKind::StdOut => true,
660 IoKind::StdIn => false,
661 IoKind::StdErr => {
662 self.add_language_server_log(language_server_id, MessageType::LOG, message, cx);
663 return Some(());
664 }
665 };
666
667 let kind = if is_received {
668 MessageKind::Receive
669 } else {
670 MessageKind::Send
671 };
672
673 self.add_language_server_rpc(language_server_id, kind, message, cx);
674 cx.notify();
675 Some(())
676 }
677
678 fn emit_event(&mut self, e: Event, cx: &mut Context<Self>) {
679 let on_headless_host = self.on_headless_host;
680 match &e {
681 Event::NewServerLogEntry { id, kind, text } => {
682 if let Some(state) = self.get_language_server_state(*id) {
683 let downstream_client = match &state.kind {
684 LanguageServerKind::Remote { project }
685 | LanguageServerKind::Local { project } => project
686 .upgrade()
687 .map(|project| project.read(cx).lsp_store()),
688 LanguageServerKind::LocalSsh { lsp_store } => lsp_store.upgrade(),
689 LanguageServerKind::Global => None,
690 }
691 .and_then(|lsp_store| lsp_store.read(cx).downstream_client());
692 if let Some((client, project_id)) = downstream_client {
693 if on_headless_host
694 || Some(LogKind::from_server_log_type(kind)) == state.toggled_log_kind
695 {
696 client
697 .send(proto::LanguageServerLog {
698 project_id,
699 language_server_id: id.to_proto(),
700 message: text.clone(),
701 log_type: Some(kind.to_proto()),
702 })
703 .ok();
704 }
705 }
706 }
707 }
708 }
709
710 cx.emit(e);
711 }
712}