Show diagnostics on hover

This commit is contained in:
MinusGix 2022-05-22 14:59:37 -05:00
parent 0f02410926
commit 234b10e7f9
3 changed files with 180 additions and 20 deletions

View File

@ -505,6 +505,9 @@ pub fn update_hover(&mut self, ctx: &mut EventCtx, offset: usize) {
return;
}
// Get the diagnostics for when we make the request
let diagnostics = self.diagnostics().map(Arc::clone);
let mut hover = Arc::make_mut(&mut self.hover);
if hover.status != HoverStatus::Inactive
@ -527,6 +530,7 @@ pub fn update_hover(&mut self, ctx: &mut EventCtx, offset: usize) {
self.proxy.clone(),
hover.request_id,
self.doc.clone(),
diagnostics,
self.doc.buffer().offset_to_position(start_offset),
hover.id,
event_sink,

View File

@ -10,6 +10,7 @@
use crate::{
command::{LapceUICommand, LAPCE_UI_COMMAND},
config::{Config, LapceTheme},
data::EditorDiagnostic,
document::Document,
proxy::LapceProxy,
rich_text::{AttributesAdder, RichText, RichTextBuilder},
@ -47,6 +48,8 @@ pub struct HoverData {
pub active_item_index: usize,
/// The hover items that are currently loaded
pub items: Arc<Vec<RichText>>,
/// The text for the diagnostic(s) at the position
pub diagnostic_content: Option<RichText>,
}
impl HoverData {
@ -65,6 +68,7 @@ pub fn new() -> Self {
active_item_index: 0,
items: Arc::new(Vec::new()),
diagnostic_content: None,
}
}
@ -110,10 +114,11 @@ pub fn cancel(&mut self) {
/// Send a request to update the hover at the given position anad file
#[allow(clippy::too_many_arguments)]
pub fn request(
&self,
&mut self,
proxy: Arc<LapceProxy>,
request_id: usize,
doc: Arc<Document>,
diagnostics: Option<Arc<Vec<EditorDiagnostic>>>,
position: Position,
hover_widget_id: WidgetId,
event_sink: ExtEventSink,
@ -122,6 +127,9 @@ pub fn request(
let buffer_id = doc.id();
let syntax = doc.syntax().cloned();
// Clone config for use inside the proxy callback
let p_config = config.clone();
// Get the information/documentation that should be shown on hover
proxy.get_hover(
request_id,
buffer_id,
@ -129,7 +137,8 @@ pub fn request(
Box::new(move |result| {
if let Ok(resp) = result {
if let Ok(resp) = serde_json::from_value::<Hover>(resp) {
let items = parse_hover_resp(syntax.as_ref(), resp, &config);
let items =
parse_hover_resp(syntax.as_ref(), resp, &p_config);
let _ = event_sink.submit_command(
LAPCE_UI_COMMAND,
@ -140,6 +149,8 @@ pub fn request(
}
}),
);
self.collect_diagnostics(position, diagnostics, config);
}
/// Receive the result of a hover request
@ -154,6 +165,73 @@ pub fn receive(&mut self, request_id: usize, items: Arc<Vec<RichText>>) {
self.status = HoverStatus::Done;
self.items = items;
}
fn collect_diagnostics(
&mut self,
position: Position,
diagnostics: Option<Arc<Vec<EditorDiagnostic>>>,
config: Arc<Config>,
) {
if let Some(diagnostics) = diagnostics {
let diagnostics = diagnostics
.iter()
.map(|diag| &diag.diagnostic)
.filter(|diag| {
position >= diag.range.start && position < diag.range.end
});
// Get the dim foreground color for extra information about the error that is typically
// not significant
let dim_color =
config.get_color_unchecked(LapceTheme::EDITOR_DIM).clone();
// Build up the text for all the diagnostics
let mut content = RichTextBuilder::new();
for diagnostic in diagnostics {
content.push(&diagnostic.message);
// If there's a source of the message (ex: it came from rustc or rust-analyzer)
// then include that
if let Some(source) = &diagnostic.source {
content.push(" ");
content.push(source).text_color(dim_color.clone());
// If there's an available error code then include that
if let Some(code) = &diagnostic.code {
// TODO: code description field has information like documentation on the
// error code which could be useful to provide as a link
// formatted as diagsource(code)
content.push("(").text_color(dim_color.clone());
match code {
lsp_types::NumberOrString::Number(v) => {
content
.push(&v.to_string())
.text_color(dim_color.clone());
}
lsp_types::NumberOrString::String(v) => {
content
.push(v.as_str())
.text_color(dim_color.clone());
}
}
content.push(")").text_color(dim_color.clone());
}
}
// TODO: The Related information field has data that can give better insight into
// the causes of the error
// (ex: The place where a variable was moved into when the 'main' error is at where
// you tried using it. This would work the best with some way to link to files)
content.push("\n");
}
self.diagnostic_content = Some(content.build());
} else {
self.diagnostic_content = None;
}
}
}
impl Default for HoverData {

View File

@ -1,7 +1,7 @@
use std::sync::Arc;
use druid::{
theme, ArcStr, BoxConstraints, Command, Data, Env, Event, EventCtx,
kurbo::Line, theme, ArcStr, BoxConstraints, Command, Data, Env, Event, EventCtx,
FontDescriptor, LayoutCtx, LifeCycle, LifeCycleCtx, PaintCtx, Point,
RenderContext, Size, Target, TextLayout, UpdateCtx, Widget, WidgetId, WidgetPod,
};
@ -191,6 +191,7 @@ fn paint(&mut self, ctx: &mut PaintCtx, data: &LapceTabData, env: &Env) {
#[derive(Default)]
struct Hover {
active_layout: TextLayout<RichText>,
active_diagnostic_layout: TextLayout<RichText>,
}
impl Hover {
@ -204,6 +205,11 @@ fn new() -> Self {
layout.set_text(RichText::new(ArcStr::from("")));
layout
},
active_diagnostic_layout: {
let mut layout = TextLayout::new();
layout.set_text(RichText::new(ArcStr::from("")));
layout
},
}
}
}
@ -236,12 +242,17 @@ fn update(
data: &LapceTabData,
_env: &Env,
) {
// If the active item has changed or we've switched our current item existence status then
// update the layout
// If the active item has changed or we've switched our current item existence status
// or we've gotten new diagnostic text
// then update the layout
if old_data.hover.request_id != data.hover.request_id
|| old_data.hover.active_item_index != data.hover.active_item_index
|| old_data.hover.get_current_item().is_some()
!= data.hover.get_current_item().is_some()
|| !old_data
.hover
.diagnostic_content
.same(&data.hover.diagnostic_content)
{
if let Some(item) = data.hover.get_current_item() {
self.active_layout.set_text(item.clone());
@ -249,17 +260,32 @@ fn update(
self.active_layout.set_text(RichText::new(ArcStr::from("")));
}
self.active_layout.set_font(
FontDescriptor::new(data.config.ui.font_family())
.with_size(data.config.ui.font_size() as f64),
);
self.active_layout.set_text_color(
data.config
.get_color_unchecked(LapceTheme::EDITOR_FOREGROUND)
.clone(),
);
if let Some(diagnostic_content) = &data.hover.diagnostic_content {
self.active_diagnostic_layout
.set_text(diagnostic_content.clone());
} else {
self.active_diagnostic_layout
.set_text(RichText::new(ArcStr::from("")));
}
if self.active_layout.needs_rebuild_after_update(ctx) {
let font = FontDescriptor::new(data.config.ui.font_family())
.with_size(data.config.ui.font_size() as f64);
let text_color = data
.config
.get_color_unchecked(LapceTheme::EDITOR_FOREGROUND)
.clone();
self.active_layout.set_font(font.clone());
self.active_layout.set_text_color(text_color.clone());
self.active_diagnostic_layout.set_font(font);
self.active_diagnostic_layout.set_text_color(text_color);
if self.active_layout.needs_rebuild_after_update(ctx)
|| self
.active_diagnostic_layout
.needs_rebuild_after_update(ctx)
{
ctx.request_layout();
}
}
@ -269,7 +295,7 @@ fn layout(
&mut self,
ctx: &mut LayoutCtx,
bc: &BoxConstraints,
_data: &LapceTabData,
data: &LapceTabData,
env: &Env,
) -> Size {
let width = bc.max().width;
@ -281,21 +307,38 @@ fn layout(
self.active_layout.set_wrap_width(max_width);
self.active_layout.rebuild_if_needed(ctx.text(), env);
self.active_diagnostic_layout.set_wrap_width(max_width);
self.active_diagnostic_layout
.rebuild_if_needed(ctx.text(), env);
let text_metrics = self.active_layout.layout_metrics();
ctx.set_baseline_offset(
text_metrics.size.height - text_metrics.first_baseline,
);
Size::new(width, text_metrics.size.height + Hover::STARTING_Y * 2.0)
let diagnostic_height = if self.active_diagnostic_layout.size().is_empty() {
0.0
} else {
let diagnostic_text_metrics =
self.active_diagnostic_layout.layout_metrics();
diagnostic_text_metrics.size.height
+ data.config.editor.line_height as f64
};
Size::new(
width,
text_metrics.size.height + diagnostic_height + Hover::STARTING_Y * 2.0,
)
}
fn paint(&mut self, ctx: &mut PaintCtx, data: &LapceTabData, _env: &Env) {
fn paint(&mut self, ctx: &mut PaintCtx, data: &LapceTabData, env: &Env) {
if data.hover.status != HoverStatus::Done {
return;
}
let rect = ctx.region().bounding_box();
let origin = Point::new(Self::STARTING_X, Self::STARTING_Y);
let diagnostic_origin = Point::new(Self::STARTING_X, Self::STARTING_Y);
ctx.fill(
rect,
@ -303,6 +346,41 @@ fn paint(&mut self, ctx: &mut PaintCtx, data: &LapceTabData, _env: &Env) {
.get_color_unchecked(LapceTheme::HOVER_BACKGROUND),
);
self.active_layout.draw(ctx, origin);
// Draw diagnostic text if it exists
let height = if self.active_diagnostic_layout.size().is_empty() {
0.0
} else {
// let text_metrics = self.active_layout.layout_metrics();
let diagnostic_text_metrics =
self.active_diagnostic_layout.layout_metrics();
let side_margin =
env.get(theme::SCROLLBAR_WIDTH) + env.get(theme::SCROLLBAR_PAD);
let line_height = data.config.editor.line_height as f64;
// Create a separating line
let line = {
let x0 = rect.x0 + side_margin;
let y = diagnostic_text_metrics.size.height + line_height;
let x1 = rect.x1 - side_margin;
Line::new(Point::new(x0, y), Point::new(x1, y))
};
// Draw the separator
ctx.stroke(
line,
data.config.get_color_unchecked(LapceTheme::LAPCE_BORDER),
1.0,
);
// Draw the diagnostic text
self.active_diagnostic_layout.draw(ctx, diagnostic_origin);
diagnostic_text_metrics.size.height + line_height
};
let doc_origin = diagnostic_origin + (0.0, height);
self.active_layout.draw(ctx, doc_origin);
}
}