From d180f7a2c3061d913fa24476dc802a3e07a946ff Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 31 May 2022 16:25:14 +0200 Subject: [PATCH] Jump to primary diagnostic when clicking on header's jump icon --- crates/diagnostics/src/diagnostics.rs | 101 ++++++++++++++++++-------- crates/workspace/src/pane.rs | 4 +- 2 files changed, 75 insertions(+), 30 deletions(-) diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index 8dc1b27d700d80e598874ccffd6612ad0cb268ae..89ff46bf6420f5f2b6d0ad1bf6943eb17c9ec65a 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -8,12 +8,12 @@ use editor::{ highlight_diagnostic_message, Autoscroll, Editor, ExcerptId, MultiBuffer, ToOffset, }; use gpui::{ - actions, elements::*, fonts::TextStyle, platform::CursorStyle, serde_json, AnyViewHandle, - AppContext, Entity, ModelHandle, MutableAppContext, RenderContext, Task, View, ViewContext, - ViewHandle, WeakViewHandle, + actions, elements::*, fonts::TextStyle, impl_internal_actions, platform::CursorStyle, + serde_json, AnyViewHandle, AppContext, Entity, ModelHandle, MutableAppContext, RenderContext, + Task, View, ViewContext, ViewHandle, WeakViewHandle, }; use language::{ - Bias, Buffer, Diagnostic, DiagnosticEntry, DiagnosticSeverity, Point, Selection, SelectionGoal, + Bias, Buffer, DiagnosticEntry, DiagnosticSeverity, Point, Selection, SelectionGoal, }; use project::{DiagnosticSummary, Project, ProjectPath}; use serde_json::json; @@ -27,15 +27,18 @@ use std::{ path::PathBuf, sync::Arc, }; -use util::TryFutureExt; +use util::{ResultExt, TryFutureExt}; use workspace::{ItemHandle as _, ItemNavHistory, Workspace}; actions!(diagnostics, [Deploy]); +impl_internal_actions!(diagnostics, [Jump]); + const CONTEXT_LINE_COUNT: u32 = 1; pub fn init(cx: &mut MutableAppContext) { cx.add_action(ProjectDiagnosticsEditor::deploy); + cx.add_action(ProjectDiagnosticsEditor::jump); items::init(cx); } @@ -56,6 +59,12 @@ struct PathState { diagnostic_groups: Vec, } +#[derive(Clone, Debug)] +struct Jump { + path: ProjectPath, + range: Range, +} + struct DiagnosticGroupState { primary_diagnostic: DiagnosticEntry, primary_excerpt_ix: usize, @@ -177,6 +186,24 @@ impl ProjectDiagnosticsEditor { } } + fn jump(workspace: &mut Workspace, action: &Jump, cx: &mut ViewContext) { + let editor = workspace.open_path(action.path.clone(), true, cx); + let range = action.range.clone(); + cx.spawn_weak(|_, mut cx| async move { + let editor = editor.await.log_err()?.downcast::()?; + editor.update(&mut cx, |editor, cx| { + let buffer = editor.buffer().read(cx).as_singleton()?; + let cursor = buffer.read(cx).clip_point(range.start, Bias::Left); + editor.change_selections(Some(Autoscroll::Newest), cx, |s| { + s.select_ranges([cursor..cursor]); + }); + Some(()) + })?; + Some(()) + }) + .detach() + } + fn update_excerpts(&mut self, cx: &mut ViewContext) { let paths = mem::take(&mut self.paths_to_update); let project = self.project.clone(); @@ -311,14 +338,19 @@ impl ProjectDiagnosticsEditor { if is_first_excerpt_for_group { is_first_excerpt_for_group = false; let mut primary = - group.entries[group.primary_ix].diagnostic.clone(); - primary.message = - primary.message.split('\n').next().unwrap().to_string(); + group.entries[group.primary_ix].resolve::(&snapshot); + primary.diagnostic.message = primary + .diagnostic + .message + .split('\n') + .next() + .unwrap() + .to_string(); group_state.block_count += 1; blocks_to_add.push(BlockProperties { position: header_position, height: 2, - render: diagnostic_header_renderer(primary), + render: diagnostic_header_renderer(primary, path.clone()), disposition: BlockDisposition::Above, }); } @@ -575,17 +607,17 @@ impl workspace::Item for ProjectDiagnosticsEditor { } } -fn diagnostic_header_renderer(diagnostic: Diagnostic) -> RenderBlock { +fn diagnostic_header_renderer(entry: DiagnosticEntry, path: ProjectPath) -> RenderBlock { enum JumpIcon {} - let (message, highlights) = highlight_diagnostic_message(&diagnostic.message); + let (message, highlights) = highlight_diagnostic_message(&entry.diagnostic.message); Arc::new(move |cx| { let settings = cx.global::(); let theme = &settings.theme.editor; let style = theme.diagnostic_header.clone(); let font_size = (style.text_scale_factor * settings.buffer_font_size).round(); let icon_width = cx.em_width * style.icon_width_factor; - let icon = if diagnostic.severity == DiagnosticSeverity::ERROR { + let icon = if entry.diagnostic.severity == DiagnosticSeverity::ERROR { Svg::new("icons/diagnostic-error-10.svg") .with_color(theme.error_diagnostic.message.text.color) } else { @@ -614,7 +646,7 @@ fn diagnostic_header_renderer(diagnostic: Diagnostic) -> RenderBlock { .aligned() .boxed(), ) - .with_children(diagnostic.code.clone().map(|code| { + .with_children(entry.diagnostic.code.clone().map(|code| { Label::new(code, style.code.text.clone().with_font_size(font_size)) .contained() .with_style(style.code.container) @@ -622,23 +654,34 @@ fn diagnostic_header_renderer(diagnostic: Diagnostic) -> RenderBlock { .boxed() })) .with_child( - MouseEventHandler::new::(0, cx, |state, _| { - let style = style.jump_icon.style_for(state, false); - Svg::new("icons/jump.svg") - .with_color(style.color) - .constrained() - .with_width(style.icon_width) - .aligned() - .contained() - .with_style(style.container) - .constrained() - .with_width(style.button_width) - .with_height(style.button_width) - .boxed() - }) + MouseEventHandler::new::( + entry.diagnostic.group_id, + cx, + |state, _| { + let style = style.jump_icon.style_for(state, false); + Svg::new("icons/jump.svg") + .with_color(style.color) + .constrained() + .with_width(style.icon_width) + .aligned() + .contained() + .with_style(style.container) + .constrained() + .with_width(style.button_width) + .with_height(style.button_width) + .boxed() + }, + ) .with_cursor_style(CursorStyle::PointingHand) - .on_click(|_, _, cx| { - dbg!("click!"); + .on_click({ + let entry = entry.clone(); + let path = path.clone(); + move |_, _, cx| { + cx.dispatch_action(Jump { + path: path.clone(), + range: entry.range.clone(), + }); + } }) .aligned() .flex_float() diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index e8e67642739224048ded305f5d970682b778c13a..e712b4a1fb83ac2ba565e1b21604d39ce10a8d2c 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -299,7 +299,9 @@ impl Pane { ) -> Box { let existing_item = pane.update(cx, |pane, cx| { for (ix, item) in pane.items.iter().enumerate() { - if item.project_entry_ids(cx).as_slice() == &[project_entry_id] { + if item.project_path(cx).is_some() + && item.project_entry_ids(cx).as_slice() == &[project_entry_id] + { let item = item.boxed_clone(); pane.activate_item(ix, true, focus_item, cx); return Some(item);