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(
471 &mut self,
472 event: &Self::Event,
473 cx: &mut ViewContext<Self>,
474 ) -> Option<workspace::searchable::SearchEvent> {
475 self.editor
476 .update(cx, |editor, cx| editor.to_search_event(event, cx))
477 }
478
479 fn clear_matches(&mut self, cx: &mut ViewContext<Self>) {
480 self.editor.update(cx, |e, cx| e.clear_matches(cx))
481 }
482
483 fn update_matches(&mut self, matches: Vec<Self::Match>, cx: &mut ViewContext<Self>) {
484 self.editor
485 .update(cx, |e, cx| e.update_matches(matches, cx))
486 }
487
488 fn query_suggestion(&mut self, cx: &mut ViewContext<Self>) -> String {
489 self.editor.update(cx, |e, cx| e.query_suggestion(cx))
490 }
491
492 fn activate_match(
493 &mut self,
494 index: usize,
495 matches: Vec<Self::Match>,
496 cx: &mut ViewContext<Self>,
497 ) {
498 self.editor
499 .update(cx, |e, cx| e.activate_match(index, matches, cx))
500 }
501
502 fn select_matches(&mut self, matches: Vec<Self::Match>, cx: &mut ViewContext<Self>) {
503 self.editor
504 .update(cx, |e, cx| e.select_matches(matches, cx))
505 }
506
507 fn find_matches(
508 &mut self,
509 query: project::search::SearchQuery,
510 cx: &mut ViewContext<Self>,
511 ) -> gpui::Task<Vec<Self::Match>> {
512 self.editor.update(cx, |e, cx| e.find_matches(query, cx))
513 }
514
515 fn active_match_index(
516 &mut self,
517 matches: Vec<Self::Match>,
518 cx: &mut ViewContext<Self>,
519 ) -> Option<usize> {
520 self.editor
521 .update(cx, |e, cx| e.active_match_index(matches, cx))
522 }
523}
524
525impl ToolbarItemView for LspLogToolbarItemView {
526 fn set_active_pane_item(
527 &mut self,
528 active_pane_item: Option<&dyn ItemHandle>,
529 _: &mut ViewContext<Self>,
530 ) -> workspace::ToolbarItemLocation {
531 self.menu_open = false;
532 if let Some(item) = active_pane_item {
533 if let Some(log_view) = item.downcast::<LspLogView>() {
534 self.log_view = Some(log_view.clone());
535 return ToolbarItemLocation::PrimaryLeft {
536 flex: Some((1., false)),
537 };
538 }
539 }
540 self.log_view = None;
541 ToolbarItemLocation::Hidden
542 }
543}
544
545impl View for LspLogToolbarItemView {
546 fn ui_name() -> &'static str {
547 "LspLogView"
548 }
549
550 fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
551 let theme = theme::current(cx).clone();
552 let Some(log_view) = self.log_view.as_ref() else { return Empty::new().into_any() };
553 let log_view = log_view.read(cx);
554 let menu_rows = log_view.menu_items(cx).unwrap_or_default();
555
556 let current_server_id = log_view.current_server_id;
557 let current_server = current_server_id.and_then(|current_server_id| {
558 if let Ok(ix) = menu_rows.binary_search_by_key(¤t_server_id, |e| e.server_id) {
559 Some(menu_rows[ix].clone())
560 } else {
561 None
562 }
563 });
564
565 enum Menu {}
566
567 Stack::new()
568 .with_child(Self::render_language_server_menu_header(
569 current_server,
570 &theme,
571 cx,
572 ))
573 .with_children(if self.menu_open {
574 Some(
575 Overlay::new(
576 MouseEventHandler::new::<Menu, _>(0, cx, move |_, cx| {
577 Flex::column()
578 .with_children(menu_rows.into_iter().map(|row| {
579 Self::render_language_server_menu_item(
580 row.server_id,
581 row.server_name,
582 row.worktree,
583 row.rpc_trace_enabled,
584 row.logs_selected,
585 row.rpc_trace_selected,
586 &theme,
587 cx,
588 )
589 }))
590 .contained()
591 .with_style(theme.toolbar_dropdown_menu.container)
592 .constrained()
593 .with_width(400.)
594 .with_height(400.)
595 })
596 .on_down_out(MouseButton::Left, |_, this, cx| {
597 this.menu_open = false;
598 cx.notify()
599 }),
600 )
601 .with_hoverable(true)
602 .with_fit_mode(OverlayFitMode::SwitchAnchor)
603 .with_anchor_corner(AnchorCorner::TopLeft)
604 .with_z_index(999)
605 .aligned()
606 .bottom()
607 .left(),
608 )
609 } else {
610 None
611 })
612 .aligned()
613 .left()
614 .clipped()
615 .into_any()
616 }
617}
618
619const RPC_MESSAGES: &str = "RPC Messages";
620const SERVER_LOGS: &str = "Server Logs";
621
622impl LspLogToolbarItemView {
623 pub fn new() -> Self {
624 Self {
625 menu_open: false,
626 log_view: None,
627 }
628 }
629
630 fn toggle_menu(&mut self, cx: &mut ViewContext<Self>) {
631 self.menu_open = !self.menu_open;
632 cx.notify();
633 }
634
635 fn toggle_logging_for_server(
636 &mut self,
637 id: LanguageServerId,
638 enabled: bool,
639 cx: &mut ViewContext<Self>,
640 ) {
641 if let Some(log_view) = &self.log_view {
642 log_view.update(cx, |log_view, cx| {
643 log_view.toggle_rpc_trace_for_server(id, enabled, cx);
644 if !enabled && Some(id) == log_view.current_server_id {
645 log_view.show_logs_for_server(id, cx);
646 cx.notify();
647 }
648 });
649 }
650 cx.notify();
651 }
652
653 fn show_logs_for_server(&mut self, id: LanguageServerId, cx: &mut ViewContext<Self>) {
654 if let Some(log_view) = &self.log_view {
655 log_view.update(cx, |view, cx| view.show_logs_for_server(id, cx));
656 self.menu_open = false;
657 cx.notify();
658 }
659 }
660
661 fn show_rpc_trace_for_server(&mut self, id: LanguageServerId, cx: &mut ViewContext<Self>) {
662 if let Some(log_view) = &self.log_view {
663 log_view.update(cx, |view, cx| view.show_rpc_trace_for_server(id, cx));
664 self.menu_open = false;
665 cx.notify();
666 }
667 }
668
669 fn render_language_server_menu_header(
670 current_server: Option<LogMenuItem>,
671 theme: &Arc<Theme>,
672 cx: &mut ViewContext<Self>,
673 ) -> impl Element<Self> {
674 enum ToggleMenu {}
675 MouseEventHandler::new::<ToggleMenu, _>(0, cx, move |state, cx| {
676 let label: Cow<str> = current_server
677 .and_then(|row| {
678 let worktree = row.worktree.read(cx);
679 Some(
680 format!(
681 "{} ({}) - {}",
682 row.server_name.0,
683 worktree.root_name(),
684 if row.rpc_trace_selected {
685 RPC_MESSAGES
686 } else {
687 SERVER_LOGS
688 },
689 )
690 .into(),
691 )
692 })
693 .unwrap_or_else(|| "No server selected".into());
694 let style = theme.toolbar_dropdown_menu.header.style_for(state);
695 Label::new(label, style.text.clone())
696 .contained()
697 .with_style(style.container)
698 })
699 .with_cursor_style(CursorStyle::PointingHand)
700 .on_click(MouseButton::Left, move |_, view, cx| {
701 view.toggle_menu(cx);
702 })
703 }
704
705 fn render_language_server_menu_item(
706 id: LanguageServerId,
707 name: LanguageServerName,
708 worktree: ModelHandle<Worktree>,
709 rpc_trace_enabled: bool,
710 logs_selected: bool,
711 rpc_trace_selected: bool,
712 theme: &Arc<Theme>,
713 cx: &mut ViewContext<Self>,
714 ) -> impl Element<Self> {
715 enum ActivateLog {}
716 enum ActivateRpcTrace {}
717
718 Flex::column()
719 .with_child({
720 let style = &theme.toolbar_dropdown_menu.section_header;
721 Label::new(
722 format!("{} ({})", name.0, worktree.read(cx).root_name()),
723 style.text.clone(),
724 )
725 .contained()
726 .with_style(style.container)
727 .constrained()
728 .with_height(theme.toolbar_dropdown_menu.row_height)
729 })
730 .with_child(
731 MouseEventHandler::new::<ActivateLog, _>(id.0, cx, move |state, _| {
732 let style = theme
733 .toolbar_dropdown_menu
734 .item
735 .in_state(logs_selected)
736 .style_for(state);
737 Label::new(SERVER_LOGS, style.text.clone())
738 .contained()
739 .with_style(style.container)
740 .constrained()
741 .with_height(theme.toolbar_dropdown_menu.row_height)
742 })
743 .with_cursor_style(CursorStyle::PointingHand)
744 .on_click(MouseButton::Left, move |_, view, cx| {
745 view.show_logs_for_server(id, cx);
746 }),
747 )
748 .with_child(
749 MouseEventHandler::new::<ActivateRpcTrace, _>(id.0, cx, move |state, cx| {
750 let style = theme
751 .toolbar_dropdown_menu
752 .item
753 .in_state(rpc_trace_selected)
754 .style_for(state);
755 Flex::row()
756 .with_child(
757 Label::new(RPC_MESSAGES, style.text.clone())
758 .constrained()
759 .with_height(theme.toolbar_dropdown_menu.row_height),
760 )
761 .with_child(
762 ui::checkbox_with_label::<Self, _, Self, _>(
763 Empty::new(),
764 &theme.welcome.checkbox,
765 rpc_trace_enabled,
766 id.0,
767 cx,
768 move |this, enabled, cx| {
769 this.toggle_logging_for_server(id, enabled, cx);
770 },
771 )
772 .flex_float(),
773 )
774 .align_children_center()
775 .contained()
776 .with_style(style.container)
777 .constrained()
778 .with_height(theme.toolbar_dropdown_menu.row_height)
779 })
780 .with_cursor_style(CursorStyle::PointingHand)
781 .on_click(MouseButton::Left, move |_, view, cx| {
782 view.show_rpc_trace_for_server(id, cx);
783 }),
784 )
785 }
786}
787
788impl Entity for LogStore {
789 type Event = ();
790}
791
792impl Entity for LspLogView {
793 type Event = editor::Event;
794}
795
796impl Entity for LspLogToolbarItemView {
797 type Event = ();
798}