use crate::color::Color;
use crate::key::Key;
pub const TINT: Color = Color {
r: 0x3b as f32 / 255.0,
g: 0x5b as f32 / 255.0,
b: 0xd4 as f32 / 255.0,
a: 0x66 as f32 / 255.0,
};
pub const ROW_END: usize = u32::MAX as usize;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Endpoint {
pub node: Key,
pub byte: usize,
pub row: Option<u64>,
}
impl Endpoint {
pub fn new(node: Key, byte: usize) -> Self {
Self {
node,
byte,
row: None,
}
}
pub fn in_row(mut self, row: Option<u64>) -> Self {
self.row = row;
self
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Selection {
pub scope: Key,
pub anchor: Endpoint,
pub focus: Endpoint,
}
impl Selection {
pub fn new(scope: Key, anchor: Endpoint, focus: Endpoint) -> Self {
Self {
scope,
anchor,
focus,
}
}
pub fn is_empty(&self) -> bool {
self.anchor == self.focus
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum Grain {
#[default]
Char,
Word,
Run,
}
impl Grain {
pub(crate) fn of_clicks(clicks: u8) -> Self {
match clicks {
0 | 1 => Grain::Char,
2 => Grain::Word,
_ => Grain::Run,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct CellEnd {
pub line: u64,
pub col: usize,
}
impl CellEnd {
pub fn new(line: u64, col: usize) -> Self {
Self { line, col }
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct CellSelection {
pub node: Key,
pub anchor: CellEnd,
pub focus: CellEnd,
pub block: bool,
}
impl CellSelection {
pub fn new(node: Key, anchor: CellEnd, focus: CellEnd) -> Self {
Self {
node,
anchor,
focus,
block: false,
}
}
pub fn block(mut self, on: bool) -> Self {
self.block = on;
self
}
pub fn is_empty(&self) -> bool {
self.anchor == self.focus
}
pub fn to_value(self, handles: crate::value::Handles) -> crate::value::Value {
use crate::value::Value;
let end = |e: CellEnd| {
Value::map([
("line", Value::Int(e.line as i64)),
("col", Value::Int(e.col as i64)),
])
};
Value::map([
("node", (handles.key)(self.node)),
("anchor", end(self.anchor)),
("focus", end(self.focus)),
("block", Value::Bool(self.block)),
])
}
pub fn ordered(&self) -> (CellEnd, CellEnd) {
if self.anchor <= self.focus {
(self.anchor, self.focus)
} else {
(self.focus, self.anchor)
}
}
pub fn cols_on(&self, line: u64, cols: usize) -> Option<(usize, usize)> {
let (a, b) = self.ordered();
if self.block {
if line < a.line || line > b.line {
return None;
}
let (lo, hi) = (a.col.min(b.col), a.col.max(b.col));
return (lo < hi).then_some((lo.min(cols), hi.min(cols)));
}
clip_to_unit((a.line, a.col), (b.line, b.col), line, cols)
}
}
pub(crate) fn clip_to_unit<U: Ord + Copy>(
start: (U, usize),
end: (U, usize),
unit: U,
len: usize,
) -> Option<(usize, usize)> {
if unit < start.0 || unit > end.0 {
return None;
}
let from = if unit == start.0 { start.1 } else { 0 };
let to = if unit == end.0 { end.1 } else { len };
let (from, to) = (from.min(len), to.min(len));
(from < to).then_some((from, to))
}
pub(crate) fn grained_edges(
anchor: (usize, usize),
live: (usize, usize),
backwards: bool,
) -> (usize, usize) {
if backwards {
(anchor.1, live.0)
} else {
(anchor.0, live.1)
}
}
pub(crate) fn unbuilt_row_is_after(row: u64, last: Option<u64>) -> bool {
last.is_some_and(|hi| row > hi)
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct RangeEnd {
pub row: Option<u64>,
pub byte: usize,
}
impl RangeEnd {
pub fn to_value(self) -> crate::value::Value {
use crate::value::Value;
Value::map([
(
"index",
self.row.map_or(Value::Null, |r| Value::Int(r as i64)),
),
("byte", Value::Int(self.byte as i64)),
])
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum CopyRequest {
Ready(String),
Asked,
Nothing,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum DragAnchor {
Bytes(Key, usize, usize),
Cells(u64, usize, usize),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) struct SelectDrag {
pub scope: Key,
pub grain: Grain,
pub anchor: Option<DragAnchor>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) struct Ends {
pub start: (u32, usize),
pub end: (u32, usize),
}
impl Ends {
pub(crate) fn ordered(a: (u32, usize), b: (u32, usize)) -> Self {
if a <= b {
Self { start: a, end: b }
} else {
Self { start: b, end: a }
}
}
pub(crate) fn range_in(&self, ord: u32, len: usize) -> Option<(usize, usize)> {
clip_to_unit(self.start, self.end, ord, len)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn cells(a: (u64, usize), b: (u64, usize)) -> CellSelection {
CellSelection::new(Key::ROOT, CellEnd::new(a.0, a.1), CellEnd::new(b.0, b.1))
}
#[test]
fn a_linewise_cell_selection_runs_edge_to_edge_in_the_middle() {
let s = cells((10, 3), (12, 5));
assert_eq!(s.cols_on(10, 80), Some((3, 80)));
assert_eq!(s.cols_on(11, 80), Some((0, 80)));
assert_eq!(s.cols_on(12, 80), Some((0, 5)));
assert_eq!(s.cols_on(13, 80), None);
assert_eq!(s.cols_on(9, 80), None);
}
#[test]
fn a_block_selection_is_the_same_columns_on_every_line() {
let s = cells((10, 6), (12, 2)).block(true);
for line in 10..=12 {
assert_eq!(s.cols_on(line, 80), Some((2, 6)));
}
assert_eq!(s.cols_on(13, 80), None);
}
#[test]
fn a_backwards_drag_selects_the_same_thing() {
assert_eq!(
cells((12, 5), (10, 3)).ordered(),
cells((10, 3), (12, 5)).ordered()
);
}
#[test]
fn ends_order_by_ordinal_then_byte() {
let e = Ends::ordered((2, 5), (0, 9));
assert_eq!(e.start, (0, 9));
assert_eq!(e.end, (2, 5));
let same = Ends::ordered((1, 7), (1, 2));
assert_eq!(same.start, (1, 2));
assert_eq!(same.end, (1, 7));
}
#[test]
fn a_middle_node_is_selected_whole() {
let e = Ends::ordered((0, 3), (2, 4));
assert_eq!(e.range_in(1, 10), Some((0, 10)));
assert_eq!(e.range_in(0, 10), Some((3, 10)));
assert_eq!(e.range_in(2, 10), Some((0, 4)));
assert_eq!(e.range_in(3, 10), None);
}
#[test]
fn an_empty_range_selects_nothing() {
let e = Ends::ordered((1, 4), (1, 4));
assert_eq!(e.range_in(1, 10), None);
}
#[test]
fn ends_clamp_to_the_content_they_land_in() {
let e = Ends::ordered((0, 2), (0, 99));
assert_eq!(e.range_in(0, 5), Some((2, 5)));
}
}