Add hover action and style context menu

Isaac Clayton created

Change summary

crates/editor/src/editor.rs  | 39 ++++++++++++++++++++++++-------------
crates/editor/src/element.rs | 29 ++++++++++++++++++++++-----
2 files changed, 48 insertions(+), 20 deletions(-)

Detailed changes

crates/editor/src/editor.rs 🔗

@@ -80,6 +80,15 @@ pub struct Scroll(pub Vector2F);
 #[derive(Clone, PartialEq)]
 pub struct Select(pub SelectPhase);
 
+#[derive(Clone)]
+pub struct ShowHover(DisplayPoint);
+
+#[derive(Clone)]
+pub struct Hover {
+    point: DisplayPoint,
+    overshoot: DisplayPoint,
+}
+
 #[derive(Clone, Deserialize, PartialEq)]
 pub struct Input(pub String);
 
@@ -191,7 +200,6 @@ actions!(
         UnfoldLines,
         FoldSelectedRanges,
         ShowCompletions,
-        ShowHover,
         OpenExcerpts,
         RestartLanguageServer,
     ]
@@ -210,7 +218,7 @@ impl_actions!(
     ]
 );
 
-impl_internal_actions!(editor, [Scroll, Select, GoToDefinitionAt]);
+impl_internal_actions!(editor, [Scroll, Select, Hover, ShowHover, GoToDefinitionAt]);
 
 enum DocumentHighlightRead {}
 enum DocumentHighlightWrite {}
@@ -297,6 +305,8 @@ pub fn init(cx: &mut MutableAppContext) {
     cx.add_action(Editor::fold_selected_ranges);
     cx.add_action(Editor::show_completions);
     cx.add_action(Editor::toggle_code_actions);
+    cx.add_action(Editor::hover);
+    cx.add_action(Editor::show_hover);
     cx.add_action(Editor::open_excerpts);
     cx.add_action(Editor::restart_language_server);
     cx.add_async_action(Editor::confirm_completion);
@@ -855,16 +865,19 @@ impl CodeActionsMenu {
 
 #[derive(Clone)]
 struct HoverPopover {
+    pub point: DisplayPoint,
     pub text: String,
     pub runs: Vec<(Range<usize>, HighlightStyle)>,
 }
 
 impl HoverPopover {
     fn render(&self, style: EditorStyle) -> ElementBox {
+        let container_style = style.autocomplete.container;
         Text::new(self.text.clone(), style.text.clone())
             .with_soft_wrap(false)
             .with_highlights(self.runs.clone())
             .contained()
+            .with_style(container_style)
             .boxed()
     }
 }
@@ -1026,6 +1039,7 @@ impl Editor {
             next_completion_id: 0,
             available_code_actions: Default::default(),
             code_actions_task: Default::default(),
+            hover_task: Default::default(),
             document_highlights_task: Default::default(),
             pending_rename: Default::default(),
             searchable: true,
@@ -2408,7 +2422,13 @@ impl Editor {
         }))
     }
 
-    fn show_hover(&mut self, _: &ShowHover, cx: &mut ViewContext<Self>) {
+    fn hover(&mut self, action: &Hover, cx: &mut ViewContext<Self>) {
+        if action.overshoot.is_zero() {
+            self.show_hover(&ShowHover(action.point), cx);
+        }
+    }
+
+    fn show_hover(&mut self, action: &ShowHover, cx: &mut ViewContext<Self>) {
         if self.pending_rename.is_some() {
             return;
         }
@@ -2419,18 +2439,9 @@ impl Editor {
             return;
         };
 
-        let position = self.selections.newest_anchor().head();
-        let (buffer, buffer_position) = if let Some(output) = self
-            .buffer
-            .read(cx)
-            .text_anchor_for_position(position.clone(), cx)
-        {
-            output
-        } else {
-            return;
-        };
-
         let hover = HoverPopover {
+            // TODO: beginning of symbol based on range
+            point: action.0,
             text: "Test hover information".to_string(),
             runs: Vec::new(),
         };

crates/editor/src/element.rs 🔗

@@ -5,7 +5,7 @@ use super::{
 };
 use crate::{
     display_map::{DisplaySnapshot, TransformBlock},
-    EditorStyle, GoToDefinition,
+    EditorStyle, GoToDefinition, Hover,
 };
 use clock::ReplicaId;
 use collections::{BTreeMap, HashMap};
@@ -126,7 +126,7 @@ impl EditorElement {
         } else if shift && alt {
             cx.dispatch_action(Select(SelectPhase::BeginColumnar {
                 position,
-                overshoot,
+                overshoot: overshoot.column(),
             }));
         } else if shift {
             cx.dispatch_action(Select(SelectPhase::Extend {
@@ -195,7 +195,7 @@ impl EditorElement {
 
             cx.dispatch_action(Select(SelectPhase::Update {
                 position,
-                overshoot,
+                overshoot: overshoot.column(),
                 scroll_position: (snapshot.scroll_position() + scroll_delta)
                     .clamp(Vector2F::zero(), layout.scroll_max),
             }));
@@ -1251,6 +1251,16 @@ impl Element for EditorElement {
                 precise,
             } => self.scroll(*position, *delta, *precise, layout, paint, cx),
             Event::KeyDown { input, .. } => self.key_down(input.as_deref(), cx),
+            Event::MouseMoved { position, .. } => {
+                if paint.text_bounds.contains_point(*position) {
+                    let (point, overshoot) =
+                        paint.point_for_position(&self.snapshot(cx), layout, *position);
+                    cx.dispatch_action(Hover { point, overshoot });
+                    true
+                } else {
+                    false
+                }
+            }
             _ => false,
         }
     }
@@ -1329,16 +1339,20 @@ pub struct PaintState {
 }
 
 impl PaintState {
+    /// Returns two display points. The first is the nearest valid
+    /// position in the current buffer and the second is the distance to the
+    /// nearest valid position if there was overshoot.
     fn point_for_position(
         &self,
         snapshot: &EditorSnapshot,
         layout: &LayoutState,
         position: Vector2F,
-    ) -> (DisplayPoint, u32) {
+    ) -> (DisplayPoint, DisplayPoint) {
         let scroll_position = snapshot.scroll_position();
         let position = position - self.text_bounds.origin();
         let y = position.y().max(0.0).min(layout.size.y());
         let row = ((y / layout.line_height) + scroll_position.y()) as u32;
+        let row_overshoot = row.saturating_sub(snapshot.max_point().row());
         let row = cmp::min(row, snapshot.max_point().row());
         let line = &layout.line_layouts[(row - scroll_position.y() as u32) as usize];
         let x = position.x() + (scroll_position.x() * layout.em_width);
@@ -1350,9 +1364,12 @@ impl PaintState {
         } else {
             0
         };
-        let overshoot = (0f32.max(x - line.width()) / layout.em_advance) as u32;
+        let column_overshoot = (0f32.max(x - line.width()) / layout.em_advance) as u32;
 
-        (DisplayPoint::new(row, column), overshoot)
+        (
+            DisplayPoint::new(row, column),
+            DisplayPoint::new(row_overshoot, column_overshoot),
+        )
     }
 }