use std::{ cell::RefCell, collections::{BTreeMap, HashMap}, path::{Path, PathBuf}, rc::Rc, time::{Duration, Instant},}; use sysinfo::{Pid, ProcessRefreshKind, RefreshKind, System}; use language::language_settings::{EditPredictionProvider, all_language_settings}; use client::proto;use collections::HashSet;use editor::{Editor, EditorEvent};use gpui::{Corner, Entity, Subscription, Task, WeakEntity, actions};use language::{BinaryStatus, BufferId, ServerHealth};use lsp::{LanguageServerId, LanguageServerName, LanguageServerSelector};use project::{ LspStore, LspStoreEvent, Worktree, lsp_store::log_store::GlobalLogStore, project_settings::ProjectSettings,};use settings::{Settings as _, SettingsStore};use ui::{ ContextMenu, ContextMenuEntry, Indicator, PopoverMenu, PopoverMenuHandle, Tooltip, prelude::*,}; use util::{ResultExt, paths::PathExt, rel_path::RelPath};use workspace::{StatusItemView, Workspace}; use crate::lsp_log_view; actions!( lsp_tool, [ /// Toggles the language server tool menu. ToggleMenu ]); pub struct LspButton { server_state: Entity<LanguageServerState>, popover_menu_handle: PopoverMenuHandle<ContextMenu>, lsp_menu: Option<Entity<ContextMenu>>, lsp_menu_refresh: Task<()>, _subscriptions: Vec<Subscription>,} struct LanguageServerState { items: Vec<LspMenuItem>, workspace: WeakEntity<Workspace>, lsp_store: WeakEntity<LspStore>, active_editor: Option<ActiveEditor>, language_servers: LanguageServers, process_memory_cache: Rc<RefCell<ProcessMemoryCache>>,} impl std::fmt::Debug for LanguageServerState { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("LanguageServerState") .field("items", &self.items) .field("workspace", &self.workspace) .field("lsp_store", &self.lsp_store) .field("active_editor", &self.active_editor) .field("language_servers", &self.language_servers) .finish_non_exhaustive() }} const PROCESS_MEMORY_CACHE_DURATION: Duration = Duration::from_secs(5); struct ProcessMemoryCache { system: System, memory_usage: HashMap<u32, u64>, last_refresh: Option<Instant>,} impl ProcessMemoryCache { fn new() -> Self { Self { system: System::new(), memory_usage: HashMap::new(), last_refresh: None, } } fn get_memory_usage(&mut self, process_id: u32) -> u64 { let cache_expired = self .last_refresh .map(|last| last.elapsed() >= PROCESS_MEMORY_CACHE_DURATION) .unwrap_or(true); if cache_expired { let refresh_kind = RefreshKind::nothing() .with_processes(ProcessRefreshKind::nothing().without_tasks().with_memory()); self.system.refresh_specifics(refresh_kind); self.memory_usage.clear(); self.last_refresh = Some(Instant::now()); } if let Some(&memory) = self.memory_usage.get(&process_id) { return memory; } let root_pid = Pid::from_u32(process_id); let parent_map: HashMap<Pid, Pid> = self .system .processes() .iter() .filter_map(|(&pid, process)| Some((pid, process.parent()?))) .collect(); let total_memory = self .system .processes() .iter() .filter(|(pid, _)| self.is_descendant_of(**pid, root_pid, &parent_map)) .map(|(_, process)| process.memory()) .sum(); self.memory_usage.insert(process_id, total_memory); total_memory } fn is_descendant_of(&self, pid: Pid, root_pid: Pid, parent_map: &HashMap<Pid, Pid>) -> bool { let mut current = pid; let mut visited = HashSet::default(); while current != root_pid { if !visited.insert(current) { return false; } match parent_map.get(¤t) { Some(&parent) => current = parent, None => return false, } } true }} struct ActiveEditor { editor: WeakEntity<Editor>, _editor_subscription: Subscription, editor_buffers: HashSet<BufferId>,} impl std::fmt::Debug for ActiveEditor { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("ActiveEditor") .field("editor", &self.editor) .field("editor_buffers", &self.editor_buffers) .finish_non_exhaustive() }} #[derive(Debug, Default, Clone)]struct LanguageServers { health_statuses: HashMap<LanguageServerId, LanguageServerHealthStatus>, binary_statuses: HashMap<LanguageServerName, LanguageServerBinaryStatus>, servers_per_buffer_abs_path: HashMap<PathBuf, ServersForPath>,} #[derive(Debug, Clone)]struct ServersForPath { servers: HashMap<LanguageServerId, Option<LanguageServerName>>, worktree: Option<WeakEntity<Worktree>>,} #[derive(Debug, Clone)]struct LanguageServerHealthStatus { name: LanguageServerName, health: Option<(Option<SharedString>, ServerHealth)>,} #[derive(Debug, Clone)]struct LanguageServerBinaryStatus { status: BinaryStatus, message: Option<SharedString>,} #[derive(Debug, Clone)]struct ServerInfo { name: LanguageServerName, id: LanguageServerId, health: Option<ServerHealth>, binary_status: Option<LanguageServerBinaryStatus>, message: Option<SharedString>,} impl ServerInfo { fn server_selector(&self) -> LanguageServerSelector { LanguageServerSelector::Id(self.id) } fn can_stop(&self) -> bool { self.binary_status.as_ref().is_none_or(|status| { matches!(status.status, BinaryStatus::None | BinaryStatus::Starting) }) }} impl LanguageServerHealthStatus { fn health(&self) -> Option<ServerHealth> { self.health.as_ref().map(|(_, health)| *health) } fn message(&self) -> Option<SharedString> { self.health .as_ref() .and_then(|(message, _)| message.clone()) }} impl LanguageServerState { fn fill_menu(&self, mut menu: ContextMenu, cx: &mut Context<Self>) -> ContextMenu { let lsp_logs = cx .try_global::<GlobalLogStore>() .map(|lsp_logs| lsp_logs.0.clone()); let Some(lsp_logs) = lsp_logs else { return menu; }; let server_metadata = self .lsp_store .update(cx, |lsp_store, _| { lsp_store .language_server_statuses() .map(|(server_id, status)| { ( server_id, ( status.server_version.clone(), status.binary.as_ref().map(|b| b.path.clone()), status.process_id, ), ) }) .collect::<HashMap<_, _>>() }) .unwrap_or_default(); let process_memory_cache = self.process_memory_cache.clone(); let mut first_button_encountered = false; for item in &self.items { if let LspMenuItem::ToggleServersButton { restart } = item { let label = if *restart { "Restart All Servers" } else { "Stop All Servers" }; let restart = *restart; let button = ContextMenuEntry::new(label).handler({ let state = cx.entity(); move |_, cx| { let lsp_store = state.read(cx).lsp_store.clone(); lsp_store .update(cx, |lsp_store, cx| { if restart { lsp_store.restart_all_language_servers(cx); } else { lsp_store.stop_all_language_servers(cx); } }) .ok(); } }); if !first_button_encountered { menu = menu.separator(); first_button_encountered = true; } menu = menu.item(button); continue; } else if let LspMenuItem::Header { header, separator } = item { menu = menu .when(*separator, |menu| menu.separator()) .when_some(header.as_ref(), |menu, header| menu.header(header)); continue; } let Some(server_info) = item.server_info() else { continue; }; let server_selector = server_info.server_selector(); let is_remote = self .lsp_store .update(cx, |lsp_store, _| lsp_store.as_remote().is_some()) .unwrap_or(false); let has_logs = is_remote || lsp_logs.read(cx).has_server_logs(&server_selector); let (status_color, status_label) = server_info .binary_status .as_ref() .and_then(|binary_status| match binary_status.status { BinaryStatus::None => None, BinaryStatus::CheckingForUpdate | BinaryStatus::Downloading | BinaryStatus::Starting => Some((Color::Modified, "Starting…")), BinaryStatus::Stopping | BinaryStatus::Stopped => { Some((Color::Disabled, "Stopped")) } BinaryStatus::Failed { .. } => Some((Color::Error, "Error")), }) .or_else(|| { Some(match server_info.health? { ServerHealth::Ok => (Color::Success, "Running"), ServerHealth::Warning => (Color::Warning, "Warning"), ServerHealth::Error => (Color::Error, "Error"), }) }) .unwrap_or((Color::Success, "Running")); let message = server_info .message .as_ref() .or_else(|| server_info.binary_status.as_ref()?.message.as_ref()) .cloned(); let (server_version, binary_path, process_id) = server_metadata .get(&server_info.id) .map(|(version, path, process_id)| { ( version.clone(), path.as_ref() .map(|p| SharedString::from(p.compact().to_string_lossy().to_string())), *process_id, ) }) .unwrap_or((None, None, None)); let truncated_message = message.as_ref().and_then(|message| { message .lines() .filter(|line| !line.trim().is_empty()) .map(SharedString::new) .next() }); let submenu_server_name = server_info.name.clone(); let submenu_server_info = server_info.clone(); menu = menu.submenu_with_colored_icon( server_info.name.0.clone(), IconName::Circle, status_color, { let lsp_logs = lsp_logs.clone(); let message = message.clone(); let server_selector = server_selector.clone(); let workspace = self.workspace.clone(); let lsp_store = self.lsp_store.clone(); let state = cx.entity().downgrade(); let can_stop = submenu_server_info.can_stop(); let process_memory_cache = process_memory_cache.clone(); move |menu, _window, _cx| { let mut submenu = menu; if let Some(ref message) = message { let workspace_for_message = workspace.clone(); let message_for_handler = message.clone(); let server_name_for_message = submenu_server_name.clone(); submenu = submenu.entry("View Message", None, move |window, cx| { let Some(create_buffer) = workspace_for_message .update(cx, |workspace, cx| { workspace.project().update(cx, |project, cx| { project.create_buffer(None, false, cx) }) }) .ok() else { return; }; let window_handle = window.window_handle(); let workspace = workspace_for_message.clone(); let message = message_for_handler.clone(); let server_name = server_name_for_message.clone(); cx.spawn(async move |cx| { let buffer = create_buffer.await?; buffer.update(cx, |buffer, cx| { buffer.edit( [( 0..0, format!( "Language server {server_name}:\n\n{message}" ), )], None, cx, ); buffer.set_capability(language::Capability::ReadOnly, cx); }); workspace.update(cx, |workspace, cx| { window_handle.update(cx, |_, window, cx| { workspace.add_item_to_active_pane( Box::new(cx.new(|cx| { let mut editor = Editor::for_buffer( buffer, None, window, cx, ); editor.set_read_only(true); editor })), None, true, window, cx, ); }) })??; anyhow::Ok(()) }) .detach(); }); } if has_logs { let lsp_logs_for_debug = lsp_logs.clone(); let workspace_for_debug = workspace.clone(); let server_selector_for_debug = server_selector.clone(); submenu = submenu.entry("View Logs", None, move |window, cx| { lsp_log_view::open_server_trace( &lsp_logs_for_debug, workspace_for_debug.clone(), server_selector_for_debug.clone(), window, cx, ); }); } let state_for_restart = state.clone(); let workspace_for_restart = workspace.clone(); let lsp_store_for_restart = lsp_store.clone(); let server_name_for_restart = submenu_server_name.clone(); submenu = submenu.entry("Restart Server", None, move |_window, cx| { let Some(workspace) = workspace_for_restart.upgrade() else { return; }; let project = workspace.read(cx).project().clone(); let path_style = project.read(cx).path_style(cx); let buffer_store = project.read(cx).buffer_store().clone(); let buffers = state_for_restart .update(cx, |state, cx| { let server_buffers = state .language_servers .servers_per_buffer_abs_path .iter() .filter_map(|(abs_path, servers)| { // Check if this server is associated with this path let has_server = servers.servers.values().any(|name| { name.as_ref() == Some(&server_name_for_restart) }); if !has_server { return None; } let worktree = servers.worktree.as_ref()?.upgrade()?; let worktree_ref = worktree.read(cx); let relative_path = abs_path .strip_prefix(&worktree_ref.abs_path()) .ok()?; let relative_path = RelPath::new(relative_path, path_style) .log_err()?; let entry = worktree_ref.entry_for_path(&relative_path)?; let project_path = project.read(cx).path_for_entry(entry.id, cx)?; buffer_store.read(cx).get_by_path(&project_path) }) .collect::<Vec<_>>(); if server_buffers.is_empty() { state .language_servers .servers_per_buffer_abs_path .iter() .filter_map(|(abs_path, servers)| { let worktree = servers.worktree.as_ref()?.upgrade()?.read(cx); let relative_path = abs_path .strip_prefix(&worktree.abs_path()) .ok()?; let relative_path = RelPath::new(relative_path, path_style) .log_err()?; let entry = worktree.entry_for_path(&relative_path)?;Lines 1–500 of 1338. Load more
use std::{ cell::RefCell, collections::{BTreeMap, HashMap}, path::{Path, PathBuf}, rc::Rc, time::{Duration, Instant}, }; use sysinfo::{Pid, ProcessRefreshKind, RefreshKind, System}; use language::language_settings::{EditPredictionProvider, all_language_settings}; use client::proto; use collections::HashSet; use editor::{Editor, EditorEvent}; use gpui::{Corner, Entity, Subscription, Task, WeakEntity, actions}; use language::{BinaryStatus, BufferId, ServerHealth}; use lsp::{LanguageServerId, LanguageServerName, LanguageServerSelector}; use project::{ LspStore, LspStoreEvent, Worktree, lsp_store::log_store::GlobalLogStore, project_settings::ProjectSettings, }; use settings::{Settings as _, SettingsStore}; use ui::{ ContextMenu, ContextMenuEntry, Indicator, PopoverMenu, PopoverMenuHandle, Tooltip, prelude::*, }; use util::{ResultExt, paths::PathExt, rel_path::RelPath}; use workspace::{StatusItemView, Workspace}; use crate::lsp_log_view; actions!( lsp_tool, [ /// Toggles the language server tool menu. ToggleMenu ] ); pub struct LspButton { server_state: Entity<LanguageServerState>, popover_menu_handle: PopoverMenuHandle<ContextMenu>, lsp_menu: Option<Entity<ContextMenu>>, lsp_menu_refresh: Task<()>, _subscriptions: Vec<Subscription>, } struct LanguageServerState { items: Vec<LspMenuItem>, workspace: WeakEntity<Workspace>, lsp_store: WeakEntity<LspStore>, active_editor: Option<ActiveEditor>, language_servers: LanguageServers, process_memory_cache: Rc<RefCell<ProcessMemoryCache>>, } impl std::fmt::Debug for LanguageServerState { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("LanguageServerState") .field("items", &self.items) .field("workspace", &self.workspace) .field("lsp_store", &self.lsp_store) .field("active_editor", &self.active_editor) .field("language_servers", &self.language_servers) .finish_non_exhaustive() } } const PROCESS_MEMORY_CACHE_DURATION: Duration = Duration::from_secs(5); struct ProcessMemoryCache { system: System, memory_usage: HashMap<u32, u64>, last_refresh: Option<Instant>, } impl ProcessMemoryCache { fn new() -> Self { Self { system: System::new(), memory_usage: HashMap::new(), last_refresh: None, } } fn get_memory_usage(&mut self, process_id: u32) -> u64 { let cache_expired = self .last_refresh .map(|last| last.elapsed() >= PROCESS_MEMORY_CACHE_DURATION) .unwrap_or(true); if cache_expired { let refresh_kind = RefreshKind::nothing() .with_processes(ProcessRefreshKind::nothing().without_tasks().with_memory()); self.system.refresh_specifics(refresh_kind); self.memory_usage.clear(); self.last_refresh = Some(Instant::now()); } if let Some(&memory) = self.memory_usage.get(&process_id) { return memory; } let root_pid = Pid::from_u32(process_id); let parent_map: HashMap<Pid, Pid> = self .system .processes() .iter() .filter_map(|(&pid, process)| Some((pid, process.parent()?))) .collect(); let total_memory = self .system .processes() .iter() .filter(|(pid, _)| self.is_descendant_of(**pid, root_pid, &parent_map)) .map(|(_, process)| process.memory()) .sum(); self.memory_usage.insert(process_id, total_memory); total_memory } fn is_descendant_of(&self, pid: Pid, root_pid: Pid, parent_map: &HashMap<Pid, Pid>) -> bool { let mut current = pid; let mut visited = HashSet::default(); while current != root_pid { if !visited.insert(current) { return false; } match parent_map.get(¤t) { Some(&parent) => current = parent, None => return false, } } true } } struct ActiveEditor { editor: WeakEntity<Editor>, _editor_subscription: Subscription, editor_buffers: HashSet<BufferId>, } impl std::fmt::Debug for ActiveEditor { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("ActiveEditor") .field("editor", &self.editor) .field("editor_buffers", &self.editor_buffers) .finish_non_exhaustive() } } #[derive(Debug, Default, Clone)] struct LanguageServers { health_statuses: HashMap<LanguageServerId, LanguageServerHealthStatus>, binary_statuses: HashMap<LanguageServerName, LanguageServerBinaryStatus>, servers_per_buffer_abs_path: HashMap<PathBuf, ServersForPath>, } #[derive(Debug, Clone)] struct ServersForPath { servers: HashMap<LanguageServerId, Option<LanguageServerName>>, worktree: Option<WeakEntity<Worktree>>, } #[derive(Debug, Clone)] struct LanguageServerHealthStatus { name: LanguageServerName, health: Option<(Option<SharedString>, ServerHealth)>, } #[derive(Debug, Clone)] struct LanguageServerBinaryStatus { status: BinaryStatus, message: Option<SharedString>, } #[derive(Debug, Clone)] struct ServerInfo { name: LanguageServerName, id: LanguageServerId, health: Option<ServerHealth>, binary_status: Option<LanguageServerBinaryStatus>, message: Option<SharedString>, } impl ServerInfo { fn server_selector(&self) -> LanguageServerSelector { LanguageServerSelector::Id(self.id) } fn can_stop(&self) -> bool { self.binary_status.as_ref().is_none_or(|status| { matches!(status.status, BinaryStatus::None | BinaryStatus::Starting) }) } } impl LanguageServerHealthStatus { fn health(&self) -> Option<ServerHealth> { self.health.as_ref().map(|(_, health)| *health) } fn message(&self) -> Option<SharedString> { self.health .as_ref() .and_then(|(message, _)| message.clone()) } } impl LanguageServerState { fn fill_menu(&self, mut menu: ContextMenu, cx: &mut Context<Self>) -> ContextMenu { let lsp_logs = cx .try_global::<GlobalLogStore>() .map(|lsp_logs| lsp_logs.0.clone()); let Some(lsp_logs) = lsp_logs else { return menu; }; let server_metadata = self .lsp_store .update(cx, |lsp_store, _| { lsp_store .language_server_statuses() .map(|(server_id, status)| { ( server_id, ( status.server_version.clone(), status.binary.as_ref().map(|b| b.path.clone()), status.process_id, ), ) }) .collect::<HashMap<_, _>>() }) .unwrap_or_default(); let process_memory_cache = self.process_memory_cache.clone(); let mut first_button_encountered = false; for item in &self.items { if let LspMenuItem::ToggleServersButton { restart } = item { let label = if *restart { "Restart All Servers" } else { "Stop All Servers" }; let restart = *restart; let button = ContextMenuEntry::new(label).handler({ let state = cx.entity(); move |_, cx| { let lsp_store = state.read(cx).lsp_store.clone(); lsp_store .update(cx, |lsp_store, cx| { if restart { lsp_store.restart_all_language_servers(cx); } else { lsp_store.stop_all_language_servers(cx); } }) .ok(); } }); if !first_button_encountered { menu = menu.separator(); first_button_encountered = true; } menu = menu.item(button); continue; } else if let LspMenuItem::Header { header, separator } = item { menu = menu .when(*separator, |menu| menu.separator()) .when_some(header.as_ref(), |menu, header| menu.header(header)); continue; } let Some(server_info) = item.server_info() else { continue; }; let server_selector = server_info.server_selector(); let is_remote = self .lsp_store .update(cx, |lsp_store, _| lsp_store.as_remote().is_some()) .unwrap_or(false); let has_logs = is_remote || lsp_logs.read(cx).has_server_logs(&server_selector); let (status_color, status_label) = server_info .binary_status .as_ref() .and_then(|binary_status| match binary_status.status { BinaryStatus::None => None, BinaryStatus::CheckingForUpdate | BinaryStatus::Downloading | BinaryStatus::Starting => Some((Color::Modified, "Starting…")), BinaryStatus::Stopping | BinaryStatus::Stopped => { Some((Color::Disabled, "Stopped")) } BinaryStatus::Failed { .. } => Some((Color::Error, "Error")), }) .or_else(|| { Some(match server_info.health? { ServerHealth::Ok => (Color::Success, "Running"), ServerHealth::Warning => (Color::Warning, "Warning"), ServerHealth::Error => (Color::Error, "Error"), }) }) .unwrap_or((Color::Success, "Running")); let message = server_info .message .as_ref() .or_else(|| server_info.binary_status.as_ref()?.message.as_ref()) .cloned(); let (server_version, binary_path, process_id) = server_metadata .get(&server_info.id) .map(|(version, path, process_id)| { ( version.clone(), path.as_ref() .map(|p| SharedString::from(p.compact().to_string_lossy().to_string())), *process_id, ) }) .unwrap_or((None, None, None)); let truncated_message = message.as_ref().and_then(|message| { message .lines() .filter(|line| !line.trim().is_empty()) .map(SharedString::new) .next() }); let submenu_server_name = server_info.name.clone(); let submenu_server_info = server_info.clone(); menu = menu.submenu_with_colored_icon( server_info.name.0.clone(), IconName::Circle, status_color, { let lsp_logs = lsp_logs.clone(); let message = message.clone(); let server_selector = server_selector.clone(); let workspace = self.workspace.clone(); let lsp_store = self.lsp_store.clone(); let state = cx.entity().downgrade(); let can_stop = submenu_server_info.can_stop(); let process_memory_cache = process_memory_cache.clone(); move |menu, _window, _cx| { let mut submenu = menu; if let Some(ref message) = message { let workspace_for_message = workspace.clone(); let message_for_handler = message.clone(); let server_name_for_message = submenu_server_name.clone(); submenu = submenu.entry("View Message", None, move |window, cx| { let Some(create_buffer) = workspace_for_message .update(cx, |workspace, cx| { workspace.project().update(cx, |project, cx| { project.create_buffer(None, false, cx) }) }) .ok() else { return; }; let window_handle = window.window_handle(); let workspace = workspace_for_message.clone(); let message = message_for_handler.clone(); let server_name = server_name_for_message.clone(); cx.spawn(async move |cx| { let buffer = create_buffer.await?; buffer.update(cx, |buffer, cx| { buffer.edit( [( 0..0, format!( "Language server {server_name}:\n\n{message}" ), )], None, cx, ); buffer.set_capability(language::Capability::ReadOnly, cx); }); workspace.update(cx, |workspace, cx| { window_handle.update(cx, |_, window, cx| { workspace.add_item_to_active_pane( Box::new(cx.new(|cx| { let mut editor = Editor::for_buffer( buffer, None, window, cx, ); editor.set_read_only(true); editor })), None, true, window, cx, ); }) })??; anyhow::Ok(()) }) .detach(); }); } if has_logs { let lsp_logs_for_debug = lsp_logs.clone(); let workspace_for_debug = workspace.clone(); let server_selector_for_debug = server_selector.clone(); submenu = submenu.entry("View Logs", None, move |window, cx| { lsp_log_view::open_server_trace( &lsp_logs_for_debug, workspace_for_debug.clone(), server_selector_for_debug.clone(), window, cx, ); }); } let state_for_restart = state.clone(); let workspace_for_restart = workspace.clone(); let lsp_store_for_restart = lsp_store.clone(); let server_name_for_restart = submenu_server_name.clone(); submenu = submenu.entry("Restart Server", None, move |_window, cx| { let Some(workspace) = workspace_for_restart.upgrade() else { return; }; let project = workspace.read(cx).project().clone(); let path_style = project.read(cx).path_style(cx); let buffer_store = project.read(cx).buffer_store().clone(); let buffers = state_for_restart .update(cx, |state, cx| { let server_buffers = state .language_servers .servers_per_buffer_abs_path .iter() .filter_map(|(abs_path, servers)| { // Check if this server is associated with this path let has_server = servers.servers.values().any(|name| { name.as_ref() == Some(&server_name_for_restart) }); if !has_server { return None; } let worktree = servers.worktree.as_ref()?.upgrade()?; let worktree_ref = worktree.read(cx); let relative_path = abs_path .strip_prefix(&worktree_ref.abs_path()) .ok()?; let relative_path = RelPath::new(relative_path, path_style) .log_err()?; let entry = worktree_ref.entry_for_path(&relative_path)?; let project_path = project.read(cx).path_for_entry(entry.id, cx)?; buffer_store.read(cx).get_by_path(&project_path) }) .collect::<Vec<_>>(); if server_buffers.is_empty() { state .language_servers .servers_per_buffer_abs_path .iter() .filter_map(|(abs_path, servers)| { let worktree = servers.worktree.as_ref()?.upgrade()?.read(cx); let relative_path = abs_path .strip_prefix(&worktree.abs_path()) .ok()?; let relative_path = RelPath::new(relative_path, path_style) .log_err()?; let entry = worktree.entry_for_path(&relative_path)?;