1use collections::{HashMap, VecDeque};
2use copilot::Copilot;
3use editor::{Editor, EditorEvent, actions::MoveToEnd, scroll::Autoscroll};
4use futures::{StreamExt, channel::mpsc};
5use gpui::{
6 AnyView, App, Context, Corner, Entity, EventEmitter, FocusHandle, Focusable, IntoElement,
7 ParentElement, Render, Styled, Subscription, WeakEntity, Window, actions, div,
8};
9use language::{LanguageServerId, language_settings::SoftWrap};
10use lsp::{
11 IoKind, LanguageServer, LanguageServerName, MessageType, SetTraceParams, TraceValue,
12 notification::SetTrace,
13};
14use project::{Project, WorktreeId, search::SearchQuery};
15use std::{any::TypeId, borrow::Cow, sync::Arc};
16use ui::{Button, Checkbox, ContextMenu, Label, PopoverMenu, ToggleState, prelude::*};
17use workspace::{
18 SplitDirection, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace, WorkspaceId,
19 item::{Item, ItemHandle},
20 searchable::{Direction, SearchEvent, SearchableItem, SearchableItemHandle},
21};
22
23const SEND_LINE: &str = "// Send:\n";
24const RECEIVE_LINE: &str = "// Receive:\n";
25const MAX_STORED_LOG_ENTRIES: usize = 2000;
26
27pub struct LogStore {
28 projects: HashMap<WeakEntity<Project>, ProjectState>,
29 language_servers: HashMap<LanguageServerId, LanguageServerState>,
30 copilot_log_subscription: Option<lsp::Subscription>,
31 _copilot_subscription: Option<gpui::Subscription>,
32 io_tx: mpsc::UnboundedSender<(LanguageServerId, IoKind, String)>,
33}
34
35struct ProjectState {
36 _subscriptions: [gpui::Subscription; 2],
37}
38
39trait Message: AsRef<str> {
40 type Level: Copy + std::fmt::Debug;
41 fn should_include(&self, _: Self::Level) -> bool {
42 true
43 }
44}
45
46struct LogMessage {
47 message: String,
48 typ: MessageType,
49}
50
51impl AsRef<str> for LogMessage {
52 fn as_ref(&self) -> &str {
53 &self.message
54 }
55}
56
57impl Message for LogMessage {
58 type Level = MessageType;
59
60 fn should_include(&self, level: Self::Level) -> bool {
61 match (self.typ, level) {
62 (MessageType::ERROR, _) => true,
63 (_, MessageType::ERROR) => false,
64 (MessageType::WARNING, _) => true,
65 (_, MessageType::WARNING) => false,
66 (MessageType::INFO, _) => true,
67 (_, MessageType::INFO) => false,
68 _ => true,
69 }
70 }
71}
72
73struct TraceMessage {
74 message: String,
75}
76
77impl AsRef<str> for TraceMessage {
78 fn as_ref(&self) -> &str {
79 &self.message
80 }
81}
82
83impl Message for TraceMessage {
84 type Level = ();
85}
86
87struct RpcMessage {
88 message: String,
89}
90
91impl AsRef<str> for RpcMessage {
92 fn as_ref(&self) -> &str {
93 &self.message
94 }
95}
96
97impl Message for RpcMessage {
98 type Level = ();
99}
100
101struct LanguageServerState {
102 name: Option<LanguageServerName>,
103 worktree_id: Option<WorktreeId>,
104 kind: LanguageServerKind,
105 log_messages: VecDeque<LogMessage>,
106 trace_messages: VecDeque<TraceMessage>,
107 rpc_state: Option<LanguageServerRpcState>,
108 trace_level: TraceValue,
109 log_level: MessageType,
110 io_logs_subscription: Option<lsp::Subscription>,
111}
112
113#[derive(PartialEq, Clone)]
114pub enum LanguageServerKind {
115 Local { project: WeakEntity<Project> },
116 Remote { project: WeakEntity<Project> },
117 Global,
118}
119
120impl LanguageServerKind {
121 fn is_remote(&self) -> bool {
122 matches!(self, LanguageServerKind::Remote { .. })
123 }
124}
125
126impl std::fmt::Debug for LanguageServerKind {
127 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
128 match self {
129 LanguageServerKind::Local { .. } => write!(f, "LanguageServerKind::Local"),
130 LanguageServerKind::Remote { .. } => write!(f, "LanguageServerKind::Remote"),
131 LanguageServerKind::Global => write!(f, "LanguageServerKind::Global"),
132 }
133 }
134}
135
136impl LanguageServerKind {
137 fn project(&self) -> Option<&WeakEntity<Project>> {
138 match self {
139 Self::Local { project } => Some(project),
140 Self::Remote { project } => Some(project),
141 Self::Global { .. } => None,
142 }
143 }
144}
145
146struct LanguageServerRpcState {
147 rpc_messages: VecDeque<RpcMessage>,
148 last_message_kind: Option<MessageKind>,
149}
150
151pub struct LspLogView {
152 pub(crate) editor: Entity<Editor>,
153 editor_subscriptions: Vec<Subscription>,
154 log_store: Entity<LogStore>,
155 current_server_id: Option<LanguageServerId>,
156 active_entry_kind: LogKind,
157 project: Entity<Project>,
158 focus_handle: FocusHandle,
159 _log_store_subscriptions: Vec<Subscription>,
160}
161
162pub struct LspLogToolbarItemView {
163 log_view: Option<Entity<LspLogView>>,
164 _log_view_subscription: Option<Subscription>,
165}
166
167#[derive(Copy, Clone, PartialEq, Eq)]
168enum MessageKind {
169 Send,
170 Receive,
171}
172
173#[derive(Clone, Copy, Debug, Default, PartialEq)]
174pub enum LogKind {
175 Rpc,
176 Trace,
177 #[default]
178 Logs,
179 ServerInfo,
180}
181
182impl LogKind {
183 fn label(&self) -> &'static str {
184 match self {
185 LogKind::Rpc => RPC_MESSAGES,
186 LogKind::Trace => SERVER_TRACE,
187 LogKind::Logs => SERVER_LOGS,
188 LogKind::ServerInfo => SERVER_INFO,
189 }
190 }
191}
192
193#[derive(Clone, Debug, PartialEq)]
194pub(crate) struct LogMenuItem {
195 pub server_id: LanguageServerId,
196 pub server_name: LanguageServerName,
197 pub worktree_root_name: String,
198 pub rpc_trace_enabled: bool,
199 pub selected_entry: LogKind,
200 pub trace_level: lsp::TraceValue,
201 pub server_kind: LanguageServerKind,
202}
203
204actions!(dev, [OpenLanguageServerLogs]);
205
206pub fn init(cx: &mut App) {
207 let log_store = cx.new(LogStore::new);
208
209 cx.observe_new(move |workspace: &mut Workspace, _, cx| {
210 let project = workspace.project();
211 if project.read(cx).is_local() || project.read(cx).is_via_ssh() {
212 log_store.update(cx, |store, cx| {
213 store.add_project(project, cx);
214 });
215 }
216
217 let log_store = log_store.clone();
218 workspace.register_action(move |workspace, _: &OpenLanguageServerLogs, window, cx| {
219 let project = workspace.project().read(cx);
220 if project.is_local() || project.is_via_ssh() {
221 workspace.split_item(
222 SplitDirection::Right,
223 Box::new(cx.new(|cx| {
224 LspLogView::new(workspace.project().clone(), log_store.clone(), window, cx)
225 })),
226 window,
227 cx,
228 );
229 }
230 });
231 })
232 .detach();
233}
234
235impl LogStore {
236 pub fn new(cx: &mut Context<Self>) -> Self {
237 let (io_tx, mut io_rx) = mpsc::unbounded();
238
239 let copilot_subscription = Copilot::global(cx).map(|copilot| {
240 let copilot = &copilot;
241 cx.subscribe(copilot, |this, copilot, inline_completion_event, cx| {
242 if let copilot::Event::CopilotLanguageServerStarted = inline_completion_event {
243 if let Some(server) = copilot.read(cx).language_server() {
244 let server_id = server.server_id();
245 let weak_this = cx.weak_entity();
246 this.copilot_log_subscription =
247 Some(server.on_notification::<copilot::request::LogMessage, _>(
248 move |params, cx| {
249 weak_this
250 .update(cx, |this, cx| {
251 this.add_language_server_log(
252 server_id,
253 MessageType::LOG,
254 ¶ms.message,
255 cx,
256 );
257 })
258 .ok();
259 },
260 ));
261 let name = LanguageServerName::new_static("copilot");
262 this.add_language_server(
263 LanguageServerKind::Global,
264 server.server_id(),
265 Some(name),
266 None,
267 Some(server.clone()),
268 cx,
269 );
270 }
271 }
272 })
273 });
274
275 let this = Self {
276 copilot_log_subscription: None,
277 _copilot_subscription: copilot_subscription,
278 projects: HashMap::default(),
279 language_servers: HashMap::default(),
280 io_tx,
281 };
282
283 cx.spawn(async move |this, cx| {
284 while let Some((server_id, io_kind, message)) = io_rx.next().await {
285 if let Some(this) = this.upgrade() {
286 this.update(cx, |this, cx| {
287 this.on_io(server_id, io_kind, &message, cx);
288 })?;
289 }
290 }
291 anyhow::Ok(())
292 })
293 .detach_and_log_err(cx);
294 this
295 }
296
297 pub fn add_project(&mut self, project: &Entity<Project>, cx: &mut Context<Self>) {
298 let weak_project = project.downgrade();
299 self.projects.insert(
300 project.downgrade(),
301 ProjectState {
302 _subscriptions: [
303 cx.observe_release(project, move |this, _, _| {
304 this.projects.remove(&weak_project);
305 this.language_servers
306 .retain(|_, state| state.kind.project() != Some(&weak_project));
307 }),
308 cx.subscribe(project, |this, project, event, cx| {
309 let server_kind = if project.read(cx).is_via_ssh() {
310 LanguageServerKind::Remote {
311 project: project.downgrade(),
312 }
313 } else {
314 LanguageServerKind::Local {
315 project: project.downgrade(),
316 }
317 };
318
319 match event {
320 project::Event::LanguageServerAdded(id, name, worktree_id) => {
321 this.add_language_server(
322 server_kind,
323 *id,
324 Some(name.clone()),
325 *worktree_id,
326 project
327 .read(cx)
328 .lsp_store()
329 .read(cx)
330 .language_server_for_id(*id),
331 cx,
332 );
333 }
334 project::Event::LanguageServerRemoved(id) => {
335 this.remove_language_server(*id, cx);
336 }
337 project::Event::LanguageServerLog(id, typ, message) => {
338 this.add_language_server(server_kind, *id, None, None, None, cx);
339 match typ {
340 project::LanguageServerLogType::Log(typ) => {
341 this.add_language_server_log(*id, *typ, message, cx);
342 }
343 project::LanguageServerLogType::Trace(_) => {
344 this.add_language_server_trace(*id, message, cx);
345 }
346 }
347 }
348 _ => {}
349 }
350 }),
351 ],
352 },
353 );
354 }
355
356 fn get_language_server_state(
357 &mut self,
358 id: LanguageServerId,
359 ) -> Option<&mut LanguageServerState> {
360 self.language_servers.get_mut(&id)
361 }
362
363 fn add_language_server(
364 &mut self,
365 kind: LanguageServerKind,
366 server_id: LanguageServerId,
367 name: Option<LanguageServerName>,
368 worktree_id: Option<WorktreeId>,
369 server: Option<Arc<LanguageServer>>,
370 cx: &mut Context<Self>,
371 ) -> Option<&mut LanguageServerState> {
372 let server_state = self.language_servers.entry(server_id).or_insert_with(|| {
373 cx.notify();
374 LanguageServerState {
375 name: None,
376 worktree_id: None,
377 kind,
378 rpc_state: None,
379 log_messages: VecDeque::with_capacity(MAX_STORED_LOG_ENTRIES),
380 trace_messages: VecDeque::with_capacity(MAX_STORED_LOG_ENTRIES),
381 trace_level: TraceValue::Off,
382 log_level: MessageType::LOG,
383 io_logs_subscription: None,
384 }
385 });
386
387 if let Some(name) = name {
388 server_state.name = Some(name);
389 }
390 if let Some(worktree_id) = worktree_id {
391 server_state.worktree_id = Some(worktree_id);
392 }
393
394 if let Some(server) = server
395 .clone()
396 .filter(|_| server_state.io_logs_subscription.is_none())
397 {
398 let io_tx = self.io_tx.clone();
399 let server_id = server.server_id();
400 server_state.io_logs_subscription = Some(server.on_io(move |io_kind, message| {
401 io_tx
402 .unbounded_send((server_id, io_kind, message.to_string()))
403 .ok();
404 }));
405 }
406
407 Some(server_state)
408 }
409
410 fn add_language_server_log(
411 &mut self,
412 id: LanguageServerId,
413 typ: MessageType,
414 message: &str,
415 cx: &mut Context<Self>,
416 ) -> Option<()> {
417 let language_server_state = self.get_language_server_state(id)?;
418
419 let log_lines = &mut language_server_state.log_messages;
420 Self::add_language_server_message(
421 log_lines,
422 id,
423 LogMessage {
424 message: message.trim_end().to_string(),
425 typ,
426 },
427 language_server_state.log_level,
428 LogKind::Logs,
429 cx,
430 );
431 Some(())
432 }
433
434 fn add_language_server_trace(
435 &mut self,
436 id: LanguageServerId,
437 message: &str,
438 cx: &mut Context<Self>,
439 ) -> Option<()> {
440 let language_server_state = self.get_language_server_state(id)?;
441
442 let log_lines = &mut language_server_state.trace_messages;
443 Self::add_language_server_message(
444 log_lines,
445 id,
446 TraceMessage {
447 message: message.trim_end().to_string(),
448 },
449 (),
450 LogKind::Trace,
451 cx,
452 );
453 Some(())
454 }
455
456 fn add_language_server_message<T: Message>(
457 log_lines: &mut VecDeque<T>,
458 id: LanguageServerId,
459 message: T,
460 current_severity: <T as Message>::Level,
461 kind: LogKind,
462 cx: &mut Context<Self>,
463 ) {
464 while log_lines.len() >= MAX_STORED_LOG_ENTRIES {
465 log_lines.pop_front();
466 }
467 let entry = format!("{}\n", message.as_ref().trim());
468 let visible = message.should_include(current_severity);
469 log_lines.push_back(message);
470
471 if visible {
472 cx.emit(Event::NewServerLogEntry { id, entry, kind });
473 cx.notify();
474 }
475 }
476
477 fn remove_language_server(&mut self, id: LanguageServerId, cx: &mut Context<Self>) {
478 self.language_servers.remove(&id);
479 cx.notify();
480 }
481
482 fn server_logs(&self, server_id: LanguageServerId) -> Option<&VecDeque<LogMessage>> {
483 Some(&self.language_servers.get(&server_id)?.log_messages)
484 }
485
486 fn server_trace(&self, server_id: LanguageServerId) -> Option<&VecDeque<TraceMessage>> {
487 Some(&self.language_servers.get(&server_id)?.trace_messages)
488 }
489
490 fn server_ids_for_project<'a>(
491 &'a self,
492 lookup_project: &'a WeakEntity<Project>,
493 ) -> impl Iterator<Item = LanguageServerId> + 'a {
494 self.language_servers
495 .iter()
496 .filter_map(move |(id, state)| match &state.kind {
497 LanguageServerKind::Local { project } | LanguageServerKind::Remote { project } => {
498 if project == lookup_project {
499 Some(*id)
500 } else {
501 None
502 }
503 }
504 LanguageServerKind::Global => Some(*id),
505 })
506 }
507
508 fn enable_rpc_trace_for_language_server(
509 &mut self,
510 server_id: LanguageServerId,
511 ) -> Option<&mut LanguageServerRpcState> {
512 let rpc_state = self
513 .language_servers
514 .get_mut(&server_id)?
515 .rpc_state
516 .get_or_insert_with(|| LanguageServerRpcState {
517 rpc_messages: VecDeque::with_capacity(MAX_STORED_LOG_ENTRIES),
518 last_message_kind: None,
519 });
520 Some(rpc_state)
521 }
522
523 pub fn disable_rpc_trace_for_language_server(
524 &mut self,
525 server_id: LanguageServerId,
526 ) -> Option<()> {
527 self.language_servers.get_mut(&server_id)?.rpc_state.take();
528 Some(())
529 }
530
531 fn on_io(
532 &mut self,
533 language_server_id: LanguageServerId,
534 io_kind: IoKind,
535 message: &str,
536 cx: &mut Context<Self>,
537 ) -> Option<()> {
538 let is_received = match io_kind {
539 IoKind::StdOut => true,
540 IoKind::StdIn => false,
541 IoKind::StdErr => {
542 self.add_language_server_log(language_server_id, MessageType::LOG, &message, cx);
543 return Some(());
544 }
545 };
546
547 let state = self
548 .get_language_server_state(language_server_id)?
549 .rpc_state
550 .as_mut()?;
551 let kind = if is_received {
552 MessageKind::Receive
553 } else {
554 MessageKind::Send
555 };
556
557 let rpc_log_lines = &mut state.rpc_messages;
558 if state.last_message_kind != Some(kind) {
559 let line_before_message = match kind {
560 MessageKind::Send => SEND_LINE,
561 MessageKind::Receive => RECEIVE_LINE,
562 };
563 rpc_log_lines.push_back(RpcMessage {
564 message: line_before_message.to_string(),
565 });
566 cx.emit(Event::NewServerLogEntry {
567 id: language_server_id,
568 entry: line_before_message.to_string(),
569 kind: LogKind::Rpc,
570 });
571 }
572
573 while rpc_log_lines.len() >= MAX_STORED_LOG_ENTRIES {
574 rpc_log_lines.pop_front();
575 }
576 let message = message.trim();
577 rpc_log_lines.push_back(RpcMessage {
578 message: message.to_string(),
579 });
580 cx.emit(Event::NewServerLogEntry {
581 id: language_server_id,
582 entry: format!("{}\n\n", message),
583 kind: LogKind::Rpc,
584 });
585 cx.notify();
586 Some(())
587 }
588}
589
590impl LspLogView {
591 pub fn new(
592 project: Entity<Project>,
593 log_store: Entity<LogStore>,
594 window: &mut Window,
595 cx: &mut Context<Self>,
596 ) -> Self {
597 let server_id = log_store
598 .read(cx)
599 .language_servers
600 .iter()
601 .find(|(_, server)| server.kind.project() == Some(&project.downgrade()))
602 .map(|(id, _)| *id);
603
604 let weak_project = project.downgrade();
605 let model_changes_subscription =
606 cx.observe_in(&log_store, window, move |this, store, window, cx| {
607 let first_server_id_for_project =
608 store.read(cx).server_ids_for_project(&weak_project).next();
609 if let Some(current_lsp) = this.current_server_id {
610 if !store.read(cx).language_servers.contains_key(¤t_lsp) {
611 if let Some(server_id) = first_server_id_for_project {
612 match this.active_entry_kind {
613 LogKind::Rpc => {
614 this.show_rpc_trace_for_server(server_id, window, cx)
615 }
616 LogKind::Trace => this.show_trace_for_server(server_id, window, cx),
617 LogKind::Logs => this.show_logs_for_server(server_id, window, cx),
618 LogKind::ServerInfo => this.show_server_info(server_id, window, cx),
619 }
620 }
621 }
622 } else if let Some(server_id) = first_server_id_for_project {
623 match this.active_entry_kind {
624 LogKind::Rpc => this.show_rpc_trace_for_server(server_id, window, cx),
625 LogKind::Trace => this.show_trace_for_server(server_id, window, cx),
626 LogKind::Logs => this.show_logs_for_server(server_id, window, cx),
627 LogKind::ServerInfo => this.show_server_info(server_id, window, cx),
628 }
629 }
630
631 cx.notify();
632 });
633 let events_subscriptions = cx.subscribe_in(
634 &log_store,
635 window,
636 move |log_view, _, e, window, cx| match e {
637 Event::NewServerLogEntry { id, entry, kind } => {
638 if log_view.current_server_id == Some(*id)
639 && *kind == log_view.active_entry_kind
640 {
641 log_view.editor.update(cx, |editor, cx| {
642 editor.set_read_only(false);
643 let last_point = editor.buffer().read(cx).len(cx);
644 let newest_cursor_is_at_end =
645 editor.selections.newest::<usize>(cx).start >= last_point;
646 editor.edit(vec![(last_point..last_point, entry.as_str())], cx);
647 let entry_length = entry.len();
648 if entry_length > 1024 {
649 editor.fold_ranges(
650 vec![last_point + 1024..last_point + entry_length],
651 false,
652 window,
653 cx,
654 );
655 }
656
657 if newest_cursor_is_at_end {
658 editor.request_autoscroll(Autoscroll::bottom(), cx);
659 }
660 editor.set_read_only(true);
661 });
662 }
663 }
664 },
665 );
666 let (editor, editor_subscriptions) = Self::editor_for_logs(String::new(), window, cx);
667
668 let focus_handle = cx.focus_handle();
669 let focus_subscription = cx.on_focus(&focus_handle, window, |log_view, window, cx| {
670 window.focus(&log_view.editor.focus_handle(cx));
671 });
672
673 let mut this = Self {
674 focus_handle,
675 editor,
676 editor_subscriptions,
677 project,
678 log_store,
679 current_server_id: None,
680 active_entry_kind: LogKind::Logs,
681 _log_store_subscriptions: vec![
682 model_changes_subscription,
683 events_subscriptions,
684 focus_subscription,
685 ],
686 };
687 if let Some(server_id) = server_id {
688 this.show_logs_for_server(server_id, window, cx);
689 }
690 this
691 }
692
693 fn editor_for_logs(
694 log_contents: String,
695 window: &mut Window,
696 cx: &mut Context<Self>,
697 ) -> (Entity<Editor>, Vec<Subscription>) {
698 let editor = initialize_new_editor(log_contents, true, window, cx);
699 let editor_subscription = cx.subscribe(
700 &editor,
701 |_, _, event: &EditorEvent, cx: &mut Context<LspLogView>| cx.emit(event.clone()),
702 );
703 let search_subscription = cx.subscribe(
704 &editor,
705 |_, _, event: &SearchEvent, cx: &mut Context<LspLogView>| cx.emit(event.clone()),
706 );
707 (editor, vec![editor_subscription, search_subscription])
708 }
709
710 fn editor_for_server_info(
711 server: &LanguageServer,
712 window: &mut Window,
713 cx: &mut Context<Self>,
714 ) -> (Entity<Editor>, Vec<Subscription>) {
715 let server_info = format!(
716 "* Server: {NAME} (id {ID})
717
718* Binary: {BINARY:#?}
719
720* Registered workspace folders:
721{WORKSPACE_FOLDERS}
722
723* Capabilities: {CAPABILITIES}
724
725* Configuration: {CONFIGURATION}",
726 NAME = server.name(),
727 ID = server.server_id(),
728 BINARY = server.binary(),
729 WORKSPACE_FOLDERS = server
730 .workspace_folders()
731 .iter()
732 .filter_map(|path| path
733 .to_file_path()
734 .ok()
735 .map(|path| path.to_string_lossy().into_owned()))
736 .collect::<Vec<_>>()
737 .join(", "),
738 CAPABILITIES = serde_json::to_string_pretty(&server.capabilities())
739 .unwrap_or_else(|e| format!("Failed to serialize capabilities: {e}")),
740 CONFIGURATION = serde_json::to_string_pretty(server.configuration())
741 .unwrap_or_else(|e| format!("Failed to serialize configuration: {e}")),
742 );
743 let editor = initialize_new_editor(server_info, false, window, cx);
744 let editor_subscription = cx.subscribe(
745 &editor,
746 |_, _, event: &EditorEvent, cx: &mut Context<LspLogView>| cx.emit(event.clone()),
747 );
748 let search_subscription = cx.subscribe(
749 &editor,
750 |_, _, event: &SearchEvent, cx: &mut Context<LspLogView>| cx.emit(event.clone()),
751 );
752 (editor, vec![editor_subscription, search_subscription])
753 }
754
755 pub(crate) fn menu_items<'a>(&'a self, cx: &'a App) -> Option<Vec<LogMenuItem>> {
756 let log_store = self.log_store.read(cx);
757
758 let unknown_server = LanguageServerName::new_static("unknown server");
759
760 let mut rows = log_store
761 .language_servers
762 .iter()
763 .map(|(server_id, state)| match &state.kind {
764 LanguageServerKind::Local { .. } | LanguageServerKind::Remote { .. } => {
765 let worktree_root_name = state
766 .worktree_id
767 .and_then(|id| self.project.read(cx).worktree_for_id(id, cx))
768 .map(|worktree| worktree.read(cx).root_name().to_string())
769 .unwrap_or_else(|| "Unknown worktree".to_string());
770
771 LogMenuItem {
772 server_id: *server_id,
773 server_name: state.name.clone().unwrap_or(unknown_server.clone()),
774 server_kind: state.kind.clone(),
775 worktree_root_name,
776 rpc_trace_enabled: state.rpc_state.is_some(),
777 selected_entry: self.active_entry_kind,
778 trace_level: lsp::TraceValue::Off,
779 }
780 }
781
782 LanguageServerKind::Global => LogMenuItem {
783 server_id: *server_id,
784 server_name: state.name.clone().unwrap_or(unknown_server.clone()),
785 server_kind: state.kind.clone(),
786 worktree_root_name: "supplementary".to_string(),
787 rpc_trace_enabled: state.rpc_state.is_some(),
788 selected_entry: self.active_entry_kind,
789 trace_level: lsp::TraceValue::Off,
790 },
791 })
792 .chain(
793 self.project
794 .read(cx)
795 .supplementary_language_servers(cx)
796 .filter_map(|(server_id, name)| {
797 let state = log_store.language_servers.get(&server_id)?;
798 Some(LogMenuItem {
799 server_id,
800 server_name: name.clone(),
801 server_kind: state.kind.clone(),
802 worktree_root_name: "supplementary".to_string(),
803 rpc_trace_enabled: state.rpc_state.is_some(),
804 selected_entry: self.active_entry_kind,
805 trace_level: lsp::TraceValue::Off,
806 })
807 }),
808 )
809 .collect::<Vec<_>>();
810 rows.sort_by_key(|row| row.server_id);
811 rows.dedup_by_key(|row| row.server_id);
812 Some(rows)
813 }
814
815 fn show_logs_for_server(
816 &mut self,
817 server_id: LanguageServerId,
818 window: &mut Window,
819 cx: &mut Context<Self>,
820 ) {
821 let typ = self
822 .log_store
823 .read(cx)
824 .language_servers
825 .get(&server_id)
826 .map(|v| v.log_level)
827 .unwrap_or(MessageType::LOG);
828 let log_contents = self
829 .log_store
830 .read(cx)
831 .server_logs(server_id)
832 .map(|v| log_contents(v, typ));
833 if let Some(log_contents) = log_contents {
834 self.current_server_id = Some(server_id);
835 self.active_entry_kind = LogKind::Logs;
836 let (editor, editor_subscriptions) = Self::editor_for_logs(log_contents, window, cx);
837 self.editor = editor;
838 self.editor_subscriptions = editor_subscriptions;
839 cx.notify();
840 }
841 window.focus(&self.focus_handle);
842 }
843
844 fn update_log_level(
845 &self,
846 server_id: LanguageServerId,
847 level: MessageType,
848 window: &mut Window,
849 cx: &mut Context<Self>,
850 ) {
851 let log_contents = self.log_store.update(cx, |this, _| {
852 if let Some(state) = this.get_language_server_state(server_id) {
853 state.log_level = level;
854 }
855
856 this.server_logs(server_id).map(|v| log_contents(v, level))
857 });
858
859 if let Some(log_contents) = log_contents {
860 self.editor.update(cx, |editor, cx| {
861 editor.set_text(log_contents, window, cx);
862 editor.move_to_end(&MoveToEnd, window, cx);
863 });
864 cx.notify();
865 }
866
867 window.focus(&self.focus_handle);
868 }
869
870 fn show_trace_for_server(
871 &mut self,
872 server_id: LanguageServerId,
873 window: &mut Window,
874 cx: &mut Context<Self>,
875 ) {
876 let log_contents = self
877 .log_store
878 .read(cx)
879 .server_trace(server_id)
880 .map(|v| log_contents(v, ()));
881 if let Some(log_contents) = log_contents {
882 self.current_server_id = Some(server_id);
883 self.active_entry_kind = LogKind::Trace;
884 let (editor, editor_subscriptions) = Self::editor_for_logs(log_contents, window, cx);
885 self.editor = editor;
886 self.editor_subscriptions = editor_subscriptions;
887 cx.notify();
888 }
889 window.focus(&self.focus_handle);
890 }
891
892 fn show_rpc_trace_for_server(
893 &mut self,
894 server_id: LanguageServerId,
895 window: &mut Window,
896 cx: &mut Context<Self>,
897 ) {
898 let rpc_log = self.log_store.update(cx, |log_store, _| {
899 log_store
900 .enable_rpc_trace_for_language_server(server_id)
901 .map(|state| log_contents(&state.rpc_messages, ()))
902 });
903 if let Some(rpc_log) = rpc_log {
904 self.current_server_id = Some(server_id);
905 self.active_entry_kind = LogKind::Rpc;
906 let (editor, editor_subscriptions) = Self::editor_for_logs(rpc_log, window, cx);
907 let language = self.project.read(cx).languages().language_for_name("JSON");
908 editor
909 .read(cx)
910 .buffer()
911 .read(cx)
912 .as_singleton()
913 .expect("log buffer should be a singleton")
914 .update(cx, |_, cx| {
915 cx.spawn({
916 let buffer = cx.entity();
917 async move |_, cx| {
918 let language = language.await.ok();
919 buffer.update(cx, |buffer, cx| {
920 buffer.set_language(language, cx);
921 })
922 }
923 })
924 .detach_and_log_err(cx);
925 });
926
927 self.editor = editor;
928 self.editor_subscriptions = editor_subscriptions;
929 cx.notify();
930 }
931
932 window.focus(&self.focus_handle);
933 }
934
935 fn toggle_rpc_trace_for_server(
936 &mut self,
937 server_id: LanguageServerId,
938 enabled: bool,
939 window: &mut Window,
940 cx: &mut Context<Self>,
941 ) {
942 self.log_store.update(cx, |log_store, _| {
943 if enabled {
944 log_store.enable_rpc_trace_for_language_server(server_id);
945 } else {
946 log_store.disable_rpc_trace_for_language_server(server_id);
947 }
948 });
949 if !enabled && Some(server_id) == self.current_server_id {
950 self.show_logs_for_server(server_id, window, cx);
951 cx.notify();
952 }
953 }
954
955 fn update_trace_level(
956 &self,
957 server_id: LanguageServerId,
958 level: TraceValue,
959 cx: &mut Context<Self>,
960 ) {
961 if let Some(server) = self
962 .project
963 .read(cx)
964 .lsp_store()
965 .read(cx)
966 .language_server_for_id(server_id)
967 {
968 self.log_store.update(cx, |this, _| {
969 if let Some(state) = this.get_language_server_state(server_id) {
970 state.trace_level = level;
971 }
972 });
973
974 server
975 .notify::<SetTrace>(&SetTraceParams { value: level })
976 .ok();
977 }
978 }
979
980 fn show_server_info(
981 &mut self,
982 server_id: LanguageServerId,
983 window: &mut Window,
984 cx: &mut Context<Self>,
985 ) {
986 let lsp_store = self.project.read(cx).lsp_store();
987 let Some(server) = lsp_store.read(cx).language_server_for_id(server_id) else {
988 return;
989 };
990 self.current_server_id = Some(server_id);
991 self.active_entry_kind = LogKind::ServerInfo;
992 let (editor, editor_subscriptions) = Self::editor_for_server_info(&server, window, cx);
993 self.editor = editor;
994 self.editor_subscriptions = editor_subscriptions;
995 cx.notify();
996 window.focus(&self.focus_handle);
997 }
998}
999
1000fn log_filter<T: Message>(line: &T, cmp: <T as Message>::Level) -> Option<&str> {
1001 if line.should_include(cmp) {
1002 Some(line.as_ref())
1003 } else {
1004 None
1005 }
1006}
1007
1008fn log_contents<T: Message>(lines: &VecDeque<T>, cmp: <T as Message>::Level) -> String {
1009 let (a, b) = lines.as_slices();
1010 let a = a.iter().filter_map(move |v| log_filter(v, cmp));
1011 let b = b.iter().filter_map(move |v| log_filter(v, cmp));
1012 a.chain(b).fold(String::new(), |mut acc, el| {
1013 acc.push_str(el);
1014 acc.push('\n');
1015 acc
1016 })
1017}
1018
1019impl Render for LspLogView {
1020 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
1021 self.editor.update(cx, |editor, cx| {
1022 editor.render(window, cx).into_any_element()
1023 })
1024 }
1025}
1026
1027impl Focusable for LspLogView {
1028 fn focus_handle(&self, _: &App) -> FocusHandle {
1029 self.focus_handle.clone()
1030 }
1031}
1032
1033impl Item for LspLogView {
1034 type Event = EditorEvent;
1035
1036 fn to_item_events(event: &Self::Event, f: impl FnMut(workspace::item::ItemEvent)) {
1037 Editor::to_item_events(event, f)
1038 }
1039
1040 fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString {
1041 "LSP Logs".into()
1042 }
1043
1044 fn telemetry_event_text(&self) -> Option<&'static str> {
1045 None
1046 }
1047
1048 fn as_searchable(&self, handle: &Entity<Self>) -> Option<Box<dyn SearchableItemHandle>> {
1049 Some(Box::new(handle.clone()))
1050 }
1051
1052 fn act_as_type<'a>(
1053 &'a self,
1054 type_id: TypeId,
1055 self_handle: &'a Entity<Self>,
1056 _: &'a App,
1057 ) -> Option<AnyView> {
1058 if type_id == TypeId::of::<Self>() {
1059 Some(self_handle.to_any())
1060 } else if type_id == TypeId::of::<Editor>() {
1061 Some(self.editor.to_any())
1062 } else {
1063 None
1064 }
1065 }
1066
1067 fn clone_on_split(
1068 &self,
1069 _workspace_id: Option<WorkspaceId>,
1070 window: &mut Window,
1071 cx: &mut Context<Self>,
1072 ) -> Option<Entity<Self>>
1073 where
1074 Self: Sized,
1075 {
1076 Some(cx.new(|cx| {
1077 let mut new_view = Self::new(self.project.clone(), self.log_store.clone(), window, cx);
1078 if let Some(server_id) = self.current_server_id {
1079 match self.active_entry_kind {
1080 LogKind::Rpc => new_view.show_rpc_trace_for_server(server_id, window, cx),
1081 LogKind::Trace => new_view.show_trace_for_server(server_id, window, cx),
1082 LogKind::Logs => new_view.show_logs_for_server(server_id, window, cx),
1083 LogKind::ServerInfo => new_view.show_server_info(server_id, window, cx),
1084 }
1085 }
1086 new_view
1087 }))
1088 }
1089}
1090
1091impl SearchableItem for LspLogView {
1092 type Match = <Editor as SearchableItem>::Match;
1093
1094 fn clear_matches(&mut self, window: &mut Window, cx: &mut Context<Self>) {
1095 self.editor.update(cx, |e, cx| e.clear_matches(window, cx))
1096 }
1097
1098 fn update_matches(
1099 &mut self,
1100 matches: &[Self::Match],
1101 window: &mut Window,
1102 cx: &mut Context<Self>,
1103 ) {
1104 self.editor
1105 .update(cx, |e, cx| e.update_matches(matches, window, cx))
1106 }
1107
1108 fn query_suggestion(&mut self, window: &mut Window, cx: &mut Context<Self>) -> String {
1109 self.editor
1110 .update(cx, |e, cx| e.query_suggestion(window, cx))
1111 }
1112
1113 fn activate_match(
1114 &mut self,
1115 index: usize,
1116 matches: &[Self::Match],
1117 window: &mut Window,
1118 cx: &mut Context<Self>,
1119 ) {
1120 self.editor
1121 .update(cx, |e, cx| e.activate_match(index, matches, window, cx))
1122 }
1123
1124 fn select_matches(
1125 &mut self,
1126 matches: &[Self::Match],
1127 window: &mut Window,
1128 cx: &mut Context<Self>,
1129 ) {
1130 self.editor
1131 .update(cx, |e, cx| e.select_matches(matches, window, cx))
1132 }
1133
1134 fn find_matches(
1135 &mut self,
1136 query: Arc<project::search::SearchQuery>,
1137 window: &mut Window,
1138 cx: &mut Context<Self>,
1139 ) -> gpui::Task<Vec<Self::Match>> {
1140 self.editor
1141 .update(cx, |e, cx| e.find_matches(query, window, cx))
1142 }
1143
1144 fn replace(
1145 &mut self,
1146 _: &Self::Match,
1147 _: &SearchQuery,
1148 _window: &mut Window,
1149 _: &mut Context<Self>,
1150 ) {
1151 // Since LSP Log is read-only, it doesn't make sense to support replace operation.
1152 }
1153 fn supported_options(&self) -> workspace::searchable::SearchOptions {
1154 workspace::searchable::SearchOptions {
1155 case: true,
1156 word: true,
1157 regex: true,
1158 find_in_results: false,
1159 // LSP log is read-only.
1160 replacement: false,
1161 selection: false,
1162 }
1163 }
1164 fn active_match_index(
1165 &mut self,
1166 direction: Direction,
1167 matches: &[Self::Match],
1168 window: &mut Window,
1169 cx: &mut Context<Self>,
1170 ) -> Option<usize> {
1171 self.editor.update(cx, |e, cx| {
1172 e.active_match_index(direction, matches, window, cx)
1173 })
1174 }
1175}
1176
1177impl EventEmitter<ToolbarItemEvent> for LspLogToolbarItemView {}
1178
1179impl ToolbarItemView for LspLogToolbarItemView {
1180 fn set_active_pane_item(
1181 &mut self,
1182 active_pane_item: Option<&dyn ItemHandle>,
1183 _: &mut Window,
1184 cx: &mut Context<Self>,
1185 ) -> workspace::ToolbarItemLocation {
1186 if let Some(item) = active_pane_item {
1187 if let Some(log_view) = item.downcast::<LspLogView>() {
1188 self.log_view = Some(log_view.clone());
1189 self._log_view_subscription = Some(cx.observe(&log_view, |_, _, cx| {
1190 cx.notify();
1191 }));
1192 return ToolbarItemLocation::PrimaryLeft;
1193 }
1194 }
1195 self.log_view = None;
1196 self._log_view_subscription = None;
1197 ToolbarItemLocation::Hidden
1198 }
1199}
1200
1201impl Render for LspLogToolbarItemView {
1202 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
1203 let Some(log_view) = self.log_view.clone() else {
1204 return div();
1205 };
1206 let (menu_rows, current_server_id) = log_view.update(cx, |log_view, cx| {
1207 let menu_rows = log_view.menu_items(cx).unwrap_or_default();
1208 let current_server_id = log_view.current_server_id;
1209 (menu_rows, current_server_id)
1210 });
1211
1212 let current_server = current_server_id.and_then(|current_server_id| {
1213 if let Ok(ix) = menu_rows.binary_search_by_key(¤t_server_id, |e| e.server_id) {
1214 Some(menu_rows[ix].clone())
1215 } else {
1216 None
1217 }
1218 });
1219 let available_language_servers: Vec<_> = menu_rows
1220 .into_iter()
1221 .map(|row| {
1222 (
1223 row.server_id,
1224 row.server_name,
1225 row.worktree_root_name,
1226 row.selected_entry,
1227 )
1228 })
1229 .collect();
1230 let log_toolbar_view = cx.entity().clone();
1231 let lsp_menu = PopoverMenu::new("LspLogView")
1232 .anchor(Corner::TopLeft)
1233 .trigger(Button::new(
1234 "language_server_menu_header",
1235 current_server
1236 .as_ref()
1237 .map(|row| {
1238 Cow::Owned(format!(
1239 "{} ({})",
1240 row.server_name.0, row.worktree_root_name,
1241 ))
1242 })
1243 .unwrap_or_else(|| "No server selected".into()),
1244 ))
1245 .menu({
1246 let log_view = log_view.clone();
1247 move |window, cx| {
1248 let log_view = log_view.clone();
1249 ContextMenu::build(window, cx, |mut menu, window, _| {
1250 for (server_id, name, worktree_root, active_entry_kind) in
1251 available_language_servers.iter()
1252 {
1253 let label = format!("{} ({})", name, worktree_root);
1254 let server_id = *server_id;
1255 let active_entry_kind = *active_entry_kind;
1256 menu = menu.entry(
1257 label,
1258 None,
1259 window.handler_for(&log_view, move |view, window, cx| {
1260 view.current_server_id = Some(server_id);
1261 view.active_entry_kind = active_entry_kind;
1262 match view.active_entry_kind {
1263 LogKind::Rpc => {
1264 view.toggle_rpc_trace_for_server(
1265 server_id, true, window, cx,
1266 );
1267 view.show_rpc_trace_for_server(server_id, window, cx);
1268 }
1269 LogKind::Trace => {
1270 view.show_trace_for_server(server_id, window, cx)
1271 }
1272 LogKind::Logs => {
1273 view.show_logs_for_server(server_id, window, cx)
1274 }
1275 LogKind::ServerInfo => {
1276 view.show_server_info(server_id, window, cx)
1277 }
1278 }
1279 cx.notify();
1280 }),
1281 );
1282 }
1283 menu
1284 })
1285 .into()
1286 }
1287 });
1288 let view_selector = current_server.map(|server| {
1289 let server_id = server.server_id;
1290 let is_remote = server.server_kind.is_remote();
1291 let rpc_trace_enabled = server.rpc_trace_enabled;
1292 let log_view = log_view.clone();
1293 PopoverMenu::new("LspViewSelector")
1294 .anchor(Corner::TopLeft)
1295 .trigger(Button::new(
1296 "language_server_menu_header",
1297 server.selected_entry.label(),
1298 ))
1299 .menu(move |window, cx| {
1300 let log_toolbar_view = log_toolbar_view.clone();
1301 let log_view = log_view.clone();
1302 Some(ContextMenu::build(window, cx, move |this, window, _| {
1303 this.entry(
1304 SERVER_LOGS,
1305 None,
1306 window.handler_for(&log_view, move |view, window, cx| {
1307 view.show_logs_for_server(server_id, window, cx);
1308 }),
1309 )
1310 .when(!is_remote, |this| {
1311 this.entry(
1312 SERVER_TRACE,
1313 None,
1314 window.handler_for(&log_view, move |view, window, cx| {
1315 view.show_trace_for_server(server_id, window, cx);
1316 }),
1317 )
1318 .custom_entry(
1319 {
1320 let log_toolbar_view = log_toolbar_view.clone();
1321 move |window, _| {
1322 h_flex()
1323 .w_full()
1324 .justify_between()
1325 .child(Label::new(RPC_MESSAGES))
1326 .child(
1327 div().child(
1328 Checkbox::new(
1329 "LspLogEnableRpcTrace",
1330 if rpc_trace_enabled {
1331 ToggleState::Selected
1332 } else {
1333 ToggleState::Unselected
1334 },
1335 )
1336 .on_click(window.listener_for(
1337 &log_toolbar_view,
1338 move |view, selection, window, cx| {
1339 let enabled = matches!(
1340 selection,
1341 ToggleState::Selected
1342 );
1343 view.toggle_rpc_logging_for_server(
1344 server_id, enabled, window, cx,
1345 );
1346 cx.stop_propagation();
1347 },
1348 )),
1349 ),
1350 )
1351 .into_any_element()
1352 }
1353 },
1354 window.handler_for(&log_view, move |view, window, cx| {
1355 view.show_rpc_trace_for_server(server_id, window, cx);
1356 }),
1357 )
1358 })
1359 .entry(
1360 SERVER_INFO,
1361 None,
1362 window.handler_for(&log_view, move |view, window, cx| {
1363 view.show_server_info(server_id, window, cx);
1364 }),
1365 )
1366 }))
1367 })
1368 });
1369 h_flex()
1370 .size_full()
1371 .justify_between()
1372 .child(
1373 h_flex()
1374 .child(lsp_menu)
1375 .children(view_selector)
1376 .child(
1377 log_view.update(cx, |this, _cx| match this.active_entry_kind {
1378 LogKind::Trace => {
1379 let log_view = log_view.clone();
1380 div().child(
1381 PopoverMenu::new("lsp-trace-level-menu")
1382 .anchor(Corner::TopLeft)
1383 .trigger(Button::new(
1384 "language_server_trace_level_selector",
1385 "Trace level",
1386 ))
1387 .menu({
1388 let log_view = log_view.clone();
1389
1390 move |window, cx| {
1391 let id = log_view.read(cx).current_server_id?;
1392
1393 let trace_level =
1394 log_view.update(cx, |this, cx| {
1395 this.log_store.update(cx, |this, _| {
1396 Some(
1397 this.get_language_server_state(id)?
1398 .trace_level,
1399 )
1400 })
1401 })?;
1402
1403 ContextMenu::build(
1404 window,
1405 cx,
1406 |mut menu, window, cx| {
1407 let log_view = log_view.clone();
1408
1409 for (option, label) in [
1410 (TraceValue::Off, "Off"),
1411 (TraceValue::Messages, "Messages"),
1412 (TraceValue::Verbose, "Verbose"),
1413 ] {
1414 menu = menu.entry(label, None, {
1415 let log_view = log_view.clone();
1416 move |_, cx| {
1417 log_view.update(cx, |this, cx| {
1418 if let Some(id) =
1419 this.current_server_id
1420 {
1421 this.update_trace_level(
1422 id, option, cx,
1423 );
1424 }
1425 });
1426 }
1427 });
1428 if option == trace_level {
1429 menu.select_last(window, cx);
1430 }
1431 }
1432
1433 menu
1434 },
1435 )
1436 .into()
1437 }
1438 }),
1439 )
1440 }
1441 LogKind::Logs => {
1442 let log_view = log_view.clone();
1443 div().child(
1444 PopoverMenu::new("lsp-log-level-menu")
1445 .anchor(Corner::TopLeft)
1446 .trigger(Button::new(
1447 "language_server_log_level_selector",
1448 "Log level",
1449 ))
1450 .menu({
1451 let log_view = log_view.clone();
1452
1453 move |window, cx| {
1454 let id = log_view.read(cx).current_server_id?;
1455
1456 let log_level =
1457 log_view.update(cx, |this, cx| {
1458 this.log_store.update(cx, |this, _| {
1459 Some(
1460 this.get_language_server_state(id)?
1461 .log_level,
1462 )
1463 })
1464 })?;
1465
1466 ContextMenu::build(
1467 window,
1468 cx,
1469 |mut menu, window, cx| {
1470 let log_view = log_view.clone();
1471
1472 for (option, label) in [
1473 (MessageType::LOG, "Log"),
1474 (MessageType::INFO, "Info"),
1475 (MessageType::WARNING, "Warning"),
1476 (MessageType::ERROR, "Error"),
1477 ] {
1478 menu = menu.entry(label, None, {
1479 let log_view = log_view.clone();
1480 move |window, cx| {
1481 log_view.update(cx, |this, cx| {
1482 if let Some(id) =
1483 this.current_server_id
1484 {
1485 this.update_log_level(
1486 id, option, window, cx,
1487 );
1488 }
1489 });
1490 }
1491 });
1492 if option == log_level {
1493 menu.select_last(window, cx);
1494 }
1495 }
1496
1497 menu
1498 },
1499 )
1500 .into()
1501 }
1502 }),
1503 )
1504 }
1505 _ => div(),
1506 }),
1507 ),
1508 )
1509 .child(
1510 div()
1511 .child(
1512 Button::new("clear_log_button", "Clear").on_click(cx.listener(
1513 |this, _, window, cx| {
1514 if let Some(log_view) = this.log_view.as_ref() {
1515 log_view.update(cx, |log_view, cx| {
1516 log_view.editor.update(cx, |editor, cx| {
1517 editor.set_read_only(false);
1518 editor.clear(window, cx);
1519 editor.set_read_only(true);
1520 });
1521 })
1522 }
1523 },
1524 )),
1525 )
1526 .ml_2(),
1527 )
1528 }
1529}
1530
1531fn initialize_new_editor(
1532 content: String,
1533 move_to_end: bool,
1534 window: &mut Window,
1535 cx: &mut App,
1536) -> Entity<Editor> {
1537 cx.new(|cx| {
1538 let mut editor = Editor::multi_line(window, cx);
1539 editor.hide_minimap_by_default(window, cx);
1540 editor.set_text(content, window, cx);
1541 editor.set_show_git_diff_gutter(false, cx);
1542 editor.set_show_runnables(false, cx);
1543 editor.set_show_breakpoints(false, cx);
1544 editor.set_read_only(true);
1545 editor.set_show_edit_predictions(Some(false), window, cx);
1546 editor.set_soft_wrap_mode(SoftWrap::EditorWidth, cx);
1547 if move_to_end {
1548 editor.move_to_end(&MoveToEnd, window, cx);
1549 }
1550 editor
1551 })
1552}
1553
1554const RPC_MESSAGES: &str = "RPC Messages";
1555const SERVER_LOGS: &str = "Server Logs";
1556const SERVER_TRACE: &str = "Server Trace";
1557const SERVER_INFO: &str = "Server Info";
1558
1559impl Default for LspLogToolbarItemView {
1560 fn default() -> Self {
1561 Self::new()
1562 }
1563}
1564
1565impl LspLogToolbarItemView {
1566 pub fn new() -> Self {
1567 Self {
1568 log_view: None,
1569 _log_view_subscription: None,
1570 }
1571 }
1572
1573 fn toggle_rpc_logging_for_server(
1574 &mut self,
1575 id: LanguageServerId,
1576 enabled: bool,
1577 window: &mut Window,
1578 cx: &mut Context<Self>,
1579 ) {
1580 if let Some(log_view) = &self.log_view {
1581 log_view.update(cx, |log_view, cx| {
1582 log_view.toggle_rpc_trace_for_server(id, enabled, window, cx);
1583 if !enabled && Some(id) == log_view.current_server_id {
1584 log_view.show_logs_for_server(id, window, cx);
1585 cx.notify();
1586 } else if enabled {
1587 log_view.show_rpc_trace_for_server(id, window, cx);
1588 cx.notify();
1589 }
1590 window.focus(&log_view.focus_handle);
1591 });
1592 }
1593 cx.notify();
1594 }
1595}
1596
1597pub enum Event {
1598 NewServerLogEntry {
1599 id: LanguageServerId,
1600 entry: String,
1601 kind: LogKind,
1602 },
1603}
1604
1605impl EventEmitter<Event> for LogStore {}
1606impl EventEmitter<Event> for LspLogView {}
1607impl EventEmitter<EditorEvent> for LspLogView {}
1608impl EventEmitter<SearchEvent> for LspLogView {}