use crate::common::harness::EditorTestHarness;
use crossterm::event::{KeyCode, KeyModifiers};
use tempfile::TempDir;
#[test]
fn test_selection_visual_rendering() {
use crossterm::event::{KeyCode, KeyModifiers};
let mut harness = EditorTestHarness::new(80, 24).unwrap();
harness.type_text("Hello World").unwrap();
harness.send_key(KeyCode::Home, KeyModifiers::NONE).unwrap();
for _ in 0..5 {
harness
.send_key(KeyCode::Right, KeyModifiers::SHIFT)
.unwrap();
}
harness.render().unwrap();
let cursor = harness.editor().active_cursors().primary();
let cursor_pos = cursor.position;
let selection = cursor.selection_range();
assert!(selection.is_some(), "Cursor should have a selection");
let range = selection.unwrap();
assert_eq!(range.start, 0, "Selection should start at position 0");
assert_eq!(range.end, 5, "Selection should end at position 5");
println!("Cursor position: {cursor_pos}, Selection: {range:?}");
let selected_text = harness
.editor_mut()
.active_state_mut()
.get_text_range(range.start, range.end);
assert_eq!(selected_text, "Hello", "Selected text should be 'Hello'");
let _screen = harness.screen_to_string();
harness.assert_screen_contains("Hello World");
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 h_pos = buffer.index_of(8, first_line_row);
let h_cell = &buffer.content[h_pos];
assert_eq!(h_cell.symbol(), "H");
assert_eq!(
h_cell.bg, selection_bg,
"Selected character 'H' should have selection background"
);
let l_pos = buffer.index_of(11, first_line_row);
let l_cell = &buffer.content[l_pos];
assert_eq!(l_cell.symbol(), "l");
assert_eq!(
l_cell.bg, selection_bg,
"Selected character 'l' should have selection background"
);
let o_pos = buffer.index_of(12, first_line_row);
let o_cell = &buffer.content[o_pos];
assert_eq!(o_cell.symbol(), "o");
assert_eq!(
o_cell.bg, selection_bg,
"Selected character 'o' (byte 4) should have selection background"
);
let space_pos = buffer.index_of(13, first_line_row);
let space_cell = &buffer.content[space_pos];
assert_eq!(space_cell.symbol(), " ");
assert_ne!(
space_cell.bg, selection_bg,
"Cursor position (byte 5, space) should NOT have selection background"
);
}
#[test]
fn test_select_word() {
use crossterm::event::{KeyCode, KeyModifiers};
let mut harness = EditorTestHarness::new(80, 24).unwrap();
harness.type_text("hello world test").unwrap();
harness.send_key(KeyCode::Home, KeyModifiers::NONE).unwrap();
for _ in 0..8 {
harness
.send_key(KeyCode::Right, KeyModifiers::NONE)
.unwrap();
}
harness
.send_key(KeyCode::Char('w'), KeyModifiers::CONTROL)
.unwrap();
let cursor = harness.editor().active_cursors().primary();
let selection = cursor.selection_range();
assert!(selection.is_some(), "Cursor should have a selection");
let range = selection.unwrap();
let selected_text = harness
.editor_mut()
.active_state_mut()
.get_text_range(range.start, range.end);
assert_eq!(selected_text, "world", "Should select the word 'world'");
}
#[test]
fn test_select_word_at_start() {
use crossterm::event::{KeyCode, KeyModifiers};
let mut harness = EditorTestHarness::new(80, 24).unwrap();
harness.type_text("hello world").unwrap();
harness.send_key(KeyCode::Home, KeyModifiers::NONE).unwrap();
for _ in 0..6 {
harness
.send_key(KeyCode::Right, KeyModifiers::NONE)
.unwrap();
}
harness
.send_key(KeyCode::Char('w'), KeyModifiers::CONTROL)
.unwrap();
let cursor = harness.editor().active_cursors().primary();
let range = cursor.selection_range().unwrap();
let selected_text = harness
.editor_mut()
.active_state_mut()
.get_text_range(range.start, range.end);
assert_eq!(selected_text, "world", "Should select the word 'world'");
}
#[test]
fn test_select_word_at_end() {
use crossterm::event::{KeyCode, KeyModifiers};
let mut harness = EditorTestHarness::new(80, 24).unwrap();
harness.type_text("hello world").unwrap();
harness.send_key(KeyCode::Home, KeyModifiers::NONE).unwrap();
for _ in 0..5 {
harness
.send_key(KeyCode::Right, KeyModifiers::NONE)
.unwrap();
}
harness
.send_key(KeyCode::Char('w'), KeyModifiers::CONTROL)
.unwrap();
let cursor = harness.editor().active_cursors().primary();
let range = cursor.selection_range().unwrap();
let selected_text = harness
.editor_mut()
.active_state_mut()
.get_text_range(range.start, range.end);
assert_eq!(selected_text, "hello", "Should select the word 'hello'");
}
#[test]
fn test_select_line() {
use crossterm::event::{KeyCode, KeyModifiers};
let mut harness = EditorTestHarness::new(80, 24).unwrap();
harness
.type_text("first line\nsecond line\nthird line")
.unwrap();
harness
.send_key(KeyCode::Home, KeyModifiers::CONTROL)
.unwrap();
harness.send_key(KeyCode::Down, KeyModifiers::NONE).unwrap();
for _ in 0..5 {
harness
.send_key(KeyCode::Right, KeyModifiers::NONE)
.unwrap();
}
harness
.send_key(KeyCode::Char('l'), KeyModifiers::CONTROL)
.unwrap();
let cursor = harness.editor().active_cursors().primary();
let selection = cursor.selection_range();
assert!(selection.is_some(), "Cursor should have a selection");
let range = selection.unwrap();
let selected_text = harness
.editor_mut()
.active_state_mut()
.get_text_range(range.start, range.end);
assert_eq!(
selected_text, "second line\n",
"Should select the entire line including newline"
);
}
#[test]
fn test_select_line_first() {
use crossterm::event::{KeyCode, KeyModifiers};
let mut harness = EditorTestHarness::new(80, 24).unwrap();
harness.type_text("first line\nsecond line").unwrap();
harness
.send_key(KeyCode::Home, KeyModifiers::CONTROL)
.unwrap();
harness
.send_key(KeyCode::Char('l'), KeyModifiers::CONTROL)
.unwrap();
let cursor = harness.editor().active_cursors().primary();
let range = cursor.selection_range().unwrap();
let selected_text = harness
.editor_mut()
.active_state_mut()
.get_text_range(range.start, range.end);
assert_eq!(
selected_text, "first line\n",
"Should select the first line"
);
}
#[test]
fn test_select_line_last() {
use crossterm::event::{KeyCode, KeyModifiers};
let mut harness = EditorTestHarness::new(80, 24).unwrap();
harness.type_text("first line\nsecond line").unwrap();
harness
.send_key(KeyCode::Char('l'), KeyModifiers::CONTROL)
.unwrap();
let cursor = harness.editor().active_cursors().primary();
let range = cursor.selection_range().unwrap();
let selected_text = harness
.editor_mut()
.active_state_mut()
.get_text_range(range.start, range.end);
assert_eq!(
selected_text, "second line",
"Should select the last line without newline"
);
}
#[test]
fn test_select_word_multi_cursor() {
use crossterm::event::{KeyCode, KeyModifiers};
let mut harness = EditorTestHarness::new(80, 24).unwrap();
harness.type_text("hello world test").unwrap();
harness.send_key(KeyCode::Home, KeyModifiers::NONE).unwrap();
harness
.send_key(KeyCode::Right, KeyModifiers::SHIFT)
.unwrap();
harness
.send_key(KeyCode::Right, KeyModifiers::SHIFT)
.unwrap();
harness
.send_key(KeyCode::Right, KeyModifiers::SHIFT)
.unwrap();
harness
.send_key(KeyCode::Right, KeyModifiers::SHIFT)
.unwrap();
harness
.send_key(KeyCode::Right, KeyModifiers::SHIFT)
.unwrap();
harness
.send_key(KeyCode::Right, KeyModifiers::NONE)
.unwrap();
harness
.send_key(KeyCode::Right, KeyModifiers::NONE)
.unwrap();
harness
.send_key(KeyCode::Down, KeyModifiers::CONTROL | KeyModifiers::ALT)
.unwrap();
assert!(
harness.editor().active_cursors().count() >= 1,
"Should have at least one cursor"
);
}
#[test]
fn test_expand_selection() {
use crossterm::event::{KeyCode, KeyModifiers};
let mut harness = EditorTestHarness::new(80, 24).unwrap();
harness.type_text("hello world test").unwrap();
harness.send_key(KeyCode::Home, KeyModifiers::NONE).unwrap();
for _ in 0..3 {
harness
.send_key(KeyCode::Right, KeyModifiers::NONE)
.unwrap();
}
harness
.send_key(KeyCode::Right, KeyModifiers::CONTROL | KeyModifiers::SHIFT)
.unwrap();
let cursor = harness.editor().active_cursors().primary();
let range = cursor.selection_range().unwrap();
let selected_text = harness
.editor_mut()
.active_state_mut()
.get_text_range(range.start, range.end);
assert_eq!(
selected_text, "lo",
"First expand should select from cursor to end of word"
);
harness
.send_key(KeyCode::Right, KeyModifiers::CONTROL | KeyModifiers::SHIFT)
.unwrap();
let cursor = harness.editor().active_cursors().primary();
let range = cursor.selection_range().unwrap();
let selected_text = harness
.editor_mut()
.active_state_mut()
.get_text_range(range.start, range.end);
assert_eq!(
selected_text, "lo world",
"Second expand should include next word"
);
harness
.send_key(KeyCode::Right, KeyModifiers::CONTROL | KeyModifiers::SHIFT)
.unwrap();
let cursor = harness.editor().active_cursors().primary();
let range = cursor.selection_range().unwrap();
let selected_text = harness
.editor_mut()
.active_state_mut()
.get_text_range(range.start, range.end);
assert_eq!(
selected_text, "lo world test",
"Third expand should include third word"
);
}
#[test]
fn test_expand_selection_no_initial_selection() {
use crossterm::event::{KeyCode, KeyModifiers};
let mut harness = EditorTestHarness::new(80, 24).unwrap();
harness.type_text("foo bar baz").unwrap();
harness.send_key(KeyCode::Home, KeyModifiers::NONE).unwrap();
for _ in 0..5 {
harness
.send_key(KeyCode::Right, KeyModifiers::NONE)
.unwrap();
}
harness
.send_key(KeyCode::Right, KeyModifiers::CONTROL | KeyModifiers::SHIFT)
.unwrap();
let cursor = harness.editor().active_cursors().primary();
let range = cursor.selection_range().unwrap();
let selected_text = harness
.editor_mut()
.active_state_mut()
.get_text_range(range.start, range.end);
assert_eq!(
selected_text, "ar",
"Should select from cursor to end of word"
);
}
#[test]
#[ignore]
fn test_expand_selection_large_buffer_performance() {
use crossterm::event::{KeyCode, KeyModifiers};
use std::fs;
use tempfile::TempDir;
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("large.txt");
let large_text = "word ".repeat(20_000); fs::write(&file_path, &large_text).unwrap();
let mut harness = EditorTestHarness::new(80, 24).unwrap();
harness.open_file(&file_path).unwrap();
harness
.send_key(KeyCode::Home, KeyModifiers::CONTROL)
.unwrap();
for _ in 0..50 {
harness
.send_key(KeyCode::Right, KeyModifiers::NONE)
.unwrap();
}
harness
.send_key(KeyCode::Right, KeyModifiers::CONTROL | KeyModifiers::SHIFT)
.unwrap();
let cursor = harness.editor().active_cursors().primary();
assert!(
cursor.selection_range().is_some(),
"Should have a selection"
);
let range = cursor.selection_range().unwrap();
let selected_text = harness
.editor_mut()
.active_state_mut()
.get_text_range(range.start, range.end);
assert!(!selected_text.is_empty(), "Selection should not be empty");
}
#[test]
#[ignore] fn test_expand_selection_very_large_buffer() {
use crossterm::event::{KeyCode, KeyModifiers};
use std::fs;
use tempfile::TempDir;
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("very_large.txt");
let large_text = "word ".repeat(2_000_000); fs::write(&file_path, &large_text).unwrap();
let mut harness = EditorTestHarness::new(80, 24).unwrap();
harness.open_file(&file_path).unwrap();
harness
.send_key(KeyCode::Home, KeyModifiers::CONTROL)
.unwrap();
for _ in 0..100 {
harness
.send_key(KeyCode::Right, KeyModifiers::NONE)
.unwrap();
}
harness
.send_key(KeyCode::Right, KeyModifiers::CONTROL | KeyModifiers::SHIFT)
.unwrap();
let cursor = harness.editor().active_cursors().primary();
assert!(
cursor.selection_range().is_some(),
"Should have selection at start"
);
harness.send_key(KeyCode::Esc, KeyModifiers::NONE).unwrap(); for _ in 0..1000 {
harness.send_key(KeyCode::Down, KeyModifiers::NONE).unwrap();
}
harness
.send_key(KeyCode::Right, KeyModifiers::CONTROL | KeyModifiers::SHIFT)
.unwrap();
let cursor = harness.editor().active_cursors().primary();
assert!(
cursor.selection_range().is_some(),
"Should have selection in middle"
);
}
#[test]
fn test_select_word_after_scrolling() {
use crossterm::event::{KeyCode, KeyModifiers};
use tracing_subscriber::EnvFilter;
let _ = tracing_subscriber::fmt()
.with_env_filter(EnvFilter::from_default_env().add_directive(tracing::Level::DEBUG.into()))
.with_test_writer()
.try_init();
let mut harness = EditorTestHarness::new(80, 24).unwrap();
let content: String = (0..100)
.map(|i| format!("line{i} word{i} test{i}"))
.collect::<Vec<_>>()
.join("\n");
let _fixture = harness.load_buffer_from_text(&content).unwrap();
harness
.send_key(KeyCode::Home, KeyModifiers::CONTROL)
.unwrap();
harness
.send_key_repeat(KeyCode::Down, KeyModifiers::NONE, 50)
.unwrap();
harness.send_key(KeyCode::Home, KeyModifiers::NONE).unwrap();
harness
.send_key_repeat(KeyCode::Right, KeyModifiers::NONE, 10)
.unwrap();
harness
.send_key(KeyCode::Char('w'), KeyModifiers::CONTROL)
.unwrap();
let cursor = harness.editor().active_cursors().primary();
let range = cursor.selection_range().unwrap();
let selected_text = harness
.editor_mut()
.active_state_mut()
.get_text_range(range.start, range.end);
assert!(
selected_text.contains("word"),
"Should select a word after scrolling"
);
assert!(!selected_text.is_empty(), "Selection should not be empty");
}
#[test]
fn test_expand_selection_after_scrolling() {
use crossterm::event::{KeyCode, KeyModifiers};
use tracing_subscriber::EnvFilter;
let _ = tracing_subscriber::fmt()
.with_env_filter(EnvFilter::from_default_env().add_directive(tracing::Level::DEBUG.into()))
.with_test_writer()
.try_init();
let mut harness = EditorTestHarness::new(80, 24).unwrap();
let content: String = (0..50)
.map(|i| format!("alpha beta gamma delta epsilon line{i}"))
.collect::<Vec<_>>()
.join("\n");
let _fixture = harness.load_buffer_from_text(&content).unwrap();
harness
.send_key(KeyCode::Home, KeyModifiers::CONTROL)
.unwrap();
harness
.send_key_repeat(KeyCode::Down, KeyModifiers::NONE, 30)
.unwrap();
harness.send_key(KeyCode::Home, KeyModifiers::NONE).unwrap();
for _ in 0..3 {
harness
.send_key(KeyCode::Right, KeyModifiers::NONE)
.unwrap();
}
harness
.send_key(KeyCode::Right, KeyModifiers::CONTROL | KeyModifiers::SHIFT)
.unwrap();
let cursor = harness.editor().active_cursors().primary();
let range = cursor.selection_range().unwrap();
let selected_text = harness
.editor_mut()
.active_state_mut()
.get_text_range(range.start, range.end);
assert_eq!(
selected_text, "ha",
"First expand should select from cursor to end of word"
);
harness
.send_key(KeyCode::Right, KeyModifiers::CONTROL | KeyModifiers::SHIFT)
.unwrap();
let cursor = harness.editor().active_cursors().primary();
let range = cursor.selection_range().unwrap();
let selected_text = harness
.editor_mut()
.active_state_mut()
.get_text_range(range.start, range.end);
assert_eq!(
selected_text, "ha beta",
"Second expand should include next word"
);
}
#[test]
fn test_expand_selection_across_lines() {
use crossterm::event::{KeyCode, KeyModifiers};
let mut harness = EditorTestHarness::new(80, 24).unwrap();
harness
.type_text("first line ending\nsecond line starting here")
.unwrap();
harness
.send_key(KeyCode::Home, KeyModifiers::CONTROL)
.unwrap();
harness.send_key(KeyCode::End, KeyModifiers::NONE).unwrap();
for _ in 0..6 {
harness.send_key(KeyCode::Left, KeyModifiers::NONE).unwrap();
}
harness
.send_key(KeyCode::Right, KeyModifiers::CONTROL | KeyModifiers::SHIFT)
.unwrap();
let cursor = harness.editor().active_cursors().primary();
let range = cursor.selection_range().unwrap();
let selected_text = harness
.editor_mut()
.active_state_mut()
.get_text_range(range.start, range.end);
assert_eq!(
selected_text, "ending",
"Should select 'ending' on first line"
);
harness
.send_key(KeyCode::Right, KeyModifiers::CONTROL | KeyModifiers::SHIFT)
.unwrap();
let cursor = harness.editor().active_cursors().primary();
let range = cursor.selection_range().unwrap();
let selected_text = harness
.editor_mut()
.active_state_mut()
.get_text_range(range.start, range.end);
assert_eq!(
selected_text, "ending\nsecond",
"Should cross line boundary and select 'second'"
);
harness
.send_key(KeyCode::Right, KeyModifiers::CONTROL | KeyModifiers::SHIFT)
.unwrap();
let cursor = harness.editor().active_cursors().primary();
let range = cursor.selection_range().unwrap();
let selected_text = harness
.editor_mut()
.active_state_mut()
.get_text_range(range.start, range.end);
assert_eq!(
selected_text, "ending\nsecond line",
"Should include 'line' from second line"
);
}
#[test]
fn test_expand_selection_from_line_end() {
use crossterm::event::{KeyCode, KeyModifiers};
let mut harness = EditorTestHarness::new(80, 24).unwrap();
harness.type_text("first line\nsecond word here").unwrap();
harness
.send_key(KeyCode::Home, KeyModifiers::CONTROL)
.unwrap();
harness.send_key(KeyCode::End, KeyModifiers::NONE).unwrap();
harness
.send_key(KeyCode::Right, KeyModifiers::CONTROL | KeyModifiers::SHIFT)
.unwrap();
let cursor = harness.editor().active_cursors().primary();
let range = cursor.selection_range().unwrap();
let selected_text = harness
.editor_mut()
.active_state_mut()
.get_text_range(range.start, range.end);
assert!(!selected_text.is_empty(), "Should select something");
assert!(
selected_text.contains("second"),
"Should jump to next line and select 'second'"
);
harness
.send_key(KeyCode::Right, KeyModifiers::CONTROL | KeyModifiers::SHIFT)
.unwrap();
let cursor = harness.editor().active_cursors().primary();
let range = cursor.selection_range().unwrap();
let selected_text = harness
.editor_mut()
.active_state_mut()
.get_text_range(range.start, range.end);
assert!(
selected_text.contains("second"),
"Should eventually reach 'second' on next line"
);
}
#[test]
fn test_select_word_with_hyphen() {
use crossterm::event::{KeyCode, KeyModifiers};
let mut harness = EditorTestHarness::new(80, 24).unwrap();
harness.type_text("foo-bar").unwrap();
harness.send_key(KeyCode::Home, KeyModifiers::NONE).unwrap();
harness
.send_key(KeyCode::Char('w'), KeyModifiers::CONTROL)
.unwrap();
let cursor = harness.editor().active_cursors().primary();
let range = cursor.selection_range().unwrap();
let selected_text = harness
.editor_mut()
.active_state_mut()
.get_text_range(range.start, range.end);
assert_eq!(
selected_text, "foo",
"Hyphen should be a word separator, selecting 'foo'"
);
}
#[test]
fn test_select_word_with_underscore() {
use crossterm::event::{KeyCode, KeyModifiers};
let mut harness = EditorTestHarness::new(80, 24).unwrap();
harness.type_text("baz_qux").unwrap();
harness.send_key(KeyCode::Home, KeyModifiers::NONE).unwrap();
harness
.send_key(KeyCode::Char('w'), KeyModifiers::CONTROL)
.unwrap();
let cursor = harness.editor().active_cursors().primary();
let range = cursor.selection_range().unwrap();
let selected_text = harness
.editor_mut()
.active_state_mut()
.get_text_range(range.start, range.end);
assert_eq!(
selected_text, "baz_qux",
"Underscore should be a word char, selecting 'baz_qux'"
);
}
#[test]
fn test_select_word_with_numbers() {
use crossterm::event::{KeyCode, KeyModifiers};
let mut harness = EditorTestHarness::new(80, 24).unwrap();
harness.type_text("test123").unwrap();
harness.send_key(KeyCode::Home, KeyModifiers::NONE).unwrap();
harness
.send_key(KeyCode::Char('w'), KeyModifiers::CONTROL)
.unwrap();
let cursor = harness.editor().active_cursors().primary();
let range = cursor.selection_range().unwrap();
let selected_text = harness
.editor_mut()
.active_state_mut()
.get_text_range(range.start, range.end);
assert_eq!(
selected_text, "test123",
"Alphanumeric should be a single word"
);
}
#[test]
fn test_select_word_with_at_symbol() {
use crossterm::event::{KeyCode, KeyModifiers};
let mut harness = EditorTestHarness::new(80, 24).unwrap();
harness.type_text("user@domain").unwrap();
harness.send_key(KeyCode::Home, KeyModifiers::NONE).unwrap();
harness
.send_key(KeyCode::Char('w'), KeyModifiers::CONTROL)
.unwrap();
let cursor = harness.editor().active_cursors().primary();
let range = cursor.selection_range().unwrap();
let selected_text = harness
.editor_mut()
.active_state_mut()
.get_text_range(range.start, range.end);
assert_eq!(
selected_text, "user",
"@ should be a word separator, selecting 'user'"
);
}
#[test]
fn test_select_word_with_dot() {
use crossterm::event::{KeyCode, KeyModifiers};
let mut harness = EditorTestHarness::new(80, 24).unwrap();
harness.type_text("domain.com").unwrap();
harness.send_key(KeyCode::Home, KeyModifiers::NONE).unwrap();
harness
.send_key(KeyCode::Char('w'), KeyModifiers::CONTROL)
.unwrap();
let cursor = harness.editor().active_cursors().primary();
let range = cursor.selection_range().unwrap();
let selected_text = harness
.editor_mut()
.active_state_mut()
.get_text_range(range.start, range.end);
assert_eq!(
selected_text, "domain",
". should be a word separator, selecting 'domain'"
);
}
#[test]
fn test_expand_selection_on_non_word_char() {
use crossterm::event::{KeyCode, KeyModifiers};
let mut harness = EditorTestHarness::new(80, 24).unwrap();
harness.type_text("**-word").unwrap();
harness.send_key(KeyCode::Home, KeyModifiers::NONE).unwrap();
harness
.send_key(KeyCode::Right, KeyModifiers::CONTROL | KeyModifiers::SHIFT)
.unwrap();
let cursor = harness.editor().active_cursors().primary();
let range = cursor.selection_range();
assert!(
range.is_some(),
"Should have a selection after Ctrl+Shift+Right"
);
if let Some(range) = range {
let selected_text = harness
.editor_mut()
.active_state_mut()
.get_text_range(range.start, range.end);
assert_eq!(
selected_text, "**-",
"Should select punctuation only (stops at word boundary)"
);
}
}
#[test]
fn test_expand_selection_on_word_char() {
use crossterm::event::{KeyCode, KeyModifiers};
let mut harness = EditorTestHarness::new(80, 24).unwrap();
harness.type_text("hello world").unwrap();
harness.send_key(KeyCode::Home, KeyModifiers::NONE).unwrap();
harness
.send_key(KeyCode::Right, KeyModifiers::CONTROL | KeyModifiers::SHIFT)
.unwrap();
let cursor = harness.editor().active_cursors().primary();
let range = cursor.selection_range().unwrap();
let selected_text = harness
.editor_mut()
.active_state_mut()
.get_text_range(range.start, range.end);
assert_eq!(selected_text, "hello", "Should select the current word");
}
#[test]
fn test_expand_selection_from_middle_of_word() {
use crossterm::event::{KeyCode, KeyModifiers};
let mut harness = EditorTestHarness::new(80, 24).unwrap();
harness.type_text("Event").unwrap();
harness.send_key(KeyCode::Home, KeyModifiers::NONE).unwrap();
harness
.send_key(KeyCode::Right, KeyModifiers::NONE)
.unwrap();
harness
.send_key(KeyCode::Right, KeyModifiers::CONTROL | KeyModifiers::SHIFT)
.unwrap();
let cursor = harness.editor().active_cursors().primary();
let range = cursor.selection_range().unwrap();
let selected_text = harness
.editor_mut()
.active_state_mut()
.get_text_range(range.start, range.end);
assert_eq!(
selected_text, "vent",
"Should select from cursor to end of word"
);
}
#[test]
fn test_select_word_left_on_non_word_char() {
use crossterm::event::{KeyCode, KeyModifiers};
let mut harness = EditorTestHarness::new(80, 24).unwrap();
harness.type_text("word**-").unwrap();
harness
.send_key(KeyCode::Left, KeyModifiers::CONTROL | KeyModifiers::SHIFT)
.unwrap();
let cursor = harness.editor().active_cursors().primary();
let range = cursor.selection_range();
assert!(
range.is_some(),
"Should have a selection after Ctrl+Shift+Left"
);
if let Some(range) = range {
let selected_text = harness
.editor_mut()
.active_state_mut()
.get_text_range(range.start, range.end);
assert_eq!(
selected_text, "**-",
"Should select backward from cursor through non-word chars"
);
}
harness
.send_key(KeyCode::Left, KeyModifiers::CONTROL | KeyModifiers::SHIFT)
.unwrap();
let cursor = harness.editor().active_cursors().primary();
let range = cursor.selection_range().unwrap();
let selected_text = harness
.editor_mut()
.active_state_mut()
.get_text_range(range.start, range.end);
assert_eq!(
selected_text, "word**-",
"Should extend selection to include 'word' after second step"
);
}
#[test]
fn test_select_prev_word_with_special_chars() {
use crossterm::event::{KeyCode, KeyModifiers};
let mut harness = EditorTestHarness::new(80, 24).unwrap();
harness
.type_text("start foo-bar baz_qux test123 user@domain.com")
.unwrap();
harness
.send_key(KeyCode::Left, KeyModifiers::CONTROL)
.unwrap();
harness
.send_key(KeyCode::Char('w'), KeyModifiers::CONTROL)
.unwrap();
let cursor = harness.editor().active_cursors().primary();
let range = cursor.selection_range().unwrap();
let selected_text = harness
.editor_mut()
.active_state_mut()
.get_text_range(range.start, range.end);
assert_eq!(selected_text, "com", "Should select 'com' backwards");
harness
.send_key(KeyCode::Left, KeyModifiers::CONTROL)
.unwrap();
harness
.send_key(KeyCode::Left, KeyModifiers::CONTROL)
.unwrap();
harness
.send_key(KeyCode::Char('w'), KeyModifiers::CONTROL)
.unwrap();
let cursor = harness.editor().active_cursors().primary();
let range = cursor.selection_range().unwrap();
let selected_text = harness
.editor_mut()
.active_state_mut()
.get_text_range(range.start, range.end);
assert_eq!(selected_text, "domain", "Should select 'domain' backwards");
harness
.send_key(KeyCode::Left, KeyModifiers::CONTROL)
.unwrap();
harness
.send_key(KeyCode::Left, KeyModifiers::CONTROL)
.unwrap();
harness
.send_key(KeyCode::Char('w'), KeyModifiers::CONTROL)
.unwrap();
let cursor = harness.editor().active_cursors().primary();
let range = cursor.selection_range().unwrap();
let selected_text = harness
.editor_mut()
.active_state_mut()
.get_text_range(range.start, range.end);
assert_eq!(
selected_text, "user",
"Should select 'user' backwards (@ is a separator)"
);
harness
.send_key(KeyCode::Left, KeyModifiers::CONTROL)
.unwrap();
harness
.send_key(KeyCode::Left, KeyModifiers::CONTROL)
.unwrap();
harness
.send_key(KeyCode::Char('w'), KeyModifiers::CONTROL)
.unwrap();
let cursor = harness.editor().active_cursors().primary();
let range = cursor.selection_range().unwrap();
let selected_text = harness
.editor_mut()
.active_state_mut()
.get_text_range(range.start, range.end);
assert_eq!(
selected_text, "test123",
"Should select 'test123' backwards"
);
harness
.send_key(KeyCode::Left, KeyModifiers::CONTROL)
.unwrap();
harness
.send_key(KeyCode::Left, KeyModifiers::CONTROL)
.unwrap();
harness
.send_key(KeyCode::Char('w'), KeyModifiers::CONTROL)
.unwrap();
let cursor = harness.editor().active_cursors().primary();
let range = cursor.selection_range().unwrap();
let selected_text = harness
.editor_mut()
.active_state_mut()
.get_text_range(range.start, range.end);
assert_eq!(
selected_text, "baz_qux",
"Should select 'baz_qux' backwards (underscore is a word char)"
);
harness
.send_key(KeyCode::Left, KeyModifiers::CONTROL)
.unwrap();
harness
.send_key(KeyCode::Left, KeyModifiers::CONTROL)
.unwrap();
harness
.send_key(KeyCode::Char('w'), KeyModifiers::CONTROL)
.unwrap();
let cursor = harness.editor().active_cursors().primary();
let range = cursor.selection_range().unwrap();
let selected_text = harness
.editor_mut()
.active_state_mut()
.get_text_range(range.start, range.end);
assert_eq!(selected_text, "bar", "Should select 'bar' backwards");
harness
.send_key(KeyCode::Left, KeyModifiers::CONTROL)
.unwrap();
harness
.send_key(KeyCode::Left, KeyModifiers::CONTROL)
.unwrap();
harness
.send_key(KeyCode::Char('w'), KeyModifiers::CONTROL)
.unwrap();
let cursor = harness.editor().active_cursors().primary();
let range = cursor.selection_range().unwrap();
let selected_text = harness
.editor_mut()
.active_state_mut()
.get_text_range(range.start, range.end);
assert_eq!(
selected_text, "foo",
"Should select 'foo' backwards (hyphen is a separator)"
);
}
#[test]
fn test_select_up() {
use tracing_subscriber::EnvFilter;
let _ = tracing_subscriber::fmt()
.with_env_filter(EnvFilter::from_default_env().add_directive(tracing::Level::DEBUG.into()))
.with_test_writer()
.try_init();
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("test.txt");
let content = "Line 1\nLine 2\nLine 3\nLine 4\nLine 5\n";
std::fs::write(&file_path, content).unwrap();
let mut harness = EditorTestHarness::new(80, 24).unwrap();
harness.open_file(&file_path).unwrap();
harness.send_key(KeyCode::Down, KeyModifiers::NONE).unwrap();
harness.send_key(KeyCode::Down, KeyModifiers::NONE).unwrap();
harness.render().unwrap();
let cursor_pos = harness.cursor_position();
let buffer_content = harness.get_buffer_content().unwrap();
assert_eq!(&buffer_content[cursor_pos..cursor_pos + 6], "Line 3");
harness.assert_no_selection();
tracing::trace!(
"Initial state - selected text: {:?}",
harness.get_selected_text()
);
harness.send_key(KeyCode::Up, KeyModifiers::SHIFT).unwrap();
harness.render().unwrap();
assert!(
harness.has_selection(),
"Should have selection after Shift+Up"
);
let selected = harness.get_selected_text();
tracing::trace!("After first Shift+Up - selected text: {:?}", selected);
assert_eq!(selected, "Line 2\n", "Selection should be 'Line 2\n'");
harness.send_key(KeyCode::Up, KeyModifiers::SHIFT).unwrap();
harness.render().unwrap();
let selected = harness.get_selected_text();
tracing::trace!("After second Shift+Up - selected text: {:?}", selected);
assert_eq!(
selected, "Line 1\nLine 2\n",
"Selection should span two lines"
);
}
#[test]
fn test_select_down() {
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("test.txt");
let content = "Line 1\nLine 2\nLine 3\nLine 4\nLine 5\n";
std::fs::write(&file_path, content).unwrap();
let mut harness = EditorTestHarness::new(80, 24).unwrap();
harness.open_file(&file_path).unwrap();
harness.assert_no_selection();
harness
.send_key(KeyCode::Down, KeyModifiers::SHIFT)
.unwrap();
harness.render().unwrap();
assert!(
harness.has_selection(),
"Should have selection after Shift+Down"
);
let selected = harness.get_selected_text();
assert_eq!(selected, "Line 1\n", "Selection should be 'Line 1\n'");
harness
.send_key(KeyCode::Down, KeyModifiers::SHIFT)
.unwrap();
harness.render().unwrap();
let selected = harness.get_selected_text();
assert_eq!(
selected, "Line 1\nLine 2\n",
"Selection should span two lines"
);
harness
.send_key(KeyCode::Down, KeyModifiers::SHIFT)
.unwrap();
harness.render().unwrap();
let selected = harness.get_selected_text();
assert_eq!(
selected, "Line 1\nLine 2\nLine 3\n",
"Selection should span three lines"
);
}
#[test]
fn test_select_up_down_reversal() {
use tracing_subscriber::EnvFilter;
let _ = tracing_subscriber::fmt()
.with_env_filter(EnvFilter::from_default_env().add_directive(tracing::Level::DEBUG.into()))
.with_test_writer()
.try_init();
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("test.txt");
let content = "Line 1\nLine 2\nLine 3\nLine 4\n";
std::fs::write(&file_path, content).unwrap();
let mut harness = EditorTestHarness::new(80, 24).unwrap();
harness.open_file(&file_path).unwrap();
harness.send_key(KeyCode::Down, KeyModifiers::NONE).unwrap();
tracing::trace!(
"Initial state (at line 2) - selected text: {:?}",
harness.get_selected_text()
);
harness
.send_key(KeyCode::Down, KeyModifiers::SHIFT)
.unwrap();
harness.render().unwrap();
tracing::trace!(
"After first Shift+Down - selected text: {:?}",
harness.get_selected_text()
);
harness
.send_key(KeyCode::Down, KeyModifiers::SHIFT)
.unwrap();
harness.render().unwrap();
let selected = harness.get_selected_text();
tracing::trace!("After second Shift+Down - selected text: {:?}", selected);
assert_eq!(selected, "Line 2\nLine 3\n");
harness.send_key(KeyCode::Up, KeyModifiers::SHIFT).unwrap();
harness.render().unwrap();
let selected = harness.get_selected_text();
tracing::trace!(
"After first Shift+Up (shrinking) - selected text: {:?}",
selected
);
assert_eq!(selected, "Line 2\n", "Selection should shrink");
harness.send_key(KeyCode::Up, KeyModifiers::SHIFT).unwrap();
harness.render().unwrap();
let selected = harness.get_selected_text();
tracing::trace!(
"After second Shift+Up (at/past anchor) - selected text: {:?}",
selected
);
}
#[test]
fn test_select_page_down() {
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("test.txt");
let mut content = String::new();
for i in 1..=50 {
content.push_str(&format!("Line {i}\n"));
}
std::fs::write(&file_path, &content).unwrap();
let mut harness = EditorTestHarness::new(80, 10).unwrap();
harness.open_file(&file_path).unwrap();
harness.assert_no_selection();
harness
.send_key(KeyCode::PageDown, KeyModifiers::SHIFT)
.unwrap();
harness.render().unwrap();
assert!(
harness.has_selection(),
"Should have selection after Shift+PageDown"
);
let selected = harness.get_selected_text();
let selected_lines = selected.lines().count();
assert!(
selected_lines >= 4,
"Should select approximately a page of lines, got {selected_lines} lines"
);
assert!(selected.contains("Line 1"));
assert!(selected.contains("Line 2"));
}
#[test]
fn test_select_page_up() {
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("test.txt");
let mut content = String::new();
for i in 1..=50 {
content.push_str(&format!("Line {i}\n"));
}
std::fs::write(&file_path, &content).unwrap();
let mut harness = EditorTestHarness::new(80, 10).unwrap();
harness.open_file(&file_path).unwrap();
harness
.send_key(KeyCode::PageDown, KeyModifiers::NONE)
.unwrap();
harness
.send_key(KeyCode::PageDown, KeyModifiers::NONE)
.unwrap();
harness
.send_key(KeyCode::PageDown, KeyModifiers::NONE)
.unwrap();
harness.render().unwrap();
let cursor_before = harness.cursor_position();
assert!(cursor_before > 100, "Should be well into the file");
harness.assert_no_selection();
harness
.send_key(KeyCode::PageUp, KeyModifiers::SHIFT)
.unwrap();
harness.render().unwrap();
assert!(
harness.has_selection(),
"Should have selection after Shift+PageUp"
);
let selected = harness.get_selected_text();
let selected_lines = selected.lines().count();
assert!(
selected_lines >= 4,
"Should select approximately a page of lines, got {selected_lines} lines"
);
assert!(!selected.is_empty(), "Selection should not be empty");
}
#[test]
fn test_select_page_up_down_combination() {
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("test.txt");
let mut content = String::new();
for i in 1..=100 {
content.push_str(&format!("Line {i}\n"));
}
std::fs::write(&file_path, &content).unwrap();
let mut harness = EditorTestHarness::new(80, 10).unwrap();
harness.open_file(&file_path).unwrap();
for _ in 0..5 {
harness
.send_key(KeyCode::PageDown, KeyModifiers::NONE)
.unwrap();
}
harness
.send_key(KeyCode::PageDown, KeyModifiers::SHIFT)
.unwrap();
harness.render().unwrap();
assert!(harness.has_selection());
let selection_after_page_down = harness.get_selected_text();
let _lines_down = selection_after_page_down.lines().count();
harness
.send_key(KeyCode::PageUp, KeyModifiers::SHIFT)
.unwrap();
harness.render().unwrap();
let selection_after_page_up = harness.get_selected_text();
assert_ne!(
selection_after_page_down, selection_after_page_up,
"Selections should differ after PageUp"
);
}
#[test]
fn test_ctrl_right_jumps_to_word_end() {
{
let text = "hello world test";
let expected_destinations: Vec<(usize, usize)> = vec![
(0, 5), (1, 5), (2, 5), (3, 5), (4, 5), (5, 11), (6, 11), (7, 11), (8, 11), (9, 11), (10, 11), (11, 16), (12, 16), (13, 16), (14, 16), (15, 16), (16, 16), ];
for (start_pos, expected_end) in expected_destinations {
let mut harness = EditorTestHarness::new(80, 24).unwrap();
harness.type_text(text).unwrap();
harness.send_key(KeyCode::Home, KeyModifiers::NONE).unwrap();
for _ in 0..start_pos {
harness
.send_key(KeyCode::Right, KeyModifiers::NONE)
.unwrap();
}
assert_eq!(harness.cursor_position(), start_pos);
harness
.send_key(KeyCode::Right, KeyModifiers::CONTROL)
.unwrap();
assert_eq!(
harness.cursor_position(),
expected_end,
"Simple text: from position {}, Ctrl+Right should jump to {} (got {})",
start_pos,
expected_end,
harness.cursor_position()
);
}
}
{
let text = "bla::{\n next";
let expected_destinations: Vec<(usize, usize)> = vec![
(0, 3), (1, 3), (2, 3), (3, 6), (4, 6), (5, 6), (6, 14), (7, 14), (8, 14), (9, 14), (10, 14), (11, 14), (12, 14), (13, 14), (14, 14), ];
for (start_pos, expected_end) in expected_destinations {
let mut harness = EditorTestHarness::new(80, 24).unwrap();
harness.type_text(text).unwrap();
harness
.send_key(KeyCode::Home, KeyModifiers::CONTROL)
.unwrap();
for _ in 0..start_pos {
harness
.send_key(KeyCode::Right, KeyModifiers::NONE)
.unwrap();
}
assert_eq!(harness.cursor_position(), start_pos);
harness
.send_key(KeyCode::Right, KeyModifiers::CONTROL)
.unwrap();
assert_eq!(
harness.cursor_position(),
expected_end,
"Multiline: from position {}, Ctrl+Right should jump to {} (got {})",
start_pos,
expected_end,
harness.cursor_position()
);
}
}
}
#[test]
fn test_ctrl_left_jumps_to_word_beginning() {
{
let text = "hello world test";
let expected_destinations: Vec<(usize, usize)> = vec![
(0, 0), (1, 0), (2, 0), (3, 0), (4, 0), (5, 0), (6, 0), (7, 6), (8, 6), (9, 6), (10, 6), (11, 6), (12, 6), (13, 12), (14, 12), (15, 12), (16, 12), ];
for (start_pos, expected_end) in expected_destinations {
let mut harness = EditorTestHarness::new(80, 24).unwrap();
harness.type_text(text).unwrap();
harness.send_key(KeyCode::Home, KeyModifiers::NONE).unwrap();
for _ in 0..start_pos {
harness
.send_key(KeyCode::Right, KeyModifiers::NONE)
.unwrap();
}
assert_eq!(harness.cursor_position(), start_pos);
harness
.send_key(KeyCode::Left, KeyModifiers::CONTROL)
.unwrap();
assert_eq!(
harness.cursor_position(),
expected_end,
"Simple text: from position {}, Ctrl+Left should jump to {} (got {})",
start_pos,
expected_end,
harness.cursor_position()
);
}
}
{
let text = "bla::{\n next";
let expected_destinations: Vec<(usize, usize)> = vec![
(0, 0), (1, 0), (2, 0), (3, 0), (4, 3), (5, 3), (6, 3), (7, 3), (8, 3), (9, 3), (10, 3), (11, 10), (12, 10), (13, 10), (14, 10), ];
for (start_pos, expected_end) in expected_destinations {
let mut harness = EditorTestHarness::new(80, 24).unwrap();
harness.type_text(text).unwrap();
harness
.send_key(KeyCode::Home, KeyModifiers::CONTROL)
.unwrap();
for _ in 0..start_pos {
harness
.send_key(KeyCode::Right, KeyModifiers::NONE)
.unwrap();
}
assert_eq!(harness.cursor_position(), start_pos);
harness
.send_key(KeyCode::Left, KeyModifiers::CONTROL)
.unwrap();
assert_eq!(
harness.cursor_position(),
expected_end,
"Multiline: from position {}, Ctrl+Left should jump to {} (got {})",
start_pos,
expected_end,
harness.cursor_position()
);
}
}
}
#[test]
fn test_word_jump_symmetry() {
let mut harness = EditorTestHarness::new(80, 24).unwrap();
harness.type_text("one two three four five").unwrap();
harness.send_key(KeyCode::Home, KeyModifiers::NONE).unwrap();
let start_position = harness.cursor_position();
assert_eq!(start_position, 0);
for _ in 0..5 {
harness
.send_key(KeyCode::Right, KeyModifiers::CONTROL)
.unwrap();
}
let end_position = harness.cursor_position();
assert_eq!(
end_position, 23,
"Should be at end of text after 5 Ctrl+Right"
);
for _ in 0..5 {
harness
.send_key(KeyCode::Left, KeyModifiers::CONTROL)
.unwrap();
}
let final_position = harness.cursor_position();
assert_eq!(
final_position, start_position,
"Jumping right then left should return to start position"
);
}
#[test]
fn test_selection_movement_matches_regular_movement() {
{
let text = "alpha beta gamma delta";
let mut harness1 = EditorTestHarness::new(80, 24).unwrap();
harness1.type_text(text).unwrap();
harness1
.send_key(KeyCode::Home, KeyModifiers::NONE)
.unwrap();
let mut harness2 = EditorTestHarness::new(80, 24).unwrap();
harness2.type_text(text).unwrap();
harness2
.send_key(KeyCode::Home, KeyModifiers::NONE)
.unwrap();
for i in 0..4 {
harness1
.send_key(KeyCode::Right, KeyModifiers::CONTROL)
.unwrap();
harness2
.send_key(KeyCode::Right, KeyModifiers::CONTROL | KeyModifiers::SHIFT)
.unwrap();
assert_eq!(
harness1.cursor_position(),
harness2.cursor_position(),
"Simple text: After {} Ctrl+Right jumps, positions should match",
i + 1
);
}
}
{
let text = "bla::{\n next";
let mut harness1 = EditorTestHarness::new(80, 24).unwrap();
harness1.type_text(text).unwrap();
harness1
.send_key(KeyCode::Home, KeyModifiers::CONTROL)
.unwrap();
let mut harness2 = EditorTestHarness::new(80, 24).unwrap();
harness2.type_text(text).unwrap();
harness2
.send_key(KeyCode::Home, KeyModifiers::CONTROL)
.unwrap();
harness1
.send_key(KeyCode::Right, KeyModifiers::CONTROL)
.unwrap();
harness2
.send_key(KeyCode::Right, KeyModifiers::CONTROL | KeyModifiers::SHIFT)
.unwrap();
assert_eq!(
harness1.cursor_position(),
harness2.cursor_position(),
"Multiline: After 1st jump, Ctrl+Right ({}) and Ctrl+Shift+Right ({}) should match",
harness1.cursor_position(),
harness2.cursor_position()
);
harness1
.send_key(KeyCode::Right, KeyModifiers::CONTROL)
.unwrap();
harness2
.send_key(KeyCode::Right, KeyModifiers::CONTROL | KeyModifiers::SHIFT)
.unwrap();
assert_eq!(
harness1.cursor_position(),
harness2.cursor_position(),
"Multiline: After 2nd jump (punctuation), Ctrl+Right ({}) and Ctrl+Shift+Right ({}) should match - both should stop at end of line",
harness1.cursor_position(),
harness2.cursor_position()
);
harness1
.send_key(KeyCode::Right, KeyModifiers::CONTROL)
.unwrap();
harness2
.send_key(KeyCode::Right, KeyModifiers::CONTROL | KeyModifiers::SHIFT)
.unwrap();
assert_eq!(
harness1.cursor_position(),
harness2.cursor_position(),
"Multiline: After 3rd jump, Ctrl+Right ({}) and Ctrl+Shift+Right ({}) should match",
harness1.cursor_position(),
harness2.cursor_position()
);
}
{
let text = "alpha beta gamma delta";
let mut harness3 = EditorTestHarness::new(80, 24).unwrap();
harness3.type_text(text).unwrap();
let mut harness4 = EditorTestHarness::new(80, 24).unwrap();
harness4.type_text(text).unwrap();
for i in 0..4 {
harness3
.send_key(KeyCode::Left, KeyModifiers::CONTROL)
.unwrap();
harness4
.send_key(KeyCode::Left, KeyModifiers::CONTROL | KeyModifiers::SHIFT)
.unwrap();
assert_eq!(
harness3.cursor_position(),
harness4.cursor_position(),
"After {} Ctrl+Left jumps, positions should match",
i + 1
);
}
assert!(
harness4.has_selection(),
"Ctrl+Shift+Left should create selection"
);
}
}
#[test]
fn test_word_navigation_sanity() {
{
let mut harness = EditorTestHarness::new(80, 24).unwrap();
harness.type_text("hello world test").unwrap();
harness.send_key(KeyCode::Home, KeyModifiers::NONE).unwrap();
harness
.send_key(KeyCode::Right, KeyModifiers::CONTROL)
.unwrap();
assert_eq!(harness.cursor_position(), 5, "From word start to word end");
harness
.send_key(KeyCode::Right, KeyModifiers::CONTROL)
.unwrap();
assert_eq!(
harness.cursor_position(),
11,
"From whitespace to next word end"
);
}
{
let mut harness = EditorTestHarness::new(80, 24).unwrap();
harness.type_text("event::{ thing").unwrap();
harness.send_key(KeyCode::Home, KeyModifiers::NONE).unwrap();
harness
.send_key(KeyCode::Right, KeyModifiers::CONTROL)
.unwrap();
assert_eq!(harness.cursor_position(), 5, "From word to end of word");
harness
.send_key(KeyCode::Right, KeyModifiers::CONTROL)
.unwrap();
assert_eq!(
harness.cursor_position(),
8,
"From punctuation to end of punctuation run"
);
harness
.send_key(KeyCode::Right, KeyModifiers::CONTROL)
.unwrap();
assert_eq!(
harness.cursor_position(),
14,
"From whitespace to end of next word"
);
}
{
let mut harness = EditorTestHarness::new(80, 24).unwrap();
harness.type_text("alpha beta").unwrap();
harness
.send_key(KeyCode::Left, KeyModifiers::CONTROL)
.unwrap();
assert_eq!(
harness.cursor_position(),
6,
"From end to start of last word"
);
harness
.send_key(KeyCode::Left, KeyModifiers::CONTROL)
.unwrap();
assert_eq!(
harness.cursor_position(),
0,
"From word start to previous word start"
);
}
{
let mut harness = EditorTestHarness::new(80, 24).unwrap();
harness.type_text("foo bar").unwrap();
harness.send_key(KeyCode::Home, KeyModifiers::NONE).unwrap();
harness
.send_key(KeyCode::Right, KeyModifiers::CONTROL | KeyModifiers::SHIFT)
.unwrap();
assert_eq!(harness.cursor_position(), 3);
let selected = harness.get_selected_text();
assert_eq!(selected, "foo", "Should select first word");
harness
.send_key(KeyCode::Right, KeyModifiers::CONTROL | KeyModifiers::SHIFT)
.unwrap();
let selected = harness.get_selected_text();
assert_eq!(selected, "foo bar", "Should extend selection to next word");
}
}
#[test]
fn test_select_at_file_boundaries() {
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("test.txt");
let content = "Line 1\nLine 2\nLine 3\n";
std::fs::write(&file_path, content).unwrap();
let mut harness = EditorTestHarness::new(80, 24).unwrap();
harness.open_file(&file_path).unwrap();
harness.send_key(KeyCode::Up, KeyModifiers::SHIFT).unwrap();
harness.render().unwrap();
harness
.send_key(KeyCode::End, KeyModifiers::CONTROL)
.unwrap();
harness.render().unwrap();
harness
.send_key(KeyCode::Down, KeyModifiers::SHIFT)
.unwrap();
harness.render().unwrap();
for _ in 0..5 {
harness.send_key(KeyCode::Up, KeyModifiers::SHIFT).unwrap();
}
harness.render().unwrap();
let _selected = harness.get_selected_text();
}
#[test]
fn test_ctrl_d_selects_entire_word_from_middle() {
let mut harness = EditorTestHarness::new(80, 24).unwrap();
harness.type_text("hello world hello").unwrap();
harness.send_key(KeyCode::Home, KeyModifiers::NONE).unwrap();
harness
.send_key(KeyCode::Right, KeyModifiers::NONE)
.unwrap();
harness
.send_key(KeyCode::Right, KeyModifiers::NONE)
.unwrap();
assert_eq!(harness.cursor_position(), 2);
harness
.send_key(KeyCode::Char('d'), KeyModifiers::CONTROL)
.unwrap();
let selected = harness.get_selected_text();
assert_eq!(
selected, "hello",
"Ctrl+D from middle of word should select entire word 'hello', not just from cursor"
);
}
#[test]
fn test_ctrl_d_finds_next_match() {
let mut harness = EditorTestHarness::new(80, 24).unwrap();
harness.type_text("foo bar foo baz foo").unwrap();
harness.send_key(KeyCode::Home, KeyModifiers::NONE).unwrap();
harness
.send_key(KeyCode::Char('d'), KeyModifiers::CONTROL)
.unwrap();
let selected = harness.get_selected_text();
assert_eq!(selected, "foo", "First Ctrl+D should select 'foo'");
harness
.send_key(KeyCode::Char('d'), KeyModifiers::CONTROL)
.unwrap();
let cursor_count = harness.cursor_count();
assert_eq!(cursor_count, 2, "Should have 2 cursors after second Ctrl+D");
}
#[test]
fn test_ctrl_d_at_word_start() {
let mut harness = EditorTestHarness::new(80, 24).unwrap();
harness.type_text("test word test").unwrap();
harness.send_key(KeyCode::Home, KeyModifiers::NONE).unwrap();
assert_eq!(harness.cursor_position(), 0);
harness
.send_key(KeyCode::Char('d'), KeyModifiers::CONTROL)
.unwrap();
let selected = harness.get_selected_text();
assert_eq!(
selected, "test",
"Ctrl+D at word start should select 'test'"
);
}
#[test]
fn test_ctrl_d_at_word_end() {
let mut harness = EditorTestHarness::new(80, 24).unwrap();
harness.type_text("word test word").unwrap();
harness.send_key(KeyCode::Home, KeyModifiers::NONE).unwrap();
for _ in 0..4 {
harness
.send_key(KeyCode::Right, KeyModifiers::NONE)
.unwrap();
}
assert_eq!(harness.cursor_position(), 4);
harness
.send_key(KeyCode::Char('d'), KeyModifiers::CONTROL)
.unwrap();
let selected = harness.get_selected_text();
assert_eq!(
selected, "word",
"Ctrl+D at word end should select entire 'word'"
);
}