use std::rc::Rc; use gpui::{ canvas, deferred, div, px, App, AppContext as _, Bounds, Context, Empty, Entity, InteractiveElement, IntoElement, ParentElement as _, Pixels, Point, Render, Styled, Window, }; use crate::{ input::{InputState, Marker}, text::TextView, ActiveTheme as _, }; pub struct DiagnosticPopover { state: Entity, pub(super) marker: Rc, bounds: Bounds, open: bool, } impl DiagnosticPopover { pub fn new(marker: &Marker, state: Entity, cx: &mut App) -> Entity { let marker = Rc::new(marker.clone()); cx.new(|_| Self { marker, state, bounds: Bounds::default(), open: true, }) } fn origin(&self, cx: &App) -> Option> { let Some(range) = self.marker.range.as_ref() else { return None; }; let Some(last_layout) = self.state.read(cx).last_layout.as_ref() else { return None; }; let line_number_width = last_layout.line_number_width; let (_, _, start_pos) = self .state .read(cx) .line_and_position_for_offset(range.start); start_pos.map(|pos| pos + Point::new(line_number_width, px(0.))) } pub(super) fn show(&mut self, cx: &mut Context) { self.open = true; cx.notify(); } pub(super) fn hide(&mut self, cx: &mut Context) { self.open = false; cx.notify(); } pub(super) fn check_to_hide(&mut self, mouse_position: Point, cx: &mut Context) { if !self.open { return; } let padding = px(5.); let bounds = Bounds { origin: self.bounds.origin.map(|v| v - padding), size: self.bounds.size.map(|v| v + padding * 2.), }; if !bounds.contains(&mouse_position) { self.hide(cx); } } } impl Render for DiagnosticPopover { fn render(&mut self, window: &mut Window, cx: &mut gpui::Context) -> impl IntoElement { if !self.open { return Empty.into_any_element(); } let view = cx.entity(); let theme = &cx.theme().highlight_theme; let message = self.marker.message.clone(); let Some(pos) = self.origin(cx) else { return Empty.into_any_element(); }; let (border, bg, fg) = ( self.marker.severity.border(theme, cx), self.marker.severity.bg(theme, cx), self.marker.severity.fg(theme, cx), ); let scroll_origin = self.state.read(cx).scroll_handle.offset(); let y = pos.y - self.bounds.size.height + scroll_origin.y; let x = pos.x + scroll_origin.x; let max_width = px(500.).min(window.bounds().size.width - x); deferred( div() .id("diagnostic-popover") .absolute() .left(x) .top(y) .px_1() .py_0p5() .text_xs() .bg(bg) .w(max_width) .text_color(fg) .border_1() .border_color(border) .rounded(cx.theme().radius) .shadow_xs() .child(TextView::markdown("message", message)) .child( canvas( move |bounds, _, cx| view.update(cx, |r, _| r.bounds = bounds), |_, _, _, _| {}, ) .top_0() .left_0() .absolute() .size_full(), ) .on_mouse_down_out(cx.listener(|this, _, _, cx| { this.open = false; cx.notify(); })), ) .into_any_element() } }