use ratatui::layout::Rect;
use std::io::Write;
use crate::layout::PaneId;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Phase {
Anchored,
Dragging,
Done,
}
#[derive(Debug, Clone)]
pub struct Selection {
pub pane_id: PaneId,
anchor: (u16, u16),
cursor: (u16, u16),
phase: Phase,
pane_inner: Rect,
}
impl Selection {
pub fn anchor(pane_id: PaneId, row: u16, col: u16, pane_inner: Rect) -> Self {
Self {
pane_id,
anchor: (row, col),
cursor: (row, col),
phase: Phase::Anchored,
pane_inner,
}
}
pub fn drag(&mut self, screen_col: u16, screen_row: u16) {
let (row, col) = self.clamp_to_pane(screen_col, screen_row);
self.cursor = (row, col);
if self.cursor != self.anchor {
self.phase = Phase::Dragging;
}
}
pub fn finish(&mut self) -> bool {
if self.phase == Phase::Dragging {
self.phase = Phase::Done;
true
} else {
false
}
}
pub fn is_visible(&self) -> bool {
self.phase == Phase::Dragging || self.phase == Phase::Done
}
pub fn was_just_click(&self) -> bool {
self.phase == Phase::Anchored
}
fn ordered(&self) -> ((u16, u16), (u16, u16)) {
let (ar, ac) = self.anchor;
let (cr, cc) = self.cursor;
if ar < cr || (ar == cr && ac <= cc) {
((ar, ac), (cr, cc))
} else {
((cr, cc), (ar, ac))
}
}
pub fn contains(&self, row: u16, col: u16) -> bool {
if !self.is_visible() {
return false;
}
let ((sr, sc), (er, ec)) = self.ordered();
if row < sr || row > er {
return false;
}
if sr == er {
col >= sc && col <= ec
} else if row == sr {
col >= sc
} else if row == er {
col <= ec
} else {
true
}
}
fn clamp_to_pane(&self, screen_col: u16, screen_row: u16) -> (u16, u16) {
let r = &self.pane_inner;
let clamped_col = screen_col.clamp(r.x, r.x + r.width.saturating_sub(1));
let clamped_row = screen_row.clamp(r.y, r.y + r.height.saturating_sub(1));
(clamped_row - r.y, clamped_col - r.x)
}
}
pub fn extract_text(screen: &vt100::Screen, selection: &Selection) -> String {
let ((sr, sc), (er, ec)) = selection.ordered();
screen.contents_between(sr, sc, er, ec + 1)
}
pub fn write_osc52(text: &str) {
use base64::Engine;
let encoded = base64::engine::general_purpose::STANDARD.encode(text);
let sequence = format!("\x1b]52;c;{encoded}\x1b\\");
let _ = std::io::stdout().write_all(sequence.as_bytes());
let _ = std::io::stdout().flush();
}
#[cfg(test)]
mod tests {
use super::*;
fn make_sel(sr: u16, sc: u16, er: u16, ec: u16) -> Selection {
let mut sel = Selection::anchor(PaneId::from_raw(0), sr, sc, Rect::new(0, 0, 80, 24));
sel.cursor = (er, ec);
sel.phase = Phase::Dragging;
sel
}
#[test]
fn ordering_forward() {
let sel = make_sel(2, 5, 4, 10);
assert_eq!(sel.ordered(), ((2, 5), (4, 10)));
}
#[test]
fn ordering_backward() {
let sel = make_sel(4, 10, 2, 5);
assert_eq!(sel.ordered(), ((2, 5), (4, 10)));
}
#[test]
fn single_line_contains() {
let sel = make_sel(2, 5, 2, 15);
assert!(!sel.contains(2, 4));
assert!(sel.contains(2, 5));
assert!(sel.contains(2, 10));
assert!(sel.contains(2, 15)); assert!(!sel.contains(2, 16));
assert!(!sel.contains(1, 10));
assert!(!sel.contains(3, 10));
}
#[test]
fn multi_line_contains() {
let sel = make_sel(2, 5, 4, 10);
assert!(!sel.contains(2, 4));
assert!(sel.contains(2, 5));
assert!(sel.contains(2, 79));
assert!(sel.contains(3, 0));
assert!(sel.contains(3, 79));
assert!(sel.contains(4, 0));
assert!(sel.contains(4, 10));
assert!(!sel.contains(4, 11));
}
#[test]
fn anchored_not_visible() {
let sel = Selection::anchor(PaneId::from_raw(0), 5, 10, Rect::new(0, 0, 80, 24));
assert!(!sel.is_visible());
assert!(!sel.contains(5, 10));
}
#[test]
fn click_without_drag() {
let mut sel = Selection::anchor(PaneId::from_raw(0), 5, 10, Rect::new(0, 0, 80, 24));
assert!(sel.was_just_click());
let copied = sel.finish();
assert!(!copied);
}
#[test]
fn drag_then_finish() {
let mut sel = Selection::anchor(PaneId::from_raw(0), 5, 10, Rect::new(10, 5, 80, 24));
sel.drag(20, 7); assert!(sel.is_visible());
assert!(!sel.was_just_click());
let copied = sel.finish();
assert!(copied);
}
#[test]
fn clamp_to_pane_bounds() {
let sel = Selection::anchor(PaneId::from_raw(0), 0, 0, Rect::new(10, 5, 80, 24));
let (row, col) = sel.clamp_to_pane(200, 100);
assert_eq!(row, 23); assert_eq!(col, 79);
let (row, col) = sel.clamp_to_pane(0, 0);
assert_eq!(row, 0);
assert_eq!(col, 0);
}
}