mirror of
https://github.com/zed-industries/zed.git
synced 2024-12-26 10:40:54 +00:00
Pull hover popover out of context menu
This commit is contained in:
parent
470c70d394
commit
560dff7329
6 changed files with 95 additions and 45 deletions
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue