From 39e8944dcc3a1b1b3ce49075b5f36c90f4b3840d Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Mon, 9 Dec 2024 14:10:11 +0100 Subject: [PATCH] language_tools: Split LSP log view selector into two (#21742) This should make it easier to interact with LSP log view when there are multiple language servers. I often find the current UI clunky when I have over 5 servers running (which isn't uncommon with multiple projects open) https://github.com/user-attachments/assets/2ecaf17f-4b40-4c8f-aa6f-03b437a3d979 Closes #ISSUE Release Notes: - N/A --- crates/language_tools/src/lsp_log.rs | 388 +++++++++++++++------------ 1 file changed, 215 insertions(+), 173 deletions(-) diff --git a/crates/language_tools/src/lsp_log.rs b/crates/language_tools/src/lsp_log.rs index 2e2c0caf40eec3..892745f4d31b23 100644 --- a/crates/language_tools/src/lsp_log.rs +++ b/crates/language_tools/src/lsp_log.rs @@ -1145,19 +1145,28 @@ impl Render for LspLogToolbarItemView { None } }); - + let available_language_servers: Vec<_> = menu_rows + .iter() + .map(|row| { + ( + row.server_id, + row.server_name.clone(), + row.worktree_root_name.clone(), + row.selected_entry, + ) + }) + .collect(); let log_toolbar_view = cx.view().clone(); let lsp_menu = PopoverMenu::new("LspLogView") .anchor(AnchorCorner::TopLeft) .trigger(Button::new( "language_server_menu_header", current_server + .as_ref() .map(|row| { Cow::Owned(format!( - "{} ({}) - {}", - row.server_name.0, - row.worktree_root_name, - row.selected_entry.label() + "{} ({})", + row.server_name.0, row.worktree_root_name, )) }) .unwrap_or_else(|| "No server selected".into()), @@ -1165,36 +1174,71 @@ impl Render for LspLogToolbarItemView { .menu({ let log_view = log_view.clone(); move |cx| { - let menu_rows = menu_rows.clone(); let log_view = log_view.clone(); - let log_toolbar_view = log_toolbar_view.clone(); - ContextMenu::build(cx, move |mut menu, cx| { - for (ix, row) in menu_rows.into_iter().enumerate() { - let server_selected = Some(row.server_id) == current_server_id; - menu = menu - .header(format!( - "{} ({})", - row.server_name.0, row.worktree_root_name - )) - .entry( - SERVER_LOGS, - None, - cx.handler_for(&log_view, move |view, cx| { - view.show_logs_for_server(row.server_id, cx); - }), - ); - // We do not support tracing for remote language servers right now - if row.server_kind.is_remote() { - continue; - } + ContextMenu::build(cx, |mut menu, cx| { + for (server_id, name, worktree_root, active_entry_kind) in + available_language_servers.iter() + { + let label = format!("{} ({})", name, worktree_root); + let server_id = *server_id; + let active_entry_kind = *active_entry_kind; menu = menu.entry( - SERVER_TRACE, + label, None, cx.handler_for(&log_view, move |view, cx| { - view.show_trace_for_server(row.server_id, cx); + view.current_server_id = Some(server_id); + view.active_entry_kind = active_entry_kind; + match view.active_entry_kind { + LogKind::Rpc => { + view.toggle_rpc_trace_for_server(server_id, true, cx); + view.show_rpc_trace_for_server(server_id, cx); + } + LogKind::Trace => view.show_trace_for_server(server_id, cx), + LogKind::Logs => view.show_logs_for_server(server_id, cx), + LogKind::Capabilities => { + view.show_capabilities_for_server(server_id, cx) + } + } + cx.notify(); }), ); - menu = menu.custom_entry( + } + menu + }) + .into() + } + }); + let view_selector = current_server.map(|server| { + let server_id = server.server_id; + let is_remote = server.server_kind.is_remote(); + let rpc_trace_enabled = server.rpc_trace_enabled; + let log_view = log_view.clone(); + PopoverMenu::new("LspViewSelector") + .anchor(AnchorCorner::TopLeft) + .trigger(Button::new( + "language_server_menu_header", + server.selected_entry.label(), + )) + .menu(move |cx| { + let log_toolbar_view = log_toolbar_view.clone(); + let log_view = log_view.clone(); + Some(ContextMenu::build(cx, move |this, cx| { + this.entry( + SERVER_LOGS, + None, + cx.handler_for(&log_view, move |view, cx| { + view.show_logs_for_server(server_id, cx); + }), + ) + .when(!is_remote, |this| { + this.entry( + SERVER_TRACE, + None, + cx.handler_for(&log_view, move |view, cx| { + view.show_trace_for_server(server_id, cx); + }), + ) + .custom_entry( { let log_toolbar_view = log_toolbar_view.clone(); move |cx| { @@ -1205,8 +1249,8 @@ impl Render for LspLogToolbarItemView { .child( div().child( Checkbox::new( - ix, - if row.rpc_trace_enabled { + "LspLogEnableRpcTrace", + if rpc_trace_enabled { Selection::Selected } else { Selection::Unselected @@ -1220,9 +1264,7 @@ impl Render for LspLogToolbarItemView { Selection::Selected ); view.toggle_rpc_logging_for_server( - row.server_id, - enabled, - cx, + server_id, enabled, cx, ); cx.stop_propagation(); }, @@ -1233,167 +1275,167 @@ impl Render for LspLogToolbarItemView { } }, cx.handler_for(&log_view, move |view, cx| { - view.show_rpc_trace_for_server(row.server_id, cx); - }), - ); - if server_selected && row.selected_entry == LogKind::Rpc { - let selected_ix = menu.select_last(); - // Each language server has: - // 1. A title. - // 2. Server logs. - // 3. Server trace. - // 4. RPC messages. - // 5. Server capabilities - // Thus, if nth server's RPC is selected, the index of selected entry should match this formula - let _expected_index = ix * 5 + 3; - debug_assert_eq!( - Some(_expected_index), - selected_ix, - "Could not scroll to a just added LSP menu item" - ); - } - menu = menu.entry( - SERVER_CAPABILITIES, - None, - cx.handler_for(&log_view, move |view, cx| { - view.show_capabilities_for_server(row.server_id, cx); + view.show_rpc_trace_for_server(server_id, cx); }), - ); - } - menu - }) - .into() - } - }); - + ) + }) + .entry( + SERVER_CAPABILITIES, + None, + cx.handler_for(&log_view, move |view, cx| { + view.show_capabilities_for_server(server_id, cx); + }), + ) + })) + }) + }); h_flex() .size_full() - .child(lsp_menu) + .justify_between() .child( - div() - .child( - Button::new("clear_log_button", "Clear").on_click(cx.listener( - |this, _, cx| { - if let Some(log_view) = this.log_view.as_ref() { - log_view.update(cx, |log_view, cx| { - log_view.editor.update(cx, |editor, cx| { - editor.set_read_only(false); - editor.clear(cx); - editor.set_read_only(true); - }); - }) - } - }, - )), - ) - .ml_2(), - ) - .child(log_view.update(cx, |this, _| match this.active_entry_kind { - LogKind::Trace => { - let log_view = log_view.clone(); - div().child( - PopoverMenu::new("lsp-trace-level-menu") - .anchor(AnchorCorner::TopLeft) - .trigger(Button::new( - "language_server_trace_level_selector", - "Trace level", - )) - .menu({ - let log_view = log_view.clone(); - - move |cx| { - let id = log_view.read(cx).current_server_id?; - - let trace_level = log_view.update(cx, |this, cx| { - this.log_store.update(cx, |this, _| { - Some(this.get_language_server_state(id)?.trace_level) - }) - })?; - - ContextMenu::build(cx, |mut menu, _| { + h_flex() + .child(lsp_menu) + .children(view_selector) + .child(log_view.update(cx, |this, _| match this.active_entry_kind { + LogKind::Trace => { + let log_view = log_view.clone(); + div().child( + PopoverMenu::new("lsp-trace-level-menu") + .anchor(AnchorCorner::TopLeft) + .trigger(Button::new( + "language_server_trace_level_selector", + "Trace level", + )) + .menu({ let log_view = log_view.clone(); - for (option, label) in [ - (TraceValue::Off, "Off"), - (TraceValue::Messages, "Messages"), - (TraceValue::Verbose, "Verbose"), - ] { - menu = menu.entry(label, None, { + move |cx| { + let id = log_view.read(cx).current_server_id?; + + let trace_level = log_view.update(cx, |this, cx| { + this.log_store.update(cx, |this, _| { + Some( + this.get_language_server_state(id)? + .trace_level, + ) + }) + })?; + + ContextMenu::build(cx, |mut menu, _| { let log_view = log_view.clone(); - move |cx| { - log_view.update(cx, |this, cx| { - if let Some(id) = this.current_server_id { - this.update_trace_level(id, option, cx); + + for (option, label) in [ + (TraceValue::Off, "Off"), + (TraceValue::Messages, "Messages"), + (TraceValue::Verbose, "Verbose"), + ] { + menu = menu.entry(label, None, { + let log_view = log_view.clone(); + move |cx| { + log_view.update(cx, |this, cx| { + if let Some(id) = + this.current_server_id + { + this.update_trace_level( + id, option, cx, + ); + } + }); } }); + if option == trace_level { + menu.select_last(); + } } - }); - if option == trace_level { - menu.select_last(); - } + + menu + }) + .into() } + }), + ) + } + LogKind::Logs => { + let log_view = log_view.clone(); + div().child( + PopoverMenu::new("lsp-log-level-menu") + .anchor(AnchorCorner::TopLeft) + .trigger(Button::new( + "language_server_log_level_selector", + "Log level", + )) + .menu({ + let log_view = log_view.clone(); - menu - }) - .into() - } - }), - ) - } - LogKind::Logs => { - let log_view = log_view.clone(); - div().child( - PopoverMenu::new("lsp-log-level-menu") - .anchor(AnchorCorner::TopLeft) - .trigger(Button::new( - "language_server_log_level_selector", - "Log level", - )) - .menu({ - let log_view = log_view.clone(); - - move |cx| { - let id = log_view.read(cx).current_server_id?; - - let log_level = log_view.update(cx, |this, cx| { - this.log_store.update(cx, |this, _| { - Some(this.get_language_server_state(id)?.log_level) - }) - })?; + move |cx| { + let id = log_view.read(cx).current_server_id?; - ContextMenu::build(cx, |mut menu, _| { - let log_view = log_view.clone(); + let log_level = log_view.update(cx, |this, cx| { + this.log_store.update(cx, |this, _| { + Some( + this.get_language_server_state(id)? + .log_level, + ) + }) + })?; - for (option, label) in [ - (MessageType::LOG, "Log"), - (MessageType::INFO, "Info"), - (MessageType::WARNING, "Warning"), - (MessageType::ERROR, "Error"), - ] { - menu = menu.entry(label, None, { + ContextMenu::build(cx, |mut menu, _| { let log_view = log_view.clone(); - move |cx| { - log_view.update(cx, |this, cx| { - if let Some(id) = this.current_server_id { - this.update_log_level(id, option, cx); + + for (option, label) in [ + (MessageType::LOG, "Log"), + (MessageType::INFO, "Info"), + (MessageType::WARNING, "Warning"), + (MessageType::ERROR, "Error"), + ] { + menu = menu.entry(label, None, { + let log_view = log_view.clone(); + move |cx| { + log_view.update(cx, |this, cx| { + if let Some(id) = + this.current_server_id + { + this.update_log_level( + id, option, cx, + ); + } + }); } }); + if option == log_level { + menu.select_last(); + } } - }); - if option == log_level { - menu.select_last(); - } - } - menu + menu + }) + .into() + } + }), + ) + } + _ => div(), + })), + ) + .child( + div() + .child( + Button::new("clear_log_button", "Clear").on_click(cx.listener( + |this, _, cx| { + if let Some(log_view) = this.log_view.as_ref() { + log_view.update(cx, |log_view, cx| { + log_view.editor.update(cx, |editor, cx| { + editor.set_read_only(false); + editor.clear(cx); + editor.set_read_only(true); + }); }) - .into() } - }), + }, + )), ) - } - _ => div(), - })) + .ml_2(), + ) } }