use crate::buf::text::Text;
use crate::prelude::*;
use crate::ui::canvas::Canvas;
use crate::ui::widget::window::opt::WindowOptions;
use litemap::LiteMap;
use std::ops::Range;
pub mod draw;
pub mod sync;
#[derive(Debug, Copy, Clone)]
pub struct RowViewport {
start_char_idx: usize,
end_char_idx: usize,
}
impl RowViewport {
pub fn new(char_idx_range: Range<usize>) -> Self {
Self {
start_char_idx: char_idx_range.start,
end_char_idx: char_idx_range.end,
}
}
pub fn chars_length(&self) -> usize {
self.end_char_idx - self.start_char_idx
}
pub fn start_char_idx(&self) -> usize {
self.start_char_idx
}
pub fn end_char_idx(&self) -> usize {
self.end_char_idx
}
}
#[derive(Debug, Clone)]
pub struct LineViewport {
rows: LiteMap<u16, RowViewport>,
start_filled_cols: usize,
end_filled_cols: usize,
}
impl LineViewport {
pub fn new(
rows: LiteMap<u16, RowViewport>,
start_filled_cols: usize,
end_filled_cols: usize,
) -> Self {
Self {
rows,
start_filled_cols,
end_filled_cols,
}
}
pub fn rows(&self) -> &LiteMap<u16, RowViewport> {
&self.rows
}
pub fn start_filled_cols(&self) -> usize {
self.start_filled_cols
}
pub fn end_filled_cols(&self) -> usize {
self.end_filled_cols
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct CursorViewport {
line_idx: usize,
char_idx: usize,
row_idx: u16,
column_idx: u16,
}
arc_ptr!(CursorViewport);
impl CursorViewport {
pub fn new(
line_idx: usize,
char_idx: usize,
row_idx: u16,
column_idx: u16,
) -> Self {
Self {
line_idx,
char_idx,
row_idx,
column_idx,
}
}
pub fn line_idx(&self) -> usize {
self.line_idx
}
pub fn char_idx(&self) -> usize {
self.char_idx
}
pub fn row_idx(&self) -> u16 {
self.row_idx
}
pub fn column_idx(&self) -> u16 {
self.column_idx
}
pub fn from_top_left(viewport: &Viewport, text: &Text) -> Self {
debug_assert!(viewport.end_line_idx() >= viewport.start_line_idx());
if viewport.end_line_idx() == viewport.start_line_idx() {
return Self::new(0, 0, 0, 0);
}
let lines = viewport.lines();
debug_assert!(viewport.end_line_idx() > viewport.start_line_idx());
debug_assert!(!lines.is_empty());
debug_assert!(
lines.len() == viewport.end_line_idx() - viewport.start_line_idx()
);
debug_assert!(lines.first().is_some());
debug_assert!(lines.last().is_some());
debug_assert!(*lines.first().unwrap().0 == viewport.start_line_idx());
debug_assert!(viewport.end_line_idx() > 0);
debug_assert!(*lines.last().unwrap().0 == viewport.end_line_idx() - 1);
let first_line = lines.first().unwrap();
let line_idx = *first_line.0;
let first_line = first_line.1;
if first_line.rows().is_empty() {
return Self::new(0, 0, 0, 0);
}
let first_row = first_line.rows().first().unwrap();
let first_row = first_row.1;
debug_assert!(first_row.end_char_idx() >= first_row.start_char_idx());
if first_row.end_char_idx() == first_row.start_char_idx() {
debug_assert_eq!(first_row.start_char_idx(), 0);
debug_assert_eq!(first_row.end_char_idx(), 0);
return Self::new(0, 0, 0, 0);
}
let char_idx = first_row.start_char_idx();
Self::from_position(viewport, text, line_idx, char_idx)
}
pub fn from_position(
viewport: &Viewport,
text: &Text,
line_idx: usize,
char_idx: usize,
) -> Self {
debug_assert!(viewport.lines().contains_key(&line_idx));
let line_viewport = viewport.lines().get(&line_idx).unwrap();
let cursor_row = line_viewport
.rows()
.iter()
.filter(|(_row_idx, row_viewport)| {
row_viewport.start_char_idx() <= char_idx
&& row_viewport.end_char_idx() > char_idx
})
.collect::<Vec<_>>();
if !cursor_row.is_empty() {
debug_assert_eq!(cursor_row.len(), 1);
let (row_idx, row_viewport) = cursor_row[0];
let mut row_start_width =
text.width_before(line_idx, row_viewport.start_char_idx());
let (first_row_idx, _first_row_viewport) =
line_viewport.rows.first().unwrap();
if first_row_idx == row_idx {
debug_assert!(row_start_width >= line_viewport.start_filled_cols());
row_start_width -= line_viewport.start_filled_cols();
};
let char_start_width = text.width_before(line_idx, char_idx);
let col_idx = (char_start_width - row_start_width) as u16;
let row_idx = *row_idx;
CursorViewport::new(line_idx, char_idx, row_idx, col_idx)
} else {
let target_is_eol = text.is_eol(line_idx, char_idx);
if target_is_eol {
let next_line_idx = line_idx + 1;
debug_assert!(viewport.lines().contains_key(&next_line_idx));
let next_line_viewport = viewport.lines().get(&next_line_idx).unwrap();
debug_assert!(next_line_viewport.rows().first().is_some());
let (first_row_idx, _first_row_viewport) =
next_line_viewport.rows().first().unwrap();
CursorViewport::new(line_idx, char_idx, *first_row_idx, 0_u16)
} else {
debug_assert!(line_viewport.rows().first().is_some());
let (first_row_idx, _first_row_viewport) =
line_viewport.rows().first().unwrap();
CursorViewport::new(line_idx, char_idx, *first_row_idx, 0_u16)
}
}
}
}
#[derive(Debug, Clone)]
pub struct Viewport {
start_line_idx: usize,
end_line_idx: usize,
start_column_idx: usize,
lines: LiteMap<usize, LineViewport>,
}
arc_ptr!(Viewport);
#[derive(Debug, Copy, Clone)]
pub enum ViewportSearchDirection {
Up,
Down,
Left,
Right,
}
impl Viewport {
pub fn view(
opts: &WindowOptions,
text: &Text,
shape: &U16Rect,
start_line: usize,
start_column: usize,
) -> Self {
let (line_idx_range, lines) =
sync::sync(opts, text, shape, start_line, start_column);
debug_assert_eq!(line_idx_range.start_line_idx(), start_line);
Viewport {
start_line_idx: line_idx_range.start_line_idx(),
end_line_idx: line_idx_range.end_line_idx(),
start_column_idx: start_column,
lines,
}
}
pub fn search_anchor(
&self,
direction: ViewportSearchDirection,
opts: &WindowOptions,
text: &Text,
shape: &U16Rect,
target_cursor_line: usize,
target_cursor_char: usize,
) -> (usize, usize) {
let height = shape.height();
let width = shape.width();
if height == 0 || width == 0 {
return (0, 0);
}
match direction {
ViewportSearchDirection::Down => sync::search_anchor_downward(
self,
opts,
text,
shape,
target_cursor_line,
target_cursor_char,
),
ViewportSearchDirection::Up => sync::search_anchor_upward(
self,
opts,
text,
shape,
target_cursor_line,
target_cursor_char,
),
ViewportSearchDirection::Left => sync::search_anchor_leftward(
self,
opts,
text,
shape,
target_cursor_line,
target_cursor_char,
),
ViewportSearchDirection::Right => sync::search_anchor_rightward(
self,
opts,
text,
shape,
target_cursor_line,
target_cursor_char,
),
}
}
#[cfg(not(test))]
fn _internal_check(&self) {}
#[cfg(test)]
fn _internal_check(&self) {
debug_assert!(self.end_line_idx >= self.start_line_idx);
debug_assert_eq!(
self.end_line_idx == self.start_line_idx,
self.lines.is_empty()
);
debug_assert!(self.lines.first().is_some());
debug_assert_eq!(*self.lines.first().unwrap().0, self.start_line_idx);
debug_assert!(self.lines.last().is_some());
debug_assert_eq!(*self.lines.last().unwrap().0, self.end_line_idx - 1);
let mut last_line_idx: Option<usize> = None;
let mut last_row_idx: Option<u16> = None;
for (line_idx, line_viewport) in self.lines.iter() {
match last_line_idx {
Some(last_line_idx1) => debug_assert_eq!(last_line_idx1 + 1, *line_idx),
None => { }
}
last_line_idx = Some(*line_idx);
let mut last_row_viewport: Option<RowViewport> = None;
for (row_idx, row_viewport) in line_viewport.rows() {
match last_row_idx {
Some(last_row_idx1) => debug_assert_eq!(last_row_idx1 + 1, *row_idx),
None => { }
}
last_row_idx = Some(*row_idx);
match last_row_viewport {
Some(last_row_viewport1) => {
debug_assert_eq!(
last_row_viewport1.end_char_idx(),
row_viewport.start_char_idx()
)
}
None => { }
}
last_row_viewport = Some(*row_viewport);
}
}
}
pub fn start_line_idx(&self) -> usize {
self._internal_check();
self.start_line_idx
}
pub fn end_line_idx(&self) -> usize {
self._internal_check();
self.end_line_idx
}
pub fn start_column_idx(&self) -> usize {
self._internal_check();
self.start_column_idx
}
pub fn lines(&self) -> &LiteMap<usize, LineViewport> {
self._internal_check();
&self.lines
}
pub fn is_empty(&self) -> bool {
self._internal_check();
self.lines.is_empty()
}
pub fn draw(&self, text: &Text, actual_shape: &U16Rect, canvas: &mut Canvas) {
draw::draw(self, text, actual_shape, canvas);
}
}