use std::{collections::HashSet, ops::Range}; use zellij_utils::position::Position; // The selection is empty when start == end // it includes the character at start, and everything before end. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct Selection { pub start: Position, pub end: Position, active: bool, // used to handle moving the selection up and down } impl Default for Selection { fn default() -> Self { Self { start: Position::new(0, 0), end: Position::new(0, 0), active: false, } } } impl Selection { pub fn start(&mut self, start: Position) { self.active = true; self.start = start; self.end = start; } pub fn to(&mut self, to: Position) { self.end = to } pub fn end(&mut self, end: Position) { self.active = false; self.end = end; } pub fn contains(&self, row: usize, col: usize) -> bool { let row = row as isize; let (start, end) = if self.start <= self.end { (self.start, self.end) } else { (self.end, self.start) }; if (start.line.0) < row && row < end.line.0 { return true; } if start.line == end.line { return row == start.line.0 && start.column.0 <= col && col < end.column.0; } if start.line.0 == row && col >= start.column.0 { return true; } end.line.0 == row && col < end.column.0 } pub fn contains_row(&self, row: usize) -> bool { let row = row as isize; let (start, end) = if self.start <= self.end { (self.start, self.end) } else { (self.end, self.start) }; start.line.0 <= row && end.line.0 >= row } pub fn is_empty(&self) -> bool { self.start == self.end } pub fn reset(&mut self) { self.start = Position::new(0, 0); self.end = self.start; } pub fn sorted(&self) -> Self { let (start, end) = if self.start <= self.end { (self.start, self.end) } else { (self.end, self.start) }; Self { start, end, active: self.active, } } pub fn line_indices(&self) -> std::ops::RangeInclusive { let sorted = self.sorted(); sorted.start.line.0..=sorted.end.line.0 } pub fn move_up(&mut self, lines: usize) { self.start.line.0 -= lines as isize; if !self.active { self.end.line.0 -= lines as isize; } } pub fn move_down(&mut self, lines: usize) { self.start.line.0 += lines as isize; if !self.active { self.end.line.0 += lines as isize; } } pub fn offset(mut self, offset_x: usize, offset_y: usize) -> Self { self.start.line.0 += offset_y as isize; self.end.line.0 += offset_y as isize; self.start.column.0 += offset_x; self.end.column.0 += offset_x; self } /// Return an iterator over the line indices, up to max, that are not present in both self and other, /// except for the indices of the first and last line of both self and s2, that are always included. pub fn diff(&self, other: &Self, max: usize) -> impl Iterator { let mut lines_to_update = HashSet::new(); lines_to_update.insert(self.start.line.0); lines_to_update.insert(self.end.line.0); lines_to_update.insert(other.start.line.0); lines_to_update.insert(other.end.line.0); let old_lines: HashSet = self.get_visible_indices(max).collect(); let new_lines: HashSet = other.get_visible_indices(max).collect(); old_lines.symmetric_difference(&new_lines).for_each(|&l| { let _ = lines_to_update.insert(l); }); lines_to_update .into_iter() .filter(move |&l| l >= 0 && l < max as isize) } fn get_visible_indices(&self, max: usize) -> Range { let Selection { start, end, .. } = self.sorted(); let start = start.line.0.max(0); let end = end.line.0.min(max as isize); start..end } } #[cfg(test)] #[path = "./unit/selection_tests.rs"] mod selection_tests;