Prepare editor to display multiple LSP hover responses for the same place (#9868)
This commit is contained in:
parent
ce37885f49
commit
80242584e7
3 changed files with 163 additions and 106 deletions
|
@ -4978,11 +4978,16 @@ async fn test_lsp_hover(
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
let hover_info = project_b
|
let hovers = project_b
|
||||||
.update(cx_b, |p, cx| p.hover(&buffer_b, 22, cx))
|
.update(cx_b, |p, cx| p.hover(&buffer_b, 22, cx))
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
hovers.len(),
|
||||||
|
1,
|
||||||
|
"Expected exactly one hover but got: {hovers:?}"
|
||||||
|
);
|
||||||
|
let hover_info = hovers.into_iter().next().unwrap();
|
||||||
|
|
||||||
buffer_b.read_with(cx_b, |buffer, _| {
|
buffer_b.read_with(cx_b, |buffer, _| {
|
||||||
let snapshot = buffer.snapshot();
|
let snapshot = buffer.snapshot();
|
||||||
|
|
|
@ -4,17 +4,18 @@ use crate::{
|
||||||
Anchor, AnchorRangeExt, DisplayPoint, Editor, EditorSettings, EditorSnapshot, EditorStyle,
|
Anchor, AnchorRangeExt, DisplayPoint, Editor, EditorSettings, EditorSnapshot, EditorStyle,
|
||||||
ExcerptId, Hover, RangeToAnchorExt,
|
ExcerptId, Hover, RangeToAnchorExt,
|
||||||
};
|
};
|
||||||
use futures::FutureExt;
|
use futures::{stream::FuturesUnordered, FutureExt};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
div, px, AnyElement, CursorStyle, Hsla, InteractiveElement, IntoElement, Model, MouseButton,
|
div, px, AnyElement, CursorStyle, Hsla, InteractiveElement, IntoElement, MouseButton,
|
||||||
ParentElement, Pixels, SharedString, Size, StatefulInteractiveElement, Styled, Task,
|
ParentElement, Pixels, SharedString, Size, StatefulInteractiveElement, Styled, Task,
|
||||||
ViewContext, WeakView,
|
ViewContext, WeakView,
|
||||||
};
|
};
|
||||||
use language::{markdown, Bias, DiagnosticEntry, Language, LanguageRegistry, ParsedMarkdown};
|
use language::{markdown, Bias, DiagnosticEntry, Language, LanguageRegistry, ParsedMarkdown};
|
||||||
|
|
||||||
use lsp::DiagnosticSeverity;
|
use lsp::DiagnosticSeverity;
|
||||||
use project::{HoverBlock, HoverBlockKind, InlayHintLabelPart, Project};
|
use project::{HoverBlock, HoverBlockKind, InlayHintLabelPart};
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
|
use smol::stream::StreamExt;
|
||||||
use std::{ops::Range, sync::Arc, time::Duration};
|
use std::{ops::Range, sync::Arc, time::Duration};
|
||||||
use ui::{prelude::*, Tooltip};
|
use ui::{prelude::*, Tooltip};
|
||||||
use util::TryFutureExt;
|
use util::TryFutureExt;
|
||||||
|
@ -83,13 +84,20 @@ pub fn hover_at_inlay(editor: &mut Editor, inlay_hover: InlayHover, cx: &mut Vie
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(InfoPopover { symbol_range, .. }) = &editor.hover_state.info_popover {
|
if editor
|
||||||
if let RangeInEditor::Inlay(range) = symbol_range {
|
.hover_state
|
||||||
if range == &inlay_hover.range {
|
.info_popovers
|
||||||
// Hover triggered from same location as last time. Don't show again.
|
.iter()
|
||||||
return;
|
.any(|InfoPopover { symbol_range, .. }| {
|
||||||
|
if let RangeInEditor::Inlay(range) = symbol_range {
|
||||||
|
if range == &inlay_hover.range {
|
||||||
|
// Hover triggered from same location as last time. Don't show again.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
false
|
||||||
|
})
|
||||||
|
{
|
||||||
hide_hover(editor, cx);
|
hide_hover(editor, cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,15 +115,13 @@ pub fn hover_at_inlay(editor: &mut Editor, inlay_hover: InlayHover, cx: &mut Vie
|
||||||
let parsed_content = parse_blocks(&blocks, &language_registry, None).await;
|
let parsed_content = parse_blocks(&blocks, &language_registry, None).await;
|
||||||
|
|
||||||
let hover_popover = InfoPopover {
|
let hover_popover = InfoPopover {
|
||||||
project: project.clone(),
|
|
||||||
symbol_range: RangeInEditor::Inlay(inlay_hover.range.clone()),
|
symbol_range: RangeInEditor::Inlay(inlay_hover.range.clone()),
|
||||||
blocks,
|
|
||||||
parsed_content,
|
parsed_content,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
// TODO: no background highlights happen for inlays currently
|
// TODO: no background highlights happen for inlays currently
|
||||||
this.hover_state.info_popover = Some(hover_popover);
|
this.hover_state.info_popovers = vec![hover_popover];
|
||||||
cx.notify();
|
cx.notify();
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
@ -132,8 +138,9 @@ pub fn hover_at_inlay(editor: &mut Editor, inlay_hover: InlayHover, cx: &mut Vie
|
||||||
/// Triggered by the `Hover` action when the cursor is not over a symbol or when the
|
/// Triggered by the `Hover` action when the cursor is not over a symbol or when the
|
||||||
/// selections changed.
|
/// selections changed.
|
||||||
pub fn hide_hover(editor: &mut Editor, cx: &mut ViewContext<Editor>) -> bool {
|
pub fn hide_hover(editor: &mut Editor, cx: &mut ViewContext<Editor>) -> bool {
|
||||||
let did_hide = editor.hover_state.info_popover.take().is_some()
|
let info_popovers = editor.hover_state.info_popovers.drain(..);
|
||||||
| editor.hover_state.diagnostic_popover.take().is_some();
|
let diagnostics_popover = editor.hover_state.diagnostic_popover.take();
|
||||||
|
let did_hide = info_popovers.count() > 0 || diagnostics_popover.is_some();
|
||||||
|
|
||||||
editor.hover_state.info_task = None;
|
editor.hover_state.info_task = None;
|
||||||
editor.hover_state.triggered_from = None;
|
editor.hover_state.triggered_from = None;
|
||||||
|
@ -190,22 +197,26 @@ fn show_hover(
|
||||||
};
|
};
|
||||||
|
|
||||||
if !ignore_timeout {
|
if !ignore_timeout {
|
||||||
if let Some(InfoPopover { symbol_range, .. }) = &editor.hover_state.info_popover {
|
if editor
|
||||||
if symbol_range
|
.hover_state
|
||||||
.as_text_range()
|
.info_popovers
|
||||||
.map(|range| {
|
.iter()
|
||||||
let hover_range = range.to_offset(&snapshot.buffer_snapshot);
|
.any(|InfoPopover { symbol_range, .. }| {
|
||||||
// LSP returns a hover result for the end index of ranges that should be hovered, so we need to
|
symbol_range
|
||||||
// use an inclusive range here to check if we should dismiss the popover
|
.as_text_range()
|
||||||
(hover_range.start..=hover_range.end).contains(&multibuffer_offset)
|
.map(|range| {
|
||||||
})
|
let hover_range = range.to_offset(&snapshot.buffer_snapshot);
|
||||||
.unwrap_or(false)
|
// LSP returns a hover result for the end index of ranges that should be hovered, so we need to
|
||||||
{
|
// use an inclusive range here to check if we should dismiss the popover
|
||||||
// Hover triggered from same location as last time. Don't show again.
|
(hover_range.start..=hover_range.end).contains(&multibuffer_offset)
|
||||||
return;
|
})
|
||||||
} else {
|
.unwrap_or(false)
|
||||||
hide_hover(editor, cx);
|
})
|
||||||
}
|
{
|
||||||
|
// Hover triggered from same location as last time. Don't show again.
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
hide_hover(editor, cx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -284,10 +295,14 @@ fn show_hover(
|
||||||
});
|
});
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let hover_result = hover_request.await.ok().flatten();
|
let hovers_response = hover_request.await.ok().unwrap_or_default();
|
||||||
|
let language_registry = project.update(&mut cx, |p, _| p.languages().clone())?;
|
||||||
let snapshot = this.update(&mut cx, |this, cx| this.snapshot(cx))?;
|
let snapshot = this.update(&mut cx, |this, cx| this.snapshot(cx))?;
|
||||||
let hover_popover = match hover_result {
|
let mut hover_highlights = Vec::with_capacity(hovers_response.len());
|
||||||
Some(hover_result) if !hover_result.is_empty() => {
|
let mut info_popovers = Vec::with_capacity(hovers_response.len());
|
||||||
|
let mut info_popover_tasks = hovers_response
|
||||||
|
.into_iter()
|
||||||
|
.map(|hover_result| async {
|
||||||
// Create symbol range of anchors for highlighting and filtering of future requests.
|
// Create symbol range of anchors for highlighting and filtering of future requests.
|
||||||
let range = hover_result
|
let range = hover_result
|
||||||
.range
|
.range
|
||||||
|
@ -303,44 +318,42 @@ fn show_hover(
|
||||||
})
|
})
|
||||||
.unwrap_or_else(|| anchor..anchor);
|
.unwrap_or_else(|| anchor..anchor);
|
||||||
|
|
||||||
let language_registry =
|
|
||||||
project.update(&mut cx, |p, _| p.languages().clone())?;
|
|
||||||
let blocks = hover_result.contents;
|
let blocks = hover_result.contents;
|
||||||
let language = hover_result.language;
|
let language = hover_result.language;
|
||||||
let parsed_content = parse_blocks(&blocks, &language_registry, language).await;
|
let parsed_content = parse_blocks(&blocks, &language_registry, language).await;
|
||||||
|
|
||||||
Some(InfoPopover {
|
(
|
||||||
project: project.clone(),
|
range.clone(),
|
||||||
symbol_range: RangeInEditor::Text(range),
|
InfoPopover {
|
||||||
blocks,
|
symbol_range: RangeInEditor::Text(range),
|
||||||
parsed_content,
|
parsed_content,
|
||||||
})
|
},
|
||||||
}
|
)
|
||||||
|
})
|
||||||
|
.collect::<FuturesUnordered<_>>();
|
||||||
|
while let Some((highlight_range, info_popover)) = info_popover_tasks.next().await {
|
||||||
|
hover_highlights.push(highlight_range);
|
||||||
|
info_popovers.push(info_popover);
|
||||||
|
}
|
||||||
|
|
||||||
_ => None,
|
this.update(&mut cx, |editor, cx| {
|
||||||
};
|
if hover_highlights.is_empty() {
|
||||||
|
editor.clear_background_highlights::<HoverState>(cx);
|
||||||
this.update(&mut cx, |this, cx| {
|
} else {
|
||||||
if let Some(symbol_range) = hover_popover
|
|
||||||
.as_ref()
|
|
||||||
.and_then(|hover_popover| hover_popover.symbol_range.as_text_range())
|
|
||||||
{
|
|
||||||
// Highlight the selected symbol using a background highlight
|
// Highlight the selected symbol using a background highlight
|
||||||
this.highlight_background::<HoverState>(
|
editor.highlight_background::<HoverState>(
|
||||||
vec![symbol_range],
|
hover_highlights,
|
||||||
|theme| theme.element_hover, // todo update theme
|
|theme| theme.element_hover, // todo update theme
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
} else {
|
|
||||||
this.clear_background_highlights::<HoverState>(cx);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.hover_state.info_popover = hover_popover;
|
editor.hover_state.info_popovers = info_popovers;
|
||||||
cx.notify();
|
cx.notify();
|
||||||
cx.refresh();
|
cx.refresh();
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
Ok::<_, anyhow::Error>(())
|
anyhow::Ok(())
|
||||||
}
|
}
|
||||||
.log_err()
|
.log_err()
|
||||||
});
|
});
|
||||||
|
@ -422,7 +435,7 @@ async fn parse_blocks(
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct HoverState {
|
pub struct HoverState {
|
||||||
pub info_popover: Option<InfoPopover>,
|
pub info_popovers: Vec<InfoPopover>,
|
||||||
pub diagnostic_popover: Option<DiagnosticPopover>,
|
pub diagnostic_popover: Option<DiagnosticPopover>,
|
||||||
pub triggered_from: Option<Anchor>,
|
pub triggered_from: Option<Anchor>,
|
||||||
pub info_task: Option<Task<Option<()>>>,
|
pub info_task: Option<Task<Option<()>>>,
|
||||||
|
@ -430,7 +443,7 @@ pub struct HoverState {
|
||||||
|
|
||||||
impl HoverState {
|
impl HoverState {
|
||||||
pub fn visible(&self) -> bool {
|
pub fn visible(&self) -> bool {
|
||||||
self.info_popover.is_some() || self.diagnostic_popover.is_some()
|
!self.info_popovers.is_empty() || self.diagnostic_popover.is_some()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render(
|
pub fn render(
|
||||||
|
@ -449,12 +462,20 @@ impl HoverState {
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|diagnostic_popover| &diagnostic_popover.local_diagnostic.range.start)
|
.map(|diagnostic_popover| &diagnostic_popover.local_diagnostic.range.start)
|
||||||
.or_else(|| {
|
.or_else(|| {
|
||||||
self.info_popover
|
self.info_popovers.iter().find_map(|info_popover| {
|
||||||
.as_ref()
|
match &info_popover.symbol_range {
|
||||||
.map(|info_popover| match &info_popover.symbol_range {
|
RangeInEditor::Text(range) => Some(&range.start),
|
||||||
RangeInEditor::Text(range) => &range.start,
|
RangeInEditor::Inlay(_) => None,
|
||||||
RangeInEditor::Inlay(range) => &range.inlay_position,
|
}
|
||||||
})
|
})
|
||||||
|
})
|
||||||
|
.or_else(|| {
|
||||||
|
self.info_popovers.iter().find_map(|info_popover| {
|
||||||
|
match &info_popover.symbol_range {
|
||||||
|
RangeInEditor::Text(_) => None,
|
||||||
|
RangeInEditor::Inlay(range) => Some(&range.inlay_position),
|
||||||
|
}
|
||||||
|
})
|
||||||
})?;
|
})?;
|
||||||
let point = anchor.to_display_point(&snapshot.display_snapshot);
|
let point = anchor.to_display_point(&snapshot.display_snapshot);
|
||||||
|
|
||||||
|
@ -468,8 +489,8 @@ impl HoverState {
|
||||||
if let Some(diagnostic_popover) = self.diagnostic_popover.as_ref() {
|
if let Some(diagnostic_popover) = self.diagnostic_popover.as_ref() {
|
||||||
elements.push(diagnostic_popover.render(style, max_size, cx));
|
elements.push(diagnostic_popover.render(style, max_size, cx));
|
||||||
}
|
}
|
||||||
if let Some(info_popover) = self.info_popover.as_mut() {
|
for info_popover in &mut self.info_popovers {
|
||||||
elements.push(info_popover.render(style, max_size, workspace, cx));
|
elements.push(info_popover.render(style, max_size, workspace.clone(), cx));
|
||||||
}
|
}
|
||||||
|
|
||||||
Some((point, elements))
|
Some((point, elements))
|
||||||
|
@ -478,9 +499,7 @@ impl HoverState {
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct InfoPopover {
|
pub struct InfoPopover {
|
||||||
pub project: Model<Project>,
|
|
||||||
symbol_range: RangeInEditor,
|
symbol_range: RangeInEditor,
|
||||||
pub blocks: Vec<HoverBlock>,
|
|
||||||
parsed_content: ParsedMarkdown,
|
parsed_content: ParsedMarkdown,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -664,12 +683,19 @@ mod tests {
|
||||||
cx.editor(|editor, _| {
|
cx.editor(|editor, _| {
|
||||||
assert!(editor.hover_state.visible());
|
assert!(editor.hover_state.visible());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
editor.hover_state.info_popover.clone().unwrap().blocks,
|
editor.hover_state.info_popovers.len(),
|
||||||
vec![HoverBlock {
|
1,
|
||||||
text: "some basic docs".to_string(),
|
"Expected exactly one hover but got: {:?}",
|
||||||
kind: HoverBlockKind::Markdown,
|
editor.hover_state.info_popovers
|
||||||
},]
|
);
|
||||||
)
|
let rendered = editor
|
||||||
|
.hover_state
|
||||||
|
.info_popovers
|
||||||
|
.first()
|
||||||
|
.cloned()
|
||||||
|
.unwrap()
|
||||||
|
.parsed_content;
|
||||||
|
assert_eq!(rendered.text, "some basic docs".to_string())
|
||||||
});
|
});
|
||||||
|
|
||||||
// Mouse moved with no hover response dismisses
|
// Mouse moved with no hover response dismisses
|
||||||
|
@ -724,12 +750,19 @@ mod tests {
|
||||||
cx.condition(|editor, _| editor.hover_state.visible()).await;
|
cx.condition(|editor, _| editor.hover_state.visible()).await;
|
||||||
cx.editor(|editor, _| {
|
cx.editor(|editor, _| {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
editor.hover_state.info_popover.clone().unwrap().blocks,
|
editor.hover_state.info_popovers.len(),
|
||||||
vec![HoverBlock {
|
1,
|
||||||
text: "some other basic docs".to_string(),
|
"Expected exactly one hover but got: {:?}",
|
||||||
kind: HoverBlockKind::Markdown,
|
editor.hover_state.info_popovers
|
||||||
}]
|
);
|
||||||
)
|
let rendered = editor
|
||||||
|
.hover_state
|
||||||
|
.info_popovers
|
||||||
|
.first()
|
||||||
|
.cloned()
|
||||||
|
.unwrap()
|
||||||
|
.parsed_content;
|
||||||
|
assert_eq!(rendered.text, "some other basic docs".to_string())
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -773,11 +806,21 @@ mod tests {
|
||||||
cx.condition(|editor, _| editor.hover_state.visible()).await;
|
cx.condition(|editor, _| editor.hover_state.visible()).await;
|
||||||
cx.editor(|editor, _| {
|
cx.editor(|editor, _| {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
editor.hover_state.info_popover.clone().unwrap().blocks,
|
editor.hover_state.info_popovers.len(),
|
||||||
vec![HoverBlock {
|
1,
|
||||||
text: "regular text for hover to show".to_string(),
|
"Expected exactly one hover but got: {:?}",
|
||||||
kind: HoverBlockKind::Markdown,
|
editor.hover_state.info_popovers
|
||||||
}],
|
);
|
||||||
|
let rendered = editor
|
||||||
|
.hover_state
|
||||||
|
.info_popovers
|
||||||
|
.first()
|
||||||
|
.cloned()
|
||||||
|
.unwrap()
|
||||||
|
.parsed_content;
|
||||||
|
assert_eq!(
|
||||||
|
rendered.text,
|
||||||
|
"regular text for hover to show".to_string(),
|
||||||
"No empty string hovers should be shown"
|
"No empty string hovers should be shown"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -824,20 +867,21 @@ mod tests {
|
||||||
.next()
|
.next()
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let languages = cx.language_registry().clone();
|
|
||||||
|
|
||||||
cx.condition(|editor, _| editor.hover_state.visible()).await;
|
cx.condition(|editor, _| editor.hover_state.visible()).await;
|
||||||
cx.editor(|editor, _| {
|
cx.editor(|editor, _| {
|
||||||
let blocks = editor.hover_state.info_popover.clone().unwrap().blocks;
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
blocks,
|
editor.hover_state.info_popovers.len(),
|
||||||
vec![HoverBlock {
|
1,
|
||||||
text: markdown_string,
|
"Expected exactly one hover but got: {:?}",
|
||||||
kind: HoverBlockKind::Markdown,
|
editor.hover_state.info_popovers
|
||||||
}],
|
|
||||||
);
|
);
|
||||||
|
let rendered = editor
|
||||||
let rendered = smol::block_on(parse_blocks(&blocks, &languages, None));
|
.hover_state
|
||||||
|
.info_popovers
|
||||||
|
.first()
|
||||||
|
.cloned()
|
||||||
|
.unwrap()
|
||||||
|
.parsed_content;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
rendered.text,
|
rendered.text,
|
||||||
code_str.trim(),
|
code_str.trim(),
|
||||||
|
@ -889,7 +933,9 @@ mod tests {
|
||||||
cx.background_executor.run_until_parked();
|
cx.background_executor.run_until_parked();
|
||||||
|
|
||||||
cx.editor(|Editor { hover_state, .. }, _| {
|
cx.editor(|Editor { hover_state, .. }, _| {
|
||||||
assert!(hover_state.diagnostic_popover.is_some() && hover_state.info_popover.is_none())
|
assert!(
|
||||||
|
hover_state.diagnostic_popover.is_some() && hover_state.info_popovers.is_empty()
|
||||||
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
// Info Popover shows after request responded to
|
// Info Popover shows after request responded to
|
||||||
|
@ -1289,8 +1335,10 @@ mod tests {
|
||||||
cx.background_executor.run_until_parked();
|
cx.background_executor.run_until_parked();
|
||||||
cx.update_editor(|editor, cx| {
|
cx.update_editor(|editor, cx| {
|
||||||
let hover_state = &editor.hover_state;
|
let hover_state = &editor.hover_state;
|
||||||
assert!(hover_state.diagnostic_popover.is_none() && hover_state.info_popover.is_some());
|
assert!(
|
||||||
let popover = hover_state.info_popover.as_ref().unwrap();
|
hover_state.diagnostic_popover.is_none() && hover_state.info_popovers.len() == 1
|
||||||
|
);
|
||||||
|
let popover = hover_state.info_popovers.first().cloned().unwrap();
|
||||||
let buffer_snapshot = editor.buffer().update(cx, |buffer, cx| buffer.snapshot(cx));
|
let buffer_snapshot = editor.buffer().update(cx, |buffer, cx| buffer.snapshot(cx));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
popover.symbol_range,
|
popover.symbol_range,
|
||||||
|
@ -1342,8 +1390,10 @@ mod tests {
|
||||||
cx.background_executor.run_until_parked();
|
cx.background_executor.run_until_parked();
|
||||||
cx.update_editor(|editor, cx| {
|
cx.update_editor(|editor, cx| {
|
||||||
let hover_state = &editor.hover_state;
|
let hover_state = &editor.hover_state;
|
||||||
assert!(hover_state.diagnostic_popover.is_none() && hover_state.info_popover.is_some());
|
assert!(
|
||||||
let popover = hover_state.info_popover.as_ref().unwrap();
|
hover_state.diagnostic_popover.is_none() && hover_state.info_popovers.len() == 1
|
||||||
|
);
|
||||||
|
let popover = hover_state.info_popovers.first().cloned().unwrap();
|
||||||
let buffer_snapshot = editor.buffer().update(cx, |buffer, cx| buffer.snapshot(cx));
|
let buffer_snapshot = editor.buffer().update(cx, |buffer, cx| buffer.snapshot(cx));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
popover.symbol_range,
|
popover.symbol_range,
|
||||||
|
|
|
@ -5189,20 +5189,22 @@ impl Project {
|
||||||
buffer: &Model<Buffer>,
|
buffer: &Model<Buffer>,
|
||||||
position: PointUtf16,
|
position: PointUtf16,
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) -> Task<Result<Option<Hover>>> {
|
) -> Task<Result<Vec<Hover>>> {
|
||||||
self.request_lsp(
|
let request_task = self.request_lsp(
|
||||||
buffer.clone(),
|
buffer.clone(),
|
||||||
LanguageServerToQuery::Primary,
|
LanguageServerToQuery::Primary,
|
||||||
GetHover { position },
|
GetHover { position },
|
||||||
cx,
|
cx,
|
||||||
)
|
);
|
||||||
|
cx.spawn(|_, _| async move { request_task.await.map(|hover| hover.into_iter().collect()) })
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn hover<T: ToPointUtf16>(
|
pub fn hover<T: ToPointUtf16>(
|
||||||
&self,
|
&self,
|
||||||
buffer: &Model<Buffer>,
|
buffer: &Model<Buffer>,
|
||||||
position: T,
|
position: T,
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) -> Task<Result<Option<Hover>>> {
|
) -> Task<Result<Vec<Hover>>> {
|
||||||
let position = position.to_point_utf16(buffer.read(cx));
|
let position = position.to_point_utf16(buffer.read(cx));
|
||||||
self.hover_impl(buffer, position, cx)
|
self.hover_impl(buffer, position, cx)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue