use std::ops::Range;
use crate::{
text::{IterCfg, Point, PrintCfg, Text},
ui::{Area, Caret},
};
#[derive(Default, Debug)]
pub struct Cursor {
caret: VPoint,
anchor: Option<VPoint>,
pub(crate) assoc_index: Option<usize>,
}
impl Cursor {
pub fn new(point: Point, text: &Text, area: &impl Area, cfg: &PrintCfg) -> Cursor {
let cfg = IterCfg::new(cfg).dont_wrap();
Cursor {
caret: VPoint::new(point, text, area, cfg),
anchor: None,
assoc_index: None,
}
}
pub fn move_to(&mut self, point: Point, text: &Text, area: &impl Area, cfg: &PrintCfg) {
let cfg = IterCfg::new(cfg).dont_wrap();
self.caret = VPoint::new(point, text, area, cfg);
}
pub fn move_hor(&mut self, by: isize, text: &Text, area: &impl Area, cfg: &PrintCfg) {
if by == 0 {
return;
}
let point = if by > 0 {
text.iter_at(self.caret())
.no_tags()
.nth(by as usize)
.map(|item| item.real)
.unwrap_or(text.max_point())
} else {
text.rev_iter_at(self.caret())
.no_tags()
.nth(by.unsigned_abs() - 1)
.map(|item| item.real)
.unwrap_or_default()
};
let cfg = IterCfg::new(cfg).dont_wrap();
self.caret = VPoint::new(point, text, area, cfg);
}
pub fn move_ver(&mut self, by: isize, text: &Text, area: &impl Area, cfg: &PrintCfg) {
let cfg = IterCfg::new(cfg).dont_wrap();
if by == 0 {
return;
}
let target = self.caret.line().saturating_add_signed(by);
let dcol = self.caret.dcol;
let point = if self.caret.line().saturating_add_signed(by) > text.len_lines() {
self.caret.point
} else if by > 0 {
area.print_iter(text.iter_at(self.caret.point), cfg)
.filter_map(|(caret, item)| Some(caret).zip(item.as_real_char()))
.find_map(|(Caret { x, len, .. }, (point, char))| {
(point.line() == target && (x + len > dcol || char == '\n')).then_some(point)
})
.unwrap_or(text.max_point())
} else if self.caret.line().checked_add_signed(by).is_none() {
self.caret.point
} else {
area.rev_print_iter(text.rev_iter_at(self.caret.point), cfg)
.filter_map(|(caret, item)| Some(caret.x).zip(item.as_real_char()))
.find_map(|(x, (point, _))| (point.line() == target && dcol >= x).then_some(point))
.unwrap_or(Point::default())
};
self.caret.point = point;
self.caret.vcol = vcol(point, text, area, cfg)
}
pub fn move_ver_wrapped(
&mut self,
_by: isize,
_text: &Text,
_area: &impl Area,
_cfg: &PrintCfg,
) {
todo!()
}
pub fn set_anchor(&mut self) {
self.anchor = Some(self.caret)
}
pub fn unset_anchor(&mut self) -> Option<Point> {
self.anchor.take().map(|a| a.point)
}
pub fn swap_ends(&mut self) {
if let Some(anchor) = self.anchor.as_mut() {
std::mem::swap(&mut self.caret, anchor)
}
}
pub fn caret(&self) -> Point {
self.caret.point
}
pub fn anchor(&self) -> Option<Point> {
self.anchor.map(|a| a.point)
}
pub fn byte(&self) -> usize {
self.caret.byte()
}
pub fn char(&self) -> usize {
self.caret.char()
}
pub fn vcol(&self) -> usize {
self.caret.vcol()
}
pub fn line(&self) -> usize {
self.caret.line()
}
pub fn range(&self) -> Range<usize> {
let anchor = self.anchor.unwrap_or(self.caret);
if anchor < self.caret {
anchor.byte()..self.caret.byte()
} else {
self.caret.byte()..anchor.byte()
}
}
pub fn point_range(&self) -> (Point, Point) {
let anchor = self.anchor.unwrap_or(self.caret);
(
self.caret.point.min(anchor.point),
self.caret.point.max(anchor.point),
)
}
}
impl std::fmt::Display for Cursor {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}:{}", self.caret.line() + 1, self.caret.vcol() + 1)
}
}
impl Clone for Cursor {
fn clone(&self) -> Self {
Cursor {
assoc_index: None,
..*self
}
}
}
#[derive(Default, Debug, Clone, Copy, Eq)]
struct VPoint {
point: Point,
vcol: usize,
dcol: usize,
}
impl PartialOrd for VPoint {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.point.cmp(&other.point))
}
}
impl Ord for VPoint {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.partial_cmp(other).unwrap()
}
}
impl PartialEq for VPoint {
fn eq(&self, other: &Self) -> bool {
self.point == other.point
}
}
impl VPoint {
fn new(point: Point, text: &Text, area: &impl Area, cfg: IterCfg) -> Self {
let vcol = vcol(point, text, area, cfg);
Self {
point,
vcol,
dcol: vcol,
}
}
fn byte(&self) -> usize {
self.point.byte()
}
fn char(&self) -> usize {
self.point.char()
}
fn line(&self) -> usize {
self.point.line()
}
fn vcol(&self) -> usize {
self.vcol
}
}
fn vcol(point: Point, text: &Text, area: &impl Area, cfg: IterCfg) -> usize {
let after = text.points_after(point).unwrap();
let iter = text.rev_iter_at(after);
area.rev_print_iter(iter, cfg)
.find_map(|(caret, item)| item.part.is_char().then_some(caret.x))
.unwrap_or(0)
}