Support highlighting in blocks

Antonio Scandurra and Nathan Sobo created

Co-Authored-By: Nathan Sobo <nathan@zed.dev>

Change summary

crates/editor/src/display_map.rs           | 67 ++++++++++++-----------
crates/editor/src/display_map/block_map.rs | 19 +++---
crates/editor/src/display_map/fold_map.rs  | 23 ++++---
crates/editor/src/display_map/tab_map.rs   | 21 ++++---
crates/editor/src/display_map/wrap_map.rs  | 11 ++-
crates/editor/src/element.rs               |  8 +-
crates/editor/src/lib.rs                   | 10 ++-
crates/gpui/src/fonts.rs                   |  2 
crates/language/src/lib.rs                 | 29 ++++++---
crates/language/src/tests.rs               |  2 
10 files changed, 105 insertions(+), 87 deletions(-)

Detailed changes

crates/editor/src/display_map.rs 🔗

@@ -12,6 +12,7 @@ use language::{Anchor, Buffer, Point, ToOffset, ToPoint};
 use std::{collections::HashSet, ops::Range};
 use sum_tree::Bias;
 use tab_map::TabMap;
+use theme::SyntaxTheme;
 use wrap_map::WrapMap;
 
 pub use block_map::{BlockDisposition, BlockProperties, BufferRows, Chunks};
@@ -230,12 +231,16 @@ impl DisplayMapSnapshot {
 
     pub fn text_chunks(&self, display_row: u32) -> impl Iterator<Item = &str> {
         self.blocks_snapshot
-            .chunks(display_row..self.max_point().row() + 1, false)
+            .chunks(display_row..self.max_point().row() + 1, None)
             .map(|h| h.text)
     }
 
-    pub fn chunks(&mut self, display_rows: Range<u32>) -> block_map::Chunks {
-        self.blocks_snapshot.chunks(display_rows, true)
+    pub fn chunks<'a>(
+        &'a self,
+        display_rows: Range<u32>,
+        theme: Option<&'a SyntaxTheme>,
+    ) -> block_map::Chunks<'a> {
+        self.blocks_snapshot.chunks(display_rows, theme)
     }
 
     pub fn chars_at<'a>(&'a self, point: DisplayPoint) -> impl Iterator<Item = char> + 'a {
