mirror of
https://github.com/zed-industries/zed.git
synced 2024-12-25 19:31:38 +03:00
Query code actions and hovers from all related local language servers (#9943)
<img width="1122" alt="Screenshot 2024-03-28 at 21 51 18" src="https://github.com/zed-industries/zed/assets/2690773/37ef7202-f10f-462f-a2fa-044b2d806191"> Part of https://github.com/zed-industries/zed/issues/7947 and https://github.com/zed-industries/zed/issues/9912 that adds makes Zed query all related language servers instead of the primary one. Collab clients are still querying the primary one only, but this is quite hard to solve, https://github.com/zed-industries/zed/pull/8634 drafts a part of it. The local part is useful per se, as many people use Zed & Tailwind but do not use collab features. Unfortunately, eslint still returns empty actions list when queried, but querying actions for all related language servers looks reasonable and rare enough to be dangerous. Release Notes: - Added Tailwind CSS hover popovers for Zed in single player mode ([7947](https://github.com/zed-industries/zed/issues/7947))
This commit is contained in:
parent
c4bc172850
commit
c7f04691d9
@ -4980,8 +4980,7 @@ async fn test_lsp_hover(
|
||||
|
||||
let hovers = project_b
|
||||
.update(cx_b, |p, cx| p.hover(&buffer_b, 22, cx))
|
||||
.await
|
||||
.unwrap();
|
||||
.await;
|
||||
assert_eq!(
|
||||
hovers.len(),
|
||||
1,
|
||||
|
@ -832,7 +832,7 @@ impl RandomizedTest for ProjectCollaborationTest {
|
||||
.boxed(),
|
||||
LspRequestKind::CodeAction => project
|
||||
.code_actions(&buffer, offset..offset, cx)
|
||||
.map_ok(|_| ())
|
||||
.map(|_| Ok(()))
|
||||
.boxed(),
|
||||
LspRequestKind::Definition => project
|
||||
.definition(&buffer, offset, cx)
|
||||
|
@ -376,6 +376,7 @@ impl Copilot {
|
||||
use node_runtime::FakeNodeRuntime;
|
||||
|
||||
let (server, fake_server) = FakeLanguageServer::new(
|
||||
LanguageServerId(0),
|
||||
LanguageServerBinary {
|
||||
path: "path/to/copilot".into(),
|
||||
arguments: vec![],
|
||||
|
@ -3758,19 +3758,17 @@ impl Editor {
|
||||
let actions = if let Ok(code_actions) = project.update(&mut cx, |project, cx| {
|
||||
project.code_actions(&start_buffer, start..end, cx)
|
||||
}) {
|
||||
code_actions.await.log_err()
|
||||
code_actions.await
|
||||
} else {
|
||||
None
|
||||
Vec::new()
|
||||
};
|
||||
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.available_code_actions = actions.and_then(|actions| {
|
||||
if actions.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some((start_buffer, actions.into()))
|
||||
}
|
||||
});
|
||||
this.available_code_actions = if actions.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some((start_buffer, actions.into()))
|
||||
};
|
||||
cx.notify();
|
||||
})
|
||||
.log_err();
|
||||
|
@ -295,7 +295,7 @@ fn show_hover(
|
||||
});
|
||||
})?;
|
||||
|
||||
let hovers_response = hover_request.await.ok().unwrap_or_default();
|
||||
let hovers_response = hover_request.await;
|
||||
let language_registry = project.update(&mut cx, |p, _| p.languages().clone())?;
|
||||
let snapshot = this.update(&mut cx, |this, cx| this.snapshot(cx))?;
|
||||
let mut hover_highlights = Vec::with_capacity(hovers_response.len());
|
||||
|
@ -234,13 +234,23 @@ impl LanguageRegistry {
|
||||
&self,
|
||||
language_name: &str,
|
||||
adapter: crate::FakeLspAdapter,
|
||||
) -> futures::channel::mpsc::UnboundedReceiver<lsp::FakeLanguageServer> {
|
||||
self.register_specific_fake_lsp_adapter(language_name, true, adapter)
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "test-support", test))]
|
||||
pub fn register_specific_fake_lsp_adapter(
|
||||
&self,
|
||||
language_name: &str,
|
||||
primary: bool,
|
||||
adapter: crate::FakeLspAdapter,
|
||||
) -> futures::channel::mpsc::UnboundedReceiver<lsp::FakeLanguageServer> {
|
||||
self.state
|
||||
.write()
|
||||
.lsp_adapters
|
||||
.entry(language_name.into())
|
||||
.or_default()
|
||||
.push(CachedLspAdapter::new(Arc::new(adapter), true));
|
||||
.push(CachedLspAdapter::new(Arc::new(adapter), primary));
|
||||
self.fake_language_servers(language_name)
|
||||
}
|
||||
|
||||
@ -739,6 +749,7 @@ impl LanguageRegistry {
|
||||
.unwrap_or_default();
|
||||
|
||||
let (server, mut fake_server) = lsp::FakeLanguageServer::new(
|
||||
server_id,
|
||||
binary,
|
||||
adapter.name.0.to_string(),
|
||||
capabilities,
|
||||
|
@ -1108,6 +1108,7 @@ pub struct FakeLanguageServer {
|
||||
impl FakeLanguageServer {
|
||||
/// Construct a fake language server.
|
||||
pub fn new(
|
||||
server_id: LanguageServerId,
|
||||
binary: LanguageServerBinary,
|
||||
name: String,
|
||||
capabilities: ServerCapabilities,
|
||||
@ -1117,8 +1118,8 @@ impl FakeLanguageServer {
|
||||
let (stdout_writer, stdout_reader) = async_pipe::pipe();
|
||||
let (notifications_tx, notifications_rx) = channel::unbounded();
|
||||
|
||||
let server = LanguageServer::new_internal(
|
||||
LanguageServerId(0),
|
||||
let mut server = LanguageServer::new_internal(
|
||||
server_id,
|
||||
stdin_writer,
|
||||
stdout_reader,
|
||||
None::<async_pipe::PipeReader>,
|
||||
@ -1129,30 +1130,35 @@ impl FakeLanguageServer {
|
||||
cx.clone(),
|
||||
|_| {},
|
||||
);
|
||||
server.name = name.as_str().into();
|
||||
let fake = FakeLanguageServer {
|
||||
binary,
|
||||
server: Arc::new(LanguageServer::new_internal(
|
||||
LanguageServerId(0),
|
||||
stdout_writer,
|
||||
stdin_reader,
|
||||
None::<async_pipe::PipeReader>,
|
||||
Arc::new(Mutex::new(None)),
|
||||
None,
|
||||
Path::new("/"),
|
||||
None,
|
||||
cx,
|
||||
move |msg| {
|
||||
notifications_tx
|
||||
.try_send((
|
||||
msg.method.to_string(),
|
||||
msg.params
|
||||
.map(|raw_value| raw_value.get())
|
||||
.unwrap_or("null")
|
||||
.to_string(),
|
||||
))
|
||||
.ok();
|
||||
},
|
||||
)),
|
||||
server: Arc::new({
|
||||
let mut server = LanguageServer::new_internal(
|
||||
server_id,
|
||||
stdout_writer,
|
||||
stdin_reader,
|
||||
None::<async_pipe::PipeReader>,
|
||||
Arc::new(Mutex::new(None)),
|
||||
None,
|
||||
Path::new("/"),
|
||||
None,
|
||||
cx,
|
||||
move |msg| {
|
||||
notifications_tx
|
||||
.try_send((
|
||||
msg.method.to_string(),
|
||||
msg.params
|
||||
.map(|raw_value| raw_value.get())
|
||||
.unwrap_or("null")
|
||||
.to_string(),
|
||||
))
|
||||
.ok();
|
||||
},
|
||||
);
|
||||
server.name = name.as_str().into();
|
||||
server
|
||||
}),
|
||||
notifications_rx,
|
||||
};
|
||||
fake.handle_request::<request::Initialize, _, _>({
|
||||
@ -1350,6 +1356,7 @@ mod tests {
|
||||
release_channel::init("0.0.0", cx);
|
||||
});
|
||||
let (server, mut fake) = FakeLanguageServer::new(
|
||||
LanguageServerId(0),
|
||||
LanguageServerBinary {
|
||||
path: "path/to/language-server".into(),
|
||||
arguments: vec![],
|
||||
|
@ -1855,6 +1855,17 @@ impl GetCodeActions {
|
||||
})
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn supports_code_actions(capabilities: &ServerCapabilities) -> bool {
|
||||
capabilities
|
||||
.code_action_provider
|
||||
.as_ref()
|
||||
.map(|options| match options {
|
||||
lsp::CodeActionProviderCapability::Simple(is_supported) => *is_supported,
|
||||
lsp::CodeActionProviderCapability::Options(_) => true,
|
||||
})
|
||||
.unwrap_or(false)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait(?Send)]
|
||||
|
@ -5192,14 +5192,64 @@ impl Project {
|
||||
buffer: &Model<Buffer>,
|
||||
position: PointUtf16,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<Vec<Hover>>> {
|
||||
let request_task = self.request_lsp(
|
||||
buffer.clone(),
|
||||
LanguageServerToQuery::Primary,
|
||||
GetHover { position },
|
||||
cx,
|
||||
);
|
||||
cx.spawn(|_, _| async move { request_task.await.map(|hover| hover.into_iter().collect()) })
|
||||
) -> Task<Vec<Hover>> {
|
||||
if self.is_local() {
|
||||
let snapshot = buffer.read(cx).snapshot();
|
||||
let offset = position.to_offset(&snapshot);
|
||||
let scope = snapshot.language_scope_at(offset);
|
||||
|
||||
let mut hover_responses = self
|
||||
.language_servers_for_buffer(buffer.read(cx), cx)
|
||||
.filter(|(_, server)| match server.capabilities().hover_provider {
|
||||
Some(lsp::HoverProviderCapability::Simple(enabled)) => enabled,
|
||||
Some(lsp::HoverProviderCapability::Options(_)) => true,
|
||||
None => false,
|
||||
})
|
||||
.filter(|(adapter, _)| {
|
||||
scope
|
||||
.as_ref()
|
||||
.map(|scope| scope.language_allowed(&adapter.name))
|
||||
.unwrap_or(true)
|
||||
})
|
||||
.map(|(_, server)| server.server_id())
|
||||
.map(|server_id| {
|
||||
self.request_lsp(
|
||||
buffer.clone(),
|
||||
LanguageServerToQuery::Other(server_id),
|
||||
GetHover { position },
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.collect::<FuturesUnordered<_>>();
|
||||
|
||||
cx.spawn(|_, _| async move {
|
||||
let mut hovers = Vec::with_capacity(hover_responses.len());
|
||||
while let Some(hover_response) = hover_responses.next().await {
|
||||
if let Some(hover) = hover_response.log_err().flatten() {
|
||||
hovers.push(hover);
|
||||
}
|
||||
}
|
||||
hovers
|
||||
})
|
||||
} else if self.is_remote() {
|
||||
let request_task = self.request_lsp(
|
||||
buffer.clone(),
|
||||
LanguageServerToQuery::Primary,
|
||||
GetHover { position },
|
||||
cx,
|
||||
);
|
||||
cx.spawn(|_, _| async move {
|
||||
request_task
|
||||
.await
|
||||
.log_err()
|
||||
.flatten()
|
||||
.map(|hover| vec![hover])
|
||||
.unwrap_or_default()
|
||||
})
|
||||
} else {
|
||||
log::error!("cannot show hovers: project does not have a remote id");
|
||||
Task::ready(Vec::new())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn hover<T: ToPointUtf16>(
|
||||
@ -5207,7 +5257,7 @@ impl Project {
|
||||
buffer: &Model<Buffer>,
|
||||
position: T,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<Vec<Hover>>> {
|
||||
) -> Task<Vec<Hover>> {
|
||||
let position = position.to_point_utf16(buffer.read(cx));
|
||||
self.hover_impl(buffer, position, cx)
|
||||
}
|
||||
@ -5561,13 +5611,54 @@ impl Project {
|
||||
buffer_handle: &Model<Buffer>,
|
||||
range: Range<Anchor>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<Vec<CodeAction>>> {
|
||||
self.request_lsp(
|
||||
buffer_handle.clone(),
|
||||
LanguageServerToQuery::Primary,
|
||||
GetCodeActions { range, kinds: None },
|
||||
cx,
|
||||
)
|
||||
) -> Task<Vec<CodeAction>> {
|
||||
if self.is_local() {
|
||||
let snapshot = buffer_handle.read(cx).snapshot();
|
||||
let offset = range.start.to_offset(&snapshot);
|
||||
let scope = snapshot.language_scope_at(offset);
|
||||
|
||||
let mut hover_responses = self
|
||||
.language_servers_for_buffer(buffer_handle.read(cx), cx)
|
||||
.filter(|(_, server)| GetCodeActions::supports_code_actions(server.capabilities()))
|
||||
.filter(|(adapter, _)| {
|
||||
scope
|
||||
.as_ref()
|
||||
.map(|scope| scope.language_allowed(&adapter.name))
|
||||
.unwrap_or(true)
|
||||
})
|
||||
.map(|(_, server)| server.server_id())
|
||||
.map(|server_id| {
|
||||
self.request_lsp(
|
||||
buffer_handle.clone(),
|
||||
LanguageServerToQuery::Other(server_id),
|
||||
GetCodeActions {
|
||||
range: range.clone(),
|
||||
kinds: None,
|
||||
},
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.collect::<FuturesUnordered<_>>();
|
||||
|
||||
cx.spawn(|_, _| async move {
|
||||
let mut hovers = Vec::with_capacity(hover_responses.len());
|
||||
while let Some(hover_response) = hover_responses.next().await {
|
||||
hovers.extend(hover_response.log_err().unwrap_or_default());
|
||||
}
|
||||
hovers
|
||||
})
|
||||
} else if self.is_remote() {
|
||||
let request_task = self.request_lsp(
|
||||
buffer_handle.clone(),
|
||||
LanguageServerToQuery::Primary,
|
||||
GetCodeActions { range, kinds: None },
|
||||
cx,
|
||||
);
|
||||
cx.spawn(|_, _| async move { request_task.await.log_err().unwrap_or_default() })
|
||||
} else {
|
||||
log::error!("cannot fetch actions: project does not have a remote id");
|
||||
Task::ready(Vec::new())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn code_actions<T: Clone + ToOffset>(
|
||||
@ -5575,7 +5666,7 @@ impl Project {
|
||||
buffer_handle: &Model<Buffer>,
|
||||
range: Range<T>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<Vec<CodeAction>>> {
|
||||
) -> Task<Vec<CodeAction>> {
|
||||
let buffer = buffer_handle.read(cx);
|
||||
let range = buffer.anchor_before(range.start)..buffer.anchor_before(range.end);
|
||||
self.code_actions_impl(buffer_handle, range, cx)
|
||||
|
@ -2522,7 +2522,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[0].clone();
|
||||
let apply = project.update(cx, |project, cx| {
|
||||
project.apply_code_action(buffer.clone(), action, true, cx)
|
||||
});
|
||||
@ -4404,6 +4404,311 @@ async fn test_create_entry(cx: &mut gpui::TestAppContext) {
|
||||
assert!(result.is_err())
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_multiple_language_server_hovers(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx);
|
||||
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree(
|
||||
"/dir",
|
||||
json!({
|
||||
"a.tsx": "a",
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
let project = Project::test(fs, ["/dir".as_ref()], cx).await;
|
||||
|
||||
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
|
||||
language_registry.add(tsx_lang());
|
||||
let language_server_names = [
|
||||
"TypeScriptServer",
|
||||
"TailwindServer",
|
||||
"ESLintServer",
|
||||
"NoHoverCapabilitiesServer",
|
||||
];
|
||||
let mut fake_tsx_language_servers = language_registry.register_specific_fake_lsp_adapter(
|
||||
"tsx",
|
||||
true,
|
||||
FakeLspAdapter {
|
||||
name: &language_server_names[0],
|
||||
capabilities: lsp::ServerCapabilities {
|
||||
hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
|
||||
..lsp::ServerCapabilities::default()
|
||||
},
|
||||
..FakeLspAdapter::default()
|
||||
},
|
||||
);
|
||||
let _a = language_registry.register_specific_fake_lsp_adapter(
|
||||
"tsx",
|
||||
false,
|
||||
FakeLspAdapter {
|
||||
name: &language_server_names[1],
|
||||
capabilities: lsp::ServerCapabilities {
|
||||
hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
|
||||
..lsp::ServerCapabilities::default()
|
||||
},
|
||||
..FakeLspAdapter::default()
|
||||
},
|
||||
);
|
||||
let _b = language_registry.register_specific_fake_lsp_adapter(
|
||||
"tsx",
|
||||
false,
|
||||
FakeLspAdapter {
|
||||
name: &language_server_names[2],
|
||||
capabilities: lsp::ServerCapabilities {
|
||||
hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
|
||||
..lsp::ServerCapabilities::default()
|
||||
},
|
||||
..FakeLspAdapter::default()
|
||||
},
|
||||
);
|
||||
let _c = language_registry.register_specific_fake_lsp_adapter(
|
||||
"tsx",
|
||||
false,
|
||||
FakeLspAdapter {
|
||||
name: &language_server_names[3],
|
||||
capabilities: lsp::ServerCapabilities {
|
||||
hover_provider: None,
|
||||
..lsp::ServerCapabilities::default()
|
||||
},
|
||||
..FakeLspAdapter::default()
|
||||
},
|
||||
);
|
||||
|
||||
let buffer = project
|
||||
.update(cx, |p, cx| p.open_local_buffer("/dir/a.tsx", cx))
|
||||
.await
|
||||
.unwrap();
|
||||
cx.executor().run_until_parked();
|
||||
|
||||
let mut servers_with_hover_requests = HashMap::default();
|
||||
for i in 0..language_server_names.len() {
|
||||
let new_server = fake_tsx_language_servers
|
||||
.next()
|
||||
.await
|
||||
.unwrap_or_else(|| panic!("Failed to get language server #{i}"));
|
||||
let new_server_name = new_server.server.name();
|
||||
assert!(
|
||||
!servers_with_hover_requests.contains_key(new_server_name),
|
||||
"Unexpected: initialized server with the same name twice. Name: `{new_server_name}`"
|
||||
);
|
||||
let new_server_name = new_server_name.to_string();
|
||||
match new_server_name.as_str() {
|
||||
"TailwindServer" | "TypeScriptServer" => {
|
||||
servers_with_hover_requests.insert(
|
||||
new_server_name.clone(),
|
||||
new_server.handle_request::<lsp::request::HoverRequest, _, _>(move |_, _| {
|
||||
let name = new_server_name.clone();
|
||||
async move {
|
||||
Ok(Some(lsp::Hover {
|
||||
contents: lsp::HoverContents::Scalar(lsp::MarkedString::String(
|
||||
format!("{name} hover"),
|
||||
)),
|
||||
range: None,
|
||||
}))
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
"ESLintServer" => {
|
||||
servers_with_hover_requests.insert(
|
||||
new_server_name,
|
||||
new_server.handle_request::<lsp::request::HoverRequest, _, _>(
|
||||
|_, _| async move { Ok(None) },
|
||||
),
|
||||
);
|
||||
}
|
||||
"NoHoverCapabilitiesServer" => {
|
||||
let _never_handled = new_server.handle_request::<lsp::request::HoverRequest, _, _>(
|
||||
|_, _| async move {
|
||||
panic!(
|
||||
"Should not call for hovers server with no corresponding capabilities"
|
||||
)
|
||||
},
|
||||
);
|
||||
}
|
||||
unexpected => panic!("Unexpected server name: {unexpected}"),
|
||||
}
|
||||
}
|
||||
|
||||
let hover_task = project.update(cx, |project, cx| {
|
||||
project.hover(&buffer, Point::new(0, 0), cx)
|
||||
});
|
||||
let _: Vec<()> = futures::future::join_all(servers_with_hover_requests.into_values().map(
|
||||
|mut hover_request| async move {
|
||||
hover_request
|
||||
.next()
|
||||
.await
|
||||
.expect("All hover requests should have been triggered")
|
||||
},
|
||||
))
|
||||
.await;
|
||||
assert_eq!(
|
||||
vec!["TailwindServer hover", "TypeScriptServer hover"],
|
||||
hover_task
|
||||
.await
|
||||
.into_iter()
|
||||
.map(|hover| hover.contents.iter().map(|block| &block.text).join("|"))
|
||||
.sorted()
|
||||
.collect::<Vec<_>>(),
|
||||
"Should receive hover responses from all related servers with hover capabilities"
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_multiple_language_server_actions(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx);
|
||||
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree(
|
||||
"/dir",
|
||||
json!({
|
||||
"a.tsx": "a",
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
let project = Project::test(fs, ["/dir".as_ref()], cx).await;
|
||||
|
||||
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
|
||||
language_registry.add(tsx_lang());
|
||||
let language_server_names = [
|
||||
"TypeScriptServer",
|
||||
"TailwindServer",
|
||||
"ESLintServer",
|
||||
"NoActionsCapabilitiesServer",
|
||||
];
|
||||
let mut fake_tsx_language_servers = language_registry.register_specific_fake_lsp_adapter(
|
||||
"tsx",
|
||||
true,
|
||||
FakeLspAdapter {
|
||||
name: &language_server_names[0],
|
||||
capabilities: lsp::ServerCapabilities {
|
||||
code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
|
||||
..lsp::ServerCapabilities::default()
|
||||
},
|
||||
..FakeLspAdapter::default()
|
||||
},
|
||||
);
|
||||
let _a = language_registry.register_specific_fake_lsp_adapter(
|
||||
"tsx",
|
||||
false,
|
||||
FakeLspAdapter {
|
||||
name: &language_server_names[1],
|
||||
capabilities: lsp::ServerCapabilities {
|
||||
code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
|
||||
..lsp::ServerCapabilities::default()
|
||||
},
|
||||
..FakeLspAdapter::default()
|
||||
},
|
||||
);
|
||||
let _b = language_registry.register_specific_fake_lsp_adapter(
|
||||
"tsx",
|
||||
false,
|
||||
FakeLspAdapter {
|
||||
name: &language_server_names[2],
|
||||
capabilities: lsp::ServerCapabilities {
|
||||
code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
|
||||
..lsp::ServerCapabilities::default()
|
||||
},
|
||||
..FakeLspAdapter::default()
|
||||
},
|
||||
);
|
||||
let _c = language_registry.register_specific_fake_lsp_adapter(
|
||||
"tsx",
|
||||
false,
|
||||
FakeLspAdapter {
|
||||
name: &language_server_names[3],
|
||||
capabilities: lsp::ServerCapabilities {
|
||||
code_action_provider: None,
|
||||
..lsp::ServerCapabilities::default()
|
||||
},
|
||||
..FakeLspAdapter::default()
|
||||
},
|
||||
);
|
||||
|
||||
let buffer = project
|
||||
.update(cx, |p, cx| p.open_local_buffer("/dir/a.tsx", cx))
|
||||
.await
|
||||
.unwrap();
|
||||
cx.executor().run_until_parked();
|
||||
|
||||
let mut servers_with_actions_requests = HashMap::default();
|
||||
for i in 0..language_server_names.len() {
|
||||
let new_server = fake_tsx_language_servers
|
||||
.next()
|
||||
.await
|
||||
.unwrap_or_else(|| panic!("Failed to get language server #{i}"));
|
||||
let new_server_name = new_server.server.name();
|
||||
assert!(
|
||||
!servers_with_actions_requests.contains_key(new_server_name),
|
||||
"Unexpected: initialized server with the same name twice. Name: `{new_server_name}`"
|
||||
);
|
||||
let new_server_name = new_server_name.to_string();
|
||||
match new_server_name.as_str() {
|
||||
"TailwindServer" | "TypeScriptServer" => {
|
||||
servers_with_actions_requests.insert(
|
||||
new_server_name.clone(),
|
||||
new_server.handle_request::<lsp::request::CodeActionRequest, _, _>(
|
||||
move |_, _| {
|
||||
let name = new_server_name.clone();
|
||||
async move {
|
||||
Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
|
||||
lsp::CodeAction {
|
||||
title: format!("{name} code action"),
|
||||
..lsp::CodeAction::default()
|
||||
},
|
||||
)]))
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
"ESLintServer" => {
|
||||
servers_with_actions_requests.insert(
|
||||
new_server_name,
|
||||
new_server.handle_request::<lsp::request::CodeActionRequest, _, _>(
|
||||
|_, _| async move { Ok(None) },
|
||||
),
|
||||
);
|
||||
}
|
||||
"NoActionsCapabilitiesServer" => {
|
||||
let _never_handled = new_server
|
||||
.handle_request::<lsp::request::CodeActionRequest, _, _>(|_, _| async move {
|
||||
panic!(
|
||||
"Should not call for code actions server with no corresponding capabilities"
|
||||
)
|
||||
});
|
||||
}
|
||||
unexpected => panic!("Unexpected server name: {unexpected}"),
|
||||
}
|
||||
}
|
||||
|
||||
let code_actions_task = project.update(cx, |project, cx| {
|
||||
project.code_actions(&buffer, 0..buffer.read(cx).len(), cx)
|
||||
});
|
||||
let _: Vec<()> = futures::future::join_all(servers_with_actions_requests.into_values().map(
|
||||
|mut code_actions_request| async move {
|
||||
code_actions_request
|
||||
.next()
|
||||
.await
|
||||
.expect("All code actions requests should have been triggered")
|
||||
},
|
||||
))
|
||||
.await;
|
||||
assert_eq!(
|
||||
vec!["TailwindServer code action", "TypeScriptServer code action"],
|
||||
code_actions_task
|
||||
.await
|
||||
.into_iter()
|
||||
.map(|code_action| code_action.lsp_action.title)
|
||||
.sorted()
|
||||
.collect::<Vec<_>>(),
|
||||
"Should receive code actions responses from all related servers with hover capabilities"
|
||||
);
|
||||
}
|
||||
|
||||
async fn search(
|
||||
project: &Model<Project>,
|
||||
query: SearchQuery,
|
||||
@ -4508,3 +4813,17 @@ fn typescript_lang() -> Arc<Language> {
|
||||
Some(tree_sitter_typescript::language_typescript()),
|
||||
))
|
||||
}
|
||||
|
||||
fn tsx_lang() -> Arc<Language> {
|
||||
Arc::new(Language::new(
|
||||
LanguageConfig {
|
||||
name: "tsx".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["tsx".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_typescript::language_tsx()),
|
||||
))
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user