use ropey::Rope;
use crate::editor::selection::Position;
use crate::editor::wrap;
#[derive(Debug, Default, Clone, Copy)]
pub struct View {
pub offset_row: usize,
pub offset_col: usize,
pub width: u16,
pub height: u16,
pub offset_wrap: usize,
pub wrap: bool,
}
impl View {
pub fn ensure_cursor_visible(&mut self, cursor: &Position, scroll_off: usize) {
let scrolloff = scroll_off.min(self.height as usize / 2);
if cursor.row < self.offset_row + scrolloff {
self.offset_row = cursor.row.saturating_sub(scrolloff);
}
if cursor.row >= self.offset_row + self.height as usize - scrolloff {
self.offset_row = cursor
.row
.saturating_sub(self.height as usize - 1 - scrolloff);
}
if cursor.col < self.offset_col {
self.offset_col = cursor.col;
}
if cursor.col >= self.offset_col + self.width as usize {
self.offset_col = cursor.col.saturating_sub(self.width as usize - 1);
}
}
pub fn ensure_cursor_visible_wrapped(
&mut self,
cursor: &Position,
scroll_off: usize,
rope: &Rope,
text_width: u16,
) {
self.offset_col = 0;
if text_width == 0 || self.height == 0 {
return;
}
let scrolloff = scroll_off.min(self.height as usize / 2);
let cursor_screen_row = self.cursor_screen_row(cursor, rope, text_width);
if let Some(screen_row) = cursor_screen_row {
if screen_row >= self.height as usize - scrolloff {
let overshoot = screen_row - (self.height as usize - 1 - scrolloff);
self.scroll_down_by(overshoot, rope, text_width);
}
else if screen_row < scrolloff {
let undershoot = scrolloff - screen_row;
self.scroll_up_by(undershoot, rope, text_width);
}
} else {
if cursor.row < self.offset_row
|| (cursor.row == self.offset_row
&& self.offset_wrap > 0
&& {
let line = rope.line(cursor.row);
let (seg, _) = wrap::char_to_wrap_pos(line, cursor.col, text_width);
seg < self.offset_wrap
})
{
let line = rope.line(cursor.row);
let (cursor_seg, _) = wrap::char_to_wrap_pos(line, cursor.col, text_width);
self.offset_row = cursor.row;
self.offset_wrap = cursor_seg.saturating_sub(scrolloff);
if cursor_seg < scrolloff {
let mut remaining = scrolloff - cursor_seg;
self.offset_wrap = 0;
while self.offset_row > 0 && remaining > 0 {
self.offset_row -= 1;
let prev_line = rope.line(self.offset_row);
let wc = wrap::wrap_count(prev_line, text_width);
if wc <= remaining {
remaining -= wc;
} else {
self.offset_wrap = wc - remaining;
remaining = 0;
}
}
}
} else {
self.position_cursor_near_bottom(cursor, scrolloff, rope, text_width);
}
}
}
fn cursor_screen_row(
&self,
cursor: &Position,
rope: &Rope,
text_width: u16,
) -> Option<usize> {
if cursor.row < self.offset_row {
return None;
}
let mut screen_row = 0usize;
let line_count = rope.len_lines();
for doc_row in self.offset_row..=cursor.row.min(line_count.saturating_sub(1)) {
let line = rope.line(doc_row);
let wc = wrap::wrap_count(line, text_width);
let start_seg = if doc_row == self.offset_row {
self.offset_wrap
} else {
0
};
if doc_row == cursor.row {
let (cursor_seg, _) = wrap::char_to_wrap_pos(line, cursor.col, text_width);
if cursor_seg < start_seg {
return None;
}
let row = screen_row + cursor_seg - start_seg;
if row >= self.height as usize {
return None;
}
return Some(row);
}
screen_row += wc - start_seg;
if screen_row >= self.height as usize {
return None;
}
}
None
}
pub(crate) fn scroll_down_by(&mut self, n: usize, rope: &Rope, text_width: u16) {
let line_count = rope.len_lines();
let mut remaining = n;
while remaining > 0 && self.offset_row < line_count {
let line = rope.line(self.offset_row);
let wc = wrap::wrap_count(line, text_width);
let segs_left = wc - self.offset_wrap;
if segs_left <= remaining {
remaining -= segs_left;
self.offset_row += 1;
self.offset_wrap = 0;
} else {
self.offset_wrap += remaining;
remaining = 0;
}
}
}
pub(crate) fn scroll_up_by(&mut self, n: usize, rope: &Rope, text_width: u16) {
let mut remaining = n;
while remaining > 0 {
if self.offset_wrap > 0 {
let go_back = remaining.min(self.offset_wrap);
self.offset_wrap -= go_back;
remaining -= go_back;
} else if self.offset_row > 0 {
self.offset_row -= 1;
let line = rope.line(self.offset_row);
let wc = wrap::wrap_count(line, text_width);
if wc <= remaining {
remaining -= wc;
} else {
self.offset_wrap = wc - remaining;
remaining = 0;
}
} else {
break;
}
}
}
fn position_cursor_near_bottom(
&mut self,
cursor: &Position,
scrolloff: usize,
rope: &Rope,
text_width: u16,
) {
let target_from_top = (self.height as usize).saturating_sub(1).saturating_sub(scrolloff);
let line = rope.line(cursor.row);
let (cursor_seg, _) = wrap::char_to_wrap_pos(line, cursor.col, text_width);
let mut row = cursor.row;
let mut seg = cursor_seg;
let mut to_go = target_from_top;
while to_go > 0 {
if seg > 0 {
let go_back = to_go.min(seg);
seg -= go_back;
to_go -= go_back;
} else if row > 0 {
row -= 1;
let prev_line = rope.line(row);
let wc = wrap::wrap_count(prev_line, text_width);
if wc <= to_go {
to_go -= wc;
seg = 0;
} else {
seg = wc - to_go;
to_go = 0;
}
} else {
break;
}
}
self.offset_row = row;
self.offset_wrap = seg;
}
}