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
180 .add_model(|cx| Buffer::new(0, cx.model_id() as u64, ""))
181 .clone(),
182 }
183 })
184 .log_buffer
185 .clone(),
186 )
187 }
188
189 fn add_language_server_log(
190 &mut self,
191 project: &ModelHandle<Project>,
192 id: LanguageServerId,
193 message: &str,
194 cx: &mut ModelContext<Self>,
195 ) -> Option<()> {
196 let buffer = self.add_language_server(&project, id, cx)?;
197 buffer.update(cx, |buffer, cx| {
198 let len = buffer.len();
199 let has_newline = message.ends_with("\n");
200 buffer.edit([(len..len, message)], None, cx);
201 if !has_newline {
202 let len = buffer.len();
203 buffer.edit([(len..len, "\n")], None, cx);
204 }
205 });
206 cx.notify();
207 Some(())
208 }
209
210 fn remove_language_server(
211 &mut self,
212 project: &ModelHandle<Project>,
213 id: LanguageServerId,
214 cx: &mut ModelContext<Self>,
215 ) -> Option<()> {
216 let project_state = self.projects.get_mut(&project.downgrade())?;
217 project_state.servers.remove(&id);
218 cx.notify();
219 Some(())
220 }
221
222 pub fn log_buffer_for_server(
223 &self,
224 project: &ModelHandle<Project>,
225 server_id: LanguageServerId,
226 ) -> Option<ModelHandle<Buffer>> {
227 let weak_project = project.downgrade();
228 let project_state = self.projects.get(&weak_project)?;
229 let server_state = project_state.servers.get(&server_id)?;
230 Some(server_state.log_buffer.clone())
231 }
232
233 pub fn enable_rpc_trace_for_language_server(
234 &mut self,
235 project: &ModelHandle<Project>,
236 server_id: LanguageServerId,
237 cx: &mut ModelContext<Self>,
238 ) -> Option<ModelHandle<Buffer>> {
239 let weak_project = project.downgrade();
240 let project_state = self.projects.get_mut(&weak_project)?;
241 let server_state = project_state.servers.get_mut(&server_id)?;
242 let server = project.read(cx).language_server_for_id(server_id)?;
243 let rpc_state = server_state.rpc_state.get_or_insert_with(|| {
244 let io_tx = self.io_tx.clone();
245 let language = project.read(cx).languages().language_for_name("JSON");
246 let buffer = cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, ""));
247 cx.spawn_weak({
248 let buffer = buffer.clone();
249 |_, mut cx| async move {
250 let language = language.await.ok();
251 buffer.update(&mut cx, |buffer, cx| {
252 buffer.set_language(language, cx);
253 });
254 }
255 })
256 .detach();
257
258 LanguageServerRpcState {
259 buffer,
260 last_message_kind: None,
261 _subscription: server.on_io(move |is_received, json| {
262 io_tx
263 .unbounded_send((weak_project, server_id, is_received, json.to_string()))
264 .ok();
265 }),
266 }
267 });
268 Some(rpc_state.buffer.clone())
269 }
270
271 pub fn disable_rpc_trace_for_language_server(
272 &mut self,
273 project: &ModelHandle<Project>,
274 server_id: LanguageServerId,
275 _: &mut ModelContext<Self>,
276 ) -> Option<()> {
277 let project = project.downgrade();
278 let project_state = self.projects.get_mut(&project)?;
279 let server_state = project_state.servers.get_mut(&server_id)?;
280 server_state.rpc_state.take();
281 Some(())
282 }
283
284 fn on_io(
285 &mut self,
286 project: WeakModelHandle<Project>,
287 language_server_id: LanguageServerId,
288 is_received: bool,
289 message: &str,
290 cx: &mut AppContext,
291 ) -> Option<()> {
292 let state = self
293 .projects
294 .get_mut(&project)?
295 .servers
296 .get_mut(&language_server_id)?
297 .rpc_state
298 .as_mut()?;
299 state.buffer.update(cx, |buffer, cx| {
300 let kind = if is_received {
301 MessageKind::Receive
302 } else {
303 MessageKind::Send
304 };
305 if state.last_message_kind != Some(kind) {
306 let len = buffer.len();
307 let line = match kind {
308 MessageKind::Send => SEND_LINE,
309 MessageKind::Receive => RECEIVE_LINE,
310 };
311 buffer.edit([(len..len, line)], None, cx);
312 state.last_message_kind = Some(kind);
313 }
314 let len = buffer.len();
315 buffer.edit([(len..len, message)], None, cx);
316 });
317 Some(())
318 }
319}
320
321impl LspLogView {
322 pub fn new(
323 project: ModelHandle<Project>,
324 log_store: ModelHandle<LogStore>,
325 cx: &mut ViewContext<Self>,
326 ) -> Self {
327 let server_id = log_store
328 .read(cx)
329 .projects
330 .get(&project.downgrade())
331 .and_then(|project| project.servers.keys().copied().next());
332 let buffer = cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, ""));
333 let mut this = Self {
334 editor: Self::editor_for_buffer(project.clone(), buffer, cx),
335 project,
336 log_store,
337 current_server_id: None,
338 is_showing_rpc_trace: false,
339 };
340 if let Some(server_id) = server_id {
341 this.show_logs_for_server(server_id, cx);
342 }
343 this
344 }
345
346 fn editor_for_buffer(
347 project: ModelHandle<Project>,
348 buffer: ModelHandle<Buffer>,
349 cx: &mut ViewContext<Self>,
350 ) -> ViewHandle<Editor> {
351 let editor = cx.add_view(|cx| {
352 let mut editor = Editor::for_buffer(buffer, Some(project), cx);
353 editor.set_read_only(true);
354 editor.move_to_end(&Default::default(), cx);
355 editor
356 });
357 cx.subscribe(&editor, |_, _, event, cx| cx.emit(event.clone()))
358 .detach();
359 editor
360 }
361
362 pub(crate) fn menu_items<'a>(&'a self, cx: &'a AppContext) -> Option<Vec<LogMenuItem>> {
363 let log_store = self.log_store.read(cx);
364 let state = log_store.projects.get(&self.project.downgrade())?;
365 let mut rows = self
366 .project
367 .read(cx)
368 .language_servers()
369 .filter_map(|(server_id, language_server_name, worktree_id)| {
370 let worktree = self.project.read(cx).worktree_for_id(worktree_id, cx)?;
371 let state = state.servers.get(&server_id)?;
372 Some(LogMenuItem {
373 server_id,
374 server_name: language_server_name,
375 worktree,
376 rpc_trace_enabled: state.rpc_state.is_some(),
377 rpc_trace_selected: self.is_showing_rpc_trace
378 && self.current_server_id == Some(server_id),
379 logs_selected: !self.is_showing_rpc_trace
380 && self.current_server_id == Some(server_id),
381 })
382 })
383 .collect::<Vec<_>>();
384 rows.sort_by_key(|row| row.server_id);
385 rows.dedup_by_key(|row| row.server_id);
386 Some(rows)
387 }
388
389 fn show_logs_for_server(&mut self, server_id: LanguageServerId, cx: &mut ViewContext<Self>) {
390 let buffer = self
391 .log_store
392 .read(cx)
393 .log_buffer_for_server(&self.project, server_id);
394 if let Some(buffer) = buffer {
395 self.current_server_id = Some(server_id);
396 self.is_showing_rpc_trace = false;
397 self.editor = Self::editor_for_buffer(self.project.clone(), buffer, cx);
398 cx.notify();
399 }
400 }
401
402 fn show_rpc_trace_for_server(
403 &mut self,
404 server_id: LanguageServerId,
405 cx: &mut ViewContext<Self>,
406 ) {
407 let buffer = self.log_store.update(cx, |log_set, cx| {
408 log_set.enable_rpc_trace_for_language_server(&self.project, server_id, cx)
409 });
410 if let Some(buffer) = buffer {
411 self.current_server_id = Some(server_id);
412 self.is_showing_rpc_trace = true;
413 self.editor = Self::editor_for_buffer(self.project.clone(), buffer, cx);
414 cx.notify();
415 }
416 }
417
418 fn toggle_rpc_trace_for_server(
419 &mut self,
420 server_id: LanguageServerId,
421 enabled: bool,
422 cx: &mut ViewContext<Self>,
423 ) {
424 self.log_store.update(cx, |log_store, cx| {
425 if enabled {
426 log_store.enable_rpc_trace_for_language_server(&self.project, server_id, cx);
427 } else {
428 log_store.disable_rpc_trace_for_language_server(&self.project, server_id, cx);
429 }
430 });
431 if !enabled && Some(server_id) == self.current_server_id {
432 self.show_logs_for_server(server_id, cx);
433 cx.notify();
434 }
435 }
436}
437
438impl View for LspLogView {
439 fn ui_name() -> &'static str {
440 "LspLogView"
441 }
442
443 fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
444 ChildView::new(&self.editor, cx).into_any()
445 }
446
447 fn focus_in(&mut self, _: gpui::AnyViewHandle, cx: &mut ViewContext<Self>) {
448 if cx.is_self_focused() {
449 cx.focus(&self.editor);
450 }
451 }
452}
453
454impl Item for LspLogView {
455 fn tab_content<V: 'static>(
456 &self,
457 _: Option<usize>,
458 style: &theme::Tab,
459 _: &AppContext,
460 ) -> AnyElement<V> {
461 Label::new("LSP Logs", style.label.clone()).into_any()
462 }
463
464 fn as_searchable(&self, handle: &ViewHandle<Self>) -> Option<Box<dyn SearchableItemHandle>> {
465 Some(Box::new(handle.clone()))
466 }
467}
468
469impl SearchableItem for LspLogView {
470 type Match = <Editor as SearchableItem>::Match;
471
472 fn to_search_event(
473 &mut self,
474 event: &Self::Event,
475 cx: &mut ViewContext<Self>,
476 ) -> Option<workspace::searchable::SearchEvent> {
477 self.editor
478 .update(cx, |editor, cx| editor.to_search_event(event, cx))
479 }
480
481 fn clear_matches(&mut self, cx: &mut ViewContext<Self>) {
482 self.editor.update(cx, |e, cx| e.clear_matches(cx))
483 }
484
485 fn update_matches(&mut self, matches: Vec<Self::Match>, cx: &mut ViewContext<Self>) {
486 self.editor
487 .update(cx, |e, cx| e.update_matches(matches, cx))
488 }
489
490 fn query_suggestion(&mut self, cx: &mut ViewContext<Self>) -> String {
491 self.editor.update(cx, |e, cx| e.query_suggestion(cx))
492 }
493
494 fn activate_match(
495 &mut self,
496 index: usize,
497 matches: Vec<Self::Match>,
498 cx: &mut ViewContext<Self>,
499 ) {
500 self.editor
501 .update(cx, |e, cx| e.activate_match(index, matches, cx))
502 }
503
504 fn select_matches(&mut self, matches: Vec<Self::Match>, cx: &mut ViewContext<Self>) {
505 self.editor
506 .update(cx, |e, cx| e.select_matches(matches, cx))
507 }
508
509 fn find_matches(
510 &mut self,
511 query: project::search::SearchQuery,
512 cx: &mut ViewContext<Self>,
513 ) -> gpui::Task<Vec<Self::Match>> {
514 self.editor.update(cx, |e, cx| e.find_matches(query, cx))
515 }
516
517 fn active_match_index(
518 &mut self,
519 matches: Vec<Self::Match>,
520 cx: &mut ViewContext<Self>,
521 ) -> Option<usize> {
522 self.editor
523 .update(cx, |e, cx| e.active_match_index(matches, cx))
524 }
525}
526
527impl ToolbarItemView for LspLogToolbarItemView {
528 fn set_active_pane_item(
529 &mut self,
530 active_pane_item: Option<&dyn ItemHandle>,
531 _: &mut ViewContext<Self>,
532 ) -> workspace::ToolbarItemLocation {
533 self.menu_open = false;
534 if let Some(item) = active_pane_item {
535 if let Some(log_view) = item.downcast::<LspLogView>() {
536 self.log_view = Some(log_view.clone());
537 return ToolbarItemLocation::PrimaryLeft {
538 flex: Some((1., false)),
539 };
540 }
541 }
542 self.log_view = None;
543 ToolbarItemLocation::Hidden
544 }
545}
546
547impl View for LspLogToolbarItemView {
548 fn ui_name() -> &'static str {
549 "LspLogView"
550 }
551
552 fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
553 let theme = theme::current(cx).clone();
554 let Some(log_view) = self.log_view.as_ref() else {
555 return Empty::new().into_any();
556 };
557 let log_view = log_view.read(cx);
558 let menu_rows = log_view.menu_items(cx).unwrap_or_default();
559
560 let current_server_id = log_view.current_server_id;
561 let current_server = current_server_id.and_then(|current_server_id| {
562 if let Ok(ix) = menu_rows.binary_search_by_key(¤t_server_id, |e| e.server_id) {
563 Some(menu_rows[ix].clone())
564 } else {
565 None
566 }
567 });
568
569 enum Menu {}
570
571 Stack::new()
572 .with_child(Self::render_language_server_menu_header(
573 current_server,
574 &theme,
575 cx,
576 ))
577 .with_children(if self.menu_open {
578 Some(
579 Overlay::new(
580 MouseEventHandler::new::<Menu, _>(0, cx, move |_, cx| {
581 Flex::column()
582 .with_children(menu_rows.into_iter().map(|row| {
583 Self::render_language_server_menu_item(
584 row.server_id,
585 row.server_name,
586 row.worktree,
587 row.rpc_trace_enabled,
588 row.logs_selected,
589 row.rpc_trace_selected,
590 &theme,
591 cx,
592 )
593 }))
594 .contained()
595 .with_style(theme.toolbar_dropdown_menu.container)
596 .constrained()
597 .with_width(400.)
598 .with_height(400.)
599 })
600 .on_down_out(MouseButton::Left, |_, this, cx| {
601 this.menu_open = false;
602 cx.notify()
603 }),
604 )
605 .with_hoverable(true)
606 .with_fit_mode(OverlayFitMode::SwitchAnchor)
607 .with_anchor_corner(AnchorCorner::TopLeft)
608 .with_z_index(999)
609 .aligned()
610 .bottom()
611 .left(),
612 )
613 } else {
614 None
615 })
616 .aligned()
617 .left()
618 .clipped()
619 .into_any()
620 }
621}
622
623const RPC_MESSAGES: &str = "RPC Messages";
624const SERVER_LOGS: &str = "Server Logs";
625
626impl LspLogToolbarItemView {
627 pub fn new() -> Self {
628 Self {
629 menu_open: false,
630 log_view: None,
631 }
632 }
633
634 fn toggle_menu(&mut self, cx: &mut ViewContext<Self>) {
635 self.menu_open = !self.menu_open;
636 cx.notify();
637 }
638
639 fn toggle_logging_for_server(
640 &mut self,
641 id: LanguageServerId,
642 enabled: bool,
643 cx: &mut ViewContext<Self>,
644 ) {
645 if let Some(log_view) = &self.log_view {
646 log_view.update(cx, |log_view, cx| {
647 log_view.toggle_rpc_trace_for_server(id, enabled, cx);
648 if !enabled && Some(id) == log_view.current_server_id {
649 log_view.show_logs_for_server(id, cx);
650 cx.notify();
651 }
652 });
653 }
654 cx.notify();
655 }
656
657 fn show_logs_for_server(&mut self, id: LanguageServerId, cx: &mut ViewContext<Self>) {
658 if let Some(log_view) = &self.log_view {
659 log_view.update(cx, |view, cx| view.show_logs_for_server(id, cx));
660 self.menu_open = false;
661 cx.notify();
662 }
663 }
664
665 fn show_rpc_trace_for_server(&mut self, id: LanguageServerId, cx: &mut ViewContext<Self>) {
666 if let Some(log_view) = &self.log_view {
667 log_view.update(cx, |view, cx| view.show_rpc_trace_for_server(id, cx));
668 self.menu_open = false;
669 cx.notify();
670 }
671 }
672
673 fn render_language_server_menu_header(
674 current_server: Option<LogMenuItem>,
675 theme: &Arc<Theme>,
676 cx: &mut ViewContext<Self>,
677 ) -> impl Element<Self> {
678 enum ToggleMenu {}
679 MouseEventHandler::new::<ToggleMenu, _>(0, cx, move |state, cx| {
680 let label: Cow<str> = current_server
681 .and_then(|row| {
682 let worktree = row.worktree.read(cx);
683 Some(
684 format!(
685 "{} ({}) - {}",
686 row.server_name.0,
687 worktree.root_name(),
688 if row.rpc_trace_selected {
689 RPC_MESSAGES
690 } else {
691 SERVER_LOGS
692 },
693 )
694 .into(),
695 )
696 })
697 .unwrap_or_else(|| "No server selected".into());
698 let style = theme.toolbar_dropdown_menu.header.style_for(state);
699 Label::new(label, style.text.clone())
700 .contained()
701 .with_style(style.container)
702 })
703 .with_cursor_style(CursorStyle::PointingHand)
704 .on_click(MouseButton::Left, move |_, view, cx| {
705 view.toggle_menu(cx);
706 })
707 }
708
709 fn render_language_server_menu_item(
710 id: LanguageServerId,
711 name: LanguageServerName,
712 worktree: ModelHandle<Worktree>,
713 rpc_trace_enabled: bool,
714 logs_selected: bool,
715 rpc_trace_selected: bool,
716 theme: &Arc<Theme>,
717 cx: &mut ViewContext<Self>,
718 ) -> impl Element<Self> {
719 enum ActivateLog {}
720 enum ActivateRpcTrace {}
721
722 Flex::column()
723 .with_child({
724 let style = &theme.toolbar_dropdown_menu.section_header;
725 Label::new(
726 format!("{} ({})", name.0, worktree.read(cx).root_name()),
727 style.text.clone(),
728 )
729 .contained()
730 .with_style(style.container)
731 .constrained()
732 .with_height(theme.toolbar_dropdown_menu.row_height)
733 })
734 .with_child(
735 MouseEventHandler::new::<ActivateLog, _>(id.0, cx, move |state, _| {
736 let style = theme
737 .toolbar_dropdown_menu
738 .item
739 .in_state(logs_selected)
740 .style_for(state);
741 Label::new(SERVER_LOGS, style.text.clone())
742 .contained()
743 .with_style(style.container)
744 .constrained()
745 .with_height(theme.toolbar_dropdown_menu.row_height)
746 })
747 .with_cursor_style(CursorStyle::PointingHand)
748 .on_click(MouseButton::Left, move |_, view, cx| {
749 view.show_logs_for_server(id, cx);
750 }),
751 )
752 .with_child(
753 MouseEventHandler::new::<ActivateRpcTrace, _>(id.0, cx, move |state, cx| {
754 let style = theme
755 .toolbar_dropdown_menu
756 .item
757 .in_state(rpc_trace_selected)
758 .style_for(state);
759 Flex::row()
760 .with_child(
761 Label::new(RPC_MESSAGES, style.text.clone())
762 .constrained()
763 .with_height(theme.toolbar_dropdown_menu.row_height),
764 )
765 .with_child(
766 ui::checkbox_with_label::<Self, _, Self, _>(
767 Empty::new(),
768 &theme.welcome.checkbox,
769 rpc_trace_enabled,
770 id.0,
771 cx,
772 move |this, enabled, cx| {
773 this.toggle_logging_for_server(id, enabled, cx);
774 },
775 )
776 .flex_float(),
777 )
778 .align_children_center()
779 .contained()
780 .with_style(style.container)
781 .constrained()
782 .with_height(theme.toolbar_dropdown_menu.row_height)
783 })
784 .with_cursor_style(CursorStyle::PointingHand)
785 .on_click(MouseButton::Left, move |_, view, cx| {
786 view.show_rpc_trace_for_server(id, cx);
787 }),
788 )
789 }
790}
791
792impl Entity for LogStore {
793 type Event = ();
794}
795
796impl Entity for LspLogView {
797 type Event = editor::Event;
798}
799
800impl Entity for LspLogToolbarItemView {
801 type Event = ();
802}