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:
parent
aa60647fe8
commit
105acacff9
21 changed files with 727 additions and 214 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -12258,6 +12258,7 @@ dependencies = [
|
|||
"language",
|
||||
"log",
|
||||
"lsp",
|
||||
"markdown",
|
||||
"node_runtime",
|
||||
"parking_lot",
|
||||
"pathdiff",
|
||||
|
|
|
@ -708,6 +708,13 @@
|
|||
"pagedown": "editor::ContextMenuLast"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && showing_signature_help && !showing_completions",
|
||||
"bindings": {
|
||||
"up": "editor::SignatureHelpPrevious",
|
||||
"down": "editor::SignatureHelpNext"
|
||||
}
|
||||
},
|
||||
// Custom bindings
|
||||
{
|
||||
"bindings": {
|
||||
|
|
|
@ -773,6 +773,13 @@
|
|||
"pagedown": "editor::ContextMenuLast"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && showing_signature_help && !showing_completions",
|
||||
"bindings": {
|
||||
"up": "editor::SignatureHelpPrevious",
|
||||
"down": "editor::SignatureHelpNext"
|
||||
}
|
||||
},
|
||||
// Custom bindings
|
||||
{
|
||||
"use_key_equivalents": true,
|
||||
|
|
|
@ -98,6 +98,13 @@
|
|||
"ctrl-n": "editor::ContextMenuNext"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && showing_signature_help && !showing_completions",
|
||||
"bindings": {
|
||||
"ctrl-p": "editor::SignatureHelpPrevious",
|
||||
"ctrl-n": "editor::SignatureHelpNext"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Workspace",
|
||||
"bindings": {
|
||||
|
|
|
@ -98,6 +98,13 @@
|
|||
"ctrl-n": "editor::ContextMenuNext"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && showing_signature_help && !showing_completions",
|
||||
"bindings": {
|
||||
"ctrl-p": "editor::SignatureHelpPrevious",
|
||||
"ctrl-n": "editor::SignatureHelpNext"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Workspace",
|
||||
"bindings": {
|
||||
|
|
|
@ -477,6 +477,13 @@
|
|||
"ctrl-n": "editor::ShowWordCompletions"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_mode == insert && showing_signature_help && !showing_completions",
|
||||
"bindings": {
|
||||
"ctrl-p": "editor::SignatureHelpPrevious",
|
||||
"ctrl-n": "editor::SignatureHelpNext"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_mode == replace",
|
||||
"bindings": {
|
||||
|
|
|
@ -424,6 +424,8 @@ actions!(
|
|||
ShowSignatureHelp,
|
||||
ShowWordCompletions,
|
||||
ShuffleLines,
|
||||
SignatureHelpNext,
|
||||
SignatureHelpPrevious,
|
||||
SortLinesCaseInsensitive,
|
||||
SortLinesCaseSensitive,
|
||||
SplitSelectionIntoLines,
|
||||
|
|
|
@ -2362,6 +2362,10 @@ impl Editor {
|
|||
None => {}
|
||||
}
|
||||
|
||||
if self.signature_help_state.has_multiple_signatures() {
|
||||
key_context.add("showing_signature_help");
|
||||
}
|
||||
|
||||
// Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
|
||||
if !self.focus_handle(cx).contains_focused(window, cx)
|
||||
|| (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
|
||||
|
@ -12582,6 +12586,38 @@ impl Editor {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn signature_help_prev(
|
||||
&mut self,
|
||||
_: &SignatureHelpPrevious,
|
||||
_: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if let Some(popover) = self.signature_help_state.popover_mut() {
|
||||
if popover.current_signature == 0 {
|
||||
popover.current_signature = popover.signatures.len() - 1;
|
||||
} else {
|
||||
popover.current_signature -= 1;
|
||||
}
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn signature_help_next(
|
||||
&mut self,
|
||||
_: &SignatureHelpNext,
|
||||
_: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if let Some(popover) = self.signature_help_state.popover_mut() {
|
||||
if popover.current_signature + 1 == popover.signatures.len() {
|
||||
popover.current_signature = 0;
|
||||
} else {
|
||||
popover.current_signature += 1;
|
||||
}
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn move_to_previous_word_start(
|
||||
&mut self,
|
||||
_: &MoveToPreviousWordStart,
|
||||
|
|
|
@ -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, |_| {});
|
||||
|
|
|
@ -546,6 +546,8 @@ impl EditorElement {
|
|||
}
|
||||
});
|
||||
register_action(editor, window, Editor::show_signature_help);
|
||||
register_action(editor, window, Editor::signature_help_prev);
|
||||
register_action(editor, window, Editor::signature_help_next);
|
||||
register_action(editor, window, Editor::next_edit_prediction);
|
||||
register_action(editor, window, Editor::previous_edit_prediction);
|
||||
register_action(editor, window, Editor::show_inline_completion);
|
||||
|
@ -4985,7 +4987,7 @@ impl EditorElement {
|
|||
|
||||
let maybe_element = self.editor.update(cx, |editor, cx| {
|
||||
if let Some(popover) = editor.signature_help_state.popover_mut() {
|
||||
let element = popover.render(max_size, cx);
|
||||
let element = popover.render(max_size, window, cx);
|
||||
Some(element)
|
||||
} else {
|
||||
None
|
||||
|
|
|
@ -1,18 +1,22 @@
|
|||
use crate::actions::ShowSignatureHelp;
|
||||
use crate::{Editor, EditorSettings, ToggleAutoSignatureHelp};
|
||||
use crate::hover_popover::open_markdown_url;
|
||||
use crate::{Editor, EditorSettings, ToggleAutoSignatureHelp, hover_markdown_style};
|
||||
use gpui::{
|
||||
App, Context, HighlightStyle, MouseButton, Size, StyledText, Task, TextStyle, Window,
|
||||
combine_highlights,
|
||||
App, Context, Div, Entity, HighlightStyle, MouseButton, ScrollHandle, Size, Stateful,
|
||||
StyledText, Task, TextStyle, Window, combine_highlights,
|
||||
};
|
||||
use language::BufferSnapshot;
|
||||
use markdown::{Markdown, MarkdownElement};
|
||||
use multi_buffer::{Anchor, ToOffset};
|
||||
use settings::Settings;
|
||||
use std::ops::Range;
|
||||
use text::Rope;
|
||||
use theme::ThemeSettings;
|
||||
use ui::{
|
||||
ActiveTheme, AnyElement, InteractiveElement, IntoElement, ParentElement, Pixels, SharedString,
|
||||
StatefulInteractiveElement, Styled, StyledExt, div, relative,
|
||||
ActiveTheme, AnyElement, ButtonCommon, ButtonStyle, Clickable, FluentBuilder, IconButton,
|
||||
IconButtonShape, IconName, IconSize, InteractiveElement, IntoElement, Label, LabelCommon,
|
||||
LabelSize, ParentElement, Pixels, Scrollbar, ScrollbarState, SharedString,
|
||||
StatefulInteractiveElement, Styled, StyledExt, div, px, relative,
|
||||
};
|
||||
|
||||
// Language-specific settings may define quotes as "brackets", so filter them out separately.
|
||||
|
@ -37,15 +41,14 @@ impl Editor {
|
|||
.map(|auto_signature_help| !auto_signature_help)
|
||||
.or_else(|| Some(!EditorSettings::get_global(cx).auto_signature_help));
|
||||
match self.auto_signature_help {
|
||||
Some(auto_signature_help) if auto_signature_help => {
|
||||
Some(true) => {
|
||||
self.show_signature_help(&ShowSignatureHelp, window, cx);
|
||||
}
|
||||
Some(_) => {
|
||||
Some(false) => {
|
||||
self.hide_signature_help(cx, SignatureHelpHiddenBy::AutoClose);
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub(super) fn hide_signature_help(
|
||||
|
@ -54,7 +57,7 @@ impl Editor {
|
|||
signature_help_hidden_by: SignatureHelpHiddenBy,
|
||||
) -> bool {
|
||||
if self.signature_help_state.is_shown() {
|
||||
self.signature_help_state.kill_task();
|
||||
self.signature_help_state.task = None;
|
||||
self.signature_help_state.hide(signature_help_hidden_by);
|
||||
cx.notify();
|
||||
true
|
||||
|
@ -187,31 +190,62 @@ impl Editor {
|
|||
};
|
||||
|
||||
if let Some(language) = language {
|
||||
let text = Rope::from(signature_help.label.clone());
|
||||
let highlights = language
|
||||
.highlight_text(&text, 0..signature_help.label.len())
|
||||
.into_iter()
|
||||
.flat_map(|(range, highlight_id)| {
|
||||
Some((range, highlight_id.style(&cx.theme().syntax())?))
|
||||
});
|
||||
signature_help.highlights =
|
||||
combine_highlights(signature_help.highlights, highlights).collect()
|
||||
for signature in &mut signature_help.signatures {
|
||||
let text = Rope::from(signature.label.to_string());
|
||||
let highlights = language
|
||||
.highlight_text(&text, 0..signature.label.len())
|
||||
.into_iter()
|
||||
.flat_map(|(range, highlight_id)| {
|
||||
Some((range, highlight_id.style(&cx.theme().syntax())?))
|
||||
});
|
||||
signature.highlights =
|
||||
combine_highlights(signature.highlights.clone(), highlights)
|
||||
.collect();
|
||||
}
|
||||
}
|
||||
let settings = ThemeSettings::get_global(cx);
|
||||
let text_style = TextStyle {
|
||||
let style = TextStyle {
|
||||
color: cx.theme().colors().text,
|
||||
font_family: settings.buffer_font.family.clone(),
|
||||
font_fallbacks: settings.buffer_font.fallbacks.clone(),
|
||||
font_size: settings.buffer_font_size(cx).into(),
|
||||
font_weight: settings.buffer_font.weight,
|
||||
line_height: relative(settings.buffer_line_height.value()),
|
||||
..Default::default()
|
||||
..TextStyle::default()
|
||||
};
|
||||
let scroll_handle = ScrollHandle::new();
|
||||
let signatures = signature_help
|
||||
.signatures
|
||||
.into_iter()
|
||||
.map(|s| SignatureHelp {
|
||||
label: s.label,
|
||||
documentation: s.documentation,
|
||||
highlights: s.highlights,
|
||||
active_parameter: s.active_parameter,
|
||||
parameter_documentation: s
|
||||
.active_parameter
|
||||
.and_then(|idx| s.parameters.get(idx))
|
||||
.and_then(|param| param.documentation.clone()),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if signatures.is_empty() {
|
||||
editor
|
||||
.signature_help_state
|
||||
.hide(SignatureHelpHiddenBy::AutoClose);
|
||||
return;
|
||||
}
|
||||
|
||||
let current_signature = signature_help
|
||||
.active_signature
|
||||
.min(signatures.len().saturating_sub(1));
|
||||
|
||||
let signature_help_popover = SignatureHelpPopover {
|
||||
label: signature_help.label.into(),
|
||||
highlights: signature_help.highlights,
|
||||
style: text_style,
|
||||
scrollbar_state: ScrollbarState::new(scroll_handle.clone()),
|
||||
style,
|
||||
signatures,
|
||||
current_signature,
|
||||
scroll_handle,
|
||||
};
|
||||
editor
|
||||
.signature_help_state
|
||||
|
@ -231,15 +265,11 @@ pub struct SignatureHelpState {
|
|||
}
|
||||
|
||||
impl SignatureHelpState {
|
||||
pub fn set_task(&mut self, task: Task<()>) {
|
||||
fn set_task(&mut self, task: Task<()>) {
|
||||
self.task = Some(task);
|
||||
self.hidden_by = None;
|
||||
}
|
||||
|
||||
pub fn kill_task(&mut self) {
|
||||
self.task = None;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn popover(&self) -> Option<&SignatureHelpPopover> {
|
||||
self.popover.as_ref()
|
||||
|
@ -249,25 +279,31 @@ impl SignatureHelpState {
|
|||
self.popover.as_mut()
|
||||
}
|
||||
|
||||
pub fn set_popover(&mut self, popover: SignatureHelpPopover) {
|
||||
fn set_popover(&mut self, popover: SignatureHelpPopover) {
|
||||
self.popover = Some(popover);
|
||||
self.hidden_by = None;
|
||||
}
|
||||
|
||||
pub fn hide(&mut self, hidden_by: SignatureHelpHiddenBy) {
|
||||
fn hide(&mut self, hidden_by: SignatureHelpHiddenBy) {
|
||||
if self.hidden_by.is_none() {
|
||||
self.popover = None;
|
||||
self.hidden_by = Some(hidden_by);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn hidden_by_selection(&self) -> bool {
|
||||
fn hidden_by_selection(&self) -> bool {
|
||||
self.hidden_by == Some(SignatureHelpHiddenBy::Selection)
|
||||
}
|
||||
|
||||
pub fn is_shown(&self) -> bool {
|
||||
self.popover.is_some()
|
||||
}
|
||||
|
||||
pub fn has_multiple_signatures(&self) -> bool {
|
||||
self.popover
|
||||
.as_ref()
|
||||
.is_some_and(|popover| popover.signatures.len() > 1)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -278,28 +314,170 @@ impl SignatureHelpState {
|
|||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct SignatureHelp {
|
||||
pub(crate) label: SharedString,
|
||||
documentation: Option<Entity<Markdown>>,
|
||||
highlights: Vec<(Range<usize>, HighlightStyle)>,
|
||||
active_parameter: Option<usize>,
|
||||
parameter_documentation: Option<Entity<Markdown>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct SignatureHelpPopover {
|
||||
pub label: SharedString,
|
||||
pub style: TextStyle,
|
||||
pub highlights: Vec<(Range<usize>, HighlightStyle)>,
|
||||
pub signatures: Vec<SignatureHelp>,
|
||||
pub current_signature: usize,
|
||||
scroll_handle: ScrollHandle,
|
||||
scrollbar_state: ScrollbarState,
|
||||
}
|
||||
|
||||
impl SignatureHelpPopover {
|
||||
pub fn render(&mut self, max_size: Size<Pixels>, cx: &mut Context<Editor>) -> AnyElement {
|
||||
div()
|
||||
.id("signature_help_popover")
|
||||
.elevation_2(cx)
|
||||
.overflow_y_scroll()
|
||||
.max_w(max_size.width)
|
||||
.max_h(max_size.height)
|
||||
.on_mouse_move(|_, _, cx| cx.stop_propagation())
|
||||
.on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
|
||||
pub fn render(
|
||||
&mut self,
|
||||
max_size: Size<Pixels>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Editor>,
|
||||
) -> AnyElement {
|
||||
let Some(signature) = self.signatures.get(self.current_signature) else {
|
||||
return div().into_any_element();
|
||||
};
|
||||
|
||||
let main_content = div()
|
||||
.occlude()
|
||||
.p_2()
|
||||
.child(
|
||||
div().px_2().py_0p5().child(
|
||||
StyledText::new(self.label.clone())
|
||||
.with_default_highlights(&self.style, self.highlights.iter().cloned()),
|
||||
),
|
||||
div()
|
||||
.id("signature_help_container")
|
||||
.overflow_y_scroll()
|
||||
.max_w(max_size.width)
|
||||
.max_h(max_size.height)
|
||||
.track_scroll(&self.scroll_handle)
|
||||
.child(
|
||||
StyledText::new(signature.label.clone()).with_default_highlights(
|
||||
&self.style,
|
||||
signature.highlights.iter().cloned(),
|
||||
),
|
||||
)
|
||||
.when_some(
|
||||
signature.parameter_documentation.clone(),
|
||||
|this, param_doc| {
|
||||
this.child(div().h_px().bg(cx.theme().colors().border_variant).my_1())
|
||||
.child(
|
||||
MarkdownElement::new(
|
||||
param_doc,
|
||||
hover_markdown_style(window, cx),
|
||||
)
|
||||
.code_block_renderer(markdown::CodeBlockRenderer::Default {
|
||||
copy_button: false,
|
||||
border: false,
|
||||
copy_button_on_hover: false,
|
||||
})
|
||||
.on_url_click(open_markdown_url),
|
||||
)
|
||||
},
|
||||
)
|
||||
.when_some(signature.documentation.clone(), |this, description| {
|
||||
this.child(div().h_px().bg(cx.theme().colors().border_variant).my_1())
|
||||
.child(
|
||||
MarkdownElement::new(description, hover_markdown_style(window, cx))
|
||||
.code_block_renderer(markdown::CodeBlockRenderer::Default {
|
||||
copy_button: false,
|
||||
border: false,
|
||||
copy_button_on_hover: false,
|
||||
})
|
||||
.on_url_click(open_markdown_url),
|
||||
)
|
||||
}),
|
||||
)
|
||||
.child(self.render_vertical_scrollbar(cx));
|
||||
let controls = if self.signatures.len() > 1 {
|
||||
let prev_button = IconButton::new("signature_help_prev", IconName::ChevronUp)
|
||||
.shape(IconButtonShape::Square)
|
||||
.style(ButtonStyle::Subtle)
|
||||
.icon_size(IconSize::Small)
|
||||
.tooltip(move |window, cx| {
|
||||
ui::Tooltip::for_action(
|
||||
"Previous Signature",
|
||||
&crate::SignatureHelpPrevious,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.on_click(cx.listener(|editor, _, window, cx| {
|
||||
editor.signature_help_prev(&crate::SignatureHelpPrevious, window, cx);
|
||||
}));
|
||||
|
||||
let next_button = IconButton::new("signature_help_next", IconName::ChevronDown)
|
||||
.shape(IconButtonShape::Square)
|
||||
.style(ButtonStyle::Subtle)
|
||||
.icon_size(IconSize::Small)
|
||||
.tooltip(move |window, cx| {
|
||||
ui::Tooltip::for_action("Next Signature", &crate::SignatureHelpNext, window, cx)
|
||||
})
|
||||
.on_click(cx.listener(|editor, _, window, cx| {
|
||||
editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
|
||||
}));
|
||||
|
||||
let page = Label::new(format!(
|
||||
"{}/{}",
|
||||
self.current_signature + 1,
|
||||
self.signatures.len()
|
||||
))
|
||||
.size(LabelSize::Small);
|
||||
|
||||
Some(
|
||||
div()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.items_center()
|
||||
.gap_0p5()
|
||||
.px_0p5()
|
||||
.py_0p5()
|
||||
.children([
|
||||
prev_button.into_any_element(),
|
||||
div().child(page).into_any_element(),
|
||||
next_button.into_any_element(),
|
||||
])
|
||||
.into_any_element(),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
div()
|
||||
.elevation_2(cx)
|
||||
.on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
|
||||
.on_mouse_move(|_, _, cx| cx.stop_propagation())
|
||||
.flex()
|
||||
.flex_row()
|
||||
.when_some(controls, |this, controls| {
|
||||
this.children(vec![
|
||||
div().flex().items_end().child(controls),
|
||||
div().w_px().bg(cx.theme().colors().border_variant),
|
||||
])
|
||||
})
|
||||
.child(main_content)
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
fn render_vertical_scrollbar(&self, cx: &mut Context<Editor>) -> Stateful<Div> {
|
||||
div()
|
||||
.occlude()
|
||||
.id("signature_help_scrollbar")
|
||||
.on_mouse_move(cx.listener(|_, _, _, cx| {
|
||||
cx.notify();
|
||||
cx.stop_propagation()
|
||||
}))
|
||||
.on_hover(|_, _, cx| cx.stop_propagation())
|
||||
.on_any_mouse_down(|_, _, cx| cx.stop_propagation())
|
||||
.on_mouse_up(MouseButton::Left, |_, _, cx| cx.stop_propagation())
|
||||
.on_scroll_wheel(cx.listener(|_, _, _, cx| cx.notify()))
|
||||
.h_full()
|
||||
.absolute()
|
||||
.right_1()
|
||||
.top_1()
|
||||
.bottom_1()
|
||||
.w(px(12.))
|
||||
.cursor_default()
|
||||
.children(Scrollbar::vertical(self.scrollbar_state.clone()))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -421,7 +421,7 @@ impl Selection {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct ParsedMarkdown {
|
||||
pub source: SharedString,
|
||||
pub events: Arc<[(Range<usize>, MarkdownEvent)]>,
|
||||
|
@ -1672,7 +1672,7 @@ struct RenderedText {
|
|||
links: Rc<[RenderedLink]>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Eq, PartialEq)]
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
struct RenderedLink {
|
||||
source_range: Range<usize>,
|
||||
destination_url: SharedString,
|
||||
|
|
|
@ -54,6 +54,7 @@ indexmap.workspace = true
|
|||
language.workspace = true
|
||||
log.workspace = true
|
||||
lsp.workspace = true
|
||||
markdown.workspace = true
|
||||
node_runtime.workspace = true
|
||||
parking_lot.workspace = true
|
||||
pathdiff.workspace = true
|
||||
|
|
|
@ -1846,12 +1846,15 @@ impl LspCommand for GetSignatureHelp {
|
|||
async fn response_from_lsp(
|
||||
self,
|
||||
message: Option<lsp::SignatureHelp>,
|
||||
_: Entity<LspStore>,
|
||||
lsp_store: Entity<LspStore>,
|
||||
_: Entity<Buffer>,
|
||||
_: LanguageServerId,
|
||||
_: AsyncApp,
|
||||
cx: AsyncApp,
|
||||
) -> Result<Self::Response> {
|
||||
Ok(message.and_then(SignatureHelp::new))
|
||||
let Some(message) = message else {
|
||||
return Ok(None);
|
||||
};
|
||||
cx.update(|cx| SignatureHelp::new(message, Some(lsp_store.read(cx).languages.clone()), cx))
|
||||
}
|
||||
|
||||
fn to_proto(&self, project_id: u64, buffer: &Buffer) -> Self::ProtoRequest {
|
||||
|
@ -1902,14 +1905,18 @@ impl LspCommand for GetSignatureHelp {
|
|||
async fn response_from_proto(
|
||||
self,
|
||||
response: proto::GetSignatureHelpResponse,
|
||||
_: Entity<LspStore>,
|
||||
lsp_store: Entity<LspStore>,
|
||||
_: Entity<Buffer>,
|
||||
_: AsyncApp,
|
||||
cx: AsyncApp,
|
||||
) -> Result<Self::Response> {
|
||||
Ok(response
|
||||
.signature_help
|
||||
.map(proto_to_lsp_signature)
|
||||
.and_then(SignatureHelp::new))
|
||||
cx.update(|cx| {
|
||||
response
|
||||
.signature_help
|
||||
.map(proto_to_lsp_signature)
|
||||
.and_then(|signature| {
|
||||
SignatureHelp::new(signature, Some(lsp_store.read(cx).languages.clone()), cx)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn buffer_id_from_proto(message: &Self::ProtoRequest) -> Result<BufferId> {
|
||||
|
|
|
@ -1,94 +1,143 @@
|
|||
use std::ops::Range;
|
||||
use std::{ops::Range, sync::Arc};
|
||||
|
||||
use gpui::{FontStyle, FontWeight, HighlightStyle};
|
||||
use gpui::{App, AppContext, Entity, FontWeight, HighlightStyle, SharedString};
|
||||
use language::LanguageRegistry;
|
||||
use markdown::Markdown;
|
||||
use rpc::proto::{self, documentation};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SignatureHelp {
|
||||
pub label: String,
|
||||
pub highlights: Vec<(Range<usize>, HighlightStyle)>,
|
||||
pub active_signature: usize,
|
||||
pub signatures: Vec<SignatureHelpData>,
|
||||
pub(super) original_data: lsp::SignatureHelp,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SignatureHelpData {
|
||||
pub label: SharedString,
|
||||
pub documentation: Option<Entity<Markdown>>,
|
||||
pub highlights: Vec<(Range<usize>, HighlightStyle)>,
|
||||
pub active_parameter: Option<usize>,
|
||||
pub parameters: Vec<ParameterInfo>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ParameterInfo {
|
||||
pub label_range: Option<Range<usize>>,
|
||||
pub documentation: Option<Entity<Markdown>>,
|
||||
}
|
||||
|
||||
impl SignatureHelp {
|
||||
pub fn new(help: lsp::SignatureHelp) -> Option<Self> {
|
||||
let function_options_count = help.signatures.len();
|
||||
|
||||
let signature_information = help
|
||||
.active_signature
|
||||
.and_then(|active_signature| help.signatures.get(active_signature as usize))
|
||||
.or_else(|| help.signatures.first())?;
|
||||
|
||||
let str_for_join = ", ";
|
||||
let parameter_length = signature_information
|
||||
.parameters
|
||||
.as_ref()
|
||||
.map_or(0, |parameters| parameters.len());
|
||||
let mut highlight_start = 0;
|
||||
let (strings, mut highlights): (Vec<_>, Vec<_>) = signature_information
|
||||
.parameters
|
||||
.as_ref()?
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, parameter_information)| {
|
||||
let label = match parameter_information.label.clone() {
|
||||
lsp::ParameterLabel::Simple(string) => string,
|
||||
lsp::ParameterLabel::LabelOffsets(offset) => signature_information
|
||||
.label
|
||||
.chars()
|
||||
.skip(offset[0] as usize)
|
||||
.take((offset[1] - offset[0]) as usize)
|
||||
.collect::<String>(),
|
||||
};
|
||||
let label_length = label.len();
|
||||
|
||||
let highlights = help.active_parameter.and_then(|active_parameter| {
|
||||
if i == active_parameter as usize {
|
||||
Some((
|
||||
highlight_start..(highlight_start + label_length),
|
||||
HighlightStyle {
|
||||
font_weight: Some(FontWeight::EXTRA_BOLD),
|
||||
..Default::default()
|
||||
},
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
if i != parameter_length {
|
||||
highlight_start += label_length + str_for_join.len();
|
||||
}
|
||||
|
||||
(label, highlights)
|
||||
})
|
||||
.unzip();
|
||||
|
||||
if strings.is_empty() {
|
||||
None
|
||||
} else {
|
||||
let mut label = strings.join(str_for_join);
|
||||
|
||||
if function_options_count >= 2 {
|
||||
let suffix = format!("(+{} overload)", function_options_count - 1);
|
||||
let highlight_start = label.len() + 1;
|
||||
highlights.push(Some((
|
||||
highlight_start..(highlight_start + suffix.len()),
|
||||
HighlightStyle {
|
||||
font_style: Some(FontStyle::Italic),
|
||||
..Default::default()
|
||||
},
|
||||
)));
|
||||
label.push(' ');
|
||||
label.push_str(&suffix);
|
||||
};
|
||||
|
||||
Some(Self {
|
||||
label,
|
||||
highlights: highlights.into_iter().flatten().collect(),
|
||||
original_data: help,
|
||||
})
|
||||
pub fn new(
|
||||
help: lsp::SignatureHelp,
|
||||
language_registry: Option<Arc<LanguageRegistry>>,
|
||||
cx: &mut App,
|
||||
) -> Option<Self> {
|
||||
if help.signatures.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let active_signature = help.active_signature.unwrap_or(0) as usize;
|
||||
let mut signatures = Vec::<SignatureHelpData>::with_capacity(help.signatures.capacity());
|
||||
for signature in &help.signatures {
|
||||
let active_parameter = signature
|
||||
.active_parameter
|
||||
.unwrap_or_else(|| help.active_parameter.unwrap_or(0))
|
||||
as usize;
|
||||
let mut highlights = Vec::new();
|
||||
let mut parameter_infos = Vec::new();
|
||||
|
||||
if let Some(parameters) = &signature.parameters {
|
||||
for (index, parameter) in parameters.iter().enumerate() {
|
||||
let label_range = match ¶meter.label {
|
||||
lsp::ParameterLabel::LabelOffsets(parameter_label_offsets) => {
|
||||
let range = *parameter_label_offsets.get(0)? as usize
|
||||
..*parameter_label_offsets.get(1)? as usize;
|
||||
if index == active_parameter {
|
||||
highlights.push((
|
||||
range.clone(),
|
||||
HighlightStyle {
|
||||
font_weight: Some(FontWeight::EXTRA_BOLD),
|
||||
..HighlightStyle::default()
|
||||
},
|
||||
));
|
||||
}
|
||||
Some(range)
|
||||
}
|
||||
lsp::ParameterLabel::Simple(parameter_label) => {
|
||||
if let Some(start) = signature.label.find(parameter_label) {
|
||||
let range = start..start + parameter_label.len();
|
||||
if index == active_parameter {
|
||||
highlights.push((
|
||||
range.clone(),
|
||||
HighlightStyle {
|
||||
font_weight: Some(FontWeight::EXTRA_BOLD),
|
||||
..HighlightStyle::default()
|
||||
},
|
||||
));
|
||||
}
|
||||
Some(range)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let documentation = parameter
|
||||
.documentation
|
||||
.as_ref()
|
||||
.map(|doc| documentation_to_markdown(doc, language_registry.clone(), cx));
|
||||
|
||||
parameter_infos.push(ParameterInfo {
|
||||
label_range,
|
||||
documentation,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let label = SharedString::from(signature.label.clone());
|
||||
let documentation = signature
|
||||
.documentation
|
||||
.as_ref()
|
||||
.map(|doc| documentation_to_markdown(doc, language_registry.clone(), cx));
|
||||
|
||||
signatures.push(SignatureHelpData {
|
||||
label,
|
||||
documentation,
|
||||
highlights,
|
||||
active_parameter: Some(active_parameter),
|
||||
parameters: parameter_infos,
|
||||
});
|
||||
}
|
||||
Some(Self {
|
||||
signatures,
|
||||
active_signature,
|
||||
original_data: help,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn documentation_to_markdown(
|
||||
documentation: &lsp::Documentation,
|
||||
language_registry: Option<Arc<LanguageRegistry>>,
|
||||
cx: &mut App,
|
||||
) -> Entity<Markdown> {
|
||||
match documentation {
|
||||
lsp::Documentation::String(string) => {
|
||||
cx.new(|cx| Markdown::new_text(SharedString::from(string), cx))
|
||||
}
|
||||
lsp::Documentation::MarkupContent(markup) => match markup.kind {
|
||||
lsp::MarkupKind::PlainText => {
|
||||
cx.new(|cx| Markdown::new_text(SharedString::from(&markup.value), cx))
|
||||
}
|
||||
lsp::MarkupKind::Markdown => cx.new(|cx| {
|
||||
Markdown::new(
|
||||
SharedString::from(&markup.value),
|
||||
language_registry,
|
||||
None,
|
||||
cx,
|
||||
)
|
||||
}),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -206,7 +255,8 @@ fn proto_to_lsp_documentation(documentation: proto::Documentation) -> Option<lsp
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use gpui::{FontStyle, FontWeight, HighlightStyle};
|
||||
use gpui::{FontWeight, HighlightStyle, SharedString, TestAppContext};
|
||||
use lsp::{Documentation, MarkupContent, MarkupKind};
|
||||
|
||||
use crate::lsp_command::signature_help::SignatureHelp;
|
||||
|
||||
|
@ -217,19 +267,14 @@ mod tests {
|
|||
}
|
||||
}
|
||||
|
||||
fn overload() -> HighlightStyle {
|
||||
HighlightStyle {
|
||||
font_style: Some(FontStyle::Italic),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_signature_help_markdown_string_1() {
|
||||
#[gpui::test]
|
||||
fn test_create_signature_help_markdown_string_1(cx: &mut TestAppContext) {
|
||||
let signature_help = lsp::SignatureHelp {
|
||||
signatures: vec![lsp::SignatureInformation {
|
||||
label: "fn test(foo: u8, bar: &str)".to_string(),
|
||||
documentation: None,
|
||||
documentation: Some(Documentation::String(
|
||||
"This is a test documentation".to_string(),
|
||||
)),
|
||||
parameters: Some(vec![
|
||||
lsp::ParameterInformation {
|
||||
label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
|
||||
|
@ -245,26 +290,37 @@ mod tests {
|
|||
active_signature: Some(0),
|
||||
active_parameter: Some(0),
|
||||
};
|
||||
let maybe_markdown = SignatureHelp::new(signature_help);
|
||||
let maybe_markdown = cx.update(|cx| SignatureHelp::new(signature_help, None, cx));
|
||||
assert!(maybe_markdown.is_some());
|
||||
|
||||
let markdown = maybe_markdown.unwrap();
|
||||
let markdown = (markdown.label, markdown.highlights);
|
||||
let signature = markdown.signatures[markdown.active_signature].clone();
|
||||
let markdown = (signature.label, signature.highlights);
|
||||
assert_eq!(
|
||||
markdown,
|
||||
(
|
||||
"foo: u8, bar: &str".to_string(),
|
||||
vec![(0..7, current_parameter())]
|
||||
SharedString::new("fn test(foo: u8, bar: &str)"),
|
||||
vec![(8..15, current_parameter())]
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
signature
|
||||
.documentation
|
||||
.unwrap()
|
||||
.update(cx, |documentation, _| documentation.source().to_owned()),
|
||||
"This is a test documentation",
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_signature_help_markdown_string_2() {
|
||||
#[gpui::test]
|
||||
fn test_create_signature_help_markdown_string_2(cx: &mut TestAppContext) {
|
||||
let signature_help = lsp::SignatureHelp {
|
||||
signatures: vec![lsp::SignatureInformation {
|
||||
label: "fn test(foo: u8, bar: &str)".to_string(),
|
||||
documentation: None,
|
||||
documentation: Some(Documentation::MarkupContent(MarkupContent {
|
||||
kind: MarkupKind::Markdown,
|
||||
value: "This is a test documentation".to_string(),
|
||||
})),
|
||||
parameters: Some(vec![
|
||||
lsp::ParameterInformation {
|
||||
label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
|
||||
|
@ -280,22 +336,30 @@ mod tests {
|
|||
active_signature: Some(0),
|
||||
active_parameter: Some(1),
|
||||
};
|
||||
let maybe_markdown = SignatureHelp::new(signature_help);
|
||||
let maybe_markdown = cx.update(|cx| SignatureHelp::new(signature_help, None, cx));
|
||||
assert!(maybe_markdown.is_some());
|
||||
|
||||
let markdown = maybe_markdown.unwrap();
|
||||
let markdown = (markdown.label, markdown.highlights);
|
||||
let signature = markdown.signatures[markdown.active_signature].clone();
|
||||
let markdown = (signature.label, signature.highlights);
|
||||
assert_eq!(
|
||||
markdown,
|
||||
(
|
||||
"foo: u8, bar: &str".to_string(),
|
||||
vec![(9..18, current_parameter())]
|
||||
SharedString::new("fn test(foo: u8, bar: &str)"),
|
||||
vec![(17..26, current_parameter())]
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
signature
|
||||
.documentation
|
||||
.unwrap()
|
||||
.update(cx, |documentation, _| documentation.source().to_owned()),
|
||||
"This is a test documentation",
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_signature_help_markdown_string_3() {
|
||||
#[gpui::test]
|
||||
fn test_create_signature_help_markdown_string_3(cx: &mut TestAppContext) {
|
||||
let signature_help = lsp::SignatureHelp {
|
||||
signatures: vec![
|
||||
lsp::SignatureInformation {
|
||||
|
@ -332,22 +396,23 @@ mod tests {
|
|||
active_signature: Some(0),
|
||||
active_parameter: Some(0),
|
||||
};
|
||||
let maybe_markdown = SignatureHelp::new(signature_help);
|
||||
let maybe_markdown = cx.update(|cx| SignatureHelp::new(signature_help, None, cx));
|
||||
assert!(maybe_markdown.is_some());
|
||||
|
||||
let markdown = maybe_markdown.unwrap();
|
||||
let markdown = (markdown.label, markdown.highlights);
|
||||
let signature = markdown.signatures[markdown.active_signature].clone();
|
||||
let markdown = (signature.label, signature.highlights);
|
||||
assert_eq!(
|
||||
markdown,
|
||||
(
|
||||
"foo: u8, bar: &str (+1 overload)".to_string(),
|
||||
vec![(0..7, current_parameter()), (19..32, overload())]
|
||||
SharedString::new("fn test1(foo: u8, bar: &str)"),
|
||||
vec![(9..16, current_parameter())]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_signature_help_markdown_string_4() {
|
||||
#[gpui::test]
|
||||
fn test_create_signature_help_markdown_string_4(cx: &mut TestAppContext) {
|
||||
let signature_help = lsp::SignatureHelp {
|
||||
signatures: vec![
|
||||
lsp::SignatureInformation {
|
||||
|
@ -384,22 +449,23 @@ mod tests {
|
|||
active_signature: Some(1),
|
||||
active_parameter: Some(0),
|
||||
};
|
||||
let maybe_markdown = SignatureHelp::new(signature_help);
|
||||
let maybe_markdown = cx.update(|cx| SignatureHelp::new(signature_help, None, cx));
|
||||
assert!(maybe_markdown.is_some());
|
||||
|
||||
let markdown = maybe_markdown.unwrap();
|
||||
let markdown = (markdown.label, markdown.highlights);
|
||||
let signature = markdown.signatures[markdown.active_signature].clone();
|
||||
let markdown = (signature.label, signature.highlights);
|
||||
assert_eq!(
|
||||
markdown,
|
||||
(
|
||||
"hoge: String, fuga: bool (+1 overload)".to_string(),
|
||||
vec![(0..12, current_parameter()), (25..38, overload())]
|
||||
SharedString::new("fn test2(hoge: String, fuga: bool)"),
|
||||
vec![(9..21, current_parameter())]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_signature_help_markdown_string_5() {
|
||||
#[gpui::test]
|
||||
fn test_create_signature_help_markdown_string_5(cx: &mut TestAppContext) {
|
||||
let signature_help = lsp::SignatureHelp {
|
||||
signatures: vec![
|
||||
lsp::SignatureInformation {
|
||||
|
@ -436,22 +502,23 @@ mod tests {
|
|||
active_signature: Some(1),
|
||||
active_parameter: Some(1),
|
||||
};
|
||||
let maybe_markdown = SignatureHelp::new(signature_help);
|
||||
let maybe_markdown = cx.update(|cx| SignatureHelp::new(signature_help, None, cx));
|
||||
assert!(maybe_markdown.is_some());
|
||||
|
||||
let markdown = maybe_markdown.unwrap();
|
||||
let markdown = (markdown.label, markdown.highlights);
|
||||
let signature = markdown.signatures[markdown.active_signature].clone();
|
||||
let markdown = (signature.label, signature.highlights);
|
||||
assert_eq!(
|
||||
markdown,
|
||||
(
|
||||
"hoge: String, fuga: bool (+1 overload)".to_string(),
|
||||
vec![(14..24, current_parameter()), (25..38, overload())]
|
||||
SharedString::new("fn test2(hoge: String, fuga: bool)"),
|
||||
vec![(23..33, current_parameter())]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_signature_help_markdown_string_6() {
|
||||
#[gpui::test]
|
||||
fn test_create_signature_help_markdown_string_6(cx: &mut TestAppContext) {
|
||||
let signature_help = lsp::SignatureHelp {
|
||||
signatures: vec![
|
||||
lsp::SignatureInformation {
|
||||
|
@ -488,22 +555,23 @@ mod tests {
|
|||
active_signature: Some(1),
|
||||
active_parameter: None,
|
||||
};
|
||||
let maybe_markdown = SignatureHelp::new(signature_help);
|
||||
let maybe_markdown = cx.update(|cx| SignatureHelp::new(signature_help, None, cx));
|
||||
assert!(maybe_markdown.is_some());
|
||||
|
||||
let markdown = maybe_markdown.unwrap();
|
||||
let markdown = (markdown.label, markdown.highlights);
|
||||
let signature = markdown.signatures[markdown.active_signature].clone();
|
||||
let markdown = (signature.label, signature.highlights);
|
||||
assert_eq!(
|
||||
markdown,
|
||||
(
|
||||
"hoge: String, fuga: bool (+1 overload)".to_string(),
|
||||
vec![(25..38, overload())]
|
||||
SharedString::new("fn test2(hoge: String, fuga: bool)"),
|
||||
vec![(9..21, current_parameter())]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_signature_help_markdown_string_7() {
|
||||
#[gpui::test]
|
||||
fn test_create_signature_help_markdown_string_7(cx: &mut TestAppContext) {
|
||||
let signature_help = lsp::SignatureHelp {
|
||||
signatures: vec![
|
||||
lsp::SignatureInformation {
|
||||
|
@ -555,33 +623,34 @@ mod tests {
|
|||
active_signature: Some(2),
|
||||
active_parameter: Some(1),
|
||||
};
|
||||
let maybe_markdown = SignatureHelp::new(signature_help);
|
||||
let maybe_markdown = cx.update(|cx| SignatureHelp::new(signature_help, None, cx));
|
||||
assert!(maybe_markdown.is_some());
|
||||
|
||||
let markdown = maybe_markdown.unwrap();
|
||||
let markdown = (markdown.label, markdown.highlights);
|
||||
let signature = markdown.signatures[markdown.active_signature].clone();
|
||||
let markdown = (signature.label, signature.highlights);
|
||||
assert_eq!(
|
||||
markdown,
|
||||
(
|
||||
"one: usize, two: u32 (+2 overload)".to_string(),
|
||||
vec![(12..20, current_parameter()), (21..34, overload())]
|
||||
SharedString::new("fn test3(one: usize, two: u32)"),
|
||||
vec![(21..29, current_parameter())]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_signature_help_markdown_string_8() {
|
||||
#[gpui::test]
|
||||
fn test_create_signature_help_markdown_string_8(cx: &mut TestAppContext) {
|
||||
let signature_help = lsp::SignatureHelp {
|
||||
signatures: vec![],
|
||||
active_signature: None,
|
||||
active_parameter: None,
|
||||
};
|
||||
let maybe_markdown = SignatureHelp::new(signature_help);
|
||||
let maybe_markdown = cx.update(|cx| SignatureHelp::new(signature_help, None, cx));
|
||||
assert!(maybe_markdown.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_signature_help_markdown_string_9() {
|
||||
#[gpui::test]
|
||||
fn test_create_signature_help_markdown_string_9(cx: &mut TestAppContext) {
|
||||
let signature_help = lsp::SignatureHelp {
|
||||
signatures: vec![lsp::SignatureInformation {
|
||||
label: "fn test(foo: u8, bar: &str)".to_string(),
|
||||
|
@ -601,17 +670,70 @@ mod tests {
|
|||
active_signature: Some(0),
|
||||
active_parameter: Some(0),
|
||||
};
|
||||
let maybe_markdown = SignatureHelp::new(signature_help);
|
||||
let maybe_markdown = cx.update(|cx| SignatureHelp::new(signature_help, None, cx));
|
||||
assert!(maybe_markdown.is_some());
|
||||
|
||||
let markdown = maybe_markdown.unwrap();
|
||||
let markdown = (markdown.label, markdown.highlights);
|
||||
let signature = markdown.signatures[markdown.active_signature].clone();
|
||||
let markdown = (signature.label, signature.highlights);
|
||||
assert_eq!(
|
||||
markdown,
|
||||
(
|
||||
"foo: u8, bar: &str".to_string(),
|
||||
vec![(0..7, current_parameter())]
|
||||
SharedString::new("fn test(foo: u8, bar: &str)"),
|
||||
vec![(8..15, current_parameter())]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_parameter_documentation(cx: &mut TestAppContext) {
|
||||
let signature_help = lsp::SignatureHelp {
|
||||
signatures: vec![lsp::SignatureInformation {
|
||||
label: "fn test(foo: u8, bar: &str)".to_string(),
|
||||
documentation: Some(Documentation::String(
|
||||
"This is a test documentation".to_string(),
|
||||
)),
|
||||
parameters: Some(vec![
|
||||
lsp::ParameterInformation {
|
||||
label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
|
||||
documentation: Some(Documentation::String("The foo parameter".to_string())),
|
||||
},
|
||||
lsp::ParameterInformation {
|
||||
label: lsp::ParameterLabel::Simple("bar: &str".to_string()),
|
||||
documentation: Some(Documentation::String("The bar parameter".to_string())),
|
||||
},
|
||||
]),
|
||||
active_parameter: None,
|
||||
}],
|
||||
active_signature: Some(0),
|
||||
active_parameter: Some(0),
|
||||
};
|
||||
let maybe_signature_help = cx.update(|cx| SignatureHelp::new(signature_help, None, cx));
|
||||
assert!(maybe_signature_help.is_some());
|
||||
|
||||
let signature_help = maybe_signature_help.unwrap();
|
||||
let signature = &signature_help.signatures[signature_help.active_signature];
|
||||
|
||||
// Check that parameter documentation is extracted
|
||||
assert_eq!(signature.parameters.len(), 2);
|
||||
assert_eq!(
|
||||
signature.parameters[0]
|
||||
.documentation
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.update(cx, |documentation, _| documentation.source().to_owned()),
|
||||
"The foo parameter",
|
||||
);
|
||||
assert_eq!(
|
||||
signature.parameters[1]
|
||||
.documentation
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.update(cx, |documentation, _| documentation.source().to_owned()),
|
||||
"The bar parameter",
|
||||
);
|
||||
|
||||
// Check that the active parameter is correct
|
||||
assert_eq!(signature.active_parameter, Some(0));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6504,7 +6504,6 @@ impl LspStore {
|
|||
.await
|
||||
.into_iter()
|
||||
.flat_map(|(_, actions)| actions)
|
||||
.filter(|help| !help.label.is_empty())
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ use crate::{
|
|||
};
|
||||
|
||||
/// A collection of colors that are used to color indent aware lines in the editor.
|
||||
#[derive(Clone, Deserialize, PartialEq)]
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq)]
|
||||
pub struct AccentColors(pub Vec<Hsla>);
|
||||
|
||||
impl Default for AccentColors {
|
||||
|
|
|
@ -535,7 +535,7 @@ pub fn all_theme_colors(cx: &mut App) -> Vec<(Hsla, SharedString)> {
|
|||
.collect()
|
||||
}
|
||||
|
||||
#[derive(Refineable, Clone, PartialEq)]
|
||||
#[derive(Refineable, Clone, Debug, PartialEq)]
|
||||
pub struct ThemeStyles {
|
||||
/// The background appearance of the window.
|
||||
pub window_background_appearance: WindowBackgroundAppearance,
|
||||
|
|
|
@ -20,7 +20,7 @@ pub struct PlayerColor {
|
|||
///
|
||||
/// The rest of the default colors crisscross back and forth on the
|
||||
/// color wheel so that the colors are as distinct as possible.
|
||||
#[derive(Clone, Deserialize, PartialEq)]
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq)]
|
||||
pub struct PlayerColors(pub Vec<PlayerColor>);
|
||||
|
||||
impl Default for PlayerColors {
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
use gpui::{Hsla, hsla};
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct SystemColors {
|
||||
pub transparent: Hsla,
|
||||
pub mac_os_traffic_light_red: Hsla,
|
||||
|
|
|
@ -268,7 +268,7 @@ pub fn refine_theme_family(theme_family_content: ThemeFamilyContent) -> ThemeFam
|
|||
}
|
||||
|
||||
/// A theme is the primary mechanism for defining the appearance of the UI.
|
||||
#[derive(Clone, PartialEq)]
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Theme {
|
||||
/// The unique identifier for the theme.
|
||||
pub id: String,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue