Pull hover popover out of context menu

This commit is contained in:
Keith Simmons 2022-05-30 21:09:19 -07:00
parent 470c70d394
commit 560dff7329
6 changed files with 95 additions and 45 deletions

View file

@ -424,7 +424,7 @@ pub struct Editor {
next_completion_id: CompletionId,
available_code_actions: Option<(ModelHandle<Buffer>, Arc<[CodeAction]>)>,
code_actions_task: Option<Task<()>>,
hover_task: Option<Task<()>>,
hover_task: Option<Task<Option<()>>>,
document_highlights_task: Option<Task<()>>,
pending_rename: Option<RenameState>,
searchable: bool,
@ -432,6 +432,7 @@ pub struct Editor {
keymap_context_layers: BTreeMap<TypeId, gpui::keymap::Context>,
input_enabled: bool,
leader_replica_id: Option<u16>,
hover_popover: Option<HoverPopover>,
}
pub struct EditorSnapshot {
@ -571,7 +572,6 @@ struct InvalidationStack<T>(Vec<T>);
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<Self>) {
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 => {}
}

View file

@ -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<SelectionLayout>)>,
context_menu: Option<(DisplayPoint, ElementBox)>,
code_actions_indicator: Option<(u32, ElementBox)>,
hover: Option<(DisplayPoint, ElementBox)>,
}
fn layout_line(

View file

@ -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<lsp::Hover>;
type Response = Option<Hover>;
type LspRequest = lsp::request::HoverRequest;
type ProtoRequest = proto::GetHover;
@ -823,20 +822,26 @@ impl LspCommand for GetHover {
message: Option<lsp::Hover>,
project: ModelHandle<Project>,
buffer: ModelHandle<Buffer>,
cx: AsyncAppContext,
mut cx: AsyncAppContext,
) -> Result<Self::Response> {
// 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 {

View file

@ -216,6 +216,12 @@ pub struct Symbol {
pub signature: [u8; 32],
}
#[derive(Debug)]
pub struct Hover {
pub contents: lsp::HoverContents,
pub range: Option<Range<language::Anchor>>,
}
#[derive(Default)]
pub struct ProjectTransaction(pub HashMap<ModelHandle<Buffer>, language::Transaction>);
@ -2890,7 +2896,7 @@ impl Project {
buffer: &ModelHandle<Buffer>,
position: T,
cx: &mut ModelContext<Self>,
) -> Task<Result<Option<lsp::Hover>>> {
) -> Task<Result<Option<Hover>>> {
// TODO: proper return type
let position = position.to_point_utf16(buffer.read(cx));
self.request_lsp(buffer.clone(), GetHover { position }, cx)

View file

@ -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)
}
}
}

View file

@ -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,
};
}