## Before <img width="1130" height="783" alt="image" src="https://github.com/user-attachments/assets/3e0db5f4-bb84-480f-8b38-dbe24e6c6214" /> ## After https://github.com/user-attachments/assets/fdaed71e-3ab8-4258-a1eb-b731181ca31c
137 lines
3.9 KiB
Rust
137 lines
3.9 KiB
Rust
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<InputState>,
|
|
pub(super) marker: Rc<Marker>,
|
|
bounds: Bounds<Pixels>,
|
|
open: bool,
|
|
}
|
|
|
|
impl DiagnosticPopover {
|
|
pub fn new(marker: &Marker, state: Entity<InputState>, cx: &mut App) -> Entity<Self> {
|
|
let marker = Rc::new(marker.clone());
|
|
|
|
cx.new(|_| Self {
|
|
marker,
|
|
state,
|
|
bounds: Bounds::default(),
|
|
open: true,
|
|
})
|
|
}
|
|
|
|
fn origin(&self, cx: &App) -> Option<Point<Pixels>> {
|
|
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>) {
|
|
self.open = true;
|
|
cx.notify();
|
|
}
|
|
|
|
pub(super) fn hide(&mut self, cx: &mut Context<Self>) {
|
|
self.open = false;
|
|
cx.notify();
|
|
}
|
|
|
|
pub(super) fn check_to_hide(&mut self, mouse_position: Point<Pixels>, cx: &mut Context<Self>) {
|
|
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<Self>) -> 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()
|
|
}
|
|
}
|