use std::collections::HashMap;
use crossterm::style::Stylize;
use get_size::GetSize;
use r3bl_rs_utils_core::*;
use serde::{Deserialize, Serialize};
#[derive(Clone, PartialEq, Serialize, Deserialize, GetSize, Default)]
pub struct SelectionMap {
pub map: HashMap<RowIndex, SelectionRange>,
pub maybe_previous_direction: Option<CaretMovementDirection>,
}
pub type RowIndex = ChUnit;
#[test]
fn test_selection_map_direction_change() {
use crate::selection_map_impl::DirectionChangeResult;
{
let map = SelectionMap {
maybe_previous_direction: None,
..Default::default()
};
let current_direction = CaretMovementDirection::Down;
let actual = map.has_caret_movement_direction_changed(current_direction);
let expected = DirectionChangeResult::DirectionIsTheSame;
assert_eq2!(actual, expected);
}
{
let map = SelectionMap {
maybe_previous_direction: Some(CaretMovementDirection::Up),
..Default::default()
};
let current_direction = CaretMovementDirection::Down;
let actual = map.has_caret_movement_direction_changed(current_direction);
let expected = DirectionChangeResult::DirectionHasChanged;
assert_eq2!(actual, expected);
}
{
let map = SelectionMap {
maybe_previous_direction: Some(CaretMovementDirection::Down),
..Default::default()
};
let current_direction = CaretMovementDirection::Down;
let actual = map.has_caret_movement_direction_changed(current_direction);
let expected = DirectionChangeResult::DirectionIsTheSame;
assert_eq2!(actual, expected);
}
}
pub mod selection_map_impl {
use std::fmt::{Debug, Display};
use crossterm::style::StyledContent;
use super::*;
use crate::{DeleteSelectionWith, EditorBuffer};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum DirectionChangeResult {
DirectionHasChanged,
DirectionIsTheSame,
}
impl SelectionMap {
pub fn get_caret_at_start_of_range(
&self,
_with: DeleteSelectionWith,
) -> Option<Position> {
let indices = self.get_ordered_indices();
let first_row_index = indices.first()?;
let last_row_index = indices.first()?;
Some(position!(
col_index: self.map.get(last_row_index)?.start_display_col_index,
row_index: *first_row_index
))
}
pub fn get_selected_lines<'a>(
&self,
buffer: &'a EditorBuffer,
) -> HashMap<RowIndex, &'a str> {
let mut it = HashMap::new();
let lines = buffer.get_lines();
let row_indices = self.get_ordered_indices();
for row_index in row_indices {
if let Some(selection_range) = self.map.get(&row_index) {
if let Some(line) = lines.get(ch!(@to_usize row_index)) {
let selected_text = line.clip_to_range(*selection_range);
it.insert(row_index, selected_text);
}
}
}
it
}
pub fn get_ordered_indices(&self) -> Vec<RowIndex> {
let row_indices = {
let mut it: Vec<ChUnit> = self.map.keys().copied().collect();
it.sort();
it
};
row_indices
}
pub fn is_empty(&self) -> bool { self.map.is_empty() }
pub fn clear(&mut self) {
self.map.clear();
self.maybe_previous_direction = None;
}
pub fn iter(&self) -> impl Iterator<Item = (&RowIndex, &SelectionRange)> {
self.map.iter()
}
pub fn get(&self, row_index: RowIndex) -> Option<&SelectionRange> {
self.map.get(&row_index)
}
pub fn has_caret_movement_direction_changed(
&self,
current_direction: CaretMovementDirection,
) -> DirectionChangeResult {
if let Some(previous_direction) = self.maybe_previous_direction {
if previous_direction != current_direction {
return DirectionChangeResult::DirectionHasChanged;
}
}
DirectionChangeResult::DirectionIsTheSame
}
pub fn insert(
&mut self,
row_index: RowIndex,
selection_range: SelectionRange,
direction: CaretMovementDirection,
) {
self.map.insert(row_index, selection_range);
self.update_previous_direction(direction);
}
pub fn remove(&mut self, row_index: RowIndex, direction: CaretMovementDirection) {
self.map.remove(&row_index);
self.update_previous_direction(direction);
}
pub fn update_previous_direction(&mut self, direction: CaretMovementDirection) {
self.maybe_previous_direction = Some(direction);
}
pub fn remove_previous_direction(&mut self) {
self.maybe_previous_direction = None;
}
pub fn locate_row(&self, row_index_arg: ChUnit) -> RowLocationInSelectionMap {
for (row_index, _range) in self.map.iter() {
if &row_index_arg == row_index {
return RowLocationInSelectionMap::Contained;
}
}
RowLocationInSelectionMap::Overflow
}
}
#[derive(Clone, PartialEq, Serialize, Deserialize, GetSize, Copy, Debug)]
pub enum RowLocationInSelectionMap {
Overflow,
Contained,
}
mod debug_display {
use super::*;
impl SelectionMap {
pub fn to_formatted_string(&self) -> StyledContent<String> {
let selection_map_str = self.to_unformatted_string();
if selection_map_str.contains("None") {
selection_map_str.white().on_dark_grey()
} else {
selection_map_str.green().on_dark_grey()
}
}
pub fn to_unformatted_string(&self) -> String {
let spacer = "\n ";
let mut vec_output = {
let mut it = vec![];
let sorted_indices = self.get_ordered_indices();
for (index, row_index) in sorted_indices.iter().enumerate() {
if let Some(selected_range) = self.map.get(row_index) {
let first_char = if index == 0 {
format!("{spacer}✂️")
} else {
"✂️".to_string()
};
it.push(format!(
"{0} ┆row: {1} => start: {2}, end: {3}┆",
first_char,
row_index,
selected_range.start_display_col_index,
selected_range.end_display_col_index
));
}
}
it
};
if vec_output.is_empty() {
vec_output.push("✂️ ┆--empty--┆".to_string());
}
vec_output
.push(format!("🧭 prev_dir: {:?}", self.maybe_previous_direction,));
vec_output.join(spacer)
}
}
impl Display for SelectionMap {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.to_formatted_string())
}
}
impl Debug for SelectionMap {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.to_formatted_string())
}
}
}
}