Skip to main content

gpui_base/input/editor/lsp/
hover.rs

1use anyhow::Result;
2use gpui::{App, Context, MouseMoveEvent, Task, Window};
3use instant::Duration;
4use ropey::Rope;
5
6use crate::input::{EditorMode, HoverPopoverState, InputBaseState, RopeExt};
7
8/// Hover provider
9///
10/// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_hover
11pub trait HoverProvider {
12    /// textDocument/hover
13    ///
14    /// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_hover
15    fn hover(
16        &self,
17        _text: &Rope,
18        _offset: usize,
19        _window: &mut Window,
20        _cx: &mut App,
21    ) -> Task<Result<Option<lsp_types::Hover>>>;
22}
23
24impl InputBaseState<EditorMode> {
25    /// Handle hover trigger LSP request.
26    pub(super) fn handle_hover_popover(
27        &mut self,
28        offset: usize,
29        window: &mut Window,
30        cx: &mut Context<InputBaseState<EditorMode>>,
31    ) {
32        if self.selecting {
33            return;
34        }
35
36        let Some(provider) = self.extras.lsp.hover_provider.clone() else {
37            return;
38        };
39
40        if let Some(hover_popover) = self.extras.hover_popover.as_ref() {
41            if hover_popover.symbol_range.contains(&offset) {
42                return;
43            }
44        }
45
46        // Currently not implemented.
47        let task = provider.hover(&self.text, offset, window, cx);
48        let mut symbol_range = self.text.word_range(offset).unwrap_or(offset..offset);
49        let editor = cx.entity();
50        let should_delay = self.extras.hover_popover.is_none();
51        self.extras.lsp._hover_task = cx.spawn_in(window, async move |_, cx| {
52            if should_delay {
53                cx.background_executor()
54                    .timer(Duration::from_millis(150))
55                    .await;
56            }
57
58            let result = task.await?;
59
60            _ = editor.update(cx, |editor, cx| {
61                match result {
62                    Some(hover) => {
63                        if let Some(range) = hover.range {
64                            let start = editor.text.position_to_offset(&range.start);
65                            let end = editor.text.position_to_offset(&range.end);
66                            symbol_range = start..end;
67                        }
68                        editor.extras.hover_popover = Some(HoverPopoverState {
69                            symbol_range,
70                            hover,
71                        });
72                    }
73                    None => {
74                        editor.extras.hover_popover = None;
75                    }
76                }
77                cx.notify();
78            });
79
80            Ok(())
81        });
82    }
83
84    pub(crate) fn handle_mouse_move(
85        &mut self,
86        offset: usize,
87        event: &MouseMoveEvent,
88        window: &mut Window,
89        cx: &mut Context<Self>,
90    ) {
91        if event.modifiers.secondary() {
92            self.handle_hover_definition(offset, window, cx);
93        } else {
94            self.extras.hover_definition.clear();
95            self.handle_hover_popover(offset, window, cx);
96        }
97        cx.notify();
98    }
99
100    pub fn clear_hover_state(&mut self, cx: &mut Context<Self>) {
101        let changed =
102            !self.extras.hover_definition.is_empty() || self.extras.hover_popover.is_some();
103        self.extras.hover_definition.clear();
104        self.extras.hover_popover = None;
105        self.extras.lsp._hover_task = Task::ready(Ok(()));
106        if changed {
107            cx.notify();
108        }
109    }
110}