#![allow(clippy::too_many_arguments)]
use crate::buf::text::Text;
use crate::prelude::*;
use crate::ui::viewport::{LineViewport, RowViewport};
use crate::ui::widget::window::opt::WindowOptions;
use icu::segmenter::{WordSegmenter, options::WordBreakInvariantOptions};
use itertools::Itertools;
use litemap::LiteMap;
use ropey::RopeSlice;
use std::ops::Range;
use super::Viewport;
#[derive(Debug, Copy, Clone, Default, PartialEq, Eq)]
pub struct ViewportLineRange {
start_line_idx: usize,
end_line_idx: usize,
}
impl ViewportLineRange {
pub fn new(line_idx_range: Range<usize>) -> Self {
Self {
start_line_idx: line_idx_range.start,
end_line_idx: line_idx_range.end,
}
}
pub fn is_empty(&self) -> bool {
self.end_line_idx <= self.start_line_idx
}
pub fn len(&self) -> usize {
self.end_line_idx - self.start_line_idx
}
pub fn start_line_idx(&self) -> usize {
self.start_line_idx
}
pub fn end_line_idx(&self) -> usize {
self.end_line_idx
}
}
pub fn sync(
opts: &WindowOptions,
text: &Text,
shape: &U16Rect,
start_line: usize,
start_column: usize,
) -> (ViewportLineRange, LiteMap<usize, LineViewport>) {
let height = shape.height();
let width = shape.width();
if height == 0 || width == 0 {
return (ViewportLineRange::default(), LiteMap::new());
}
match (opts.wrap(), opts.line_break()) {
(false, _) => sync_nowrap(text, shape, start_line, start_column),
(true, false) => {
sync_wrap_nolinebreak(text, shape, start_line, start_column)
}
(true, true) => sync_wrap_linebreak(text, shape, start_line, start_column),
}
}
fn _end_char_and_prefills(
text: &Text,
bline: &RopeSlice,
l: usize,
c: usize,
end_width: usize,
) -> (usize, usize) {
let c_width = text.width_until(l, c);
if c_width > end_width {
let c_width_before = text.width_before(l, c);
(c, end_width.saturating_sub(c_width_before))
} else {
debug_assert!(bline.len_chars() > 0);
let next_to_last_visible_char =
text.last_char_on_line_no_eol(l).unwrap_or(0_usize) + 1;
let c_next = std::cmp::min(c + 1, next_to_last_visible_char);
(c_next, 0_usize)
}
}
fn proc_line_nowrap(
text: &Text,
start_column: usize,
current_line: usize,
current_row: u16,
_window_height: u16,
window_width: u16,
) -> (LiteMap<u16, RowViewport>, usize, usize, u16) {
let bufline = text.rope().line(current_line);
let (start_char, start_fills, end_char, end_fills) = if bufline.len_chars()
== 0
{
(0_usize, 0_usize, 0_usize, 0_usize)
} else {
match text.char_after(current_line, start_column) {
Some(start_char) => {
let start_fills = {
let width_before = text.width_before(current_line, start_char);
width_before.saturating_sub(start_column)
};
let end_width = start_column + window_width as usize;
let (end_char, end_fills) = match text.char_at(current_line, end_width)
{
Some(c) => {
_end_char_and_prefills(text, &bufline, current_line, c, end_width)
}
None => {
(bufline.len_chars(), 0_usize)
}
};
(start_char, start_fills, end_char, end_fills)
}
None => (0_usize, 0_usize, 0_usize, 0_usize),
}
};
let mut rows: LiteMap<u16, RowViewport> = LiteMap::with_capacity(1);
rows.insert(current_row, RowViewport::new(start_char..end_char));
(rows, start_fills, end_fills, current_row)
}
fn sync_nowrap(
text: &Text,
shape: &U16Rect,
start_line: usize,
start_column: usize,
) -> (ViewportLineRange, LiteMap<usize, LineViewport>) {
let height = shape.height();
let width = shape.width();
let buffer_len_lines = text.rope().len_lines();
let mut line_viewports: LiteMap<usize, LineViewport> =
LiteMap::with_capacity(height as usize);
let mut current_row = 0_u16;
let mut current_line = start_line;
if current_line < buffer_len_lines {
while current_row < height && current_line < buffer_len_lines {
let (rows, start_fills, end_fills, _) = proc_line_nowrap(
text,
start_column,
current_line,
current_row,
height,
width,
);
line_viewports.insert(
current_line,
LineViewport::new(rows, start_fills, end_fills),
);
current_line += 1;
current_row += 1;
}
(
ViewportLineRange::new(start_line..current_line),
line_viewports,
)
} else {
(ViewportLineRange::default(), LiteMap::new())
}
}
fn proc_line_wrap_nolinebreak(
text: &Text,
start_column: usize,
current_line: usize,
mut current_row: u16,
window_height: u16,
window_width: u16,
) -> (LiteMap<u16, RowViewport>, usize, usize, u16) {
let bufline = text.rope().line(current_line);
let bufline_len_chars = bufline.len_chars();
if bufline_len_chars == 0 {
let mut rows: LiteMap<u16, RowViewport> = LiteMap::with_capacity(1);
rows.insert(current_row, RowViewport::new(0..0));
(rows, 0_usize, 0_usize, current_row)
} else {
let mut rows: LiteMap<u16, RowViewport> =
LiteMap::with_capacity(window_height as usize);
match text.char_after(current_line, start_column) {
Some(mut start_char) => {
let start_fills = {
let width_before = text.width_before(current_line, start_char);
width_before.saturating_sub(start_column)
};
let mut end_width = start_column + window_width as usize;
let mut end_fills = 0_usize;
debug_assert!(current_row < window_height);
while current_row < window_height {
let (end_char, end_fills_result) = match text
.char_at(current_line, end_width)
{
Some(c) => {
_end_char_and_prefills(text, &bufline, current_line, c, end_width)
}
None => {
(bufline_len_chars, 0_usize)
}
};
end_fills = end_fills_result;
rows.insert(current_row, RowViewport::new(start_char..end_char));
debug_assert!(bufline.len_chars() > 0);
if end_char
> text
.last_char_on_line_no_eol(current_line)
.unwrap_or(0_usize)
{
break;
}
current_row += 1;
start_char = end_char;
end_width =
text.width_before(current_line, end_char) + window_width as usize;
}
(rows, start_fills, end_fills, current_row)
}
None => (rows, 0_usize, 0_usize, current_row),
}
}
}
fn sync_wrap_nolinebreak(
text: &Text,
shape: &U16Rect,
start_line: usize,
start_column: usize,
) -> (ViewportLineRange, LiteMap<usize, LineViewport>) {
let height = shape.height();
let width = shape.width();
let buffer_len_lines = text.rope().len_lines();
let mut line_viewports: LiteMap<usize, LineViewport> =
LiteMap::with_capacity(height as usize);
let mut current_row = 0_u16;
let mut current_line = start_line;
if current_line < buffer_len_lines {
while current_row < height && current_line < buffer_len_lines {
let (rows, start_fills, end_fills, changed_current_row) =
proc_line_wrap_nolinebreak(
text,
start_column,
current_line,
current_row,
height,
width,
);
current_row = changed_current_row;
line_viewports.insert(
current_line,
LineViewport::new(rows, start_fills, end_fills),
);
current_line += 1;
current_row += 1;
}
(
ViewportLineRange::new(start_line..current_line),
line_viewports,
)
} else {
(ViewportLineRange::default(), LiteMap::new())
}
}
fn _find_word_by_char(
words: &[&str],
word_end_chars_index: &LiteMap<usize, usize>,
char_idx: usize,
) -> (usize, usize, usize) {
let mut low = 0;
let mut high = words.len() - 1;
while low <= high {
let mid = (low + high) / 2;
let start_char_idx = if mid > 0 {
*word_end_chars_index.get(&(mid - 1)).unwrap()
} else {
0_usize
};
let end_char_idx = *word_end_chars_index.get(&mid).unwrap();
if start_char_idx <= char_idx && end_char_idx > char_idx {
return (mid, start_char_idx, end_char_idx);
} else if start_char_idx > char_idx {
high = mid - 1;
} else {
low = mid + 1;
}
}
unreachable!()
}
fn _part1(
words: &[&str],
words_end_char_idx: &LiteMap<usize, usize>,
text: &Text,
bline: &RopeSlice,
l: usize,
c: usize,
end_width: usize,
start_char: usize,
last_word_is_too_long: &mut Option<(usize, usize, usize, usize)>,
) -> (usize, usize) {
let (wd_idx, start_c_of_wd, end_c_of_wd) =
_find_word_by_char(words, words_end_char_idx, c);
let end_c_width = text.width_before(l, end_c_of_wd);
if end_c_width > end_width {
if start_c_of_wd > start_char {
_end_char_and_prefills(text, bline, l, start_c_of_wd - 1, end_width)
} else {
debug_assert!(start_c_of_wd <= start_char);
*last_word_is_too_long = Some((wd_idx, start_c_of_wd, end_c_of_wd, c));
_end_char_and_prefills(text, bline, l, c, end_width)
}
} else {
debug_assert_eq!(c + 1, end_c_of_wd);
let c_next = std::cmp::min(end_c_of_wd, bline.len_chars());
(c_next, 0_usize)
}
}
fn _cloned_line_max_len(window_height: u16, window_width: u16) -> usize {
window_height as usize * window_width as usize * 2 + 16
}
fn proc_line_wrap_linebreak(
text: &Text,
start_column: usize,
current_line: usize,
mut current_row: u16,
window_height: u16,
window_width: u16,
) -> (LiteMap<u16, RowViewport>, usize, usize, u16) {
let bufline = text.rope().line(current_line);
if bufline.len_chars() == 0 {
let mut rows: LiteMap<u16, RowViewport> = LiteMap::with_capacity(1);
rows.insert(current_row, RowViewport::new(0..0));
(rows, 0_usize, 0_usize, current_row)
} else {
let mut rows: LiteMap<u16, RowViewport> =
LiteMap::with_capacity(window_height as usize);
let cloned_start_char = text
.char_before(current_line, start_column)
.unwrap_or(0_usize);
let cloned_line = text
.clone_line(
current_line,
cloned_start_char,
_cloned_line_max_len(window_height, window_width),
)
.unwrap();
trace!(
"cloned_line({}):{:?}, start_column:{}",
cloned_line.len(),
cloned_line.as_str(),
start_column
);
let segmenter =
WordSegmenter::new_auto(WordBreakInvariantOptions::default());
let words: Vec<&str> = segmenter
.segment_str(&cloned_line)
.tuple_windows()
.map(|(i, j)| &cloned_line[i..j])
.collect();
let words_end_char_idx = words
.iter()
.enumerate()
.scan(cloned_start_char, |state, (i, wd)| {
*state += wd.chars().count();
Some((i, *state))
})
.collect::<LiteMap<usize, usize>>();
match text.char_after(current_line, start_column) {
Some(mut start_char) => {
let start_fills = {
let width_before = text.width_before(current_line, start_char);
width_before.saturating_sub(start_column)
};
let mut end_width = start_column + window_width as usize;
let mut end_fills = 0_usize;
let mut last_word_is_too_long: Option<(usize, usize, usize, usize)> =
None;
debug_assert!(current_row < window_height);
while current_row < window_height {
let (end_char, end_fills_result) =
match text.char_at(current_line, end_width) {
Some(c) => {
match last_word_is_too_long {
Some((
last_wd_idx,
start_c_of_last_wd,
end_c_of_last_wd,
_continued_c_of_last_wd,
)) => {
match text.char_at(current_line, end_width) {
Some(c) => {
if end_c_of_last_wd > c {
last_word_is_too_long = Some((
last_wd_idx,
start_c_of_last_wd,
end_c_of_last_wd,
c,
));
_end_char_and_prefills(
text,
&bufline,
current_line,
c,
end_width,
)
} else {
_part1(
&words,
&words_end_char_idx,
text,
&bufline,
current_line,
c,
end_width,
start_char,
&mut last_word_is_too_long,
)
}
}
None => {
(bufline.len_chars(), 0_usize)
}
}
}
None => {
_part1(
&words,
&words_end_char_idx,
text,
&bufline,
current_line,
c,
end_width,
start_char,
&mut last_word_is_too_long,
)
}
}
}
None => {
(bufline.len_chars(), 0_usize)
}
};
end_fills = end_fills_result;
rows.insert(current_row, RowViewport::new(start_char..end_char));
debug_assert!(bufline.len_chars() > 0);
if end_char
> text
.last_char_on_line_no_eol(current_line)
.unwrap_or(0_usize)
{
break;
}
current_row += 1;
start_char = end_char;
end_width =
text.width_before(current_line, end_char) + window_width as usize;
}
(rows, start_fills, end_fills, current_row)
}
None => (rows, 0_usize, 0_usize, current_row),
}
}
}
fn sync_wrap_linebreak(
text: &Text,
shape: &U16Rect,
start_line: usize,
start_column: usize,
) -> (ViewportLineRange, LiteMap<usize, LineViewport>) {
let height = shape.height();
let width = shape.width();
let buffer_len_lines = text.rope().len_lines();
let mut line_viewports: LiteMap<usize, LineViewport> =
LiteMap::with_capacity(height as usize);
let mut current_row = 0_u16;
let mut current_line = start_line;
if current_line < buffer_len_lines {
while current_row < height && current_line < buffer_len_lines {
let (rows, start_fills, end_fills, changed_current_row) =
proc_line_wrap_linebreak(
text,
start_column,
current_line,
current_row,
height,
width,
);
current_row = changed_current_row;
line_viewports.insert(
current_line,
LineViewport::new(rows, start_fills, end_fills),
);
current_line += 1;
current_row += 1;
}
(
ViewportLineRange::new(start_line..current_line),
line_viewports,
)
} else {
(ViewportLineRange::default(), LiteMap::new())
}
}
mod detail {
use crate::buf::text::Text;
#[derive(Debug, Copy, Clone)]
pub struct AdjustOptions {
pub no_leftward: bool,
pub no_rightward: bool,
}
impl AdjustOptions {
pub fn no_leftward() -> Self {
Self {
no_leftward: true,
no_rightward: false,
}
}
pub fn no_rightward() -> Self {
Self {
no_leftward: false,
no_rightward: true,
}
}
pub fn all() -> Self {
Self {
no_leftward: false,
no_rightward: false,
}
}
}
pub fn cursor_width_to_left_no_eol(
text: &Text,
target_cursor_line: usize,
target_cursor_char: usize,
) -> usize {
let mut target_cursor_width =
text.width_before(target_cursor_line, target_cursor_char);
let target_is_eol = text.is_eol(target_cursor_line, target_cursor_char);
if target_is_eol {
target_cursor_width =
match text.last_char_on_line_no_eol(target_cursor_line) {
Some(last_visible_char) => {
text.width_before(target_cursor_line, last_visible_char)
}
None => target_cursor_width.saturating_sub(1),
};
}
target_cursor_width
}
}
mod nowrap_detail {
use super::*;
fn to_left(
text: &Text,
_window_actual_shape: &U16Rect,
target_viewport_start_column: usize,
target_cursor_line: usize,
target_cursor_char: usize,
) -> Option<usize> {
if cfg!(debug_assertions) {
match text.char_at(target_cursor_line, target_viewport_start_column) {
Some(target_viewport_start_char) => trace!(
"target_cursor_line:{},target_cursor_char:{}({:?}),viewport_start_column:{},viewport_start_char:{}({:?})",
target_cursor_line,
target_cursor_char,
text
.rope()
.line(target_cursor_line)
.get_char(target_cursor_char)
.unwrap_or('?'),
target_viewport_start_column,
target_viewport_start_char,
text
.rope()
.line(target_cursor_line)
.get_char(target_viewport_start_char)
.unwrap_or('?')
),
None => trace!(
"target_cursor_line:{},target_cursor_char:{}({:?}),viewport_start_column:{},viewport_start_char:None",
target_cursor_line,
target_cursor_char,
text
.rope()
.line(target_cursor_line)
.get_char(target_cursor_char)
.unwrap_or('?'),
target_viewport_start_column,
),
}
}
let target_cursor_width = detail::cursor_width_to_left_no_eol(
text,
target_cursor_line,
target_cursor_char,
);
let on_left_side = target_cursor_width < target_viewport_start_column;
if on_left_side {
let start_column = target_cursor_width;
Some(start_column)
} else {
None
}
}
fn to_right(
text: &Text,
window_actual_shape: &U16Rect,
target_viewport_start_column: usize,
target_cursor_line: usize,
target_cursor_char: usize,
) -> Option<usize> {
let width = window_actual_shape.width();
let viewport_end_column = target_viewport_start_column + width as usize;
if cfg!(debug_assertions) {
let target_viewport_start_char = match text
.char_after(target_cursor_line, target_viewport_start_column)
{
Some(c) => {
format!("{}({:?})", c, text.rope().line(target_cursor_line).char(c))
}
None => "None".to_string(),
};
let viewport_end_char =
match text.char_at(target_cursor_line, viewport_end_column) {
Some(c) => {
format!("{}({:?})", c, text.rope().line(target_cursor_line).char(c))
}
None => "None".to_string(),
};
trace!(
"target_cursor_line:{},target_cursor_char:{}({:?}),viewport_start_column:{},viewport_start_char:{},viewport_end_column:{},viewport_end_char:{}",
target_cursor_line,
target_cursor_char,
text
.rope()
.line(target_cursor_line)
.get_char(target_cursor_char)
.unwrap_or('?'),
target_viewport_start_column,
target_viewport_start_char,
viewport_end_column,
viewport_end_char,
);
}
let target_is_eol = text.is_eol(target_cursor_line, target_cursor_char);
let target_cursor_width = text
.width_until(target_cursor_line, target_cursor_char)
+ if target_is_eol { 1 } else { 0 }; let on_right_side = target_cursor_width > viewport_end_column;
if on_right_side {
let end_column = target_cursor_width;
let start_column = end_column.saturating_sub(width as usize);
Some(start_column)
} else {
None
}
}
pub fn adjust_nowrap(
opts: detail::AdjustOptions,
text: &Text,
window_actual_shape: &U16Rect,
target_cursor_line: usize,
target_cursor_char: usize,
start_line: usize,
start_column: usize,
) -> (usize, usize) {
debug_assert!(!(opts.no_leftward && opts.no_rightward));
if opts.no_leftward {
if cfg!(debug_assertions) {
debug_assert!(
to_left(
text,
window_actual_shape,
start_column,
target_cursor_line,
target_cursor_char,
)
.is_none()
);
}
} else {
let start_column_on_left_side = to_left(
text,
window_actual_shape,
start_column,
target_cursor_line,
target_cursor_char,
);
if let Some(start_column_left) = start_column_on_left_side {
return (start_line, start_column_left);
}
}
if opts.no_rightward {
if cfg!(debug_assertions) {
debug_assert!(
to_right(
text,
window_actual_shape,
start_column,
target_cursor_line,
target_cursor_char,
)
.is_none()
);
}
} else {
let start_column_on_right_side = to_right(
text,
window_actual_shape,
start_column,
target_cursor_line,
target_cursor_char,
);
if let Some(start_column_right) = start_column_on_right_side {
return (start_line, start_column_right);
}
}
(start_line, start_column)
}
}
mod wrap_detail {
use super::*;
pub type SyncFn = fn(
&Text,
&U16Rect,
usize,
usize,
) -> (
/* line range */ ViewportLineRange,
/* lines_viewport */ LiteMap<usize, LineViewport>,
);
pub type ProcessLineFn = fn(
&Text,
usize,
usize,
u16,
u16,
u16,
) -> (
/* rows */ LiteMap<u16, RowViewport>,
/* start_fills */ usize,
/* end_fills */ usize,
/* next_current_row */ u16,
);
pub fn maximized_viewport_height(height: u16) -> u16 {
height.saturating_add(3)
}
fn find_start_char(
proc_fn: ProcessLineFn,
text: &Text,
window_actual_shape: &U16Rect,
target_cursor_line: usize,
target_cursor_char: usize,
mut start_column: usize,
) -> usize {
let bufline = text.rope().line(target_cursor_line);
let bufline_len_char = bufline.len_chars();
let bufline_chars_width =
text.width_until(target_cursor_line, bufline_len_char);
while start_column < bufline_chars_width {
let (rows, _start_fills, _end_fills, _) = proc_fn(
text,
start_column,
target_cursor_line,
0_u16,
window_actual_shape.height(),
window_actual_shape.width(),
);
let (_last_row_idx, last_row_viewport) = rows.last().unwrap();
if last_row_viewport.end_char_idx() > target_cursor_char {
return start_column;
}
start_column += 1;
}
unreachable!()
}
fn reverse_search_start_column(
proc_fn: ProcessLineFn,
text: &Text,
window_actual_shape: &U16Rect,
target_cursor_line: usize,
target_cursor_char: usize,
) -> usize {
let target_is_eol = text.is_eol(target_cursor_line, target_cursor_char);
let target_cursor_width = text
.width_until(target_cursor_line, target_cursor_char)
+ if target_is_eol { 1 } else { 0 };
let approximate_start_column = target_cursor_width.saturating_sub(
(window_actual_shape.height() as usize)
* (window_actual_shape.width() as usize),
);
find_start_char(
proc_fn,
text,
window_actual_shape,
target_cursor_line,
target_cursor_char,
approximate_start_column,
)
}
fn to_left_1(
proc_fn: ProcessLineFn,
text: &Text,
window_actual_shape: &U16Rect,
target_viewport_start_column: usize,
target_cursor_line: usize,
target_cursor_char: usize,
) -> Option<usize> {
let mut start_column = target_viewport_start_column;
if cfg!(debug_assertions) {
match text.char_at(target_cursor_line, target_viewport_start_column) {
Some(target_viewport_start_char) => trace!(
"target_cursor_line:{},target_cursor_char:{}({:?}),viewport_start_column:{},viewport_start_char:{}({:?})",
target_cursor_line,
target_cursor_char,
text
.rope()
.line(target_cursor_line)
.get_char(target_cursor_char)
.unwrap_or('?'),
target_viewport_start_column,
target_viewport_start_char,
text
.rope()
.line(target_cursor_line)
.get_char(target_viewport_start_char)
.unwrap_or('?')
),
None => trace!(
"target_cursor_line:{},target_cursor_char:{}({:?}),viewport_start_column:{},viewport_start_char:None",
target_cursor_line,
target_cursor_char,
text
.rope()
.line(target_cursor_line)
.get_char(target_cursor_char)
.unwrap_or('?'),
target_viewport_start_column,
),
}
}
let mut on_left_side = false;
debug_assert!(text.rope().get_line(target_cursor_line).is_some());
let last_char = text
.last_char_on_line(target_cursor_line) .unwrap_or(0_usize);
let (
preview_target_rows,
_preview_target_start_fills,
_preview_target_end_fills,
_,
) = proc_fn(
text,
start_column,
target_cursor_line,
0_u16,
maximized_viewport_height(window_actual_shape.height()),
window_actual_shape.width(),
);
let extra_space_left = match preview_target_rows.last() {
Some((_last_row_idx, last_row_viewport)) => {
last_row_viewport.end_char_idx() > last_char
}
None => true,
};
if extra_space_left {
let start_column_include_last_visible_char = reverse_search_start_column(
proc_fn,
text,
window_actual_shape,
target_cursor_line,
last_char,
);
if start_column > start_column_include_last_visible_char {
start_column = start_column_include_last_visible_char;
on_left_side = true;
}
}
let target_cursor_width = detail::cursor_width_to_left_no_eol(
text,
target_cursor_line,
target_cursor_char,
);
if target_cursor_width < start_column {
on_left_side = true;
start_column = target_cursor_width;
}
if on_left_side {
return Some(start_column);
}
None
}
fn to_right_1(
proc_fn: ProcessLineFn,
text: &Text,
window_actual_shape: &U16Rect,
target_viewport_start_column: usize,
target_cursor_line: usize,
target_cursor_char: usize,
) -> Option<usize> {
let height = window_actual_shape.height();
let width = window_actual_shape.width();
let (
preview_target_rows,
_preview_target_start_fills,
_preview_target_end_fills,
_,
) = proc_fn(
text,
target_viewport_start_column,
target_cursor_line,
0_u16,
height,
width,
);
debug_assert!(preview_target_rows.last().is_some());
let (_last_row_idx, last_row_viewport) =
preview_target_rows.last().unwrap();
let on_right_side = last_row_viewport.end_char_idx()
> last_row_viewport.start_char_idx()
&& target_cursor_char >= last_row_viewport.end_char_idx();
if on_right_side {
let start_column = reverse_search_start_column(
proc_fn,
text,
window_actual_shape,
target_cursor_line,
target_cursor_char,
);
Some(start_column)
} else {
None
}
}
pub fn adjust_wrap_1(
opts: detail::AdjustOptions,
proc_fn: ProcessLineFn,
text: &Text,
window_actual_shape: &U16Rect,
target_cursor_line: usize,
target_cursor_char: usize,
start_line: usize,
start_column: usize,
) -> (usize, usize) {
debug_assert!(!(opts.no_leftward && opts.no_rightward));
if opts.no_leftward {
if cfg!(debug_assertions) {
debug_assert!(
to_left_1(
proc_fn,
text,
window_actual_shape,
start_column,
target_cursor_line,
target_cursor_char,
)
.is_none()
);
}
} else {
let start_column_on_left_side = to_left_1(
proc_fn,
text,
window_actual_shape,
start_column,
target_cursor_line,
target_cursor_char,
);
if let Some(start_column_left) = start_column_on_left_side {
return (start_line, start_column_left);
}
}
if opts.no_rightward {
if cfg!(debug_assertions) {
debug_assert!(
to_right_1(
proc_fn,
text,
window_actual_shape,
start_column,
target_cursor_line,
target_cursor_char,
)
.is_none()
);
}
} else {
let start_column_on_right_side = to_right_1(
proc_fn,
text,
window_actual_shape,
start_column,
target_cursor_line,
target_cursor_char,
);
if let Some(start_column_right) = start_column_on_right_side {
return (start_line, start_column_right);
}
}
(start_line, start_column)
}
fn to_left_2_1(
_proc_fn: ProcessLineFn,
text: &Text,
_window_actual_shape: &U16Rect,
target_viewport_start_column: usize,
target_cursor_line: usize,
target_cursor_char: usize,
) -> Option<usize> {
if cfg!(debug_assertions) {
match text.char_at(target_cursor_line, target_viewport_start_column) {
Some(target_viewport_start_char) => trace!(
"target_cursor_line:{},target_cursor_char:{}({:?}),viewport_start_column:{},viewport_start_char:{}({:?})",
target_cursor_line,
target_cursor_char,
text
.rope()
.line(target_cursor_line)
.get_char(target_cursor_char)
.unwrap_or('?'),
target_viewport_start_column,
target_viewport_start_char,
text
.rope()
.line(target_cursor_line)
.get_char(target_viewport_start_char)
.unwrap_or('?')
),
None => trace!(
"target_cursor_line:{},target_cursor_char:{}({:?}),viewport_start_column:{},viewport_start_char:None",
target_cursor_line,
target_cursor_char,
text
.rope()
.line(target_cursor_line)
.get_char(target_cursor_char)
.unwrap_or('?'),
target_viewport_start_column,
),
}
let target_cursor_width =
text.width_before(target_cursor_line, target_cursor_char);
debug_assert_eq!(target_viewport_start_column, 0_usize);
let on_left_side = target_cursor_width < target_viewport_start_column;
debug_assert!(!on_left_side);
}
None
}
fn to_right_2_1(
proc_fn: ProcessLineFn,
text: &Text,
window_actual_shape: &U16Rect,
target_viewport_start_column: usize,
target_cursor_line: usize,
target_cursor_char: usize,
) -> Option<usize> {
debug_assert_eq!(target_viewport_start_column, 0_usize);
let height = window_actual_shape.height();
let width = window_actual_shape.width();
let (
preview_target_rows,
_preview_target_start_fills,
_preview_target_end_fills,
_,
) = proc_fn(
text,
target_viewport_start_column,
target_cursor_line,
0_u16,
height,
width,
);
debug_assert!(preview_target_rows.last().is_some());
let (_last_row_idx, last_row_viewport) =
preview_target_rows.last().unwrap();
let on_right_side = last_row_viewport.end_char_idx()
> last_row_viewport.start_char_idx()
&& target_cursor_char >= last_row_viewport.end_char_idx();
if on_right_side {
debug_assert!(text.is_eol(target_cursor_line, target_cursor_char));
let start_column = reverse_search_start_column(
proc_fn,
text,
window_actual_shape,
target_cursor_line,
target_cursor_char,
);
Some(start_column)
} else {
None
}
}
pub fn adjust_wrap_2_1(
opts: detail::AdjustOptions,
proc_fn: ProcessLineFn,
text: &Text,
window_actual_shape: &U16Rect,
target_cursor_line: usize,
target_cursor_char: usize,
start_line: usize,
start_column: usize,
) -> (usize, usize) {
debug_assert!(!(opts.no_leftward && opts.no_rightward));
if opts.no_leftward {
if cfg!(debug_assertions) {
debug_assert!(
to_left_2_1(
proc_fn,
text,
window_actual_shape,
start_column,
target_cursor_line,
target_cursor_char,
)
.is_none()
);
}
} else {
let start_column_on_left_side = to_left_2_1(
proc_fn,
text,
window_actual_shape,
start_column,
target_cursor_line,
target_cursor_char,
);
if let Some(start_column_left) = start_column_on_left_side {
return (start_line, start_column_left);
}
}
if opts.no_rightward {
if cfg!(debug_assertions) {
debug_assert!(
to_right_2_1(
proc_fn,
text,
window_actual_shape,
start_column,
target_cursor_line,
target_cursor_char,
)
.is_none()
);
}
} else {
let start_column_on_right_side = to_right_2_1(
proc_fn,
text,
window_actual_shape,
start_column,
target_cursor_line,
target_cursor_char,
);
if let Some(start_column_right) = start_column_on_right_side {
return (start_line, start_column_right);
}
}
(start_line, start_column)
}
fn to_left_2_2(
_proc_fn: ProcessLineFn,
text: &Text,
_window_actual_shape: &U16Rect,
target_viewport_start_column: usize,
target_cursor_line: usize,
target_cursor_char: usize,
) -> Option<usize> {
if cfg!(debug_assertions) {
match text.char_at(target_cursor_line, target_viewport_start_column) {
Some(target_viewport_start_char) => trace!(
"target_cursor_line:{},target_cursor_char:{}({:?}),viewport_start_column:{},viewport_start_char:{}({:?})",
target_cursor_line,
target_cursor_char,
text
.rope()
.line(target_cursor_line)
.get_char(target_cursor_char)
.unwrap_or('?'),
target_viewport_start_column,
target_viewport_start_char,
text
.rope()
.line(target_cursor_line)
.get_char(target_viewport_start_char)
.unwrap_or('?')
),
None => trace!(
"target_cursor_line:{},target_cursor_char:{}({:?}),viewport_start_column:{},viewport_start_char:None",
target_cursor_line,
target_cursor_char,
text
.rope()
.line(target_cursor_line)
.get_char(target_cursor_char)
.unwrap_or('?'),
target_viewport_start_column,
),
}
let target_cursor_width =
text.width_before(target_cursor_line, target_cursor_char);
debug_assert_eq!(target_viewport_start_column, 0_usize);
let on_left_side = target_cursor_width < target_viewport_start_column;
debug_assert!(!on_left_side);
}
None
}
fn to_right_2_2(
proc_fn: ProcessLineFn,
lines_viewport: &LiteMap<usize, LineViewport>,
text: &Text,
window_actual_shape: &U16Rect,
target_viewport_start_line: usize,
target_viewport_start_column: usize,
target_cursor_line: usize,
target_cursor_char: usize,
) -> Option<(usize, usize)> {
debug_assert_eq!(target_viewport_start_column, 0_usize);
let height = window_actual_shape.height();
let width = window_actual_shape.width();
debug_assert!(lines_viewport.contains_key(&target_cursor_line));
let current_target_rows =
lines_viewport.get(&target_cursor_line).unwrap().rows();
debug_assert!(current_target_rows.last().is_some());
let (current_last_row_idx, current_last_row_viewport) =
current_target_rows.last().unwrap();
let (
preview_target_rows,
_preview_target_start_fills,
_preview_target_end_fills,
_,
) = proc_fn(
text,
target_viewport_start_column,
target_cursor_line,
0_u16,
height,
width,
);
let fully_show = preview_target_rows.len() == current_target_rows.len();
let is_eol = text.is_eol(target_cursor_line, target_cursor_char);
let is_last_row = *current_last_row_idx == height.saturating_sub(1);
let out_of_view = current_last_row_viewport.end_char_idx()
> current_last_row_viewport.start_char_idx()
&& target_cursor_char >= current_last_row_viewport.end_char_idx();
let on_right_side = fully_show && is_eol && is_last_row && out_of_view;
if on_right_side {
debug_assert!(target_cursor_line > target_viewport_start_line);
Some((target_viewport_start_line + 1, target_viewport_start_column))
} else {
None
}
}
pub fn adjust_wrap_2_2(
opts: detail::AdjustOptions,
proc_fn: ProcessLineFn,
lines_viewport: &LiteMap<usize, LineViewport>,
text: &Text,
window_actual_shape: &U16Rect,
target_cursor_line: usize,
target_cursor_char: usize,
start_line: usize,
start_column: usize,
) -> (usize, usize) {
debug_assert!(!(opts.no_leftward && opts.no_rightward));
if opts.no_leftward {
if cfg!(debug_assertions) {
debug_assert!(
to_left_2_2(
proc_fn,
text,
window_actual_shape,
start_column,
target_cursor_line,
target_cursor_char,
)
.is_none()
);
}
} else {
let start_column_on_left_side = to_left_2_2(
proc_fn,
text,
window_actual_shape,
start_column,
target_cursor_line,
target_cursor_char,
);
if let Some(start_column_left) = start_column_on_left_side {
return (start_line, start_column_left);
}
}
if opts.no_rightward {
if cfg!(debug_assertions) {
debug_assert!(
to_right_2_2(
proc_fn,
lines_viewport,
text,
window_actual_shape,
start_line,
start_column,
target_cursor_line,
target_cursor_char,
)
.is_none()
);
}
} else {
let start_line_column_on_right_side = to_right_2_2(
proc_fn,
lines_viewport,
text,
window_actual_shape,
start_line,
start_column,
target_cursor_line,
target_cursor_char,
);
if let Some((start_line_right, start_column_right)) =
start_line_column_on_right_side
{
return (start_line_right, start_column_right);
}
}
(start_line, start_column)
}
pub fn reverse_search_start_line(
proc_fn: ProcessLineFn,
text: &Text,
window_actual_shape: &U16Rect,
target_cursor_line: usize,
) -> usize {
let height = window_actual_shape.height();
let width = window_actual_shape.width();
let mut n = 0_usize;
let mut current_line = target_cursor_line as isize;
while (n < height as usize) && (current_line >= 0) {
let (rows, _start_fills, _end_fills, _) =
proc_fn(text, 0_usize, current_line as usize, 0_u16, height, width);
n += rows.len();
if current_line == 0 || n >= height as usize {
break;
}
current_line -= 1;
}
if (current_line as usize) < target_cursor_line && n > (height as usize) {
current_line as usize + 1
} else {
current_line as usize
}
}
}
pub fn search_anchor_downward(
viewport: &Viewport,
opts: &WindowOptions,
text: &Text,
shape: &U16Rect,
target_cursor_line: usize,
target_cursor_char: usize,
) -> (usize, usize) {
debug_assert!(target_cursor_line >= viewport.start_line_idx());
let buffer_len_lines = text.rope().len_lines();
let target_cursor_line =
std::cmp::min(target_cursor_line, buffer_len_lines.saturating_sub(1));
let target_cursor_char = std::cmp::min(
target_cursor_char,
text
.last_char_on_line(target_cursor_line)
.unwrap_or(0_usize),
);
match (opts.wrap(), opts.line_break()) {
(false, _) => search_anchor_downward_nowrap(
viewport,
text,
shape,
target_cursor_line,
target_cursor_char,
),
(true, false) => search_anchor_downward_wrap(
sync_wrap_nolinebreak,
proc_line_wrap_nolinebreak,
viewport,
text,
shape,
target_cursor_line,
target_cursor_char,
),
(true, true) => search_anchor_downward_wrap(
sync_wrap_linebreak,
proc_line_wrap_linebreak,
viewport,
text,
shape,
target_cursor_line,
target_cursor_char,
),
}
}
fn search_anchor_downward_nowrap(
viewport: &Viewport,
text: &Text,
window_actual_shape: &U16Rect,
target_cursor_line: usize,
target_cursor_char: usize,
) -> (usize, usize) {
let viewport_start_line = viewport.start_line_idx();
let viewport_start_column = viewport.start_column_idx();
let height = window_actual_shape.height();
let width = window_actual_shape.width();
debug_assert!(viewport.lines().last().is_some());
let (&last_line, _last_line_viewport) = viewport.lines().last().unwrap();
let start_line = if target_cursor_line <= last_line {
viewport_start_line
} else {
let mut n = 0_usize;
let mut current_line = target_cursor_line as isize;
while (n + 1 < height as usize) && (current_line >= 0) {
let current_row = 0_u16;
let (rows, _start_fills, _end_fills, _) = proc_line_nowrap(
text,
viewport_start_column,
current_line as usize,
current_row,
height,
width,
);
n += rows.len();
if current_line == 0 {
break;
}
current_line -= 1;
}
current_line as usize
};
nowrap_detail::adjust_nowrap(
detail::AdjustOptions::all(),
text,
window_actual_shape,
target_cursor_line,
target_cursor_char,
start_line,
viewport.start_column_idx(),
)
}
fn search_anchor_downward_wrap(
sync_fn: wrap_detail::SyncFn,
proc_fn: wrap_detail::ProcessLineFn,
viewport: &Viewport,
text: &Text,
window_actual_shape: &U16Rect,
target_cursor_line: usize,
target_cursor_char: usize,
) -> (usize, usize) {
let viewport_start_line = viewport.start_line_idx();
let viewport_start_column = viewport.start_column_idx();
let height = window_actual_shape.height();
let width = window_actual_shape.width();
let (
preview_target_rows,
_preview_target_start_fills,
_preview_target_end_fills,
_,
) = proc_fn(
text,
0,
target_cursor_line,
0_u16,
wrap_detail::maximized_viewport_height(height),
width,
);
let cannot_fully_contains_target_cursor_line =
preview_target_rows.len() > height as usize;
let only_contains_target_cursor_line =
preview_target_rows.len() == height as usize;
if cannot_fully_contains_target_cursor_line {
let start_line = target_cursor_line;
let start_column = viewport_start_column;
wrap_detail::adjust_wrap_1(
detail::AdjustOptions::all(),
proc_fn,
text,
window_actual_shape,
target_cursor_line,
target_cursor_char,
start_line,
start_column,
)
} else if only_contains_target_cursor_line {
let start_line = target_cursor_line;
let start_column = 0_usize;
wrap_detail::adjust_wrap_2_1(
detail::AdjustOptions::all(),
proc_fn,
text,
window_actual_shape,
target_cursor_line,
target_cursor_char,
start_line,
start_column,
)
} else {
let start_line = wrap_detail::reverse_search_start_line(
proc_fn,
text,
window_actual_shape,
target_cursor_line,
);
let start_line = std::cmp::max(start_line, viewport_start_line);
let start_column = 0_usize;
let (_new_line_range, new_lines_viewport) =
sync_fn(text, window_actual_shape, start_line, start_column);
wrap_detail::adjust_wrap_2_2(
detail::AdjustOptions::all(),
proc_fn,
&new_lines_viewport,
text,
window_actual_shape,
target_cursor_line,
target_cursor_char,
start_line,
start_column,
)
}
}
pub fn search_anchor_upward(
viewport: &Viewport,
opts: &WindowOptions,
text: &Text,
window_actual_shape: &U16Rect,
target_cursor_line: usize,
target_cursor_char: usize,
) -> (usize, usize) {
debug_assert!(target_cursor_line < viewport.end_line_idx());
let buffer_len_lines = text.rope().len_lines();
let target_cursor_line =
std::cmp::min(target_cursor_line, buffer_len_lines.saturating_sub(1));
let target_cursor_char = std::cmp::min(
target_cursor_char,
text
.last_char_on_line(target_cursor_line)
.unwrap_or(0_usize),
);
match (opts.wrap(), opts.line_break()) {
(false, _) => search_anchor_upward_nowrap(
viewport,
text,
window_actual_shape,
target_cursor_line,
target_cursor_char,
),
(true, false) => search_anchor_upward_wrap(
sync_wrap_nolinebreak,
proc_line_wrap_nolinebreak,
viewport,
text,
window_actual_shape,
target_cursor_line,
target_cursor_char,
),
(true, true) => search_anchor_upward_wrap(
sync_wrap_linebreak,
proc_line_wrap_linebreak,
viewport,
text,
window_actual_shape,
target_cursor_line,
target_cursor_char,
),
}
}
fn search_anchor_upward_nowrap(
viewport: &Viewport,
text: &Text,
window_actual_shape: &U16Rect,
target_cursor_line: usize,
target_cursor_char: usize,
) -> (usize, usize) {
let viewport_start_line = viewport.start_line_idx();
let _viewport_start_column = viewport.start_column_idx();
debug_assert!(viewport.lines().first().is_some());
let (&first_line, _first_line_viewport) = viewport.lines().first().unwrap();
let start_line = if target_cursor_line >= first_line {
viewport_start_line
} else {
target_cursor_line
};
nowrap_detail::adjust_nowrap(
detail::AdjustOptions::all(),
text,
window_actual_shape,
target_cursor_line,
target_cursor_char,
start_line,
viewport.start_column_idx(),
)
}
fn search_anchor_upward_wrap(
sync_fn: wrap_detail::SyncFn,
proc_fn: wrap_detail::ProcessLineFn,
viewport: &Viewport,
text: &Text,
window_actual_shape: &U16Rect,
target_cursor_line: usize,
target_cursor_char: usize,
) -> (usize, usize) {
let viewport_start_line = viewport.start_line_idx();
let viewport_start_column = viewport.start_column_idx();
let height = window_actual_shape.height();
let width = window_actual_shape.width();
let (
preview_target_rows,
_preview_target_start_fills,
_preview_target_end_fills,
_,
) = proc_fn(
text,
0,
target_cursor_line,
0_u16,
wrap_detail::maximized_viewport_height(height),
width,
);
let cannot_fully_contains_target_cursor_line =
preview_target_rows.len() > height as usize;
let only_contains_target_cursor_line =
preview_target_rows.len() == height as usize;
if cannot_fully_contains_target_cursor_line {
let start_line = target_cursor_line;
let start_column = viewport_start_column;
wrap_detail::adjust_wrap_1(
detail::AdjustOptions::all(),
proc_fn,
text,
window_actual_shape,
target_cursor_line,
target_cursor_char,
start_line,
start_column,
)
} else if only_contains_target_cursor_line {
let start_line = target_cursor_line;
let start_column = 0_usize;
wrap_detail::adjust_wrap_2_1(
detail::AdjustOptions::all(),
proc_fn,
text,
window_actual_shape,
target_cursor_line,
target_cursor_char,
start_line,
start_column,
)
} else {
let start_line = std::cmp::min(target_cursor_line, viewport_start_line);
let start_column = 0_usize;
let (_new_line_range, new_lines_viewport) =
sync_fn(text, window_actual_shape, start_line, start_column);
wrap_detail::adjust_wrap_2_2(
detail::AdjustOptions::all(),
proc_fn,
&new_lines_viewport,
text,
window_actual_shape,
target_cursor_line,
target_cursor_char,
start_line,
start_column,
)
}
}
pub fn search_anchor_leftward(
viewport: &Viewport,
opts: &WindowOptions,
text: &Text,
window_actual_shape: &U16Rect,
target_cursor_line: usize,
target_cursor_char: usize,
) -> (usize, usize) {
debug_assert!(
target_cursor_line >= viewport.start_line_idx()
&& target_cursor_line < viewport.end_line_idx()
);
let buffer_len_lines = text.rope().len_lines();
let target_cursor_line =
std::cmp::min(target_cursor_line, buffer_len_lines.saturating_sub(1));
let target_cursor_char = std::cmp::min(
target_cursor_char,
text
.last_char_on_line(target_cursor_line)
.unwrap_or(0_usize),
);
match (opts.wrap(), opts.line_break()) {
(false, _) => search_anchor_leftward_nowrap(
viewport,
text,
window_actual_shape,
target_cursor_line,
target_cursor_char,
),
(true, false) => search_anchor_leftward_wrap(
proc_line_wrap_nolinebreak,
viewport,
text,
window_actual_shape,
target_cursor_line,
target_cursor_char,
),
(true, true) => search_anchor_leftward_wrap(
proc_line_wrap_linebreak,
viewport,
text,
window_actual_shape,
target_cursor_line,
target_cursor_char,
),
}
}
fn search_anchor_leftward_nowrap(
viewport: &Viewport,
text: &Text,
window_actual_shape: &U16Rect,
target_cursor_line: usize,
target_cursor_char: usize,
) -> (usize, usize) {
let start_line = viewport.start_line_idx();
let start_column = viewport.start_column_idx();
nowrap_detail::adjust_nowrap(
detail::AdjustOptions::no_rightward(),
text,
window_actual_shape,
target_cursor_line,
target_cursor_char,
start_line,
start_column,
)
}
fn search_anchor_leftward_wrap(
proc_fn: wrap_detail::ProcessLineFn,
viewport: &Viewport,
text: &Text,
window_actual_shape: &U16Rect,
target_cursor_line: usize,
target_cursor_char: usize,
) -> (usize, usize) {
let viewport_start_line = viewport.start_line_idx();
let viewport_start_column = viewport.start_column_idx();
let height = window_actual_shape.height();
let width = window_actual_shape.width();
let (
preview_target_rows,
_preview_target_start_fills,
_preview_target_end_fills,
_,
) = proc_fn(
text,
0,
target_cursor_line,
0_u16,
wrap_detail::maximized_viewport_height(height),
width,
);
let cannot_fully_contains_target_cursor_line =
preview_target_rows.len() > height as usize;
let only_contains_target_cursor_line =
preview_target_rows.len() == height as usize;
if cannot_fully_contains_target_cursor_line {
let start_line = target_cursor_line;
let start_column = viewport_start_column;
wrap_detail::adjust_wrap_1(
detail::AdjustOptions::no_rightward(),
proc_fn,
text,
window_actual_shape,
target_cursor_line,
target_cursor_char,
start_line,
start_column,
)
} else if only_contains_target_cursor_line {
let start_line = target_cursor_line;
let start_column = 0_usize;
wrap_detail::adjust_wrap_2_1(
detail::AdjustOptions::no_rightward(),
proc_fn,
text,
window_actual_shape,
target_cursor_line,
target_cursor_char,
start_line,
start_column,
)
} else {
let start_line = viewport_start_line;
let start_column = 0_usize;
wrap_detail::adjust_wrap_2_2(
detail::AdjustOptions::no_rightward(),
proc_fn,
viewport.lines(),
text,
window_actual_shape,
target_cursor_line,
target_cursor_char,
start_line,
start_column,
)
}
}
pub fn search_anchor_rightward(
viewport: &Viewport,
opts: &WindowOptions,
text: &Text,
window_actual_shape: &U16Rect,
target_cursor_line: usize,
target_cursor_char: usize,
) -> (usize, usize) {
debug_assert!(
target_cursor_line >= viewport.start_line_idx()
&& target_cursor_line < viewport.end_line_idx()
);
let buffer_len_lines = text.rope().len_lines();
let target_cursor_line =
std::cmp::min(target_cursor_line, buffer_len_lines.saturating_sub(1));
let target_cursor_char = std::cmp::min(
target_cursor_char,
text
.last_char_on_line(target_cursor_line)
.unwrap_or(0_usize),
);
match (opts.wrap(), opts.line_break()) {
(false, _) => search_anchor_rightward_nowrap(
viewport,
text,
window_actual_shape,
target_cursor_line,
target_cursor_char,
),
(true, false) => search_anchor_rightward_wrap(
proc_line_wrap_nolinebreak,
viewport,
text,
window_actual_shape,
target_cursor_line,
target_cursor_char,
),
(true, true) => search_anchor_rightward_wrap(
proc_line_wrap_linebreak,
viewport,
text,
window_actual_shape,
target_cursor_line,
target_cursor_char,
),
}
}
fn search_anchor_rightward_nowrap(
viewport: &Viewport,
text: &Text,
window_actual_shape: &U16Rect,
target_cursor_line: usize,
target_cursor_char: usize,
) -> (usize, usize) {
let start_line = viewport.start_line_idx();
let start_column = viewport.start_column_idx();
nowrap_detail::adjust_nowrap(
detail::AdjustOptions::no_leftward(),
text,
window_actual_shape,
target_cursor_line,
target_cursor_char,
start_line,
start_column,
)
}
fn search_anchor_rightward_wrap(
proc_fn: wrap_detail::ProcessLineFn,
viewport: &Viewport,
text: &Text,
window_actual_shape: &U16Rect,
target_cursor_line: usize,
target_cursor_char: usize,
) -> (usize, usize) {
let viewport_start_line = viewport.start_line_idx();
let viewport_start_column = viewport.start_column_idx();
let height = window_actual_shape.height();
let width = window_actual_shape.width();
let (
preview_target_rows,
_preview_target_start_fills,
_preview_target_end_fills,
_,
) = proc_fn(
text,
0,
target_cursor_line,
0_u16,
wrap_detail::maximized_viewport_height(height),
width,
);
let cannot_fully_contains_target_cursor_line =
preview_target_rows.len() > height as usize;
let only_contains_target_cursor_line =
preview_target_rows.len() == height as usize;
if cannot_fully_contains_target_cursor_line {
let start_line = target_cursor_line;
let start_column = viewport_start_column;
wrap_detail::adjust_wrap_1(
detail::AdjustOptions::no_leftward(),
proc_fn,
text,
window_actual_shape,
target_cursor_line,
target_cursor_char,
start_line,
start_column,
)
} else if only_contains_target_cursor_line {
let start_line = target_cursor_line;
let start_column = 0_usize;
wrap_detail::adjust_wrap_2_1(
detail::AdjustOptions::no_leftward(),
proc_fn,
text,
window_actual_shape,
target_cursor_line,
target_cursor_char,
start_line,
start_column,
)
} else {
let start_line = viewport_start_line;
let start_column = 0_usize;
wrap_detail::adjust_wrap_2_2(
detail::AdjustOptions::no_leftward(),
proc_fn,
viewport.lines(),
text,
window_actual_shape,
target_cursor_line,
target_cursor_char,
start_line,
start_column,
)
}
}