Detailed changes
@@ -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 => {}
}
@@ -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(
@@ -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 {
@@ -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)
@@ -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)
}
-}
+}
@@ -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,
};
}