lsp: Complete overloaded signature help implementation (#33199)

This PR revives zed-industries/zed#27818 and aims to complete the
partially implemented overloaded signature help feature.

The first commit is a rebase of zed-industries/zed#27818, and the
subsequent commit addresses all review feedback from the original PR.

Now the overloaded signature help works like


https://github.com/user-attachments/assets/e253c9a0-e3a5-4bfe-8003-eb75de41f672

Closes #21493

Release Notes:

- Implemented signature help for overloaded items. Additionally, added a
support for rendering signature help documentation.

---------

Co-authored-by: Fernando Tagawa <tagawafernando@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Kirill Bulatov <mail4score@gmail.com>
Co-authored-by: Kirill Bulatov <kirill@zed.dev>
This commit is contained in:
Shuhei Kadowaki 2025-07-03 02:51:08 +09:00 committed by GitHub
parent aa60647fe8
commit 105acacff9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 727 additions and 214 deletions

View file

@ -10866,9 +10866,10 @@ async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
cx.editor(|editor, _, _| {
let signature_help_state = editor.signature_help_state.popover().cloned();
let signature = signature_help_state.unwrap();
assert_eq!(
signature_help_state.unwrap().label,
"param1: u8, param2: u8"
signature.signatures[signature.current_signature].label,
"fn sample(param1: u8, param2: u8)"
);
});
}
@ -11037,9 +11038,10 @@ async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestA
cx.update_editor(|editor, _, _| {
let signature_help_state = editor.signature_help_state.popover().cloned();
assert!(signature_help_state.is_some());
let signature = signature_help_state.unwrap();
assert_eq!(
signature_help_state.unwrap().label,
"param1: u8, param2: u8"
signature.signatures[signature.current_signature].label,
"fn sample(param1: u8, param2: u8)"
);
editor.signature_help_state = SignatureHelpState::default();
});
@ -11078,9 +11080,10 @@ async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestA
cx.editor(|editor, _, _| {
let signature_help_state = editor.signature_help_state.popover().cloned();
assert!(signature_help_state.is_some());
let signature = signature_help_state.unwrap();
assert_eq!(
signature_help_state.unwrap().label,
"param1: u8, param2: u8"
signature.signatures[signature.current_signature].label,
"fn sample(param1: u8, param2: u8)"
);
});
}
@ -11139,9 +11142,10 @@ async fn test_signature_help(cx: &mut TestAppContext) {
cx.editor(|editor, _, _| {
let signature_help_state = editor.signature_help_state.popover().cloned();
assert!(signature_help_state.is_some());
let signature = signature_help_state.unwrap();
assert_eq!(
signature_help_state.unwrap().label,
"param1: u8, param2: u8"
signature.signatures[signature.current_signature].label,
"fn sample(param1: u8, param2: u8)"
);
});
@ -11349,6 +11353,132 @@ async fn test_signature_help(cx: &mut TestAppContext) {
.await;
}
#[gpui::test]
async fn test_signature_help_multiple_signatures(cx: &mut TestAppContext) {
init_test(cx, |_| {});
let mut cx = EditorLspTestContext::new_rust(
lsp::ServerCapabilities {
signature_help_provider: Some(lsp::SignatureHelpOptions {
..Default::default()
}),
..Default::default()
},
cx,
)
.await;
cx.set_state(indoc! {"
fn main() {
overloadedˇ
}
"});
cx.update_editor(|editor, window, cx| {
editor.handle_input("(", window, cx);
editor.show_signature_help(&ShowSignatureHelp, window, cx);
});
// Mock response with 3 signatures
let mocked_response = lsp::SignatureHelp {
signatures: vec![
lsp::SignatureInformation {
label: "fn overloaded(x: i32)".to_string(),
documentation: None,
parameters: Some(vec![lsp::ParameterInformation {
label: lsp::ParameterLabel::Simple("x: i32".to_string()),
documentation: None,
}]),
active_parameter: None,
},
lsp::SignatureInformation {
label: "fn overloaded(x: i32, y: i32)".to_string(),
documentation: None,
parameters: Some(vec![
lsp::ParameterInformation {
label: lsp::ParameterLabel::Simple("x: i32".to_string()),
documentation: None,
},
lsp::ParameterInformation {
label: lsp::ParameterLabel::Simple("y: i32".to_string()),
documentation: None,
},
]),
active_parameter: None,
},
lsp::SignatureInformation {
label: "fn overloaded(x: i32, y: i32, z: i32)".to_string(),
documentation: None,
parameters: Some(vec![
lsp::ParameterInformation {
label: lsp::ParameterLabel::Simple("x: i32".to_string()),
documentation: None,
},
lsp::ParameterInformation {
label: lsp::ParameterLabel::Simple("y: i32".to_string()),
documentation: None,
},
lsp::ParameterInformation {
label: lsp::ParameterLabel::Simple("z: i32".to_string()),
documentation: None,
},
]),
active_parameter: None,
},
],
active_signature: Some(1),
active_parameter: Some(0),
};
handle_signature_help_request(&mut cx, mocked_response).await;
cx.condition(|editor, _| editor.signature_help_state.is_shown())
.await;
// Verify we have multiple signatures and the right one is selected
cx.editor(|editor, _, _| {
let popover = editor.signature_help_state.popover().cloned().unwrap();
assert_eq!(popover.signatures.len(), 3);
// active_signature was 1, so that should be the current
assert_eq!(popover.current_signature, 1);
assert_eq!(popover.signatures[0].label, "fn overloaded(x: i32)");
assert_eq!(popover.signatures[1].label, "fn overloaded(x: i32, y: i32)");
assert_eq!(
popover.signatures[2].label,
"fn overloaded(x: i32, y: i32, z: i32)"
);
});
// Test navigation functionality
cx.update_editor(|editor, window, cx| {
editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
});
cx.editor(|editor, _, _| {
let popover = editor.signature_help_state.popover().cloned().unwrap();
assert_eq!(popover.current_signature, 2);
});
// Test wrap around
cx.update_editor(|editor, window, cx| {
editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
});
cx.editor(|editor, _, _| {
let popover = editor.signature_help_state.popover().cloned().unwrap();
assert_eq!(popover.current_signature, 0);
});
// Test previous navigation
cx.update_editor(|editor, window, cx| {
editor.signature_help_prev(&crate::SignatureHelpPrevious, window, cx);
});
cx.editor(|editor, _, _| {
let popover = editor.signature_help_state.popover().cloned().unwrap();
assert_eq!(popover.current_signature, 2);
});
}
#[gpui::test]
async fn test_completion_mode(cx: &mut TestAppContext) {
init_test(cx, |_| {});