use crate::common::harness::EditorTestHarness;
use crossterm::event::{KeyCode, KeyModifiers};
#[test]
fn test_block_select_down_basic() {
let mut harness = EditorTestHarness::new(80, 24).unwrap();
harness
.type_text("line1 text here\nline2 text here\nline3 text here")
.unwrap();
harness
.send_key(KeyCode::Home, KeyModifiers::CONTROL)
.unwrap();
for _ in 0..6 {
harness
.send_key(KeyCode::Right, KeyModifiers::NONE)
.unwrap();
}
harness.assert_no_selection();
harness
.send_key(KeyCode::Down, KeyModifiers::ALT | KeyModifiers::SHIFT)
.unwrap();
harness.render().unwrap();
assert!(
harness.has_selection(),
"Should have selection after Alt+Shift+Down"
);
}
#[test]
fn test_block_select_multiple_consecutive() {
let mut harness = EditorTestHarness::new(80, 24).unwrap();
harness
.type_text("aaaa bbbb cccc\naaaa bbbb cccc\naaaa bbbb cccc\naaaa bbbb cccc\naaaa bbbb cccc")
.unwrap();
harness
.send_key(KeyCode::Home, KeyModifiers::CONTROL)
.unwrap();
for _ in 0..5 {
harness
.send_key(KeyCode::Right, KeyModifiers::NONE)
.unwrap();
}
harness
.send_key(KeyCode::Down, KeyModifiers::ALT | KeyModifiers::SHIFT)
.unwrap();
harness.render().unwrap();
assert!(
harness.has_selection(),
"Should have selection after first Alt+Shift+Down"
);
harness
.send_key(KeyCode::Down, KeyModifiers::ALT | KeyModifiers::SHIFT)
.unwrap();
harness.render().unwrap();
assert!(
harness.has_selection(),
"Should still have selection after second Alt+Shift+Down"
);
harness
.send_key(KeyCode::Down, KeyModifiers::ALT | KeyModifiers::SHIFT)
.unwrap();
harness.render().unwrap();
assert!(
harness.has_selection(),
"Should still have selection after third Alt+Shift+Down"
);
}
#[test]
fn test_block_select_then_escape() {
let mut harness = EditorTestHarness::new(80, 24).unwrap();
harness
.type_text("line1 text\nline2 text\nline3 text")
.unwrap();
harness
.send_key(KeyCode::Home, KeyModifiers::CONTROL)
.unwrap();
harness
.send_key(KeyCode::Down, KeyModifiers::ALT | KeyModifiers::SHIFT)
.unwrap();
harness.render().unwrap();
assert!(harness.has_selection(), "Should have selection");
harness.send_key(KeyCode::Esc, KeyModifiers::NONE).unwrap();
harness.render().unwrap();
harness.assert_no_selection();
}
#[test]
fn test_block_select_all_directions() {
{
let mut harness = EditorTestHarness::new(80, 24).unwrap();
harness.type_text("aaaa\nbbbb\ncccc").unwrap();
harness
.send_key(KeyCode::Home, KeyModifiers::CONTROL)
.unwrap();
harness
.send_key(KeyCode::Right, KeyModifiers::NONE)
.unwrap();
harness
.send_key(KeyCode::Down, KeyModifiers::ALT | KeyModifiers::SHIFT)
.unwrap();
harness.render().unwrap();
assert!(
harness.has_selection(),
"Alt+Shift+Down should create selection"
);
}
{
let mut harness = EditorTestHarness::new(80, 24).unwrap();
harness.type_text("aaaa\nbbbb\ncccc").unwrap();
harness.send_key(KeyCode::Up, KeyModifiers::NONE).unwrap();
harness.send_key(KeyCode::Home, KeyModifiers::NONE).unwrap();
harness
.send_key(KeyCode::Right, KeyModifiers::NONE)
.unwrap();
harness
.send_key(KeyCode::Up, KeyModifiers::ALT | KeyModifiers::SHIFT)
.unwrap();
harness.render().unwrap();
assert!(
harness.has_selection(),
"Alt+Shift+Up should create selection"
);
}
{
let mut harness = EditorTestHarness::new(80, 24).unwrap();
harness.type_text("aaaa\nbbbb\ncccc").unwrap();
harness
.send_key(KeyCode::Home, KeyModifiers::CONTROL)
.unwrap();
harness
.send_key(KeyCode::Right, KeyModifiers::ALT | KeyModifiers::SHIFT)
.unwrap();
harness.render().unwrap();
assert!(
harness.has_selection(),
"Alt+Shift+Right should create selection"
);
}
{
let mut harness = EditorTestHarness::new(80, 24).unwrap();
harness.type_text("aaaa\nbbbb\ncccc").unwrap();
harness
.send_key(KeyCode::Home, KeyModifiers::CONTROL)
.unwrap();
harness
.send_key(KeyCode::Right, KeyModifiers::NONE)
.unwrap();
harness
.send_key(KeyCode::Right, KeyModifiers::NONE)
.unwrap();
harness
.send_key(KeyCode::Left, KeyModifiers::ALT | KeyModifiers::SHIFT)
.unwrap();
harness.render().unwrap();
assert!(
harness.has_selection(),
"Alt+Shift+Left should create selection"
);
}
}
#[test]
fn test_block_select_persistence_across_cycles() {
let mut harness = EditorTestHarness::new(80, 24).unwrap();
harness
.type_text("1111 2222 3333\n1111 2222 3333\n1111 2222 3333\n1111 2222 3333")
.unwrap();
harness
.send_key(KeyCode::Home, KeyModifiers::CONTROL)
.unwrap();
for _ in 0..5 {
harness
.send_key(KeyCode::Right, KeyModifiers::NONE)
.unwrap();
}
harness
.send_key(KeyCode::Down, KeyModifiers::ALT | KeyModifiers::SHIFT)
.unwrap();
harness
.send_key(KeyCode::Down, KeyModifiers::ALT | KeyModifiers::SHIFT)
.unwrap();
harness.render().unwrap();
assert!(
harness.has_selection(),
"First cycle: should have selection"
);
harness.send_key(KeyCode::Esc, KeyModifiers::NONE).unwrap();
harness.render().unwrap();
harness.assert_no_selection();
harness
.send_key(KeyCode::Home, KeyModifiers::CONTROL)
.unwrap();
for _ in 0..5 {
harness
.send_key(KeyCode::Right, KeyModifiers::NONE)
.unwrap();
}
harness
.send_key(KeyCode::Down, KeyModifiers::ALT | KeyModifiers::SHIFT)
.unwrap();
harness.render().unwrap();
assert!(
harness.has_selection(),
"Second cycle: should have selection (fails if there's a state persistence bug)"
);
harness.send_key(KeyCode::Esc, KeyModifiers::NONE).unwrap();
harness.render().unwrap();
harness
.send_key(KeyCode::Home, KeyModifiers::CONTROL)
.unwrap();
harness
.send_key(KeyCode::Down, KeyModifiers::ALT | KeyModifiers::SHIFT)
.unwrap();
harness.render().unwrap();
assert!(
harness.has_selection(),
"Third cycle: should still have selection"
);
}
#[test]
fn test_block_select_then_type() {
let mut harness = EditorTestHarness::new(80, 24).unwrap();
harness.type_text("aaaa\nbbbb\ncccc").unwrap();
harness
.send_key(KeyCode::Home, KeyModifiers::CONTROL)
.unwrap();
harness
.send_key(KeyCode::Down, KeyModifiers::ALT | KeyModifiers::SHIFT)
.unwrap();
harness
.send_key(KeyCode::Right, KeyModifiers::ALT | KeyModifiers::SHIFT)
.unwrap();
harness
.send_key(KeyCode::Right, KeyModifiers::ALT | KeyModifiers::SHIFT)
.unwrap();
harness.render().unwrap();
assert!(
harness.has_selection(),
"Should have selection before typing"
);
harness.type_text("X").unwrap();
harness.render().unwrap();
harness.assert_no_selection();
}
#[test]
fn test_normal_then_block_selection() {
let mut harness = EditorTestHarness::new(80, 24).unwrap();
harness
.type_text("aaaa bbbb\ncccc dddd\neeee ffff")
.unwrap();
harness
.send_key(KeyCode::Home, KeyModifiers::CONTROL)
.unwrap();
harness
.send_key(KeyCode::Right, KeyModifiers::SHIFT)
.unwrap();
harness
.send_key(KeyCode::Right, KeyModifiers::SHIFT)
.unwrap();
harness.render().unwrap();
assert!(harness.has_selection(), "Should have normal selection");
harness.send_key(KeyCode::Esc, KeyModifiers::NONE).unwrap();
harness.render().unwrap();
harness.assert_no_selection();
harness
.send_key(KeyCode::Down, KeyModifiers::ALT | KeyModifiers::SHIFT)
.unwrap();
harness.render().unwrap();
assert!(
harness.has_selection(),
"Block selection should work after normal selection was cleared"
);
}
#[test]
fn test_block_selection_renders_rectangular() {
let mut harness = EditorTestHarness::new(80, 24).unwrap();
harness
.type_text("0123456789\n0123456789\n0123456789")
.unwrap();
harness
.send_key(KeyCode::Home, KeyModifiers::CONTROL)
.unwrap();
harness
.send_key(KeyCode::Right, KeyModifiers::NONE)
.unwrap();
harness
.send_key(KeyCode::Right, KeyModifiers::NONE)
.unwrap();
harness
.send_key(KeyCode::Down, KeyModifiers::ALT | KeyModifiers::SHIFT)
.unwrap();
harness
.send_key(KeyCode::Right, KeyModifiers::ALT | KeyModifiers::SHIFT)
.unwrap();
harness
.send_key(KeyCode::Right, KeyModifiers::ALT | KeyModifiers::SHIFT)
.unwrap();
harness.render().unwrap();
let buffer = harness.buffer();
let theme = harness.editor().theme();
let selection_bg = theme.selection_bg;
let (content_first_row, _content_last_row) = harness.content_area_rows();
let first_line_row = content_first_row as u16;
let gutter_width = harness.editor().active_state().margins.left_total_width() as u16;
let pos_line0_col2 = buffer.index_of(gutter_width + 2, first_line_row);
let cell = &buffer.content[pos_line0_col2];
assert_eq!(cell.symbol(), "2");
assert_eq!(
cell.bg, selection_bg,
"Block selection: line 0 column 2 should have selection background"
);
let pos_line1_col2 = buffer.index_of(gutter_width + 2, first_line_row + 1);
let cell = &buffer.content[pos_line1_col2];
assert_eq!(cell.symbol(), "2");
assert_eq!(
cell.bg, selection_bg,
"Block selection: line 1 column 2 should have selection background"
);
let pos_line0_col0 = buffer.index_of(gutter_width, first_line_row);
let cell = &buffer.content[pos_line0_col0];
assert_eq!(cell.symbol(), "0");
assert_ne!(
cell.bg, selection_bg,
"Block selection: line 0 column 0 should NOT have selection background"
);
let pos_line1_col0 = buffer.index_of(gutter_width, first_line_row + 1);
let cell = &buffer.content[pos_line1_col0];
assert_eq!(cell.symbol(), "0");
assert_ne!(
cell.bg, selection_bg,
"Block selection: line 1 column 0 should NOT have selection background (this fails if block renders as normal selection)"
);
}
#[test]
fn test_block_selection_copy_copies_rectangular_region() {
let mut harness = EditorTestHarness::new(80, 24).unwrap();
harness.editor_mut().set_clipboard_for_test("".to_string());
harness
.type_text("AAAA BBBB CCCC\nAAAA BBBB CCCC\nAAAA BBBB CCCC")
.unwrap();
harness
.send_key(KeyCode::Home, KeyModifiers::CONTROL)
.unwrap();
for _ in 0..5 {
harness
.send_key(KeyCode::Right, KeyModifiers::NONE)
.unwrap();
}
harness
.send_key(KeyCode::Down, KeyModifiers::ALT | KeyModifiers::SHIFT)
.unwrap();
for _ in 0..4 {
harness
.send_key(KeyCode::Right, KeyModifiers::ALT | KeyModifiers::SHIFT)
.unwrap();
}
harness.render().unwrap();
assert!(
harness.has_selection(),
"Should have block selection before copy"
);
harness
.send_key(KeyCode::Char('c'), KeyModifiers::CONTROL)
.unwrap();
harness.render().unwrap();
let clipboard_content = harness.editor_mut().clipboard_content_for_test();
println!("Clipboard content: {:?}", clipboard_content);
assert!(
!clipboard_content.contains("AAAA"),
"Block selection copy should NOT include content outside the rectangle (found AAAA). Got: {:?}",
clipboard_content
);
assert!(
!clipboard_content.contains("CCCC"),
"Block selection copy should NOT include content outside the rectangle (found CCCC). Got: {:?}",
clipboard_content
);
assert!(
clipboard_content.contains("BBBB"),
"Block selection copy should include the selected block content. Got: {:?}",
clipboard_content
);
let expected = "BBBB\nBBBB";
assert_eq!(
clipboard_content, expected,
"Block selection copy should produce exactly the rectangular region"
);
}