Enhance keyboard navigation when showing next diagnostic
Co-Authored-By: Nathan Sobo <nathan@zed.dev>
This commit is contained in:
parent
643545e91e
commit
5094380c83
4 changed files with 99 additions and 28 deletions
|
@ -527,6 +527,7 @@ impl<'a> sum_tree::SeekTarget<'a, AnchorRangeMultimapSummary, FullOffsetRange> f
|
||||||
|
|
||||||
pub trait AnchorRangeExt {
|
pub trait AnchorRangeExt {
|
||||||
fn cmp<'a>(&self, b: &Range<Anchor>, buffer: impl Into<Content<'a>>) -> Result<Ordering>;
|
fn cmp<'a>(&self, b: &Range<Anchor>, buffer: impl Into<Content<'a>>) -> Result<Ordering>;
|
||||||
|
fn to_offset<'a>(&self, content: impl Into<Content<'a>>) -> Range<usize>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AnchorRangeExt for Range<Anchor> {
|
impl AnchorRangeExt for Range<Anchor> {
|
||||||
|
@ -537,4 +538,9 @@ impl AnchorRangeExt for Range<Anchor> {
|
||||||
ord @ _ => ord,
|
ord @ _ => ord,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn to_offset<'a>(&self, content: impl Into<Content<'a>>) -> Range<usize> {
|
||||||
|
let content = content.into();
|
||||||
|
self.start.to_offset(&content)..self.end.to_offset(&content)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,8 @@ mod patch;
|
||||||
mod tab_map;
|
mod tab_map;
|
||||||
mod wrap_map;
|
mod wrap_map;
|
||||||
|
|
||||||
use block_map::{BlockId, BlockMap, BlockPoint};
|
pub use block_map::{BlockDisposition, BlockId, BlockProperties, BufferRows, Chunks};
|
||||||
|
use block_map::{BlockMap, BlockPoint};
|
||||||
use buffer::Rope;
|
use buffer::Rope;
|
||||||
use fold_map::{FoldMap, ToFoldPoint as _};
|
use fold_map::{FoldMap, ToFoldPoint as _};
|
||||||
use gpui::{fonts::FontId, Entity, ModelContext, ModelHandle};
|
use gpui::{fonts::FontId, Entity, ModelContext, ModelHandle};
|
||||||
|
@ -15,8 +16,6 @@ use tab_map::TabMap;
|
||||||
use theme::SyntaxTheme;
|
use theme::SyntaxTheme;
|
||||||
use wrap_map::WrapMap;
|
use wrap_map::WrapMap;
|
||||||
|
|
||||||
pub use block_map::{BlockDisposition, BlockProperties, BufferRows, Chunks};
|
|
||||||
|
|
||||||
pub trait ToDisplayPoint {
|
pub trait ToDisplayPoint {
|
||||||
fn to_display_point(&self, map: &DisplayMapSnapshot) -> DisplayPoint;
|
fn to_display_point(&self, map: &DisplayMapSnapshot) -> DisplayPoint;
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,7 @@ use smol::Timer;
|
||||||
use std::{
|
use std::{
|
||||||
cell::RefCell,
|
cell::RefCell,
|
||||||
cmp::{self, Ordering},
|
cmp::{self, Ordering},
|
||||||
|
collections::HashSet,
|
||||||
iter, mem,
|
iter, mem,
|
||||||
ops::{Range, RangeInclusive},
|
ops::{Range, RangeInclusive},
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
|
@ -304,6 +305,7 @@ pub struct Editor {
|
||||||
add_selections_state: Option<AddSelectionsState>,
|
add_selections_state: Option<AddSelectionsState>,
|
||||||
autoclose_stack: Vec<BracketPairState>,
|
autoclose_stack: Vec<BracketPairState>,
|
||||||
select_larger_syntax_node_stack: Vec<Box<[Selection<usize>]>>,
|
select_larger_syntax_node_stack: Vec<Box<[Selection<usize>]>>,
|
||||||
|
active_diagnostics: Option<ActiveDiagnosticGroup>,
|
||||||
scroll_position: Vector2F,
|
scroll_position: Vector2F,
|
||||||
scroll_top_anchor: Anchor,
|
scroll_top_anchor: Anchor,
|
||||||
autoscroll_requested: bool,
|
autoscroll_requested: bool,
|
||||||
|
@ -336,6 +338,12 @@ struct BracketPairState {
|
||||||
pair: BracketPair,
|
pair: BracketPair,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct ActiveDiagnosticGroup {
|
||||||
|
primary_range: Range<Anchor>,
|
||||||
|
block_ids: HashSet<BlockId>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
struct ClipboardSelection {
|
struct ClipboardSelection {
|
||||||
len: usize,
|
len: usize,
|
||||||
|
@ -423,6 +431,7 @@ impl Editor {
|
||||||
add_selections_state: None,
|
add_selections_state: None,
|
||||||
autoclose_stack: Default::default(),
|
autoclose_stack: Default::default(),
|
||||||
select_larger_syntax_node_stack: Vec::new(),
|
select_larger_syntax_node_stack: Vec::new(),
|
||||||
|
active_diagnostics: None,
|
||||||
build_settings,
|
build_settings,
|
||||||
scroll_position: Vector2F::zero(),
|
scroll_position: Vector2F::zero(),
|
||||||
scroll_top_anchor: Anchor::min(),
|
scroll_top_anchor: Anchor::min(),
|
||||||
|
@ -2205,36 +2214,93 @@ impl Editor {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn show_next_diagnostic(&mut self, _: &ShowNextDiagnostic, cx: &mut ViewContext<Self>) {
|
pub fn show_next_diagnostic(&mut self, _: &ShowNextDiagnostic, cx: &mut ViewContext<Self>) {
|
||||||
let selection = self.newest_selection(cx);
|
let selection = self.newest_selection::<usize>(cx);
|
||||||
let buffer = self.buffer.read(cx.as_ref());
|
let buffer = self.buffer.read(cx.as_ref());
|
||||||
let diagnostic_group_id = dbg!(buffer
|
let active_primary_range = self.active_diagnostics.as_ref().map(|active_diagnostics| {
|
||||||
.diagnostics_in_range::<_, usize>(selection.head()..buffer.len())
|
active_diagnostics
|
||||||
.filter(|(_, diagnostic)| diagnostic.is_primary)
|
.primary_range
|
||||||
.next())
|
.to_offset(buffer)
|
||||||
.map(|(_, diagnostic)| diagnostic.group_id);
|
.to_inclusive()
|
||||||
|
});
|
||||||
|
let mut search_start = if let Some(active_primary_range) = active_primary_range.as_ref() {
|
||||||
|
if active_primary_range.contains(&selection.head()) {
|
||||||
|
*active_primary_range.end()
|
||||||
|
} else {
|
||||||
|
selection.head()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
selection.head()
|
||||||
|
};
|
||||||
|
|
||||||
if let Some(group_id) = diagnostic_group_id {
|
loop {
|
||||||
self.display_map.update(cx, |display_map, cx| {
|
let next_group = buffer
|
||||||
let buffer = self.buffer.read(cx);
|
.diagnostics_in_range::<_, usize>(search_start..buffer.len())
|
||||||
let diagnostic_group = buffer
|
.filter(|(_, diagnostic)| diagnostic.is_primary)
|
||||||
.diagnostic_group::<Point>(group_id)
|
.skip_while(|(range, _)| {
|
||||||
.map(|(range, diagnostic)| (range, diagnostic.message.clone()))
|
Some(range.end) == active_primary_range.as_ref().map(|r| *r.end())
|
||||||
.collect::<Vec<_>>();
|
})
|
||||||
|
.next()
|
||||||
|
.map(|(range, diagnostic)| (range, diagnostic.group_id));
|
||||||
|
|
||||||
dbg!(group_id, &diagnostic_group);
|
if let Some((primary_range, group_id)) = next_group {
|
||||||
|
self.dismiss_diagnostics(cx);
|
||||||
|
self.active_diagnostics = self.display_map.update(cx, |display_map, cx| {
|
||||||
|
let buffer = self.buffer.read(cx);
|
||||||
|
let diagnostic_group = buffer
|
||||||
|
.diagnostic_group::<Point>(group_id)
|
||||||
|
.map(|(range, diagnostic)| (range, diagnostic.message.clone()))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let primary_range = buffer.anchor_after(primary_range.start)
|
||||||
|
..buffer.anchor_before(primary_range.end);
|
||||||
|
|
||||||
display_map.insert_blocks(
|
let block_ids = display_map
|
||||||
diagnostic_group
|
.insert_blocks(
|
||||||
.iter()
|
diagnostic_group
|
||||||
.map(|(range, message)| BlockProperties {
|
.iter()
|
||||||
position: range.start,
|
.map(|(range, message)| BlockProperties {
|
||||||
text: message.as_str(),
|
position: range.start,
|
||||||
runs: vec![],
|
text: message.as_str(),
|
||||||
disposition: BlockDisposition::Above,
|
runs: vec![],
|
||||||
}),
|
disposition: BlockDisposition::Above,
|
||||||
|
}),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
.into_iter()
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Some(ActiveDiagnosticGroup {
|
||||||
|
primary_range,
|
||||||
|
block_ids,
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
self.update_selections(
|
||||||
|
vec![Selection {
|
||||||
|
id: selection.id,
|
||||||
|
start: primary_range.start,
|
||||||
|
end: primary_range.start,
|
||||||
|
reversed: false,
|
||||||
|
goal: SelectionGoal::None,
|
||||||
|
}],
|
||||||
|
true,
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
|
break;
|
||||||
|
} else if search_start == 0 {
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
// Cycle around to the start of the buffer.
|
||||||
|
search_start = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dismiss_diagnostics(&mut self, cx: &mut ViewContext<Self>) {
|
||||||
|
if let Some(active_diagnostic_group) = self.active_diagnostics.take() {
|
||||||
|
self.display_map.update(cx, |display_map, cx| {
|
||||||
|
display_map.remove_blocks(active_diagnostic_group.block_ids, cx);
|
||||||
});
|
});
|
||||||
|
cx.notify();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -816,7 +816,7 @@ impl Buffer {
|
||||||
|
|
||||||
pub fn diagnostics_in_range<'a, T, O>(
|
pub fn diagnostics_in_range<'a, T, O>(
|
||||||
&'a self,
|
&'a self,
|
||||||
range: Range<T>,
|
search_range: Range<T>,
|
||||||
) -> impl Iterator<Item = (Range<O>, &Diagnostic)> + 'a
|
) -> impl Iterator<Item = (Range<O>, &Diagnostic)> + 'a
|
||||||
where
|
where
|
||||||
T: 'a + ToOffset,
|
T: 'a + ToOffset,
|
||||||
|
@ -824,7 +824,7 @@ impl Buffer {
|
||||||
{
|
{
|
||||||
let content = self.content();
|
let content = self.content();
|
||||||
self.diagnostics
|
self.diagnostics
|
||||||
.intersecting_ranges(range, content, true)
|
.intersecting_ranges(search_range, content, true)
|
||||||
.map(move |(_, range, diagnostic)| (range, diagnostic))
|
.map(move |(_, range, diagnostic)| (range, diagnostic))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue