Always delay hover display

This commit is contained in:
Keith Simmons 2022-06-09 18:08:23 -07:00
parent e21d1b1acb
commit 02f0fee6ae
3 changed files with 213 additions and 209 deletions

View File

@ -6147,12 +6147,8 @@ pub fn styled_runs_for_code_label<'a>(
#[cfg(test)]
mod tests {
use crate::{
hover_popover::{hover, hover_at, HoverAt, HOVER_DELAY_MILLIS, HOVER_GRACE_MILLIS},
test::{
assert_text_with_selections, build_editor, select_ranges, EditorLspTestContext,
EditorTestContext,
},
use crate::test::{
assert_text_with_selections, build_editor, select_ranges, EditorTestContext,
};
use super::*;
@ -6164,7 +6160,7 @@ mod tests {
use indoc::indoc;
use language::{FakeLspAdapter, LanguageConfig};
use lsp::FakeLanguageServer;
use project::{FakeFs, HoverBlock};
use project::FakeFs;
use settings::LanguageOverride;
use std::{cell::RefCell, rc::Rc, time::Instant};
use text::Point;
@ -9395,193 +9391,6 @@ mod tests {
}
}
#[gpui::test]
async fn test_hover_popover(cx: &mut gpui::TestAppContext) {
let mut cx = EditorLspTestContext::new_rust(
lsp::ServerCapabilities {
hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
..Default::default()
},
cx,
)
.await;
// Basic hover delays and then pops without moving the mouse
cx.set_state(indoc! {"
fn |test()
println!();"});
let hover_point = cx.display_point(indoc! {"
fn test()
print|ln!();"});
cx.update_editor(|editor, cx| {
hover_at(
editor,
&HoverAt {
point: Some(hover_point),
},
cx,
)
});
assert!(!cx.editor(|editor, _| editor.hover_state.visible()));
// After delay, hover should be visible.
let symbol_range = cx.lsp_range(indoc! {"
fn test()
[println!]();"});
let mut requests =
cx.lsp
.handle_request::<lsp::request::HoverRequest, _, _>(move |_, _| async move {
Ok(Some(lsp::Hover {
contents: lsp::HoverContents::Markup(lsp::MarkupContent {
kind: lsp::MarkupKind::Markdown,
value: indoc! {"
# Some basic docs
Some test documentation"}
.to_string(),
}),
range: Some(symbol_range),
}))
});
cx.foreground()
.advance_clock(Duration::from_millis(HOVER_DELAY_MILLIS + 100));
requests.next().await;
cx.editor(|editor, _| {
assert!(editor.hover_state.visible());
assert_eq!(
editor.hover_state.popover.clone().unwrap().contents,
vec![
HoverBlock {
text: "Some basic docs".to_string(),
language: None
},
HoverBlock {
text: "Some test documentation".to_string(),
language: None
}
]
)
});
// Mouse moved with no hover response dismisses
let hover_point = cx.display_point(indoc! {"
fn te|st()
println!();"});
cx.update_editor(|editor, cx| {
hover_at(
editor,
&HoverAt {
point: Some(hover_point),
},
cx,
)
});
cx.lsp
.handle_request::<lsp::request::HoverRequest, _, _>(|_, _| async move { Ok(None) })
.next()
.await;
cx.foreground().run_until_parked();
cx.editor(|editor, _| {
assert!(!editor.hover_state.visible());
});
cx.foreground()
.advance_clock(Duration::from_millis(HOVER_GRACE_MILLIS + 100));
// Hover with keyboard has no delay
cx.set_state(indoc! {"
f|n test()
println!();"});
cx.update_editor(|editor, cx| hover(editor, &hover_popover::Hover, cx));
let symbol_range = cx.lsp_range(indoc! {"
[fn] test()
println!();"});
cx.lsp
.handle_request::<lsp::request::HoverRequest, _, _>(move |_, _| async move {
Ok(Some(lsp::Hover {
contents: lsp::HoverContents::Markup(lsp::MarkupContent {
kind: lsp::MarkupKind::Markdown,
value: indoc! {"
# Some other basic docs
Some other test documentation"}
.to_string(),
}),
range: Some(symbol_range),
}))
})
.next()
.await;
cx.foreground().run_until_parked();
cx.editor(|editor, _| {
assert!(editor.hover_state.visible());
assert_eq!(
editor.hover_state.popover.clone().unwrap().contents,
vec![
HoverBlock {
text: "Some other basic docs".to_string(),
language: None
},
HoverBlock {
text: "Some other test documentation".to_string(),
language: None
}
]
)
});
// Open hover popover disables delay
let hover_point = cx.display_point(indoc! {"
fn test()
print|ln!();"});
cx.update_editor(|editor, cx| {
hover_at(
editor,
&HoverAt {
point: Some(hover_point),
},
cx,
)
});
let symbol_range = cx.lsp_range(indoc! {"
fn test()
[println!]();"});
cx.lsp
.handle_request::<lsp::request::HoverRequest, _, _>(move |_, _| async move {
Ok(Some(lsp::Hover {
contents: lsp::HoverContents::Markup(lsp::MarkupContent {
kind: lsp::MarkupKind::Markdown,
value: indoc! {"
# Some third basic docs
Some third test documentation"}
.to_string(),
}),
range: Some(symbol_range),
}))
})
.next()
.await;
cx.foreground().run_until_parked();
// No delay as the popover is already visible
cx.editor(|editor, _| {
assert!(editor.hover_state.visible());
assert_eq!(
editor.hover_state.popover.clone().unwrap().contents,
vec![
HoverBlock {
text: "Some third basic docs".to_string(),
language: None
},
HoverBlock {
text: "Some third test documentation".to_string(),
language: None
}
]
)
});
}
#[gpui::test]
async fn test_toggle_comment(cx: &mut gpui::TestAppContext) {
cx.update(|cx| cx.set_global(Settings::test(cx)));

View File

@ -19,9 +19,8 @@ use crate::{
EditorStyle,
};
pub const HOVER_DELAY_MILLIS: u64 = 500;
pub const HOVER_REQUEST_DELAY_MILLIS: u64 = 250;
pub const HOVER_GRACE_MILLIS: u64 = 250;
pub const HOVER_DELAY_MILLIS: u64 = 350;
pub const HOVER_REQUEST_DELAY_MILLIS: u64 = 200;
#[derive(Clone, PartialEq)]
pub struct HoverAt {
@ -116,17 +115,7 @@ fn show_hover(
return;
};
// We should only delay if the hover popover isn't visible, it wasn't recently hidden, and
// the hover wasn't triggered from the keyboard
let should_delay = editor.hover_state.popover.is_none() // Hover not visible currently
&& editor
.hover_state
.hidden_at
.map(|hidden| hidden.elapsed().as_millis() > HOVER_GRACE_MILLIS as u128)
.unwrap_or(true) // Hover wasn't recently visible
&& !ignore_timeout; // Hover was not triggered from keyboard
if should_delay {
if !ignore_timeout {
if let Some(range) = &editor.hover_state.symbol_range {
if range
.to_offset(&snapshot.buffer_snapshot)
@ -134,6 +123,8 @@ fn show_hover(
{
// Hover triggered from same location as last time. Don't show again.
return;
} else {
hide_hover(editor, cx);
}
}
}
@ -156,7 +147,7 @@ fn show_hover(
let task = cx.spawn_weak(|this, mut cx| {
async move {
// If we need to delay, delay a set amount initially before making the lsp request
let delay = if should_delay {
let delay = if !ignore_timeout {
// Construct delay task to wait for later
let total_delay = Some(
cx.background()
@ -327,3 +318,149 @@ impl HoverPopover {
(display_point, element)
}
}
#[cfg(test)]
mod tests {
use futures::StreamExt;
use indoc::indoc;
use project::HoverBlock;
use crate::test::EditorLspTestContext;
use super::*;
#[gpui::test]
async fn test_hover_popover(cx: &mut gpui::TestAppContext) {
let mut cx = EditorLspTestContext::new_rust(
lsp::ServerCapabilities {
hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
..Default::default()
},
cx,
)
.await;
// Basic hover delays and then pops without moving the mouse
cx.set_state(indoc! {"
fn |test()
println!();"});
let hover_point = cx.display_point(indoc! {"
fn test()
print|ln!();"});
cx.update_editor(|editor, cx| {
hover_at(
editor,
&HoverAt {
point: Some(hover_point),
},
cx,
)
});
assert!(!cx.editor(|editor, _| editor.hover_state.visible()));
// After delay, hover should be visible.
let symbol_range = cx.lsp_range(indoc! {"
fn test()
[println!]();"});
let mut requests =
cx.lsp
.handle_request::<lsp::request::HoverRequest, _, _>(move |_, _| async move {
Ok(Some(lsp::Hover {
contents: lsp::HoverContents::Markup(lsp::MarkupContent {
kind: lsp::MarkupKind::Markdown,
value: indoc! {"
# Some basic docs
Some test documentation"}
.to_string(),
}),
range: Some(symbol_range),
}))
});
cx.foreground()
.advance_clock(Duration::from_millis(HOVER_DELAY_MILLIS + 100));
requests.next().await;
cx.editor(|editor, _| {
assert!(editor.hover_state.visible());
assert_eq!(
editor.hover_state.popover.clone().unwrap().contents,
vec![
HoverBlock {
text: "Some basic docs".to_string(),
language: None
},
HoverBlock {
text: "Some test documentation".to_string(),
language: None
}
]
)
});
// Mouse moved with no hover response dismisses
let hover_point = cx.display_point(indoc! {"
fn te|st()
println!();"});
cx.update_editor(|editor, cx| {
hover_at(
editor,
&HoverAt {
point: Some(hover_point),
},
cx,
)
});
let mut request = cx
.lsp
.handle_request::<lsp::request::HoverRequest, _, _>(|_, _| async move { Ok(None) });
cx.foreground()
.advance_clock(Duration::from_millis(HOVER_DELAY_MILLIS + 100));
request.next().await;
cx.editor(|editor, _| {
assert!(!editor.hover_state.visible());
});
// Hover with keyboard has no delay
cx.set_state(indoc! {"
f|n test()
println!();"});
cx.update_editor(|editor, cx| hover(editor, &Hover, cx));
let symbol_range = cx.lsp_range(indoc! {"
[fn] test()
println!();"});
cx.lsp
.handle_request::<lsp::request::HoverRequest, _, _>(move |_, _| async move {
Ok(Some(lsp::Hover {
contents: lsp::HoverContents::Markup(lsp::MarkupContent {
kind: lsp::MarkupKind::Markdown,
value: indoc! {"
# Some other basic docs
Some other test documentation"}
.to_string(),
}),
range: Some(symbol_range),
}))
})
.next()
.await;
cx.foreground().run_until_parked();
cx.editor(|editor, _| {
assert!(editor.hover_state.visible());
assert_eq!(
editor.hover_state.popover.clone().unwrap().contents,
vec![
HoverBlock {
text: "Some other basic docs".to_string(),
language: None
},
HoverBlock {
text: "Some other test documentation".to_string(),
language: None
}
]
)
});
}
}

View File

@ -0,0 +1,58 @@
use std::{
ops::Range,
time::{Duration, Instant},
};
use gpui::{
actions,
elements::{Flex, MouseEventHandler, Padding, Text},
impl_internal_actions,
platform::CursorStyle,
Axis, Element, ElementBox, ModelHandle, MutableAppContext, RenderContext, Task, ViewContext,
};
use language::Bias;
use project::{HoverBlock, Project};
use util::TryFutureExt;
use crate::{
display_map::ToDisplayPoint, Anchor, AnchorRangeExt, DisplayPoint, Editor, EditorSnapshot,
EditorStyle,
};
#[derive(Clone, PartialEq)]
pub struct FetchDefinition {
pub point: Option<DisplayPoint>,
}
#[derive(Clone, PartialEq)]
pub struct GoToFetchedDefinition {
pub point: Option<DisplayPoint>,
}
impl_internal_actions!(edtior, [FetchDefinition, GoToFetchedDefinition]);
pub fn init(cx: &mut MutableAppContext) {
cx.add_action(fetch_definition);
cx.add_action(go_to_fetched_definition);
}
pub fn fetch_definition(
editor: &mut Editor,
FetchDefinition { point }: &FetchDefinition,
cx: &mut ViewContext<Editor>,
) {
}
pub fn go_to_fetched_definition(
editor: &mut Editor,
GoToFetchedDefinition { point }: &GoToFetchedDefinition,
cx: &mut ViewContext<Editor>,
) {
}
#[derive(Default)]
pub struct LinkGoToDefinitionState {
pub triggered_from
pub symbol_range: Option<Range<Anchor>>,
pub task: Option<Task<Option<()>>>,
}