1use collections::HashMap;
2use editor::Editor;
3use futures::{channel::mpsc, StreamExt};
4use gpui::{
5 actions,
6 elements::{
7 AnchorCorner, ChildView, Empty, Flex, Label, MouseEventHandler, Overlay, OverlayFitMode,
8 ParentElement, Stack,
9 },
10 platform::{CursorStyle, MouseButton},
11 AnyElement, AppContext, Element, Entity, ModelContext, ModelHandle, View, ViewContext,
12 ViewHandle, WeakModelHandle,
13};
14use language::{Buffer, LanguageServerId, LanguageServerName};
15use project::{Project, Worktree};
16use std::{borrow::Cow, sync::Arc};
17use theme::{ui, Theme};
18use workspace::{
19 item::{Item, ItemHandle},
20 searchable::{SearchableItem, SearchableItemHandle},
21 ToolbarItemLocation, ToolbarItemView, Workspace, WorkspaceCreated,
22};
23
24const SEND_LINE: &str = "// Send:\n";
25const RECEIVE_LINE: &str = "// Receive:\n";
26
27pub struct LogStore {
28 projects: HashMap<WeakModelHandle<Project>, ProjectState>,
29 io_tx: mpsc::UnboundedSender<(WeakModelHandle<Project>, LanguageServerId, bool, String)>,
30}
31
32struct ProjectState {
33 servers: HashMap<LanguageServerId, LanguageServerState>,
34 _subscriptions: [gpui::Subscription; 2],
35}
36
37struct LanguageServerState {
38 log_buffer: ModelHandle<Buffer>,
39 rpc_state: Option<LanguageServerRpcState>,
40}
41
42struct LanguageServerRpcState {
43 buffer: ModelHandle<Buffer>,
44 last_message_kind: Option<MessageKind>,
45 _subscription: lsp::Subscription,
46}
47
48pub struct LspLogView {
49 pub(crate) editor: ViewHandle<Editor>,
50 log_store: ModelHandle<LogStore>,
51 current_server_id: Option<LanguageServerId>,
52 is_showing_rpc_trace: bool,
53 project: ModelHandle<Project>,
54}
55
56pub struct LspLogToolbarItemView {
57 log_view: Option<ViewHandle<LspLogView>>,
58 menu_open: bool,
59}
60
61#[derive(Copy, Clone, PartialEq, Eq)]
62enum MessageKind {
63 Send,
64 Receive,
65}
66
67#[derive(Clone, Debug, PartialEq)]
68pub(crate) struct LogMenuItem {
69 pub server_id: LanguageServerId,
70 pub server_name: LanguageServerName,
71 pub worktree: ModelHandle<Worktree>,
72 pub rpc_trace_enabled: bool,
73 pub rpc_trace_selected: bool,
74 pub logs_selected: bool,
75}
76
77actions!(debug, [OpenLanguageServerLogs]);
78
79pub fn init(cx: &mut AppContext) {
80 let log_store = cx.add_model(|cx| LogStore::new(cx));
81
82 cx.subscribe_global::<WorkspaceCreated, _>({
83 let log_store = log_store.clone();
84 move |event, cx| {
85 let workspace = &event.0;
86 if let Some(workspace) = workspace.upgrade(cx) {
87 let project = workspace.read(cx).project().clone();
88 if project.read(cx).is_local() {
89 log_store.update(cx, |store, cx| {
90 store.add_project(&project, cx);
91 });
92 }
93 }
94 }
95 })
96 .detach();
97
98 cx.add_action(
99 move |workspace: &mut Workspace, _: &OpenLanguageServerLogs, cx: _| {
100 let project = workspace.project().read(cx);
101 if project.is_local() {
102 workspace.add_item(
103 Box::new(cx.add_view(|cx| {
104 LspLogView::new(workspace.project().clone(), log_store.clone(), cx)
105 })),
106 cx,
107 );
108 }
109 },
110 );
111}
112
113impl LogStore {
114 pub fn new(cx: &mut ModelContext<Self>) -> Self {
115 let (io_tx, mut io_rx) = mpsc::unbounded();
116 let this = Self {
117 projects: HashMap::default(),
118 io_tx,
119 };
120 cx.spawn_weak(|this, mut cx| async move {
121 while let Some((project, server_id, is_output, mut message)) = io_rx.next().await {
122 if let Some(this) = this.upgrade(&cx) {
123 this.update(&mut cx, |this, cx| {
124 message.push('\n');
125 this.on_io(project, server_id, is_output, &message, cx);
126 });
127 }
128 }
129 anyhow::Ok(())
130 })
131 .detach();
132 this
133 }
134
135 pub fn add_project(&mut self, project: &ModelHandle<Project>, cx: &mut ModelContext<Self>) {
136 use project::Event::*;
137
138 let weak_project = project.downgrade();
139 self.projects.insert(
140 weak_project,
141 ProjectState {
142 servers: HashMap::default(),
143 _subscriptions: [
144 cx.observe_release(&project, move |this, _, _| {
145 this.projects.remove(&weak_project);
146 }),
147 cx.subscribe(project, |this, project, event, cx| match event {
148 LanguageServerAdded(id) => {
149 this.add_language_server(&project, *id, cx);
150 }
151 LanguageServerRemoved(id) => {
152 this.remove_language_server(&project, *id, cx);
153 }
154 LanguageServerLog(id, message) => {
155 this.add_language_server_log(&project, *id, message, cx);
156 }
157 _ => {}
158 }),
159 ],
160 },
161 );
162 }
163
164 fn add_language_server(
165 &mut self,
166 project: &ModelHandle<Project>,
167 id: LanguageServerId,
168 cx: &mut ModelContext<Self>,
169 ) -> Option<ModelHandle<Buffer>> {
170 let project_state = self.projects.get_mut(&project.downgrade())?;
171 Some(
172 project_state
173 .servers
174 .entry(id)
175 .or_insert_with(|| {
176 cx.notify();
177 LanguageServerState {
178 rpc_state: None,
179 log_buffer: cx.add_model(|cx| Buffer::new(0, "", cx)).clone(),
180 }
181 })
182 .log_buffer
183 .clone(),
184 )
185 }
186
187 fn add_language_server_log(
188 &mut self,
189 project: &ModelHandle<Project>,
190 id: LanguageServerId,
191 message: &str,
192 cx: &mut ModelContext<Self>,
193 ) -> Option<()> {
194 let buffer = self.add_language_server(&project, id, cx)?;
195 buffer.update(cx, |buffer, cx| {
196 let len = buffer.len();
197 let has_newline = message.ends_with("\n");
198 buffer.edit([(len..len, message)], None, cx);
199 if !has_newline {
200 let len = buffer.len();
201 buffer.edit([(len..len, "\n")], None, cx);
202 }
203 });
204 cx.notify();
205 Some(())
206 }
207
208 fn remove_language_server(
209 &mut self,
210 project: &ModelHandle<Project>,
211 id: LanguageServerId,
212 cx: &mut ModelContext<Self>,
213 ) -> Option<()> {
214 let project_state = self.projects.get_mut(&project.downgrade())?;
215 project_state.servers.remove(&id);
216 cx.notify();
217 Some(())
218 }
219
220 pub fn log_buffer_for_server(
221 &self,
222 project: &ModelHandle<Project>,
223 server_id: LanguageServerId,
224 ) -> Option<ModelHandle<Buffer>> {
225 let weak_project = project.downgrade();
226 let project_state = self.projects.get(&weak_project)?;
227 let server_state = project_state.servers.get(&server_id)?;
228 Some(server_state.log_buffer.clone())
229 }
230
231 pub fn enable_rpc_trace_for_language_server(
232 &mut self,
233 project: &ModelHandle<Project>,
234 server_id: LanguageServerId,
235 cx: &mut ModelContext<Self>,
236 ) -> Option<ModelHandle<Buffer>> {
237 let weak_project = project.downgrade();
238 let project_state = self.projects.get_mut(&weak_project)?;
239 let server_state = project_state.servers.get_mut(&server_id)?;
240 let server = project.read(cx).language_server_for_id(server_id)?;
241 let rpc_state = server_state.rpc_state.get_or_insert_with(|| {
242 let io_tx = self.io_tx.clone();
243 let language = project.read(cx).languages().language_for_name("JSON");
244 let buffer = cx.add_model(|cx| Buffer::new(0, "", cx));
245 cx.spawn_weak({
246 let buffer = buffer.clone();
247 |_, mut cx| async move {
248 let language = language.await.ok();
249 buffer.update(&mut cx, |buffer, cx| {
250 buffer.set_language(language, cx);
251 });
252 }
253 })
254 .detach();
255
256 LanguageServerRpcState {
257 buffer,
258 last_message_kind: None,
259 _subscription: server.on_io(move |is_received, json| {
260 io_tx
261 .unbounded_send((weak_project, server_id, is_received, json.to_string()))
262 .ok();
263 }),
264 }
265 });
266 Some(rpc_state.buffer.clone())
267 }
268
269 pub fn disable_rpc_trace_for_language_server(
270 &mut self,
271 project: &ModelHandle<Project>,
272 server_id: LanguageServerId,
273 _: &mut ModelContext<Self>,
274 ) -> Option<()> {
275 let project = project.downgrade();
276 let project_state = self.projects.get_mut(&project)?;
277 let server_state = project_state.servers.get_mut(&server_id)?;
278 server_state.rpc_state.take();
279 Some(())
280 }
281
282 fn on_io(
283 &mut self,
284 project: WeakModelHandle<Project>,
285 language_server_id: LanguageServerId,
286 is_received: bool,
287 message: &str,
288 cx: &mut AppContext,
289 ) -> Option<()> {
290 let state = self
291 .projects
292 .get_mut(&project)?
293 .servers
294 .get_mut(&language_server_id)?
295 .rpc_state
296 .as_mut()?;
297 state.buffer.update(cx, |buffer, cx| {
298 let kind = if is_received {
299 MessageKind::Receive
300 } else {
301 MessageKind::Send
302 };
303 if state.last_message_kind != Some(kind) {
304 let len = buffer.len();
305 let line = match kind {
306 MessageKind::Send => SEND_LINE,
307 MessageKind::Receive => RECEIVE_LINE,
308 };
309 buffer.edit([(len..len, line)], None, cx);
310 state.last_message_kind = Some(kind);
311 }
312 let len = buffer.len();
313 buffer.edit([(len..len, message)], None, cx);
314 });
315 Some(())
316 }
317}
318
319impl LspLogView {
320 pub fn new(
321 project: ModelHandle<Project>,
322 log_store: ModelHandle<LogStore>,
323 cx: &mut ViewContext<Self>,
324 ) -> Self {
325 let server_id = log_store
326 .read(cx)
327 .projects
328 .get(&project.downgrade())
329 .and_then(|project| project.servers.keys().copied().next());
330 let buffer = cx.add_model(|cx| Buffer::new(0, "", cx));
331 let mut this = Self {
332 editor: Self::editor_for_buffer(project.clone(), buffer, cx),
333 project,
334 log_store,
335 current_server_id: None,
336 is_showing_rpc_trace: false,
337 };
338 if let Some(server_id) = server_id {
339 this.show_logs_for_server(server_id, cx);
340 }
341 this
342 }
343
344 fn editor_for_buffer(
345 project: ModelHandle<Project>,
346 buffer: ModelHandle<Buffer>,
347 cx: &mut ViewContext<Self>,
348 ) -> ViewHandle<Editor> {
349 let editor = cx.add_view(|cx| {
350 let mut editor = Editor::for_buffer(buffer, Some(project), cx);
351 editor.set_read_only(true);
352 editor.move_to_end(&Default::default(), cx);
353 editor
354 });
355 cx.subscribe(&editor, |_, _, event, cx| cx.emit(event.clone()))
356 .detach();
357 editor
358 }
359
360 pub(crate) fn menu_items<'a>(&'a self, cx: &'a AppContext) -> Option<Vec<LogMenuItem>> {
361 let log_store = self.log_store.read(cx);
362 let state = log_store.projects.get(&self.project.downgrade())?;
363 let mut rows = self
364 .project
365 .read(cx)
366 .language_servers()
367 .filter_map(|(server_id, language_server_name, worktree_id)| {
368 let worktree = self.project.read(cx).worktree_for_id(worktree_id, cx)?;
369 let state = state.servers.get(&server_id)?;
370 Some(LogMenuItem {
371 server_id,
372 server_name: language_server_name,
373 worktree,
374 rpc_trace_enabled: state.rpc_state.is_some(),
375 rpc_trace_selected: self.is_showing_rpc_trace
376 && self.current_server_id == Some(server_id),
377 logs_selected: !self.is_showing_rpc_trace
378 && self.current_server_id == Some(server_id),
379 })
380 })
381 .collect::<Vec<_>>();
382 rows.sort_by_key(|row| row.server_id);
383 rows.dedup_by_key(|row| row.server_id);
384 Some(rows)
385 }
386
387 fn show_logs_for_server(&mut self, server_id: LanguageServerId, cx: &mut ViewContext<Self>) {
388 let buffer = self
389 .log_store
390 .read(cx)
391 .log_buffer_for_server(&self.project, server_id);
392 if let Some(buffer) = buffer {
393 self.current_server_id = Some(server_id);
394 self.is_showing_rpc_trace = false;
395 self.editor = Self::editor_for_buffer(self.project.clone(), buffer, cx);
396 cx.notify();
397 }
398 }
399
400 fn show_rpc_trace_for_server(
401 &mut self,
402 server_id: LanguageServerId,
403 cx: &mut ViewContext<Self>,
404 ) {
405 let buffer = self.log_store.update(cx, |log_set, cx| {
406 log_set.enable_rpc_trace_for_language_server(&self.project, server_id, cx)
407 });
408 if let Some(buffer) = buffer {
409 self.current_server_id = Some(server_id);
410 self.is_showing_rpc_trace = true;
411 self.editor = Self::editor_for_buffer(self.project.clone(), buffer, cx);
412 cx.notify();
413 }
414 }
415
416 fn toggle_rpc_trace_for_server(
417 &mut self,
418 server_id: LanguageServerId,
419 enabled: bool,
420 cx: &mut ViewContext<Self>,
421 ) {
422 self.log_store.update(cx, |log_store, cx| {
423 if enabled {
424 log_store.enable_rpc_trace_for_language_server(&self.project, server_id, cx);
425 } else {
426 log_store.disable_rpc_trace_for_language_server(&self.project, server_id, cx);
427 }
428 });
429 if !enabled && Some(server_id) == self.current_server_id {
430 self.show_logs_for_server(server_id, cx);
431 cx.notify();
432 }
433 }
434}
435
436impl View for LspLogView {
437 fn ui_name() -> &'static str {
438 "LspLogView"
439 }
440
441 fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
442 ChildView::new(&self.editor, cx).into_any()
443 }
444
445 fn focus_in(&mut self, _: gpui::AnyViewHandle, cx: &mut ViewContext<Self>) {
446 if cx.is_self_focused() {
447 cx.focus(&self.editor);
448 }
449 }
450}
451
452impl Item for LspLogView {
453 fn tab_content<V: View>(
454 &self,
455 _: Option<usize>,
456 style: &theme::Tab,
457 _: &AppContext,
458 ) -> AnyElement<V> {
459 Label::new("LSP Logs", style.label.clone()).into_any()
460 }
461
462 fn as_searchable(&self, handle: &ViewHandle<Self>) -> Option<Box<dyn SearchableItemHandle>> {
463 Some(Box::new(handle.clone()))
464 }
465}
466
467impl SearchableItem for LspLogView {
468 type Match = <Editor as SearchableItem>::Match;
469
470 fn to_search_event(event: &Self::Event) -> Option<workspace::searchable::SearchEvent> {
471 Editor::to_search_event(event)
472 }
473
474 fn clear_matches(&mut self, cx: &mut ViewContext<Self>) {
475 self.editor.update(cx, |e, cx| e.clear_matches(cx))
476 }
477
478 fn update_matches(&mut self, matches: Vec<Self::Match>, cx: &mut ViewContext<Self>) {
479 self.editor
480 .update(cx, |e, cx| e.update_matches(matches, cx))
481 }
482
483 fn query_suggestion(&mut self, cx: &mut ViewContext<Self>) -> String {
484 self.editor.update(cx, |e, cx| e.query_suggestion(cx))
485 }
486
487 fn activate_match(
488 &mut self,
489 index: usize,
490 matches: Vec<Self::Match>,
491 cx: &mut ViewContext<Self>,
492 ) {
493 self.editor
494 .update(cx, |e, cx| e.activate_match(index, matches, cx))
495 }
496
497 fn select_matches(&mut self, matches: Vec<Self::Match>, cx: &mut ViewContext<Self>) {
498 self.editor
499 .update(cx, |e, cx| e.select_matches(matches, cx))
500 }
501
502 fn find_matches(
503 &mut self,
504 query: project::search::SearchQuery,
505 cx: &mut ViewContext<Self>,
506 ) -> gpui::Task<Vec<Self::Match>> {
507 self.editor.update(cx, |e, cx| e.find_matches(query, cx))
508 }
509
510 fn active_match_index(
511 &mut self,
512 matches: Vec<Self::Match>,
513 cx: &mut ViewContext<Self>,
514 ) -> Option<usize> {
515 self.editor
516 .update(cx, |e, cx| e.active_match_index(matches, cx))
517 }
518}
519
520impl ToolbarItemView for LspLogToolbarItemView {
521 fn set_active_pane_item(
522 &mut self,
523 active_pane_item: Option<&dyn ItemHandle>,
524 _: &mut ViewContext<Self>,
525 ) -> workspace::ToolbarItemLocation {
526 self.menu_open = false;
527 if let Some(item) = active_pane_item {
528 if let Some(log_view) = item.downcast::<LspLogView>() {
529 self.log_view = Some(log_view.clone());
530 return ToolbarItemLocation::PrimaryLeft {
531 flex: Some((1., false)),
532 };
533 }
534 }
535 self.log_view = None;
536 ToolbarItemLocation::Hidden
537 }
538}
539
540impl View for LspLogToolbarItemView {
541 fn ui_name() -> &'static str {
542 "LspLogView"
543 }
544
545 fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
546 let theme = theme::current(cx).clone();
547 let Some(log_view) = self.log_view.as_ref() else { return Empty::new().into_any() };
548 let log_view = log_view.read(cx);
549 let menu_rows = log_view.menu_items(cx).unwrap_or_default();
550
551 let current_server_id = log_view.current_server_id;
552 let current_server = current_server_id.and_then(|current_server_id| {
553 if let Ok(ix) = menu_rows.binary_search_by_key(¤t_server_id, |e| e.server_id) {
554 Some(menu_rows[ix].clone())
555 } else {
556 None
557 }
558 });
559
560 enum Menu {}
561
562 Stack::new()
563 .with_child(Self::render_language_server_menu_header(
564 current_server,
565 &theme,
566 cx,
567 ))
568 .with_children(if self.menu_open {
569 Some(
570 Overlay::new(
571 MouseEventHandler::<Menu, _>::new(0, cx, move |_, cx| {
572 Flex::column()
573 .with_children(menu_rows.into_iter().map(|row| {
574 Self::render_language_server_menu_item(
575 row.server_id,
576 row.server_name,
577 row.worktree,
578 row.rpc_trace_enabled,
579 row.logs_selected,
580 row.rpc_trace_selected,
581 &theme,
582 cx,
583 )
584 }))
585 .contained()
586 .with_style(theme.toolbar_dropdown_menu.container)
587 .constrained()
588 .with_width(400.)
589 .with_height(400.)
590 })
591 .on_down_out(MouseButton::Left, |_, this, cx| {
592 this.menu_open = false;
593 cx.notify()
594 }),
595 )
596 .with_hoverable(true)
597 .with_fit_mode(OverlayFitMode::SwitchAnchor)
598 .with_anchor_corner(AnchorCorner::TopLeft)
599 .with_z_index(999)
600 .aligned()
601 .bottom()
602 .left(),
603 )
604 } else {
605 None
606 })
607 .aligned()
608 .left()
609 .clipped()
610 .into_any()
611 }
612}
613
614const RPC_MESSAGES: &str = "RPC Messages";
615const SERVER_LOGS: &str = "Server Logs";
616
617impl LspLogToolbarItemView {
618 pub fn new() -> Self {
619 Self {
620 menu_open: false,
621 log_view: None,
622 }
623 }
624
625 fn toggle_menu(&mut self, cx: &mut ViewContext<Self>) {
626 self.menu_open = !self.menu_open;
627 cx.notify();
628 }
629
630 fn toggle_logging_for_server(
631 &mut self,
632 id: LanguageServerId,
633 enabled: bool,
634 cx: &mut ViewContext<Self>,
635 ) {
636 if let Some(log_view) = &self.log_view {
637 log_view.update(cx, |log_view, cx| {
638 log_view.toggle_rpc_trace_for_server(id, enabled, cx);
639 if !enabled && Some(id) == log_view.current_server_id {
640 log_view.show_logs_for_server(id, cx);
641 cx.notify();
642 }
643 });
644 }
645 cx.notify();
646 }
647
648 fn show_logs_for_server(&mut self, id: LanguageServerId, cx: &mut ViewContext<Self>) {
649 if let Some(log_view) = &self.log_view {
650 log_view.update(cx, |view, cx| view.show_logs_for_server(id, cx));
651 self.menu_open = false;
652 cx.notify();
653 }
654 }
655
656 fn show_rpc_trace_for_server(&mut self, id: LanguageServerId, cx: &mut ViewContext<Self>) {
657 if let Some(log_view) = &self.log_view {
658 log_view.update(cx, |view, cx| view.show_rpc_trace_for_server(id, cx));
659 self.menu_open = false;
660 cx.notify();
661 }
662 }
663
664 fn render_language_server_menu_header(
665 current_server: Option<LogMenuItem>,
666 theme: &Arc<Theme>,
667 cx: &mut ViewContext<Self>,
668 ) -> impl Element<Self> {
669 enum ToggleMenu {}
670 MouseEventHandler::<ToggleMenu, Self>::new(0, cx, move |state, cx| {
671 let label: Cow<str> = current_server
672 .and_then(|row| {
673 let worktree = row.worktree.read(cx);
674 Some(
675 format!(
676 "{} ({}) - {}",
677 row.server_name.0,
678 worktree.root_name(),
679 if row.rpc_trace_selected {
680 RPC_MESSAGES
681 } else {
682 SERVER_LOGS
683 },
684 )
685 .into(),
686 )
687 })
688 .unwrap_or_else(|| "No server selected".into());
689 let style = theme.toolbar_dropdown_menu.header.style_for(state);
690 Label::new(label, style.text.clone())
691 .contained()
692 .with_style(style.container)
693 })
694 .with_cursor_style(CursorStyle::PointingHand)
695 .on_click(MouseButton::Left, move |_, view, cx| {
696 view.toggle_menu(cx);
697 })
698 }
699
700 fn render_language_server_menu_item(
701 id: LanguageServerId,
702 name: LanguageServerName,
703 worktree: ModelHandle<Worktree>,
704 rpc_trace_enabled: bool,
705 logs_selected: bool,
706 rpc_trace_selected: bool,
707 theme: &Arc<Theme>,
708 cx: &mut ViewContext<Self>,
709 ) -> impl Element<Self> {
710 enum ActivateLog {}
711 enum ActivateRpcTrace {}
712
713 Flex::column()
714 .with_child({
715 let style = &theme.toolbar_dropdown_menu.section_header;
716 Label::new(
717 format!("{} ({})", name.0, worktree.read(cx).root_name()),
718 style.text.clone(),
719 )
720 .contained()
721 .with_style(style.container)
722 .constrained()
723 .with_height(theme.toolbar_dropdown_menu.row_height)
724 })
725 .with_child(
726 MouseEventHandler::<ActivateLog, _>::new(id.0, cx, move |state, _| {
727 let style = theme
728 .toolbar_dropdown_menu
729 .item
730 .in_state(logs_selected)
731 .style_for(state);
732 Label::new(SERVER_LOGS, style.text.clone())
733 .contained()
734 .with_style(style.container)
735 .constrained()
736 .with_height(theme.toolbar_dropdown_menu.row_height)
737 })
738 .with_cursor_style(CursorStyle::PointingHand)
739 .on_click(MouseButton::Left, move |_, view, cx| {
740 view.show_logs_for_server(id, cx);
741 }),
742 )
743 .with_child(
744 MouseEventHandler::<ActivateRpcTrace, _>::new(id.0, cx, move |state, cx| {
745 let style = theme
746 .toolbar_dropdown_menu
747 .item
748 .in_state(rpc_trace_selected)
749 .style_for(state);
750 Flex::row()
751 .with_child(
752 Label::new(RPC_MESSAGES, style.text.clone())
753 .constrained()
754 .with_height(theme.toolbar_dropdown_menu.row_height),
755 )
756 .with_child(
757 ui::checkbox_with_label::<Self, _, Self, _>(
758 Empty::new(),
759 &theme.welcome.checkbox,
760 rpc_trace_enabled,
761 id.0,
762 cx,
763 move |this, enabled, cx| {
764 this.toggle_logging_for_server(id, enabled, cx);
765 },
766 )
767 .flex_float(),
768 )
769 .align_children_center()
770 .contained()
771 .with_style(style.container)
772 .constrained()
773 .with_height(theme.toolbar_dropdown_menu.row_height)
774 })
775 .with_cursor_style(CursorStyle::PointingHand)
776 .on_click(MouseButton::Left, move |_, view, cx| {
777 view.show_rpc_trace_for_server(id, cx);
778 }),
779 )
780 }
781}
782
783impl Entity for LogStore {
784 type Event = ();
785}
786
787impl Entity for LspLogView {
788 type Event = editor::Event;
789}
790
791impl Entity for LspLogToolbarItemView {
792 type Event = ();
793}