use std::ops::Range;
use gpui::{App, Font, LineFragment, Pixels, Point, ShapedLine, Size, Window, point, px, size};
use ropey::Rope;
use smallvec::SmallVec;
use crate::input::RopeExt;
#[derive(Debug, Clone)]
pub(super) struct LineItem {
line: Rope,
pub(super) wrapped_lines: Vec<Range<usize>>,
}
impl LineItem {
#[inline]
pub(super) fn len(&self) -> usize {
self.line.len()
}
#[inline]
pub(super) fn lines_len(&self) -> usize {
self.wrapped_lines.len()
}
pub(super) fn height(&self, line_height: Pixels) -> Pixels {
self.lines_len() as f32 * line_height
}
}
#[derive(Debug, Default)]
pub(super) struct LongestRow {
pub row: usize,
pub len: usize,
}
pub(super) struct TextWrapper {
text: Rope,
soft_lines: usize,
font: Font,
font_size: Pixels,
wrap_width: Option<Pixels>,
pub(super) longest_row: LongestRow,
pub(super) lines: Vec<LineItem>,
_initialized: bool,
}
#[allow(unused)]
impl TextWrapper {
pub(super) fn new(font: Font, font_size: Pixels, wrap_width: Option<Pixels>) -> Self {
Self {
text: Rope::new(),
font,
font_size,
wrap_width,
soft_lines: 0,
longest_row: LongestRow::default(),
lines: Vec::new(),
_initialized: false,
}
}
#[inline]
pub(super) fn set_default_text(&mut self, text: &Rope) {
self.text = text.clone();
}
#[inline]
pub(super) fn len(&self) -> usize {
self.soft_lines
}
#[inline]
pub(super) fn line(&self, row: usize) -> Option<&LineItem> {
self.lines.iter().skip(row).next()
}
pub(super) fn set_wrap_width(&mut self, wrap_width: Option<Pixels>, cx: &mut App) {
if wrap_width == self.wrap_width {
return;
}
self.wrap_width = wrap_width;
self.update_all(&self.text.clone(), cx);
}
pub(super) fn set_font(&mut self, font: Font, font_size: Pixels, cx: &mut App) {
if self.font.eq(&font) && self.font_size == font_size {
return;
}
self.font = font;
self.font_size = font_size;
self.update_all(&self.text.clone(), cx);
}
pub(super) fn prepare_if_need(&mut self, text: &Rope, cx: &mut App) {
if self._initialized {
return;
}
self._initialized = true;
self.update_all(text, cx);
}
pub(super) fn update(
&mut self,
changed_text: &Rope,
range: &Range<usize>,
new_text: &Rope,
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,
&mut |line_str, wrap_width| {
line_wrapper
.wrap_line(&[LineFragment::text(line_str)], wrap_width)
.collect()
},
);
}
fn _update<F>(
&mut self,
changed_text: &Rope,
range: &Range<usize>,
new_text: &Rope,
wrap_line: &mut F,
) where
F: FnMut(&str, Pixels) -> Vec<gpui::Boundary>,
{
let start_row = self.text.offset_to_point(range.start).row;
let start_row = start_row.min(self.lines.len().saturating_sub(1));
let end_row = self.text.offset_to_point(range.end).row;
let end_row = end_row.min(self.lines.len().saturating_sub(1));
let rows_range = start_row..=end_row;
if rows_range.contains(&self.longest_row.row) {
self.longest_row = LongestRow::default();
}
let mut longest_row_ix = self.longest_row.row;
let mut longest_row_len = self.longest_row.len;
let new_start_row = changed_text.offset_to_point(range.start).row;
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;
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 (ix, line) in Rope::from(changed_text.slice(new_range))
.iter_lines()
.enumerate()
{
let line_str = line.to_string();
let mut wrapped_lines = vec![];
let mut prev_boundary_ix = 0;
if line_str.len() > longest_row_len {
longest_row_ix = new_start_row + ix;
longest_row_len = line_str.len();
}
if let Some(wrap_width) = wrap_width {
for boundary in wrap_line(&line_str, wrap_width) {
wrapped_lines.push(prev_boundary_ix..boundary.ix);
prev_boundary_ix = boundary.ix;
}
}
if !line_str[prev_boundary_ix..].is_empty() || prev_boundary_ix == 0 {
wrapped_lines.push(prev_boundary_ix..line.len());
}
new_lines.push(LineItem {
line: Rope::from(line),
wrapped_lines,
});
}
if self.lines.len() == 0 {
self.lines = new_lines;
} else {
self.lines.splice(rows_range, new_lines);
}
self.text = changed_text.clone();
self.soft_lines = self.lines.iter().map(|l| l.lines_len()).sum();
self.longest_row = LongestRow {
row: longest_row_ix,
len: longest_row_len,
}
}
fn update_all(&mut self, text: &Rope, cx: &mut App) {
self.update(text, &(0..text.len()), &text, cx);
}
pub(crate) fn offset_to_display_point(&self, offset: usize) -> DisplayPoint {
let row = self.text.offset_to_point(offset).row;
let start = self.text.line_start_offset(row);
let line = &self.lines[row];
let mut wrapped_row = self
.lines
.iter()
.take(row)
.map(|l| l.lines_len())
.sum::<usize>();
let local_offset = offset.saturating_sub(start);
for (ix, range) in line.wrapped_lines.iter().enumerate() {
if range.contains(&local_offset) {
return DisplayPoint::new(
wrapped_row + ix,
ix,
local_offset.saturating_sub(range.start),
);
}
}
let last_range = line.wrapped_lines.last().unwrap_or(&(0..0));
let ix = line.lines_len().saturating_sub(1);
return DisplayPoint::new(wrapped_row + ix, ix, last_range.len());
}
pub(crate) fn display_point_to_offset(&self, point: DisplayPoint) -> usize {
let mut wrapped_row = 0;
for (row, line) in self.lines.iter().enumerate() {
if wrapped_row + line.lines_len() > point.row {
let line_start = self.text.line_start_offset(row);
let local_row = point.row.saturating_sub(wrapped_row);
if let Some(range) = line.wrapped_lines.get(local_row) {
return line_start + (range.start + point.column).min(range.end);
} else {
return line_start + line.len();
}
}
wrapped_row += line.lines_len();
}
return self.text.len();
}
pub(crate) fn display_point_to_point(&self, point: DisplayPoint) -> tree_sitter::Point {
let offset = self.display_point_to_offset(point);
self.text.offset_to_point(offset)
}
pub(crate) fn point_to_display_point(&self, point: tree_sitter::Point) -> DisplayPoint {
let offset = self.text.point_to_offset(point);
self.offset_to_display_point(offset)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DisplayPoint {
pub row: usize,
pub local_row: usize,
pub column: usize,
}
impl DisplayPoint {
pub fn new(row: usize, local_row: usize, column: usize) -> Self {
Self {
row,
local_row,
column,
}
}
}
pub(crate) struct LineLayout {
len: usize,
pub(crate) wrapped_lines: SmallVec<[ShapedLine; 1]>,
pub(crate) longest_width: Pixels,
}
impl LineLayout {
pub(crate) fn new() -> Self {
Self {
len: 0,
longest_width: px(0.),
wrapped_lines: SmallVec::new(),
}
}
pub(crate) fn lines(mut self, wrapped_lines: SmallVec<[ShapedLine; 1]>) -> Self {
self.set_wrapped_lines(wrapped_lines);
self
}
pub(crate) fn set_wrapped_lines(&mut self, wrapped_lines: SmallVec<[ShapedLine; 1]>) {
self.len = wrapped_lines.iter().map(|l| l.len).sum();
let width = wrapped_lines
.iter()
.map(|l| l.width)
.max()
.unwrap_or_default();
self.longest_width = width;
self.wrapped_lines = wrapped_lines;
}
#[inline]
pub(super) fn len(&self) -> usize {
self.len
}
pub(crate) fn position_for_index(
&self,
offset: usize,
line_height: Pixels,
) -> Option<Point<Pixels>> {
let mut acc_len = 0;
let mut offset_y = px(0.);
for (i, line) in self.wrapped_lines.iter().enumerate() {
let is_last = i + 1 == self.wrapped_lines.len();
let line_len = if is_last { line.len + 1 } else { line.len };
let range = acc_len..(acc_len + line_len);
if range.contains(&offset) {
let x = line.x_for_index(offset.saturating_sub(acc_len));
return Some(point(x, offset_y));
}
acc_len += line_len;
offset_y += line_height;
}
None
}
pub(super) fn closest_index_for_x(&self, x: Pixels) -> usize {
let mut acc_len = 0;
for (i, line) in self.wrapped_lines.iter().enumerate() {
let is_last = i + 1 == self.wrapped_lines.len();
if x <= line.width {
let mut ix = line.closest_index_for_x(x);
if !is_last && ix == line.text.len() {
let c_len = line.text.chars().last().map(|c| c.len_utf8()).unwrap_or(0);
ix = ix.saturating_sub(c_len);
}
return acc_len + ix;
}
acc_len += line.text.len();
}
acc_len
}
pub(super) fn closest_index_for_position(
&self,
pos: Point<Pixels>,
line_height: Pixels,
) -> Option<usize> {
let mut offset = 0;
let mut line_top = px(0.);
for (i, line) in self.wrapped_lines.iter().enumerate() {
let is_last = i + 1 == self.wrapped_lines.len();
let line_bottom = line_top + line_height;
if pos.y >= line_top && pos.y < line_bottom {
let mut ix = line.closest_index_for_x(pos.x);
if !is_last && ix == line.text.len() {
let c_len = line.text.chars().last().map(|c| c.len_utf8()).unwrap_or(0);
ix = ix.saturating_sub(c_len);
}
return Some(offset + ix);
}
offset += line.text.len();
line_top = line_bottom;
}
None
}
pub(super) fn index_for_position(
&self,
pos: Point<Pixels>,
line_height: Pixels,
) -> Option<usize> {
let mut offset = 0;
let mut line_top = px(0.);
for line in self.wrapped_lines.iter() {
let line_bottom = line_top + line_height;
if pos.y >= line_top && pos.y < line_bottom {
let ix = line.index_for_x(pos.x)?;
return Some(offset + ix);
}
offset += line.text.len();
line_top = line_bottom;
}
None
}
pub(super) fn size(&self, line_height: Pixels) -> Size<Pixels> {
size(self.longest_width, self.wrapped_lines.len() * line_height)
}
pub(super) fn paint(
&self,
pos: Point<Pixels>,
line_height: Pixels,
window: &mut Window,
cx: &mut App,
) {
for (ix, line) in self.wrapped_lines.iter().enumerate() {
_ = line.paint(
pos + point(px(0.), ix * line_height),
line_height,
window,
cx,
);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use gpui::{Boundary, FontFeatures, FontStyle, FontWeight, px};
#[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, 世界!\r\nThis is second line.\nThis is third line.\n这里是第 4 行。",
);
fn fake_wrap_line(_line: &str, _wrap_width: Pixels) -> Vec<Boundary> {
vec![]
}
#[track_caller]
fn assert_wrapper_lines(text: &Rope, wrapper: &TextWrapper, expected_lines: &[&[&str]]) {
let mut actual_lines = vec![];
let mut offset = 0;
for line in wrapper.lines.iter() {
actual_lines.push(
line.wrapped_lines
.iter()
.map(|range| text.slice(offset + range.start..offset + range.end))
.collect::<Vec<_>>(),
);
offset += line.len() + 1;
}
assert_eq!(actual_lines, expected_lines);
}
wrapper._update(&text, &(0..text.len()), &text, &mut fake_wrap_line);
assert_eq!(wrapper.lines.len(), 4);
assert_wrapper_lines(
&text,
&wrapper,
&[
&["Hello, 世界!\r"],
&["This is second line."],
&["This is third line."],
&["这里是第 4 行。"],
],
);
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), &mut fake_wrap_line);
assert_eq!(
text.to_string(),
"Hello, 世界!\r\nThis is second line.\nThis is third line.\n这里是第 4 行。New text"
);
assert_eq!(wrapper.lines.len(), 4);
assert_eq!(wrapper.lines.len(), 4);
assert_wrapper_lines(
&text,
&wrapper,
&[
&["Hello, 世界!\r"],
&["This is second line."],
&["This is third line."],
&["这里是第 4 行。New text"],
],
);
let range = 0..5;
let new_text = "AAA";
text.replace(range.clone(), new_text);
wrapper._update(&text, &range, &Rope::from(new_text), &mut fake_wrap_line);
assert_eq!(
text.to_string(),
"AAA, 世界!\r\nThis is second line.\nThis is third line.\n这里是第 4 行。New text"
);
assert_eq!(wrapper.lines.len(), 4);
assert_wrapper_lines(
&text,
&wrapper,
&[
&["AAA, 世界!\r"],
&["This is second line."],
&["This is third line."],
&["这里是第 4 行。New text"],
],
);
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(""), &mut fake_wrap_line);
assert_eq!(
text.to_string(),
"AAA, 世界!\r\nThis is third line.\n这里是第 4 行。New text"
);
assert_eq!(wrapper.lines.len(), 3);
assert_wrapper_lines(
&text,
&wrapper,
&[
&["AAA, 世界!\r"],
&["This is third line."],
&["这里是第 4 行。New text"],
],
);
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), &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);
assert_wrapper_lines(
&text,
&wrapper,
&[
&["This is a new line."],
&["This is new line 2."],
&["这里是第 4 行。New text"],
],
);
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), &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);
assert_wrapper_lines(
&text,
&wrapper,
&[
&["This is a new line."],
&["This is new line 2."],
&["这里是第 4 行。New text"],
&["This is a new line at the end."],
],
);
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), &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);
assert_wrapper_lines(
&text,
&wrapper,
&[
&["This is a new line at the beginning."],
&["This is a new line."],
&["This is new line 2."],
&["这里是第 4 行。New text"],
&["This is a new line at the end."],
],
);
let range = 0..text.len();
let new_text = "";
text.replace(range.clone(), new_text);
wrapper._update(&text, &range, &Rope::from(new_text), &mut fake_wrap_line);
assert_eq!(text.to_string(), "");
assert_eq!(wrapper.lines.len(), 1);
assert_eq!(wrapper.lines[0].wrapped_lines, vec![0..0]);
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, &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);
}
#[test]
fn test_line_layout() {
let mut line_layout = LineLayout::new();
let line1 = ShapedLine::default().with_len(100);
let line2 = ShapedLine::default().with_len(50);
let wrapped_lines = smallvec::smallvec![line1, line2];
line_layout.set_wrapped_lines(wrapped_lines);
assert_eq!(line_layout.len(), 150);
assert_eq!(line_layout.wrapped_lines.len(), 2);
}
#[test]
fn test_offset_to_display_point() {
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);
wrapper.text = Rope::from(
"Hello, 世界!\r\nThis is second line.\nThis is third line.\n这里是第 4 行。",
);
wrapper.lines = vec![
LineItem {
line: Rope::from("Hello, 世界!\r"),
wrapped_lines: vec![0..15],
},
LineItem {
line: Rope::from("This is second line."),
wrapped_lines: vec![0..10, 10..20],
},
LineItem {
line: Rope::from("This is third line."),
wrapped_lines: vec![0..9, 9..15, 15..20],
},
LineItem {
line: Rope::from("这里是第 4 行。"),
wrapped_lines: vec![0..22],
},
];
assert_eq!(
wrapper.offset_to_display_point(12),
DisplayPoint::new(0, 0, 12)
);
assert_eq!(
wrapper.offset_to_display_point(15),
DisplayPoint::new(0, 0, 15)
);
assert_eq!(
wrapper.offset_to_display_point(16),
DisplayPoint::new(1, 0, 0)
);
assert_eq!(
wrapper.offset_to_display_point(21),
DisplayPoint::new(1, 0, 5)
);
assert_eq!(
wrapper.offset_to_display_point(27),
DisplayPoint::new(2, 1, 1)
);
assert_eq!(
wrapper.offset_to_display_point(37),
DisplayPoint::new(3, 0, 0)
);
assert_eq!(
wrapper.offset_to_display_point(54),
DisplayPoint::new(5, 2, 2)
);
assert_eq!(
wrapper.offset_to_display_point(59),
DisplayPoint::new(6, 0, 2)
);
assert_eq!(
wrapper.display_point_to_offset(DisplayPoint::new(6, 0, 2)),
59
);
assert_eq!(
wrapper.display_point_to_offset(DisplayPoint::new(5, 2, 2)),
54
);
assert_eq!(
wrapper.display_point_to_offset(DisplayPoint::new(3, 0, 0)),
37
);
assert_eq!(
wrapper.display_point_to_offset(DisplayPoint::new(2, 1, 1)),
27
);
assert_eq!(
wrapper.display_point_to_offset(DisplayPoint::new(1, 0, 5)),
21
);
assert_eq!(
wrapper.display_point_to_offset(DisplayPoint::new(1, 0, 0)),
16
);
assert_eq!(
wrapper.display_point_to_offset(DisplayPoint::new(0, 0, 15)),
15
);
}
}