From 560dff732945473f4cbdc40252a9db576da5cdc1 Mon Sep 17 00:00:00 2001 From: Keith Simmons Date: Mon, 30 May 2022 21:09:19 -0700 Subject: [PATCH] Pull hover popover out of context menu --- crates/editor/src/editor.rs | 50 ++++++++++++++----------------- crates/editor/src/element.rs | 40 +++++++++++++++++++++++++ crates/project/src/lsp_command.rs | 37 +++++++++++++---------- crates/project/src/project.rs | 8 ++++- crates/theme/src/theme.rs | 3 +- styles/src/styleTree/editor.ts | 2 ++ 6 files changed, 95 insertions(+), 45 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 0220c89cd0..c5933388f5 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -424,7 +424,7 @@ pub struct Editor { next_completion_id: CompletionId, available_code_actions: Option<(ModelHandle, Arc<[CodeAction]>)>, code_actions_task: Option>, - hover_task: Option>, + hover_task: Option>>, document_highlights_task: Option>, pending_rename: Option, searchable: bool, @@ -432,6 +432,7 @@ pub struct Editor { keymap_context_layers: BTreeMap, input_enabled: bool, leader_replica_id: Option, + hover_popover: Option, } pub struct EditorSnapshot { @@ -571,7 +572,6 @@ struct InvalidationStack(Vec); enum ContextMenu { Completions(CompletionsMenu), CodeActions(CodeActionsMenu), - Hover(HoverPopover), } impl ContextMenu { @@ -580,7 +580,6 @@ impl ContextMenu { match self { ContextMenu::Completions(menu) => menu.select_prev(cx), ContextMenu::CodeActions(menu) => menu.select_prev(cx), - _ => {} } true } else { @@ -593,7 +592,6 @@ impl ContextMenu { match self { ContextMenu::Completions(menu) => menu.select_next(cx), ContextMenu::CodeActions(menu) => menu.select_next(cx), - _ => {} } true } else { @@ -605,7 +603,6 @@ impl ContextMenu { match self { ContextMenu::Completions(menu) => menu.visible(), ContextMenu::CodeActions(menu) => menu.visible(), - ContextMenu::Hover(_) => true, } } @@ -871,14 +868,16 @@ struct HoverPopover { } 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() + fn render(&self, style: EditorStyle) -> (DisplayPoint, ElementBox) { + ( + self.point, + Text::new(self.text.clone(), style.text.clone()) + .with_soft_wrap(false) + .with_highlights(self.runs.clone()) + .contained() + .with_style(style.hover_popover) + .boxed(), + ) } } @@ -1048,6 +1047,7 @@ impl Editor { keymap_context_layers: Default::default(), input_enabled: true, leader_replica_id: None, + hover_popover: None, }; this.end_selection(cx); @@ -1433,6 +1433,8 @@ impl Editor { } } + self.hover_popover.take(); + if old_cursor_position.to_display_point(&display_map).row() != new_cursor_position.to_display_point(&display_map).row() { @@ -2456,7 +2458,6 @@ impl Editor { let point = action.0.clone(); - let id = post_inc(&mut self.next_completion_id); let task = cx.spawn_weak(|this, mut cx| { async move { // TODO: what to show while language server is loading? @@ -2475,7 +2476,7 @@ impl Editor { }, }; - let mut hover_popover = HoverPopover { + let hover_popover = HoverPopover { // TODO: fix tooltip to beginning of symbol based on range point, text, @@ -2484,15 +2485,8 @@ impl Editor { if let Some(this) = this.upgrade(&cx) { this.update(&mut cx, |this, cx| { - if !matches!( - this.context_menu.as_ref(), - None | Some(ContextMenu::Hover(_)) - ) { - return; - } - if this.focused { - this.show_context_menu(ContextMenu::Hover(hover_popover), cx); + this.hover_popover = Some(hover_popover); } cx.notify(); @@ -2502,7 +2496,8 @@ impl Editor { } .log_err() }); - self.completion_tasks.push((id, task)); + + self.hover_task = Some(task); } async fn open_project_transaction( @@ -2751,6 +2746,10 @@ impl Editor { .map(|menu| menu.render(cursor_position, style)) } + pub fn render_hover_popover(&self, style: EditorStyle) -> Option<(DisplayPoint, ElementBox)> { + self.hover_popover.as_ref().map(|hover| hover.render(style)) + } + fn show_context_menu(&mut self, menu: ContextMenu, cx: &mut ViewContext) { if !matches!(menu, ContextMenu::Completions(_)) { self.completion_tasks.clear(); @@ -5785,9 +5784,6 @@ impl View for Editor { Some(ContextMenu::CodeActions(_)) => { context.set.insert("showing_code_actions".into()); } - Some(ContextMenu::Hover(_)) => { - context.set.insert("showing_hover".into()); - } None => {} } diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 73b9b28e82..9095dbbb7b 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -508,6 +508,28 @@ impl EditorElement { cx.scene.pop_stacking_context(); } + if let Some((position, hover_popover)) = layout.hover.as_mut() { + cx.scene.push_stacking_context(None); + + let cursor_row_layout = &layout.line_layouts[(position.row() - start_row) as usize]; + let x = cursor_row_layout.x_for_index(position.column() as usize) - scroll_left; + let y = (position.row() + 1) as f32 * layout.line_height - scroll_top; + let mut popover_origin = content_origin + vec2f(x, y); + let popover_height = hover_popover.size().y(); + + if popover_origin.y() + popover_height > bounds.lower_left().y() { + popover_origin.set_y(popover_origin.y() - layout.line_height - popover_height); + } + + hover_popover.paint( + popover_origin, + RectF::from_points(Vector2F::zero(), vec2f(f32::MAX, f32::MAX)), // Let content bleed outside of editor + cx, + ); + + cx.scene.pop_stacking_context(); + } + cx.scene.pop_layer(); } @@ -1081,6 +1103,7 @@ impl Element for EditorElement { let mut context_menu = None; let mut code_actions_indicator = None; + let mut hover = None; cx.render(&self.view.upgrade(cx).unwrap(), |view, cx| { let newest_selection_head = view .selections @@ -1097,6 +1120,8 @@ impl Element for EditorElement { code_actions_indicator = view .render_code_actions_indicator(&style, cx) .map(|indicator| (newest_selection_head.row(), indicator)); + + hover = view.render_hover_popover(style); } }); @@ -1120,6 +1145,19 @@ impl Element for EditorElement { ); } + if let Some((_, hover)) = hover.as_mut() { + hover.layout( + SizeConstraint { + min: Vector2F::zero(), + max: vec2f( + f32::INFINITY, + (12. * line_height).min((size.y() - line_height) / 2.), + ), + }, + cx, + ); + } + let blocks = self.layout_blocks( start_row..end_row, &snapshot, @@ -1156,6 +1194,7 @@ impl Element for EditorElement { selections, context_menu, code_actions_indicator, + hover, }, ) } @@ -1299,6 +1338,7 @@ pub struct LayoutState { selections: Vec<(ReplicaId, Vec)>, context_menu: Option<(DisplayPoint, ElementBox)>, code_actions_indicator: Option<(u32, ElementBox)>, + hover: Option<(DisplayPoint, ElementBox)>, } fn layout_line( diff --git a/crates/project/src/lsp_command.rs b/crates/project/src/lsp_command.rs index dec34f8898..fc0fd3b0dc 100644 --- a/crates/project/src/lsp_command.rs +++ b/crates/project/src/lsp_command.rs @@ -1,4 +1,4 @@ -use crate::{DocumentHighlight, Location, Project, ProjectTransaction}; +use crate::{DocumentHighlight, Location, Project, ProjectTransaction, Hover}; use anyhow::{anyhow, Result}; use async_trait::async_trait; use client::{proto, PeerId}; @@ -801,8 +801,7 @@ impl LspCommand for GetDocumentHighlights { #[async_trait(?Send)] impl LspCommand for GetHover { - // TODO: proper response type - type Response = Option; + type Response = Option; type LspRequest = lsp::request::HoverRequest; type ProtoRequest = proto::GetHover; @@ -823,20 +822,26 @@ impl LspCommand for GetHover { message: Option, project: ModelHandle, buffer: ModelHandle, - cx: AsyncAppContext, + mut cx: AsyncAppContext, ) -> Result { - // let (lsp_adapter, language_server) = project - // .read_with(&cx, |project, cx| { - // project - // .language_server_for_buffer(buffer.read(cx), cx) - // .cloned() - // }) - // .ok_or_else(|| anyhow!("no language server found for buffer"))?; - - // TODO: what here? - Ok(Some( - message.ok_or_else(|| anyhow!("invalid lsp response"))?, - )) + Ok(message.map(|hover| { + let range = hover.range.map(|range| { + cx.read(|cx| { + let buffer = buffer.read(cx); + let token_start = buffer + .clip_point_utf16(point_from_lsp(range.start), Bias::Left); + let token_end = buffer + .clip_point_utf16(point_from_lsp(range.end), Bias::Left); + buffer.anchor_after(token_start).. + buffer.anchor_before(token_end) + }) + }); + + Hover { + contents: hover.contents, + range + } + })) } fn to_proto(&self, project_id: u64, buffer: &Buffer) -> Self::ProtoRequest { diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index e7d145b184..07157c65f7 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -216,6 +216,12 @@ pub struct Symbol { pub signature: [u8; 32], } +#[derive(Debug)] +pub struct Hover { + pub contents: lsp::HoverContents, + pub range: Option>, +} + #[derive(Default)] pub struct ProjectTransaction(pub HashMap, language::Transaction>); @@ -2890,7 +2896,7 @@ impl Project { buffer: &ModelHandle, position: T, cx: &mut ModelContext, - ) -> Task>> { + ) -> Task>> { // TODO: proper return type let position = position.to_point_utf16(buffer.read(cx)); self.request_lsp(buffer.clone(), GetHover { position }, cx) diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index f469423921..92bc9dfc8e 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -444,6 +444,7 @@ pub struct Editor { pub autocomplete: AutocompleteStyle, pub code_actions_indicator: Color, pub unnecessary_code_fade: f32, + pub hover_popover: ContainerStyle, } #[derive(Clone, Deserialize, Default)] @@ -621,4 +622,4 @@ impl<'de> Deserialize<'de> for SyntaxTheme { Ok(result) } -} +} \ No newline at end of file diff --git a/styles/src/styleTree/editor.ts b/styles/src/styleTree/editor.ts index d669e4e734..14e7f9449f 100644 --- a/styles/src/styleTree/editor.ts +++ b/styles/src/styleTree/editor.ts @@ -8,6 +8,7 @@ import { text, TextColor } from "./components"; +import hoverPopover from "./hoverPopover"; export default function editor(theme: Theme) { const autocompleteItem = { @@ -145,6 +146,7 @@ export default function editor(theme: Theme) { invalidHintDiagnostic: diagnostic(theme, "muted"), invalidInformationDiagnostic: diagnostic(theme, "muted"), invalidWarningDiagnostic: diagnostic(theme, "muted"), + hover_popover: hoverPopover(theme), syntax, }; }