Stop sending redundant LSP proto requests (#35581)

Before, each time any LSP feature was used on client remote, it always
produced a `proto::` request that always had been sent to the host, from
where returned as an empty response.

Instead, propagate more language server-related data to the client,
`lsp::ServerCapability`, so Zed client can omit certain requests if
those are not supported.

On top of that, rework the approach Zed uses to query for the data
refreshes: before, editors tried to fetch the data when the server start
was reported (locally and remotely).
Now, a later event is selected: on each `textDocument/didOpen` for the
buffer contained in this editor, we will query for new LSP data, reusing
the cache if needed.

Before, servers could reject unregistered files' LSP queries, or process
them slowly when starting up.
Now, such refreshes are happening later and should be cached.

This requires a collab DB change, to restore server data on rejoin.

Release Notes:

- Fixed excessive LSP requests sent during remote sessions
This commit is contained in:
Kirill Bulatov 2025-08-05 16:36:05 +03:00 committed by GitHub
parent 5b40b3618f
commit 22473fc611
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 793 additions and 351 deletions

View file

@ -173,6 +173,7 @@ CREATE TABLE "language_servers" (
"id" INTEGER NOT NULL, "id" INTEGER NOT NULL,
"project_id" INTEGER NOT NULL REFERENCES projects (id) ON DELETE CASCADE, "project_id" INTEGER NOT NULL REFERENCES projects (id) ON DELETE CASCADE,
"name" VARCHAR NOT NULL, "name" VARCHAR NOT NULL,
"capabilities" TEXT NOT NULL,
PRIMARY KEY (project_id, id) PRIMARY KEY (project_id, id)
); );

View file

@ -0,0 +1,5 @@
ALTER TABLE language_servers
ADD COLUMN capabilities TEXT NOT NULL DEFAULT '{}';
ALTER TABLE language_servers
ALTER COLUMN capabilities DROP DEFAULT;

View file

