use collections::VecDeque;use edit_prediction::EditPredictionStore;use editor::{Editor, EditorEvent, MultiBufferOffset, actions::MoveToEnd, scroll::Autoscroll};use gpui::{ Anchor, App, Context, Entity, EventEmitter, FocusHandle, Focusable, IntoElement, ParentElement, Render, Styled, Subscription, Task, WeakEntity, Window, actions, div,};use itertools::Itertools as _;use language::{LanguageServerId, language_settings::SoftWrap};use lsp::{ LanguageServer, LanguageServerName, LanguageServerSelector, MessageType, SetTraceParams, TraceValue, notification::SetTrace,};use project::{ LanguageServerStatus, Project, lsp_store::log_store::{self, Event, LanguageServerKind, LogKind, LogStore, Message}, search::SearchQuery,};use proto::toggle_lsp_logs::LogType;use settings::SeedQuerySetting;use std::{any::TypeId, borrow::Cow, sync::Arc};use ui::{Checkbox, ContextMenu, PopoverMenu, ToggleState, prelude::*};use util::ResultExt as _;use workspace::{ SplitDirection, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace, WorkspaceId, item::{Item, ItemHandle}, searchable::{Direction, SearchEvent, SearchToken, SearchableItem, SearchableItemHandle},}; use crate::get_or_create_tool; pub fn open( log_store: &Entity<LogStore>, workspace: WeakEntity<Workspace>, server: LanguageServerSelector, window: &mut Window, cx: &mut App,) { log_store.update(cx, |_, cx| { cx.spawn_in(window, async move |log_store, cx| { let Some(log_store) = log_store.upgrade() else { return; }; workspace .update_in(cx, |workspace, window, cx| { let project = workspace.project().clone(); let tool_log_store = log_store.clone(); let log_view = get_or_create_tool( workspace, SplitDirection::Right, window, cx, move |window, cx| LspLogView::new(project, tool_log_store, window, cx), ); log_view.update(cx, |log_view, cx| { let server_id = match server { LanguageServerSelector::Id(id) => Some(id), LanguageServerSelector::Name(name) => { log_store.read(cx).language_servers.iter().find_map( |(id, state)| { if state.name.as_ref() == Some(&name) { Some(*id) } else { None } }, ) } }; if let Some(server_id) = server_id { log_view.show_logs_for_server(server_id, window, cx); } }); }) .ok(); }) .detach(); })} pub struct LspLogView { pub(crate) editor: Entity<Editor>, editor_subscriptions: Vec<Subscription>, log_store: Entity<LogStore>, current_server_id: Option<LanguageServerId>, active_entry_kind: LogKind, project: Entity<Project>, focus_handle: FocusHandle, _log_store_subscriptions: Vec<Subscription>,} pub struct LspLogToolbarItemView { log_view: Option<Entity<LspLogView>>, _log_view_subscription: Option<Subscription>,} #[derive(Clone, Debug, PartialEq)]pub(crate) struct LogMenuItem { pub server_id: LanguageServerId, pub server_name: LanguageServerName, pub worktree_root_name: String, pub rpc_trace_enabled: bool, pub selected_entry: LogKind, pub trace_level: lsp::TraceValue, pub server_kind: LanguageServerKind,} actions!( dev, [ /// Opens the language server protocol logs viewer. OpenLanguageServerLogs ]); pub fn init(on_headless_host: bool, cx: &mut App) { let log_store = log_store::init(on_headless_host, cx); cx.observe_new(move |workspace: &mut Workspace, _, cx| { log_store.update(cx, |store, cx| { store.add_project(workspace.project(), cx); }); let log_store = log_store.clone(); workspace.register_action(move |workspace, _: &OpenLanguageServerLogs, window, cx| { let log_store = log_store.clone(); let project = workspace.project().clone(); get_or_create_tool( workspace, SplitDirection::Right, window, cx, move |window, cx| LspLogView::new(project, log_store, window, cx), ); }); }) .detach();} impl LspLogView { pub fn new( project: Entity<Project>, log_store: Entity<LogStore>, window: &mut Window, cx: &mut Context<Self>, ) -> Self { let server_id = log_store .read(cx) .language_servers .iter() .find(|(_, server)| server.kind.project() == Some(&project.downgrade())) .map(|(id, _)| *id); let weak_project = project.downgrade(); let model_changes_subscription = cx.observe_in(&log_store, window, move |this, store, window, cx| { let first_server_id_for_project = store.read(cx).server_ids_for_project(&weak_project).next(); if let Some(current_lsp) = this.current_server_id { if !store.read(cx).language_servers.contains_key(¤t_lsp) && let Some(server_id) = first_server_id_for_project { match this.active_entry_kind { LogKind::Rpc => this.show_rpc_trace_for_server(server_id, window, cx), LogKind::Trace => this.show_trace_for_server(server_id, window, cx), LogKind::Logs => this.show_logs_for_server(server_id, window, cx), LogKind::ServerInfo => this.show_server_info(server_id, window, cx), } } } else if let Some(server_id) = first_server_id_for_project { match this.active_entry_kind { LogKind::Rpc => this.show_rpc_trace_for_server(server_id, window, cx), LogKind::Trace => this.show_trace_for_server(server_id, window, cx), LogKind::Logs => this.show_logs_for_server(server_id, window, cx), LogKind::ServerInfo => this.show_server_info(server_id, window, cx), } } cx.notify(); }); let events_subscriptions = cx.subscribe_in( &log_store, window, move |log_view, _, e, window, cx| match e { Event::NewServerLogEntry { id, kind, text } => { if log_view.current_server_id == Some(*id) && LogKind::from_server_log_type(kind) == log_view.active_entry_kind { log_view.editor.update(cx, |editor, cx| { editor.set_read_only(false); let last_offset = editor.buffer().read(cx).len(cx); let newest_cursor_is_at_end = editor .selections .newest::<MultiBufferOffset>(&editor.display_snapshot(cx)) .start >= last_offset; editor.edit( vec![ (last_offset..last_offset, text.as_str()), (last_offset..last_offset, "\n"), ], cx, ); if text.len() > 1024 { let b = editor.buffer().read(cx).as_singleton().unwrap().read(cx); let fold_offset = b.as_rope().ceil_char_boundary(last_offset.0 + 1024); editor.fold_ranges( vec![ MultiBufferOffset(fold_offset) ..MultiBufferOffset(b.as_rope().len()), ], false, window, cx, ); } if newest_cursor_is_at_end { editor.request_autoscroll(Autoscroll::bottom(), cx); } editor.set_read_only(true); }); } } }, ); let (editor, editor_subscriptions) = Self::editor_for_logs(String::new(), window, cx); let focus_handle = cx.focus_handle(); let focus_subscription = cx.on_focus(&focus_handle, window, |log_view, window, cx| { window.focus(&log_view.editor.focus_handle(cx), cx); }); cx.on_release(|log_view, cx| { log_view.log_store.update(cx, |log_store, cx| { for (server_id, state) in &log_store.language_servers { if let Some(log_kind) = state.toggled_log_kind { if let Some(log_type) = log_type(log_kind) { send_toggle_log_message(state, *server_id, false, log_type, cx); } } } }); }) .detach(); let mut lsp_log_view = Self { focus_handle, editor, editor_subscriptions, project, log_store, current_server_id: None, active_entry_kind: LogKind::Logs, _log_store_subscriptions: vec![ model_changes_subscription, events_subscriptions, focus_subscription, ], }; if let Some(server_id) = server_id { lsp_log_view.show_logs_for_server(server_id, window, cx); } lsp_log_view } fn editor_for_logs( log_contents: String, window: &mut Window, cx: &mut Context<Self>, ) -> (Entity<Editor>, Vec<Subscription>) { let editor = initialize_new_editor(log_contents, true, window, cx); let editor_subscription = cx.subscribe( &editor, |_, _, event: &EditorEvent, cx: &mut Context<LspLogView>| cx.emit(event.clone()), ); let search_subscription = cx.subscribe( &editor, |_, _, event: &SearchEvent, cx: &mut Context<LspLogView>| cx.emit(event.clone()), ); (editor, vec![editor_subscription, search_subscription]) } fn editor_for_server_info( info: ServerInfo, window: &mut Window, cx: &mut Context<Self>, ) -> (Entity<Editor>, Vec<Subscription>) { let server_info = format!( "* Server: {NAME} (id {ID}) * Version: {VERSION} * Binary: {BINARY} * Registered workspace folders:{WORKSPACE_FOLDERS} * Capabilities: {CAPABILITIES} * Configuration: {CONFIGURATION}", NAME = info.status.name, ID = info.id, VERSION = info .status .server_version .as_ref() .map(|version| version.as_ref()) .unwrap_or("Unknown"), BINARY = info .status .binary .as_ref() .map_or_else(|| "Unknown".to_string(), |binary| format!("{:#?}", binary)), WORKSPACE_FOLDERS = info .status .workspace_folders .iter() .filter_map(|uri| uri.to_file_path().ok()) .map(|path| path.to_string_lossy().into_owned()) .join(", "), CAPABILITIES = serde_json::to_string_pretty(&info.capabilities) .unwrap_or_else(|e| format!("Failed to serialize capabilities: {e}")), CONFIGURATION = info .status .configuration .map(|configuration| serde_json::to_string_pretty(&configuration)) .transpose() .unwrap_or_else(|e| Some(format!("Failed to serialize configuration: {e}"))) .unwrap_or_else(|| "Unknown".to_string()), ); let editor = initialize_new_editor(server_info, false, window, cx); let editor_subscription = cx.subscribe( &editor, |_, _, event: &EditorEvent, cx: &mut Context<LspLogView>| cx.emit(event.clone()), ); let search_subscription = cx.subscribe( &editor, |_, _, event: &SearchEvent, cx: &mut Context<LspLogView>| cx.emit(event.clone()), ); (editor, vec![editor_subscription, search_subscription]) } pub(crate) fn try_ensure_copilot_for_project(&self, cx: &mut App) { self.log_store.update(cx, |this, cx| { let copilot = EditPredictionStore::try_global(cx) .and_then(|store| store.read(cx).copilot_for_project(&self.project))?; let server = copilot.read(cx).language_server()?.clone(); let log_subscription = this.copilot_state_for_project(&self.project.downgrade()); if let Some(subscription_slot @ None) = log_subscription { let weak_lsp_store = cx.weak_entity(); let server_id = server.server_id(); let name = LanguageServerName::new_static("copilot"); *subscription_slot = Some(server.on_notification::<lsp::notification::LogMessage, _>( move |params, cx| { weak_lsp_store .update(cx, |lsp_store, cx| { lsp_store.add_language_server_log( server_id, MessageType::LOG, ¶ms.message, cx, ); }) .ok(); }, )); this.add_language_server( LanguageServerKind::Global, server.server_id(), Some(name), None, Some(server.clone()), cx, ); } Some(()) }); } pub(crate) fn menu_items(&self, cx: &mut App) -> Option<Vec<LogMenuItem>> { self.try_ensure_copilot_for_project(cx); let log_store = self.log_store.read(cx); let unknown_server = LanguageServerName::new_static("unknown server"); let mut rows = log_store .language_servers .iter() .map(|(server_id, state)| match &state.kind { LanguageServerKind::Local { .. } | LanguageServerKind::Remote { .. } | LanguageServerKind::LocalSsh { .. } => { let worktree_root_name = state .worktree_id .and_then(|id| self.project.read(cx).worktree_for_id(id, cx)) .map(|worktree| worktree.read(cx).root_name_str().to_string()) .unwrap_or_else(|| "Unknown worktree".to_string()); LogMenuItem { server_id: *server_id, server_name: state.name.clone().unwrap_or(unknown_server.clone()), server_kind: state.kind.clone(), worktree_root_name, rpc_trace_enabled: state.rpc_state.is_some(), selected_entry: self.active_entry_kind, trace_level: lsp::TraceValue::Off, } } LanguageServerKind::Global => LogMenuItem { server_id: *server_id, server_name: state.name.clone().unwrap_or(unknown_server.clone()), server_kind: state.kind.clone(), worktree_root_name: "supplementary".to_string(), rpc_trace_enabled: state.rpc_state.is_some(), selected_entry: self.active_entry_kind, trace_level: lsp::TraceValue::Off, }, }) .chain( self.project .read(cx) .supplementary_language_servers(cx) .filter_map(|(server_id, name)| { let state = log_store.language_servers.get(&server_id)?; Some(LogMenuItem { server_id, server_name: name, server_kind: state.kind.clone(), worktree_root_name: "supplementary".to_string(), rpc_trace_enabled: state.rpc_state.is_some(), selected_entry: self.active_entry_kind, trace_level: lsp::TraceValue::Off, }) }), ) .collect::<Vec<_>>(); rows.sort_by_key(|row| row.server_id); rows.dedup_by_key(|row| row.server_id); Some(rows) } fn show_logs_for_server( &mut self, server_id: LanguageServerId, window: &mut Window, cx: &mut Context<Self>, ) { let typ = self .log_store .read(cx) .language_servers .get(&server_id) .map(|v| v.log_level) .unwrap_or(MessageType::LOG); let log_contents = self .log_store .read(cx) .server_logs(server_id) .map(|v| log_contents(v, typ)); if let Some(log_contents) = log_contents { self.current_server_id = Some(server_id); self.active_entry_kind = LogKind::Logs; let (editor, editor_subscriptions) = Self::editor_for_logs(log_contents, window, cx); self.editor = editor; self.editor_subscriptions = editor_subscriptions; cx.notify(); } self.editor.read(cx).focus_handle(cx).focus(window, cx); self.log_store.update(cx, |log_store, cx| { let state = log_store.get_language_server_state(server_id)?; state.toggled_log_kind = Some(LogKind::Logs); send_toggle_log_message(state, server_id, true, LogType::Log, cx); Some(()) }); } fn update_log_level( &self, server_id: LanguageServerId, level: MessageType, window: &mut Window, cx: &mut Context<Self>, ) { let log_contents = self.log_store.update(cx, |this, _| { if let Some(state) = this.get_language_server_state(server_id) { state.log_level = level; } this.server_logs(server_id).map(|v| log_contents(v, level)) }); if let Some(log_contents) = log_contents { self.editor.update(cx, |editor, cx| { editor.set_text(log_contents, window, cx); editor.move_to_end(&MoveToEnd, window, cx);Lines 1–500 of 1383. Load more
use collections::VecDeque; use edit_prediction::EditPredictionStore; use editor::{Editor, EditorEvent, MultiBufferOffset, actions::MoveToEnd, scroll::Autoscroll}; use gpui::{ Anchor, App, Context, Entity, EventEmitter, FocusHandle, Focusable, IntoElement, ParentElement, Render, Styled, Subscription, Task, WeakEntity, Window, actions, div, }; use itertools::Itertools as _; use language::{LanguageServerId, language_settings::SoftWrap}; use lsp::{ LanguageServer, LanguageServerName, LanguageServerSelector, MessageType, SetTraceParams, TraceValue, notification::SetTrace, }; use project::{ LanguageServerStatus, Project, lsp_store::log_store::{self, Event, LanguageServerKind, LogKind, LogStore, Message}, search::SearchQuery, }; use proto::toggle_lsp_logs::LogType; use settings::SeedQuerySetting; use std::{any::TypeId, borrow::Cow, sync::Arc}; use ui::{Checkbox, ContextMenu, PopoverMenu, ToggleState, prelude::*}; use util::ResultExt as _; use workspace::{ SplitDirection, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace, WorkspaceId, item::{Item, ItemHandle}, searchable::{Direction, SearchEvent, SearchToken, SearchableItem, SearchableItemHandle}, }; use crate::get_or_create_tool; pub fn open( log_store: &Entity<LogStore>, workspace: WeakEntity<Workspace>, server: LanguageServerSelector, window: &mut Window, cx: &mut App, ) { log_store.update(cx, |_, cx| { cx.spawn_in(window, async move |log_store, cx| { let Some(log_store) = log_store.upgrade() else { return; }; workspace .update_in(cx, |workspace, window, cx| { let project = workspace.project().clone(); let tool_log_store = log_store.clone(); let log_view = get_or_create_tool( workspace, SplitDirection::Right, window, cx, move |window, cx| LspLogView::new(project, tool_log_store, window, cx), ); log_view.update(cx, |log_view, cx| { let server_id = match server { LanguageServerSelector::Id(id) => Some(id), LanguageServerSelector::Name(name) => { log_store.read(cx).language_servers.iter().find_map( |(id, state)| { if state.name.as_ref() == Some(&name) { Some(*id) } else { None } }, ) } }; if let Some(server_id) = server_id { log_view.show_logs_for_server(server_id, window, cx); } }); }) .ok(); }) .detach(); }) } pub struct LspLogView { pub(crate) editor: Entity<Editor>, editor_subscriptions: Vec<Subscription>, log_store: Entity<LogStore>, current_server_id: Option<LanguageServerId>, active_entry_kind: LogKind, project: Entity<Project>, focus_handle: FocusHandle, _log_store_subscriptions: Vec<Subscription>, } pub struct LspLogToolbarItemView { log_view: Option<Entity<LspLogView>>, _log_view_subscription: Option<Subscription>, } #[derive(Clone, Debug, PartialEq)] pub(crate) struct LogMenuItem { pub server_id: LanguageServerId, pub server_name: LanguageServerName, pub worktree_root_name: String, pub rpc_trace_enabled: bool, pub selected_entry: LogKind, pub trace_level: lsp::TraceValue, pub server_kind: LanguageServerKind, } actions!( dev, [ /// Opens the language server protocol logs viewer. OpenLanguageServerLogs ] ); pub fn init(on_headless_host: bool, cx: &mut App) { let log_store = log_store::init(on_headless_host, cx); cx.observe_new(move |workspace: &mut Workspace, _, cx| { log_store.update(cx, |store, cx| { store.add_project(workspace.project(), cx); }); let log_store = log_store.clone(); workspace.register_action(move |workspace, _: &OpenLanguageServerLogs, window, cx| { let log_store = log_store.clone(); let project = workspace.project().clone(); get_or_create_tool( workspace, SplitDirection::Right, window, cx, move |window, cx| LspLogView::new(project, log_store, window, cx), ); }); }) .detach(); } impl LspLogView { pub fn new( project: Entity<Project>, log_store: Entity<LogStore>, window: &mut Window, cx: &mut Context<Self>, ) -> Self { let server_id = log_store .read(cx) .language_servers .iter() .find(|(_, server)| server.kind.project() == Some(&project.downgrade())) .map(|(id, _)| *id); let weak_project = project.downgrade(); let model_changes_subscription = cx.observe_in(&log_store, window, move |this, store, window, cx| { let first_server_id_for_project = store.read(cx).server_ids_for_project(&weak_project).next(); if let Some(current_lsp) = this.current_server_id { if !store.read(cx).language_servers.contains_key(¤t_lsp) && let Some(server_id) = first_server_id_for_project { match this.active_entry_kind { LogKind::Rpc => this.show_rpc_trace_for_server(server_id, window, cx), LogKind::Trace => this.show_trace_for_server(server_id, window, cx), LogKind::Logs => this.show_logs_for_server(server_id, window, cx), LogKind::ServerInfo => this.show_server_info(server_id, window, cx), } } } else if let Some(server_id) = first_server_id_for_project { match this.active_entry_kind { LogKind::Rpc => this.show_rpc_trace_for_server(server_id, window, cx), LogKind::Trace => this.show_trace_for_server(server_id, window, cx), LogKind::Logs => this.show_logs_for_server(server_id, window, cx), LogKind::ServerInfo => this.show_server_info(server_id, window, cx), } } cx.notify(); }); let events_subscriptions = cx.subscribe_in( &log_store, window, move |log_view, _, e, window, cx| match e { Event::NewServerLogEntry { id, kind, text } => { if log_view.current_server_id == Some(*id) && LogKind::from_server_log_type(kind) == log_view.active_entry_kind { log_view.editor.update(cx, |editor, cx| { editor.set_read_only(false); let last_offset = editor.buffer().read(cx).len(cx); let newest_cursor_is_at_end = editor .selections .newest::<MultiBufferOffset>(&editor.display_snapshot(cx)) .start >= last_offset; editor.edit( vec![ (last_offset..last_offset, text.as_str()), (last_offset..last_offset, "\n"), ], cx, ); if text.len() > 1024 { let b = editor.buffer().read(cx).as_singleton().unwrap().read(cx); let fold_offset = b.as_rope().ceil_char_boundary(last_offset.0 + 1024); editor.fold_ranges( vec![ MultiBufferOffset(fold_offset) ..MultiBufferOffset(b.as_rope().len()), ], false, window, cx, ); } if newest_cursor_is_at_end { editor.request_autoscroll(Autoscroll::bottom(), cx); } editor.set_read_only(true); }); } } }, ); let (editor, editor_subscriptions) = Self::editor_for_logs(String::new(), window, cx); let focus_handle = cx.focus_handle(); let focus_subscription = cx.on_focus(&focus_handle, window, |log_view, window, cx| { window.focus(&log_view.editor.focus_handle(cx), cx); }); cx.on_release(|log_view, cx| { log_view.log_store.update(cx, |log_store, cx| { for (server_id, state) in &log_store.language_servers { if let Some(log_kind) = state.toggled_log_kind { if let Some(log_type) = log_type(log_kind) { send_toggle_log_message(state, *server_id, false, log_type, cx); } } } }); }) .detach(); let mut lsp_log_view = Self { focus_handle, editor, editor_subscriptions, project, log_store, current_server_id: None, active_entry_kind: LogKind::Logs, _log_store_subscriptions: vec![ model_changes_subscription, events_subscriptions, focus_subscription, ], }; if let Some(server_id) = server_id { lsp_log_view.show_logs_for_server(server_id, window, cx); } lsp_log_view } fn editor_for_logs( log_contents: String, window: &mut Window, cx: &mut Context<Self>, ) -> (Entity<Editor>, Vec<Subscription>) { let editor = initialize_new_editor(log_contents, true, window, cx); let editor_subscription = cx.subscribe( &editor, |_, _, event: &EditorEvent, cx: &mut Context<LspLogView>| cx.emit(event.clone()), ); let search_subscription = cx.subscribe( &editor, |_, _, event: &SearchEvent, cx: &mut Context<LspLogView>| cx.emit(event.clone()), ); (editor, vec![editor_subscription, search_subscription]) } fn editor_for_server_info( info: ServerInfo, window: &mut Window, cx: &mut Context<Self>, ) -> (Entity<Editor>, Vec<Subscription>) { let server_info = format!( "* Server: {NAME} (id {ID}) * Version: {VERSION} * Binary: {BINARY} * Registered workspace folders: {WORKSPACE_FOLDERS} * Capabilities: {CAPABILITIES} * Configuration: {CONFIGURATION}", NAME = info.status.name, ID = info.id, VERSION = info .status .server_version .as_ref() .map(|version| version.as_ref()) .unwrap_or("Unknown"), BINARY = info .status .binary .as_ref() .map_or_else(|| "Unknown".to_string(), |binary| format!("{:#?}", binary)), WORKSPACE_FOLDERS = info .status .workspace_folders .iter() .filter_map(|uri| uri.to_file_path().ok()) .map(|path| path.to_string_lossy().into_owned()) .join(", "), CAPABILITIES = serde_json::to_string_pretty(&info.capabilities) .unwrap_or_else(|e| format!("Failed to serialize capabilities: {e}")), CONFIGURATION = info .status .configuration .map(|configuration| serde_json::to_string_pretty(&configuration)) .transpose() .unwrap_or_else(|e| Some(format!("Failed to serialize configuration: {e}"))) .unwrap_or_else(|| "Unknown".to_string()), ); let editor = initialize_new_editor(server_info, false, window, cx); let editor_subscription = cx.subscribe( &editor, |_, _, event: &EditorEvent, cx: &mut Context<LspLogView>| cx.emit(event.clone()), ); let search_subscription = cx.subscribe( &editor, |_, _, event: &SearchEvent, cx: &mut Context<LspLogView>| cx.emit(event.clone()), ); (editor, vec![editor_subscription, search_subscription]) } pub(crate) fn try_ensure_copilot_for_project(&self, cx: &mut App) { self.log_store.update(cx, |this, cx| { let copilot = EditPredictionStore::try_global(cx) .and_then(|store| store.read(cx).copilot_for_project(&self.project))?; let server = copilot.read(cx).language_server()?.clone(); let log_subscription = this.copilot_state_for_project(&self.project.downgrade()); if let Some(subscription_slot @ None) = log_subscription { let weak_lsp_store = cx.weak_entity(); let server_id = server.server_id(); let name = LanguageServerName::new_static("copilot"); *subscription_slot = Some(server.on_notification::<lsp::notification::LogMessage, _>( move |params, cx| { weak_lsp_store .update(cx, |lsp_store, cx| { lsp_store.add_language_server_log( server_id, MessageType::LOG, ¶ms.message, cx, ); }) .ok(); }, )); this.add_language_server( LanguageServerKind::Global, server.server_id(), Some(name), None, Some(server.clone()), cx, ); } Some(()) }); } pub(crate) fn menu_items(&self, cx: &mut App) -> Option<Vec<LogMenuItem>> { self.try_ensure_copilot_for_project(cx); let log_store = self.log_store.read(cx); let unknown_server = LanguageServerName::new_static("unknown server"); let mut rows = log_store .language_servers .iter() .map(|(server_id, state)| match &state.kind { LanguageServerKind::Local { .. } | LanguageServerKind::Remote { .. } | LanguageServerKind::LocalSsh { .. } => { let worktree_root_name = state .worktree_id .and_then(|id| self.project.read(cx).worktree_for_id(id, cx)) .map(|worktree| worktree.read(cx).root_name_str().to_string()) .unwrap_or_else(|| "Unknown worktree".to_string()); LogMenuItem { server_id: *server_id, server_name: state.name.clone().unwrap_or(unknown_server.clone()), server_kind: state.kind.clone(), worktree_root_name, rpc_trace_enabled: state.rpc_state.is_some(), selected_entry: self.active_entry_kind, trace_level: lsp::TraceValue::Off, } } LanguageServerKind::Global => LogMenuItem { server_id: *server_id, server_name: state.name.clone().unwrap_or(unknown_server.clone()), server_kind: state.kind.clone(), worktree_root_name: "supplementary".to_string(), rpc_trace_enabled: state.rpc_state.is_some(), selected_entry: self.active_entry_kind, trace_level: lsp::TraceValue::Off, }, }) .chain( self.project .read(cx) .supplementary_language_servers(cx) .filter_map(|(server_id, name)| { let state = log_store.language_servers.get(&server_id)?; Some(LogMenuItem { server_id, server_name: name, server_kind: state.kind.clone(), worktree_root_name: "supplementary".to_string(), rpc_trace_enabled: state.rpc_state.is_some(), selected_entry: self.active_entry_kind, trace_level: lsp::TraceValue::Off, }) }), ) .collect::<Vec<_>>(); rows.sort_by_key(|row| row.server_id); rows.dedup_by_key(|row| row.server_id); Some(rows) } fn show_logs_for_server( &mut self, server_id: LanguageServerId, window: &mut Window, cx: &mut Context<Self>, ) { let typ = self .log_store .read(cx) .language_servers .get(&server_id) .map(|v| v.log_level) .unwrap_or(MessageType::LOG); let log_contents = self .log_store .read(cx) .server_logs(server_id) .map(|v| log_contents(v, typ)); if let Some(log_contents) = log_contents { self.current_server_id = Some(server_id); self.active_entry_kind = LogKind::Logs; let (editor, editor_subscriptions) = Self::editor_for_logs(log_contents, window, cx); self.editor = editor; self.editor_subscriptions = editor_subscriptions; cx.notify(); } self.editor.read(cx).focus_handle(cx).focus(window, cx); self.log_store.update(cx, |log_store, cx| { let state = log_store.get_language_server_state(server_id)?; state.toggled_log_kind = Some(LogKind::Logs); send_toggle_log_message(state, server_id, true, LogType::Log, cx); Some(()) }); } fn update_log_level( &self, server_id: LanguageServerId, level: MessageType, window: &mut Window, cx: &mut Context<Self>, ) { let log_contents = self.log_store.update(cx, |this, _| { if let Some(state) = this.get_language_server_state(server_id) { state.log_level = level; } this.server_logs(server_id).map(|v| log_contents(v, level)) }); if let Some(log_contents) = log_contents { self.editor.update(cx, |editor, cx| { editor.set_text(log_contents, window, cx); editor.move_to_end(&MoveToEnd, window, cx);