@@ -736,8 +741,8 @@ mod tests {
         .unindent();
 
         let theme = SyntaxTheme::new(vec![
-            ("mod.body".to_string(), Color::from_u32(0xff0000ff).into()),
-            ("fn.name".to_string(), Color::from_u32(0x00ff00ff).into()),
+            ("mod.body".to_string(), Color::red().into()),
+            ("fn.name".to_string(), Color::blue().into()),
         ]);
         let lang = Arc::new(
             Language::new(
@@ -776,19 +781,19 @@ mod tests {
             cx.update(|cx| chunks(0..5, &map, &theme, cx)),
             vec![
                 ("fn ".to_string(), None),
-                ("outer".to_string(), Some("fn.name")),
+                ("outer".to_string(), Some(Color::blue())),
                 ("() {}\n\nmod module ".to_string(), None),
-                ("{\n    fn ".to_string(), Some("mod.body")),
-                ("inner".to_string(), Some("fn.name")),
-                ("() {}\n}".to_string(), Some("mod.body")),
+                ("{\n    fn ".to_string(), Some(Color::red())),
+                ("inner".to_string(), Some(Color::blue())),
+                ("() {}\n}".to_string(), Some(Color::red())),
             ]
         );
         assert_eq!(
             cx.update(|cx| chunks(3..5, &map, &theme, cx)),
             vec![
-                ("    fn ".to_string(), Some("mod.body")),
-                ("inner".to_string(), Some("fn.name")),
-                ("() {}\n}".to_string(), Some("mod.body")),
+                ("    fn ".to_string(), Some(Color::red())),
+                ("inner".to_string(), Some(Color::blue())),
+                ("() {}\n}".to_string(), Some(Color::red())),
             ]
         );
 
@@ -799,11 +804,11 @@ mod tests {
             cx.update(|cx| chunks(0..2, &map, &theme, cx)),
             vec![
                 ("fn ".to_string(), None),
-                ("out".to_string(), Some("fn.name")),
+                ("out".to_string(), Some(Color::blue())),
                 ("…".to_string(), None),
-                ("  fn ".to_string(), Some("mod.body")),
-                ("inner".to_string(), Some("fn.name")),
-                ("() {}\n}".to_string(), Some("mod.body")),
+                ("  fn ".to_string(), Some(Color::red())),
+                ("inner".to_string(), Some(Color::blue())),
+                ("() {}\n}".to_string(), Some(Color::red())),
             ]
         );
     }
@@ -823,8 +828,8 @@ mod tests {
         .unindent();
 
         let theme = SyntaxTheme::new(vec![
-            ("mod.body".to_string(), Color::from_u32(0xff0000ff).into()),
-            ("fn.name".to_string(), Color::from_u32(0x00ff00ff).into()),
+            ("mod.body".to_string(), Color::red().into()),
+            ("fn.name".to_string(), Color::blue().into()),
         ]);
         let lang = Arc::new(
             Language::new(
@@ -864,7 +869,7 @@ mod tests {
             cx.update(|cx| chunks(0..5, &map, &theme, cx)),
             [
                 ("fn \n".to_string(), None),
-                ("oute\nr".to_string(), Some("fn.name")),
+                ("oute\nr".to_string(), Some(Color::blue())),
                 ("() \n{}\n\n".to_string(), None),
             ]
         );
@@ -879,10 +884,10 @@ mod tests {
         assert_eq!(
             cx.update(|cx| chunks(1..4, &map, &theme, cx)),
             [
-                ("out".to_string(), Some("fn.name")),
+                ("out".to_string(), Some(Color::blue())),
                 ("…\n".to_string(), None),
-                ("  \nfn ".to_string(), Some("mod.body")),
-                ("i\n".to_string(), Some("fn.name"))
+                ("  \nfn ".to_string(), Some(Color::red())),
+                ("i\n".to_string(), Some(Color::blue()))
             ]
         );
     }
@@ -1018,19 +1023,19 @@ mod tests {
         map: &ModelHandle<DisplayMap>,
         theme: &'a SyntaxTheme,
         cx: &mut MutableAppContext,
-    ) -> Vec<(String, Option<&'a str>)> {
-        let mut snapshot = map.update(cx, |map, cx| map.snapshot(cx));
-        let mut chunks: Vec<(String, Option<&str>)> = Vec::new();
-        for chunk in snapshot.chunks(rows) {
-            let style_name = chunk.highlight_id.name(theme);
-            if let Some((last_chunk, last_style_name)) = chunks.last_mut() {
-                if style_name == *last_style_name {
+    ) -> Vec<(String, Option<Color>)> {
+        let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
+        let mut chunks: Vec<(String, Option<Color>)> = Vec::new();
+        for chunk in snapshot.chunks(rows, Some(theme)) {
+            let color = chunk.highlight_style.map(|s| s.color);
+            if let Some((last_chunk, last_color)) = chunks.last_mut() {
+                if color == *last_color {
                     last_chunk.push_str(chunk.text);
                 } else {
-                    chunks.push((chunk.text.to_string(), style_name));
+                    chunks.push((chunk.text.to_string(), color));
                 }
             } else {
-                chunks.push((chunk.text.to_string(), style_name));
+                chunks.push((chunk.text.to_string(), color));
             }
         }
         chunks

crates/editor/src/display_map/block_map.rs 🔗

@@ -15,6 +15,7 @@ use std::{
     },
 };
 use sum_tree::SumTree;
+use theme::SyntaxTheme;
 
 pub struct BlockMap {
     buffer: ModelHandle<Buffer>,
@@ -459,12 +460,12 @@ impl<'a> BlockMapWriter<'a> {
 impl BlockSnapshot {
     #[cfg(test)]
     fn text(&mut self) -> String {
-        self.chunks(0..self.transforms.summary().output_rows, false)
+        self.chunks(0..self.transforms.summary().output_rows, None)
             .map(|chunk| chunk.text)
             .collect()
     }
 
-    pub fn chunks(&self, rows: Range<u32>, highlights: bool) -> Chunks {
+    pub fn chunks<'a>(&'a self, rows: Range<u32>, theme: Option<&'a SyntaxTheme>) -> Chunks<'a> {
         let max_output_row = cmp::min(rows.end, self.transforms.summary().output_rows);
         let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>();
         let input_end = {
@@ -492,9 +493,7 @@ impl BlockSnapshot {
             cursor.start().1 .0 + overshoot
         };
         Chunks {
-            input_chunks: self
-                .wrap_snapshot
-                .chunks(input_start..input_end, highlights),
+            input_chunks: self.wrap_snapshot.chunks(input_start..input_end, theme),
             input_chunk: Default::default(),
             block_chunks: None,
             transforms: cursor,
@@ -785,9 +784,9 @@ impl<'a> Iterator for BlockChunks<'a> {
 
         let chunk = self.chunk?;
         let mut chunk_len = chunk.len();
-        // let mut highlight_style = None;
-        if let Some((run_len, _)) = self.runs.peek() {
-            // highlight_style = Some(style.clone());
+        let mut highlight_style = None;
+        if let Some((run_len, style)) = self.runs.peek() {
+            highlight_style = Some(style.clone());
             let run_end_in_chunk = self.run_start + run_len - self.offset;
             if run_end_in_chunk <= chunk_len {
                 chunk_len = run_end_in_chunk;
@@ -806,7 +805,7 @@ impl<'a> Iterator for BlockChunks<'a> {
 
         Some(Chunk {
             text: chunk,
-            highlight_id: Default::default(),
+            highlight_style,
             diagnostic: None,
         })
     }
@@ -1314,7 +1313,7 @@ mod tests {
             for start_row in 0..expected_row_count {
                 let expected_text = expected_lines[start_row..].join("\n");
                 let actual_text = blocks_snapshot
-                    .chunks(start_row as u32..expected_row_count as u32, false)
+                    .chunks(start_row as u32..expected_row_count as u32, None)
                     .map(|chunk| chunk.text)
                     .collect::<String>();
                 assert_eq!(

crates/editor/src/display_map/fold_map.rs 🔗

@@ -1,7 +1,5 @@
 use gpui::{AppContext, ModelHandle};
-use language::{
-    Anchor, AnchorRangeExt, Buffer, Chunk, HighlightId, Point, PointUtf16, TextSummary, ToOffset,
-};
+use language::{Anchor, AnchorRangeExt, Buffer, Chunk, Point, PointUtf16, TextSummary, ToOffset};
 use parking_lot::Mutex;
 use std::{
     cmp::{self, Ordering},
@@ -10,6 +8,7 @@ use std::{
     sync::atomic::{AtomicUsize, Ordering::SeqCst},
 };
 use sum_tree::{Bias, Cursor, FilterCursor, SumTree};
+use theme::SyntaxTheme;
 
 pub trait ToFoldPoint {
     fn to_fold_point(&self, snapshot: &Snapshot, bias: Bias) -> FoldPoint;
@@ -498,7 +497,7 @@ pub struct Snapshot {
 impl Snapshot {
     #[cfg(test)]
     pub fn text(&self) -> String {
-        self.chunks(FoldOffset(0)..self.len(), false)
+        self.chunks(FoldOffset(0)..self.len(), None)
             .map(|c| c.text)
             .collect()
     }
@@ -630,11 +629,15 @@ impl Snapshot {
 
     pub fn chars_at(&self, start: FoldPoint) -> impl '_ + Iterator<Item = char> {
         let start = start.to_offset(self);
-        self.chunks(start..self.len(), false)
+        self.chunks(start..self.len(), None)
             .flat_map(|chunk| chunk.text.chars())
     }
 
-    pub fn chunks(&self, range: Range<FoldOffset>, enable_highlights: bool) -> Chunks {
+    pub fn chunks<'a>(
+        &'a self,
+        range: Range<FoldOffset>,
+        theme: Option<&'a SyntaxTheme>,
+    ) -> Chunks<'a> {
         let mut transform_cursor = self.transforms.cursor::<(FoldOffset, usize)>();
 
         transform_cursor.seek(&range.end, Bias::Right, &());
@@ -647,9 +650,7 @@ impl Snapshot {
 
         Chunks {
             transform_cursor,
-            buffer_chunks: self
-                .buffer_snapshot
-                .chunks(buffer_start..buffer_end, enable_highlights),
+            buffer_chunks: self.buffer_snapshot.chunks(buffer_start..buffer_end, theme),
             buffer_chunk: None,
             buffer_offset: buffer_start,
             output_offset: range.start.0,
@@ -974,7 +975,7 @@ impl<'a> Iterator for Chunks<'a> {
             self.output_offset += output_text.len();
             return Some(Chunk {
                 text: output_text,
-                highlight_id: HighlightId::default(),
+                highlight_style: None,
                 diagnostic: None,
             });
         }
@@ -1384,7 +1385,7 @@ mod tests {
                 log::info!("slicing {:?}..{:?} (text: {:?})", start, end, text);
                 assert_eq!(
                     snapshot
-                        .chunks(start..end, false)
+                        .chunks(start..end, None)
                         .map(|c| c.text)
                         .collect::<String>(),
                     text,

crates/editor/src/display_map/tab_map.rs 🔗

@@ -4,6 +4,7 @@ use language::{rope, Chunk};
 use parking_lot::Mutex;
 use std::{cmp, mem, ops::Range};
 use sum_tree::Bias;
+use theme::SyntaxTheme;
 
 pub struct TabMap(Mutex<Snapshot>);
 
@@ -33,7 +34,7 @@ impl TabMap {
             let mut delta = 0;
             for chunk in old_snapshot
                 .fold_snapshot
-                .chunks(fold_edit.old_bytes.end..max_offset, false)
+                .chunks(fold_edit.old_bytes.end..max_offset, None)
             {
                 let patterns: &[_] = &['\t', '\n'];
                 if let Some(ix) = chunk.text.find(patterns) {
@@ -116,7 +117,7 @@ impl Snapshot {
             self.max_point()
         };
         for c in self
-            .chunks(range.start..line_end, false)
+            .chunks(range.start..line_end, None)
             .flat_map(|chunk| chunk.text.chars())
         {
             if c == '\n' {
@@ -130,7 +131,7 @@ impl Snapshot {
             last_line_chars = first_line_chars;
         } else {
             for _ in self
-                .chunks(TabPoint::new(range.end.row(), 0)..range.end, false)
+                .chunks(TabPoint::new(range.end.row(), 0)..range.end, None)
                 .flat_map(|chunk| chunk.text.chars())
             {
                 last_line_chars += 1;
@@ -150,7 +151,11 @@ impl Snapshot {
         self.fold_snapshot.version
     }
 
-    pub fn chunks(&self, range: Range<TabPoint>, highlights: bool) -> Chunks {
+    pub fn chunks<'a>(
+        &'a self,
+        range: Range<TabPoint>,
+        theme: Option<&'a SyntaxTheme>,
+    ) -> Chunks<'a> {
         let (input_start, expanded_char_column, to_next_stop) =
             self.to_fold_point(range.start, Bias::Left);
         let input_start = input_start.to_offset(&self.fold_snapshot);
@@ -165,9 +170,7 @@ impl Snapshot {
         };
 
         Chunks {
-            fold_chunks: self
-                .fold_snapshot
-                .chunks(input_start..input_end, highlights),
+            fold_chunks: self.fold_snapshot.chunks(input_start..input_end, theme),
             column: expanded_char_column,
             output_position: range.start.0,
             max_output_position: range.end.0,
@@ -186,7 +189,7 @@ impl Snapshot {
 
     #[cfg(test)]
     pub fn text(&self) -> String {
-        self.chunks(TabPoint::zero()..self.max_point(), false)
+        self.chunks(TabPoint::zero()..self.max_point(), None)
             .map(|chunk| chunk.text)
             .collect()
     }
@@ -502,7 +505,7 @@ mod tests {
             assert_eq!(
                 expected_text,
                 tabs_snapshot
-                    .chunks(start..end, false)
+                    .chunks(start..end, None)
                     .map(|c| c.text)
                     .collect::<String>()
             );

crates/editor/src/display_map/wrap_map.rs 🔗

@@ -12,6 +12,7 @@ use lazy_static::lazy_static;
 use smol::future::yield_now;
 use std::{collections::VecDeque, mem, ops::Range, time::Duration};
 use sum_tree::{Bias, Cursor, SumTree};
+use theme::SyntaxTheme;
 
 pub use super::tab_map::TextSummary;
 pub type Edit = buffer::Edit<u32>;
@@ -427,7 +428,7 @@ impl Snapshot {
                 let mut remaining = None;
                 let mut chunks = new_tab_snapshot.chunks(
                     TabPoint::new(edit.new_rows.start, 0)..new_tab_snapshot.max_point(),
-                    false,
+                    None,
                 );
                 let mut edit_transforms = Vec::<Transform>::new();
                 for _ in edit.new_rows.start..edit.new_rows.end {
@@ -553,11 +554,11 @@ impl Snapshot {
     }
 
     pub fn text_chunks(&self, wrap_row: u32) -> impl Iterator<Item = &str> {
-        self.chunks(wrap_row..self.max_point().row() + 1, false)
+        self.chunks(wrap_row..self.max_point().row() + 1, None)
             .map(|h| h.text)
     }
 
-    pub fn chunks(&self, rows: Range<u32>, highlights: bool) -> Chunks {
+    pub fn chunks<'a>(&'a self, rows: Range<u32>, theme: Option<&'a SyntaxTheme>) -> Chunks<'a> {
         let output_start = WrapPoint::new(rows.start, 0);
         let output_end = WrapPoint::new(rows.end, 0);
         let mut transforms = self.transforms.cursor::<(WrapPoint, TabPoint)>();
@@ -570,7 +571,7 @@ impl Snapshot {
             .to_tab_point(output_end)
             .min(self.tab_snapshot.max_point());
         Chunks {
-            input_chunks: self.tab_snapshot.chunks(input_start..input_end, highlights),
+            input_chunks: self.tab_snapshot.chunks(input_start..input_end, theme),
             input_chunk: Default::default(),
             output_position: output_start,
             max_output_row: rows.end,
@@ -1233,7 +1234,7 @@ mod tests {
                 }
 
                 let actual_text = self
-                    .chunks(start_row..end_row, false)
+                    .chunks(start_row..end_row, None)
                     .map(|c| c.text)
                     .collect::<String>();
                 assert_eq!(

crates/editor/src/element.rs 🔗

@@ -493,7 +493,7 @@ impl EditorElement {
         let mut styles = Vec::new();
         let mut row = rows.start;
         let mut line_exceeded_max_len = false;
-        let chunks = snapshot.chunks(rows.clone());
+        let chunks = snapshot.chunks(rows.clone(), Some(&style.syntax));
 
         let newline_chunk = Chunk {
             text: "\n",
@@ -517,10 +517,8 @@ impl EditorElement {
                 }
 
                 if !line_chunk.is_empty() && !line_exceeded_max_len {
-                    let highlight_style = chunk
-                        .highlight_id
-                        .style(&style.syntax)
-                        .unwrap_or(style.text.clone().into());
+                    let highlight_style =
+                        chunk.highlight_style.unwrap_or(style.text.clone().into());
                     // Avoid a lookup if the font properties match the previous ones.
                     let font_id = if highlight_style.font_properties == prev_font_properties {
                         prev_font_id

crates/editor/src/lib.rs 🔗

@@ -31,7 +31,7 @@ use std::{
     time::Duration,
 };
 use sum_tree::Bias;
-use theme::EditorStyle;
+use theme::{EditorStyle, SyntaxTheme};
 use util::post_inc;
 
 const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
@@ -2709,8 +2709,12 @@ impl Snapshot {
         self.display_snapshot.buffer_rows(start_row)
     }
 
-    pub fn chunks(&mut self, display_rows: Range<u32>) -> display_map::Chunks {
-        self.display_snapshot.chunks(display_rows)
+    pub fn chunks<'a>(
+        &'a self,
+        display_rows: Range<u32>,
+        theme: Option<&'a SyntaxTheme>,
+    ) -> display_map::Chunks<'a> {
+        self.display_snapshot.chunks(display_rows, theme)
     }
 
     pub fn scroll_position(&self) -> Vector2F {

crates/gpui/src/fonts.rs 🔗

@@ -30,7 +30,7 @@ pub struct TextStyle {
     pub underline: Option<Color>,
 }
 
-#[derive(Clone, Debug, Default)]
+#[derive(Copy, Clone, Debug, Default)]
 pub struct HighlightStyle {
     pub color: Color,
     pub font_properties: Properties,

crates/language/src/lib.rs 🔗

@@ -12,7 +12,7 @@ use anyhow::{anyhow, Result};
 pub use buffer::{Buffer as TextBuffer, Operation as _, *};
 use clock::ReplicaId;
 use futures::FutureExt as _;
-use gpui::{AppContext, Entity, ModelContext, MutableAppContext, Task};
+use gpui::{fonts::HighlightStyle, AppContext, Entity, ModelContext, MutableAppContext, Task};
 use lazy_static::lazy_static;
 use lsp::LanguageServer;
 use parking_lot::Mutex;
@@ -34,6 +34,7 @@ use std::{
     time::{Duration, Instant, SystemTime, UNIX_EPOCH},
     vec,
 };
+use theme::SyntaxTheme;
 use tree_sitter::{InputEdit, Parser, QueryCursor, Tree};
 use util::{post_inc, TryFutureExt as _};
 
@@ -190,6 +191,7 @@ struct Highlights<'a> {
     next_capture: Option<(tree_sitter::QueryMatch<'a, 'a>, usize)>,
     stack: Vec<(usize, HighlightId)>,
     highlight_map: HighlightMap,
+    theme: &'a SyntaxTheme,
     _query_cursor: QueryCursorHandle,
 }
 
@@ -207,7 +209,7 @@ pub struct Chunks<'a> {
 #[derive(Clone, Copy, Debug, Default)]
 pub struct Chunk<'a> {
     pub text: &'a str,
-    pub highlight_id: HighlightId,
+    pub highlight_style: Option<HighlightStyle>,
     pub diagnostic: Option<DiagnosticSeverity>,
 }
 
@@ -1634,12 +1636,16 @@ impl Snapshot {
             .all(|chunk| chunk.matches(|c: char| !c.is_whitespace()).next().is_none())
     }
 
-    pub fn chunks<T: ToOffset>(&self, range: Range<T>, highlight: bool) -> Chunks {
+    pub fn chunks<'a, T: ToOffset>(
+        &'a self,
+        range: Range<T>,
+        theme: Option<&'a SyntaxTheme>,
+    ) -> Chunks<'a> {
         let range = range.start.to_offset(&*self)..range.end.to_offset(&*self);
 
         let mut highlights = None;
         let mut diagnostic_endpoints = Vec::<DiagnosticEndpoint>::new();
-        if highlight {
+        if let Some(theme) = theme {
             for (_, range, diagnostic) in
                 self.diagnostics
                     .intersecting_ranges(range.clone(), self.content(), true)
@@ -1676,6 +1682,7 @@ impl Snapshot {
                     stack: Default::default(),
                     highlight_map: language.highlight_map(),
                     _query_cursor: query_cursor,
+                    theme,
                 })
             }
         }
@@ -1845,12 +1852,12 @@ impl<'a> Iterator for Chunks<'a> {
             let mut chunk_end = (self.chunks.offset() + chunk.len())
                 .min(next_capture_start)
                 .min(next_diagnostic_endpoint);
-            let mut highlight_id = HighlightId::default();
-            if let Some((parent_capture_end, parent_highlight_id)) =
-                self.highlights.as_ref().and_then(|h| h.stack.last())
-            {
-                chunk_end = chunk_end.min(*parent_capture_end);
-                highlight_id = *parent_highlight_id;
+            let mut highlight_style = None;
+            if let Some(highlights) = self.highlights.as_ref() {
+                if let Some((parent_capture_end, parent_highlight_id)) = highlights.stack.last() {
+                    chunk_end = chunk_end.min(*parent_capture_end);
+                    highlight_style = parent_highlight_id.style(highlights.theme);
+                }
             }
 
             let slice =
@@ -1862,7 +1869,7 @@ impl<'a> Iterator for Chunks<'a> {
 
             Some(Chunk {
                 text: slice,
-                highlight_id,
+                highlight_style,
                 diagnostic: self.current_diagnostic_severity(),
             })
         } else {

crates/language/src/tests.rs 🔗

@@ -906,7 +906,7 @@ fn chunks_with_diagnostics<T: ToOffset + ToPoint>(
     range: Range<T>,
 ) -> Vec<(String, Option<DiagnosticSeverity>)> {
     let mut chunks: Vec<(String, Option<DiagnosticSeverity>)> = Vec::new();
-    for chunk in buffer.snapshot().chunks(range, true) {
+    for chunk in buffer.snapshot().chunks(range, Some(&Default::default())) {
         if chunks
             .last()
             .map_or(false, |prev_chunk| prev_chunk.1 == chunk.diagnostic)