From 234b10e7f98323b5f497988f215a4ea1e0173445 Mon Sep 17 00:00:00 2001 From: MinusGix Date: Sun, 22 May 2022 14:59:37 -0500 Subject: [PATCH] Show diagnostics on hover --- lapce-data/src/editor.rs | 4 ++ lapce-data/src/hover.rs | 82 +++++++++++++++++++++++++++- lapce-ui/src/hover.rs | 114 ++++++++++++++++++++++++++++++++------- 3 files changed, 180 insertions(+), 20 deletions(-) diff --git a/lapce-data/src/editor.rs b/lapce-data/src/editor.rs index c0e881fe..2c88a239 100644 --- a/lapce-data/src/editor.rs +++ b/lapce-data/src/editor.rs @@ -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, diff --git a/lapce-data/src/hover.rs b/lapce-data/src/hover.rs index 54bab559..e766fe97 100644 --- a/lapce-data/src/hover.rs +++ b/lapce-data/src/hover.rs @@ -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>, + /// The text for the diagnostic(s) at the position + pub diagnostic_content: Option, } 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, request_id: usize, doc: Arc, + diagnostics: Option>>, 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::(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>) { self.status = HoverStatus::Done; self.items = items; } + + fn collect_diagnostics( + &mut self, + position: Position, + diagnostics: Option>>, + config: Arc, + ) { + 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 { diff --git a/lapce-ui/src/hover.rs b/lapce-ui/src/hover.rs index 021098d1..e2b8f79d 100644 --- a/lapce-ui/src/hover.rs +++ b/lapce-ui/src/hover.rs @@ -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, + active_diagnostic_layout: TextLayout, } 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); } }