input: Use shape line for soft wrap lines. (#1293)
This change aim to fix the before version `shape_text` (in Element paint) and `wrap_line` (in TextWrapper cached soft wrap map) may have difference wrap width. Now move to based on TextWrapper's wrap map to paint text by use `shape_line` method.
This commit is contained in:
parent
4983300ddf
commit
172015f147
3 changed files with 432 additions and 65 deletions
|
|
@ -3,14 +3,18 @@ use std::{ops::Range, rc::Rc};
|
|||
use gpui::{
|
||||
fill, point, px, relative, size, App, Bounds, Corners, Element, ElementId, ElementInputHandler,
|
||||
Entity, GlobalElementId, Half, HighlightStyle, Hitbox, IntoElement, LayoutId, MouseButton,
|
||||
MouseMoveEvent, Path, Pixels, Point, ShapedLine, SharedString, Size, Style, TextAlign, TextRun,
|
||||
TextStyle, UnderlineStyle, Window,
|
||||
MouseMoveEvent, Path, Pixels, Point, ShapedLine, SharedString, Size, Style, TextRun, TextStyle,
|
||||
UnderlineStyle, Window,
|
||||
};
|
||||
use ropey::Rope;
|
||||
use smallvec::SmallVec;
|
||||
|
||||
use crate::{
|
||||
input::{blink_cursor::CURSOR_WIDTH, RopeExt as _},
|
||||
input::{
|
||||
blink_cursor::CURSOR_WIDTH,
|
||||
text_wrapper::{LineLayout, TextWrapper},
|
||||
RopeExt as _,
|
||||
},
|
||||
ActiveTheme as _, Colorize, Root,
|
||||
};
|
||||
|
||||
|
|
@ -522,6 +526,53 @@ impl TextElement {
|
|||
(line_number_width, line_number_len)
|
||||
}
|
||||
|
||||
fn layout_lines(
|
||||
text: &Rope,
|
||||
text_wrapper: &TextWrapper,
|
||||
visible_range: &Range<usize>,
|
||||
font_size: Pixels,
|
||||
runs: &[TextRun],
|
||||
window: &mut Window,
|
||||
) -> Vec<LineLayout> {
|
||||
let visible_text = text
|
||||
.slice_lines(visible_range.start..visible_range.end)
|
||||
.to_string();
|
||||
|
||||
let mut lines = vec![];
|
||||
let mut offset = 0;
|
||||
for (ix, line) in visible_text.split("\n").enumerate() {
|
||||
let line_item = text_wrapper
|
||||
.lines
|
||||
.get(visible_range.start + ix)
|
||||
.expect("line should exists in text_wrapper");
|
||||
// if line_item.len() != line.len() {
|
||||
// dbg!(&line, &line_item.wrapped_lines);
|
||||
// }
|
||||
debug_assert_eq!(line_item.len(), line.len());
|
||||
|
||||
let mut line_layout = LineLayout::new();
|
||||
let mut wrapped_lines = SmallVec::with_capacity(1);
|
||||
|
||||
for range in &line_item.wrapped_lines {
|
||||
let line_runs = runs_for_range(runs, offset, &range);
|
||||
let sub_line: SharedString = line[range.clone()].to_string().into();
|
||||
let shaped_line = window
|
||||
.text_system()
|
||||
.shape_line(sub_line, font_size, &line_runs, None);
|
||||
|
||||
wrapped_lines.push(shaped_line);
|
||||
}
|
||||
|
||||
line_layout.set_wrapped_lines(wrapped_lines);
|
||||
lines.push(line_layout);
|
||||
|
||||
// +1 for the `\n`
|
||||
offset += line.len() + 1;
|
||||
}
|
||||
|
||||
lines
|
||||
}
|
||||
|
||||
/// First usize is the offset of skipped.
|
||||
fn highlight_lines(
|
||||
&mut self,
|
||||
|
|
@ -801,17 +852,14 @@ impl Element for TextElement {
|
|||
None
|
||||
};
|
||||
|
||||
// NOTE: Here 50 lines about 150µs
|
||||
// let measure = crate::Measure::new("shape_text");
|
||||
let visible_text = display_text
|
||||
.slice_lines(visible_range.start..visible_range.end)
|
||||
.to_string();
|
||||
|
||||
let lines = window
|
||||
.text_system()
|
||||
.shape_text(visible_text.into(), font_size, &runs, wrap_width, None)
|
||||
.expect("failed to shape text");
|
||||
// measure.end();
|
||||
let lines = Self::layout_lines(
|
||||
&text,
|
||||
&state.text_wrapper,
|
||||
&visible_range,
|
||||
font_size,
|
||||
&runs,
|
||||
window,
|
||||
);
|
||||
|
||||
let mut longest_line_width = wrap_width.unwrap_or(px(0.));
|
||||
if state.mode.is_multi_line() && !state.soft_wrap && lines.len() > 1 {
|
||||
|
|
@ -938,7 +986,7 @@ impl Element for TextElement {
|
|||
.text_system()
|
||||
.shape_line(line_no, font_size, &runs, None),
|
||||
);
|
||||
for _ in 0..line.wrap_boundaries.len() {
|
||||
for _ in 0..line.wrapped_lines.len().saturating_sub(1) {
|
||||
sub_lines.push(ShapedLine::default());
|
||||
}
|
||||
line_numbers.push(sub_lines);
|
||||
|
|
@ -1082,7 +1130,7 @@ impl Element for TextElement {
|
|||
origin.x + prepaint.last_layout.line_number_width,
|
||||
origin.y + offset_y,
|
||||
);
|
||||
_ = line.paint(p, line_height, TextAlign::Left, None, window, cx);
|
||||
_ = line.paint(p, line_height, window, cx);
|
||||
offset_y += line.size(line_height).height;
|
||||
}
|
||||
|
||||
|
|
@ -1162,3 +1210,105 @@ impl Element for TextElement {
|
|||
self.paint_mouse_listeners(window, cx);
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the runs for the given range.
|
||||
///
|
||||
/// The range is the byte range of the wrapped line.
|
||||
pub(super) fn runs_for_range(
|
||||
runs: &[TextRun],
|
||||
line_offset: usize,
|
||||
range: &Range<usize>,
|
||||
) -> Vec<TextRun> {
|
||||
let mut result = vec![];
|
||||
let range = (line_offset + range.start)..(line_offset + range.end);
|
||||
let mut cursor = 0;
|
||||
|
||||
for run in runs {
|
||||
let run_start = cursor;
|
||||
let run_end = cursor + run.len;
|
||||
|
||||
if run_end <= range.start {
|
||||
cursor = run_end;
|
||||
continue;
|
||||
}
|
||||
|
||||
if run_start >= range.end {
|
||||
break;
|
||||
}
|
||||
|
||||
let start = range.start.max(run_start) - run_start;
|
||||
let end = range.end.min(run_end) - run_start;
|
||||
let len = end - start;
|
||||
|
||||
if len > 0 {
|
||||
result.push(TextRun { len, ..run.clone() });
|
||||
}
|
||||
|
||||
cursor = run_end;
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_runs_for_range() {
|
||||
let run = TextRun {
|
||||
len: 0,
|
||||
font: gpui::font(".SystemUIFont"),
|
||||
color: gpui::black(),
|
||||
background_color: None,
|
||||
underline: None,
|
||||
strikethrough: None,
|
||||
};
|
||||
|
||||
// use hello this-is-test
|
||||
let runs = vec![
|
||||
// use
|
||||
TextRun {
|
||||
len: 3,
|
||||
..run.clone()
|
||||
},
|
||||
// \s
|
||||
TextRun {
|
||||
len: 1,
|
||||
..run.clone()
|
||||
},
|
||||
// hello
|
||||
TextRun {
|
||||
len: 5,
|
||||
..run.clone()
|
||||
},
|
||||
// \s
|
||||
TextRun {
|
||||
len: 1,
|
||||
..run.clone()
|
||||
},
|
||||
// this-is-test
|
||||
TextRun {
|
||||
len: 12,
|
||||
..run.clone()
|
||||
},
|
||||
];
|
||||
|
||||
#[track_caller]
|
||||
fn assert_runs(actual: Vec<TextRun>, expected: &[usize]) {
|
||||
let left = actual.iter().map(|run| run.len).collect::<Vec<_>>();
|
||||
assert_eq!(left, expected);
|
||||
}
|
||||
|
||||
assert_runs(runs_for_range(&runs, 0, &(0..0)), &[]);
|
||||
assert_runs(runs_for_range(&runs, 0, &(0..100)), &[3, 1, 5, 1, 12]);
|
||||
|
||||
assert_runs(runs_for_range(&runs, 0, &(0..6)), &[3, 1, 2]);
|
||||
assert_runs(runs_for_range(&runs, 0, &(1..6)), &[2, 1, 2]);
|
||||
assert_runs(runs_for_range(&runs, 0, &(3..10)), &[1, 5, 1]);
|
||||
assert_runs(runs_for_range(&runs, 0, &(5..8)), &[3]);
|
||||
assert_runs(runs_for_range(&runs, 3, &(0..3)), &[1, 2]);
|
||||
assert_runs(runs_for_range(&runs, 3, &(2..10)), &[4, 1, 3]);
|
||||
assert_runs(runs_for_range(&runs, 9, &(0..8)), &[1, 7]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,11 +9,9 @@ use gpui::{
|
|||
InteractiveElement as _, IntoElement, KeyBinding, KeyDownEvent, MouseButton, MouseDownEvent,
|
||||
MouseMoveEvent, MouseUpEvent, ParentElement as _, Pixels, Point, Render, ScrollHandle,
|
||||
ScrollWheelEvent, SharedString, Styled as _, Subscription, Task, UTF16Selection, Window,
|
||||
WrappedLine,
|
||||
};
|
||||
use ropey::{Rope, RopeSlice};
|
||||
use serde::Deserialize;
|
||||
use smallvec::SmallVec;
|
||||
use std::cell::RefCell;
|
||||
use std::ops::Range;
|
||||
use std::rc::Rc;
|
||||
|
|
@ -33,6 +31,7 @@ use crate::input::{
|
|||
element::RIGHT_MARGIN,
|
||||
popovers::{ContextMenu, DiagnosticPopover, HoverPopover, MouseContextMenu},
|
||||
search::{self, SearchPanel},
|
||||
text_wrapper::LineLayout,
|
||||
HoverDefinition, Lsp, Position,
|
||||
};
|
||||
use crate::input::{RopeExt as _, Selection};
|
||||
|
|
@ -239,7 +238,7 @@ pub(super) struct LastLayout {
|
|||
/// The range of byte offset of the visible lines.
|
||||
pub(super) visible_range_offset: Range<usize>,
|
||||
/// The last layout lines (Only have visible lines).
|
||||
pub(super) lines: Rc<SmallVec<[WrappedLine; 1]>>,
|
||||
pub(super) lines: Rc<Vec<LineLayout>>,
|
||||
/// The line_height of text layout, this will change will InputElement painted.
|
||||
pub(super) line_height: Pixels,
|
||||
/// The wrap width of text layout, this will change will InputElement painted.
|
||||
|
|
@ -659,7 +658,7 @@ impl InputState {
|
|||
if new_row >= last_layout.visible_range.start {
|
||||
let visible_row = new_row.saturating_sub(last_layout.visible_range.start);
|
||||
if let Some(line) = last_layout.lines.get(visible_row) {
|
||||
if let Ok(x) = line.closest_index_for_position(
|
||||
if let Some(x) = line.closest_index_for_position(
|
||||
Point {
|
||||
x: preferred_x,
|
||||
y: px(0.),
|
||||
|
|
@ -1544,7 +1543,7 @@ impl InputState {
|
|||
}
|
||||
|
||||
self.selecting = true;
|
||||
let offset = self.index_for_mouse_position(event.position, window, cx);
|
||||
let offset = self.index_for_mouse_position(event.position);
|
||||
|
||||
if self.handle_click_hover_definition(event, offset, window, cx) {
|
||||
return;
|
||||
|
|
@ -1586,7 +1585,7 @@ impl InputState {
|
|||
cx: &mut Context<Self>,
|
||||
) {
|
||||
// Show diagnostic popover on mouse move
|
||||
let offset = self.index_for_mouse_position(event.position, window, cx);
|
||||
let offset = self.index_for_mouse_position(event.position);
|
||||
self.handle_mouse_move(offset, event, window, cx);
|
||||
|
||||
if self.mode.is_code_editor() {
|
||||
|
|
@ -1814,12 +1813,7 @@ impl InputState {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn index_for_mouse_position(
|
||||
&self,
|
||||
position: Point<Pixels>,
|
||||
_window: &Window,
|
||||
_cx: &App,
|
||||
) -> usize {
|
||||
pub(crate) fn index_for_mouse_position(&self, position: Point<Pixels>) -> usize {
|
||||
// If the text is empty, always return 0
|
||||
if self.text.len() == 0 {
|
||||
return 0;
|
||||
|
|
@ -1858,7 +1852,7 @@ impl InputState {
|
|||
let line_origin = self.line_origin_with_y_offset(&mut y_offset, line, line_height);
|
||||
let pos = inner_position - line_origin;
|
||||
|
||||
let Some(rendered_line) = last_layout.lines.get(ix) else {
|
||||
let Some(line_layout) = last_layout.lines.get(ix) else {
|
||||
if pos.y < line_origin.y + line_height {
|
||||
break;
|
||||
}
|
||||
|
|
@ -1868,37 +1862,18 @@ impl InputState {
|
|||
|
||||
// Return offset by use closest_index_for_x if is single line mode.
|
||||
if self.mode.is_single_line() {
|
||||
return rendered_line.unwrapped_layout.closest_index_for_x(pos.x);
|
||||
return line_layout.closest_index_for_x(pos.x);
|
||||
}
|
||||
|
||||
let index_result = rendered_line.closest_index_for_position(pos, line_height);
|
||||
if let Ok(v) = index_result {
|
||||
if let Some(v) = line_layout.closest_index_for_position(pos, line_height) {
|
||||
index += v;
|
||||
break;
|
||||
} else if let Ok(_) =
|
||||
rendered_line.index_for_position(point(px(0.), pos.y), line_height)
|
||||
{
|
||||
// Click in the this line but not in the text, move cursor to the end of the line.
|
||||
// The fallback index is saved in Err from `index_for_position` method.
|
||||
index += index_result.unwrap_err();
|
||||
} else if pos.y < px(0.) {
|
||||
break;
|
||||
} else if rendered_line.text.trim_end_matches(|c| c == '\r').len() == 0 {
|
||||
// empty line on Windows is `\r`, other is ''
|
||||
let line_bounds = Bounds {
|
||||
origin: line_origin,
|
||||
size: gpui::size(bounds.size.width, line_height),
|
||||
};
|
||||
let pos = inner_position;
|
||||
index += rendered_line.len();
|
||||
if line_bounds.contains(&pos) {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
index += rendered_line.len();
|
||||
}
|
||||
|
||||
// +1 for revert `lines` split `\n`
|
||||
index += 1;
|
||||
// +1 for `\n`
|
||||
index += line_layout.len() + 1;
|
||||
}
|
||||
|
||||
if index > self.text.len() {
|
||||
|
|
@ -2126,7 +2101,7 @@ impl InputState {
|
|||
return;
|
||||
}
|
||||
|
||||
let offset = self.index_for_mouse_position(event.position, window, cx);
|
||||
let offset = self.index_for_mouse_position(event.position);
|
||||
self.select_to(offset, cx);
|
||||
}
|
||||
|
||||
|
|
@ -2504,7 +2479,7 @@ impl EntityInputHandler for InputState {
|
|||
let offset = last_layout.visible_range_offset.start;
|
||||
|
||||
for line in last_layout.lines.iter() {
|
||||
if let Ok(utf8_index) = line.index_for_position(line_point, line_height) {
|
||||
if let Some(utf8_index) = line.index_for_position(line_point, line_height) {
|
||||
return Some(self.offset_to_utf16(offset + utf8_index));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
use std::ops::Range;
|
||||
|
||||
use gpui::{App, Font, LineFragment, Pixels};
|
||||
use gpui::{point, px, size, App, Font, LineFragment, Pixels, Point, ShapedLine, Size, Window};
|
||||
use ropey::Rope;
|
||||
use smallvec::SmallVec;
|
||||
|
||||
use crate::input::RopeExt;
|
||||
|
||||
/// A line with soft wrapped lines info.
|
||||
#[derive(Clone)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub(super) struct LineItem {
|
||||
/// The original line text.
|
||||
line: Rope,
|
||||
|
|
@ -15,7 +16,7 @@ pub(super) struct LineItem {
|
|||
/// FIXME: Here in somecase, the `line_wrapper.wrap_line` has returned different
|
||||
/// like the `window.text_system().shape_text`. So, this value may not equal
|
||||
/// the actual rendered lines.
|
||||
wrapped_lines: Vec<Range<usize>>,
|
||||
pub(super) wrapped_lines: Vec<Range<usize>>,
|
||||
}
|
||||
|
||||
impl LineItem {
|
||||
|
|
@ -176,9 +177,9 @@ impl TextWrapper {
|
|||
let new_range = new_start_offset..new_end_offset;
|
||||
|
||||
let mut new_lines = vec![];
|
||||
|
||||
let wrap_width = self.wrap_width;
|
||||
|
||||
// line not contains `\n`.
|
||||
for (ix, line) in Rope::from(changed_text.slice(new_range))
|
||||
.iter_lines()
|
||||
.enumerate()
|
||||
|
|
@ -237,6 +238,145 @@ impl TextWrapper {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) struct LineLayout {
|
||||
/// Total bytes length of this line.
|
||||
len: usize,
|
||||
/// The soft wrapped lines of this line (Include the first line).
|
||||
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 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
|
||||
}
|
||||
|
||||
/// Get the position (x, y) for the given index in this line layout.
|
||||
///
|
||||
/// - The `offset` is a local byte index in this line layout.
|
||||
/// - The return value is relative to the top-left corner of this line layout, start from (0, 0)
|
||||
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 line in self.wrapped_lines.iter() {
|
||||
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.text.len();
|
||||
offset_y += line_height;
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub(super) fn closest_index_for_x(&self, x: Pixels) -> usize {
|
||||
let mut acc_len = 0;
|
||||
for line in self.wrapped_lines.iter() {
|
||||
if x <= line.width {
|
||||
let ix = line.closest_index_for_x(x);
|
||||
return acc_len + ix;
|
||||
}
|
||||
acc_len += line.text.len();
|
||||
}
|
||||
|
||||
acc_len
|
||||
}
|
||||
|
||||
/// Get the index for the given position (x, y) in this line layout.
|
||||
///
|
||||
/// The `pos` is relative to the top-left corner of this line layout, start from (0, 0)
|
||||
/// The return value is a local byte index in this line layout, start from 0.
|
||||
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 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.closest_index_for_x(pos.x);
|
||||
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::*;
|
||||
|
|
@ -253,15 +393,43 @@ mod tests {
|
|||
};
|
||||
|
||||
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 行。");
|
||||
let mut text = Rope::from(
|
||||
"Hello, 世界!\r\nThis is second line.\nThis is third line.\n这里是第 4 行。",
|
||||
);
|
||||
|
||||
fn fake_wrap_line(_line_str: &str, _wrap_width: Pixels) -> Vec<Boundary> {
|
||||
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<_>>(),
|
||||
);
|
||||
// +1 \n
|
||||
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 行。"],
|
||||
],
|
||||
);
|
||||
|
||||
// Add a new text to end
|
||||
let range = text.len()..text.len();
|
||||
|
|
@ -270,9 +438,20 @@ mod tests {
|
|||
wrapper._update(&text, &range, &Rope::from(new_text), &mut fake_wrap_line);
|
||||
assert_eq!(
|
||||
text.to_string(),
|
||||
"Hello, 世界!\nThis is second line.\nThis is third line.\n这里是第 4 行。New text"
|
||||
"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"],
|
||||
],
|
||||
);
|
||||
|
||||
// Replace first line `Hello` to `AAA`
|
||||
let range = 0..5;
|
||||
|
|
@ -281,9 +460,20 @@ mod tests {
|
|||
wrapper._update(&text, &range, &Rope::from(new_text), &mut fake_wrap_line);
|
||||
assert_eq!(
|
||||
text.to_string(),
|
||||
"AAA, 世界!\nThis is second line.\nThis is third line.\n这里是第 4 行。New text"
|
||||
"AAA, 世界!\r\nThis is second line.\nThis is third line.\n这里是第 4 行。New text"
|
||||
);
|
||||
dbg!(&wrapper.lines);
|
||||
assert_eq!(wrapper.lines.len(), 4);
|
||||
assert_wrapper_lines(
|
||||
&text,
|
||||
&wrapper,
|
||||
&[
|
||||
&["AAA, 世界!\r"],
|
||||
&["This is second line."],
|
||||
&["This is third line."],
|
||||
&["这里是第 4 行。New text"],
|
||||
],
|
||||
);
|
||||
|
||||
// Remove the second line
|
||||
let start_offset = text.line_start_offset(1);
|
||||
|
|
@ -293,9 +483,18 @@ mod tests {
|
|||
wrapper._update(&text, &range, &Rope::from(""), &mut fake_wrap_line);
|
||||
assert_eq!(
|
||||
text.to_string(),
|
||||
"AAA, 世界!\nThis is third line.\n这里是第 4 行。New text"
|
||||
"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"],
|
||||
],
|
||||
);
|
||||
|
||||
// Replace the first 2 lines to "This is a new line."
|
||||
let range = text.line_start_offset(0)..text.line_end_offset(1) + 1;
|
||||
|
|
@ -307,6 +506,15 @@ mod tests {
|
|||
"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"],
|
||||
],
|
||||
);
|
||||
|
||||
// Add a new line at the end
|
||||
let range = text.len()..text.len();
|
||||
|
|
@ -318,6 +526,16 @@ mod tests {
|
|||
"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."],
|
||||
],
|
||||
);
|
||||
|
||||
// Add a new line at the beginning
|
||||
let range = 0..0;
|
||||
|
|
@ -329,6 +547,17 @@ mod tests {
|
|||
"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."],
|
||||
],
|
||||
);
|
||||
|
||||
// Remove all to at least one line in `lines`.
|
||||
let range = 0..text.len();
|
||||
|
|
@ -337,6 +566,7 @@ mod tests {
|
|||
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]);
|
||||
|
||||
// Test update_all
|
||||
let range = 0..text.len();
|
||||
|
|
@ -349,4 +579,16 @@ mod tests {
|
|||
);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue