mirror of
https://github.com/zed-industries/zed.git
synced 2025-02-05 18:25:57 +00:00
Merge pull request #1166 from zed-industries/hover-fixes
Always delay hover display
This commit is contained in:
commit
8e440bf7ca
3 changed files with 213 additions and 209 deletions
|
@ -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)));
|
||||
|
|
|
@ -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
|
||||
}
|
||||
]
|
||||
)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
58
crates/editor/src/link_go_to_definition.rs
Normal file
58
crates/editor/src/link_go_to_definition.rs
Normal 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<()>>>,
|
||||
}
|
Loading…
Reference in a new issue