use crate::core::{Core, Cursor, CursorRange};
#[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 {
fn get_range(
&self,
action: Action,
prefix: TextObjectPrefix,
core: &Core,
) -> Option<CursorRange>;
}
struct Word;
struct Quote(char);
struct Parens(char, char);
impl TextObject for Quote {
fn get_range(&self, _: Action, prefix: TextObjectPrefix, core: &Core) -> Option<CursorRange> {
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.char_at(t) == Some(self.0) {
level = !level;
if !level && t >= core.cursor() && l <= core.cursor() {
if prefix == TextObjectPrefix::Inner {
let l = core.next_cursor(l)?;
let r = core.prev_cursor(t)?;
return if l <= r {
Some(CursorRange(l, r))
} else {
None
};
} else {
return Some(CursorRange(l, t));
}
}
l = t;
}
if t > core.cursor() && !level {
return None;
}
t = core.next_cursor(t)?;
}
}
_ => None,
}
}
}
impl TextObject for Parens {
fn get_range(&self, _: Action, prefix: TextObjectPrefix, core: &Core) -> Option<CursorRange> {
match prefix {
TextObjectPrefix::A | TextObjectPrefix::Inner => {
let mut stack = Vec::new();
let mut t = Cursor { row: 0, col: 0 };
loop {
if core.char_at(t) == Some(self.0) {
stack.push(t);
} else if core.char_at(t) == Some(self.1) {
if let Some(l) = stack.pop() {
if l <= core.cursor() && t >= core.cursor() {
if prefix == TextObjectPrefix::Inner {
let l = core.next_cursor(l)?;
let r = core.prev_cursor(t)?;
return if l < r { Some(CursorRange(l, r)) } else { None };
} else {
return Some(CursorRange(l, t));
}
}
}
}
if t > core.cursor() && stack.get(0).map(|&c| c > core.cursor()).unwrap_or(true)
{
return None;
}
t = core.next_cursor(t)?;
}
}
_ => None,
}
}
}
impl TextObject for Word {
fn get_range(
&self,
action: Action,
prefix: TextObjectPrefix,
core: &Core,
) -> Option<CursorRange> {
Some(match prefix {
TextObjectPrefix::None => {
let l = core.cursor();
let line = core.current_line();
let mut i = l.col;
while i + 1 < line.len_chars() && line.char(i + 1).is_alphanumeric() {
i += 1;
}
if action != Action::Change && prefix != TextObjectPrefix::Inner {
while i + 1 < line.len_chars() && line.char(i + 1) == ' ' {
i += 1;
}
}
CursorRange(l, Cursor { row: l.row, col: i })
}
TextObjectPrefix::A | TextObjectPrefix::Inner => {
let pos = core.cursor();
let line = core.current_line();
let mut l = pos.col;
let mut r = pos.col;
while l > 0 && line.char(l - 1).is_alphanumeric() {
l -= 1;
}
while r + 1 < line.len_chars() && line.char(r + 1).is_alphanumeric() {
r += 1;
}
if action != Action::Change && prefix != TextObjectPrefix::Inner {
while r + 1 < line.len_chars() && line.char(r + 1) == ' ' {
r += 1;
}
}
CursorRange(
Cursor {
row: pos.row,
col: l,
},
Cursor {
row: pos.row,
col: 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(&mut self, c: char, core: &Core) -> Option<Option<CursorRange>> {
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;
let line = core.current_line();
while r.col < line.len_chars() && line.char(r.col) != find {
r.col += 1;
}
if r.col == line.len_chars() {
return Some(None);
}
if !inclusive {
if let Some(prev) = core.prev_cursor(r) {
r = prev;
} else {
return Some(None);
}
}
if r >= l {
Some(Some(CursorRange(l, r)))
} else {
Some(None)
}
}
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,
},
}
}
}