use super::App;
pub(crate) fn normalize_selection(
a: super::SelectionPoint,
b: super::SelectionPoint,
) -> (super::SelectionPoint, super::SelectionPoint) {
if (a.row, a.col) <= (b.row, b.col) { (a, b) } else { (b, a) }
}
pub(super) fn clear_selection(app: &mut App) {
app.selection = None;
app.rendered_chat_lines.clear();
app.rendered_input_lines.clear();
}
#[cfg(test)]
fn slice_by_cols(text: &str, start_col: usize, end_col: usize) -> String {
let mut out = String::new();
for (i, ch) in text.chars().enumerate() {
if i >= end_col {
break;
}
if i >= start_col {
out.push(ch);
}
}
out
}
#[cfg(test)]
mod tests {
use super::*;
use crate::app::SelectionPoint;
use pretty_assertions::assert_eq;
#[test]
fn normalize_already_ordered() {
let a = SelectionPoint { row: 0, col: 2 };
let b = SelectionPoint { row: 1, col: 5 };
let (start, end) = normalize_selection(a, b);
assert_eq!(start, a);
assert_eq!(end, b);
}
#[test]
fn normalize_reversed() {
let a = SelectionPoint { row: 2, col: 3 };
let b = SelectionPoint { row: 0, col: 1 };
let (start, end) = normalize_selection(a, b);
assert_eq!(start, b);
assert_eq!(end, a);
}
#[test]
fn normalize_same_point() {
let p = SelectionPoint { row: 5, col: 10 };
let (start, end) = normalize_selection(p, p);
assert_eq!(start, p);
assert_eq!(end, p);
}
#[test]
fn normalize_same_row_different_cols() {
let a = SelectionPoint { row: 3, col: 10 };
let b = SelectionPoint { row: 3, col: 2 };
let (start, end) = normalize_selection(a, b);
assert_eq!(start.col, 2);
assert_eq!(end.col, 10);
}
#[test]
fn normalize_origin() {
let a = SelectionPoint { row: 0, col: 0 };
let b = SelectionPoint { row: 0, col: 0 };
let (start, end) = normalize_selection(a, b);
assert_eq!(start, a);
assert_eq!(end, b);
}
#[test]
fn normalize_same_row_same_col_nonzero() {
let p = SelectionPoint { row: 7, col: 7 };
let (start, end) = normalize_selection(p, p);
assert_eq!(start, p);
assert_eq!(end, p);
}
#[test]
fn normalize_large_coordinates() {
let a = SelectionPoint { row: usize::MAX, col: usize::MAX };
let b = SelectionPoint { row: 0, col: 0 };
let (start, end) = normalize_selection(a, b);
assert_eq!(start.row, 0);
assert_eq!(end.row, usize::MAX);
}
#[test]
fn normalize_row_priority_over_col() {
let a = SelectionPoint { row: 0, col: usize::MAX };
let b = SelectionPoint { row: 1, col: 0 };
let (start, end) = normalize_selection(a, b);
assert_eq!(start, a, "row 0 must come first regardless of col");
assert_eq!(end, b);
}
#[test]
fn normalize_adjacent_rows_col_zero() {
let a = SelectionPoint { row: 5, col: 0 };
let b = SelectionPoint { row: 4, col: 0 };
let (start, end) = normalize_selection(a, b);
assert_eq!(start.row, 4);
assert_eq!(end.row, 5);
}
#[test]
fn normalize_symmetry_many_pairs() {
let pairs = [
(SelectionPoint { row: 0, col: 0 }, SelectionPoint { row: 0, col: 1 }),
(SelectionPoint { row: 3, col: 9 }, SelectionPoint { row: 1, col: 100 }),
(SelectionPoint { row: 100, col: 0 }, SelectionPoint { row: 0, col: 100 }),
(SelectionPoint { row: 42, col: 42 }, SelectionPoint { row: 42, col: 42 }),
];
for (a, b) in pairs {
let (s1, e1) = normalize_selection(a, b);
let (s2, e2) = normalize_selection(b, a);
assert_eq!((s1, e1), (s2, e2), "normalize must be symmetric for {a:?} / {b:?}");
}
}
#[test]
fn normalize_idempotent() {
let a = SelectionPoint { row: 10, col: 20 };
let b = SelectionPoint { row: 3, col: 50 };
let (s, e) = normalize_selection(a, b);
let (s2, e2) = normalize_selection(s, e);
assert_eq!((s, e), (s2, e2));
}
#[test]
fn slice_ascii_mid() {
assert_eq!(slice_by_cols("hello world", 2, 7), "llo w");
}
#[test]
fn slice_full_string() {
assert_eq!(slice_by_cols("abc", 0, 3), "abc");
}
#[test]
fn slice_single_char() {
assert_eq!(slice_by_cols("hello", 1, 2), "e");
}
#[test]
fn slice_single_char_string_full() {
assert_eq!(slice_by_cols("x", 0, 1), "x");
}
#[test]
fn slice_empty_string() {
assert_eq!(slice_by_cols("", 0, 5), "");
}
#[test]
fn slice_empty_string_zero_range() {
assert_eq!(slice_by_cols("", 0, 0), "");
}
#[test]
fn slice_start_equals_end() {
assert_eq!(slice_by_cols("hello", 3, 3), "");
}
#[test]
fn slice_out_of_bounds() {
assert_eq!(slice_by_cols("hi", 0, 100), "hi");
}
#[test]
fn slice_start_beyond_string() {
assert_eq!(slice_by_cols("hi", 50, 100), "");
}
#[test]
fn slice_start_greater_than_end() {
assert_eq!(slice_by_cols("hello world", 7, 2), "");
}
#[test]
fn slice_tab_chars() {
let s = "a\tb\tc";
assert_eq!(slice_by_cols(s, 0, 2), "a\t");
assert_eq!(slice_by_cols(s, 1, 4), "\tb\t");
}
#[test]
fn slice_with_embedded_newline() {
let s = "ab\ncd";
assert_eq!(slice_by_cols(s, 1, 4), "b\nc");
}
#[test]
fn slice_with_embedded_cr() {
let s = "ab\rcd";
assert_eq!(slice_by_cols(s, 0, 5), "ab\rcd");
}
#[test]
fn slice_with_null_byte() {
let s = "a\0b";
assert_eq!(slice_by_cols(s, 0, 3), "a\0b");
assert_eq!(slice_by_cols(s, 1, 2), "\0");
}
#[test]
fn slice_unicode_emoji() {
let s = "a\u{1F600}b\u{1F600}c";
assert_eq!(slice_by_cols(s, 1, 4), "\u{1F600}b\u{1F600}");
}
#[test]
fn slice_cjk_chars() {
let s = "\u{4F60}\u{597D}\u{4E16}\u{754C}"; assert_eq!(slice_by_cols(s, 1, 3), "\u{597D}\u{4E16}");
}
#[test]
fn slice_mixed_unicode_and_ascii() {
let s = "hi\u{1F600}world";
assert_eq!(slice_by_cols(s, 0, 3), "hi\u{1F600}");
assert_eq!(slice_by_cols(s, 3, 8), "world");
}
#[test]
fn slice_combining_diacritical_splits_glyph() {
let s = "e\u{0301}x"; assert_eq!(slice_by_cols(s, 0, 1), "e"); assert_eq!(slice_by_cols(s, 1, 2), "\u{0301}"); assert_eq!(slice_by_cols(s, 0, 2), "e\u{0301}"); assert_eq!(slice_by_cols(s, 0, 3), "e\u{0301}x"); }
#[test]
fn slice_stacked_combining_marks() {
let s = "a\u{030A}\u{0304}z"; assert_eq!(slice_by_cols(s, 0, 3), "a\u{030A}\u{0304}");
assert_eq!(slice_by_cols(s, 1, 3), "\u{030A}\u{0304}"); assert_eq!(slice_by_cols(s, 3, 4), "z");
}
#[test]
fn slice_zwj_family_emoji() {
let s = "\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F467}";
assert_eq!(s.chars().count(), 5);
assert_eq!(slice_by_cols(s, 0, 2), "\u{1F468}\u{200D}");
assert_eq!(slice_by_cols(s, 0, 5), s);
}
#[test]
fn slice_flag_emoji_splits() {
let flag = "\u{1F1FA}\u{1F1F8}"; assert_eq!(flag.chars().count(), 2);
assert_eq!(slice_by_cols(flag, 0, 1), "\u{1F1FA}"); assert_eq!(slice_by_cols(flag, 0, 2), flag); assert_eq!(slice_by_cols(flag, 1, 2), "\u{1F1F8}"); }
#[test]
fn slice_arabic_rtl() {
let s = "\u{0645}\u{0631}\u{062D}\u{0628}\u{0627}"; assert_eq!(s.chars().count(), 5);
assert_eq!(slice_by_cols(s, 1, 4), "\u{0631}\u{062D}\u{0628}");
}
#[test]
fn slice_all_emoji() {
let s = "\u{1F600}\u{1F601}\u{1F602}\u{1F603}\u{1F604}";
assert_eq!(slice_by_cols(s, 2, 4), "\u{1F602}\u{1F603}");
assert_eq!(slice_by_cols(s, 0, 5), s);
}
#[test]
fn slice_stress_10k_chars() {
let s: String = (0..10_000).map(|i| if i % 2 == 0 { 'a' } else { 'b' }).collect();
let sliced = slice_by_cols(&s, 4000, 4010);
assert_eq!(sliced.len(), 10);
assert_eq!(sliced, "ababababab");
}
#[test]
fn slice_stress_10k_emoji() {
let s: String = "\u{1F600}".repeat(10_000);
let sliced = slice_by_cols(&s, 9990, 10_000);
assert_eq!(sliced.chars().count(), 10);
}
#[test]
fn slice_last_char_only() {
assert_eq!(slice_by_cols("abcdef", 5, 6), "f");
}
#[test]
fn slice_first_char_only() {
assert_eq!(slice_by_cols("abcdef", 0, 1), "a");
}
#[test]
fn slice_variation_selector() {
let s = "\u{2764}\u{FE0F}x"; assert_eq!(s.chars().count(), 3);
assert_eq!(slice_by_cols(s, 0, 2), "\u{2764}\u{FE0F}");
assert_eq!(slice_by_cols(s, 2, 3), "x");
}
#[test]
fn slice_mixed_scripts() {
let s = "Hi\u{4F60}\u{1F600}\u{0645}!";
assert_eq!(s.chars().count(), 6);
assert_eq!(slice_by_cols(s, 0, 6), s);
assert_eq!(slice_by_cols(s, 2, 5), "\u{4F60}\u{1F600}\u{0645}");
}
#[test]
fn slice_zero_width_at_every_position() {
let s = "hello";
for i in 0..=5 {
assert_eq!(slice_by_cols(s, i, i), "", "zero-width at col {i}");
}
}
#[test]
fn slice_sliding_window() {
let s = "abcde";
let windows: Vec<String> = (0..3).map(|i| slice_by_cols(s, i, i + 3)).collect();
assert_eq!(windows, vec!["abc", "bcd", "cde"]);
}
}