use crate::core::CoreBuffer;
use crate::core::{Core, Cursor};
use std::ops::Bound;
#[derive(Copy, Clone, PartialEq, Eq)]
pub enum Action {
Delete,
Yank,
Change,
}
impl Action {
pub fn from_char(c: char) -> Option<Self> {
match c {
'd' => Some(Action::Delete),
'y' => Some(Action::Yank),
'c' => Some(Action::Change),
_ => None,
}
}
pub fn to_char(self) -> char {
match self {
Action::Delete => 'd',
Action::Yank => 'y',
Action::Change => 'c',
}
}
}
#[derive(Copy, Clone, PartialEq, Eq)]
pub enum TextObjectPrefix {
Inner,
A,
None,
}
#[derive(Copy, Clone, PartialEq, Eq)]
pub enum Prefix {
TextObjectPrefix(TextObjectPrefix),
Find { inclusive: bool },
}
pub trait TextObject<B: CoreBuffer> {
fn get_range(
&self,
action: Action,
prefix: TextObjectPrefix,
core: &Core<B>,
) -> (Bound<Cursor>, Bound<Cursor>);
}
struct Word;
struct Quote(char);
struct Parens(char, char);
impl<B: CoreBuffer> TextObject<B> for Quote {
fn get_range(
&self,
_: Action,
prefix: TextObjectPrefix,
core: &Core<B>,
) -> (Bound<Cursor>, Bound<Cursor>) {
match prefix {
TextObjectPrefix::A | TextObjectPrefix::Inner => {
let mut l = Cursor { row: 0, col: 0 };
let mut t = Cursor { row: 0, col: 0 };
let mut level = false;
loop {
if core.core_buffer().char_at(t) == Some(self.0) {
level = !level;
if !level && t >= core.cursor() && l <= core.cursor() {
if prefix == TextObjectPrefix::Inner {
return if l <= t {
(Bound::Excluded(l), Bound::Excluded(t))
} else {
(Bound::Included(l), Bound::Excluded(l))
};
} else {
return (Bound::Included(l), Bound::Included(t));
}
}
l = t;
}
if t > core.cursor() && !level {
return (Bound::Included(l), Bound::Excluded(l));
}
if let Some(next) = core.next_cursor(t) {
t = next;
} else {
return (
Bound::Included(Cursor { row: 0, col: 0 }),
Bound::Excluded(Cursor { row: 0, col: 0 }),
);
}
}
}
_ => (
Bound::Included(Cursor { row: 0, col: 0 }),
Bound::Excluded(Cursor { row: 0, col: 0 }),
),
}
}
}
impl<B: CoreBuffer> TextObject<B> for Parens {
fn get_range(
&self,
_: Action,
prefix: TextObjectPrefix,
core: &Core<B>,
) -> (Bound<Cursor>, Bound<Cursor>) {
match prefix {
TextObjectPrefix::A | TextObjectPrefix::Inner => {
let mut stack = Vec::new();
let mut t = Cursor { row: 0, col: 0 };
loop {
if core.core_buffer().char_at(t) == Some(self.0) {
stack.push(t);
} else if core.core_buffer().char_at(t) == Some(self.1) {
if let Some(l) = stack.pop() {
if l <= core.cursor() && t >= core.cursor() {
if prefix == TextObjectPrefix::Inner {
return if l < t {
(Bound::Excluded(l), Bound::Excluded(t))
} else {
(
Bound::Included(Cursor { row: 0, col: 0 }),
Bound::Excluded(Cursor { row: 0, col: 0 }),
)
};
} else {
return (Bound::Included(l), Bound::Included(t));
}
}
}
}
if t > core.cursor() && stack.get(0).map(|&c| c > core.cursor()).unwrap_or(true)
{
return (
Bound::Included(Cursor { row: 0, col: 0 }),
Bound::Excluded(Cursor { row: 0, col: 0 }),
);
}
if let Some(next) = core.next_cursor(t) {
t = next;
} else {
return (
Bound::Included(Cursor { row: 0, col: 0 }),
Bound::Excluded(Cursor { row: 0, col: 0 }),
);
}
}
}
_ => (
Bound::Included(Cursor { row: 0, col: 0 }),
Bound::Excluded(Cursor { row: 0, col: 0 }),
),
}
}
}
impl<B: CoreBuffer> TextObject<B> for Word {
fn get_range(
&self,
action: Action,
prefix: TextObjectPrefix,
core: &Core<B>,
) -> (Bound<Cursor>, Bound<Cursor>) {
match prefix {
TextObjectPrefix::None => {
let l = core.cursor();
let mut r = l;
while r.col < core.core_buffer().len_line(r.row)
&& core
.core_buffer()
.char_at(r)
.map(|c| c.is_alphanumeric())
.unwrap_or(false)
{
r.col += 1;
}
if action != Action::Change {
while r.col < core.core_buffer().len_line(r.row)
&& core.core_buffer().char_at(r) == Some(' ')
{
r.col += 1;
}
}
(Bound::Included(l), Bound::Excluded(r))
}
TextObjectPrefix::A | TextObjectPrefix::Inner => {
let mut l = core.cursor();
let mut r = l;
while l.col > 0
&& core
.core_buffer()
.char_at(Cursor {
row: l.row,
col: l.col - 1,
})
.map(|c| c.is_alphanumeric())
.unwrap_or(false)
{
l.col -= 1;
}
while r.col < core.core_buffer().len_line(r.row)
&& core
.core_buffer()
.char_at(r)
.map(|c| c.is_alphanumeric())
.unwrap_or(false)
{
r.col += 1;
}
if action != Action::Change && prefix != TextObjectPrefix::Inner {
while r.col < core.core_buffer().len_line(r.row)
&& core
.core_buffer()
.char_at(r)
.map(|c| c == ' ')
.unwrap_or(false)
{
r.col += 1;
}
}
(Bound::Included(l), Bound::Excluded(r))
}
}
}
}
pub struct TextObjectParser {
pub action: Action,
pub prefix: Prefix,
}
impl TextObjectParser {
pub fn new(action: Action) -> Self {
Self {
action,
prefix: Prefix::TextObjectPrefix(TextObjectPrefix::None),
}
}
}
impl TextObjectParser {
pub fn parse<B: CoreBuffer>(
&mut self,
c: char,
core: &Core<B>,
) -> Option<(Bound<Cursor>, Bound<Cursor>)> {
if let Prefix::TextObjectPrefix(_) = self.prefix {
match c {
'a' => {
self.prefix = Prefix::TextObjectPrefix(TextObjectPrefix::A);
}
'i' => {
self.prefix = Prefix::TextObjectPrefix(TextObjectPrefix::Inner);
}
'f' | 't' => {
if let Prefix::TextObjectPrefix { .. } = self.prefix {
self.prefix = Prefix::Find {
inclusive: c == 'f',
};
return None;
}
}
_ => (),
}
}
match self.prefix {
Prefix::Find { inclusive } => {
let find = c;
let l = core.cursor();
let mut r = l;
while r.col < core.core_buffer().len_line(r.row)
&& core.core_buffer().char_at(r) != Some(find)
{
r.col += 1;
}
if r.col == core.core_buffer().len_line(r.row) {
return Some((Bound::Included(l), Bound::Excluded(l)));
}
if inclusive {
Some((Bound::Included(l), Bound::Included(r)))
} else {
Some((Bound::Included(l), Bound::Excluded(r)))
}
}
Prefix::TextObjectPrefix(text_object_prefix) => match c {
'w' => Some(Word.get_range(self.action, text_object_prefix, core)),
'\'' | '"' => Some(Quote(c).get_range(self.action, text_object_prefix, core)),
'{' | '}' => {
Some(Parens('{', '}').get_range(self.action, text_object_prefix, core))
}
'(' | ')' => {
Some(Parens('(', ')').get_range(self.action, text_object_prefix, core))
}
'[' | ']' => {
Some(Parens('[', ']').get_range(self.action, text_object_prefix, core))
}
_ => None,
},
}
}
}