use crate::primitives::termtui::screen::Screen;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct CopyPos {
pub x: i32,
pub y: i32,
}
impl CopyPos {
pub fn new(x: i32, y: i32) -> Self {
Self { x, y }
}
pub fn to_low_high(start: &CopyPos, end: &CopyPos) -> (CopyPos, CopyPos) {
if start.y < end.y || (start.y == end.y && start.x <= end.x) {
(*start, *end)
} else {
(*end, *start)
}
}
}
#[derive(Clone, Copy, Debug)]
pub enum CopyMoveDir {
Up,
Down,
Left,
Right,
LineStart,
LineEnd,
PageUp,
PageDown,
Top,
Bottom,
WordLeft,
WordRight,
}
impl CopyMoveDir {
pub fn delta(&self) -> (i32, i32) {
match self {
CopyMoveDir::Up => (0, -1),
CopyMoveDir::Down => (0, 1),
CopyMoveDir::Left => (-1, 0),
CopyMoveDir::Right => (1, 0),
_ => (0, 0), }
}
}
#[derive(Clone, Default)]
pub enum CopyMode {
#[default]
None,
Active {
frozen_screen: Box<Screen>,
cursor: CopyPos,
anchor: Option<CopyPos>,
screen_height: i32,
screen_width: i32,
scrollback_available: i32,
},
}
impl CopyMode {
pub fn is_active(&self) -> bool {
matches!(self, CopyMode::Active { .. })
}
pub fn enter(screen: Screen, start: CopyPos) -> Self {
let size = screen.size();
let scrollback = screen.primary_grid().scrollback_available() as i32;
CopyMode::Active {
frozen_screen: Box::new(screen),
cursor: start,
anchor: None,
screen_height: size.rows as i32,
screen_width: size.cols as i32,
scrollback_available: scrollback,
}
}
pub fn move_cursor(&mut self, dx: i32, dy: i32) {
if let CopyMode::Active {
cursor,
screen_width,
screen_height,
scrollback_available,
..
} = self
{
let new_x = (cursor.x + dx).clamp(0, *screen_width - 1);
let new_y = (cursor.y + dy).clamp(-*scrollback_available, *screen_height - 1);
cursor.x = new_x;
cursor.y = new_y;
}
}
pub fn move_dir(&mut self, dir: CopyMoveDir) {
if let CopyMode::Active {
cursor,
screen_width,
screen_height,
scrollback_available,
..
} = self
{
match dir {
CopyMoveDir::Up | CopyMoveDir::Down | CopyMoveDir::Left | CopyMoveDir::Right => {
let (dx, dy) = dir.delta();
self.move_cursor(dx, dy);
}
CopyMoveDir::LineStart => {
cursor.x = 0;
}
CopyMoveDir::LineEnd => {
cursor.x = *screen_width - 1;
}
CopyMoveDir::PageUp => {
let page = *screen_height / 2;
cursor.y = (cursor.y - page).max(-*scrollback_available);
}
CopyMoveDir::PageDown => {
let page = *screen_height / 2;
cursor.y = (cursor.y + page).min(*screen_height - 1);
}
CopyMoveDir::Top => {
cursor.y = -*scrollback_available;
cursor.x = 0;
}
CopyMoveDir::Bottom => {
cursor.y = *screen_height - 1;
cursor.x = *screen_width - 1;
}
CopyMoveDir::WordLeft | CopyMoveDir::WordRight => {
let delta = if matches!(dir, CopyMoveDir::WordLeft) {
-5
} else {
5
};
cursor.x = (cursor.x + delta).clamp(0, *screen_width - 1);
}
}
}
}
pub fn set_anchor(&mut self) {
if let CopyMode::Active { cursor, anchor, .. } = self {
if anchor.is_some() {
*anchor = None;
} else {
*anchor = Some(*cursor);
}
}
}
pub fn set_end(&mut self) {
if let CopyMode::Active { cursor, anchor, .. } = self {
if anchor.is_none() {
*anchor = Some(*cursor);
}
}
}
pub fn get_selection(&self) -> Option<(CopyPos, CopyPos)> {
if let CopyMode::Active { cursor, anchor, .. } = self {
anchor.map(|a| (a, *cursor))
} else {
None
}
}
pub fn get_selected_text(&self) -> Option<String> {
if let CopyMode::Active {
frozen_screen,
cursor,
anchor,
..
} = self
{
let anchor = (*anchor)?;
let (low, high) = CopyPos::to_low_high(&anchor, cursor);
Some(frozen_screen.get_selected_text(low.x, low.y, high.x, high.y))
} else {
None
}
}
pub fn frozen_screen(&self) -> Option<&Screen> {
if let CopyMode::Active { frozen_screen, .. } = self {
Some(frozen_screen)
} else {
None
}
}
pub fn cursor(&self) -> Option<CopyPos> {
if let CopyMode::Active { cursor, .. } = self {
Some(*cursor)
} else {
None
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_test_screen() -> Screen {
Screen::new(24, 80, 1000)
}
#[test]
fn test_copy_mode_enter() {
let screen = make_test_screen();
let mode = CopyMode::enter(screen, CopyPos::new(0, 0));
assert!(mode.is_active());
}
#[test]
fn test_copy_mode_move_cursor() {
let screen = make_test_screen();
let mut mode = CopyMode::enter(screen, CopyPos::new(10, 10));
mode.move_cursor(5, 2);
if let CopyMode::Active { cursor, .. } = mode {
assert_eq!(cursor.x, 15);
assert_eq!(cursor.y, 12);
} else {
panic!("Expected active mode");
}
}
#[test]
fn test_copy_mode_bounds() {
let screen = make_test_screen();
let mut mode = CopyMode::enter(screen, CopyPos::new(0, 0));
mode.move_cursor(-10, -10);
if let CopyMode::Active { cursor, .. } = mode {
assert_eq!(cursor.x, 0);
assert!(cursor.y <= 0);
} else {
panic!("Expected active mode");
}
}
#[test]
fn test_copy_mode_selection() {
let screen = make_test_screen();
let mut mode = CopyMode::enter(screen, CopyPos::new(5, 5));
assert!(mode.get_selection().is_none());
mode.set_anchor();
mode.move_cursor(10, 0);
let selection = mode.get_selection();
assert!(selection.is_some());
let (start, end) = selection.unwrap();
assert_eq!(start.x, 5);
assert_eq!(end.x, 15);
}
#[test]
fn test_copy_pos_to_low_high() {
let a = CopyPos::new(10, 5);
let b = CopyPos::new(5, 10);
let (low, high) = CopyPos::to_low_high(&a, &b);
assert_eq!(low.y, 5);
assert_eq!(high.y, 10);
}
}