Stop waiting for part of LSP responses on remote Collab clients' part (#36557)
Instead of holding a connection for potentially long LSP queries (e.g. rust-analyzer might take minutes to look up a definition), disconnect right after sending the initial request and handle the follow-up responses later. As a bonus, this allows to cancel previously sent request on the local Collab clients' side due to this, as instead of holding and serving the old connection, local clients now can stop previous requests, if needed. Current PR does not convert all LSP requests to the new paradigm, but the problematic ones, deprecating `MultiLspQuery` and moving all its requests to the new paradigm. Release Notes: - Improved resource usage when querying LSP over Collab --------- Co-authored-by: David Kleingeld <git@davidsk.dev> Co-authored-by: Mikayla Maki <mikayla@zed.dev> Co-authored-by: David Kleingeld <davidsk@zed.dev>
This commit is contained in:
parent
c731bb6d91
commit
5dcb90858e
20 changed files with 1395 additions and 681 deletions
|
@ -1691,7 +1691,7 @@ impl SemanticsProvider for SlashCommandSemanticsProvider {
|
|||
buffer: &Entity<Buffer>,
|
||||
position: text::Anchor,
|
||||
cx: &mut App,
|
||||
) -> Option<Task<Vec<project::Hover>>> {
|
||||
) -> Option<Task<Option<Vec<project::Hover>>>> {
|
||||
let snapshot = buffer.read(cx).snapshot();
|
||||
let offset = position.to_offset(&snapshot);
|
||||
let (start, end) = self.range.get()?;
|
||||
|
@ -1699,14 +1699,14 @@ impl SemanticsProvider for SlashCommandSemanticsProvider {
|
|||
return None;
|
||||
}
|
||||
let range = snapshot.anchor_after(start)..snapshot.anchor_after(end);
|
||||
Some(Task::ready(vec![project::Hover {
|
||||
Some(Task::ready(Some(vec![project::Hover {
|
||||
contents: vec![project::HoverBlock {
|
||||
text: "Slash commands are not supported".into(),
|
||||
kind: project::HoverBlockKind::PlainText,
|
||||
}],
|
||||
range: Some(range),
|
||||
language: None,
|
||||
}]))
|
||||
}])))
|
||||
}
|
||||
|
||||
fn inline_values(
|
||||
|
@ -1756,7 +1756,7 @@ impl SemanticsProvider for SlashCommandSemanticsProvider {
|
|||
_position: text::Anchor,
|
||||
_kind: editor::GotoDefinitionKind,
|
||||
_cx: &mut App,
|
||||
) -> Option<Task<Result<Vec<project::LocationLink>>>> {
|
||||
) -> Option<Task<Result<Option<Vec<project::LocationLink>>>>> {
|
||||
None
|
||||
}
|
||||
|
||||
|
|
|
@ -400,6 +400,8 @@ impl Server {
|
|||
.add_request_handler(forward_mutating_project_request::<proto::SaveBuffer>)
|
||||
.add_request_handler(forward_mutating_project_request::<proto::BlameBuffer>)
|
||||
.add_request_handler(multi_lsp_query)
|
||||
.add_request_handler(lsp_query)
|
||||
.add_message_handler(broadcast_project_message_from_host::<proto::LspQueryResponse>)
|
||||
.add_request_handler(forward_mutating_project_request::<proto::RestartLanguageServers>)
|
||||
.add_request_handler(forward_mutating_project_request::<proto::StopLanguageServers>)
|
||||
.add_request_handler(forward_mutating_project_request::<proto::LinkedEditingRange>)
|
||||
|
@ -910,7 +912,9 @@ impl Server {
|
|||
user_id=field::Empty,
|
||||
login=field::Empty,
|
||||
impersonator=field::Empty,
|
||||
// todo(lsp) remove after Zed Stable hits v0.204.x
|
||||
multi_lsp_query_request=field::Empty,
|
||||
lsp_query_request=field::Empty,
|
||||
release_channel=field::Empty,
|
||||
{ TOTAL_DURATION_MS }=field::Empty,
|
||||
{ PROCESSING_DURATION_MS }=field::Empty,
|
||||
|
@ -2356,6 +2360,7 @@ where
|
|||
Ok(())
|
||||
}
|
||||
|
||||
// todo(lsp) remove after Zed Stable hits v0.204.x
|
||||
async fn multi_lsp_query(
|
||||
request: MultiLspQuery,
|
||||
response: Response<MultiLspQuery>,
|
||||
|
@ -2366,6 +2371,21 @@ async fn multi_lsp_query(
|
|||
forward_mutating_project_request(request, response, session).await
|
||||
}
|
||||
|
||||
async fn lsp_query(
|
||||
request: proto::LspQuery,
|
||||
response: Response<proto::LspQuery>,
|
||||
session: MessageContext,
|
||||
) -> Result<()> {
|
||||
let (name, should_write) = request.query_name_and_write_permissions();
|
||||
tracing::Span::current().record("lsp_query_request", name);
|
||||
tracing::info!("lsp_query message received");
|
||||
if should_write {
|
||||
forward_mutating_project_request(request, response, session).await
|
||||
} else {
|
||||
forward_read_only_project_request(request, response, session).await
|
||||
}
|
||||
}
|
||||
|
||||
/// Notify other participants that a new buffer has been created
|
||||
async fn create_buffer_for_peer(
|
||||
request: proto::CreateBufferForPeer,
|
||||
|
|
|
@ -15,13 +15,14 @@ use editor::{
|
|||
},
|
||||
};
|
||||
use fs::Fs;
|
||||
use futures::{StreamExt, lock::Mutex};
|
||||
use futures::{SinkExt, StreamExt, channel::mpsc, lock::Mutex};
|
||||
use gpui::{App, Rgba, TestAppContext, UpdateGlobal, VisualContext, VisualTestContext};
|
||||
use indoc::indoc;
|
||||
use language::{
|
||||
FakeLspAdapter,
|
||||
language_settings::{AllLanguageSettings, InlayHintSettings},
|
||||
};
|
||||
use lsp::LSP_REQUEST_TIMEOUT;
|
||||
use project::{
|
||||
ProjectPath, SERVER_PROGRESS_THROTTLE_TIMEOUT,
|
||||
lsp_store::lsp_ext_command::{ExpandedMacro, LspExtExpandMacro},
|
||||
|
@ -1017,6 +1018,211 @@ async fn test_collaborating_with_renames(cx_a: &mut TestAppContext, cx_b: &mut T
|
|||
})
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_slow_lsp_server(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
|
||||
let mut server = TestServer::start(cx_a.executor()).await;
|
||||
let client_a = server.create_client(cx_a, "user_a").await;
|
||||
let client_b = server.create_client(cx_b, "user_b").await;
|
||||
server
|
||||
.create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
|
||||
.await;
|
||||
let active_call_a = cx_a.read(ActiveCall::global);
|
||||
cx_b.update(editor::init);
|
||||
|
||||
let command_name = "test_command";
|
||||
let capabilities = lsp::ServerCapabilities {
|
||||
code_lens_provider: Some(lsp::CodeLensOptions {
|
||||
resolve_provider: None,
|
||||
}),
|
||||
execute_command_provider: Some(lsp::ExecuteCommandOptions {
|
||||
commands: vec![command_name.to_string()],
|
||||
..lsp::ExecuteCommandOptions::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()
|
||||
},
|
||||
);
|
||||
|
||||
client_a
|
||||
.fs()
|
||||
.insert_tree(
|
||||
path!("/dir"),
|
||||
json!({
|
||||
"one.rs": "const ONE: usize = 1;"
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
let (project_a, worktree_id) = client_a.build_local_project(path!("/dir"), cx_a).await;
|
||||
let project_id = active_call_a
|
||||
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
let project_b = client_b.join_remote_project(project_id, cx_b).await;
|
||||
|
||||
let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
|
||||
let editor_b = workspace_b
|
||||
.update_in(cx_b, |workspace, window, cx| {
|
||||
workspace.open_path((worktree_id, "one.rs"), None, true, window, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
.downcast::<Editor>()
|
||||
.unwrap();
|
||||
let (lsp_store_b, buffer_b) = editor_b.update(cx_b, |editor, cx| {
|
||||
let lsp_store = editor.project().unwrap().read(cx).lsp_store();
|
||||
let buffer = editor.buffer().read(cx).as_singleton().unwrap();
|
||||
(lsp_store, buffer)
|
||||
});
|
||||
let fake_language_server = fake_language_servers.next().await.unwrap();
|
||||
cx_a.run_until_parked();
|
||||
cx_b.run_until_parked();
|
||||
|
||||
let long_request_time = LSP_REQUEST_TIMEOUT / 2;
|
||||
let (request_started_tx, mut request_started_rx) = mpsc::unbounded();
|
||||
let requests_started = Arc::new(AtomicUsize::new(0));
|
||||
let requests_completed = Arc::new(AtomicUsize::new(0));
|
||||
let _lens_requests = fake_language_server
|
||||
.set_request_handler::<lsp::request::CodeLensRequest, _, _>({
|
||||
let request_started_tx = request_started_tx.clone();
|
||||
let requests_started = requests_started.clone();
|
||||
let requests_completed = requests_completed.clone();
|
||||
move |params, cx| {
|
||||
let mut request_started_tx = request_started_tx.clone();
|
||||
let requests_started = requests_started.clone();
|
||||
let requests_completed = requests_completed.clone();
|
||||
async move {
|
||||
assert_eq!(
|
||||
params.text_document.uri.as_str(),
|
||||
uri!("file:///dir/one.rs")
|
||||
);
|
||||
requests_started.fetch_add(1, atomic::Ordering::Release);
|
||||
request_started_tx.send(()).await.unwrap();
|
||||
cx.background_executor().timer(long_request_time).await;
|
||||
let i = requests_completed.fetch_add(1, atomic::Ordering::Release) + 1;
|
||||
Ok(Some(vec![lsp::CodeLens {
|
||||
range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 9)),
|
||||
command: Some(lsp::Command {
|
||||
title: format!("LSP Command {i}"),
|
||||
command: command_name.to_string(),
|
||||
arguments: None,
|
||||
}),
|
||||
data: None,
|
||||
}]))
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Move cursor to a location, this should trigger the code lens call.
|
||||
editor_b.update_in(cx_b, |editor, window, cx| {
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
||||
s.select_ranges([7..7])
|
||||
});
|
||||
});
|
||||
let () = request_started_rx.next().await.unwrap();
|
||||
assert_eq!(
|
||||
requests_started.load(atomic::Ordering::Acquire),
|
||||
1,
|
||||
"Selection change should have initiated the first request"
|
||||
);
|
||||
assert_eq!(
|
||||
requests_completed.load(atomic::Ordering::Acquire),
|
||||
0,
|
||||
"Slow requests should be running still"
|
||||
);
|
||||
let _first_task = lsp_store_b.update(cx_b, |lsp_store, cx| {
|
||||
lsp_store
|
||||
.forget_code_lens_task(buffer_b.read(cx).remote_id())
|
||||
.expect("Should have the fetch task started")
|
||||
});
|
||||
|
||||
editor_b.update_in(cx_b, |editor, window, cx| {
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
||||
s.select_ranges([1..1])
|
||||
});
|
||||
});
|
||||
let () = request_started_rx.next().await.unwrap();
|
||||
assert_eq!(
|
||||
requests_started.load(atomic::Ordering::Acquire),
|
||||
2,
|
||||
"Selection change should have initiated the second request"
|
||||
);
|
||||
assert_eq!(
|
||||
requests_completed.load(atomic::Ordering::Acquire),
|
||||
0,
|
||||
"Slow requests should be running still"
|
||||
);
|
||||
let _second_task = lsp_store_b.update(cx_b, |lsp_store, cx| {
|
||||
lsp_store
|
||||
.forget_code_lens_task(buffer_b.read(cx).remote_id())
|
||||
.expect("Should have the fetch task started for the 2nd time")
|
||||
});
|
||||
|
||||
editor_b.update_in(cx_b, |editor, window, cx| {
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
||||
s.select_ranges([2..2])
|
||||
});
|
||||
});
|
||||
let () = request_started_rx.next().await.unwrap();
|
||||
assert_eq!(
|
||||
requests_started.load(atomic::Ordering::Acquire),
|
||||
3,
|
||||
"Selection change should have initiated the third request"
|
||||
);
|
||||
assert_eq!(
|
||||
requests_completed.load(atomic::Ordering::Acquire),
|
||||
0,
|
||||
"Slow requests should be running still"
|
||||
);
|
||||
|
||||
_first_task.await.unwrap();
|
||||
_second_task.await.unwrap();
|
||||
cx_b.run_until_parked();
|
||||
assert_eq!(
|
||||
requests_started.load(atomic::Ordering::Acquire),
|
||||
3,
|
||||
"No selection changes should trigger no more code lens requests"
|
||||
);
|
||||
assert_eq!(
|
||||
requests_completed.load(atomic::Ordering::Acquire),
|
||||
3,
|
||||
"After enough time, all 3 LSP requests should have been served by the language server"
|
||||
);
|
||||
let resulting_lens_actions = editor_b
|
||||
.update(cx_b, |editor, cx| {
|
||||
let lsp_store = editor.project().unwrap().read(cx).lsp_store();
|
||||
lsp_store.update(cx, |lsp_store, cx| {
|
||||
lsp_store.code_lens_actions(&buffer_b, cx)
|
||||
})
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
resulting_lens_actions.len(),
|
||||
1,
|
||||
"Should have fetched one code lens action, but got: {resulting_lens_actions:?}"
|
||||
);
|
||||
assert_eq!(
|
||||
resulting_lens_actions.first().unwrap().lsp_action.title(),
|
||||
"LSP Command 3",
|
||||
"Only the final code lens action should be in the data"
|
||||
)
|
||||
}
|
||||
|
||||
#[gpui::test(iterations = 10)]
|
||||
async fn test_language_server_statuses(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
|
||||
let mut server = TestServer::start(cx_a.executor()).await;
|
||||
|
|
|
@ -4850,6 +4850,7 @@ async fn test_definition(
|
|||
let definitions_1 = project_b
|
||||
.update(cx_b, |p, cx| p.definitions(&buffer_b, 23, cx))
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
cx_b.read(|cx| {
|
||||
assert_eq!(
|
||||
|
@ -4885,6 +4886,7 @@ async fn test_definition(
|
|||
let definitions_2 = project_b
|
||||
.update(cx_b, |p, cx| p.definitions(&buffer_b, 33, cx))
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
cx_b.read(|cx| {
|
||||
assert_eq!(definitions_2.len(), 1);
|
||||
|
@ -4922,6 +4924,7 @@ async fn test_definition(
|
|||
let type_definitions = project_b
|
||||
.update(cx_b, |p, cx| p.type_definitions(&buffer_b, 7, cx))
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
cx_b.read(|cx| {
|
||||
assert_eq!(
|
||||
|
@ -5060,7 +5063,7 @@ async fn test_references(
|
|||
])))
|
||||
.unwrap();
|
||||
|
||||
let references = references.await.unwrap();
|
||||
let references = references.await.unwrap().unwrap();
|
||||
executor.run_until_parked();
|
||||
project_b.read_with(cx_b, |project, cx| {
|
||||
// User is informed that a request is no longer pending.
|
||||
|
@ -5104,7 +5107,7 @@ async fn test_references(
|
|||
lsp_response_tx
|
||||
.unbounded_send(Err(anyhow!("can't find references")))
|
||||
.unwrap();
|
||||
assert_eq!(references.await.unwrap(), []);
|
||||
assert_eq!(references.await.unwrap().unwrap(), []);
|
||||
|
||||
// User is informed that the request is no longer pending.
|
||||
executor.run_until_parked();
|
||||
|
@ -5505,7 +5508,8 @@ async fn test_lsp_hover(
|
|||
// Request hover information as the guest.
|
||||
let mut hovers = project_b
|
||||
.update(cx_b, |p, cx| p.hover(&buffer_b, 22, cx))
|
||||
.await;
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
hovers.len(),
|
||||
2,
|
||||
|
@ -5764,7 +5768,7 @@ async fn test_open_buffer_while_getting_definition_pointing_to_it(
|
|||
definitions = project_b.update(cx_b, |p, cx| p.definitions(&buffer_b1, 23, cx));
|
||||
}
|
||||
|
||||
let definitions = definitions.await.unwrap();
|
||||
let definitions = definitions.await.unwrap().unwrap();
|
||||
assert_eq!(
|
||||
definitions.len(),
|
||||
1,
|
||||
|
|
|
@ -15710,7 +15710,9 @@ impl Editor {
|
|||
};
|
||||
|
||||
cx.spawn_in(window, async move |editor, cx| {
|
||||
let definitions = definitions.await?;
|
||||
let Some(definitions) = definitions.await? else {
|
||||
return Ok(Navigated::No);
|
||||
};
|
||||
let navigated = editor
|
||||
.update_in(cx, |editor, window, cx| {
|
||||
editor.navigate_to_hover_links(
|
||||
|
@ -16052,7 +16054,9 @@ impl Editor {
|
|||
}
|
||||
});
|
||||
|
||||
let locations = references.await?;
|
||||
let Some(locations) = references.await? else {
|
||||
return anyhow::Ok(Navigated::No);
|
||||
};
|
||||
if locations.is_empty() {
|
||||
return anyhow::Ok(Navigated::No);
|
||||
}
|
||||
|
@ -21837,7 +21841,7 @@ pub trait SemanticsProvider {
|
|||
buffer: &Entity<Buffer>,
|
||||
position: text::Anchor,
|
||||
cx: &mut App,
|
||||
) -> Option<Task<Vec<project::Hover>>>;
|
||||
) -> Option<Task<Option<Vec<project::Hover>>>>;
|
||||
|
||||
fn inline_values(
|
||||
&self,
|
||||
|
@ -21876,7 +21880,7 @@ pub trait SemanticsProvider {
|
|||
position: text::Anchor,
|
||||
kind: GotoDefinitionKind,
|
||||
cx: &mut App,
|
||||
) -> Option<Task<Result<Vec<LocationLink>>>>;
|
||||
) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
|
||||
|
||||
fn range_for_rename(
|
||||
&self,
|
||||
|
@ -21989,7 +21993,13 @@ impl CodeActionProvider for Entity<Project> {
|
|||
Ok(code_lens_actions
|
||||
.context("code lens fetch")?
|
||||
.into_iter()
|
||||
.chain(code_actions.context("code action fetch")?)
|
||||
.flatten()
|
||||
.chain(
|
||||
code_actions
|
||||
.context("code action fetch")?
|
||||
.into_iter()
|
||||
.flatten(),
|
||||
)
|
||||
.collect())
|
||||
})
|
||||
})
|
||||
|
@ -22284,7 +22294,7 @@ impl SemanticsProvider for Entity<Project> {
|
|||
buffer: &Entity<Buffer>,
|
||||
position: text::Anchor,
|
||||
cx: &mut App,
|
||||
) -> Option<Task<Vec<project::Hover>>> {
|
||||
) -> Option<Task<Option<Vec<project::Hover>>>> {
|
||||
Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
|
||||
}
|
||||
|
||||
|
@ -22305,7 +22315,7 @@ impl SemanticsProvider for Entity<Project> {
|
|||
position: text::Anchor,
|
||||
kind: GotoDefinitionKind,
|
||||
cx: &mut App,
|
||||
) -> Option<Task<Result<Vec<LocationLink>>>> {
|
||||
) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
|
||||
Some(self.update(cx, |project, cx| match kind {
|
||||
GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
|
||||
GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
|
||||
|
|
|
@ -559,7 +559,7 @@ pub fn show_link_definition(
|
|||
provider.definitions(&buffer, buffer_position, preferred_kind, cx)
|
||||
})?;
|
||||
if let Some(task) = task {
|
||||
task.await.ok().map(|definition_result| {
|
||||
task.await.ok().flatten().map(|definition_result| {
|
||||
(
|
||||
definition_result.iter().find_map(|link| {
|
||||
link.origin.as_ref().and_then(|origin| {
|
||||
|
|
|
@ -428,7 +428,7 @@ fn show_hover(
|
|||
};
|
||||
|
||||
let hovers_response = if let Some(hover_request) = hover_request {
|
||||
hover_request.await
|
||||
hover_request.await.unwrap_or_default()
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
|
|
|
@ -431,7 +431,7 @@ impl SemanticsProvider for BranchBufferSemanticsProvider {
|
|||
buffer: &Entity<Buffer>,
|
||||
position: text::Anchor,
|
||||
cx: &mut App,
|
||||
) -> Option<Task<Vec<project::Hover>>> {
|
||||
) -> Option<Task<Option<Vec<project::Hover>>>> {
|
||||
let buffer = self.to_base(buffer, &[position], cx)?;
|
||||
self.0.hover(&buffer, position, cx)
|
||||
}
|
||||
|
@ -490,7 +490,7 @@ impl SemanticsProvider for BranchBufferSemanticsProvider {
|
|||
position: text::Anchor,
|
||||
kind: crate::GotoDefinitionKind,
|
||||
cx: &mut App,
|
||||
) -> Option<Task<anyhow::Result<Vec<project::LocationLink>>>> {
|
||||
) -> Option<Task<anyhow::Result<Option<Vec<project::LocationLink>>>>> {
|
||||
let buffer = self.to_base(buffer, &[position], cx)?;
|
||||
self.0.definitions(&buffer, position, kind, cx)
|
||||
}
|
||||
|
|
|
@ -182,7 +182,9 @@ impl Editor {
|
|||
let signature_help = task.await;
|
||||
editor
|
||||
.update(cx, |editor, cx| {
|
||||
let Some(mut signature_help) = signature_help.into_iter().next() else {
|
||||
let Some(mut signature_help) =
|
||||
signature_help.unwrap_or_default().into_iter().next()
|
||||
else {
|
||||
editor
|
||||
.signature_help_state
|
||||
.hide(SignatureHelpHiddenBy::AutoClose);
|
||||
|
|
|
@ -45,7 +45,7 @@ use util::{ConnectionResult, ResultExt, TryFutureExt, redact};
|
|||
const JSON_RPC_VERSION: &str = "2.0";
|
||||
const CONTENT_LEN_HEADER: &str = "Content-Length: ";
|
||||
|
||||
const LSP_REQUEST_TIMEOUT: Duration = Duration::from_secs(60 * 2);
|
||||
pub const LSP_REQUEST_TIMEOUT: Duration = Duration::from_secs(60 * 2);
|
||||
const SERVER_SHUTDOWN_TIMEOUT: Duration = Duration::from_secs(5);
|
||||
|
||||
type NotificationHandler = Box<dyn Send + FnMut(Option<RequestId>, Value, &mut AsyncApp)>;
|
||||
|
|
|
@ -3444,8 +3444,7 @@ impl LspCommand for GetCodeLens {
|
|||
capabilities
|
||||
.server_capabilities
|
||||
.code_lens_provider
|
||||
.as_ref()
|
||||
.is_some_and(|code_lens_options| code_lens_options.resolve_provider.unwrap_or(false))
|
||||
.is_some()
|
||||
}
|
||||
|
||||
fn to_lsp(
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -3415,7 +3415,7 @@ impl Project {
|
|||
buffer: &Entity<Buffer>,
|
||||
position: T,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<Vec<LocationLink>>> {
|
||||
) -> Task<Result<Option<Vec<LocationLink>>>> {
|
||||
let position = position.to_point_utf16(buffer.read(cx));
|
||||
let guard = self.retain_remotely_created_models(cx);
|
||||
let task = self.lsp_store.update(cx, |lsp_store, cx| {
|
||||
|
@ -3433,7 +3433,7 @@ impl Project {
|
|||
buffer: &Entity<Buffer>,
|
||||
position: T,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<Vec<LocationLink>>> {
|
||||
) -> Task<Result<Option<Vec<LocationLink>>>> {
|
||||
let position = position.to_point_utf16(buffer.read(cx));
|
||||
let guard = self.retain_remotely_created_models(cx);
|
||||
let task = self.lsp_store.update(cx, |lsp_store, cx| {
|
||||
|
@ -3451,7 +3451,7 @@ impl Project {
|
|||
buffer: &Entity<Buffer>,
|
||||
position: T,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<Vec<LocationLink>>> {
|
||||
) -> Task<Result<Option<Vec<LocationLink>>>> {
|
||||
let position = position.to_point_utf16(buffer.read(cx));
|
||||
let guard = self.retain_remotely_created_models(cx);
|
||||
let task = self.lsp_store.update(cx, |lsp_store, cx| {
|
||||
|
@ -3469,7 +3469,7 @@ impl Project {
|
|||
buffer: &Entity<Buffer>,
|
||||
position: T,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<Vec<LocationLink>>> {
|
||||
) -> Task<Result<Option<Vec<LocationLink>>>> {
|
||||
let position = position.to_point_utf16(buffer.read(cx));
|
||||
let guard = self.retain_remotely_created_models(cx);
|
||||
let task = self.lsp_store.update(cx, |lsp_store, cx| {
|
||||
|
@ -3487,7 +3487,7 @@ impl Project {
|
|||
buffer: &Entity<Buffer>,
|
||||
position: T,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<Vec<Location>>> {
|
||||
) -> Task<Result<Option<Vec<Location>>>> {
|
||||
let position = position.to_point_utf16(buffer.read(cx));
|
||||
let guard = self.retain_remotely_created_models(cx);
|
||||
let task = self.lsp_store.update(cx, |lsp_store, cx| {
|
||||
|
@ -3585,23 +3585,12 @@ impl Project {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn signature_help<T: ToPointUtf16>(
|
||||
&self,
|
||||
buffer: &Entity<Buffer>,
|
||||
position: T,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Vec<SignatureHelp>> {
|
||||
self.lsp_store.update(cx, |lsp_store, cx| {
|
||||
lsp_store.signature_help(buffer, position, cx)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn hover<T: ToPointUtf16>(
|
||||
&self,
|
||||
buffer: &Entity<Buffer>,
|
||||
position: T,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Vec<Hover>> {
|
||||
) -> Task<Option<Vec<Hover>>> {
|
||||
let position = position.to_point_utf16(buffer.read(cx));
|
||||
self.lsp_store
|
||||
.update(cx, |lsp_store, cx| lsp_store.hover(buffer, position, cx))
|
||||
|
@ -3637,7 +3626,7 @@ impl Project {
|
|||
range: Range<T>,
|
||||
kinds: Option<Vec<CodeActionKind>>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<Vec<CodeAction>>> {
|
||||
) -> Task<Result<Option<Vec<CodeAction>>>> {
|
||||
let buffer = buffer_handle.read(cx);
|
||||
let range = buffer.anchor_before(range.start)..buffer.anchor_before(range.end);
|
||||
self.lsp_store.update(cx, |lsp_store, cx| {
|
||||
|
@ -3650,7 +3639,7 @@ impl Project {
|
|||
buffer: &Entity<Buffer>,
|
||||
range: Range<T>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<Vec<CodeAction>>> {
|
||||
) -> Task<Result<Option<Vec<CodeAction>>>> {
|
||||
let snapshot = buffer.read(cx).snapshot();
|
||||
let range = range.to_point(&snapshot);
|
||||
let range_start = snapshot.anchor_before(range.start);
|
||||
|
@ -3668,16 +3657,18 @@ impl Project {
|
|||
let mut code_lens_actions = code_lens_actions
|
||||
.await
|
||||
.map_err(|e| anyhow!("code lens fetch failed: {e:#}"))?;
|
||||
code_lens_actions.retain(|code_lens_action| {
|
||||
range
|
||||
.start
|
||||
.cmp(&code_lens_action.range.start, &snapshot)
|
||||
.is_ge()
|
||||
&& range
|
||||
.end
|
||||
.cmp(&code_lens_action.range.end, &snapshot)
|
||||
.is_le()
|
||||
});
|
||||
if let Some(code_lens_actions) = &mut code_lens_actions {
|
||||
code_lens_actions.retain(|code_lens_action| {
|
||||
range
|
||||
.start
|
||||
.cmp(&code_lens_action.range.start, &snapshot)
|
||||
.is_ge()
|
||||
&& range
|
||||
.end
|
||||
.cmp(&code_lens_action.range.end, &snapshot)
|
||||
.is_le()
|
||||
});
|
||||
}
|
||||
Ok(code_lens_actions)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -3005,6 +3005,7 @@ async fn test_definition(cx: &mut gpui::TestAppContext) {
|
|||
let mut definitions = project
|
||||
.update(cx, |project, cx| project.definitions(&buffer, 22, cx))
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
// Assert no new language server started
|
||||
|
@ -3519,7 +3520,7 @@ async fn test_apply_code_actions_with_commands(cx: &mut gpui::TestAppContext) {
|
|||
.next()
|
||||
.await;
|
||||
|
||||
let action = actions.await.unwrap()[0].clone();
|
||||
let action = actions.await.unwrap().unwrap()[0].clone();
|
||||
let apply = project.update(cx, |project, cx| {
|
||||
project.apply_code_action(buffer.clone(), action, true, cx)
|
||||
});
|
||||
|
@ -6110,6 +6111,7 @@ async fn test_multiple_language_server_hovers(cx: &mut gpui::TestAppContext) {
|
|||
hover_task
|
||||
.await
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.map(|hover| hover.contents.iter().map(|block| &block.text).join("|"))
|
||||
.sorted()
|
||||
.collect::<Vec<_>>(),
|
||||
|
@ -6183,6 +6185,7 @@ async fn test_hovers_with_empty_parts(cx: &mut gpui::TestAppContext) {
|
|||
hover_task
|
||||
.await
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.map(|hover| hover.contents.iter().map(|block| &block.text).join("|"))
|
||||
.sorted()
|
||||
.collect::<Vec<_>>(),
|
||||
|
@ -6261,7 +6264,7 @@ async fn test_code_actions_only_kinds(cx: &mut gpui::TestAppContext) {
|
|||
.await
|
||||
.expect("The code action request should have been triggered");
|
||||
|
||||
let code_actions = code_actions_task.await.unwrap();
|
||||
let code_actions = code_actions_task.await.unwrap().unwrap();
|
||||
assert_eq!(code_actions.len(), 1);
|
||||
assert_eq!(
|
||||
code_actions[0].lsp_action.action_kind(),
|
||||
|
@ -6420,6 +6423,7 @@ async fn test_multiple_language_server_actions(cx: &mut gpui::TestAppContext) {
|
|||
code_actions_task
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.map(|code_action| code_action.lsp_action.title().to_owned())
|
||||
.sorted()
|
||||
|
|
|
@ -753,28 +753,47 @@ message TextEdit {
|
|||
PointUtf16 lsp_range_end = 3;
|
||||
}
|
||||
|
||||
message MultiLspQuery {
|
||||
message LspQuery {
|
||||
uint64 project_id = 1;
|
||||
uint64 buffer_id = 2;
|
||||
repeated VectorClockEntry version = 3;
|
||||
oneof strategy {
|
||||
AllLanguageServers all = 4;
|
||||
}
|
||||
uint64 lsp_request_id = 2;
|
||||
oneof request {
|
||||
GetReferences get_references = 3;
|
||||
GetDocumentColor get_document_color = 4;
|
||||
GetHover get_hover = 5;
|
||||
GetCodeActions get_code_actions = 6;
|
||||
GetSignatureHelp get_signature_help = 7;
|
||||
GetCodeLens get_code_lens = 8;
|
||||
GetDocumentDiagnostics get_document_diagnostics = 9;
|
||||
GetDocumentColor get_document_color = 10;
|
||||
GetDefinition get_definition = 11;
|
||||
GetDeclaration get_declaration = 12;
|
||||
GetTypeDefinition get_type_definition = 13;
|
||||
GetImplementation get_implementation = 14;
|
||||
GetReferences get_references = 15;
|
||||
GetDefinition get_definition = 10;
|
||||
GetDeclaration get_declaration = 11;
|
||||
GetTypeDefinition get_type_definition = 12;
|
||||
GetImplementation get_implementation = 13;
|
||||
}
|
||||
}
|
||||
|
||||
message LspQueryResponse {
|
||||
uint64 project_id = 1;
|
||||
uint64 lsp_request_id = 2;
|
||||
repeated LspResponse responses = 3;
|
||||
}
|
||||
|
||||
message LspResponse {
|
||||
oneof response {
|
||||
GetHoverResponse get_hover_response = 1;
|
||||
GetCodeActionsResponse get_code_actions_response = 2;
|
||||
GetSignatureHelpResponse get_signature_help_response = 3;
|
||||
GetCodeLensResponse get_code_lens_response = 4;
|
||||
GetDocumentDiagnosticsResponse get_document_diagnostics_response = 5;
|
||||
GetDocumentColorResponse get_document_color_response = 6;
|
||||
GetDefinitionResponse get_definition_response = 8;
|
||||
GetDeclarationResponse get_declaration_response = 9;
|
||||
GetTypeDefinitionResponse get_type_definition_response = 10;
|
||||
GetImplementationResponse get_implementation_response = 11;
|
||||
GetReferencesResponse get_references_response = 12;
|
||||
}
|
||||
uint64 server_id = 7;
|
||||
}
|
||||
|
||||
message AllLanguageServers {}
|
||||
|
||||
message LanguageServerSelector {
|
||||
|
@ -798,27 +817,6 @@ message StopLanguageServers {
|
|||
bool all = 4;
|
||||
}
|
||||
|
||||
message MultiLspQueryResponse {
|
||||
repeated LspResponse responses = 1;
|
||||
}
|
||||
|
||||
message LspResponse {
|
||||
oneof response {
|
||||
GetHoverResponse get_hover_response = 1;
|
||||
GetCodeActionsResponse get_code_actions_response = 2;
|
||||
GetSignatureHelpResponse get_signature_help_response = 3;
|
||||
GetCodeLensResponse get_code_lens_response = 4;
|
||||
GetDocumentDiagnosticsResponse get_document_diagnostics_response = 5;
|
||||
GetDocumentColorResponse get_document_color_response = 6;
|
||||
GetDefinitionResponse get_definition_response = 8;
|
||||
GetDeclarationResponse get_declaration_response = 9;
|
||||
GetTypeDefinitionResponse get_type_definition_response = 10;
|
||||
GetImplementationResponse get_implementation_response = 11;
|
||||
GetReferencesResponse get_references_response = 12;
|
||||
}
|
||||
uint64 server_id = 7;
|
||||
}
|
||||
|
||||
message LspExtRunnables {
|
||||
uint64 project_id = 1;
|
||||
uint64 buffer_id = 2;
|
||||
|
@ -909,3 +907,30 @@ message PullWorkspaceDiagnostics {
|
|||
uint64 project_id = 1;
|
||||
uint64 server_id = 2;
|
||||
}
|
||||
|
||||
// todo(lsp) remove after Zed Stable hits v0.204.x
|
||||
message MultiLspQuery {
|
||||
uint64 project_id = 1;
|
||||
uint64 buffer_id = 2;
|
||||
repeated VectorClockEntry version = 3;
|
||||
oneof strategy {
|
||||
AllLanguageServers all = 4;
|
||||
}
|
||||
oneof request {
|
||||
GetHover get_hover = 5;
|
||||
GetCodeActions get_code_actions = 6;
|
||||
GetSignatureHelp get_signature_help = 7;
|
||||
GetCodeLens get_code_lens = 8;
|
||||
GetDocumentDiagnostics get_document_diagnostics = 9;
|
||||
GetDocumentColor get_document_color = 10;
|
||||
GetDefinition get_definition = 11;
|
||||
GetDeclaration get_declaration = 12;
|
||||
GetTypeDefinition get_type_definition = 13;
|
||||
GetImplementation get_implementation = 14;
|
||||
GetReferences get_references = 15;
|
||||
}
|
||||
}
|
||||
|
||||
message MultiLspQueryResponse {
|
||||
repeated LspResponse responses = 1;
|
||||
}
|
||||
|
|
|
@ -393,7 +393,10 @@ message Envelope {
|
|||
GetCrashFilesResponse get_crash_files_response = 362;
|
||||
|
||||
GitClone git_clone = 363;
|
||||
GitCloneResponse git_clone_response = 364; // current max
|
||||
GitCloneResponse git_clone_response = 364;
|
||||
|
||||
LspQuery lsp_query = 365;
|
||||
LspQueryResponse lsp_query_response = 366; // current max
|
||||
}
|
||||
|
||||
reserved 87 to 88;
|
||||
|
|
|
@ -69,3 +69,32 @@ macro_rules! entity_messages {
|
|||
})*
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! lsp_messages {
|
||||
($(($request_name:ident, $response_name:ident, $stop_previous_requests:expr)),* $(,)?) => {
|
||||
$(impl LspRequestMessage for $request_name {
|
||||
type Response = $response_name;
|
||||
|
||||
fn to_proto_query(self) -> $crate::lsp_query::Request {
|
||||
$crate::lsp_query::Request::$request_name(self)
|
||||
}
|
||||
|
||||
fn response_to_proto_query(response: Self::Response) -> $crate::lsp_response::Response {
|
||||
$crate::lsp_response::Response::$response_name(response)
|
||||
}
|
||||
|
||||
fn buffer_id(&self) -> u64 {
|
||||
self.buffer_id
|
||||
}
|
||||
|
||||
fn buffer_version(&self) -> &[$crate::VectorClockEntry] {
|
||||
&self.version
|
||||
}
|
||||
|
||||
fn stop_previous_requests() -> bool {
|
||||
$stop_previous_requests
|
||||
}
|
||||
})*
|
||||
};
|
||||
}
|
||||
|
|
|
@ -169,6 +169,9 @@ messages!(
|
|||
(MarkNotificationRead, Foreground),
|
||||
(MoveChannel, Foreground),
|
||||
(ReorderChannel, Foreground),
|
||||
(LspQuery, Background),
|
||||
(LspQueryResponse, Background),
|
||||
// todo(lsp) remove after Zed Stable hits v0.204.x
|
||||
(MultiLspQuery, Background),
|
||||
(MultiLspQueryResponse, Background),
|
||||
(OnTypeFormatting, Background),
|
||||
|
@ -426,7 +429,10 @@ request_messages!(
|
|||
(SetRoomParticipantRole, Ack),
|
||||
(BlameBuffer, BlameBufferResponse),
|
||||
(RejoinRemoteProjects, RejoinRemoteProjectsResponse),
|
||||
// todo(lsp) remove after Zed Stable hits v0.204.x
|
||||
(MultiLspQuery, MultiLspQueryResponse),
|
||||
(LspQuery, Ack),
|
||||
(LspQueryResponse, Ack),
|
||||
(RestartLanguageServers, Ack),
|
||||
(StopLanguageServers, Ack),
|
||||
(OpenContext, OpenContextResponse),
|
||||
|
@ -478,6 +484,20 @@ request_messages!(
|
|||
(GitClone, GitCloneResponse)
|
||||
);
|
||||
|
||||
lsp_messages!(
|
||||
(GetReferences, GetReferencesResponse, true),
|
||||
(GetDocumentColor, GetDocumentColorResponse, true),
|
||||
(GetHover, GetHoverResponse, true),
|
||||
(GetCodeActions, GetCodeActionsResponse, true),
|
||||
(GetSignatureHelp, GetSignatureHelpResponse, true),
|
||||
(GetCodeLens, GetCodeLensResponse, true),
|
||||
(GetDocumentDiagnostics, GetDocumentDiagnosticsResponse, true),
|
||||
(GetDefinition, GetDefinitionResponse, true),
|
||||
(GetDeclaration, GetDeclarationResponse, true),
|
||||
(GetTypeDefinition, GetTypeDefinitionResponse, true),
|
||||
(GetImplementation, GetImplementationResponse, true),
|
||||
);
|
||||
|
||||
entity_messages!(
|
||||
{project_id, ShareProject},
|
||||
AddProjectCollaborator,
|
||||
|
@ -520,6 +540,9 @@ entity_messages!(
|
|||
LeaveProject,
|
||||
LinkedEditingRange,
|
||||
LoadCommitDiff,
|
||||
LspQuery,
|
||||
LspQueryResponse,
|
||||
// todo(lsp) remove after Zed Stable hits v0.204.x
|
||||
MultiLspQuery,
|
||||
RestartLanguageServers,
|
||||
StopLanguageServers,
|
||||
|
@ -777,6 +800,28 @@ pub fn split_repository_update(
|
|||
}])
|
||||
}
|
||||
|
||||
impl LspQuery {
|
||||
pub fn query_name_and_write_permissions(&self) -> (&str, bool) {
|
||||
match self.request {
|
||||
Some(lsp_query::Request::GetHover(_)) => ("GetHover", false),
|
||||
Some(lsp_query::Request::GetCodeActions(_)) => ("GetCodeActions", true),
|
||||
Some(lsp_query::Request::GetSignatureHelp(_)) => ("GetSignatureHelp", false),
|
||||
Some(lsp_query::Request::GetCodeLens(_)) => ("GetCodeLens", true),
|
||||
Some(lsp_query::Request::GetDocumentDiagnostics(_)) => {
|
||||
("GetDocumentDiagnostics", false)
|
||||
}
|
||||
Some(lsp_query::Request::GetDefinition(_)) => ("GetDefinition", false),
|
||||
Some(lsp_query::Request::GetDeclaration(_)) => ("GetDeclaration", false),
|
||||
Some(lsp_query::Request::GetTypeDefinition(_)) => ("GetTypeDefinition", false),
|
||||
Some(lsp_query::Request::GetImplementation(_)) => ("GetImplementation", false),
|
||||
Some(lsp_query::Request::GetReferences(_)) => ("GetReferences", false),
|
||||
Some(lsp_query::Request::GetDocumentColor(_)) => ("GetDocumentColor", false),
|
||||
None => ("<unknown>", true),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// todo(lsp) remove after Zed Stable hits v0.204.x
|
||||
impl MultiLspQuery {
|
||||
pub fn request_str(&self) -> &str {
|
||||
match self.request {
|
||||
|
|
|
@ -31,6 +31,58 @@ pub trait RequestMessage: EnvelopedMessage {
|
|||
type Response: EnvelopedMessage;
|
||||
}
|
||||
|
||||
/// A trait to bind LSP request and responses for the proto layer.
|
||||
/// Should be used for every LSP request that has to traverse through the proto layer.
|
||||
///
|
||||
/// `lsp_messages` macro in the same crate provides a convenient way to implement this.
|
||||
pub trait LspRequestMessage: EnvelopedMessage {
|
||||
type Response: EnvelopedMessage;
|
||||
|
||||
fn to_proto_query(self) -> crate::lsp_query::Request;
|
||||
|
||||
fn response_to_proto_query(response: Self::Response) -> crate::lsp_response::Response;
|
||||
|
||||
fn buffer_id(&self) -> u64;
|
||||
|
||||
fn buffer_version(&self) -> &[crate::VectorClockEntry];
|
||||
|
||||
/// Whether to deduplicate the requests, or keep the previous ones running when another
|
||||
/// request of the same kind is processed.
|
||||
fn stop_previous_requests() -> bool;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct LspRequestId(pub u64);
|
||||
|
||||
/// A response from a single language server.
|
||||
/// There could be multiple responses for a single LSP request,
|
||||
/// from different servers.
|
||||
pub struct ProtoLspResponse<R> {
|
||||
pub server_id: u64,
|
||||
pub response: R,
|
||||
}
|
||||
|
||||
impl ProtoLspResponse<Box<dyn AnyTypedEnvelope>> {
|
||||
pub fn into_response<T: LspRequestMessage>(self) -> Result<ProtoLspResponse<T::Response>> {
|
||||
let envelope = self
|
||||
.response
|
||||
.into_any()
|
||||
.downcast::<TypedEnvelope<T::Response>>()
|
||||
.map_err(|_| {
|
||||
anyhow::anyhow!(
|
||||
"cannot downcast LspResponse to {} for message {}",
|
||||
T::Response::NAME,
|
||||
T::NAME,
|
||||
)
|
||||
})?;
|
||||
|
||||
Ok(ProtoLspResponse {
|
||||
server_id: self.server_id,
|
||||
response: envelope.payload,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub trait AnyTypedEnvelope: Any + Send + Sync {
|
||||
fn payload_type_id(&self) -> TypeId;
|
||||
fn payload_type_name(&self) -> &'static str;
|
||||
|
|
|
@ -1,35 +1,48 @@
|
|||
use anyhow::Context;
|
||||
use anyhow::{Context, Result};
|
||||
use collections::HashMap;
|
||||
use futures::{
|
||||
Future, FutureExt as _,
|
||||
channel::oneshot,
|
||||
future::{BoxFuture, LocalBoxFuture},
|
||||
};
|
||||
use gpui::{AnyEntity, AnyWeakEntity, AsyncApp, Entity};
|
||||
use gpui::{AnyEntity, AnyWeakEntity, AsyncApp, BackgroundExecutor, Entity, FutureExt as _};
|
||||
use parking_lot::Mutex;
|
||||
use proto::{
|
||||
AnyTypedEnvelope, EntityMessage, Envelope, EnvelopedMessage, RequestMessage, TypedEnvelope,
|
||||
error::ErrorExt as _,
|
||||
AnyTypedEnvelope, EntityMessage, Envelope, EnvelopedMessage, LspRequestId, LspRequestMessage,
|
||||
RequestMessage, TypedEnvelope, error::ErrorExt as _,
|
||||
};
|
||||
use std::{
|
||||
any::{Any, TypeId},
|
||||
sync::{Arc, Weak},
|
||||
sync::{
|
||||
Arc, OnceLock,
|
||||
atomic::{self, AtomicU64},
|
||||
},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AnyProtoClient(Arc<dyn ProtoClient>);
|
||||
pub struct AnyProtoClient(Arc<State>);
|
||||
|
||||
impl AnyProtoClient {
|
||||
pub fn downgrade(&self) -> AnyWeakProtoClient {
|
||||
AnyWeakProtoClient(Arc::downgrade(&self.0))
|
||||
}
|
||||
}
|
||||
type RequestIds = Arc<
|
||||
Mutex<
|
||||
HashMap<
|
||||
LspRequestId,
|
||||
oneshot::Sender<
|
||||
Result<
|
||||
Option<TypedEnvelope<Vec<proto::ProtoLspResponse<Box<dyn AnyTypedEnvelope>>>>>,
|
||||
>,
|
||||
>,
|
||||
>,
|
||||
>,
|
||||
>;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AnyWeakProtoClient(Weak<dyn ProtoClient>);
|
||||
static NEXT_LSP_REQUEST_ID: OnceLock<Arc<AtomicU64>> = OnceLock::new();
|
||||
static REQUEST_IDS: OnceLock<RequestIds> = OnceLock::new();
|
||||
|
||||
impl AnyWeakProtoClient {
|
||||
pub fn upgrade(&self) -> Option<AnyProtoClient> {
|
||||
self.0.upgrade().map(AnyProtoClient)
|
||||
}
|
||||
struct State {
|
||||
client: Arc<dyn ProtoClient>,
|
||||
next_lsp_request_id: Arc<AtomicU64>,
|
||||
request_ids: RequestIds,
|
||||
}
|
||||
|
||||
pub trait ProtoClient: Send + Sync {
|
||||
|
@ -37,11 +50,11 @@ pub trait ProtoClient: Send + Sync {
|
|||
&self,
|
||||
envelope: Envelope,
|
||||
request_type: &'static str,
|
||||
) -> BoxFuture<'static, anyhow::Result<Envelope>>;
|
||||
) -> BoxFuture<'static, Result<Envelope>>;
|
||||
|
||||
fn send(&self, envelope: Envelope, message_type: &'static str) -> anyhow::Result<()>;
|
||||
fn send(&self, envelope: Envelope, message_type: &'static str) -> Result<()>;
|
||||
|
||||
fn send_response(&self, envelope: Envelope, message_type: &'static str) -> anyhow::Result<()>;
|
||||
fn send_response(&self, envelope: Envelope, message_type: &'static str) -> Result<()>;
|
||||
|
||||
fn message_handler_set(&self) -> &parking_lot::Mutex<ProtoMessageHandlerSet>;
|
||||
|
||||
|
@ -65,7 +78,7 @@ pub type ProtoMessageHandler = Arc<
|
|||
Box<dyn AnyTypedEnvelope>,
|
||||
AnyProtoClient,
|
||||
AsyncApp,
|
||||
) -> LocalBoxFuture<'static, anyhow::Result<()>>,
|
||||
) -> LocalBoxFuture<'static, Result<()>>,
|
||||
>;
|
||||
|
||||
impl ProtoMessageHandlerSet {
|
||||
|
@ -113,7 +126,7 @@ impl ProtoMessageHandlerSet {
|
|||
message: Box<dyn AnyTypedEnvelope>,
|
||||
client: AnyProtoClient,
|
||||
cx: AsyncApp,
|
||||
) -> Option<LocalBoxFuture<'static, anyhow::Result<()>>> {
|
||||
) -> Option<LocalBoxFuture<'static, Result<()>>> {
|
||||
let payload_type_id = message.payload_type_id();
|
||||
let mut this = this.lock();
|
||||
let handler = this.message_handlers.get(&payload_type_id)?.clone();
|
||||
|
@ -169,43 +182,195 @@ where
|
|||
T: ProtoClient + 'static,
|
||||
{
|
||||
fn from(client: Arc<T>) -> Self {
|
||||
Self(client)
|
||||
Self::new(client)
|
||||
}
|
||||
}
|
||||
|
||||
impl AnyProtoClient {
|
||||
pub fn new<T: ProtoClient + 'static>(client: Arc<T>) -> Self {
|
||||
Self(client)
|
||||
Self(Arc::new(State {
|
||||
client,
|
||||
next_lsp_request_id: NEXT_LSP_REQUEST_ID
|
||||
.get_or_init(|| Arc::new(AtomicU64::new(0)))
|
||||
.clone(),
|
||||
request_ids: REQUEST_IDS.get_or_init(RequestIds::default).clone(),
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn is_via_collab(&self) -> bool {
|
||||
self.0.is_via_collab()
|
||||
self.0.client.is_via_collab()
|
||||
}
|
||||
|
||||
pub fn request<T: RequestMessage>(
|
||||
&self,
|
||||
request: T,
|
||||
) -> impl Future<Output = anyhow::Result<T::Response>> + use<T> {
|
||||
) -> impl Future<Output = Result<T::Response>> + use<T> {
|
||||
let envelope = request.into_envelope(0, None, None);
|
||||
let response = self.0.request(envelope, T::NAME);
|
||||
let response = self.0.client.request(envelope, T::NAME);
|
||||
async move {
|
||||
T::Response::from_envelope(response.await?)
|
||||
.context("received response of the wrong type")
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send<T: EnvelopedMessage>(&self, request: T) -> anyhow::Result<()> {
|
||||
pub fn send<T: EnvelopedMessage>(&self, request: T) -> Result<()> {
|
||||
let envelope = request.into_envelope(0, None, None);
|
||||
self.0.send(envelope, T::NAME)
|
||||
self.0.client.send(envelope, T::NAME)
|
||||
}
|
||||
|
||||
pub fn send_response<T: EnvelopedMessage>(
|
||||
&self,
|
||||
request_id: u32,
|
||||
request: T,
|
||||
) -> anyhow::Result<()> {
|
||||
pub fn send_response<T: EnvelopedMessage>(&self, request_id: u32, request: T) -> Result<()> {
|
||||
let envelope = request.into_envelope(0, Some(request_id), None);
|
||||
self.0.send(envelope, T::NAME)
|
||||
self.0.client.send(envelope, T::NAME)
|
||||
}
|
||||
|
||||
pub fn request_lsp<T>(
|
||||
&self,
|
||||
project_id: u64,
|
||||
timeout: Duration,
|
||||
executor: BackgroundExecutor,
|
||||
request: T,
|
||||
) -> impl Future<
|
||||
Output = Result<Option<TypedEnvelope<Vec<proto::ProtoLspResponse<T::Response>>>>>,
|
||||
> + use<T>
|
||||
where
|
||||
T: LspRequestMessage,
|
||||
{
|
||||
let new_id = LspRequestId(
|
||||
self.0
|
||||
.next_lsp_request_id
|
||||
.fetch_add(1, atomic::Ordering::Acquire),
|
||||
);
|
||||
let (tx, rx) = oneshot::channel();
|
||||
{
|
||||
self.0.request_ids.lock().insert(new_id, tx);
|
||||
}
|
||||
|
||||
let query = proto::LspQuery {
|
||||
project_id,
|
||||
lsp_request_id: new_id.0,
|
||||
request: Some(request.clone().to_proto_query()),
|
||||
};
|
||||
let request = self.request(query);
|
||||
let request_ids = self.0.request_ids.clone();
|
||||
async move {
|
||||
match request.await {
|
||||
Ok(_request_enqueued) => {}
|
||||
Err(e) => {
|
||||
request_ids.lock().remove(&new_id);
|
||||
return Err(e).context("sending LSP proto request");
|
||||
}
|
||||
}
|
||||
|
||||
let response = rx.with_timeout(timeout, &executor).await;
|
||||
{
|
||||
request_ids.lock().remove(&new_id);
|
||||
}
|
||||
match response {
|
||||
Ok(Ok(response)) => {
|
||||
let response = response
|
||||
.context("waiting for LSP proto response")?
|
||||
.map(|response| {
|
||||
anyhow::Ok(TypedEnvelope {
|
||||
payload: response
|
||||
.payload
|
||||
.into_iter()
|
||||
.map(|lsp_response| lsp_response.into_response::<T>())
|
||||
.collect::<Result<Vec<_>>>()?,
|
||||
sender_id: response.sender_id,
|
||||
original_sender_id: response.original_sender_id,
|
||||
message_id: response.message_id,
|
||||
received_at: response.received_at,
|
||||
})
|
||||
})
|
||||
.transpose()
|
||||
.context("converting LSP proto response")?;
|
||||
Ok(response)
|
||||
}
|
||||
Err(_cancelled_due_timeout) => Ok(None),
|
||||
Ok(Err(_channel_dropped)) => Ok(None),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_lsp_response<T: LspRequestMessage>(
|
||||
&self,
|
||||
project_id: u64,
|
||||
lsp_request_id: LspRequestId,
|
||||
server_responses: HashMap<u64, T::Response>,
|
||||
) -> Result<()> {
|
||||
self.send(proto::LspQueryResponse {
|
||||
project_id,
|
||||
lsp_request_id: lsp_request_id.0,
|
||||
responses: server_responses
|
||||
.into_iter()
|
||||
.map(|(server_id, response)| proto::LspResponse {
|
||||
server_id,
|
||||
response: Some(T::response_to_proto_query(response)),
|
||||
})
|
||||
.collect(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn handle_lsp_response(&self, mut envelope: TypedEnvelope<proto::LspQueryResponse>) {
|
||||
let request_id = LspRequestId(envelope.payload.lsp_request_id);
|
||||
let mut response_senders = self.0.request_ids.lock();
|
||||
if let Some(tx) = response_senders.remove(&request_id) {
|
||||
let responses = envelope.payload.responses.drain(..).collect::<Vec<_>>();
|
||||
tx.send(Ok(Some(proto::TypedEnvelope {
|
||||
sender_id: envelope.sender_id,
|
||||
original_sender_id: envelope.original_sender_id,
|
||||
message_id: envelope.message_id,
|
||||
received_at: envelope.received_at,
|
||||
payload: responses
|
||||
.into_iter()
|
||||
.filter_map(|response| {
|
||||
use proto::lsp_response::Response;
|
||||
|
||||
let server_id = response.server_id;
|
||||
let response = match response.response? {
|
||||
Response::GetReferencesResponse(response) => {
|
||||
to_any_envelope(&envelope, response)
|
||||
}
|
||||
Response::GetDocumentColorResponse(response) => {
|
||||
to_any_envelope(&envelope, response)
|
||||
}
|
||||
Response::GetHoverResponse(response) => {
|
||||
to_any_envelope(&envelope, response)
|
||||
}
|
||||
Response::GetCodeActionsResponse(response) => {
|
||||
to_any_envelope(&envelope, response)
|
||||
}
|
||||
Response::GetSignatureHelpResponse(response) => {
|
||||
to_any_envelope(&envelope, response)
|
||||
}
|
||||
Response::GetCodeLensResponse(response) => {
|
||||
to_any_envelope(&envelope, response)
|
||||
}
|
||||
Response::GetDocumentDiagnosticsResponse(response) => {
|
||||
to_any_envelope(&envelope, response)
|
||||
}
|
||||
Response::GetDefinitionResponse(response) => {
|
||||
to_any_envelope(&envelope, response)
|
||||
}
|
||||
Response::GetDeclarationResponse(response) => {
|
||||
to_any_envelope(&envelope, response)
|
||||
}
|
||||
Response::GetTypeDefinitionResponse(response) => {
|
||||
to_any_envelope(&envelope, response)
|
||||
}
|
||||
Response::GetImplementationResponse(response) => {
|
||||
to_any_envelope(&envelope, response)
|
||||
}
|
||||
};
|
||||
Some(proto::ProtoLspResponse {
|
||||
server_id,
|
||||
response,
|
||||
})
|
||||
})
|
||||
.collect(),
|
||||
})))
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_request_handler<M, E, H, F>(&self, entity: gpui::WeakEntity<E>, handler: H)
|
||||
|
@ -213,31 +378,35 @@ impl AnyProtoClient {
|
|||
M: RequestMessage,
|
||||
E: 'static,
|
||||
H: 'static + Sync + Fn(Entity<E>, TypedEnvelope<M>, AsyncApp) -> F + Send + Sync,
|
||||
F: 'static + Future<Output = anyhow::Result<M::Response>>,
|
||||
F: 'static + Future<Output = Result<M::Response>>,
|
||||
{
|
||||
self.0.message_handler_set().lock().add_message_handler(
|
||||
TypeId::of::<M>(),
|
||||
entity.into(),
|
||||
Arc::new(move |entity, envelope, client, cx| {
|
||||
let entity = entity.downcast::<E>().unwrap();
|
||||
let envelope = envelope.into_any().downcast::<TypedEnvelope<M>>().unwrap();
|
||||
let request_id = envelope.message_id();
|
||||
handler(entity, *envelope, cx)
|
||||
.then(move |result| async move {
|
||||
match result {
|
||||
Ok(response) => {
|
||||
client.send_response(request_id, response)?;
|
||||
Ok(())
|
||||
self.0
|
||||
.client
|
||||
.message_handler_set()
|
||||
.lock()
|
||||
.add_message_handler(
|
||||
TypeId::of::<M>(),
|
||||
entity.into(),
|
||||
Arc::new(move |entity, envelope, client, cx| {
|
||||
let entity = entity.downcast::<E>().unwrap();
|
||||
let envelope = envelope.into_any().downcast::<TypedEnvelope<M>>().unwrap();
|
||||
let request_id = envelope.message_id();
|
||||
handler(entity, *envelope, cx)
|
||||
.then(move |result| async move {
|
||||
match result {
|
||||
Ok(response) => {
|
||||
client.send_response(request_id, response)?;
|
||||
Ok(())
|
||||
}
|
||||
Err(error) => {
|
||||
client.send_response(request_id, error.to_proto())?;
|
||||
Err(error)
|
||||
}
|
||||
}
|
||||
Err(error) => {
|
||||
client.send_response(request_id, error.to_proto())?;
|
||||
Err(error)
|
||||
}
|
||||
}
|
||||
})
|
||||
.boxed_local()
|
||||
}),
|
||||
)
|
||||
})
|
||||
.boxed_local()
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn add_entity_request_handler<M, E, H, F>(&self, handler: H)
|
||||
|
@ -245,7 +414,7 @@ impl AnyProtoClient {
|
|||
M: EnvelopedMessage + RequestMessage + EntityMessage,
|
||||
E: 'static,
|
||||
H: 'static + Sync + Send + Fn(gpui::Entity<E>, TypedEnvelope<M>, AsyncApp) -> F,
|
||||
F: 'static + Future<Output = anyhow::Result<M::Response>>,
|
||||
F: 'static + Future<Output = Result<M::Response>>,
|
||||
{
|
||||
let message_type_id = TypeId::of::<M>();
|
||||
let entity_type_id = TypeId::of::<E>();
|
||||
|
@ -257,6 +426,7 @@ impl AnyProtoClient {
|
|||
.remote_entity_id()
|
||||
};
|
||||
self.0
|
||||
.client
|
||||
.message_handler_set()
|
||||
.lock()
|
||||
.add_entity_message_handler(
|
||||
|
@ -290,7 +460,7 @@ impl AnyProtoClient {
|
|||
M: EnvelopedMessage + EntityMessage,
|
||||
E: 'static,
|
||||
H: 'static + Sync + Send + Fn(gpui::Entity<E>, TypedEnvelope<M>, AsyncApp) -> F,
|
||||
F: 'static + Future<Output = anyhow::Result<()>>,
|
||||
F: 'static + Future<Output = Result<()>>,
|
||||
{
|
||||
let message_type_id = TypeId::of::<M>();
|
||||
let entity_type_id = TypeId::of::<E>();
|
||||
|
@ -302,6 +472,7 @@ impl AnyProtoClient {
|
|||
.remote_entity_id()
|
||||
};
|
||||
self.0
|
||||
.client
|
||||
.message_handler_set()
|
||||
.lock()
|
||||
.add_entity_message_handler(
|
||||
|
@ -319,7 +490,7 @@ impl AnyProtoClient {
|
|||
pub fn subscribe_to_entity<E: 'static>(&self, remote_id: u64, entity: &Entity<E>) {
|
||||
let id = (TypeId::of::<E>(), remote_id);
|
||||
|
||||
let mut message_handlers = self.0.message_handler_set().lock();
|
||||
let mut message_handlers = self.0.client.message_handler_set().lock();
|
||||
if message_handlers
|
||||
.entities_by_type_and_remote_id
|
||||
.contains_key(&id)
|
||||
|
@ -335,3 +506,16 @@ impl AnyProtoClient {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn to_any_envelope<T: EnvelopedMessage>(
|
||||
envelope: &TypedEnvelope<proto::LspQueryResponse>,
|
||||
response: T,
|
||||
) -> Box<dyn AnyTypedEnvelope> {
|
||||
Box::new(proto::TypedEnvelope {
|
||||
sender_id: envelope.sender_id,
|
||||
original_sender_id: envelope.original_sender_id,
|
||||
message_id: envelope.message_id,
|
||||
received_at: envelope.received_at,
|
||||
payload: response,
|
||||
}) as Box<_>
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue