Jump to primary diagnostic when clicking on header's jump icon

This commit is contained in:
Antonio Scandurra 2022-05-31 16:25:14 +02:00
parent 4f9c207425
commit d180f7a2c3
2 changed files with 75 additions and 30 deletions

View file

@ -8,12 +8,12 @@ use editor::{
highlight_diagnostic_message, Autoscroll, Editor, ExcerptId, MultiBuffer, ToOffset, highlight_diagnostic_message, Autoscroll, Editor, ExcerptId, MultiBuffer, ToOffset,
}; };
use gpui::{ use gpui::{
actions, elements::*, fonts::TextStyle, platform::CursorStyle, serde_json, AnyViewHandle, actions, elements::*, fonts::TextStyle, impl_internal_actions, platform::CursorStyle,
AppContext, Entity, ModelHandle, MutableAppContext, RenderContext, Task, View, ViewContext, serde_json, AnyViewHandle, AppContext, Entity, ModelHandle, MutableAppContext, RenderContext,
ViewHandle, WeakViewHandle, Task, View, ViewContext, ViewHandle, WeakViewHandle,
}; };
use language::{ use language::{
Bias, Buffer, Diagnostic, DiagnosticEntry, DiagnosticSeverity, Point, Selection, SelectionGoal, Bias, Buffer, DiagnosticEntry, DiagnosticSeverity, Point, Selection, SelectionGoal,
}; };
use project::{DiagnosticSummary, Project, ProjectPath}; use project::{DiagnosticSummary, Project, ProjectPath};
use serde_json::json; use serde_json::json;
@ -27,15 +27,18 @@ use std::{
path::PathBuf, path::PathBuf,
sync::Arc, sync::Arc,
}; };
use util::TryFutureExt; use util::{ResultExt, TryFutureExt};
use workspace::{ItemHandle as _, ItemNavHistory, Workspace}; use workspace::{ItemHandle as _, ItemNavHistory, Workspace};
actions!(diagnostics, [Deploy]); actions!(diagnostics, [Deploy]);
impl_internal_actions!(diagnostics, [Jump]);
const CONTEXT_LINE_COUNT: u32 = 1; const CONTEXT_LINE_COUNT: u32 = 1;
pub fn init(cx: &mut MutableAppContext) { pub fn init(cx: &mut MutableAppContext) {
cx.add_action(ProjectDiagnosticsEditor::deploy); cx.add_action(ProjectDiagnosticsEditor::deploy);
cx.add_action(ProjectDiagnosticsEditor::jump);
items::init(cx); items::init(cx);
} }
@ -56,6 +59,12 @@ struct PathState {
diagnostic_groups: Vec<DiagnosticGroupState>, diagnostic_groups: Vec<DiagnosticGroupState>,
} }
#[derive(Clone, Debug)]
struct Jump {
path: ProjectPath,
range: Range<Point>,
}
struct DiagnosticGroupState { struct DiagnosticGroupState {
primary_diagnostic: DiagnosticEntry<language::Anchor>, primary_diagnostic: DiagnosticEntry<language::Anchor>,
primary_excerpt_ix: usize, primary_excerpt_ix: usize,
@ -177,6 +186,24 @@ impl ProjectDiagnosticsEditor {
} }
} }
fn jump(workspace: &mut Workspace, action: &Jump, cx: &mut ViewContext<Workspace>) {
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>()?;
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<Self>) { fn update_excerpts(&mut self, cx: &mut ViewContext<Self>) {
let paths = mem::take(&mut self.paths_to_update); let paths = mem::take(&mut self.paths_to_update);
let project = self.project.clone(); let project = self.project.clone();
@ -311,14 +338,19 @@ impl ProjectDiagnosticsEditor {
if is_first_excerpt_for_group { if is_first_excerpt_for_group {
is_first_excerpt_for_group = false; is_first_excerpt_for_group = false;
let mut primary = let mut primary =
group.entries[group.primary_ix].diagnostic.clone(); group.entries[group.primary_ix].resolve::<Point>(&snapshot);
primary.message = primary.diagnostic.message = primary
primary.message.split('\n').next().unwrap().to_string(); .diagnostic
.message
.split('\n')
.next()
.unwrap()
.to_string();
group_state.block_count += 1; group_state.block_count += 1;
blocks_to_add.push(BlockProperties { blocks_to_add.push(BlockProperties {
position: header_position, position: header_position,
height: 2, height: 2,
render: diagnostic_header_renderer(primary), render: diagnostic_header_renderer(primary, path.clone()),
disposition: BlockDisposition::Above, 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<Point>, path: ProjectPath) -> RenderBlock {
enum JumpIcon {} enum JumpIcon {}
let (message, highlights) = highlight_diagnostic_message(&diagnostic.message); let (message, highlights) = highlight_diagnostic_message(&entry.diagnostic.message);
Arc::new(move |cx| { Arc::new(move |cx| {
let settings = cx.global::<Settings>(); let settings = cx.global::<Settings>();
let theme = &settings.theme.editor; let theme = &settings.theme.editor;
let style = theme.diagnostic_header.clone(); let style = theme.diagnostic_header.clone();
let font_size = (style.text_scale_factor * settings.buffer_font_size).round(); let font_size = (style.text_scale_factor * settings.buffer_font_size).round();
let icon_width = cx.em_width * style.icon_width_factor; 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") Svg::new("icons/diagnostic-error-10.svg")
.with_color(theme.error_diagnostic.message.text.color) .with_color(theme.error_diagnostic.message.text.color)
} else { } else {
@ -614,7 +646,7 @@ fn diagnostic_header_renderer(diagnostic: Diagnostic) -> RenderBlock {
.aligned() .aligned()
.boxed(), .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)) Label::new(code, style.code.text.clone().with_font_size(font_size))
.contained() .contained()
.with_style(style.code.container) .with_style(style.code.container)
@ -622,7 +654,10 @@ fn diagnostic_header_renderer(diagnostic: Diagnostic) -> RenderBlock {
.boxed() .boxed()
})) }))
.with_child( .with_child(
MouseEventHandler::new::<JumpIcon, _, _>(0, cx, |state, _| { MouseEventHandler::new::<JumpIcon, _, _>(
entry.diagnostic.group_id,
cx,
|state, _| {
let style = style.jump_icon.style_for(state, false); let style = style.jump_icon.style_for(state, false);
Svg::new("icons/jump.svg") Svg::new("icons/jump.svg")
.with_color(style.color) .with_color(style.color)
@ -635,10 +670,18 @@ fn diagnostic_header_renderer(diagnostic: Diagnostic) -> RenderBlock {
.with_width(style.button_width) .with_width(style.button_width)
.with_height(style.button_width) .with_height(style.button_width)
.boxed() .boxed()
}) },
)
.with_cursor_style(CursorStyle::PointingHand) .with_cursor_style(CursorStyle::PointingHand)
.on_click(|_, _, cx| { .on_click({
dbg!("click!"); let entry = entry.clone();
let path = path.clone();
move |_, _, cx| {
cx.dispatch_action(Jump {
path: path.clone(),
range: entry.range.clone(),
});
}
}) })
.aligned() .aligned()
.flex_float() .flex_float()

View file

@ -299,7 +299,9 @@ impl Pane {
) -> Box<dyn ItemHandle> { ) -> Box<dyn ItemHandle> {
let existing_item = pane.update(cx, |pane, cx| { let existing_item = pane.update(cx, |pane, cx| {
for (ix, item) in pane.items.iter().enumerate() { 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(); let item = item.boxed_clone();
pane.activate_item(ix, true, focus_item, cx); pane.activate_item(ix, true, focus_item, cx);
return Some(item); return Some(item);