diff --git a/crates/ui/src/input/state.rs b/crates/ui/src/input/state.rs index a73c363a..b9e515b8 100644 --- a/crates/ui/src/input/state.rs +++ b/crates/ui/src/input/state.rs @@ -2282,7 +2282,8 @@ impl EntityInputHandler for InputState { if let Some(diagnostics) = self.mode.diagnostics_mut() { diagnostics.reset(&self.text) } - self.text_wrapper.update(&self.text, false, cx); + self.text_wrapper + .update(&self.text, &range, &Rope::from(new_text), false, cx); self.mode .update_highlighter(&range, &self.text, &new_text, true, cx); self.selected_range = (new_offset..new_offset).into(); @@ -2331,7 +2332,8 @@ impl EntityInputHandler for InputState { if let Some(diagnostics) = self.mode.diagnostics_mut() { diagnostics.reset(&self.text) } - self.text_wrapper.update(&self.text, false, cx); + self.text_wrapper + .update(&self.text, &range, &Rope::from(new_text), false, cx); self.mode .update_highlighter(&range, &self.text, &new_text, true, cx); if new_text.is_empty() { @@ -2437,7 +2439,7 @@ impl Focusable for InputState { impl Render for InputState { fn render(&mut self, _: &mut Window, cx: &mut Context) -> impl IntoElement { - self.text_wrapper.update(&self.text, false, cx); + self.text_wrapper.update_all(&self.text, false, cx); self.mode .update_highlighter(&(0..0), &self.text, "", false, cx); diff --git a/crates/ui/src/input/text_wrapper.rs b/crates/ui/src/input/text_wrapper.rs index 19e95245..e2cd7d78 100644 --- a/crates/ui/src/input/text_wrapper.rs +++ b/crates/ui/src/input/text_wrapper.rs @@ -3,7 +3,7 @@ use std::ops::Range; use gpui::{App, Font, LineFragment, Pixels}; use rope::Rope; -use crate::input::RopeExt as _; +use crate::input::RopeExt; /// A line with soft wrapped lines info. #[derive(Clone)] @@ -88,7 +88,7 @@ impl TextWrapper { } self.wrap_width = wrap_width; - self.update(&self.text.clone(), true, cx); + self.update_all(&self.text.clone(), true, cx); } pub(super) fn set_font(&mut self, font: Font, font_size: Pixels, cx: &mut App) { @@ -98,24 +98,77 @@ impl TextWrapper { self.font = font; self.font_size = font_size; - self.update(&self.text.clone(), true, cx); + self.update_all(&self.text.clone(), true, cx); } /// Update the text wrapper and recalculate the wrapped lines. /// /// If the `text` is the same as the current text, do nothing. - pub(super) fn update(&mut self, text: &Rope, force: bool, cx: &mut App) { - if self.text.eq(text) && !force { - return; - } - - let wrap_width = self.wrap_width; + /// + /// - `changed_text`: The text [`Rope`] that has changed. + /// - `range`: The `selected_range` before change. + /// - `new_text`: The inserted text. + /// - `force`: Whether to force the update, if false, the update will be skipped if the text is the same. + /// - `cx`: The application context. + pub(super) fn update( + &mut self, + changed_text: &Rope, + range: &Range, + new_text: &Rope, + force: bool, + cx: &mut App, + ) { let mut line_wrapper = cx .text_system() .line_wrapper(self.font.clone(), self.font_size); + self._update( + changed_text, + range, + new_text, + force, + &mut |line_str, wrap_width| { + line_wrapper + .wrap_line(&[LineFragment::text(line_str)], wrap_width) + .collect() + }, + ); + } - self.lines.clear(); - for line in text.lines() { + fn _update( + &mut self, + changed_text: &Rope, + range: &Range, + new_text: &Rope, + force: bool, + wrap_line: &mut F, + ) where + F: FnMut(&str, Pixels) -> Vec, + { + if self.text.eq(changed_text) && !force { + return; + } + + // Remove the old changed lines. + let start_row = self.text.offset_to_point(range.start).row as usize; + let start_row = start_row.min(self.lines.len().saturating_sub(1)); + let end_row = self.text.offset_to_point(range.end).row as usize; + let end_row = end_row.min(self.lines.len().saturating_sub(1)); + let rows_range = start_row..=end_row; + + // To add the new lines. + let new_start_row = changed_text.offset_to_point(range.start).row as usize; + let new_start_offset = changed_text.line_start_offset(new_start_row); + let new_end_row = changed_text + .offset_to_point(range.start + new_text.len()) + .row as usize; + let new_end_offset = changed_text.line_end_offset(new_end_row); + let new_range = new_start_offset..new_end_offset; + + let mut new_lines = vec![]; + + let wrap_width = self.wrap_width; + + for line in changed_text.slice(new_range).lines() { let line_str = line.to_string(); let mut wrapped_lines = vec![]; let mut prev_boundary_ix = 0; @@ -123,8 +176,7 @@ impl TextWrapper { // If wrap_width is Pixels::MAX, skip wrapping to disable word wrap if let Some(wrap_width) = wrap_width { // Here only have wrapped line, if there is no wrap meet, the `line_wraps` result will empty. - for boundary in line_wrapper.wrap_line(&[LineFragment::text(&line_str)], wrap_width) - { + for boundary in wrap_line(&line_str, wrap_width) { wrapped_lines.push(prev_boundary_ix..boundary.ix); prev_boundary_ix = boundary.ix; } @@ -135,13 +187,179 @@ impl TextWrapper { wrapped_lines.push(prev_boundary_ix..line.len()); } - self.lines.push(LineItem { + new_lines.push(LineItem { line: line.clone(), wrapped_lines, }); } - self.text = text.clone(); + // dbg!(&new_lines.len()); + // dbg!(self.lines.len()); + if self.lines.len() == 0 { + self.lines = new_lines; + } else { + self.lines.splice(rows_range, new_lines); + } + + // dbg!(self.lines.len()); + self.text = changed_text.clone(); self.soft_lines = self.lines.iter().map(|l| l.lines_len()).sum(); } + + /// Update the text wrapper and recalculate the wrapped lines. + /// + /// If the `text` is the same as the current text, do nothing. + pub(crate) fn update_all(&mut self, text: &Rope, force: bool, cx: &mut App) { + self.update(text, &(0..text.len()), &text, force, cx); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use gpui::{px, Boundary, FontFeatures, FontStyle, FontWeight}; + + #[test] + fn test_update() { + let font = gpui::Font { + family: "Arial".into(), + weight: FontWeight::default(), + style: FontStyle::Normal, + features: FontFeatures::default(), + fallbacks: None, + }; + + let mut wrapper = TextWrapper::new(font, px(14.), None); + let mut text = + Rope::from("Hello, 世界!\nThis is second line.\nThis is third line.\n这里是第 4 行。"); + + fn fake_wrap_line(_line_str: &str, _wrap_width: Pixels) -> Vec { + vec![] + } + + wrapper._update(&text, &(0..text.len()), &text, false, &mut fake_wrap_line); + assert_eq!(wrapper.lines.len(), 4); + + // Add a new text to end + let range = text.len()..text.len(); + let new_text = "New text"; + text.replace(range.clone(), new_text); + wrapper._update( + &text, + &range, + &Rope::from(new_text), + false, + &mut fake_wrap_line, + ); + assert_eq!( + text.to_string(), + "Hello, 世界!\nThis is second line.\nThis is third line.\n这里是第 4 行。New text" + ); + assert_eq!(wrapper.lines.len(), 4); + + // Replace first line `Hello` to `AAA` + let range = 0..5; + let new_text = "AAA"; + text.replace(range.clone(), new_text); + wrapper._update( + &text, + &range, + &Rope::from(new_text), + false, + &mut fake_wrap_line, + ); + assert_eq!( + text.to_string(), + "AAA, 世界!\nThis is second line.\nThis is third line.\n这里是第 4 行。New text" + ); + assert_eq!(wrapper.lines.len(), 4); + + // Remove the second line + let start_offset = text.line_start_offset(1); + let end_offset = text.line_end_offset(1); + let range = start_offset..end_offset + 1; + text.replace(range.clone(), ""); + wrapper._update(&text, &range, &Rope::from(""), false, &mut fake_wrap_line); + assert_eq!( + text.to_string(), + "AAA, 世界!\nThis is third line.\n这里是第 4 行。New text" + ); + assert_eq!(wrapper.lines.len(), 3); + + // Replace the first 2 lines to "This is a new line." + let range = text.line_start_offset(0)..text.line_end_offset(1) + 1; + let new_text = "This is a new line.\nThis is new line 2.\n"; + text.replace(range.clone(), new_text); + wrapper._update( + &text, + &range, + &Rope::from(new_text), + false, + &mut fake_wrap_line, + ); + assert_eq!( + text.to_string(), + "This is a new line.\nThis is new line 2.\n这里是第 4 行。New text" + ); + assert_eq!(wrapper.lines.len(), 3); + + // Add a new line at the end + let range = text.len()..text.len(); + let new_text = "\nThis is a new line at the end."; + text.replace(range.clone(), new_text); + wrapper._update( + &text, + &range, + &Rope::from(new_text), + false, + &mut fake_wrap_line, + ); + assert_eq!( + text.to_string(), + "This is a new line.\nThis is new line 2.\n这里是第 4 行。New text\nThis is a new line at the end." + ); + assert_eq!(wrapper.lines.len(), 4); + + // Add a new line at the beginning + let range = 0..0; + let new_text = "This is a new line at the beginning.\n"; + text.replace(range.clone(), new_text); + wrapper._update( + &text, + &range, + &Rope::from(new_text), + false, + &mut fake_wrap_line, + ); + assert_eq!( + text.to_string(), + "This is a new line at the beginning.\nThis is a new line.\nThis is new line 2.\n这里是第 4 行。New text\nThis is a new line at the end." + ); + assert_eq!(wrapper.lines.len(), 5); + + // Remove all to at least one line in `lines`. + let range = 0..text.len(); + let new_text = ""; + text.replace(range.clone(), new_text); + wrapper._update( + &text, + &range, + &Rope::from(new_text), + false, + &mut fake_wrap_line, + ); + assert_eq!(text.to_string(), ""); + assert_eq!(wrapper.lines.len(), 1); + + // Test update_all + let range = 0..text.len(); + let new_text = "This is a full text.\nThis is a second line."; + text.replace(range.clone(), new_text); + wrapper._update(&text, &range, &text, false, &mut fake_wrap_line); + assert_eq!( + text.to_string(), + "This is a full text.\nThis is a second line." + ); + assert_eq!(wrapper.lines.len(), 2); + } }