pub mod draw;
pub mod sync;
use crate::buf::text::Text;
use crate::hl::ColorSchemeArc;
use crate::prelude::*;
use crate::syntax::Syntax;
use crate::ui::canvas::Canvas;
use crate::ui::widget::window::opt::WindowOptions;
use litemap::LiteMap;
use std::ops::Range;
#[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,
size: &U16Size,
) -> 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, size, line_idx, char_idx)
}
pub fn from_position(
viewport: &Viewport,
text: &Text,
size: &U16Size,
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()
.find(|(_row_idx, row_viewport)| {
row_viewport.start_char_idx() <= char_idx
&& row_viewport.end_char_idx() > char_idx
});
if let Some((row_idx, row_viewport)) = cursor_row {
trace!(
"from_pos-1 row_idx:{},row_viewport:{:?}",
row_idx, row_viewport
);
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;
trace!(
"from_pos-1 line/char:{}/{},row/col:{}/{},first_row_idx:{},first_row_viewport:{:?},row_start_width:{},char_start_width:{}",
line_idx,
char_idx,
row_idx,
col_idx,
first_row_idx,
_first_row_viewport,
row_start_width,
char_start_width
);
CursorViewport::new(line_idx, char_idx, row_idx, col_idx)
} else if let Some(buffer_line) = text.rope().get_line(line_idx)
&& buffer_line.len_chars() == 0
{
debug_assert!(line_viewport.rows().first().is_some());
let (first_row_idx, _first_row_viewport) =
line_viewport.rows().first().unwrap();
trace!(
"from_pos-2 line/char:{}/{},first_row_idx:{},first_row_viewport:{:?}",
line_idx, char_idx, first_row_idx, _first_row_viewport
);
CursorViewport::new(line_idx, char_idx, *first_row_idx, 0_u16)
} else if text.is_eol_or_line_end(line_idx, char_idx) {
debug_assert!(line_viewport.rows().last().is_some());
let (last_row_idx, last_row_viewport) =
line_viewport.rows().last().unwrap();
debug_assert!(last_row_viewport.end_char_idx() <= char_idx);
let last_row_use_full_width = {
let last_row_end_column =
text.width_before(line_idx, last_row_viewport.end_char_idx());
let last_row_start_char_column =
text.width_before(line_idx, last_row_viewport.start_char_idx());
let last_row_width =
last_row_end_column.saturating_sub(last_row_start_char_column);
last_row_width >= size.width() as usize
};
if last_row_use_full_width {
debug_assert!(*last_row_idx + 1 < size.height());
trace!(
"from_pos-3 line/char:{}/{},last_row_idx:{},last_row_viewport:{:?}",
line_idx, char_idx, last_row_idx, last_row_viewport
);
CursorViewport::new(line_idx, char_idx, *last_row_idx + 1, 0_u16)
} else {
let mut row_start_width =
text.width_before(line_idx, last_row_viewport.start_char_idx());
let (first_row_idx, _first_row_viewport) =
line_viewport.rows.first().unwrap();
if first_row_idx == last_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 = *last_row_idx;
trace!(
"from_pos-4 line/char:{}/{},row/col:{}/{},first_row_viewport:{:?},last_row_viewport:{:?},row_start_width:{},char_start_width:{}",
line_idx,
char_idx,
row_idx,
col_idx,
_first_row_viewport,
last_row_viewport,
row_start_width,
char_start_width
);
CursorViewport::new(line_idx, char_idx, row_idx, col_idx)
}
} else {
debug_assert!(line_viewport.rows().first().is_some());
let (first_row_idx, _first_row_viewport) =
line_viewport.rows().first().unwrap();
trace!(
"from_pos-5 line/char:{}/{},first_row_idx:{},first_row_viewport:{:?}",
line_idx, char_idx, first_row_idx, _first_row_viewport,
);
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);
impl Viewport {
pub fn view(
opts: &WindowOptions,
text: &Text,
size: &U16Size,
start_line: usize,
start_column: usize,
) -> Self {
let (line_idx_range, lines) =
sync::sync(opts, text, size, 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(
&self,
cursor_viewport: &CursorViewport,
opts: &WindowOptions,
text: &Text,
size: &U16Size,
target_cursor_line: usize,
target_cursor_char: usize,
) -> (usize, usize) {
if size.is_zero() {
return (0, 0);
}
sync::search(
self,
cursor_viewport,
opts,
text,
size,
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()
);
if !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() {
if cfg!(debug_assertions)
&& let Some(last_line_idx1) = last_line_idx
{
debug_assert_eq!(last_line_idx1 + 1, *line_idx);
}
last_line_idx = Some(*line_idx);
let mut last_row_viewport: Option<RowViewport> = None;
for (row_idx, row_viewport) in line_viewport.rows() {
if cfg!(debug_assertions)
&& let Some(last_row_idx1) = last_row_idx
{
debug_assert_eq!(last_row_idx1 + 1, *row_idx);
}
last_row_idx = Some(*row_idx);
if cfg!(debug_assertions)
&& let Some(last_row_viewport1) = last_row_viewport
{
debug_assert_eq!(
last_row_viewport1.end_char_idx(),
row_viewport.start_char_idx()
);
}
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,
syntax: &Option<Syntax>,
colorscheme: &Option<ColorSchemeArc>,
actual_shape: &U16Rect,
canvas: &mut Canvas,
) {
draw::draw(self, text, syntax, colorscheme, actual_shape, canvas);
}
}