Compare commits

...
Sign in to create a new pull request.

2 commits

Author SHA1 Message Date
Lukas Wirth
557fbe5c68 Hack extensions language servers to record their IDs 2025-08-23 16:37:32 +02:00
Lukas Wirth
4292d883b0 Use language server ids for lsp tool 2025-08-23 12:46:50 +02:00
11 changed files with 219 additions and 144 deletions

View file

@ -56,6 +56,7 @@ pub struct ActivityIndicator {
#[derive(Debug)]
struct ServerStatus {
name: LanguageServerName,
id: LanguageServerId,
status: LanguageServerStatusUpdate,
}
@ -86,11 +87,12 @@ impl ActivityIndicator {
let this = cx.new(|cx| {
let mut status_events = languages.language_server_binary_statuses();
cx.spawn(async move |this, cx| {
while let Some((name, binary_status)) = status_events.next().await {
while let Some((id, name, binary_status)) = status_events.next().await {
this.update(cx, |this: &mut ActivityIndicator, cx| {
this.statuses.retain(|s| s.name != name);
this.statuses.retain(|s| s.id != id);
this.statuses.push(ServerStatus {
name,
id,
status: LanguageServerStatusUpdate::Binary(binary_status),
});
cx.notify();
@ -117,7 +119,13 @@ impl ActivityIndicator {
cx.subscribe(
&project.read(cx).lsp_store(),
|activity_indicator, _, event, cx| {
if let LspStoreEvent::LanguageServerUpdate { name, message, .. } = event {
if let LspStoreEvent::LanguageServerUpdate {
language_server_id,
name,
message,
..
} = event
{
if let proto::update_language_server::Variant::StatusUpdate(status_update) =
message
{
@ -180,9 +188,11 @@ impl ActivityIndicator {
};
activity_indicator.statuses.retain(|s| s.name != name);
activity_indicator
.statuses
.push(ServerStatus { name, status });
activity_indicator.statuses.push(ServerStatus {
name,
id: *language_server_id,
status,
});
}
cx.notify()
}

View file

@ -281,7 +281,7 @@ pub trait ExtensionLanguageServerProxy: Send + Sync + 'static {
fn update_language_server_status(
&self,
language_server_id: LanguageServerName,
language_server_name: LanguageServerName,
status: BinaryStatus,
);
}
@ -315,14 +315,14 @@ impl ExtensionLanguageServerProxy for ExtensionHostProxy {
fn update_language_server_status(
&self,
language_server_id: LanguageServerName,
language_server_name: LanguageServerName,
status: BinaryStatus,
) {
let Some(proxy) = self.language_server_proxy.read().clone() else {
return;
};
proxy.update_language_server_status(language_server_id, status)
proxy.update_language_server_status(language_server_name, status)
}
}

View file

@ -1361,11 +1361,12 @@ impl ExtensionStore {
for (manifest, wasm_extension) in &wasm_extensions {
let extension = Arc::new(wasm_extension.clone());
for (language_server_id, language_server_config) in &manifest.language_servers {
for (language_server_name, language_server_config) in &manifest.language_servers
{
for language in language_server_config.languages() {
this.proxy.register_language_server(
extension.clone(),
language_server_id.clone(),
language_server_name.clone(),
language.clone(),
);
}

View file

@ -12,7 +12,7 @@ use gpui::{AppContext as _, SemanticVersion, TestAppContext};
use http_client::{FakeHttpClient, Response};
use language::{BinaryStatus, LanguageMatcher, LanguageName, LanguageRegistry};
use language_extension::LspAccess;
use lsp::LanguageServerName;
use lsp::{LanguageServerId, LanguageServerName};
use node_runtime::NodeRuntime;
use parking_lot::Mutex;
use project::{DEFAULT_COMPLETION_CONTEXT, Project};
@ -737,18 +737,25 @@ async fn test_extension_store_with_test_extension(cx: &mut TestAppContext) {
],
[
(
LanguageServerId(0),
LanguageServerName::new_static("gleam"),
BinaryStatus::Starting
),
(
LanguageServerId(0),
LanguageServerName::new_static("gleam"),
BinaryStatus::CheckingForUpdate
),
(
LanguageServerId(0),
LanguageServerName::new_static("gleam"),
BinaryStatus::Downloading
),
(LanguageServerName::new_static("gleam"), BinaryStatus::None)
(
LanguageServerId(0),
LanguageServerName::new_static("gleam"),
BinaryStatus::None
)
]
);

View file

@ -177,21 +177,21 @@ impl HeadlessExtensionStore {
let wasm_extension: Arc<dyn Extension> =
Arc::new(WasmExtension::load(&extension_dir, &manifest, wasm_host.clone(), cx).await?);
for (language_server_id, language_server_config) in &manifest.language_servers {
for (language_server_name, language_server_config) in &manifest.language_servers {
for language in language_server_config.languages() {
this.update(cx, |this, _cx| {
this.loaded_language_servers
.entry(manifest.id.clone())
.or_default()
.push((language_server_id.clone(), language.clone()));
.push((language_server_name.clone(), language.clone()));
this.proxy.register_language_server(
wasm_extension.clone(),
language_server_id.clone(),
language_server_name.clone(),
language.clone(),
);
})?;
}
log::info!("Loaded language server: {}", language_server_id);
log::info!("Loaded language server: {}", language_server_name);
}
for (debug_adapter, meta) in &manifest.debug_adapters {

View file

@ -214,12 +214,20 @@ impl CachedLspAdapter {
delegate: Arc<dyn LspAdapterDelegate>,
toolchains: Option<Toolchain>,
binary_options: LanguageServerBinaryOptions,
server_id: LanguageServerId,
cx: &mut AsyncApp,
) -> Result<LanguageServerBinary> {
let cached_binary = self.cached_binary.lock().await;
self.adapter
.clone()
.get_language_server_command(delegate, toolchains, binary_options, cached_binary, cx)
.get_language_server_command(
delegate,
toolchains,
binary_options,
cached_binary,
server_id,
cx,
)
.await
}
@ -291,7 +299,12 @@ pub trait LspAdapterDelegate: Send + Sync {
fn http_client(&self) -> Arc<dyn HttpClient>;
fn worktree_id(&self) -> WorktreeId;
fn worktree_root_path(&self) -> &Path;
fn update_status(&self, language: LanguageServerName, status: BinaryStatus);
fn update_status(
&self,
language: LanguageServerId,
server_name: LanguageServerName,
status: BinaryStatus,
);
fn registered_lsp_adapters(&self) -> Vec<Arc<dyn LspAdapter>>;
async fn language_server_download_dir(&self, name: &LanguageServerName) -> Option<Arc<Path>>;
@ -315,6 +328,7 @@ pub trait LspAdapter: 'static + Send + Sync {
toolchains: Option<Toolchain>,
binary_options: LanguageServerBinaryOptions,
mut cached_binary: futures::lock::MutexGuard<'a, Option<LanguageServerBinary>>,
server_id: LanguageServerId,
cx: &'a mut AsyncApp,
) -> Pin<Box<dyn 'a + Future<Output = Result<LanguageServerBinary>>>> {
async move {
@ -330,27 +344,41 @@ pub trait LspAdapter: 'static + Send + Sync {
// because we don't want to download and overwrite our global one
// for each worktree we might have open.
if binary_options.allow_path_lookup
&& let Some(binary) = self.check_if_user_installed(delegate.as_ref(), toolchains, cx).await {
log::debug!(
"found user-installed language server for {}. path: {:?}, arguments: {:?}",
self.name().0,
binary.path,
binary.arguments
);
return Ok(binary);
}
&& let Some(binary) = self
.check_if_user_installed(delegate.as_ref(), toolchains, cx)
.await
{
log::debug!(
"found user-installed language server for {}. path: {:?}, arguments: {:?}",
self.name().0,
binary.path,
binary.arguments
);
return Ok(binary);
}
anyhow::ensure!(binary_options.allow_binary_download, "downloading language servers disabled");
anyhow::ensure!(
binary_options.allow_binary_download,
"downloading language servers disabled"
);
if let Some(cached_binary) = cached_binary.as_ref() {
return Ok(cached_binary.clone());
}
let Some(container_dir) = delegate.language_server_download_dir(&self.name()).await else {
let Some(container_dir) = delegate.language_server_download_dir(&self.name()).await
else {
anyhow::bail!("no language server download dir defined")
};
let mut binary = try_fetch_server_binary(self.as_ref(), &delegate, container_dir.to_path_buf(), cx).await;
let mut binary = try_fetch_server_binary(
self.as_ref(),
&delegate,
container_dir.to_path_buf(),
server_id,
cx,
)
.await;
if let Err(error) = binary.as_ref() {
if let Some(prev_downloaded_binary) = self
@ -358,7 +386,8 @@ pub trait LspAdapter: 'static + Send + Sync {
.await
{
log::info!(
"failed to fetch newest version of language server {:?}. error: {:?}, falling back to using {:?}",
"failed to fetch newest version of language server {:?}. \
error: {:?}, falling back to using {:?}",
self.name(),
error,
prev_downloaded_binary.path
@ -366,6 +395,7 @@ pub trait LspAdapter: 'static + Send + Sync {
binary = Ok(prev_downloaded_binary);
} else {
delegate.update_status(
server_id,
self.name(),
BinaryStatus::Failed {
error: format!("{error:?}"),
@ -594,6 +624,7 @@ async fn try_fetch_server_binary<L: LspAdapter + 'static + Send + Sync + ?Sized>
adapter: &L,
delegate: &Arc<dyn LspAdapterDelegate>,
container_dir: PathBuf,
server_id: LanguageServerId,
cx: &mut AsyncApp,
) -> Result<LanguageServerBinary> {
if let Some(task) = adapter.will_fetch_server(delegate, cx) {
@ -602,7 +633,7 @@ async fn try_fetch_server_binary<L: LspAdapter + 'static + Send + Sync + ?Sized>
let name = adapter.name();
log::debug!("fetching latest version of language server {:?}", name.0);
delegate.update_status(name.clone(), BinaryStatus::CheckingForUpdate);
delegate.update_status(server_id, adapter.name(), BinaryStatus::CheckingForUpdate);
let latest_version = adapter
.fetch_latest_server_version(delegate.as_ref())
@ -613,16 +644,16 @@ async fn try_fetch_server_binary<L: LspAdapter + 'static + Send + Sync + ?Sized>
.await
{
log::debug!("language server {:?} is already installed", name.0);
delegate.update_status(name.clone(), BinaryStatus::None);
delegate.update_status(server_id, adapter.name(), BinaryStatus::None);
Ok(binary)
} else {
log::info!("downloading language server {:?}", name.0);
delegate.update_status(adapter.name(), BinaryStatus::Downloading);
delegate.update_status(server_id, adapter.name(), BinaryStatus::Downloading);
let binary = adapter
.fetch_server_binary(latest_version, container_dir, delegate.as_ref())
.await;
delegate.update_status(name.clone(), BinaryStatus::None);
delegate.update_status(server_id, adapter.name(), BinaryStatus::None);
binary
}
}
@ -2197,6 +2228,7 @@ impl LspAdapter for FakeLspAdapter {
_: Option<Toolchain>,
_: LanguageServerBinaryOptions,
_: futures::lock::MutexGuard<'a, Option<LanguageServerBinary>>,
_: LanguageServerId,
_: &'a mut AsyncApp,
) -> Pin<Box<dyn 'a + Future<Output = Result<LanguageServerBinary>>>> {
async move { Ok(self.language_server_binary.clone()) }.boxed_local()

View file

@ -252,7 +252,9 @@ pub struct LanguageQueries {
#[derive(Clone, Default)]
struct ServerStatusSender {
txs: Arc<Mutex<Vec<mpsc::UnboundedSender<(LanguageServerName, BinaryStatus)>>>>,
txs: Arc<
Mutex<Vec<mpsc::UnboundedSender<(LanguageServerId, LanguageServerName, BinaryStatus)>>>,
>,
}
pub struct LoadedLanguage {
@ -1077,8 +1079,13 @@ impl LanguageRegistry {
self.state.read().all_lsp_adapters.get(name).cloned()
}
pub fn update_lsp_binary_status(&self, server_name: LanguageServerName, status: BinaryStatus) {
self.lsp_binary_status_tx.send(server_name, status);
pub fn update_lsp_binary_status(
&self,
server_id: LanguageServerId,
name: LanguageServerName,
status: BinaryStatus,
) {
self.lsp_binary_status_tx.send(server_id, name, status);
}
pub fn next_language_server_id(&self) -> LanguageServerId {
@ -1133,7 +1140,7 @@ impl LanguageRegistry {
pub fn language_server_binary_statuses(
&self,
) -> mpsc::UnboundedReceiver<(LanguageServerName, BinaryStatus)> {
) -> mpsc::UnboundedReceiver<(LanguageServerId, LanguageServerName, BinaryStatus)> {
self.lsp_binary_status_tx.subscribe()
}
@ -1247,14 +1254,19 @@ impl LanguageRegistryState {
}
impl ServerStatusSender {
fn subscribe(&self) -> mpsc::UnboundedReceiver<(LanguageServerName, BinaryStatus)> {
fn subscribe(
&self,
) -> mpsc::UnboundedReceiver<(LanguageServerId, LanguageServerName, BinaryStatus)> {
let (tx, rx) = mpsc::unbounded();
self.txs.lock().push(tx);
rx
}
fn send(&self, name: LanguageServerName, status: BinaryStatus) {
fn send(&self, id: LanguageServerId, name: LanguageServerName, status: BinaryStatus) {
let mut txs = self.txs.lock();
txs.retain(|tx| tx.unbounded_send((name.clone(), status.clone())).is_ok());
txs.retain(|tx| {
tx.unbounded_send((id, name.clone(), status.clone()))
.is_ok()
});
}
}

View file

@ -1,8 +1,8 @@
use std::any::Any;
use std::ops::Range;
use std::path::PathBuf;
use std::pin::Pin;
use std::sync::Arc;
use std::{any::Any, sync::OnceLock};
use anyhow::{Context as _, Result};
use async_trait::async_trait;
@ -16,8 +16,8 @@ use language::{
Toolchain,
};
use lsp::{
CodeActionKind, LanguageServerBinary, LanguageServerBinaryOptions, LanguageServerName,
LanguageServerSelector,
CodeActionKind, LanguageServerBinary, LanguageServerBinaryOptions, LanguageServerId,
LanguageServerName, LanguageServerSelector,
};
use serde::Serialize;
use serde_json::Value;
@ -58,15 +58,21 @@ impl ExtensionLanguageServerProxy for LanguageServerRegistryProxy {
fn register_language_server(
&self,
extension: Arc<dyn Extension>,
language_server_id: LanguageServerName,
language_server_name: LanguageServerName,
language: LanguageName,
) {
let language_server_id = Arc::new(OnceLock::new());
self.language_server_ids
.write()
.unwrap()
.insert(language_server_name.clone(), language_server_id.clone());
self.language_registry.register_lsp_adapter(
language.clone(),
Arc::new(ExtensionLspAdapter::new(
extension,
language_server_id,
language_server_name,
language,
language_server_id,
)),
);
}
@ -122,30 +128,41 @@ impl ExtensionLanguageServerProxy for LanguageServerRegistryProxy {
fn update_language_server_status(
&self,
language_server_id: LanguageServerName,
language_server_name: LanguageServerName,
status: BinaryStatus,
) {
self.language_registry
.update_lsp_binary_status(language_server_id, status);
if let Some(id) = self
.language_server_ids
.read()
.unwrap()
.get(&language_server_name)
&& let Some(&id) = id.get()
{
self.language_registry
.update_lsp_binary_status(id, language_server_name, status);
}
}
}
struct ExtensionLspAdapter {
extension: Arc<dyn Extension>,
language_server_id: LanguageServerName,
language_server_name: LanguageServerName,
language_name: LanguageName,
language_server_id: Arc<OnceLock<LanguageServerId>>,
}
impl ExtensionLspAdapter {
fn new(
extension: Arc<dyn Extension>,
language_server_id: LanguageServerName,
language_server_name: LanguageServerName,
language_name: LanguageName,
language_server_id: Arc<OnceLock<LanguageServerId>>,
) -> Self {
Self {
extension,
language_server_id,
language_server_name,
language_name,
language_server_id,
}
}
}
@ -153,7 +170,7 @@ impl ExtensionLspAdapter {
#[async_trait(?Send)]
impl LspAdapter for ExtensionLspAdapter {
fn name(&self) -> LanguageServerName {
self.language_server_id.clone()
self.language_server_name.clone()
}
fn get_language_server_command<'a>(
@ -162,6 +179,7 @@ impl LspAdapter for ExtensionLspAdapter {
_: Option<Toolchain>,
_: LanguageServerBinaryOptions,
_: futures::lock::MutexGuard<'a, Option<LanguageServerBinary>>,
language_server_id: LanguageServerId,
_: &'a mut AsyncApp,
) -> Pin<Box<dyn 'a + Future<Output = Result<LanguageServerBinary>>>> {
async move {
@ -169,7 +187,7 @@ impl LspAdapter for ExtensionLspAdapter {
let command = self
.extension
.language_server_command(
self.language_server_id.clone(),
self.language_server_name.clone(),
self.language_name.clone(),
delegate,
)
@ -191,6 +209,10 @@ impl LspAdapter for ExtensionLspAdapter {
.await
.context("failed to set file permissions")?;
}
self.language_server_id
.set(language_server_id)
.ok()
.context("failed to set language server id")?;
Ok(LanguageServerBinary {
path,
@ -230,7 +252,7 @@ impl LspAdapter for ExtensionLspAdapter {
.extension
.manifest()
.language_servers
.get(&self.language_server_id)
.get(&self.language_server_name)
.and_then(|server| server.code_action_kinds.clone());
code_action_kinds.or(Some(vec![
@ -256,7 +278,7 @@ impl LspAdapter for ExtensionLspAdapter {
self.extension
.manifest()
.language_servers
.get(&self.language_server_id)
.get(&self.language_server_name)
.map(|server| server.language_ids.clone())
.unwrap_or_default()
}
@ -270,7 +292,7 @@ impl LspAdapter for ExtensionLspAdapter {
let json_options = self
.extension
.language_server_initialization_options(
self.language_server_id.clone(),
self.language_server_name.clone(),
self.language_name.clone(),
delegate,
)
@ -294,7 +316,7 @@ impl LspAdapter for ExtensionLspAdapter {
let delegate = Arc::new(WorktreeDelegateAdapter(delegate.clone())) as _;
let json_options: Option<String> = self
.extension
.language_server_workspace_configuration(self.language_server_id.clone(), delegate)
.language_server_workspace_configuration(self.language_server_name.clone(), delegate)
.await?;
Ok(if let Some(json_options) = json_options {
serde_json::from_str(&json_options).with_context(|| {
@ -315,7 +337,7 @@ impl LspAdapter for ExtensionLspAdapter {
let json_options: Option<String> = self
.extension
.language_server_additional_initialization_options(
self.language_server_id.clone(),
self.language_server_name.clone(),
target_language_server_id.clone(),
delegate,
)
@ -343,7 +365,7 @@ impl LspAdapter for ExtensionLspAdapter {
let json_options: Option<String> = self
.extension
.language_server_additional_workspace_configuration(
self.language_server_id.clone(),
self.language_server_name.clone(),
target_language_server_id.clone(),
delegate,
)
@ -370,7 +392,7 @@ impl LspAdapter for ExtensionLspAdapter {
let labels = self
.extension
.labels_for_completions(self.language_server_id.clone(), completions)
.labels_for_completions(self.language_server_name.clone(), completions)
.await?;
Ok(labels_from_extension(labels, language))
@ -392,7 +414,7 @@ impl LspAdapter for ExtensionLspAdapter {
let labels = self
.extension
.labels_for_symbols(self.language_server_id.clone(), symbols)
.labels_for_symbols(self.language_server_name.clone(), symbols)
.await?;
Ok(labels_from_extension(labels, language))

View file

@ -1,12 +1,14 @@
mod extension_lsp_adapter;
use std::path::PathBuf;
use std::sync::Arc;
use std::sync::{Arc, OnceLock};
use std::{path::PathBuf, sync::RwLock};
use anyhow::Result;
use collections::FxHashMap;
use extension::{ExtensionGrammarProxy, ExtensionHostProxy, ExtensionLanguageProxy};
use gpui::{App, Entity};
use language::{LanguageMatcher, LanguageName, LanguageRegistry, LoadedLanguage};
use lsp::{LanguageServerId, LanguageServerName};
use project::LspStore;
#[derive(Clone)]
@ -23,6 +25,7 @@ pub fn init(
) {
let language_server_registry_proxy = LanguageServerRegistryProxy {
language_registry,
language_server_ids: Default::default(),
lsp_access,
};
extension_host_proxy.register_grammar_proxy(language_server_registry_proxy.clone());
@ -33,6 +36,9 @@ pub fn init(
#[derive(Clone)]
struct LanguageServerRegistryProxy {
language_registry: Arc<LanguageRegistry>,
// extensions can only spawn one language server currently
language_server_ids:
Arc<RwLock<FxHashMap<LanguageServerName, Arc<OnceLock<LanguageServerId>>>>>,
lsp_access: LspAccess,
}

View file

@ -65,7 +65,7 @@ impl std::fmt::Debug for ActiveEditor {
#[derive(Debug, Default, Clone)]
struct LanguageServers {
health_statuses: HashMap<LanguageServerId, LanguageServerHealthStatus>,
binary_statuses: HashMap<LanguageServerName, LanguageServerBinaryStatus>,
binary_statuses: HashMap<LanguageServerId, LanguageServerBinaryStatus>,
servers_per_buffer_abs_path: HashMap<PathBuf, ServersForPath>,
}
@ -85,6 +85,7 @@ struct LanguageServerHealthStatus {
struct LanguageServerBinaryStatus {
status: BinaryStatus,
message: Option<SharedString>,
name: LanguageServerName,
}
#[derive(Debug)]
@ -370,17 +371,19 @@ impl LanguageServers {
binary_status: BinaryStatus,
message: Option<&str>,
name: LanguageServerName,
id: LanguageServerId,
) {
let binary_status_message = message.map(SharedString::new);
if matches!(
binary_status,
BinaryStatus::Stopped | BinaryStatus::Failed { .. }
) {
self.health_statuses.retain(|_, server| server.name != name);
self.health_statuses.remove(&id);
}
self.binary_statuses.insert(
name,
id,
LanguageServerBinaryStatus {
name,
status: binary_status,
message: binary_status_message,
},
@ -583,18 +586,16 @@ impl LspTool {
proto::ServerBinaryStatus::Starting => BinaryStatus::Starting,
proto::ServerBinaryStatus::Stopping => BinaryStatus::Stopping,
proto::ServerBinaryStatus::Stopped => BinaryStatus::Stopped,
proto::ServerBinaryStatus::Failed => {
let Some(error) = status_update.message.clone() else {
return;
};
BinaryStatus::Failed { error }
}
proto::ServerBinaryStatus::Failed => BinaryStatus::Failed {
error: status_update.message.clone().unwrap_or_default(),
},
};
self.server_state.update(cx, |state, _| {
state.language_servers.update_binary_status(
binary_status,
status_update.message.as_deref(),
name.clone(),
*language_server_id,
);
});
updated = true;
@ -684,27 +685,16 @@ impl LspTool {
})
})
.collect::<HashSet<_>>();
let mut server_ids_to_worktrees =
HashMap::<LanguageServerId, Entity<Worktree>>::default();
let mut server_names_to_worktrees = HashMap::<
LanguageServerName,
HashSet<(Entity<Worktree>, LanguageServerId)>,
>::default();
for servers_for_path in state.language_servers.servers_per_buffer_abs_path.values() {
if let Some(worktree) = servers_for_path
.worktree
.as_ref()
.and_then(|worktree| worktree.upgrade())
{
for (server_id, server_name) in &servers_for_path.servers {
for (server_id, _) in &servers_for_path.servers {
server_ids_to_worktrees.insert(*server_id, worktree.clone());
if let Some(server_name) = server_name {
server_names_to_worktrees
.entry(server_name.clone())
.or_default()
.insert((worktree.clone(), *server_id));
}
}
}
}
@ -714,19 +704,12 @@ impl LspTool {
let mut servers_with_health_checks = HashSet::default();
for (server_id, health) in &state.language_servers.health_statuses {
let worktree = server_ids_to_worktrees.get(server_id).or_else(|| {
let worktrees = server_names_to_worktrees.get(&health.name)?;
worktrees
.iter()
.find(|(worktree, _)| active_worktrees.contains(worktree))
.or_else(|| worktrees.iter().next())
.map(|(worktree, _)| worktree)
});
servers_with_health_checks.insert(&health.name);
let worktree = server_ids_to_worktrees.get(server_id);
servers_with_health_checks.insert(*server_id);
let worktree_name =
worktree.map(|worktree| SharedString::new(worktree.read(cx).root_name()));
let binary_status = state.language_servers.binary_statuses.get(&health.name);
let binary_status = state.language_servers.binary_statuses.get(server_id);
let server_data = ServerData::WithHealthCheck {
server_id: *server_id,
health,
@ -743,11 +726,11 @@ impl LspTool {
let mut can_stop_all = !state.language_servers.health_statuses.is_empty();
let mut can_restart_all = state.language_servers.health_statuses.is_empty();
for (server_name, binary_status) in state
for (server_id, binary_status) in state
.language_servers
.binary_statuses
.iter()
.filter(|(name, _)| !servers_with_health_checks.contains(name))
.filter(|&(id, _)| !servers_with_health_checks.contains(id))
{
match binary_status.status {
BinaryStatus::None => {
@ -774,40 +757,25 @@ impl LspTool {
BinaryStatus::Failed { .. } => {}
}
match server_names_to_worktrees.get(server_name) {
Some(worktrees_for_name) => {
match worktrees_for_name
.iter()
.find(|(worktree, _)| active_worktrees.contains(worktree))
.or_else(|| worktrees_for_name.iter().next())
{
Some((worktree, server_id)) => {
let worktree_name =
SharedString::new(worktree.read(cx).root_name());
servers_per_worktree
.entry(worktree_name.clone())
.or_default()
.push(ServerData::WithBinaryStatus {
server_name,
binary_status,
server_id: Some(*server_id),
});
}
None => servers_without_worktree.push(ServerData::WithBinaryStatus {
let server_name = &binary_status.name;
match server_ids_to_worktrees.get(server_id) {
Some(worktree) if active_worktrees.contains(worktree) => {
let worktree_name = SharedString::new(worktree.read(cx).root_name());
servers_per_worktree.entry(worktree_name).or_default().push(
ServerData::WithBinaryStatus {
server_name,
binary_status,
server_id: None,
}),
}
server_id: Some(*server_id),
},
);
}
None => servers_without_worktree.push(ServerData::WithBinaryStatus {
_ => servers_without_worktree.push(ServerData::WithBinaryStatus {
server_name,
binary_status,
server_id: None,
server_id: Some(*server_id),
}),
}
}
let mut new_lsp_items =
Vec::with_capacity(servers_per_worktree.len() + servers_without_worktree.len() + 2);
for (worktree_name, worktree_servers) in servers_per_worktree {

View file

@ -308,6 +308,7 @@ impl LocalLspStore {
toolchain.clone(),
delegate.clone(),
true,
server_id,
cx,
);
let pending_workspace_folders: Arc<Mutex<BTreeSet<Url>>> = Default::default();
@ -351,11 +352,11 @@ impl LocalLspStore {
}
});
let server_name = adapter.name.clone();
let startup = {
let server_name = adapter.name.0.clone();
let server_name = server_name.clone();
let delegate = delegate as Arc<dyn LspAdapterDelegate>;
let key = key.clone();
let adapter = adapter.clone();
let lsp_store = self.weak.clone();
let pending_workspace_folders = pending_workspace_folders.clone();
let fs = self.fs.clone();
@ -460,8 +461,10 @@ impl LocalLspStore {
Err(err) => {
let log = stderr_capture.lock().take().unwrap_or_default();
log::error!("Failed to start language server {server_name:?}: {err:?}");
delegate.update_status(
adapter.name(),
server_id,
server_name,
BinaryStatus::Failed {
error: if log.is_empty() {
format!("{err:#}")
@ -470,7 +473,6 @@ impl LocalLspStore {
},
},
);
log::error!("Failed to start language server {server_name:?}: {err:?}");
if !log.is_empty() {
log::error!("server stderr: {log}");
}
@ -485,7 +487,7 @@ impl LocalLspStore {
};
self.languages
.update_lsp_binary_status(adapter.name(), BinaryStatus::Starting);
.update_lsp_binary_status(server_id, server_name, BinaryStatus::Starting);
self.language_servers.insert(server_id, state);
self.language_server_ids
@ -504,6 +506,7 @@ impl LocalLspStore {
toolchain: Option<Toolchain>,
delegate: Arc<dyn LspAdapterDelegate>,
allow_binary_download: bool,
server_id: LanguageServerId,
cx: &mut App,
) -> Task<Result<LanguageServerBinary>> {
if let Some(settings) = settings.binary.as_ref()
@ -539,10 +542,16 @@ impl LocalLspStore {
cx.spawn(async move |cx| {
let binary_result = adapter
.clone()
.get_language_server_command(delegate.clone(), toolchain, lsp_binary_options, cx)
.get_language_server_command(
delegate.clone(),
toolchain,
lsp_binary_options,
server_id,
cx,
)
.await;
delegate.update_status(adapter.name.clone(), BinaryStatus::None);
delegate.update_status(server_id, adapter.name(), BinaryStatus::None);
let mut binary = binary_result?;
let mut shell_env = delegate.shell_env().await;
@ -10409,17 +10418,22 @@ impl LspStore {
if let Some(name) = name {
log::info!("stopping language server {name}");
self.languages
.update_lsp_binary_status(name.clone(), BinaryStatus::Stopping);
self.languages.update_lsp_binary_status(
server_id,
name.clone(),
BinaryStatus::Stopping,
);
cx.notify();
return cx.spawn(async move |lsp_store, cx| {
Self::shutdown_language_server(server_state, name.clone(), cx).await;
lsp_store
.update(cx, |lsp_store, cx| {
lsp_store
.languages
.update_lsp_binary_status(name, BinaryStatus::Stopped);
lsp_store.languages.update_lsp_binary_status(
server_id,
name.clone(),
BinaryStatus::Stopped,
);
cx.emit(LspStoreEvent::LanguageServerRemoved(server_id));
cx.notify();
})
@ -10882,7 +10896,7 @@ impl LspStore {
);
local
.languages
.update_lsp_binary_status(adapter.name(), BinaryStatus::None);
.update_lsp_binary_status(server_id, adapter.name(), BinaryStatus::None);
if let Some(file_ops_caps) = language_server
.capabilities()
.workspace
@ -12197,7 +12211,7 @@ fn subscribe_to_binary_statuses(
) -> Task<()> {
let mut server_statuses = languages.language_server_binary_statuses();
cx.spawn(async move |lsp_store, cx| {
while let Some((server_name, binary_status)) = server_statuses.next().await {
while let Some((server_id, server_name, binary_status)) = server_statuses.next().await {
if lsp_store
.update(cx, |_, cx| {
let mut message = None;
@ -12216,9 +12230,7 @@ fn subscribe_to_binary_statuses(
}
};
cx.emit(LspStoreEvent::LanguageServerUpdate {
// Binary updates are about the binary that might not have any language server id at that point.
// Reuse `LanguageServerUpdate` for them and provide a fake id that won't be used on the receiver side.
language_server_id: LanguageServerId(0),
language_server_id: server_id,
name: Some(server_name),
message: proto::update_language_server::Variant::StatusUpdate(
proto::StatusUpdate {
@ -13154,9 +13166,14 @@ impl LspAdapterDelegate for LocalLspAdapterDelegate {
Ok(())
}
fn update_status(&self, server_name: LanguageServerName, status: language::BinaryStatus) {
fn update_status(
&self,
server_id: LanguageServerId,
server_name: LanguageServerName,
status: language::BinaryStatus,
) {
self.language_registry
.update_lsp_binary_status(server_name, status);
.update_lsp_binary_status(server_id, server_name, status);
}
fn registered_lsp_adapters(&self) -> Vec<Arc<dyn LspAdapter>> {