use super::{ Highlights, dimensions::RowDelta, fold_map::{Chunk, FoldRows}, tab_map::{self, TabEdit, TabPoint, TabSnapshot},}; use futures_lite::future::yield_now;use gpui::{App, AppContext as _, Context, Entity, Font, LineWrapper, Pixels, Task};use language::{LanguageAwareStyling, Point};use multi_buffer::RowInfo;use std::{cmp, collections::VecDeque, mem, ops::Range, sync::LazyLock, time::Duration};use sum_tree::{Bias, Cursor, Dimensions, SumTree};use text::Patch; pub use super::tab_map::TextSummary;pub type WrapEdit = text::Edit<WrapRow>;pub type WrapPatch = text::Patch<WrapRow>; #[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]pub struct WrapRow(pub u32); const WRAP_YIELD_ROW_INTERVAL: usize = 100; impl_for_row_types! { WrapRow => RowDelta} /// Handles soft wrapping of text.////// See the [`display_map` module documentation](crate::display_map) for more information.pub struct WrapMap { snapshot: WrapSnapshot, pending_edits: VecDeque<(TabSnapshot, Vec<TabEdit>)>, interpolated_edits: WrapPatch, edits_since_sync: WrapPatch, wrap_width: Option<Pixels>, background_task: Option<Task<()>>, font_with_size: (Font, Pixels),} #[derive(Clone)]pub struct WrapSnapshot { pub(super) tab_snapshot: TabSnapshot, transforms: SumTree<Transform>, interpolated: bool,} impl std::ops::Deref for WrapSnapshot { type Target = TabSnapshot; fn deref(&self) -> &Self::Target { &self.tab_snapshot }} #[derive(Clone, Debug, Default, Eq, PartialEq)]struct Transform { summary: TransformSummary, display_text: Option<&'static str>,} #[derive(Clone, Debug, Default, Eq, PartialEq)]struct TransformSummary { input: TextSummary, output: TextSummary,} impl TransformSummary { fn has_wraps(&self) -> bool { self.input.lines != self.output.lines }} #[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]pub struct WrapPoint(pub Point); pub struct WrapChunks<'a> { input_chunks: tab_map::TabChunks<'a>, input_chunk: Chunk<'a>, output_position: WrapPoint, max_output_row: WrapRow, transforms: Cursor<'a, 'static, Transform, Dimensions<WrapPoint, TabPoint>>, snapshot: &'a WrapSnapshot,} #[derive(Clone)]pub struct WrapRows<'a> { input_buffer_rows: FoldRows<'a>, input_buffer_row: RowInfo, output_row: WrapRow, soft_wrapped: bool, max_output_row: WrapRow, transforms: Cursor<'a, 'static, Transform, Dimensions<WrapPoint, TabPoint>>,} impl WrapRows<'_> { #[ztracing::instrument(skip_all)] pub(crate) fn seek(&mut self, start_row: WrapRow) { self.transforms .seek(&WrapPoint::new(start_row, 0), Bias::Left); let mut input_row = self.transforms.start().1.row(); if self.transforms.item().is_some_and(|t| t.is_isomorphic()) { input_row += (start_row - self.transforms.start().0.row()).0; } self.soft_wrapped = self.transforms.item().is_some_and(|t| !t.is_isomorphic()); self.input_buffer_rows.seek(input_row); self.input_buffer_row = self.input_buffer_rows.next().unwrap(); self.output_row = start_row; }} impl WrapMap { #[ztracing::instrument(skip_all)] pub fn new( tab_snapshot: TabSnapshot, font: Font, font_size: Pixels, wrap_width: Option<Pixels>, cx: &mut App, ) -> (Entity<Self>, WrapSnapshot) { let handle = cx.new(|cx| { let mut this = Self { font_with_size: (font, font_size), wrap_width: None, pending_edits: Default::default(), interpolated_edits: Default::default(), edits_since_sync: Default::default(), snapshot: WrapSnapshot::new(tab_snapshot), background_task: None, }; this.set_wrap_width(wrap_width, cx); mem::take(&mut this.edits_since_sync); this }); let snapshot = handle.read(cx).snapshot.clone(); (handle, snapshot) } #[cfg(test)] pub fn is_rewrapping(&self) -> bool { self.background_task.is_some() } #[ztracing::instrument(skip_all)] pub fn sync( &mut self, tab_snapshot: TabSnapshot, edits: Vec<TabEdit>, cx: &mut Context<Self>, ) -> (WrapSnapshot, WrapPatch) { if self.wrap_width.is_some() { self.pending_edits.push_back((tab_snapshot, edits)); self.flush_edits(cx); } else { self.edits_since_sync = self .edits_since_sync .compose(self.snapshot.interpolate(tab_snapshot, &edits)); self.snapshot.interpolated = false; } (self.snapshot.clone(), mem::take(&mut self.edits_since_sync)) } #[ztracing::instrument(skip_all)] pub fn set_font_with_size( &mut self, font: Font, font_size: Pixels, cx: &mut Context<Self>, ) -> bool { let font_with_size = (font, font_size); if font_with_size == self.font_with_size { false } else { self.font_with_size = font_with_size; self.rewrap(cx); true } } #[ztracing::instrument(skip_all)] pub fn set_wrap_width(&mut self, wrap_width: Option<Pixels>, cx: &mut Context<Self>) -> bool { if wrap_width == self.wrap_width { return false; } self.wrap_width = wrap_width; self.rewrap(cx); true } #[ztracing::instrument(skip_all)] fn rewrap(&mut self, cx: &mut Context<Self>) { self.background_task.take(); self.interpolated_edits.clear(); self.pending_edits.clear(); if let Some(wrap_width) = self.wrap_width { let mut new_snapshot = self.snapshot.clone(); let text_system = cx.text_system(); let (font, font_size) = self.font_with_size.clone(); let mut line_wrapper = text_system.line_wrapper(font, font_size); let tab_snapshot = new_snapshot.tab_snapshot.clone(); let total_rows = tab_snapshot.max_point().row() as usize + 1; let range = TabPoint::zero()..tab_snapshot.max_point(); let tab_edits = [TabEdit { old: range.clone(), new: range, }]; if total_rows < WRAP_YIELD_ROW_INTERVAL { let edits = gpui::block_on(new_snapshot.update( tab_snapshot, &tab_edits, wrap_width, &mut line_wrapper, )); self.snapshot = new_snapshot; self.edits_since_sync = self.edits_since_sync.compose(&edits); } else { let task = cx.background_spawn(async move { let edits = new_snapshot .update(tab_snapshot, &tab_edits, wrap_width, &mut line_wrapper) .await; (new_snapshot, edits) }); match cx .foreground_executor() .block_with_timeout(Duration::from_millis(5), task) { Ok((snapshot, edits)) => { self.snapshot = snapshot; self.edits_since_sync = self.edits_since_sync.compose(&edits); } Err(wrap_task) => { self.background_task = Some(cx.spawn(async move |this, cx| { let (snapshot, edits) = wrap_task.await; this.update(cx, |this, cx| { this.snapshot = snapshot; this.edits_since_sync = this .edits_since_sync .compose(mem::take(&mut this.interpolated_edits).invert()) .compose(&edits); this.background_task = None; this.flush_edits(cx); cx.notify(); }) .ok(); })); } } } } else { let old_rows = self.snapshot.transforms.summary().output.lines.row + 1; self.snapshot.transforms = SumTree::default(); let summary = self.snapshot.tab_snapshot.text_summary(); if !summary.lines.is_zero() { self.snapshot .transforms .push(Transform::isomorphic(summary), ()); } let new_rows = self.snapshot.transforms.summary().output.lines.row + 1; self.snapshot.interpolated = false; self.edits_since_sync = self.edits_since_sync.compose(Patch::new(vec![WrapEdit { old: WrapRow(0)..WrapRow(old_rows), new: WrapRow(0)..WrapRow(new_rows), }])); } } #[ztracing::instrument(skip_all)] fn flush_edits(&mut self, cx: &mut Context<Self>) { if !self.snapshot.interpolated { let mut to_remove_len = 0; for (tab_snapshot, _) in &self.pending_edits { if tab_snapshot.version <= self.snapshot.tab_snapshot.version { to_remove_len += 1; } else { break; } } self.pending_edits.drain(..to_remove_len); } if self.pending_edits.is_empty() { return; } if let Some(wrap_width) = self.wrap_width && self.background_task.is_none() { let mut pending_edits = self.pending_edits.clone(); let mut snapshot = self.snapshot.clone(); let text_system = cx.text_system().clone(); let (font, font_size) = self.font_with_size.clone(); let mut line_wrapper = text_system.line_wrapper(font, font_size); if pending_edits.len() == 1 && let Some((_, tab_edits)) = pending_edits.back() && let [edit] = &**tab_edits && ((edit.new.end.row().saturating_sub(edit.new.start.row()) + 1) as usize) < WRAP_YIELD_ROW_INTERVAL && let Some((tab_snapshot, tab_edits)) = pending_edits.pop_back() { let wrap_edits = gpui::block_on(snapshot.update( tab_snapshot, &tab_edits, wrap_width, &mut line_wrapper, )); self.snapshot = snapshot; self.edits_since_sync = self.edits_since_sync.compose(&wrap_edits); } else { let update_task = cx.background_spawn(async move { let mut edits = Patch::default(); for (tab_snapshot, tab_edits) in pending_edits { let wrap_edits = snapshot .update(tab_snapshot, &tab_edits, wrap_width, &mut line_wrapper) .await; edits = edits.compose(&wrap_edits); } (snapshot, edits) }); match cx .foreground_executor() .block_with_timeout(Duration::from_millis(1), update_task) { Ok((snapshot, output_edits)) => { self.snapshot = snapshot; self.edits_since_sync = self.edits_since_sync.compose(&output_edits); } Err(update_task) => { self.background_task = Some(cx.spawn(async move |this, cx| { let (snapshot, edits) = update_task.await; this.update(cx, |this, cx| { this.snapshot = snapshot; this.edits_since_sync = this .edits_since_sync .compose(mem::take(&mut this.interpolated_edits).invert()) .compose(&edits); this.background_task = None; this.flush_edits(cx); cx.notify(); }) .ok(); })); } } } } let was_interpolated = self.snapshot.interpolated; let mut to_remove_len = 0; for (tab_snapshot, edits) in &self.pending_edits { if tab_snapshot.version <= self.snapshot.tab_snapshot.version { to_remove_len += 1; } else { let interpolated_edits = self.snapshot.interpolate(tab_snapshot.clone(), edits); self.edits_since_sync = self.edits_since_sync.compose(&interpolated_edits); self.interpolated_edits = self.interpolated_edits.compose(&interpolated_edits); } } if !was_interpolated { self.pending_edits.drain(..to_remove_len); } }} impl WrapSnapshot { #[ztracing::instrument(skip_all)] fn new(tab_snapshot: TabSnapshot) -> Self { let mut transforms = SumTree::default(); let extent = tab_snapshot.text_summary(); if !extent.lines.is_zero() { transforms.push(Transform::isomorphic(extent), ()); } Self { transforms, tab_snapshot, interpolated: true, } } #[ztracing::instrument(skip_all)] fn interpolate(&mut self, new_tab_snapshot: TabSnapshot, tab_edits: &[TabEdit]) -> WrapPatch { let mut new_transforms; if tab_edits.is_empty() { new_transforms = self.transforms.clone(); } else if !self.transforms.summary().has_wraps() && !new_tab_snapshot.text_summary().lines.is_zero() { // Fast path: without existing wraps, interpolation is a passthrough over the new tab snapshot. new_transforms = SumTree::default(); new_transforms.push(Transform::isomorphic(new_tab_snapshot.text_summary()), ()); } else { let mut old_cursor = self.transforms.cursor::<TabPoint>(()); let mut tab_edits_iter = tab_edits.iter().peekable(); new_transforms = old_cursor.slice(&tab_edits_iter.peek().unwrap().old.start, Bias::Right); while let Some(edit) = tab_edits_iter.next() { if edit.new.start > TabPoint::from(new_transforms.summary().input.lines) { let summary = new_tab_snapshot.text_summary_for_range( TabPoint::from(new_transforms.summary().input.lines)..edit.new.start, ); new_transforms.push_or_extend(Transform::isomorphic(summary)); } if !edit.new.is_empty() { new_transforms.push_or_extend(Transform::isomorphic( new_tab_snapshot.text_summary_for_range(edit.new.clone()), )); } old_cursor.seek_forward(&edit.old.end, Bias::Right); if let Some(next_edit) = tab_edits_iter.peek() { if next_edit.old.start > old_cursor.end() { if old_cursor.end() > edit.old.end { let summary = self .tab_snapshot .text_summary_for_range(edit.old.end..old_cursor.end()); new_transforms.push_or_extend(Transform::isomorphic(summary)); } old_cursor.next(); new_transforms .append(old_cursor.slice(&next_edit.old.start, Bias::Right), ()); } } else { if old_cursor.end() > edit.old.end { let summary = self .tab_snapshot .text_summary_for_range(edit.old.end..old_cursor.end()); new_transforms.push_or_extend(Transform::isomorphic(summary)); } old_cursor.next(); new_transforms.append(old_cursor.suffix(), ()); } } } let old_snapshot = mem::replace( self, WrapSnapshot { tab_snapshot: new_tab_snapshot, transforms: new_transforms, interpolated: true, }, ); self.check_invariants(); old_snapshot.compute_edits(tab_edits, self) } #[ztracing::instrument(skip_all)] async fn update( &mut self, new_tab_snapshot: TabSnapshot, tab_edits: &[TabEdit], wrap_width: Pixels, line_wrapper: &mut LineWrapper, ) -> WrapPatch { #[derive(Debug)] struct RowEdit { old_rows: Range<u32>, new_rows: Range<u32>, } let mut tab_edits_iter = tab_edits.iter().peekable(); let mut row_edits = Vec::with_capacity(tab_edits.len()); while let Some(edit) = tab_edits_iter.next() { let mut row_edit = RowEdit { old_rows: edit.old.start.row()..edit.old.end.row() + 1, new_rows: edit.new.start.row()..edit.new.end.row() + 1, }; while let Some(next_edit) = tab_edits_iter.peek() { if next_edit.old.start.row() <= row_edit.old_rows.end { row_edit.old_rows.end = cmp::max(row_edit.old_rows.end, next_edit.old.end.row() + 1); row_edit.new_rows.end = cmp::max(row_edit.new_rows.end, next_edit.new.end.row() + 1); tab_edits_iter.next(); } else { break; } } row_edits.push(row_edit); } let mut new_transforms; if row_edits.is_empty() { new_transforms = self.transforms.clone();Lines 1–500 of 1758. Load more
use super::{ Highlights, dimensions::RowDelta, fold_map::{Chunk, FoldRows}, tab_map::{self, TabEdit, TabPoint, TabSnapshot}, }; use futures_lite::future::yield_now; use gpui::{App, AppContext as _, Context, Entity, Font, LineWrapper, Pixels, Task}; use language::{LanguageAwareStyling, Point}; use multi_buffer::RowInfo; use std::{cmp, collections::VecDeque, mem, ops::Range, sync::LazyLock, time::Duration}; use sum_tree::{Bias, Cursor, Dimensions, SumTree}; use text::Patch; pub use super::tab_map::TextSummary; pub type WrapEdit = text::Edit<WrapRow>; pub type WrapPatch = text::Patch<WrapRow>; #[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)] pub struct WrapRow(pub u32); const WRAP_YIELD_ROW_INTERVAL: usize = 100; impl_for_row_types! { WrapRow => RowDelta } /// Handles soft wrapping of text. /// /// See the [`display_map` module documentation](crate::display_map) for more information. pub struct WrapMap { snapshot: WrapSnapshot, pending_edits: VecDeque<(TabSnapshot, Vec<TabEdit>)>, interpolated_edits: WrapPatch, edits_since_sync: WrapPatch, wrap_width: Option<Pixels>, background_task: Option<Task<()>>, font_with_size: (Font, Pixels), } #[derive(Clone)] pub struct WrapSnapshot { pub(super) tab_snapshot: TabSnapshot, transforms: SumTree<Transform>, interpolated: bool, } impl std::ops::Deref for WrapSnapshot { type Target = TabSnapshot; fn deref(&self) -> &Self::Target { &self.tab_snapshot } } #[derive(Clone, Debug, Default, Eq, PartialEq)] struct Transform { summary: TransformSummary, display_text: Option<&'static str>, } #[derive(Clone, Debug, Default, Eq, PartialEq)] struct TransformSummary { input: TextSummary, output: TextSummary, } impl TransformSummary { fn has_wraps(&self) -> bool { self.input.lines != self.output.lines } } #[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)] pub struct WrapPoint(pub Point); pub struct WrapChunks<'a> { input_chunks: tab_map::TabChunks<'a>, input_chunk: Chunk<'a>, output_position: WrapPoint, max_output_row: WrapRow, transforms: Cursor<'a, 'static, Transform, Dimensions<WrapPoint, TabPoint>>, snapshot: &'a WrapSnapshot, } #[derive(Clone)] pub struct WrapRows<'a> { input_buffer_rows: FoldRows<'a>, input_buffer_row: RowInfo, output_row: WrapRow, soft_wrapped: bool, max_output_row: WrapRow, transforms: Cursor<'a, 'static, Transform, Dimensions<WrapPoint, TabPoint>>, } impl WrapRows<'_> { #[ztracing::instrument(skip_all)] pub(crate) fn seek(&mut self, start_row: WrapRow) { self.transforms .seek(&WrapPoint::new(start_row, 0), Bias::Left); let mut input_row = self.transforms.start().1.row(); if self.transforms.item().is_some_and(|t| t.is_isomorphic()) { input_row += (start_row - self.transforms.start().0.row()).0; } self.soft_wrapped = self.transforms.item().is_some_and(|t| !t.is_isomorphic()); self.input_buffer_rows.seek(input_row); self.input_buffer_row = self.input_buffer_rows.next().unwrap(); self.output_row = start_row; } } impl WrapMap { #[ztracing::instrument(skip_all)] pub fn new( tab_snapshot: TabSnapshot, font: Font, font_size: Pixels, wrap_width: Option<Pixels>, cx: &mut App, ) -> (Entity<Self>, WrapSnapshot) { let handle = cx.new(|cx| { let mut this = Self { font_with_size: (font, font_size), wrap_width: None, pending_edits: Default::default(), interpolated_edits: Default::default(), edits_since_sync: Default::default(), snapshot: WrapSnapshot::new(tab_snapshot), background_task: None, }; this.set_wrap_width(wrap_width, cx); mem::take(&mut this.edits_since_sync); this }); let snapshot = handle.read(cx).snapshot.clone(); (handle, snapshot) } #[cfg(test)] pub fn is_rewrapping(&self) -> bool { self.background_task.is_some() } #[ztracing::instrument(skip_all)] pub fn sync( &mut self, tab_snapshot: TabSnapshot, edits: Vec<TabEdit>, cx: &mut Context<Self>, ) -> (WrapSnapshot, WrapPatch) { if self.wrap_width.is_some() { self.pending_edits.push_back((tab_snapshot, edits)); self.flush_edits(cx); } else { self.edits_since_sync = self .edits_since_sync .compose(self.snapshot.interpolate(tab_snapshot, &edits)); self.snapshot.interpolated = false; } (self.snapshot.clone(), mem::take(&mut self.edits_since_sync)) } #[ztracing::instrument(skip_all)] pub fn set_font_with_size( &mut self, font: Font, font_size: Pixels, cx: &mut Context<Self>, ) -> bool { let font_with_size = (font, font_size); if font_with_size == self.font_with_size { false } else { self.font_with_size = font_with_size; self.rewrap(cx); true } } #[ztracing::instrument(skip_all)] pub fn set_wrap_width(&mut self, wrap_width: Option<Pixels>, cx: &mut Context<Self>) -> bool { if wrap_width == self.wrap_width { return false; } self.wrap_width = wrap_width; self.rewrap(cx); true } #[ztracing::instrument(skip_all)] fn rewrap(&mut self, cx: &mut Context<Self>) { self.background_task.take(); self.interpolated_edits.clear(); self.pending_edits.clear(); if let Some(wrap_width) = self.wrap_width { let mut new_snapshot = self.snapshot.clone(); let text_system = cx.text_system(); let (font, font_size) = self.font_with_size.clone(); let mut line_wrapper = text_system.line_wrapper(font, font_size); let tab_snapshot = new_snapshot.tab_snapshot.clone(); let total_rows = tab_snapshot.max_point().row() as usize + 1; let range = TabPoint::zero()..tab_snapshot.max_point(); let tab_edits = [TabEdit { old: range.clone(), new: range, }]; if total_rows < WRAP_YIELD_ROW_INTERVAL { let edits = gpui::block_on(new_snapshot.update( tab_snapshot, &tab_edits, wrap_width, &mut line_wrapper, )); self.snapshot = new_snapshot; self.edits_since_sync = self.edits_since_sync.compose(&edits); } else { let task = cx.background_spawn(async move { let edits = new_snapshot .update(tab_snapshot, &tab_edits, wrap_width, &mut line_wrapper) .await; (new_snapshot, edits) }); match cx .foreground_executor() .block_with_timeout(Duration::from_millis(5), task) { Ok((snapshot, edits)) => { self.snapshot = snapshot; self.edits_since_sync = self.edits_since_sync.compose(&edits); } Err(wrap_task) => { self.background_task = Some(cx.spawn(async move |this, cx| { let (snapshot, edits) = wrap_task.await; this.update(cx, |this, cx| { this.snapshot = snapshot; this.edits_since_sync = this .edits_since_sync .compose(mem::take(&mut this.interpolated_edits).invert()) .compose(&edits); this.background_task = None; this.flush_edits(cx); cx.notify(); }) .ok(); })); } } } } else { let old_rows = self.snapshot.transforms.summary().output.lines.row + 1; self.snapshot.transforms = SumTree::default(); let summary = self.snapshot.tab_snapshot.text_summary(); if !summary.lines.is_zero() { self.snapshot .transforms .push(Transform::isomorphic(summary), ()); } let new_rows = self.snapshot.transforms.summary().output.lines.row + 1; self.snapshot.interpolated = false; self.edits_since_sync = self.edits_since_sync.compose(Patch::new(vec![WrapEdit { old: WrapRow(0)..WrapRow(old_rows), new: WrapRow(0)..WrapRow(new_rows), }])); } } #[ztracing::instrument(skip_all)] fn flush_edits(&mut self, cx: &mut Context<Self>) { if !self.snapshot.interpolated { let mut to_remove_len = 0; for (tab_snapshot, _) in &self.pending_edits { if tab_snapshot.version <= self.snapshot.tab_snapshot.version { to_remove_len += 1; } else { break; } } self.pending_edits.drain(..to_remove_len); } if self.pending_edits.is_empty() { return; } if let Some(wrap_width) = self.wrap_width && self.background_task.is_none() { let mut pending_edits = self.pending_edits.clone(); let mut snapshot = self.snapshot.clone(); let text_system = cx.text_system().clone(); let (font, font_size) = self.font_with_size.clone(); let mut line_wrapper = text_system.line_wrapper(font, font_size); if pending_edits.len() == 1 && let Some((_, tab_edits)) = pending_edits.back() && let [edit] = &**tab_edits && ((edit.new.end.row().saturating_sub(edit.new.start.row()) + 1) as usize) < WRAP_YIELD_ROW_INTERVAL && let Some((tab_snapshot, tab_edits)) = pending_edits.pop_back() { let wrap_edits = gpui::block_on(snapshot.update( tab_snapshot, &tab_edits, wrap_width, &mut line_wrapper, )); self.snapshot = snapshot; self.edits_since_sync = self.edits_since_sync.compose(&wrap_edits); } else { let update_task = cx.background_spawn(async move { let mut edits = Patch::default(); for (tab_snapshot, tab_edits) in pending_edits { let wrap_edits = snapshot .update(tab_snapshot, &tab_edits, wrap_width, &mut line_wrapper) .await; edits = edits.compose(&wrap_edits); } (snapshot, edits) }); match cx .foreground_executor() .block_with_timeout(Duration::from_millis(1), update_task) { Ok((snapshot, output_edits)) => { self.snapshot = snapshot; self.edits_since_sync = self.edits_since_sync.compose(&output_edits); } Err(update_task) => { self.background_task = Some(cx.spawn(async move |this, cx| { let (snapshot, edits) = update_task.await; this.update(cx, |this, cx| { this.snapshot = snapshot; this.edits_since_sync = this .edits_since_sync .compose(mem::take(&mut this.interpolated_edits).invert()) .compose(&edits); this.background_task = None; this.flush_edits(cx); cx.notify(); }) .ok(); })); } } } } let was_interpolated = self.snapshot.interpolated; let mut to_remove_len = 0; for (tab_snapshot, edits) in &self.pending_edits { if tab_snapshot.version <= self.snapshot.tab_snapshot.version { to_remove_len += 1; } else { let interpolated_edits = self.snapshot.interpolate(tab_snapshot.clone(), edits); self.edits_since_sync = self.edits_since_sync.compose(&interpolated_edits); self.interpolated_edits = self.interpolated_edits.compose(&interpolated_edits); } } if !was_interpolated { self.pending_edits.drain(..to_remove_len); } } } impl WrapSnapshot { #[ztracing::instrument(skip_all)] fn new(tab_snapshot: TabSnapshot) -> Self { let mut transforms = SumTree::default(); let extent = tab_snapshot.text_summary(); if !extent.lines.is_zero() { transforms.push(Transform::isomorphic(extent), ()); } Self { transforms, tab_snapshot, interpolated: true, } } #[ztracing::instrument(skip_all)] fn interpolate(&mut self, new_tab_snapshot: TabSnapshot, tab_edits: &[TabEdit]) -> WrapPatch { let mut new_transforms; if tab_edits.is_empty() { new_transforms = self.transforms.clone(); } else if !self.transforms.summary().has_wraps() && !new_tab_snapshot.text_summary().lines.is_zero() { // Fast path: without existing wraps, interpolation is a passthrough over the new tab snapshot. new_transforms = SumTree::default(); new_transforms.push(Transform::isomorphic(new_tab_snapshot.text_summary()), ()); } else { let mut old_cursor = self.transforms.cursor::<TabPoint>(()); let mut tab_edits_iter = tab_edits.iter().peekable(); new_transforms = old_cursor.slice(&tab_edits_iter.peek().unwrap().old.start, Bias::Right); while let Some(edit) = tab_edits_iter.next() { if edit.new.start > TabPoint::from(new_transforms.summary().input.lines) { let summary = new_tab_snapshot.text_summary_for_range( TabPoint::from(new_transforms.summary().input.lines)..edit.new.start, ); new_transforms.push_or_extend(Transform::isomorphic(summary)); } if !edit.new.is_empty() { new_transforms.push_or_extend(Transform::isomorphic( new_tab_snapshot.text_summary_for_range(edit.new.clone()), )); } old_cursor.seek_forward(&edit.old.end, Bias::Right); if let Some(next_edit) = tab_edits_iter.peek() { if next_edit.old.start > old_cursor.end() { if old_cursor.end() > edit.old.end { let summary = self .tab_snapshot .text_summary_for_range(edit.old.end..old_cursor.end()); new_transforms.push_or_extend(Transform::isomorphic(summary)); } old_cursor.next(); new_transforms .append(old_cursor.slice(&next_edit.old.start, Bias::Right), ()); } } else { if old_cursor.end() > edit.old.end { let summary = self .tab_snapshot .text_summary_for_range(edit.old.end..old_cursor.end()); new_transforms.push_or_extend(Transform::isomorphic(summary)); } old_cursor.next(); new_transforms.append(old_cursor.suffix(), ()); } } } let old_snapshot = mem::replace( self, WrapSnapshot { tab_snapshot: new_tab_snapshot, transforms: new_transforms, interpolated: true, }, ); self.check_invariants(); old_snapshot.compute_edits(tab_edits, self) } #[ztracing::instrument(skip_all)] async fn update( &mut self, new_tab_snapshot: TabSnapshot, tab_edits: &[TabEdit], wrap_width: Pixels, line_wrapper: &mut LineWrapper, ) -> WrapPatch { #[derive(Debug)] struct RowEdit { old_rows: Range<u32>, new_rows: Range<u32>, } let mut tab_edits_iter = tab_edits.iter().peekable(); let mut row_edits = Vec::with_capacity(tab_edits.len()); while let Some(edit) = tab_edits_iter.next() { let mut row_edit = RowEdit { old_rows: edit.old.start.row()..edit.old.end.row() + 1, new_rows: edit.new.start.row()..edit.new.end.row() + 1, }; while let Some(next_edit) = tab_edits_iter.peek() { if next_edit.old.start.row() <= row_edit.old_rows.end { row_edit.old_rows.end = cmp::max(row_edit.old_rows.end, next_edit.old.end.row() + 1); row_edit.new_rows.end = cmp::max(row_edit.new_rows.end, next_edit.new.end.row() + 1); tab_edits_iter.next(); } else { break; } } row_edits.push(row_edit); } let mut new_transforms; if row_edits.is_empty() { new_transforms = self.transforms.clone();