mirror of https://github.com/lapce/lapce.git
Show diagnostics on hover
This commit is contained in:
parent
0f02410926
commit
234b10e7f9
|
@ -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,
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue