use std::ops::Range;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct Pos {
pub line: u32,
pub col: u32,
}
impl Pos {
pub const ORIGIN: Pos = Pos { line: 0, col: 0 };
pub const fn new(line: u32, col: u32) -> Self {
Pos { line, col }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum SelectionKind {
#[default]
Char,
Line,
Block,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Selection {
pub anchor: Pos,
pub head: Pos,
pub kind: SelectionKind,
}
impl Selection {
pub const fn caret(pos: Pos) -> Self {
Selection {
anchor: pos,
head: pos,
kind: SelectionKind::Char,
}
}
pub const fn char_range(anchor: Pos, head: Pos) -> Self {
Selection {
anchor,
head,
kind: SelectionKind::Char,
}
}
pub fn is_empty(&self) -> bool {
self.anchor == self.head
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SelectionSet {
pub items: Vec<Selection>,
pub primary: usize,
}
impl SelectionSet {
pub fn caret(pos: Pos) -> Self {
SelectionSet {
items: vec![Selection::caret(pos)],
primary: 0,
}
}
pub fn primary(&self) -> &Selection {
self.items
.get(self.primary)
.or_else(|| self.items.first())
.expect("SelectionSet must contain at least one selection")
}
}
impl Default for SelectionSet {
fn default() -> Self {
SelectionSet::caret(Pos::ORIGIN)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Edit {
pub range: Range<Pos>,
pub replacement: String,
}
impl Edit {
pub fn insert(at: Pos, text: impl Into<String>) -> Self {
Edit {
range: at..at,
replacement: text.into(),
}
}
pub fn delete(range: Range<Pos>) -> Self {
Edit {
range,
replacement: String::new(),
}
}
pub fn replace(range: Range<Pos>, text: impl Into<String>) -> Self {
Edit {
range,
replacement: text.into(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Mode {
#[default]
Normal,
Insert,
Visual,
Replace,
Command,
OperatorPending,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum CursorShape {
#[default]
Block,
Bar,
Underline,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Style {
pub fg: Option<Color>,
pub bg: Option<Color>,
pub attrs: Attrs,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct Color(pub u8, pub u8, pub u8);
bitflags::bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Hash)]
pub struct Attrs: u8 {
const BOLD = 1 << 0;
const ITALIC = 1 << 1;
const UNDERLINE = 1 << 2;
const REVERSE = 1 << 3;
const DIM = 1 << 4;
const STRIKE = 1 << 5;
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HighlightKind {
Selection,
SearchMatch,
IncSearch,
MatchParen,
Syntax(u32),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Highlight {
pub range: Range<Pos>,
pub kind: HighlightKind,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn caret_is_empty() {
let sel = Selection::caret(Pos::new(2, 4));
assert!(sel.is_empty());
assert_eq!(sel.anchor, sel.head);
}
#[test]
fn selection_set_default_has_one_caret() {
let set = SelectionSet::default();
assert_eq!(set.items.len(), 1);
assert_eq!(set.primary, 0);
assert_eq!(set.primary().anchor, Pos::ORIGIN);
}
#[test]
fn edit_constructors() {
let p = Pos::new(0, 5);
assert_eq!(Edit::insert(p, "x").range, p..p);
assert!(Edit::insert(p, "x").replacement == "x");
assert!(Edit::delete(p..p).replacement.is_empty());
}
#[test]
fn attrs_flags() {
let a = Attrs::BOLD | Attrs::UNDERLINE;
assert!(a.contains(Attrs::BOLD));
assert!(!a.contains(Attrs::ITALIC));
}
}