use ratatui::text::Line;
use crate::document::Buffer;
use crate::markdown::table_layout::char_cells;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Selection {
pub anchor: usize,
pub active: usize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CellBand {
pub lines: (usize, usize),
pub cols: (usize, usize),
}
impl CellBand {
pub fn char_cols(&self, line: &Line<'_>) -> (usize, usize) {
let char_at = |target: usize| {
let mut cells = 0usize;
let mut idx = 0usize;
for ch in line.spans.iter().flat_map(|s| s.content.chars()) {
if cells >= target {
break;
}
cells += char_cells(ch);
idx += 1;
}
idx
};
(char_at(self.cols.0), char_at(self.cols.1))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct VisualSelection {
pub anchor: (usize, usize),
pub active: (usize, usize),
pub band: Option<CellBand>,
}
impl VisualSelection {
pub fn span(anchor: (usize, usize), active: (usize, usize)) -> Self {
Self {
anchor,
active,
band: None,
}
}
pub fn range(&self) -> ((usize, usize), (usize, usize)) {
if self.anchor <= self.active {
(self.anchor, self.active)
} else {
(self.active, self.anchor)
}
}
pub fn is_empty(&self) -> bool {
self.anchor == self.active
}
}
impl Selection {
#[allow(dead_code)]
pub fn new(anchor: usize) -> Self {
Self {
anchor,
active: anchor,
}
}
pub fn range(&self) -> (usize, usize) {
(self.anchor.min(self.active), self.anchor.max(self.active))
}
pub fn selected_text(&self, buf: &Buffer) -> String {
let (start, end) = self.range();
let end = end.min(buf.len_chars());
if start >= end {
return String::new();
}
buf.slice_to_string(start, end)
}
pub fn is_empty(&self) -> bool {
self.anchor == self.active
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::document::Buffer;
fn buf(s: &str) -> Buffer {
Buffer::from_str(s)
}
#[test]
fn cell_band_char_cols_follow_each_lines_glyph_widths() {
let band = CellBand {
lines: (0, 1),
cols: (9, 11),
};
assert_eq!(band.char_cols(&Line::from("│ 日本 │ ab │")), (7, 9));
assert_eq!(band.char_cols(&Line::from("│ 語 │ cd │")), (8, 10));
}
#[test]
fn new_selection_is_empty() {
let s = Selection::new(5);
assert!(s.is_empty());
assert_eq!(s.range(), (5, 5));
}
#[test]
fn range_forward() {
let s = Selection {
anchor: 2,
active: 7,
};
assert_eq!(s.range(), (2, 7));
}
#[test]
fn range_backward() {
let s = Selection {
anchor: 7,
active: 2,
};
assert_eq!(s.range(), (2, 7));
}
#[test]
fn selected_text_forward() {
let b = buf("hello world");
let s = Selection {
anchor: 6,
active: 11,
};
assert_eq!(s.selected_text(&b), "world");
}
#[test]
fn selected_text_backward() {
let b = buf("hello world");
let s = Selection {
anchor: 11,
active: 6,
};
assert_eq!(s.selected_text(&b), "world");
}
#[test]
fn selected_text_empty() {
let b = buf("hello");
let s = Selection::new(3);
assert_eq!(s.selected_text(&b), "");
}
}