use super::CursorViewport;
use super::Viewport;
use crate::buf::text::Text;
use crate::prelude::*;
use crate::ui::viewport::LineViewport;
use crate::ui::viewport::RowViewport;
use crate::ui::widget::window::opt::WindowOptions;
use icu::segmenter::WordSegmenter;
use icu::segmenter::options::WordBreakInvariantOptions;
use itertools::Itertools;
use litemap::LiteMap;
use ropey::RopeSlice;
use std::ops::Range;
#[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,
size: &U16Size,
start_line: usize,
start_column: usize,
) -> (ViewportLineRange, LiteMap<usize, LineViewport>) {
if size.is_zero() {
return (ViewportLineRange::default(), LiteMap::new());
}
match (opts.wrap(), opts.line_break()) {
(false, _) => nowrap_sync(text, size, start_line, start_column),
(true, false) => {
wrap_nolinebreak_sync(text, size, start_line, start_column)
}
(true, true) => wrap_linebreak_sync(text, size, start_line, start_column),
}
}
fn _end_char_and_filled_cols(
text: &Text,
buffer_line: &RopeSlice,
current_line: usize,
end_width_char: usize,
end_width: usize,
) -> (usize, usize) {
let c_width = text.width_until(current_line, end_width_char);
if c_width > end_width {
let c_width_before = text.width_before(current_line, end_width_char);
(end_width_char, end_width.saturating_sub(c_width_before))
} else {
debug_assert!(buffer_line.len_chars() > 0);
let next_to_last_visible_char = text
.last_char_idx_on_line_exclude_eol(current_line)
.unwrap_or(0_usize)
+ 1;
let c_next = std::cmp::min(end_width_char + 1, next_to_last_visible_char);
(c_next, 0_usize)
}
}
#[allow(unused_assignments)]
fn nowrap_line_process(
text: &Text,
start_column: usize,
current_line: usize,
current_row: u16,
_window_height: u16,
window_width: u16,
) -> (LiteMap<u16, RowViewport>, usize, usize, u16) {
let buffer_line = text.rope().line(current_line);
let mut rows: LiteMap<u16, RowViewport> = LiteMap::with_capacity(1);
let mut start_fills: usize = 0;
let mut end_fills: usize = 0;
if buffer_line.len_chars() == 0 {
rows.insert(current_row, RowViewport::new(0..0));
start_fills = 0;
end_fills = 0;
} else {
if let Some(start_char) = text.char_after(current_line, start_column) {
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_f) = match text.char_at(current_line, end_width) {
Some(end_width_char) => _end_char_and_filled_cols(
text,
&buffer_line,
current_line,
end_width_char,
end_width,
),
None => {
(buffer_line.len_chars(), 0)
}
};
end_fills = end_f;
rows.insert(current_row, RowViewport::new(start_char..end_char));
} else {
start_fills = 0;
end_fills = 0;
rows.insert(current_row, RowViewport::new(0..0));
}
}
(rows, start_fills, end_fills, current_row)
}
fn nowrap_sync(
text: &Text,
size: &U16Size,
start_line: usize,
start_column: usize,
) -> (ViewportLineRange, LiteMap<usize, LineViewport>) {
let window_height = size.height();
let window_width = size.width();
let buffer_len_lines = text.rope().len_lines();
let mut line_viewports: LiteMap<usize, LineViewport> =
LiteMap::with_capacity(window_height as usize);
let mut current_row = 0_u16;
let mut current_line = start_line;
if current_line < buffer_len_lines {
while current_row < window_height && current_line < buffer_len_lines {
let (rows, start_fills, end_fills, _last_row) = nowrap_line_process(
text,
start_column,
current_line,
current_row,
window_height,
window_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())
}
}
#[allow(unused_assignments)]
fn wrap_nolinebreak_line_process(
text: &Text,
start_column: usize,
current_line: usize,
current_row: u16,
window_height: u16,
window_width: u16,
) -> (LiteMap<u16, RowViewport>, usize, usize, u16) {
let buffer_line = text.rope().line(current_line);
let mut rows: LiteMap<u16, RowViewport> =
LiteMap::with_capacity(std::cmp::min(
buffer_line.len_chars() / (window_width as usize),
window_height as usize,
));
let mut current_row = current_row;
let mut start_fills: usize = 0;
let mut end_fills: usize = 0;
if buffer_line.len_chars() == 0 {
rows.insert(current_row, RowViewport::new(0..0));
start_fills = 0;
end_fills = 0;
} else {
if let Some(mut start_char) = text.char_after(current_line, start_column) {
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;
debug_assert!(current_row < window_height);
while current_row < window_height {
let (end_char, end_f) = match text.char_at(current_line, end_width) {
Some(end_width_char) => _end_char_and_filled_cols(
text,
&buffer_line,
current_line,
end_width_char,
end_width,
),
None => {
(buffer_line.len_chars(), 0_usize)
}
};
end_fills = end_f;
rows.insert(current_row, RowViewport::new(start_char..end_char));
debug_assert!(buffer_line.len_chars() > 0);
if end_char
> text
.last_char_idx_on_line_exclude_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;
}
} else {
start_fills = 0;
end_fills = 0;
}
}
(rows, start_fills, end_fills, current_row)
}
fn wrap_nolinebreak_sync(
text: &Text,
size: &U16Size,
start_line: usize,
start_column: usize,
) -> (ViewportLineRange, LiteMap<usize, LineViewport>) {
let window_height = size.height();
let window_width = size.width();
let buffer_len_lines = text.rope().len_lines();
let mut line_viewports: LiteMap<usize, LineViewport> =
LiteMap::with_capacity(window_height as usize);
let mut current_row = 0_u16;
let mut current_line = start_line;
if current_line < buffer_len_lines {
while current_row < window_height && current_line < buffer_len_lines {
let (rows, start_fills, end_fills, last_row) =
wrap_nolinebreak_line_process(
text,
start_column,
current_line,
current_row,
window_height,
window_width,
);
current_row = last_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 _binary_search_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,
buffer_line: &RopeSlice,
current_line: usize,
end_width_char: usize,
end_width: usize,
start_char: usize,
last_word_is_too_long: &mut Option<(usize, usize, usize, usize)>,
) -> (usize, usize) {
let (end_wd_idx, start_c_of_end_wd, end_c_of_end_wd) =
_binary_search_word_by_char(words, words_end_char_idx, end_width_char);
let end_c_width = text.width_before(current_line, end_c_of_end_wd);
if end_c_width > end_width {
if start_c_of_end_wd > start_char {
_end_char_and_filled_cols(
text,
buffer_line,
current_line,
start_c_of_end_wd - 1,
end_width,
)
} else {
debug_assert!(start_c_of_end_wd <= start_char);
*last_word_is_too_long = Some((
end_wd_idx,
start_c_of_end_wd,
end_c_of_end_wd,
end_width_char,
));
_end_char_and_filled_cols(
text,
buffer_line,
current_line,
end_width_char,
end_width,
)
}
} else {
debug_assert_eq!(end_width_char + 1, end_c_of_end_wd);
let c_next = std::cmp::min(end_c_of_end_wd, buffer_line.len_chars());
(c_next, 0_usize)
}
}
#[allow(unused_assignments)]
fn wrap_linebreak_line_process(
text: &Text,
start_column: usize,
current_line: usize,
current_row: u16,
window_height: u16,
window_width: u16,
) -> (LiteMap<u16, RowViewport>, usize, usize, u16) {
let buffer_line = text.rope().line(current_line);
let mut rows: LiteMap<u16, RowViewport> =
LiteMap::with_capacity(std::cmp::min(
buffer_line.len_chars() / (window_width as usize),
window_height as usize,
));
let mut current_row = current_row;
let mut start_fills: usize = 0;
let mut end_fills: usize = 0;
if buffer_line.len_chars() == 0 {
rows.insert(current_row, RowViewport::new(0..0));
start_fills = 0;
end_fills = 0;
} else {
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,
(window_height as usize + 1) * (window_width as usize + 1) * 2 + 1,
)
.unwrap();
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>>();
if let Some(mut start_char) = text.char_after(current_line, start_column) {
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 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_f) = match text.char_at(current_line, end_width) {
Some(end_width_char) => {
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,
)) => {
if end_c_of_last_wd > end_width_char {
last_word_is_too_long = Some((
last_wd_idx,
start_c_of_last_wd,
end_c_of_last_wd,
end_width_char,
));
_end_char_and_filled_cols(
text,
&buffer_line,
current_line,
end_width_char,
end_width,
)
} else {
_part1(
&words,
&words_end_char_idx,
text,
&buffer_line,
current_line,
end_width_char,
end_width,
start_char,
&mut last_word_is_too_long,
)
}
}
None => {
_part1(
&words,
&words_end_char_idx,
text,
&buffer_line,
current_line,
end_width_char,
end_width,
start_char,
&mut last_word_is_too_long,
)
}
}
}
None => {
(buffer_line.len_chars(), 0_usize)
}
};
end_fills = end_f;
rows.insert(current_row, RowViewport::new(start_char..end_char));
debug_assert!(buffer_line.len_chars() > 0);
if end_char
> text
.last_char_idx_on_line_exclude_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;
}
} else {
start_fills = 0;
end_fills = 0;
}
}
(rows, start_fills, end_fills, current_row)
}
fn wrap_linebreak_sync(
text: &Text,
size: &U16Size,
start_line: usize,
start_column: usize,
) -> (ViewportLineRange, LiteMap<usize, LineViewport>) {
let height = size.height();
let width = size.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, last_row) =
wrap_linebreak_line_process(
text,
start_column,
current_line,
current_row,
height,
width,
);
current_row = last_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())
}
}
type WrapSyncFn = fn(
&Text,
&U16Size,
usize,
usize,
) -> (
/* line range */ ViewportLineRange,
/* lines_viewport */ LiteMap<usize, LineViewport>,
);
type WrapLineProcessFn = fn(
&Text,
usize,
usize,
u16,
u16,
u16,
) -> (
/* rows */ LiteMap<u16, RowViewport>,
/* start_fills */ usize,
/* end_fills */ usize,
/* next_current_row */ u16,
);
type WrapHorizontalSearchFn =
fn(
WrapSyncFn,
WrapLineProcessFn,
&Viewport,
&CursorViewport,
&WindowOptions,
&Text,
&U16Size,
usize,
usize,
usize,
usize,
) -> (/* start_line */ usize, /* start_column */ usize);
pub fn search(
viewport: &Viewport,
cursor_viewport: &CursorViewport,
opts: &WindowOptions,
text: &Text,
size: &U16Size,
target_cursor_line: usize,
target_cursor_char: usize,
) -> (usize, usize) {
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_line_end_char =
match text.last_char_idx_on_line_include_eol(target_cursor_line) {
Some(last_char) => {
if !text.is_eol(target_cursor_line, last_char)
&& target_cursor_char > last_char
{
last_char + 1
} else {
last_char
}
}
None => {
0
}
};
let target_cursor_char =
std::cmp::min(target_cursor_char, target_cursor_line_end_char);
let (sync_fn, line_process_fn, search_left_fn, search_right_fn): (
WrapSyncFn,
WrapLineProcessFn,
WrapHorizontalSearchFn,
WrapHorizontalSearchFn,
) = if opts.line_break() {
(
wrap_linebreak_sync,
wrap_linebreak_line_process,
wrap_search_left,
wrap_search_right,
)
} else {
(
wrap_nolinebreak_sync,
wrap_nolinebreak_line_process,
wrap_search_left,
wrap_search_right,
)
};
if target_cursor_line < cursor_viewport.line_idx() {
if opts.wrap() {
wrap_search_up(
sync_fn,
line_process_fn,
search_left_fn,
search_right_fn,
viewport,
cursor_viewport,
opts,
text,
size,
target_cursor_line,
target_cursor_char,
)
} else {
nowrap_search_up(
viewport,
cursor_viewport,
text,
size,
target_cursor_line,
target_cursor_char,
)
}
} else {
if opts.wrap() {
wrap_search_down(
sync_fn,
line_process_fn,
search_left_fn,
search_right_fn,
viewport,
cursor_viewport,
opts,
text,
size,
target_cursor_line,
target_cursor_char,
)
} else {
nowrap_search_down(
viewport,
cursor_viewport,
text,
size,
target_cursor_line,
target_cursor_char,
)
}
}
}
fn _if_contains_target_cursor_line(
viewport: &Viewport,
target_cursor_line: usize,
) -> bool {
target_cursor_line >= viewport.start_line_idx()
&& target_cursor_line < viewport.end_line_idx()
}
fn _can_fully_contain_target_cursor_line(
line_process_fn: WrapLineProcessFn,
text: &Text,
size: &U16Size,
target_cursor_line: usize,
) -> (bool, bool) {
let window_height = size.height();
let window_width = size.width();
let (
preview_target_rows,
_preview_target_start_fills,
_preview_target_end_fills,
_,
) = line_process_fn(
text,
0,
target_cursor_line,
0_u16,
window_height.saturating_add(3),
window_width,
);
let cannot_fully_contain_target_cursor_line =
preview_target_rows.len() > window_height as usize;
let can_exactly_contain_target_cursor_line =
preview_target_rows.len() == window_height as usize;
(
cannot_fully_contain_target_cursor_line,
can_exactly_contain_target_cursor_line,
)
}
fn nowrap_search_down(
viewport: &Viewport,
cursor_viewport: &CursorViewport,
text: &Text,
size: &U16Size,
target_cursor_line: usize,
target_cursor_char: usize,
) -> (usize, usize) {
let window_height = size.height();
let already_contains_target_cursor_line =
_if_contains_target_cursor_line(viewport, target_cursor_line);
let current_cursor_column =
text.width_before(cursor_viewport.line_idx(), cursor_viewport.char_idx());
let target_cursor_column =
text.width_before(target_cursor_line, target_cursor_char);
if already_contains_target_cursor_line {
let start_line = viewport.start_line_idx();
let start_column = viewport.start_column_idx();
if target_cursor_column < current_cursor_column {
trace!(
"viewport:{}/{},cursor_viewport:{:?},start_line/column:{}/{},target_cursor:{}/{}",
viewport.start_line_idx(),
viewport.start_column_idx(),
cursor_viewport,
start_line,
start_column,
target_cursor_line,
target_cursor_char,
);
nowrap_search_left(
text,
size,
start_line,
start_column,
target_cursor_line,
target_cursor_char,
)
} else {
nowrap_search_right(
text,
size,
start_line,
start_column,
target_cursor_line,
target_cursor_char,
)
}
} else {
let start_line = std::cmp::max(
0,
(target_cursor_line as isize) - (window_height as isize) + 1,
) as usize;
let start_column = viewport.start_column_idx();
if target_cursor_column < current_cursor_column {
nowrap_search_left(
text,
size,
start_line,
start_column,
target_cursor_line,
target_cursor_char,
)
} else {
nowrap_search_right(
text,
size,
start_line,
start_column,
target_cursor_line,
target_cursor_char,
)
}
}
}
fn nowrap_search_up(
viewport: &Viewport,
cursor_viewport: &CursorViewport,
text: &Text,
size: &U16Size,
target_cursor_line: usize,
target_cursor_char: usize,
) -> (usize, usize) {
let already_contains_target_cursor_line =
_if_contains_target_cursor_line(viewport, target_cursor_line);
let current_cursor_column =
text.width_before(cursor_viewport.line_idx(), cursor_viewport.char_idx());
let target_cursor_column =
text.width_before(target_cursor_line, target_cursor_char);
if already_contains_target_cursor_line {
let start_line = viewport.start_line_idx();
let start_column = viewport.start_column_idx();
if target_cursor_column < current_cursor_column {
nowrap_search_left(
text,
size,
start_line,
start_column,
target_cursor_line,
target_cursor_char,
)
} else {
nowrap_search_right(
text,
size,
start_line,
start_column,
target_cursor_line,
target_cursor_char,
)
}
} else {
let start_line = target_cursor_line;
let start_column = viewport.start_column_idx();
if target_cursor_column < current_cursor_column {
nowrap_search_left(
text,
size,
start_line,
start_column,
target_cursor_line,
target_cursor_char,
)
} else {
nowrap_search_right(
text,
size,
start_line,
start_column,
target_cursor_line,
target_cursor_char,
)
}
}
}
fn _reverse_search_target_cursor_line(
sync_fn: WrapSyncFn,
line_process_fn: WrapLineProcessFn,
viewport: &Viewport,
text: &Text,
size: &U16Size,
target_cursor_line: usize,
target_cursor_char: usize,
) -> usize {
let window_height = size.height();
let window_width = size.width();
let mut current_row: usize = 0;
let mut current_line: isize = target_cursor_line as isize;
while (current_row < window_height as usize) && (current_line >= 0) {
let (rows, _start_fills, _end_fills, _last_row) = line_process_fn(
text,
0,
current_line as usize,
0,
window_height,
window_width,
);
current_row += rows.len();
current_line -= 1;
}
let start_line = if current_row > window_height as usize {
debug_assert!(current_line + 2 <= target_cursor_line as isize);
current_line + 2
} else {
debug_assert!(current_line < target_cursor_line as isize);
current_line + 1
};
debug_assert!(start_line >= 0);
debug_assert!(start_line <= target_cursor_line as isize);
let start_line =
std::cmp::max(viewport.start_line_idx(), start_line as usize);
let (
cannot_fully_contain_target_cursor_line,
can_exactly_contain_target_cursor_line,
) = _can_fully_contain_target_cursor_line(
line_process_fn,
text,
size,
target_cursor_line,
);
if cannot_fully_contain_target_cursor_line
|| can_exactly_contain_target_cursor_line
{
return start_line;
}
let (_preview_line_range, preview_viewport) =
sync_fn(text, size, start_line, 0);
debug_assert!(preview_viewport.last().is_some());
let (last_preview_line, last_preview_line_viewport) =
preview_viewport.last().unwrap();
let target_cursor_char_is_at_right_bottom_corner = {
let is_last_line = *last_preview_line == target_cursor_line;
if is_last_line {
if let Some((_last_preview_row, last_preview_row_viewport)) =
last_preview_line_viewport.rows().last()
{
let last_row_not_empty = last_preview_row_viewport.end_char_idx()
> last_preview_row_viewport.start_char_idx();
let at_last_row =
last_preview_row_viewport.end_char_idx() <= target_cursor_char;
let last_row_end_column = text.width_before(
target_cursor_line,
last_preview_row_viewport.end_char_idx(),
);
let last_row_start_column = text.width_before(
target_cursor_line,
last_preview_row_viewport.start_char_idx(),
);
let last_row_width =
last_row_end_column.saturating_sub(last_row_start_column);
let last_row_use_full_width = last_row_width >= window_width as usize;
last_row_not_empty && at_last_row && last_row_use_full_width
} else {
false
}
} else {
false
}
};
let start_line = if target_cursor_char_is_at_right_bottom_corner {
start_line + 1
} else {
start_line
};
std::cmp::min(start_line, target_cursor_line)
}
fn wrap_search_down(
sync_fn: WrapSyncFn,
line_process_fn: WrapLineProcessFn,
search_left_fn: WrapHorizontalSearchFn,
search_right_fn: WrapHorizontalSearchFn,
viewport: &Viewport,
cursor_viewport: &CursorViewport,
opts: &WindowOptions,
text: &Text,
size: &U16Size,
target_cursor_line: usize,
target_cursor_char: usize,
) -> (usize, usize) {
let current_cursor_column =
text.width_before(cursor_viewport.line_idx(), cursor_viewport.char_idx());
let target_cursor_column =
text.width_before(target_cursor_line, target_cursor_char);
let start_line = _reverse_search_target_cursor_line(
sync_fn,
line_process_fn,
viewport,
text,
size,
target_cursor_line,
target_cursor_char,
);
let start_column = viewport.start_column_idx();
if target_cursor_column < current_cursor_column {
search_left_fn(
sync_fn,
line_process_fn,
viewport,
cursor_viewport,
opts,
text,
size,
start_line,
start_column,
target_cursor_line,
target_cursor_char,
)
} else {
search_right_fn(
sync_fn,
line_process_fn,
viewport,
cursor_viewport,
opts,
text,
size,
start_line,
start_column,
target_cursor_line,
target_cursor_char,
)
}
}
fn wrap_search_up(
sync_fn: WrapSyncFn,
line_process_fn: WrapLineProcessFn,
search_left_fn: WrapHorizontalSearchFn,
search_right_fn: WrapHorizontalSearchFn,
viewport: &Viewport,
cursor_viewport: &CursorViewport,
opts: &WindowOptions,
text: &Text,
size: &U16Size,
target_cursor_line: usize,
target_cursor_char: usize,
) -> (usize, usize) {
let current_cursor_column =
text.width_before(cursor_viewport.line_idx(), cursor_viewport.char_idx());
let target_cursor_column =
text.width_before(target_cursor_line, target_cursor_char);
let start_line = std::cmp::min(target_cursor_line, viewport.start_line_idx());
let start_column = viewport.start_column_idx();
if target_cursor_column < current_cursor_column {
search_left_fn(
sync_fn,
line_process_fn,
viewport,
cursor_viewport,
opts,
text,
size,
start_line,
start_column,
target_cursor_line,
target_cursor_char,
)
} else {
search_right_fn(
sync_fn,
line_process_fn,
viewport,
cursor_viewport,
opts,
text,
size,
start_line,
start_column,
target_cursor_line,
target_cursor_char,
)
}
}
fn _find_target_cursor_column_exclude_eol(
text: &Text,
target_cursor_line: usize,
target_cursor_char: usize,
) -> usize {
let mut target_cursor_column =
text.width_before(target_cursor_line, target_cursor_char);
if text.is_eol_or_line_end(target_cursor_line, target_cursor_char) {
if cfg!(debug_assertions) {
if let Some(last_char_exclude_eol) =
text.last_char_idx_on_line_exclude_eol(target_cursor_line)
{
debug_assert!(
target_cursor_char > last_char_exclude_eol
&& target_cursor_char <= last_char_exclude_eol + 2
);
}
}
let last_char_exclude_eol = text
.last_char_idx_on_line_exclude_eol(target_cursor_line)
.unwrap_or(0);
target_cursor_column =
text.width_before(target_cursor_line, last_char_exclude_eol);
}
target_cursor_column
}
fn _find_target_cursor_column_include_eol(
text: &Text,
target_cursor_line: usize,
target_cursor_char: usize,
) -> usize {
let out_of_line =
text.is_eol_or_line_end(target_cursor_line, target_cursor_char);
text.width_until(target_cursor_line, target_cursor_char)
+ if out_of_line { 1 } else { 0 }
}
fn nowrap_search_left(
text: &Text,
_size: &U16Size,
suggest_start_line: usize,
suggest_start_column: usize,
target_cursor_line: usize,
target_cursor_char: usize,
) -> (usize, usize) {
let mut suggest_start_column = suggest_start_column;
let target_cursor_column = _find_target_cursor_column_exclude_eol(
text,
target_cursor_line,
target_cursor_char,
);
if target_cursor_column < suggest_start_column {
suggest_start_column = target_cursor_column;
}
trace!(
"suggest_line/column:{}/{},target_line/char/column:{}/{}/{}",
suggest_start_line,
suggest_start_column,
target_cursor_line,
target_cursor_char,
target_cursor_column,
);
(suggest_start_line, suggest_start_column)
}
fn nowrap_search_right(
text: &Text,
size: &U16Size,
suggest_start_line: usize,
suggest_start_column: usize,
target_cursor_line: usize,
target_cursor_char: usize,
) -> (usize, usize) {
let window_width = size.width();
let suggest_end_column = suggest_start_column + window_width as usize;
let mut suggest_start_column = suggest_start_column;
let target_cursor_column = _find_target_cursor_column_include_eol(
text,
target_cursor_line,
target_cursor_char,
);
if target_cursor_column > suggest_end_column {
suggest_start_column =
target_cursor_column.saturating_sub(window_width as usize);
}
let last_char = text
.last_char_idx_on_line_exclude_eol(target_cursor_line)
.unwrap_or(0);
let last_char_column = text.width_before(target_cursor_line, last_char);
let suggest_start_column =
std::cmp::min(suggest_start_column, last_char_column);
(suggest_start_line, suggest_start_column)
}
fn wrap_search_left(
_sync_fn: WrapSyncFn,
line_process_fn: WrapLineProcessFn,
_viewport: &Viewport,
_cursor_viewport: &CursorViewport,
_opts: &WindowOptions,
text: &Text,
size: &U16Size,
suggest_start_line: usize,
suggest_start_column: usize,
target_cursor_line: usize,
target_cursor_char: usize,
) -> (usize, usize) {
let window_height = size.height();
let window_width = size.width();
let (
cannot_fully_contain_target_cursor_line,
can_exactly_contain_target_cursor_line,
) = _can_fully_contain_target_cursor_line(
line_process_fn,
text,
size,
target_cursor_line,
);
let target_cursor_column = _find_target_cursor_column_exclude_eol(
text,
target_cursor_line,
target_cursor_char,
);
if cannot_fully_contain_target_cursor_line
|| can_exactly_contain_target_cursor_line
{
let start_line = target_cursor_line;
let start_column =
std::cmp::min(suggest_start_column, target_cursor_column);
let target_cursor_line_end_char = text
.last_char_idx_on_line_include_eol(target_cursor_line)
.unwrap_or(0);
let target_cursor_line_end_column = text
.width_until(target_cursor_line, target_cursor_line_end_char)
+ if text
.is_eol_or_line_end(target_cursor_line, target_cursor_line_end_char)
{
1
} else {
0
};
let target_cursor_line_start_column = target_cursor_line_end_column
.saturating_sub((window_width as usize) * (window_height as usize));
debug_assert!(
cannot_fully_contain_target_cursor_line
|| can_exactly_contain_target_cursor_line
);
let target_cursor_line_start_column = _reverse_search_start_column(
line_process_fn,
text,
size,
start_line,
target_cursor_line_start_column,
target_cursor_line,
target_cursor_line_end_char,
);
let start_column =
std::cmp::min(start_column, target_cursor_line_start_column);
(start_line, start_column)
} else {
let start_column = 0;
let start_line = suggest_start_line;
(start_line, start_column)
}
}
fn _reverse_search_start_column(
line_process_fn: WrapLineProcessFn,
text: &Text,
size: &U16Size,
_suggest_start_line: usize,
suggest_start_column: usize,
target_cursor_line: usize,
target_cursor_char: usize,
) -> usize {
let window_height = size.height();
let window_width = size.width();
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);
let eol_or_line_end =
text.is_eol_or_line_end(target_cursor_line, target_cursor_char);
let mut suggest_start_column = suggest_start_column;
while suggest_start_column < bufline_chars_width {
let (preview_target_rows, _preview_start_fills, _preview_end_fills, _) =
line_process_fn(
text,
suggest_start_column,
target_cursor_line,
0_u16,
window_height,
window_width,
);
debug_assert!(preview_target_rows.len() <= window_height as usize);
let contains_target_cursor_char =
preview_target_rows.iter().any(|(_row_idx, row_viewport)| {
target_cursor_char >= row_viewport.start_char_idx()
&& target_cursor_char < row_viewport.end_char_idx()
});
if contains_target_cursor_char {
return suggest_start_column;
}
if eol_or_line_end {
let contains_target_cursor_char_as_eol =
preview_target_rows.iter().any(|(_row_idx, row_viewport)| {
target_cursor_char == row_viewport.end_char_idx()
});
if contains_target_cursor_char_as_eol {
let last_row_use_full_width = {
debug_assert!(preview_target_rows.last().is_some());
let (_last_preview_row_idx, last_preview_row_viewport) =
preview_target_rows.last().unwrap();
let last_row_end_column = text.width_before(
target_cursor_line,
last_preview_row_viewport.end_char_idx(),
);
let last_row_start_column = text.width_before(
target_cursor_line,
last_preview_row_viewport.start_char_idx(),
);
let last_row_width =
last_row_end_column.saturating_sub(last_row_start_column);
last_row_width >= window_width as usize
};
if !last_row_use_full_width {
return suggest_start_column;
}
}
}
suggest_start_column += 1;
}
unreachable!()
}
fn wrap_search_right(
_sync_fn: WrapSyncFn,
line_process_fn: WrapLineProcessFn,
_viewport: &Viewport,
_cursor_viewport: &CursorViewport,
_opts: &WindowOptions,
text: &Text,
size: &U16Size,
suggest_start_line: usize,
suggest_start_column: usize,
target_cursor_line: usize,
target_cursor_char: usize,
) -> (usize, usize) {
let window_height = size.height();
let window_width = size.width();
let (
cannot_fully_contain_target_cursor_line,
can_exactly_contain_target_cursor_line,
) = _can_fully_contain_target_cursor_line(
line_process_fn,
text,
size,
target_cursor_line,
);
let target_cursor_column =
text.width_until(target_cursor_line, target_cursor_char);
if cannot_fully_contain_target_cursor_line
|| can_exactly_contain_target_cursor_line
{
let start_line = target_cursor_line;
let target_cursor_end_column =
if text.is_eol_or_line_end(target_cursor_line, target_cursor_char) {
target_cursor_column + 1
} else {
target_cursor_column
};
let target_cursor_start_column = target_cursor_end_column
.saturating_sub((window_width as usize) * (window_height as usize));
debug_assert!(
cannot_fully_contain_target_cursor_line
|| can_exactly_contain_target_cursor_line
);
let target_cursor_start_column = _reverse_search_start_column(
line_process_fn,
text,
size,
start_line,
target_cursor_start_column,
target_cursor_line,
target_cursor_char,
);
let start_column =
std::cmp::max(suggest_start_column, target_cursor_start_column);
(start_line, start_column)
} else {
let start_column = 0;
let start_line = suggest_start_line;
(start_line, start_column)
}
}