Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make Merge behave consistently #4042

Closed
175 changes: 38 additions & 137 deletions helix-core/src/syntax.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,14 @@ use crate::{
auto_pairs::AutoPairs,
chars::char_is_line_ending,
diagnostic::Severity,
graphemes::ensure_grapheme_boundary_next_byte,
regex::Regex,
transaction::{ChangeSet, Operation},
Rope, RopeSlice, Tendril,
};
pub use overlay::{
monotonic_overlay, overlapping_overlay, MonotonicOverlay, OverlappingOverlay, Span,
};

use arc_swap::{ArcSwap, Guard};
use slotmap::{DefaultKey as LayerId, HopSlotMap};
Expand All @@ -25,6 +29,8 @@ use serde::{Deserialize, Serialize};

use helix_loader::grammar::{get_language, load_runtime_file};

mod overlay;

fn deserialize_regex<'de, D>(deserializer: D) -> Result<Option<Regex>, D::Error>
where
D: serde::Deserializer<'de>,
Expand Down Expand Up @@ -885,7 +891,7 @@ impl Syntax {
source: RopeSlice<'a>,
range: Option<std::ops::Range<usize>>,
cancellation_flag: Option<&'a AtomicUsize>,
) -> impl Iterator<Item = Result<HighlightEvent, Error>> + 'a {
) -> HighlightIter<'a> {
let mut layers = self
.layers
.iter()
Expand Down Expand Up @@ -1142,7 +1148,7 @@ pub enum Error {
}

/// Represents a single step in rendering a syntax-highlighted document.
#[derive(Copy, Clone, Debug)]
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum HighlightEvent {
Source { start: usize, end: usize },
HighlightStart(Highlight),
Expand Down Expand Up @@ -1184,7 +1190,36 @@ struct LocalScope<'a> {
}

#[derive(Debug)]
struct HighlightIter<'a> {
pub struct CharacterHighlightIter<'a>(HighlightIter<'a>);

impl Iterator for CharacterHighlightIter<'_> {
type Item = Result<HighlightEvent, Error>;

fn next(&mut self) -> Option<Self::Item> {
let res = match self.0.next()? {
// TODO: use byte slices directly
// convert byte offsets to char offset
Ok(HighlightEvent::Source { start, end }) => {
let text = self.0.source;
let start = text.byte_to_char(ensure_grapheme_boundary_next_byte(text, start));
let end = text.byte_to_char(ensure_grapheme_boundary_next_byte(text, end));
Ok(HighlightEvent::Source { start, end })
}
event => event,
};

Some(res)
}
}

impl<'a> HighlightIter<'a> {
pub fn to_chars(self) -> CharacterHighlightIter<'a> {
CharacterHighlightIter(self)
}
}

#[derive(Debug)]
pub struct HighlightIter<'a> {
source: RopeSlice<'a>,
byte_offset: usize,
cancellation_flag: Option<&'a AtomicUsize>,
Expand Down Expand Up @@ -1859,140 +1894,6 @@ fn injection_for_match<'a>(
(language_name, content_node, included_children)
}

pub struct Merge<I> {
iter: I,
spans: Box<dyn Iterator<Item = (usize, std::ops::Range<usize>)>>,

next_event: Option<HighlightEvent>,
next_span: Option<(usize, std::ops::Range<usize>)>,

queue: Vec<HighlightEvent>,
}

/// Merge a list of spans into the highlight event stream.
pub fn merge<I: Iterator<Item = HighlightEvent>>(
iter: I,
spans: Vec<(usize, std::ops::Range<usize>)>,
) -> Merge<I> {
let spans = Box::new(spans.into_iter());
let mut merge = Merge {
iter,
spans,
next_event: None,
next_span: None,
queue: Vec::new(),
};
merge.next_event = merge.iter.next();
merge.next_span = merge.spans.next();
merge
}

impl<I: Iterator<Item = HighlightEvent>> Iterator for Merge<I> {
type Item = HighlightEvent;
fn next(&mut self) -> Option<Self::Item> {
use HighlightEvent::*;
if let Some(event) = self.queue.pop() {
return Some(event);
}

loop {
match (self.next_event, &self.next_span) {
// this happens when range is partially or fully offscreen
(Some(Source { start, .. }), Some((span, range))) if start > range.start => {
if start > range.end {
self.next_span = self.spans.next();
} else {
self.next_span = Some((*span, start..range.end));
};
}
_ => break,
}
}

match (self.next_event, &self.next_span) {
(Some(HighlightStart(i)), _) => {
self.next_event = self.iter.next();
Some(HighlightStart(i))
}
(Some(HighlightEnd), _) => {
self.next_event = self.iter.next();
Some(HighlightEnd)
}
(Some(Source { start, end }), Some((_, range))) if start < range.start => {
let intersect = range.start.min(end);
let event = Source {
start,
end: intersect,
};

if end == intersect {
// the event is complete
self.next_event = self.iter.next();
} else {
// subslice the event
self.next_event = Some(Source {
start: intersect,
end,
});
};

Some(event)
}
(Some(Source { start, end }), Some((span, range))) if start == range.start => {
let intersect = range.end.min(end);
let event = HighlightStart(Highlight(*span));

// enqueue in reverse order
self.queue.push(HighlightEnd);
self.queue.push(Source {
start,
end: intersect,
});

if end == intersect {
// the event is complete
self.next_event = self.iter.next();
} else {
// subslice the event
self.next_event = Some(Source {
start: intersect,
end,
});
};

if intersect == range.end {
self.next_span = self.spans.next();
} else {
self.next_span = Some((*span, intersect..range.end));
}

Some(event)
}
(Some(event), None) => {
self.next_event = self.iter.next();
Some(event)
}
// Can happen if cursor at EOF and/or diagnostic reaches past the end.
// We need to actually emit events for the cursor-at-EOF situation,
// even though the range is past the end of the text. This needs to be
// handled appropriately by the drawing code by not assuming that
// all `Source` events point to valid indices in the rope.
(None, Some((span, range))) => {
let event = HighlightStart(Highlight(*span));
self.queue.push(HighlightEnd);
self.queue.push(Source {
start: range.start,
end: range.end,
});
self.next_span = self.spans.next();
Some(event)
}
(None, None) => None,
e => unreachable!("{:?}", e),
}
}
}

#[cfg(test)]
mod test {
use super::*;
Expand Down
Loading