use std::collections::{HashMap, HashSet};
use crate::app::App;
use crate::text_width::{char_cell_width, visual_col_of};
use super::SCROLL_OFF;
use super::diagnostics::DiagLine;
pub(super) fn compute_scroll(
app: &App,
height: usize,
row_diag: &HashMap<usize, Vec<DiagLine>>,
hidden: &HashSet<usize>,
) -> usize {
let cur = app.editor.cursor.row;
let mut scroll = app.active_doc().scroll.get();
if app.active_doc().pending_center.replace(false) && height > 0 {
let last = app.active_doc().lines.len().saturating_sub(1);
let max_scroll = last.saturating_sub(height.saturating_sub(1));
scroll = cur.saturating_sub(height / 2).min(max_scroll);
app.active_doc().scroll.set(scroll);
app.active_doc().viewport_height.set(height);
return scroll;
}
let off = if height > 2 * SCROLL_OFF + 1 {
SCROLL_OFF
} else {
0
};
if cur < scroll + off {
scroll = cur.saturating_sub(off);
} else if height > 0 {
let effective_height = height.saturating_sub(off);
loop {
if scroll >= cur {
break;
}
let mut consumed: usize = 0;
for row in scroll..cur {
if hidden.contains(&row) {
continue;
}
consumed += 1 + row_diag.get(&row).map_or(0, Vec::len);
if consumed >= effective_height {
break;
}
}
if consumed < effective_height {
break;
}
scroll += 1;
}
}
let last_row = app.active_doc().lines.len().saturating_sub(1);
scroll = scroll.min(last_row);
app.active_doc().scroll.set(scroll);
app.active_doc().viewport_height.set(height);
scroll
}
pub(super) fn compute_col_scroll(app: &App, width: usize, tab_width: usize) -> usize {
if width == 0 {
app.active_doc().col_scroll.set(0);
return 0;
}
let line = &app.active_doc().lines[app.editor.cursor.row];
let visual_col = visual_col_of(line, app.editor.cursor.col, tab_width);
let cursor_width = cursor_cell_width(line, app.editor.cursor.col, visual_col, tab_width);
let prev = app.active_doc().col_scroll.get();
let col_scroll = horizontal_scroll(visual_col, cursor_width, width, prev);
app.active_doc().col_scroll.set(col_scroll);
col_scroll
}
pub(super) fn horizontal_scroll(
visual_col: usize,
cursor_width: usize,
width: usize,
prev: usize,
) -> usize {
if width == 0 {
return 0;
}
let cursor_width = cursor_width.min(width);
if visual_col < prev {
visual_col
} else if visual_col + cursor_width > prev + width {
visual_col + cursor_width - width
} else {
prev
}
}
pub(super) fn cursor_cell_width(
line: &str,
char_col: usize,
visual_col: usize,
tab_width: usize,
) -> usize {
match line.chars().nth(char_col) {
Some('\t') => tab_width - (visual_col % tab_width),
Some(ch) => char_cell_width(ch),
None => 1,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ascii_cursor_at_right_edge_scrolls_one_column() {
assert_eq!(horizontal_scroll(80, 1, 80, 0), 1);
}
#[test]
fn wide_cursor_at_right_edge_keeps_both_cells_visible() {
assert_eq!(horizontal_scroll(79, 2, 80, 0), 1);
assert_eq!(horizontal_scroll(78, 2, 80, 0), 0);
}
#[test]
fn wide_glyph_in_one_cell_viewport_keeps_left_edge() {
assert_eq!(horizontal_scroll(40, 2, 1, 0), 40);
assert_eq!(horizontal_scroll(40, 2, 1, 50), 40);
}
#[test]
fn scroll_left_when_cursor_precedes_window() {
assert_eq!(horizontal_scroll(3, 1, 80, 10), 3);
}
#[test]
fn sticky_when_cursor_already_visible() {
assert_eq!(horizontal_scroll(40, 1, 80, 10), 10);
assert_eq!(horizontal_scroll(40, 2, 80, 10), 10);
}
#[test]
fn cursor_cell_width_handles_tabs_and_wide_chars() {
assert_eq!(cursor_cell_width("\t", 0, 0, 4), 4);
assert_eq!(cursor_cell_width("ab\t", 2, 2, 4), 2);
assert_eq!(cursor_cell_width("あ", 0, 0, 4), 2);
assert_eq!(cursor_cell_width("a", 0, 0, 4), 1);
assert_eq!(cursor_cell_width("a", 1, 1, 4), 1);
}
}