use super::*;
fn make_parser(rows: u16, cols: u16, scrollback: usize) -> crate::Parser {
crate::Parser::new(TerminalSize { rows, cols }, scrollback)
}
#[test]
fn selection_initially_none() {
let screen = Screen::new(crate::grid::Size { rows: 5, cols: 10 }, 100);
assert_eq!(screen.selection_range(), None);
assert_eq!(screen.selected_text(), None);
}
#[test]
fn selection_start_then_clear() {
let mut parser = make_parser(3, 10, 100);
parser.process(b"abc");
parser
.screen_mut()
.selection_start(AbsolutePosition { row: 0, col: 0 }, SelectionMode::Linear);
parser
.screen_mut()
.selection_extend(AbsolutePosition { row: 0, col: 2 });
assert!(parser.screen().selection_range().is_some());
parser.screen_mut().selection_clear();
assert_eq!(parser.screen().selection_range(), None);
assert_eq!(parser.screen().selected_text(), None);
}
#[test]
fn selection_linear_single_line() {
let mut parser = make_parser(3, 20, 100);
parser.process(b"hello world");
parser
.screen_mut()
.selection_start(AbsolutePosition { row: 0, col: 0 }, SelectionMode::Linear);
parser
.screen_mut()
.selection_extend(AbsolutePosition { row: 0, col: 4 });
assert_eq!(parser.screen().selected_text().as_deref(), Some("hello"),);
}
#[test]
fn selection_linear_two_hard_lines() {
let mut parser = make_parser(5, 20, 100);
parser.process(b"line one\r\nline two\r\nline three\r\n");
parser
.screen_mut()
.selection_start(AbsolutePosition { row: 0, col: 0 }, SelectionMode::Linear);
parser
.screen_mut()
.selection_extend(AbsolutePosition { row: 1, col: 7 });
assert_eq!(
parser.screen().selected_text().as_deref(),
Some("line one\nline two"),
);
}
#[test]
fn selection_linear_wrapped_no_newline() {
let mut parser = make_parser(5, 5, 100);
parser.process(b"helloworld");
parser
.screen_mut()
.selection_start(AbsolutePosition { row: 0, col: 0 }, SelectionMode::Linear);
parser
.screen_mut()
.selection_extend(AbsolutePosition { row: 1, col: 4 });
assert_eq!(
parser.screen().selected_text().as_deref(),
Some("helloworld"),
);
}
#[test]
fn selection_linear_trims_trailing_whitespace_per_row() {
let mut parser = make_parser(3, 10, 100);
parser.process(b"hi\r\nthere");
parser
.screen_mut()
.selection_start(AbsolutePosition { row: 0, col: 0 }, SelectionMode::Linear);
parser
.screen_mut()
.selection_extend(AbsolutePosition { row: 1, col: 4 });
assert_eq!(
parser.screen().selected_text().as_deref(),
Some("hi\nthere"),
);
}
#[test]
fn selection_linear_wide_cell_in_range() {
let mut parser = make_parser(3, 10, 100);
parser.process("AB\u{4e16}CD".as_bytes());
parser
.screen_mut()
.selection_start(AbsolutePosition { row: 0, col: 0 }, SelectionMode::Linear);
parser
.screen_mut()
.selection_extend(AbsolutePosition { row: 0, col: 5 });
assert_eq!(
parser.screen().selected_text().as_deref(),
Some("AB\u{4e16}CD"),
);
}
#[test]
fn selection_linear_normalizes_when_extended_backward() {
let mut parser = make_parser(3, 20, 100);
parser.process(b"hello world");
parser
.screen_mut()
.selection_start(AbsolutePosition { row: 0, col: 4 }, SelectionMode::Linear);
parser
.screen_mut()
.selection_extend(AbsolutePosition { row: 0, col: 0 });
assert_eq!(parser.screen().selected_text().as_deref(), Some("hello"),);
}
#[test]
fn selection_scrollback_stable_after_scroll() {
let mut parser = make_parser(3, 10, 100);
parser.process(b"line0\r\n");
let start = parser
.screen()
.visible_to_absolute(Position { row: 0, col: 0 })
.unwrap();
let end = parser
.screen()
.visible_to_absolute(Position { row: 0, col: 4 })
.unwrap();
parser
.screen_mut()
.selection_start(start, SelectionMode::Linear);
parser.screen_mut().selection_extend(end);
assert_eq!(parser.screen().selected_text().as_deref(), Some("line0"));
for i in 1..6 {
parser.process(format!("line{i}\r\n").as_bytes());
}
assert_eq!(
parser.screen().selected_text().as_deref(),
Some("line0"),
"absolute selection coordinates must remain valid once the row scrolls into history",
);
}
#[test]
fn selection_line_mode_full_lines() {
let mut parser = make_parser(5, 20, 100);
parser.process(b"first line\r\nsecond line\r\nthird line\r\n");
parser
.screen_mut()
.selection_start(AbsolutePosition { row: 0, col: 4 }, SelectionMode::Line);
parser
.screen_mut()
.selection_extend(AbsolutePosition { row: 1, col: 2 });
assert_eq!(
parser.screen().selected_text().as_deref(),
Some("first line\nsecond line"),
);
}
#[test]
fn selection_word_snaps_alphanumeric_run() {
let mut parser = make_parser(3, 30, 100);
parser.process(b"hello world");
parser
.screen_mut()
.selection_start(AbsolutePosition { row: 0, col: 8 }, SelectionMode::Word);
assert_eq!(parser.screen().selected_text().as_deref(), Some("world"),);
}
#[test]
fn selection_word_snaps_to_word_at_cursor_end() {
let mut parser = make_parser(3, 30, 100);
parser.process(b"hello world there");
parser
.screen_mut()
.selection_start(AbsolutePosition { row: 0, col: 1 }, SelectionMode::Word);
parser
.screen_mut()
.selection_extend(AbsolutePosition { row: 0, col: 9 });
assert_eq!(
parser.screen().selected_text().as_deref(),
Some("hello world"),
);
}
#[test]
fn selection_word_punctuation_is_its_own_class() {
let mut parser = make_parser(3, 20, 100);
parser.process(b"foo+bar");
parser
.screen_mut()
.selection_start(AbsolutePosition { row: 0, col: 3 }, SelectionMode::Word);
assert_eq!(parser.screen().selected_text().as_deref(), Some("+"));
}
#[test]
fn selection_word_underscore_is_word() {
let mut parser = make_parser(3, 20, 100);
parser.process(b"foo_bar baz");
parser
.screen_mut()
.selection_start(AbsolutePosition { row: 0, col: 4 }, SelectionMode::Word);
assert_eq!(parser.screen().selected_text().as_deref(), Some("foo_bar"));
}
#[test]
fn selection_block_single_row_is_horizontal_slice() {
let mut parser = make_parser(3, 20, 100);
parser.process(b"abcdefghij");
parser
.screen_mut()
.selection_start(AbsolutePosition { row: 0, col: 2 }, SelectionMode::Block);
parser
.screen_mut()
.selection_extend(AbsolutePosition { row: 0, col: 6 });
assert_eq!(parser.screen().selected_text().as_deref(), Some("cdefg"));
}
#[test]
fn selection_block_multi_row_joins_with_newline() {
let mut parser = make_parser(5, 20, 100);
parser.process(b"abcdefghij\r\nklmnopqrst\r\nuvwxyz0123\r\n");
parser
.screen_mut()
.selection_start(AbsolutePosition { row: 0, col: 2 }, SelectionMode::Block);
parser
.screen_mut()
.selection_extend(AbsolutePosition { row: 2, col: 5 });
assert_eq!(
parser.screen().selected_text().as_deref(),
Some("cdef\nmnop\nwxyz"),
);
}
#[test]
fn selection_block_corners_normalize() {
let mut parser = make_parser(5, 20, 100);
parser.process(b"abcdefghij\r\nklmnopqrst\r\n");
parser
.screen_mut()
.selection_start(AbsolutePosition { row: 0, col: 6 }, SelectionMode::Block);
parser
.screen_mut()
.selection_extend(AbsolutePosition { row: 1, col: 2 });
assert_eq!(
parser.screen().selected_text().as_deref(),
Some("cdefg\nmnopq"),
);
}
#[test]
fn selection_block_left_edge_on_wide_continuation_includes_wide_head() {
let mut parser = make_parser(3, 10, 100);
parser.process("A\u{4e16}BCD".as_bytes());
parser
.screen_mut()
.selection_start(AbsolutePosition { row: 0, col: 2 }, SelectionMode::Block);
parser
.screen_mut()
.selection_extend(AbsolutePosition { row: 0, col: 4 });
assert_eq!(
parser.screen().selected_text().as_deref(),
Some("\u{4e16}BC"),
);
}
#[test]
fn selection_clears_on_size_change() {
let mut parser = make_parser(3, 20, 100);
parser.process(b"hello world");
parser
.screen_mut()
.selection_start(AbsolutePosition { row: 0, col: 0 }, SelectionMode::Linear);
parser
.screen_mut()
.selection_extend(AbsolutePosition { row: 0, col: 4 });
parser
.screen_mut()
.set_size(TerminalSize { rows: 5, cols: 30 });
assert_eq!(parser.screen().selection_range(), None);
}