From a142391011738720fb08b8ce81aaafcfe892a193 Mon Sep 17 00:00:00 2001 From: Michael Davis Date: Sat, 17 Sep 2022 19:35:30 -0500 Subject: [PATCH] Split out spans into a new module The span iterators and tests took up a significant portion of the syntax module but were fairly self-contained. In addition, the Span type was an informal (usize, std::ops::Range) which was a bit tricky to deal with: * the type was long to write out for all producers/consumers * std::ops::Range does not implement Copy Plus we don't end up using any Range methods except its Ord/PartialOrd implementations. Given its first-class usage for diagnostics and selections, it seems appropriate to separate it into its own struct type and module. --- helix-core/src/syntax.rs | 447 +---------------------------- helix-core/src/syntax/span.rs | 511 ++++++++++++++++++++++++++++++++++ helix-term/src/ui/editor.rs | 68 +++-- helix-term/src/ui/lsp.rs | 9 +- helix-term/src/ui/markdown.rs | 4 +- 5 files changed, 564 insertions(+), 475 deletions(-) create mode 100644 helix-core/src/syntax/span.rs diff --git a/helix-core/src/syntax.rs b/helix-core/src/syntax.rs index d86fa629fd84..fe7dc0433edb 100644 --- a/helix-core/src/syntax.rs +++ b/helix-core/src/syntax.rs @@ -1,3 +1,5 @@ +pub mod span; + use crate::{ auto_pairs::AutoPairs, chars::char_is_line_ending, @@ -2206,226 +2208,6 @@ impl, R: Iterator> Ite } } -/// A pair of highlight index and range. -/// -/// This corresponds to a HighlightStart, Source, and HighlightEnd. -type Span = (usize, std::ops::Range); - -struct SpanIter { - spans: Vec, - index: usize, - event_queue: VecDeque, - range_ends: Vec, - cursor: usize, -} - -/// Creates an iterator of [HighlightEvent]s from a [Vec] of spans - pairs -/// of the highlight and range. -/// -/// Spans may overlap. In the produced [HighlightEvent] iterator, all -/// `HighlightEvent::Source` events will be sorted by `start` and will not -/// overlap. -/// -/// `spans` is assumed to be sorted by range in lexiographical order. -pub fn span_iter( - spans: Vec<(usize, std::ops::Range)>, -) -> impl Iterator { - SpanIter { - spans, - index: 0, - event_queue: VecDeque::new(), - range_ends: Vec::new(), - cursor: 0, - } -} - -impl Iterator for SpanIter { - type Item = HighlightEvent; - - fn next(&mut self) -> Option { - use HighlightEvent::*; - - // Emit any queued highlight events - if let Some(event) = self.event_queue.pop_front() { - return Some(event); - } - - if self.index == self.spans.len() { - // There are no more spans. Emit Sources and HighlightEnds for - // any ranges which have not been terminated yet. - for end in self.range_ends.drain(..) { - if self.cursor != end { - debug_assert!(self.cursor < end); - self.event_queue.push_back(Source { - start: self.cursor, - end, - }); - } - self.event_queue.push_back(HighlightEnd); - self.cursor = end; - } - return self.event_queue.pop_front(); - } - - let range = self.spans[self.index].1.clone(); - let mut subslice = None; - - self.range_ends.retain(|end| { - if range.start >= *end { - // The new range is past the end of this in-progress range. - // Complete the in-progress range by emitting a Source, - // if necessary, and a HighlightEnd and advance the cursor. - if self.cursor != *end { - debug_assert!(self.cursor < *end); - self.event_queue.push_back(Source { - start: self.cursor, - end: *end, - }); - } - self.event_queue.push_back(HighlightEnd); - self.cursor = *end; - false - } else if range.end > *end && subslice.is_none() { - // If the new range is longer than some in-progress range, - // we need to subslice this range and any ranges with the - // same start. `subslice` is set to the smallest `end` for - // which `range.start < end < range.end`. - subslice = Some(*end); - true - } else { - true - } - }); - - // Emit a Source event between consecutive HighlightStart events - if range.start != self.cursor && !self.range_ends.is_empty() { - debug_assert!(self.cursor < range.start); - self.event_queue.push_back(Source { - start: self.cursor, - end: range.start, - }); - } - - self.cursor = range.start; - - // Handle all spans that share this starting point. Either subslice - // or fully consume the span. - let mut i = self.index; - let mut subslices = 0; - loop { - match self.spans.get_mut(i) { - Some((highlight, range)) if range.start == self.cursor => { - self.event_queue - .push_back(HighlightStart(Highlight(*highlight))); - i += 1; - - match subslice { - Some(intersect) => { - // If this span needs to be subsliced, consume the - // left part of the subslice and leave the right. - self.range_ends.push(intersect); - range.start = intersect; - subslices += 1; - } - None => { - // If there is no subslice, consume the span. - self.range_ends.push(range.end); - self.index = i; - } - } - } - _ => break, - } - } - - // Ensure range-ends are sorted ascending. Ranges which start at the - // same point may be in descending order because of the assumed - // sort-order of input ranges. - self.range_ends.sort_unstable(); - - // When spans are subsliced, the span Vec may need to be re-sorted - // because the `range.start` may now be greater than some `range.start` - // later in the Vec. This is not a classic "sort": we take several - // shortcuts to improve the runtime so that the sort may be done in - // time linear to the cardinality of the span Vec. Practically speaking - // the runtime is even better since we only scan from `self.index` to - // the first element of the Vec with a `range.start` after this range. - if let Some(intersect) = subslice { - let mut after = None; - - // Find the index of the largest span smaller than the intersect point. - // `i` starts on the index after the last subsliced span. - loop { - match self.spans.get(i) { - Some((_, range)) if range.start < intersect => { - after = Some(i); - i += 1; - } - _ => break, - } - } - - // Rotate the subsliced spans so that they come after the spans that - // have smaller `range.start`s. - if let Some(after) = after { - self.spans[self.index..=after].rotate_left(subslices); - } - } - - self.event_queue.pop_front() - } -} - -struct FlatSpanIter { - iter: I, -} - -/// Converts a Vec of spans into an [Iterator] over [HighlightEvent]s -/// -/// This implementation does not resolve overlapping spans. Zero-width spans are -/// eliminated but otherwise the ranges are trusted to not overlap. -/// -/// This iterator has much less overhead than [span_iter] and is appropriate for -/// cases where the input spans are known to satisfy all of [merge]'s assumptions -/// and invariants, such as with selection highlights. -/// -/// # Panics -/// -/// Panics on debug builds when the input spans overlap or are not sorted. -pub fn flat_span_iter(spans: Vec) -> impl Iterator { - use HighlightEvent::*; - - // Consecutive items are sorted and non-overlapping - debug_assert!(spans.windows(2).all(|window| { - let a = &window[0]; - let b = &window[1]; - b.1.start >= a.1.end - })); - - FlatSpanIter { - iter: spans - .into_iter() - .filter(|(_h, r)| r.start != r.end) - .flat_map(|(h, r)| { - [ - HighlightStart(Highlight(h)), - Source { - start: r.start, - end: r.end, - }, - HighlightEnd, - ] - }), - } -} - -impl> Iterator for FlatSpanIter { - type Item = HighlightEvent; - fn next(&mut self) -> Option { - self.iter.next() - } -} - #[cfg(test)] mod test { use super::*; @@ -2607,231 +2389,6 @@ mod test { assert!(results.is_err()); } - #[test] - fn test_non_overlapping_span_iter_events() { - use HighlightEvent::*; - let input = vec![(1, 0..5), (2, 6..10)]; - let output: Vec<_> = span_iter(input).collect(); - assert_eq!( - output, - &[ - HighlightStart(Highlight(1)), - Source { start: 0, end: 5 }, - HighlightEnd, // ends 1 - HighlightStart(Highlight(2)), - Source { start: 6, end: 10 }, - HighlightEnd, // ends 2 - ], - ); - } - - #[test] - fn test_simple_overlapping_span_iter_events() { - use HighlightEvent::*; - - let input = vec![(1, 0..10), (2, 3..6)]; - let output: Vec<_> = span_iter(input).collect(); - assert_eq!( - output, - &[ - HighlightStart(Highlight(1)), - Source { start: 0, end: 3 }, - HighlightStart(Highlight(2)), - Source { start: 3, end: 6 }, - HighlightEnd, // ends 2 - Source { start: 6, end: 10 }, - HighlightEnd, // ends 1 - ], - ); - } - - #[test] - fn test_many_overlapping_span_iter_events() { - use HighlightEvent::*; - - /* - Input: - - 5 - |-------| - 4 - |----------| - 3 - |---------------------------| - 2 - |---------------| - 1 - |---------------------------------------| - - |---|---|---|---|---|---|---|---|---|---|---|---|---|---|---| - 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 - */ - let input = vec![(1, 0..10), (2, 1..5), (3, 6..13), (4, 12..15), (5, 13..15)]; - - /* - Output: - - 2 3 4 5 - |---------------| |---------------| |---|-------| - - 1 3 4 - |---------------------------------------|-----------|-------| - - |---|---|---|---|---|---|---|---|---|---|---|---|---|---|---| - 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 - */ - let output: Vec<_> = span_iter(input).collect(); - - assert_eq!( - output, - &[ - HighlightStart(Highlight(1)), - Source { start: 0, end: 1 }, - HighlightStart(Highlight(2)), - Source { start: 1, end: 5 }, - HighlightEnd, // ends 2 - Source { start: 5, end: 6 }, - HighlightStart(Highlight(3)), - Source { start: 6, end: 10 }, - HighlightEnd, // ends 3 - HighlightEnd, // ends 1 - HighlightStart(Highlight(3)), - Source { start: 10, end: 12 }, - HighlightStart(Highlight(4)), - Source { start: 12, end: 13 }, - HighlightEnd, // ends 4 - HighlightEnd, // ends 3 - HighlightStart(Highlight(4)), - HighlightStart(Highlight(5)), - Source { start: 13, end: 15 }, - HighlightEnd, // ends 5 - HighlightEnd, // ends 4 - ], - ); - } - - #[test] - fn test_multiple_duplicate_overlapping_span_iter_events() { - use HighlightEvent::*; - // This is based an a realistic case from rust-analyzer - // diagnostics. Spans may both overlap and duplicate one - // another at varying diagnostic levels. - - /* - Input: - - 5 - |---------------| - 3,4 - |-----------------------| - 1,2 - |-----------------------| - - |---|---|---|---|---|---|---|---|---|---| - 0 1 2 3 4 5 6 7 8 9 10 - */ - - let input = vec![(1, 0..6), (2, 0..6), (3, 4..10), (4, 4..10), (5, 4..8)]; - - /* - Output: - - 1,2 1,2,3,4,5 3,4,5 3,4 - |---------------|-------|-------|-------| - - |---|---|---|---|---|---|---|---|---|---| - 0 1 2 3 4 5 6 7 8 9 10 - */ - let output: Vec<_> = span_iter(input).collect(); - assert_eq!( - output, - &[ - HighlightStart(Highlight(1)), - HighlightStart(Highlight(2)), - Source { start: 0, end: 4 }, - HighlightStart(Highlight(3)), - HighlightStart(Highlight(4)), - HighlightStart(Highlight(5)), - Source { start: 4, end: 6 }, - HighlightEnd, // ends 5 - HighlightEnd, // ends 4 - HighlightEnd, // ends 3 - HighlightEnd, // ends 2 - HighlightEnd, // ends 1 - HighlightStart(Highlight(3)), - HighlightStart(Highlight(4)), - HighlightStart(Highlight(5)), - Source { start: 6, end: 8 }, - HighlightEnd, // ends 5 - Source { start: 8, end: 10 }, - HighlightEnd, // ends 4 - HighlightEnd, // ends 3 - ], - ); - } - - #[test] - fn test_span_iter_events_where_ranges_must_be_sorted() { - use HighlightEvent::*; - // This case needs the span Vec to be re-sorted because - // span 3 is subsliced to 9..10, putting it after span 4 - // in the ordering. - - /* - Input: - - 4 5 - |---|---| - 2 3 - |---------------| |---------------| - 1 - |-----------------------------------| - - |---|---|---|---|---|---|---|---|---|---| - 0 1 2 3 4 5 6 7 8 9 10 - */ - let input = vec![(1, 0..9), (2, 1..5), (3, 6..10), (4, 7..8), (5, 8..9)]; - - /* - Output: - - 4 5 - |---|---| - 2 3 - |---------------| |-----------| - 1 3 - |-----------------------------------|---| - - |---|---|---|---|---|---|---|---|---|---| - 0 1 2 3 4 5 6 7 8 9 10 - */ - let output: Vec<_> = span_iter(input).collect(); - assert_eq!( - output, - &[ - HighlightStart(Highlight(1)), - Source { start: 0, end: 1 }, - HighlightStart(Highlight(2)), - Source { start: 1, end: 5 }, - HighlightEnd, // ends 2 - Source { start: 5, end: 6 }, - HighlightStart(Highlight(3)), - Source { start: 6, end: 7 }, - HighlightStart(Highlight(4)), - Source { start: 7, end: 8 }, - HighlightEnd, // ends 4 - HighlightStart(Highlight(5)), - Source { start: 8, end: 9 }, - HighlightEnd, // ends 5 - HighlightEnd, // ends 3 - HighlightEnd, // ends 1 - HighlightStart(Highlight(3)), - Source { start: 9, end: 10 }, - HighlightEnd, // ends 3 - ], - ); - } - #[test] fn test_sample_highlight_event_stream_merge() { use HighlightEvent::*; diff --git a/helix-core/src/syntax/span.rs b/helix-core/src/syntax/span.rs new file mode 100644 index 000000000000..d94826021bfb --- /dev/null +++ b/helix-core/src/syntax/span.rs @@ -0,0 +1,511 @@ +use std::collections::VecDeque; + +use crate::syntax::Highlight; + +use super::HighlightEvent; + +/// A range highlighted with a given scope. +/// +/// Spans are a simplifer data structure for describing a highlight range +/// than [super::HighlightEvent]s. +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub struct Span { + pub scope: usize, + pub start: usize, + pub end: usize, +} + +impl Ord for Span { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + // Sort by range: ascending by start and then ascending by end for ties. + if self.start == other.start { + self.end.cmp(&other.end) + } else { + self.start.cmp(&other.start) + } + } +} + +impl PartialOrd for Span { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +struct SpanIter { + spans: Vec, + index: usize, + event_queue: VecDeque, + range_ends: Vec, + cursor: usize, +} + +/// Creates an iterator of [HighlightEvent]s from a [Vec] of [Span]s. +/// +/// Spans may overlap. In the produced [HighlightEvent] iterator, all +/// `HighlightEvent::Source` events will be sorted by `start` and will not +/// overlap. The iterator produced by this function satisfies all invariants +/// and assumptions for [super::merge] +/// +/// `spans` is assumed to be sorted by `range.start` ascending and then by +/// `range.end` descending for any ties. +/// +/// # Panics +/// +/// Panics on debug builds when the input spans overlap or are not sorted. +pub fn span_iter(spans: Vec) -> impl Iterator { + // Assert that `spans` is sorted by `range.start` ascending and + // `range.end` descending. + debug_assert!(spans.windows(2).all(|window| window[0] <= window[1])); + + SpanIter { + spans, + index: 0, + event_queue: VecDeque::new(), + range_ends: Vec::new(), + cursor: 0, + } +} + +impl Iterator for SpanIter { + type Item = HighlightEvent; + + fn next(&mut self) -> Option { + use HighlightEvent::*; + + // Emit any queued highlight events + if let Some(event) = self.event_queue.pop_front() { + return Some(event); + } + + if self.index == self.spans.len() { + // There are no more spans. Emit Sources and HighlightEnds for + // any ranges which have not been terminated yet. + for end in self.range_ends.drain(..) { + if self.cursor != end { + debug_assert!(self.cursor < end); + self.event_queue.push_back(Source { + start: self.cursor, + end, + }); + } + self.event_queue.push_back(HighlightEnd); + self.cursor = end; + } + return self.event_queue.pop_front(); + } + + let span = self.spans[self.index]; + let mut subslice = None; + + self.range_ends.retain(|end| { + if span.start >= *end { + // The new range is past the end of this in-progress range. + // Complete the in-progress range by emitting a Source, + // if necessary, and a HighlightEnd and advance the cursor. + if self.cursor != *end { + debug_assert!(self.cursor < *end); + self.event_queue.push_back(Source { + start: self.cursor, + end: *end, + }); + } + self.event_queue.push_back(HighlightEnd); + self.cursor = *end; + false + } else if span.end > *end && subslice.is_none() { + // If the new range is longer than some in-progress range, + // we need to subslice this range and any ranges with the + // same start. `subslice` is set to the smallest `end` for + // which `range.start < end < range.end`. + subslice = Some(*end); + true + } else { + true + } + }); + + // Emit a Source event between consecutive HighlightStart events + if span.start != self.cursor && !self.range_ends.is_empty() { + debug_assert!(self.cursor < span.start); + self.event_queue.push_back(Source { + start: self.cursor, + end: span.start, + }); + } + + self.cursor = span.start; + + // Handle all spans that share this starting point. Either subslice + // or fully consume the span. + let mut i = self.index; + let mut subslices = 0; + loop { + match self.spans.get_mut(i) { + Some(span) if span.start == self.cursor => { + self.event_queue + .push_back(HighlightStart(Highlight(span.scope))); + i += 1; + + match subslice { + Some(intersect) => { + // If this span needs to be subsliced, consume the + // left part of the subslice and leave the right. + self.range_ends.push(intersect); + span.start = intersect; + subslices += 1; + } + None => { + // If there is no subslice, consume the span. + self.range_ends.push(span.end); + self.index = i; + } + } + } + _ => break, + } + } + + // Ensure range-ends are sorted ascending. Ranges which start at the + // same point may be in descending order because of the assumed + // sort-order of input ranges. + self.range_ends.sort_unstable(); + + // When spans are subsliced, the span Vec may need to be re-sorted + // because the `range.start` may now be greater than some `range.start` + // later in the Vec. This is not a classic "sort": we take several + // shortcuts to improve the runtime so that the sort may be done in + // time linear to the cardinality of the span Vec. Practically speaking + // the runtime is even better since we only scan from `self.index` to + // the first element of the Vec with a `range.start` after this range. + if let Some(intersect) = subslice { + let mut after = None; + + // Find the index of the largest span smaller than the intersect point. + // `i` starts on the index after the last subsliced span. + loop { + match self.spans.get(i) { + Some(span) if span.start < intersect => { + after = Some(i); + i += 1; + } + _ => break, + } + } + + // Rotate the subsliced spans so that they come after the spans that + // have smaller `range.start`s. + if let Some(after) = after { + self.spans[self.index..=after].rotate_left(subslices); + } + } + + self.event_queue.pop_front() + } +} + +struct FlatSpanIter { + iter: I, +} + +/// Converts a Vec of spans into an [Iterator] over [HighlightEvent]s +/// +/// This implementation does not resolve overlapping spans. Zero-width spans are +/// eliminated but otherwise the ranges are trusted to not overlap. +/// +/// This iterator has much less overhead than [span_iter] and is appropriate for +/// cases where the input spans are known to satisfy all of [super::merge]'s +/// assumptions and invariants, such as with selection highlights. +/// +/// # Panics +/// +/// Panics on debug builds when the input spans overlap or are not sorted. +pub fn flat_span_iter(spans: Vec) -> impl Iterator { + use HighlightEvent::*; + + // Consecutive items are sorted and non-overlapping + debug_assert!(spans + .windows(2) + .all(|window| window[1].start >= window[0].end)); + + FlatSpanIter { + iter: spans + .into_iter() + .filter(|span| span.start != span.end) + .flat_map(|span| { + [ + HighlightStart(Highlight(span.scope)), + Source { + start: span.start, + end: span.end, + }, + HighlightEnd, + ] + }), + } +} + +impl> Iterator for FlatSpanIter { + type Item = HighlightEvent; + fn next(&mut self) -> Option { + self.iter.next() + } +} + +#[cfg(test)] +mod test { + use super::*; + + macro_rules! span { + ($scope:literal, $range:expr) => { + Span { + scope: $scope, + start: $range.start, + end: $range.end, + } + }; + } + + #[test] + fn test_non_overlapping_span_iter_events() { + use HighlightEvent::*; + let input = vec![span!(1, 0..5), span!(2, 6..10)]; + let output: Vec<_> = span_iter(input).collect(); + assert_eq!( + output, + &[ + HighlightStart(Highlight(1)), + Source { start: 0, end: 5 }, + HighlightEnd, // ends 1 + HighlightStart(Highlight(2)), + Source { start: 6, end: 10 }, + HighlightEnd, // ends 2 + ], + ); + } + + #[test] + fn test_simple_overlapping_span_iter_events() { + use HighlightEvent::*; + + let input = vec![span!(1, 0..10), span!(2, 3..6)]; + let output: Vec<_> = span_iter(input).collect(); + assert_eq!( + output, + &[ + HighlightStart(Highlight(1)), + Source { start: 0, end: 3 }, + HighlightStart(Highlight(2)), + Source { start: 3, end: 6 }, + HighlightEnd, // ends 2 + Source { start: 6, end: 10 }, + HighlightEnd, // ends 1 + ], + ); + } + + #[test] + fn test_many_overlapping_span_iter_events() { + use HighlightEvent::*; + + /* + Input: + + 5 + |-------| + 4 + |----------| + 3 + |---------------------------| + 2 + |---------------| + 1 + |---------------------------------------| + + |---|---|---|---|---|---|---|---|---|---|---|---|---|---|---| + 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 + */ + let input = vec![ + span!(1, 0..10), + span!(2, 1..5), + span!(3, 6..13), + span!(4, 12..15), + span!(5, 13..15), + ]; + + /* + Output: + + 2 3 4 5 + |---------------| |---------------| |---|-------| + + 1 3 4 + |---------------------------------------|-----------|-------| + + |---|---|---|---|---|---|---|---|---|---|---|---|---|---|---| + 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 + */ + let output: Vec<_> = span_iter(input).collect(); + + assert_eq!( + output, + &[ + HighlightStart(Highlight(1)), + Source { start: 0, end: 1 }, + HighlightStart(Highlight(2)), + Source { start: 1, end: 5 }, + HighlightEnd, // ends 2 + Source { start: 5, end: 6 }, + HighlightStart(Highlight(3)), + Source { start: 6, end: 10 }, + HighlightEnd, // ends 3 + HighlightEnd, // ends 1 + HighlightStart(Highlight(3)), + Source { start: 10, end: 12 }, + HighlightStart(Highlight(4)), + Source { start: 12, end: 13 }, + HighlightEnd, // ends 4 + HighlightEnd, // ends 3 + HighlightStart(Highlight(4)), + HighlightStart(Highlight(5)), + Source { start: 13, end: 15 }, + HighlightEnd, // ends 5 + HighlightEnd, // ends 4 + ], + ); + } + + #[test] + fn test_multiple_duplicate_overlapping_span_iter_events() { + use HighlightEvent::*; + // This is based an a realistic case from rust-analyzer + // diagnostics. Spans may both overlap and duplicate one + // another at varying diagnostic levels. + + /* + Input: + + 4,5 + |-----------------------| + 3 + |---------------| + 1,2 + |-----------------------| + + |---|---|---|---|---|---|---|---|---|---| + 0 1 2 3 4 5 6 7 8 9 10 + */ + + let input = vec![ + span!(1, 0..6), + span!(2, 0..6), + span!(3, 4..8), + span!(4, 4..10), + span!(5, 4..10), + ]; + + /* + Output: + + 1,2 1..5 3..5 4,5 + |---------------|-------|-------|-------| + + |---|---|---|---|---|---|---|---|---|---| + 0 1 2 3 4 5 6 7 8 9 10 + */ + let output: Vec<_> = span_iter(input).collect(); + assert_eq!( + output, + &[ + HighlightStart(Highlight(1)), + HighlightStart(Highlight(2)), + Source { start: 0, end: 4 }, + HighlightStart(Highlight(3)), + HighlightStart(Highlight(4)), + HighlightStart(Highlight(5)), + Source { start: 4, end: 6 }, + HighlightEnd, // ends 5 + HighlightEnd, // ends 4 + HighlightEnd, // ends 3 + HighlightEnd, // ends 2 + HighlightEnd, // ends 1 + HighlightStart(Highlight(3)), + HighlightStart(Highlight(4)), + HighlightStart(Highlight(5)), + Source { start: 6, end: 8 }, + HighlightEnd, // ends 5 + Source { start: 8, end: 10 }, + HighlightEnd, // ends 4 + HighlightEnd, // ends 3 + ], + ); + } + + #[test] + fn test_span_iter_events_where_ranges_must_be_sorted() { + use HighlightEvent::*; + // This case needs the span Vec to be re-sorted because + // span 3 is subsliced to 9..10, putting it after span 4 and 5 + // in the ordering. + + /* + Input: + + 4 5 + |---|---| + 2 3 + |---------------| |---------------| + 1 + |-----------------------------------| + + |---|---|---|---|---|---|---|---|---|---| + 0 1 2 3 4 5 6 7 8 9 10 + */ + let input = vec![ + span!(1, 0..9), + span!(2, 1..5), + span!(3, 6..10), + span!(4, 7..8), + span!(5, 8..9), + ]; + + /* + Output: + + 4 5 + |---|---| + 2 3 + |---------------| |-----------| + 1 3 + |-----------------------------------|---| + + |---|---|---|---|---|---|---|---|---|---| + 0 1 2 3 4 5 6 7 8 9 10 + */ + let output: Vec<_> = span_iter(input).collect(); + assert_eq!( + output, + &[ + HighlightStart(Highlight(1)), + Source { start: 0, end: 1 }, + HighlightStart(Highlight(2)), + Source { start: 1, end: 5 }, + HighlightEnd, // ends 2 + Source { start: 5, end: 6 }, + HighlightStart(Highlight(3)), + Source { start: 6, end: 7 }, + HighlightStart(Highlight(4)), + Source { start: 7, end: 8 }, + HighlightEnd, // ends 4 + HighlightStart(Highlight(5)), + Source { start: 8, end: 9 }, + HighlightEnd, // ends 5 + HighlightEnd, // ends 3 + HighlightEnd, // ends 1 + HighlightStart(Highlight(3)), + Source { start: 9, end: 10 }, + HighlightEnd, // ends 3 + ], + ); + } +} diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 778b080d2b29..471c40b0f25f 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -11,7 +11,7 @@ use helix_core::{ ensure_grapheme_boundary_next_byte, next_grapheme_boundary, prev_grapheme_boundary, }, movement::Direction, - syntax::{self, HighlightEvent}, + syntax::{self, span::Span, HighlightEvent}, unicode::width::UnicodeWidthStr, LineEnding, Position, Range, Selection, Transaction, }; @@ -120,20 +120,22 @@ impl EditorView { let highlights = Self::doc_syntax_highlights(doc, view.offset, inner.height, theme); let highlights = syntax::merge( highlights, - Box::new(syntax::span_iter(Self::doc_diagnostics_highlights( + Box::new(syntax::span::span_iter(Self::doc_diagnostics_highlights( doc, theme, ))), ); let highlights: Box> = if is_focused { Box::new(syntax::merge( highlights, - Box::new(syntax::flat_span_iter(Self::doc_selection_highlights( - editor.mode(), - doc, - view, - theme, - &editor.config().cursor_shape, - ))), + Box::new(syntax::span::flat_span_iter( + Self::doc_selection_highlights( + editor.mode(), + doc, + view, + theme, + &editor.config().cursor_shape, + ), + )), )) } else { Box::new(highlights) @@ -264,10 +266,7 @@ impl EditorView { } /// Get highlight spans for document diagnostics - pub fn doc_diagnostics_highlights( - doc: &Document, - theme: &Theme, - ) -> Vec<(usize, std::ops::Range)> { + pub fn doc_diagnostics_highlights(doc: &Document, theme: &Theme) -> Vec { use helix_core::diagnostic::Severity; let get_scope_of = |scope| { theme @@ -298,10 +297,11 @@ impl EditorView { Some(Severity::Error) => error, _ => r#default, }; - ( - diagnostic_scope, - diagnostic.range.start..diagnostic.range.end, - ) + Span { + scope: diagnostic_scope, + start: diagnostic.range.start, + end: diagnostic.range.end, + } }) .collect() } @@ -313,7 +313,7 @@ impl EditorView { view: &View, theme: &Theme, cursor_shape_config: &CursorShapeConfig, - ) -> Vec<(usize, std::ops::Range)> { + ) -> Vec { let text = doc.text().slice(..); let selection = doc.selection(view.id); let primary_idx = selection.primary_index(); @@ -342,7 +342,7 @@ impl EditorView { .find_scope_index("ui.selection.primary") .unwrap_or(selection_scope); - let mut spans: Vec<(usize, std::ops::Range)> = Vec::new(); + let mut spans: Vec = Vec::new(); for (i, range) in selection.iter().enumerate() { let selection_is_primary = i == primary_idx; let (cursor_scope, selection_scope) = if selection_is_primary { @@ -359,7 +359,11 @@ impl EditorView { // underline cursor (eg. when a regex prompt has focus) then // the primary cursor will be invisible. This doesn't happen // with block cursors since we manually draw *all* cursors. - spans.push((cursor_scope, range.head..range.head + 1)); + spans.push(Span { + scope: cursor_scope, + start: range.head, + end: range.head + 1, + }); } continue; } @@ -368,17 +372,33 @@ impl EditorView { if range.head > range.anchor { // Standard case. let cursor_start = prev_grapheme_boundary(text, range.head); - spans.push((selection_scope, range.anchor..cursor_start)); + spans.push(Span { + scope: selection_scope, + start: range.anchor, + end: cursor_start, + }); if !selection_is_primary || cursor_is_block { - spans.push((cursor_scope, cursor_start..range.head)); + spans.push(Span { + scope: cursor_scope, + start: cursor_start, + end: range.head, + }); } } else { // Reverse case. let cursor_end = next_grapheme_boundary(text, range.head); if !selection_is_primary || cursor_is_block { - spans.push((cursor_scope, range.head..cursor_end)); + spans.push(Span { + scope: cursor_scope, + start: range.head, + end: cursor_end, + }); } - spans.push((selection_scope, cursor_end..range.anchor)); + spans.push(Span { + scope: selection_scope, + start: cursor_end, + end: range.anchor, + }); } } diff --git a/helix-term/src/ui/lsp.rs b/helix-term/src/ui/lsp.rs index f2854551ded0..18af75b2cd5d 100644 --- a/helix-term/src/ui/lsp.rs +++ b/helix-term/src/ui/lsp.rs @@ -52,10 +52,11 @@ impl Component for SignatureHelp { let margin = Margin::horizontal(1); let active_param_span = self.active_param_range.map(|(start, end)| { - vec![( - cx.editor.theme.find_scope_index("ui.selection").unwrap(), - start..end, - )] + vec![syntax::span::Span { + scope: cx.editor.theme.find_scope_index("ui.selection").unwrap(), + start, + end, + }] }); let sig_text = crate::ui::markdown::highlighted_code_block( diff --git a/helix-term/src/ui/markdown.rs b/helix-term/src/ui/markdown.rs index 4071fdb0f849..afc6b0647a5d 100644 --- a/helix-term/src/ui/markdown.rs +++ b/helix-term/src/ui/markdown.rs @@ -31,7 +31,7 @@ pub fn highlighted_code_block<'a>( language: &str, theme: Option<&Theme>, config_loader: Arc, - additional_highlight_spans: Option)>>, + additional_highlight_spans: Option>, ) -> Text<'a> { let mut spans = Vec::new(); let mut lines = Vec::new(); @@ -63,7 +63,7 @@ pub fn highlighted_code_block<'a>( if let Some(spans) = additional_highlight_spans { Box::new(helix_core::syntax::merge( highlight_iter, - Box::new(helix_core::syntax::span_iter(spans)), + Box::new(helix_core::syntax::span::span_iter(spans)), )) } else { Box::new(highlight_iter)