@ -529,11 +529,17 @@ pub struct RejoinedProject {
pub worktrees: Vec<RejoinedWorktree>, pub worktrees: Vec<RejoinedWorktree>,
pub updated_repositories: Vec<proto::UpdateRepository>, pub updated_repositories: Vec<proto::UpdateRepository>,
pub removed_repositories: Vec<u64>, pub removed_repositories: Vec<u64>,
pub language_servers: Vec<proto::LanguageServer>, pub language_servers: Vec<LanguageServer>,
} }
impl RejoinedProject { impl RejoinedProject {
pub fn to_proto(&self) -> proto::RejoinedProject { pub fn to_proto(&self) -> proto::RejoinedProject {
let (language_servers, language_server_capabilities) = self
.language_servers
.clone()
.into_iter()
.map(|server| (server.server, server.capabilities))
.unzip();
proto::RejoinedProject { proto::RejoinedProject {
id: self.id.to_proto(), id: self.id.to_proto(),
worktrees: self worktrees: self
@ -551,7 +557,8 @@ impl RejoinedProject {
.iter() .iter()
.map(|collaborator| collaborator.to_proto()) .map(|collaborator| collaborator.to_proto())
.collect(), .collect(),
language_servers: self.language_servers.clone(), language_servers,
language_server_capabilities,
} }
} }
} }
@ -598,7 +605,7 @@ pub struct Project {
pub collaborators: Vec<ProjectCollaborator>, pub collaborators: Vec<ProjectCollaborator>,
pub worktrees: BTreeMap<u64, Worktree>, pub worktrees: BTreeMap<u64, Worktree>,
pub repositories: Vec<proto::UpdateRepository>, pub repositories: Vec<proto::UpdateRepository>,
pub language_servers: Vec<proto::LanguageServer>, pub language_servers: Vec<LanguageServer>,
} }
pub struct ProjectCollaborator { pub struct ProjectCollaborator {
@ -623,6 +630,12 @@ impl ProjectCollaborator {
} }
} }
#[derive(Debug, Clone)]
pub struct LanguageServer {
pub server: proto::LanguageServer,
pub capabilities: String,
}
#[derive(Debug)] #[derive(Debug)]
pub struct LeftProject { pub struct LeftProject {
pub id: ProjectId, pub id: ProjectId,

View file

@ -786,6 +786,32 @@ impl Database {
}) })
.collect()) .collect())
} }
/// Update language server capabilities for a given id.
pub async fn update_server_capabilities(
&self,
project_id: ProjectId,
server_id: u64,
new_capabilities: String,
) -> Result<()> {
self.transaction(|tx| {
let new_capabilities = new_capabilities.clone();
async move {
Ok(
language_server::Entity::update(language_server::ActiveModel {
project_id: ActiveValue::unchanged(project_id),
id: ActiveValue::unchanged(server_id as i64),
capabilities: ActiveValue::set(new_capabilities),
..Default::default()
})
.exec(&*tx)
.await?,
)
}
})
.await?;
Ok(())
}
} }
fn operation_to_storage( fn operation_to_storage(

View file

@ -692,6 +692,7 @@ impl Database {
project_id: ActiveValue::set(project_id), project_id: ActiveValue::set(project_id),
id: ActiveValue::set(server.id as i64), id: ActiveValue::set(server.id as i64),
name: ActiveValue::set(server.name.clone()), name: ActiveValue::set(server.name.clone()),
capabilities: ActiveValue::set(update.capabilities.clone()),
}) })
.on_conflict( .on_conflict(
OnConflict::columns([ OnConflict::columns([
@ -1054,10 +1055,13 @@ impl Database {
repositories, repositories,
language_servers: language_servers language_servers: language_servers
.into_iter() .into_iter()
.map(|language_server| proto::LanguageServer { .map(|language_server| LanguageServer {
server: proto::LanguageServer {
id: language_server.id as u64, id: language_server.id as u64,
name: language_server.name, name: language_server.name,
worktree_id: None, worktree_id: None,
},
capabilities: language_server.capabilities,
}) })
.collect(), .collect(),
}; };

View file

@ -804,10 +804,13 @@ impl Database {
.all(tx) .all(tx)
.await? .await?
.into_iter() .into_iter()
.map(|language_server| proto::LanguageServer { .map(|language_server| LanguageServer {
server: proto::LanguageServer {
id: language_server.id as u64, id: language_server.id as u64,
name: language_server.name, name: language_server.name,
worktree_id: None, worktree_id: None,
},
capabilities: language_server.capabilities,
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();

View file

@ -9,6 +9,7 @@ pub struct Model {
#[sea_orm(primary_key)] #[sea_orm(primary_key)]
pub id: i64, pub id: i64,
pub name: String, pub name: String,
pub capabilities: String,
} }
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]

View file

@ -1990,12 +1990,19 @@ async fn join_project(
} }
// First, we send the metadata associated with each worktree. // First, we send the metadata associated with each worktree.
let (language_servers, language_server_capabilities) = project
.language_servers
.clone()
.into_iter()
.map(|server| (server.server, server.capabilities))
.unzip();
response.send(proto::JoinProjectResponse { response.send(proto::JoinProjectResponse {
project_id: project.id.0 as u64, project_id: project.id.0 as u64,
worktrees: worktrees.clone(), worktrees: worktrees.clone(),
replica_id: replica_id.0 as u32, replica_id: replica_id.0 as u32,
collaborators: collaborators.clone(), collaborators: collaborators.clone(),
language_servers: project.language_servers.clone(), language_servers,
language_server_capabilities,
role: project.role.into(), role: project.role.into(),
})?; })?;
@ -2054,8 +2061,8 @@ async fn join_project(
session.connection_id, session.connection_id,
proto::UpdateLanguageServer { proto::UpdateLanguageServer {
project_id: project_id.to_proto(), project_id: project_id.to_proto(),
server_name: Some(language_server.name.clone()), server_name: Some(language_server.server.name.clone()),
language_server_id: language_server.id, language_server_id: language_server.server.id,
variant: Some( variant: Some(
proto::update_language_server::Variant::DiskBasedDiagnosticsUpdated( proto::update_language_server::Variant::DiskBasedDiagnosticsUpdated(
proto::LspDiskBasedDiagnosticsUpdated {}, proto::LspDiskBasedDiagnosticsUpdated {},
@ -2267,9 +2274,17 @@ async fn update_language_server(
session: Session, session: Session,
) -> Result<()> { ) -> Result<()> {
let project_id = ProjectId::from_proto(request.project_id); let project_id = ProjectId::from_proto(request.project_id);
let project_connection_ids = session let db = session.db().await;
.db()
.await if let Some(proto::update_language_server::Variant::MetadataUpdated(update)) = &request.variant
{
if let Some(capabilities) = update.capabilities.clone() {
db.update_server_capabilities(project_id, request.language_server_id, capabilities)
.await?;
}
}
let project_connection_ids = db
.project_connection_ids(project_id, session.connection_id, true) .project_connection_ids(project_id, session.connection_id, true)
.await?; .await?;
broadcast( broadcast(

View file

@ -296,19 +296,28 @@ async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mu
.await; .await;
let active_call_a = cx_a.read(ActiveCall::global); let active_call_a = cx_a.read(ActiveCall::global);
let capabilities = lsp::ServerCapabilities {
completion_provider: Some(lsp::CompletionOptions {
trigger_characters: Some(vec![".".to_string()]),
resolve_provider: Some(true),
..lsp::CompletionOptions::default()
}),
..lsp::ServerCapabilities::default()
};
client_a.language_registry().add(rust_lang()); client_a.language_registry().add(rust_lang());
let mut fake_language_servers = client_a.language_registry().register_fake_lsp( let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
"Rust", "Rust",
FakeLspAdapter { FakeLspAdapter {
capabilities: lsp::ServerCapabilities { capabilities: capabilities.clone(),
completion_provider: Some(lsp::CompletionOptions { ..FakeLspAdapter::default()
trigger_characters: Some(vec![".".to_string()]),
resolve_provider: Some(true),
..Default::default()
}),
..Default::default()
}, },
..Default::default() );
client_b.language_registry().add(rust_lang());
client_b.language_registry().register_fake_lsp_adapter(
"Rust",
FakeLspAdapter {
capabilities,
..FakeLspAdapter::default()
}, },
); );
@ -566,11 +575,14 @@ async fn test_collaborating_with_code_actions(
cx_b.update(editor::init); cx_b.update(editor::init);
// Set up a fake language server.
client_a.language_registry().add(rust_lang()); client_a.language_registry().add(rust_lang());
let mut fake_language_servers = client_a let mut fake_language_servers = client_a
.language_registry() .language_registry()
.register_fake_lsp("Rust", FakeLspAdapter::default()); .register_fake_lsp("Rust", FakeLspAdapter::default());
client_b.language_registry().add(rust_lang());
client_b
.language_registry()
.register_fake_lsp("Rust", FakeLspAdapter::default());
client_a client_a
.fs() .fs()
@ -775,19 +787,27 @@ async fn test_collaborating_with_renames(cx_a: &mut TestAppContext, cx_b: &mut T
cx_b.update(editor::init); cx_b.update(editor::init);
// Set up a fake language server. let capabilities = lsp::ServerCapabilities {
client_a.language_registry().add(rust_lang());
let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
"Rust",
FakeLspAdapter {
capabilities: lsp::ServerCapabilities {
rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions { rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
prepare_provider: Some(true), prepare_provider: Some(true),
work_done_progress_options: Default::default(), work_done_progress_options: Default::default(),
})), })),
..Default::default() ..lsp::ServerCapabilities::default()
};
client_a.language_registry().add(rust_lang());
let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
"Rust",
FakeLspAdapter {
capabilities: capabilities.clone(),
..FakeLspAdapter::default()
}, },
..Default::default() );
client_b.language_registry().add(rust_lang());
client_b.language_registry().register_fake_lsp_adapter(
"Rust",
FakeLspAdapter {
capabilities,
..FakeLspAdapter::default()
}, },
); );
@ -818,6 +838,8 @@ async fn test_collaborating_with_renames(cx_a: &mut TestAppContext, cx_b: &mut T
.downcast::<Editor>() .downcast::<Editor>()
.unwrap(); .unwrap();
let fake_language_server = fake_language_servers.next().await.unwrap(); let fake_language_server = fake_language_servers.next().await.unwrap();
cx_a.run_until_parked();
cx_b.run_until_parked();
// Move cursor to a location that can be renamed. // Move cursor to a location that can be renamed.
let prepare_rename = editor_b.update_in(cx_b, |editor, window, cx| { let prepare_rename = editor_b.update_in(cx_b, |editor, window, cx| {
@ -1055,7 +1077,7 @@ async fn test_language_server_statuses(cx_a: &mut TestAppContext, cx_b: &mut Tes
project_a.read_with(cx_a, |project, cx| { project_a.read_with(cx_a, |project, cx| {
let status = project.language_server_statuses(cx).next().unwrap().1; let status = project.language_server_statuses(cx).next().unwrap().1;
assert_eq!(status.name, "the-language-server"); assert_eq!(status.name.0, "the-language-server");
assert_eq!(status.pending_work.len(), 1); assert_eq!(status.pending_work.len(), 1);
assert_eq!( assert_eq!(
status.pending_work["the-token"].message.as_ref().unwrap(), status.pending_work["the-token"].message.as_ref().unwrap(),
@ -1072,7 +1094,7 @@ async fn test_language_server_statuses(cx_a: &mut TestAppContext, cx_b: &mut Tes
project_b.read_with(cx_b, |project, cx| { project_b.read_with(cx_b, |project, cx| {
let status = project.language_server_statuses(cx).next().unwrap().1; let status = project.language_server_statuses(cx).next().unwrap().1;
assert_eq!(status.name, "the-language-server"); assert_eq!(status.name.0, "the-language-server");
}); });
executor.advance_clock(SERVER_PROGRESS_THROTTLE_TIMEOUT); executor.advance_clock(SERVER_PROGRESS_THROTTLE_TIMEOUT);
@ -1089,7 +1111,7 @@ async fn test_language_server_statuses(cx_a: &mut TestAppContext, cx_b: &mut Tes
project_a.read_with(cx_a, |project, cx| { project_a.read_with(cx_a, |project, cx| {
let status = project.language_server_statuses(cx).next().unwrap().1; let status = project.language_server_statuses(cx).next().unwrap().1;
assert_eq!(status.name, "the-language-server"); assert_eq!(status.name.0, "the-language-server");
assert_eq!(status.pending_work.len(), 1); assert_eq!(status.pending_work.len(), 1);
assert_eq!( assert_eq!(
status.pending_work["the-token"].message.as_ref().unwrap(), status.pending_work["the-token"].message.as_ref().unwrap(),
@ -1099,7 +1121,7 @@ async fn test_language_server_statuses(cx_a: &mut TestAppContext, cx_b: &mut Tes
project_b.read_with(cx_b, |project, cx| { project_b.read_with(cx_b, |project, cx| {
let status = project.language_server_statuses(cx).next().unwrap().1; let status = project.language_server_statuses(cx).next().unwrap().1;
assert_eq!(status.name, "the-language-server"); assert_eq!(status.name.0, "the-language-server");
assert_eq!(status.pending_work.len(), 1); assert_eq!(status.pending_work.len(), 1);
assert_eq!( assert_eq!(
status.pending_work["the-token"].message.as_ref().unwrap(), status.pending_work["the-token"].message.as_ref().unwrap(),
@ -1422,18 +1444,27 @@ async fn test_on_input_format_from_guest_to_host(
.await; .await;
let active_call_a = cx_a.read(ActiveCall::global); let active_call_a = cx_a.read(ActiveCall::global);
client_a.language_registry().add(rust_lang()); let capabilities = lsp::ServerCapabilities {
let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
"Rust",
FakeLspAdapter {
capabilities: lsp::ServerCapabilities {
document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions { document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
first_trigger_character: ":".to_string(), first_trigger_character: ":".to_string(),
more_trigger_character: Some(vec![">".to_string()]), more_trigger_character: Some(vec![">".to_string()]),
}), }),
..Default::default() ..lsp::ServerCapabilities::default()
};
client_a.language_registry().add(rust_lang());
let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
"Rust",
FakeLspAdapter {
capabilities: capabilities.clone(),
..FakeLspAdapter::default()
}, },
..Default::default() );
client_b.language_registry().add(rust_lang());
client_b.language_registry().register_fake_lsp_adapter(
"Rust",
FakeLspAdapter {
capabilities,
..FakeLspAdapter::default()
}, },
); );
@ -1588,16 +1619,24 @@ async fn test_mutual_editor_inlay_hint_cache_update(
}); });
}); });
let capabilities = lsp::ServerCapabilities {
inlay_hint_provider: Some(lsp::OneOf::Left(true)),
..lsp::ServerCapabilities::default()
};
client_a.language_registry().add(rust_lang()); client_a.language_registry().add(rust_lang());
client_b.language_registry().add(rust_lang());
let mut fake_language_servers = client_a.language_registry().register_fake_lsp( let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
"Rust", "Rust",
FakeLspAdapter { FakeLspAdapter {
capabilities: lsp::ServerCapabilities { capabilities: capabilities.clone(),
inlay_hint_provider: Some(lsp::OneOf::Left(true)), ..FakeLspAdapter::default()
..Default::default()
}, },
..Default::default() );
client_b.language_registry().add(rust_lang());
client_b.language_registry().register_fake_lsp_adapter(
"Rust",
FakeLspAdapter {
capabilities,
..FakeLspAdapter::default()
}, },
); );
@ -1830,16 +1869,24 @@ async fn test_inlay_hint_refresh_is_forwarded(
}); });
}); });
let capabilities = lsp::ServerCapabilities {
inlay_hint_provider: Some(lsp::OneOf::Left(true)),
..lsp::ServerCapabilities::default()
};
client_a.language_registry().add(rust_lang()); client_a.language_registry().add(rust_lang());
client_b.language_registry().add(rust_lang());
let mut fake_language_servers = client_a.language_registry().register_fake_lsp( let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
"Rust", "Rust",
FakeLspAdapter { FakeLspAdapter {
capabilities: lsp::ServerCapabilities { capabilities: capabilities.clone(),
inlay_hint_provider: Some(lsp::OneOf::Left(true)), ..FakeLspAdapter::default()
..Default::default()
}, },
..Default::default() );
client_b.language_registry().add(rust_lang());
client_b.language_registry().register_fake_lsp_adapter(
"Rust",
FakeLspAdapter {
capabilities,
..FakeLspAdapter::default()
}, },
); );
@ -2004,15 +2051,23 @@ async fn test_lsp_document_color(cx_a: &mut TestAppContext, cx_b: &mut TestAppCo
}); });
}); });
let capabilities = lsp::ServerCapabilities {
color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
..lsp::ServerCapabilities::default()
};
client_a.language_registry().add(rust_lang()); client_a.language_registry().add(rust_lang());
client_b.language_registry().add(rust_lang());
let mut fake_language_servers = client_a.language_registry().register_fake_lsp( let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
"Rust", "Rust",
FakeLspAdapter { FakeLspAdapter {
capabilities: lsp::ServerCapabilities { capabilities: capabilities.clone(),
color_provider: Some(lsp::ColorProviderCapability::Simple(true)), ..FakeLspAdapter::default()
..lsp::ServerCapabilities::default()
}, },
);
client_b.language_registry().add(rust_lang());
client_b.language_registry().register_fake_lsp_adapter(
"Rust",
FakeLspAdapter {
capabilities,
..FakeLspAdapter::default() ..FakeLspAdapter::default()
}, },
); );
@ -2063,6 +2118,8 @@ async fn test_lsp_document_color(cx_a: &mut TestAppContext, cx_b: &mut TestAppCo
.unwrap(); .unwrap();
let fake_language_server = fake_language_servers.next().await.unwrap(); let fake_language_server = fake_language_servers.next().await.unwrap();
cx_a.run_until_parked();
cx_b.run_until_parked();
let requests_made = Arc::new(AtomicUsize::new(0)); let requests_made = Arc::new(AtomicUsize::new(0));
let closure_requests_made = Arc::clone(&requests_made); let closure_requests_made = Arc::clone(&requests_made);
@ -2264,12 +2321,7 @@ async fn test_lsp_pull_diagnostics(
cx_a.update(editor::init); cx_a.update(editor::init);
cx_b.update(editor::init); cx_b.update(editor::init);
client_a.language_registry().add(rust_lang()); let capabilities = lsp::ServerCapabilities {
client_b.language_registry().add(rust_lang());
let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
"Rust",
FakeLspAdapter {
capabilities: lsp::ServerCapabilities {
diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options( diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
lsp::DiagnosticOptions { lsp::DiagnosticOptions {
identifier: Some("test-pulls".to_string()), identifier: Some("test-pulls".to_string()),
@ -2281,7 +2333,20 @@ async fn test_lsp_pull_diagnostics(
}, },
)), )),
..lsp::ServerCapabilities::default() ..lsp::ServerCapabilities::default()
};
client_a.language_registry().add(rust_lang());
let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
"Rust",
FakeLspAdapter {
capabilities: capabilities.clone(),
..FakeLspAdapter::default()
}, },
);
client_b.language_registry().add(rust_lang());
client_b.language_registry().register_fake_lsp_adapter(
"Rust",
FakeLspAdapter {
capabilities,
..FakeLspAdapter::default() ..FakeLspAdapter::default()
}, },
); );
@ -2334,6 +2399,8 @@ async fn test_lsp_pull_diagnostics(
.unwrap(); .unwrap();
let fake_language_server = fake_language_servers.next().await.unwrap(); let fake_language_server = fake_language_servers.next().await.unwrap();
cx_a.run_until_parked();
cx_b.run_until_parked();
let expected_push_diagnostic_main_message = "pushed main diagnostic"; let expected_push_diagnostic_main_message = "pushed main diagnostic";
let expected_push_diagnostic_lib_message = "pushed lib diagnostic"; let expected_push_diagnostic_lib_message = "pushed lib diagnostic";
let expected_pull_diagnostic_main_message = "pulled main diagnostic"; let expected_pull_diagnostic_main_message = "pulled main diagnostic";
@ -2689,6 +2756,7 @@ async fn test_lsp_pull_diagnostics(
.unwrap() .unwrap()
.downcast::<Editor>() .downcast::<Editor>()
.unwrap(); .unwrap();
cx_b.run_until_parked();
pull_diagnostics_handle.next().await.unwrap(); pull_diagnostics_handle.next().await.unwrap();
assert_eq!( assert_eq!(

View file

@ -4778,10 +4778,27 @@ async fn test_definition(
.await; .await;
let active_call_a = cx_a.read(ActiveCall::global); let active_call_a = cx_a.read(ActiveCall::global);
let mut fake_language_servers = client_a let capabilities = lsp::ServerCapabilities {
.language_registry() definition_provider: Some(OneOf::Left(true)),
.register_fake_lsp("Rust", Default::default()); type_definition_provider: Some(lsp::TypeDefinitionProviderCapability::Simple(true)),
..lsp::ServerCapabilities::default()
};
client_a.language_registry().add(rust_lang()); client_a.language_registry().add(rust_lang());
let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
"Rust",
FakeLspAdapter {
capabilities: capabilities.clone(),
..FakeLspAdapter::default()
},
);
client_b.language_registry().add(rust_lang());
client_b.language_registry().register_fake_lsp_adapter(
"Rust",
FakeLspAdapter {
capabilities,
..FakeLspAdapter::default()
},
);
client_a client_a
.fs() .fs()
@ -4827,13 +4844,19 @@ async fn test_definition(
))) )))
}, },
); );
cx_a.run_until_parked();
cx_b.run_until_parked();
let definitions_1 = project_b let definitions_1 = project_b
.update(cx_b, |p, cx| p.definitions(&buffer_b, 23, cx)) .update(cx_b, |p, cx| p.definitions(&buffer_b, 23, cx))
.await .await
.unwrap(); .unwrap();
cx_b.read(|cx| { cx_b.read(|cx| {
assert_eq!(definitions_1.len(), 1); assert_eq!(
definitions_1.len(),
1,
"Unexpected definitions: {definitions_1:?}"
);
assert_eq!(project_b.read(cx).worktrees(cx).count(), 2); assert_eq!(project_b.read(cx).worktrees(cx).count(), 2);
let target_buffer = definitions_1[0].target.buffer.read(cx); let target_buffer = definitions_1[0].target.buffer.read(cx);
assert_eq!( assert_eq!(
@ -4901,7 +4924,11 @@ async fn test_definition(
.await .await
.unwrap(); .unwrap();
cx_b.read(|cx| { cx_b.read(|cx| {
assert_eq!(type_definitions.len(), 1); assert_eq!(
type_definitions.len(),
1,
"Unexpected type definitions: {type_definitions:?}"
);
let target_buffer = type_definitions[0].target.buffer.read(cx); let target_buffer = type_definitions[0].target.buffer.read(cx);
assert_eq!(target_buffer.text(), "type T2 = usize;"); assert_eq!(target_buffer.text(), "type T2 = usize;");
assert_eq!( assert_eq!(
@ -4925,16 +4952,26 @@ async fn test_references(
.await; .await;
let active_call_a = cx_a.read(ActiveCall::global); let active_call_a = cx_a.read(ActiveCall::global);
let capabilities = lsp::ServerCapabilities {
references_provider: Some(lsp::OneOf::Left(true)),
..lsp::ServerCapabilities::default()
};
client_a.language_registry().add(rust_lang()); client_a.language_registry().add(rust_lang());
let mut fake_language_servers = client_a.language_registry().register_fake_lsp( let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
"Rust", "Rust",
FakeLspAdapter { FakeLspAdapter {
name: "my-fake-lsp-adapter", name: "my-fake-lsp-adapter",
capabilities: lsp::ServerCapabilities { capabilities: capabilities.clone(),
references_provider: Some(lsp::OneOf::Left(true)), ..FakeLspAdapter::default()
..Default::default()
}, },
..Default::default() );
client_b.language_registry().add(rust_lang());
client_b.language_registry().register_fake_lsp_adapter(
"Rust",
FakeLspAdapter {
name: "my-fake-lsp-adapter",
capabilities: capabilities,
..FakeLspAdapter::default()
}, },
); );
@ -4989,6 +5026,8 @@ async fn test_references(
} }
} }
}); });
cx_a.run_until_parked();
cx_b.run_until_parked();
let references = project_b.update(cx_b, |p, cx| p.references(&buffer_b, 7, cx)); let references = project_b.update(cx_b, |p, cx| p.references(&buffer_b, 7, cx));
@ -4996,7 +5035,7 @@ async fn test_references(
executor.run_until_parked(); executor.run_until_parked();
project_b.read_with(cx_b, |project, cx| { project_b.read_with(cx_b, |project, cx| {
let status = project.language_server_statuses(cx).next().unwrap().1; let status = project.language_server_statuses(cx).next().unwrap().1;
assert_eq!(status.name, "my-fake-lsp-adapter"); assert_eq!(status.name.0, "my-fake-lsp-adapter");
assert_eq!( assert_eq!(
status.pending_work.values().next().unwrap().message, status.pending_work.values().next().unwrap().message,
Some("Finding references...".into()) Some("Finding references...".into())
@ -5054,7 +5093,7 @@ async fn test_references(
executor.run_until_parked(); executor.run_until_parked();
project_b.read_with(cx_b, |project, cx| { project_b.read_with(cx_b, |project, cx| {
let status = project.language_server_statuses(cx).next().unwrap().1; let status = project.language_server_statuses(cx).next().unwrap().1;
assert_eq!(status.name, "my-fake-lsp-adapter"); assert_eq!(status.name.0, "my-fake-lsp-adapter");
assert_eq!( assert_eq!(
status.pending_work.values().next().unwrap().message, status.pending_work.values().next().unwrap().message,
Some("Finding references...".into()) Some("Finding references...".into())
@ -5204,10 +5243,26 @@ async fn test_document_highlights(
) )
.await; .await;
let mut fake_language_servers = client_a
.language_registry()
.register_fake_lsp("Rust", Default::default());
client_a.language_registry().add(rust_lang()); client_a.language_registry().add(rust_lang());
let capabilities = lsp::ServerCapabilities {
document_highlight_provider: Some(lsp::OneOf::Left(true)),
..lsp::ServerCapabilities::default()
};
let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
"Rust",
FakeLspAdapter {
capabilities: capabilities.clone(),
..FakeLspAdapter::default()
},
);
client_b.language_registry().add(rust_lang());
client_b.language_registry().register_fake_lsp_adapter(
"Rust",
FakeLspAdapter {
capabilities,
..FakeLspAdapter::default()
},
);
let (project_a, worktree_id) = client_a.build_local_project(path!("/root-1"), cx_a).await; let (project_a, worktree_id) = client_a.build_local_project(path!("/root-1"), cx_a).await;
let project_id = active_call_a let project_id = active_call_a
@ -5256,6 +5311,8 @@ async fn test_document_highlights(
])) ]))
}, },
); );
cx_a.run_until_parked();
cx_b.run_until_parked();
let highlights = project_b let highlights = project_b
.update(cx_b, |p, cx| p.document_highlights(&buffer_b, 34, cx)) .update(cx_b, |p, cx| p.document_highlights(&buffer_b, 34, cx))
@ -5306,30 +5363,49 @@ async fn test_lsp_hover(
client_a.language_registry().add(rust_lang()); client_a.language_registry().add(rust_lang());
let language_server_names = ["rust-analyzer", "CrabLang-ls"]; let language_server_names = ["rust-analyzer", "CrabLang-ls"];
let capabilities_1 = lsp::ServerCapabilities {
hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
..lsp::ServerCapabilities::default()
};
let capabilities_2 = lsp::ServerCapabilities {
hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
..lsp::ServerCapabilities::default()
};
let mut language_servers = [ let mut language_servers = [
client_a.language_registry().register_fake_lsp( client_a.language_registry().register_fake_lsp(
"Rust", "Rust",
FakeLspAdapter { FakeLspAdapter {
name: "rust-analyzer", name: language_server_names[0],
capabilities: lsp::ServerCapabilities { capabilities: capabilities_1.clone(),
hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
..lsp::ServerCapabilities::default()
},
..FakeLspAdapter::default() ..FakeLspAdapter::default()
}, },
), ),
client_a.language_registry().register_fake_lsp( client_a.language_registry().register_fake_lsp(
"Rust", "Rust",
FakeLspAdapter { FakeLspAdapter {
name: "CrabLang-ls", name: language_server_names[1],
capabilities: lsp::ServerCapabilities { capabilities: capabilities_2.clone(),
hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
..lsp::ServerCapabilities::default()
},
..FakeLspAdapter::default() ..FakeLspAdapter::default()
}, },
), ),
]; ];
client_b.language_registry().add(rust_lang());
client_b.language_registry().register_fake_lsp_adapter(
"Rust",
FakeLspAdapter {
name: language_server_names[0],
capabilities: capabilities_1,
..FakeLspAdapter::default()
},
);
client_b.language_registry().register_fake_lsp_adapter(
"Rust",
FakeLspAdapter {
name: language_server_names[1],
capabilities: capabilities_2,
..FakeLspAdapter::default()
},
);
let (project_a, worktree_id) = client_a.build_local_project(path!("/root-1"), cx_a).await; let (project_a, worktree_id) = client_a.build_local_project(path!("/root-1"), cx_a).await;
let project_id = active_call_a let project_id = active_call_a
@ -5423,6 +5499,8 @@ async fn test_lsp_hover(
unexpected => panic!("Unexpected server name: {unexpected}"), unexpected => panic!("Unexpected server name: {unexpected}"),
} }
} }
cx_a.run_until_parked();
cx_b.run_until_parked();
// Request hover information as the guest. // Request hover information as the guest.
let mut hovers = project_b let mut hovers = project_b
@ -5605,10 +5683,26 @@ async fn test_open_buffer_while_getting_definition_pointing_to_it(
.await; .await;
let active_call_a = cx_a.read(ActiveCall::global); let active_call_a = cx_a.read(ActiveCall::global);
let capabilities = lsp::ServerCapabilities {
definition_provider: Some(OneOf::Left(true)),
..lsp::ServerCapabilities::default()
};
client_a.language_registry().add(rust_lang()); client_a.language_registry().add(rust_lang());
let mut fake_language_servers = client_a let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
.language_registry() "Rust",
.register_fake_lsp("Rust", Default::default()); FakeLspAdapter {
capabilities: capabilities.clone(),
..FakeLspAdapter::default()
},
);
client_b.language_registry().add(rust_lang());
client_b.language_registry().register_fake_lsp_adapter(
"Rust",
FakeLspAdapter {
capabilities,
..FakeLspAdapter::default()
},
);
client_a client_a
.fs() .fs()
@ -5649,6 +5743,8 @@ async fn test_open_buffer_while_getting_definition_pointing_to_it(
let definitions; let definitions;
let buffer_b2; let buffer_b2;
if rng.r#gen() { if rng.r#gen() {
cx_a.run_until_parked();
cx_b.run_until_parked();
definitions = project_b.update(cx_b, |p, cx| p.definitions(&buffer_b1, 23, cx)); definitions = project_b.update(cx_b, |p, cx| p.definitions(&buffer_b1, 23, cx));
(buffer_b2, _) = project_b (buffer_b2, _) = project_b
.update(cx_b, |p, cx| { .update(cx_b, |p, cx| {
@ -5663,11 +5759,17 @@ async fn test_open_buffer_while_getting_definition_pointing_to_it(
}) })
.await .await
.unwrap(); .unwrap();
cx_a.run_until_parked();
cx_b.run_until_parked();
definitions = project_b.update(cx_b, |p, cx| p.definitions(&buffer_b1, 23, cx)); definitions = project_b.update(cx_b, |p, cx| p.definitions(&buffer_b1, 23, cx));
} }
let definitions = definitions.await.unwrap(); let definitions = definitions.await.unwrap();
assert_eq!(definitions.len(), 1); assert_eq!(
definitions.len(),
1,
"Unexpected definitions: {definitions:?}"
);
assert_eq!(definitions[0].target.buffer, buffer_b2); assert_eq!(definitions[0].target.buffer, buffer_b2);
} }

View file

@ -134,7 +134,7 @@ use language::{
use linked_editing_ranges::refresh_linked_ranges; use linked_editing_ranges::refresh_linked_ranges;
use lsp::{ use lsp::{
CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode, CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
LanguageServerId, LanguageServerName, LanguageServerId,
}; };
use lsp_colors::LspColorData; use lsp_colors::LspColorData;
use markdown::Markdown; use markdown::Markdown;
@ -1864,7 +1864,6 @@ impl Editor {
editor.tasks_update_task = editor.tasks_update_task =
Some(editor.refresh_runnables(window, cx)); Some(editor.refresh_runnables(window, cx));
} }
editor.update_lsp_data(true, None, window, cx);
} }
project::Event::SnippetEdit(id, snippet_edits) => { project::Event::SnippetEdit(id, snippet_edits) => {
if let Some(buffer) = editor.buffer.read(cx).buffer(*id) { if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
@ -1886,6 +1885,11 @@ impl Editor {
} }
} }
} }
project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
if editor.buffer().read(cx).buffer(*buffer_id).is_some() {
editor.update_lsp_data(false, Some(*buffer_id), window, cx);
}
}
_ => {} _ => {}
}, },
)); ));
@ -15846,7 +15850,7 @@ impl Editor {
let language_server_name = project let language_server_name = project
.language_server_statuses(cx) .language_server_statuses(cx)
.find(|(id, _)| server_id == *id) .find(|(id, _)| server_id == *id)
.map(|(_, status)| LanguageServerName::from(status.name.as_str())); .map(|(_, status)| status.name.clone());
language_server_name.map(|language_server_name| { language_server_name.map(|language_server_name| {
project.open_local_buffer_via_lsp( project.open_local_buffer_via_lsp(
lsp_location.uri.clone(), lsp_location.uri.clone(),

View file

@ -95,7 +95,7 @@ pub(super) fn refresh_linked_ranges(
let snapshot = buffer.read(cx).snapshot(); let snapshot = buffer.read(cx).snapshot();
let buffer_id = buffer.read(cx).remote_id(); let buffer_id = buffer.read(cx).remote_id();
let linked_edits_task = project.linked_edit(buffer, *start, cx); let linked_edits_task = project.linked_edits(buffer, *start, cx);
let highlights = move || async move { let highlights = move || async move {
let edits = linked_edits_task.await.log_err()?; let edits = linked_edits_task.await.log_err()?;
// Find the range containing our current selection. // Find the range containing our current selection.

View file

@ -411,30 +411,6 @@ impl LanguageRegistry {
cached cached
} }
pub fn get_or_register_lsp_adapter(
&self,
language_name: LanguageName,
server_name: LanguageServerName,
build_adapter: impl FnOnce() -> Arc<dyn LspAdapter> + 'static,
) -> Arc<CachedLspAdapter> {
let registered = self
.state
.write()
.lsp_adapters
.entry(language_name.clone())
.or_default()
.iter()
.find(|cached_adapter| cached_adapter.name == server_name)
.cloned();
if let Some(found) = registered {
found
} else {
let adapter = build_adapter();
self.register_lsp_adapter(language_name, adapter)
}
}
/// Register a fake language server and adapter /// Register a fake language server and adapter
/// The returned channel receives a new instance of the language server every time it is started /// The returned channel receives a new instance of the language server every time it is started
#[cfg(any(feature = "test-support", test))] #[cfg(any(feature = "test-support", test))]

View file

@ -2154,6 +2154,16 @@ impl LspCommand for GetHover {
} }
} }
impl GetCompletions {
pub fn can_resolve_completions(capabilities: &lsp::ServerCapabilities) -> bool {
capabilities
.completion_provider
.as_ref()
.and_then(|options| options.resolve_provider)
.unwrap_or(false)
}
}
#[async_trait(?Send)] #[async_trait(?Send)]
impl LspCommand for GetCompletions { impl LspCommand for GetCompletions {
type Response = CoreCompletionResponse; type Response = CoreCompletionResponse;
@ -2762,6 +2772,23 @@ impl GetCodeActions {
} }
} }
impl OnTypeFormatting {
pub fn supports_on_type_formatting(trigger: &str, capabilities: &ServerCapabilities) -> bool {
let Some(on_type_formatting_options) = &capabilities.document_on_type_formatting_provider
else {
return false;
};
on_type_formatting_options
.first_trigger_character
.contains(trigger)
|| on_type_formatting_options
.more_trigger_character
.iter()
.flatten()
.any(|chars| chars.contains(trigger))
}
}
#[async_trait(?Send)] #[async_trait(?Send)]
impl LspCommand for OnTypeFormatting { impl LspCommand for OnTypeFormatting {
type Response = Option<Transaction>; type Response = Option<Transaction>;
@ -2773,20 +2800,7 @@ impl LspCommand for OnTypeFormatting {
} }
fn check_capabilities(&self, capabilities: AdapterServerCapabilities) -> bool { fn check_capabilities(&self, capabilities: AdapterServerCapabilities) -> bool {
let Some(on_type_formatting_options) = &capabilities Self::supports_on_type_formatting(&self.trigger, &capabilities.server_capabilities)
.server_capabilities
.document_on_type_formatting_provider
else {
return false;
};
on_type_formatting_options
.first_trigger_character
.contains(&self.trigger)
|| on_type_formatting_options
.more_trigger_character
.iter()
.flatten()
.any(|chars| chars.contains(&self.trigger))
} }
fn to_lsp( fn to_lsp(
@ -4221,8 +4235,9 @@ impl LspCommand for GetDocumentColor {
server_capabilities server_capabilities
.server_capabilities .server_capabilities
.color_provider .color_provider
.as_ref()
.is_some_and(|capability| match capability { .is_some_and(|capability| match capability {
lsp::ColorProviderCapability::Simple(supported) => supported, lsp::ColorProviderCapability::Simple(supported) => *supported,
lsp::ColorProviderCapability::ColorProvider(..) => true, lsp::ColorProviderCapability::ColorProvider(..) => true,
lsp::ColorProviderCapability::Options(..) => true, lsp::ColorProviderCapability::Options(..) => true,
}) })

File diff suppressed because it is too large Load diff

View file

@ -277,6 +277,13 @@ pub enum Event {
LanguageServerAdded(LanguageServerId, LanguageServerName, Option<WorktreeId>), LanguageServerAdded(LanguageServerId, LanguageServerName, Option<WorktreeId>),
LanguageServerRemoved(LanguageServerId), LanguageServerRemoved(LanguageServerId),
LanguageServerLog(LanguageServerId, LanguageServerLogType, String), LanguageServerLog(LanguageServerId, LanguageServerLogType, String),
// [`lsp::notification::DidOpenTextDocument`] was sent to this server using the buffer data.
// Zed's buffer-related data is updated accordingly.
LanguageServerBufferRegistered {
server_id: LanguageServerId,
buffer_id: BufferId,
buffer_abs_path: PathBuf,
},
Toast { Toast {
notification_id: SharedString, notification_id: SharedString,
message: String, message: String,
@ -2931,8 +2938,8 @@ impl Project {
} }
LspStoreEvent::LanguageServerUpdate { LspStoreEvent::LanguageServerUpdate {
language_server_id, language_server_id,
message,
name, name,
message,
} => { } => {
if self.is_local() { if self.is_local() {
self.enqueue_buffer_ordered_message( self.enqueue_buffer_ordered_message(
@ -2944,6 +2951,32 @@ impl Project {
) )
.ok(); .ok();
} }
match message {
proto::update_language_server::Variant::MetadataUpdated(update) => {
if let Some(capabilities) = update
.capabilities
.as_ref()
.and_then(|capabilities| serde_json::from_str(capabilities).ok())
{
self.lsp_store.update(cx, |lsp_store, _| {
lsp_store
.lsp_server_capabilities
.insert(*language_server_id, capabilities);
});
}
}
proto::update_language_server::Variant::RegisteredForBuffer(update) => {
if let Some(buffer_id) = BufferId::new(update.buffer_id).ok() {
cx.emit(Event::LanguageServerBufferRegistered {
buffer_id,
server_id: *language_server_id,
buffer_abs_path: PathBuf::from(&update.buffer_abs_path),
});
}
}
_ => (),
}
} }
LspStoreEvent::Notification(message) => cx.emit(Event::Toast { LspStoreEvent::Notification(message) => cx.emit(Event::Toast {
notification_id: "lsp".into(), notification_id: "lsp".into(),
@ -3476,20 +3509,6 @@ impl Project {
}) })
} }
fn document_highlights_impl(
&mut self,
buffer: &Entity<Buffer>,
position: PointUtf16,
cx: &mut Context<Self>,
) -> Task<Result<Vec<DocumentHighlight>>> {
self.request_lsp(
buffer.clone(),
LanguageServerToQuery::FirstCapable,
GetDocumentHighlights { position },
cx,
)
}
pub fn document_highlights<T: ToPointUtf16>( pub fn document_highlights<T: ToPointUtf16>(
&mut self, &mut self,
buffer: &Entity<Buffer>, buffer: &Entity<Buffer>,
@ -3497,7 +3516,12 @@ impl Project {
cx: &mut Context<Self>, cx: &mut Context<Self>,
) -> Task<Result<Vec<DocumentHighlight>>> { ) -> Task<Result<Vec<DocumentHighlight>>> {
let position = position.to_point_utf16(buffer.read(cx)); let position = position.to_point_utf16(buffer.read(cx));
self.document_highlights_impl(buffer, position, cx) self.request_lsp(
buffer.clone(),
LanguageServerToQuery::FirstCapable,
GetDocumentHighlights { position },
cx,
)
} }
pub fn document_symbols( pub fn document_symbols(
@ -3598,14 +3622,14 @@ impl Project {
.update(cx, |lsp_store, cx| lsp_store.hover(buffer, position, cx)) .update(cx, |lsp_store, cx| lsp_store.hover(buffer, position, cx))
} }
pub fn linked_edit( pub fn linked_edits(
&self, &self,
buffer: &Entity<Buffer>, buffer: &Entity<Buffer>,
position: Anchor, position: Anchor,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) -> Task<Result<Vec<Range<Anchor>>>> { ) -> Task<Result<Vec<Range<Anchor>>>> {
self.lsp_store.update(cx, |lsp_store, cx| { self.lsp_store.update(cx, |lsp_store, cx| {
lsp_store.linked_edit(buffer, position, cx) lsp_store.linked_edits(buffer, position, cx)
}) })
} }
@ -3697,19 +3721,6 @@ impl Project {
}) })
} }
fn prepare_rename_impl(
&mut self,
buffer: Entity<Buffer>,
position: PointUtf16,
cx: &mut Context<Self>,
) -> Task<Result<PrepareRenameResponse>> {
self.request_lsp(
buffer,
LanguageServerToQuery::FirstCapable,
PrepareRename { position },
cx,
)
}
pub fn prepare_rename<T: ToPointUtf16>( pub fn prepare_rename<T: ToPointUtf16>(
&mut self, &mut self,
buffer: Entity<Buffer>, buffer: Entity<Buffer>,
@ -3717,7 +3728,12 @@ impl Project {
cx: &mut Context<Self>, cx: &mut Context<Self>,
) -> Task<Result<PrepareRenameResponse>> { ) -> Task<Result<PrepareRenameResponse>> {
let position = position.to_point_utf16(buffer.read(cx)); let position = position.to_point_utf16(buffer.read(cx));
self.prepare_rename_impl(buffer, position, cx) self.request_lsp(
buffer,
LanguageServerToQuery::FirstCapable,
PrepareRename { position },
cx,
)
} }
pub fn perform_rename<T: ToPointUtf16>( pub fn perform_rename<T: ToPointUtf16>(

View file

@ -1100,7 +1100,7 @@ async fn test_reporting_fs_changes_to_language_servers(cx: &mut gpui::TestAppCon
let fake_server = fake_servers.next().await.unwrap(); let fake_server = fake_servers.next().await.unwrap();
let (server_id, server_name) = lsp_store.read_with(cx, |lsp_store, _| { let (server_id, server_name) = lsp_store.read_with(cx, |lsp_store, _| {
let (id, status) = lsp_store.language_server_statuses().next().unwrap(); let (id, status) = lsp_store.language_server_statuses().next().unwrap();
(id, LanguageServerName::from(status.name.as_str())) (id, status.name.clone())
}); });
// Simulate jumping to a definition in a dependency outside of the worktree. // Simulate jumping to a definition in a dependency outside of the worktree.
@ -1698,7 +1698,7 @@ async fn test_restarting_server_with_diagnostics_running(cx: &mut gpui::TestAppC
name: "the-language-server", name: "the-language-server",
disk_based_diagnostics_sources: vec!["disk".into()], disk_based_diagnostics_sources: vec!["disk".into()],
disk_based_diagnostics_progress_token: Some(progress_token.into()), disk_based_diagnostics_progress_token: Some(progress_token.into()),
..Default::default() ..FakeLspAdapter::default()
}, },
); );
@ -1710,6 +1710,7 @@ async fn test_restarting_server_with_diagnostics_running(cx: &mut gpui::TestAppC
}) })
.await .await
.unwrap(); .unwrap();
let buffer_id = buffer.read_with(cx, |buffer, _| buffer.remote_id());
// Simulate diagnostics starting to update. // Simulate diagnostics starting to update.
let fake_server = fake_servers.next().await.unwrap(); let fake_server = fake_servers.next().await.unwrap();
fake_server.start_progress(progress_token).await; fake_server.start_progress(progress_token).await;
@ -1736,6 +1737,14 @@ async fn test_restarting_server_with_diagnostics_running(cx: &mut gpui::TestAppC
); );
assert_eq!(events.next().await.unwrap(), Event::RefreshInlayHints); assert_eq!(events.next().await.unwrap(), Event::RefreshInlayHints);
fake_server.start_progress(progress_token).await; fake_server.start_progress(progress_token).await;
assert_eq!(
events.next().await.unwrap(),
Event::LanguageServerBufferRegistered {
server_id: LanguageServerId(1),
buffer_id,
buffer_abs_path: PathBuf::from(path!("/dir/a.rs")),
}
);
assert_eq!( assert_eq!(
events.next().await.unwrap(), events.next().await.unwrap(),
Event::DiskBasedDiagnosticsStarted { Event::DiskBasedDiagnosticsStarted {

View file

@ -71,6 +71,7 @@ message RejoinedProject {
repeated WorktreeMetadata worktrees = 2; repeated WorktreeMetadata worktrees = 2;
repeated Collaborator collaborators = 3; repeated Collaborator collaborators = 3;
repeated LanguageServer language_servers = 4; repeated LanguageServer language_servers = 4;
repeated string language_server_capabilities = 5;
} }
message LeaveRoom {} message LeaveRoom {}
@ -199,6 +200,7 @@ message JoinProjectResponse {
repeated WorktreeMetadata worktrees = 2; repeated WorktreeMetadata worktrees = 2;
repeated Collaborator collaborators = 3; repeated Collaborator collaborators = 3;
repeated LanguageServer language_servers = 4; repeated LanguageServer language_servers = 4;
repeated string language_server_capabilities = 8;
ChannelRole role = 6; ChannelRole role = 6;
reserved 7; reserved 7;
} }

View file

@ -518,6 +518,7 @@ message LanguageServer {
message StartLanguageServer { message StartLanguageServer {
uint64 project_id = 1; uint64 project_id = 1;
LanguageServer server = 2; LanguageServer server = 2;
string capabilities = 3;
} }
message UpdateDiagnosticSummary { message UpdateDiagnosticSummary {
@ -545,6 +546,7 @@ message UpdateLanguageServer {
LspDiskBasedDiagnosticsUpdated disk_based_diagnostics_updated = 7; LspDiskBasedDiagnosticsUpdated disk_based_diagnostics_updated = 7;
StatusUpdate status_update = 9; StatusUpdate status_update = 9;
RegisteredForBuffer registered_for_buffer = 10; RegisteredForBuffer registered_for_buffer = 10;
ServerMetadataUpdated metadata_updated = 11;
} }
} }
@ -597,6 +599,11 @@ enum ServerBinaryStatus {
message RegisteredForBuffer { message RegisteredForBuffer {
string buffer_abs_path = 1; string buffer_abs_path = 1;
uint64 buffer_id = 2;
}
message ServerMetadataUpdated {
optional string capabilities = 1;
} }
message LanguageServerLog { message LanguageServerLog {