use crate::vim_state::vim;
use hjkl_engine::Editor;
use hjkl_engine::buf_helpers::buf_line;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BlockCols {
pub left: usize,
pub right: usize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct BlockRow {
pub start: usize,
pub end: usize,
pub startspaces: usize,
pub endspaces: usize,
}
pub fn block_cell_width(ch: char, vcol: usize, tabstop: usize) -> usize {
if ch == '\t' {
let ts = tabstop.max(1);
ts - vcol % ts
} else {
hjkl_buffer::char_col_to_visual_col(ch.encode_utf8(&mut [0; 4]), 1, tabstop)
}
}
pub fn block_row(line: &str, cols: BlockCols, tabstop: usize, is_del: bool) -> BlockRow {
let chars: Vec<char> = line.chars().collect();
let n = chars.len();
let mut row = BlockRow::default();
let (mut vcol, mut i, mut incr, mut prev_start) = (0, 0, 0, 0);
while vcol < cols.left && i < n {
incr = block_cell_width(chars[i], vcol, tabstop);
vcol += incr;
prev_start = i;
i += 1;
}
let start_vcol = vcol;
let mut start = i;
let start_char_vcols = incr;
if start_vcol < cols.left {
if !is_del && cols.right != usize::MAX {
row.endspaces = cols.right - cols.left + 1;
}
row.start = n;
row.end = n;
return row;
}
row.startspaces = start_vcol - cols.left;
if is_del && row.startspaces > 0 {
row.startspaces = start_char_vcols - row.startspaces;
}
let mut end = start;
if start_vcol > cols.right {
row.startspaces = cols.right - cols.left + 1;
if is_del {
row.startspaces = start_char_vcols - (start_vcol - cols.left);
row.endspaces = start_vcol - cols.right - 1;
}
} else {
let (mut v, mut j, mut prev_end) = (start_vcol, start, start);
while v <= cols.right && j < n {
prev_end = j;
incr = block_cell_width(chars[j], v, tabstop);
v += incr;
j += 1;
}
end = j;
if v > cols.right && cols.right != usize::MAX {
row.endspaces = v - cols.right - 1;
if !is_del && row.endspaces > 0 {
row.endspaces = incr - row.endspaces;
if end != start {
end = prev_end;
}
}
}
}
if is_del && row.startspaces > 0 {
start = prev_start;
}
row.start = start;
row.end = end;
row
}
pub fn char_at_vcol(line: &str, vcol: usize, tabstop: usize) -> Result<usize, usize> {
let mut v = 0;
for (i, ch) in line.chars().enumerate() {
if v >= vcol {
return Ok(i);
}
v += block_cell_width(ch, v, tabstop);
}
if v >= vcol {
Ok(line.chars().count())
} else {
Err(vcol - v)
}
}
pub fn tab_cut_at(line: &str, vcol: usize, tabstop: usize) -> Option<(usize, usize, usize)> {
let mut v = 0;
for (i, ch) in line.chars().enumerate() {
let w = block_cell_width(ch, v, tabstop);
if v >= vcol {
return None;
}
if ch == '\t' && v + w > vcol {
return Some((i, v, w));
}
v += w;
}
None
}
fn char_cells(line: &str, col: usize, tabstop: usize) -> (usize, usize) {
let first = hjkl_buffer::char_col_to_visual_col(line, col, tabstop);
match line.chars().nth(col) {
Some(ch) => (
first,
first + block_cell_width(ch, first, tabstop).max(1) - 1,
),
None => (first, first),
}
}
pub fn block_corners<H: hjkl_engine::types::Host>(
ed: &Editor<hjkl_buffer::View, H>,
) -> ((usize, usize), (usize, usize)) {
let tabstop = ed.settings().tabstop;
let (ar, ac) = vim(ed).block_anchor;
let cr = ed.cursor().0;
let anchor = char_cells(&buf_line(ed.buffer(), ar).unwrap_or_default(), ac, tabstop);
let cursor = char_cells(
&buf_line(ed.buffer(), cr).unwrap_or_default(),
vim(ed).block_vcol,
tabstop,
);
let (left, right) = (anchor.0.min(cursor.0), anchor.1.max(cursor.1));
if vim(ed).block_to_eol {
return ((ar, left), (cr, usize::MAX));
}
if anchor.0 <= cursor.0 {
((ar, left), (cr, right))
} else {
((ar, right), (cr, left))
}
}
pub fn block_rect<H: hjkl_engine::types::Host>(
ed: &Editor<hjkl_buffer::View, H>,
) -> (usize, usize, BlockCols) {
let tabstop = ed.settings().tabstop;
let (ar, ac) = vim(ed).block_anchor;
let cr = ed.cursor().0;
let cc = vim(ed).block_vcol;
let anchor = char_cells(&buf_line(ed.buffer(), ar).unwrap_or_default(), ac, tabstop);
let cursor = char_cells(&buf_line(ed.buffer(), cr).unwrap_or_default(), cc, tabstop);
let right = if vim(ed).block_to_eol {
usize::MAX
} else {
anchor.1.max(cursor.1)
};
let cols = BlockCols {
left: anchor.0.min(cursor.0),
right,
};
(ar.min(cr), ar.max(cr), cols)
}
#[cfg(test)]
mod tests {
use super::*;
fn cols(left: usize, right: usize) -> BlockCols {
BlockCols { left, right }
}
#[test]
fn plain_text_is_its_columns() {
let row = block_row("abcdef", cols(1, 3), 4, false);
assert_eq!(
(row.start, row.end, row.startspaces, row.endspaces),
(1, 4, 0, 0)
);
let row = block_row("abcdef", cols(1, 3), 4, true);
assert_eq!(
(row.start, row.end, row.startspaces, row.endspaces),
(1, 4, 0, 0)
);
}
#[test]
fn yank_of_a_row_short_of_the_block_is_padded() {
let row = block_row("ab", cols(4, 6), 4, false);
assert_eq!((row.start, row.end, row.endspaces), (2, 2, 3));
let row = block_row("abcd", cols(4, 6), 4, false);
assert_eq!((row.start, row.end, row.endspaces), (4, 4, 0));
}
#[test]
fn tab_straddling_the_left_edge() {
let yank = block_row("\tab", cols(2, 4), 4, false);
assert_eq!((yank.start, yank.end, yank.startspaces), (1, 2, 2));
let del = block_row("\tab", cols(2, 4), 4, true);
assert_eq!(
(del.start, del.end, del.startspaces, del.endspaces),
(0, 2, 2, 0)
);
}
#[test]
fn one_tab_covering_the_whole_block() {
let yank = block_row("\tab", cols(1, 2), 4, false);
assert_eq!((yank.start, yank.end, yank.startspaces), (1, 1, 2));
let del = block_row("\tab", cols(1, 2), 4, true);
assert_eq!(
(del.start, del.end, del.startspaces, del.endspaces),
(0, 1, 1, 1)
);
}
#[test]
fn block_past_a_tab_line_end_is_empty() {
let row = block_row("\tab", cols(6, 9), 4, true);
assert_eq!(
(row.start, row.end, row.startspaces, row.endspaces),
(3, 3, 0, 0)
);
}
#[test]
fn dollar_block_runs_to_each_row_end() {
let row = block_row("abcdef", cols(2, usize::MAX), 4, false);
assert_eq!((row.start, row.end, row.endspaces), (2, 6, 0));
}
}