use super::{motion::Motion, parser::ReedlineOption, ViMode};
use crate::enums::{TextObject, TextObjectScope, TextObjectType};
use crate::{Direction, EditCommand, Granularity, MotionTarget, ReedlineEvent, Vi};
use std::iter::Peekable;
pub fn parse_command<'iter, I>(mode: ViMode, input: &mut Peekable<I>) -> Option<Command>
where
I: Iterator<Item = &'iter char>,
{
match input.peek() {
Some('d') => {
let _ = input.next();
if let Some('i') = input.peek() {
let _ = input.next();
input.next().and_then(|c| {
bracket_pair_for(*c)
.map(|(left, right)| Command::DeleteInsidePair { left, right })
.or_else(|| {
char_to_text_object(*c, TextObjectScope::Inner)
.map(|text_object| Command::DeleteTextObject { text_object })
})
})
} else if let Some('a') = input.peek() {
let _ = input.next();
input.next().and_then(|c| {
bracket_pair_for(*c)
.map(|(left, right)| Command::DeleteAroundPair { left, right })
.or_else(|| {
char_to_text_object(*c, TextObjectScope::Around)
.map(|text_object| Command::DeleteTextObject { text_object })
})
})
} else {
Some(Command::Delete)
}
}
Some('y') => {
let _ = input.next();
if let Some('i') = input.peek() {
let _ = input.next();
input.next().and_then(|c| {
bracket_pair_for(*c)
.map(|(left, right)| Command::YankInsidePair { left, right })
.or_else(|| {
char_to_text_object(*c, TextObjectScope::Inner)
.map(|text_object| Command::YankTextObject { text_object })
})
})
} else if let Some('a') = input.peek() {
let _ = input.next();
input.next().and_then(|c| {
bracket_pair_for(*c)
.map(|(left, right)| Command::YankAroundPair { left, right })
.or_else(|| {
char_to_text_object(*c, TextObjectScope::Around)
.map(|text_object| Command::YankTextObject { text_object })
})
})
} else {
Some(Command::Yank)
}
}
Some('p') => {
let _ = input.next();
Some(Command::PasteAfter)
}
Some('P') => {
let _ = input.next();
Some(Command::PasteBefore)
}
Some('i') => {
let _ = input.next();
Some(Command::EnterViInsert)
}
Some('a') => {
let _ = input.next();
Some(Command::EnterViAppend)
}
Some('u') => {
let _ = input.next();
Some(Command::Undo)
}
Some('c') => {
let _ = input.next();
if let Some('i') = input.peek() {
let _ = input.next();
input.next().and_then(|c| {
bracket_pair_for(*c)
.map(|(left, right)| Command::ChangeInsidePair { left, right })
.or_else(|| {
char_to_text_object(*c, TextObjectScope::Inner)
.map(|text_object| Command::ChangeTextObject { text_object })
})
})
} else if let Some('a') = input.peek() {
let _ = input.next();
input.next().and_then(|c| {
char_to_text_object(*c, TextObjectScope::Around)
.map(|text_object| Command::ChangeTextObject { text_object })
})
} else {
Some(Command::Change)
}
}
Some('x') => {
let _ = input.next();
Some(Command::DeleteChar)
}
Some('r') => {
let _ = input.next();
input
.next()
.map(|c| Command::ReplaceChar(*c))
.or(Some(Command::Incomplete))
}
Some('s') => {
let _ = input.next();
Some(Command::SubstituteCharWithInsert)
}
Some('?') => {
let _ = input.next();
Some(Command::HistorySearch)
}
Some('C') => {
let _ = input.next();
Some(Command::ChangeToLineEnd)
}
Some('D') => {
let _ = input.next();
Some(Command::DeleteToEnd)
}
Some('I') => {
let _ = input.next();
Some(Command::PrependToStart)
}
Some('A') => {
let _ = input.next();
Some(Command::AppendToEnd)
}
Some('S') => {
let _ = input.next();
Some(Command::RewriteCurrentLine)
}
Some('~') => {
let _ = input.next();
Some(Command::Switchcase)
}
Some('.') => {
let _ = input.next();
Some(Command::RepeatLastAction)
}
Some(&&o @ ('o' | 'O')) => match mode {
ViMode::Normal => {
let _ = input.next();
if o.is_ascii_lowercase() {
Some(Command::NewlineBelow)
} else {
Some(Command::NewlineAbove)
}
}
ViMode::Visual => {
let _ = input.next();
Some(Command::SwapCursorAndAnchor)
}
ViMode::Insert => None,
},
_ => None,
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum Command {
Incomplete,
Delete,
DeleteChar,
ReplaceChar(char),
SubstituteCharWithInsert,
NewlineAbove,
NewlineBelow,
PasteAfter,
PasteBefore,
EnterViAppend,
EnterViInsert,
Undo,
ChangeToLineEnd,
DeleteToEnd,
AppendToEnd,
PrependToStart,
RewriteCurrentLine,
Change,
HistorySearch,
Switchcase,
RepeatLastAction,
Yank,
ChangeInsidePair { left: char, right: char },
DeleteInsidePair { left: char, right: char },
YankInsidePair { left: char, right: char },
DeleteAroundPair { left: char, right: char },
YankAroundPair { left: char, right: char },
ChangeTextObject { text_object: TextObject },
YankTextObject { text_object: TextObject },
DeleteTextObject { text_object: TextObject },
SwapCursorAndAnchor,
}
impl Command {
pub fn whole_line_char(&self) -> Option<char> {
match self {
Command::Delete => Some('d'),
Command::Change => Some('c'),
Command::Yank => Some('y'),
_ => None,
}
}
pub fn requires_motion(&self) -> bool {
matches!(self, Command::Delete | Command::Change | Command::Yank)
}
pub fn to_reedline(&self, vi_state: &mut Vi) -> Vec<ReedlineOption> {
match self {
Self::EnterViInsert => vec![ReedlineOption::Event(ReedlineEvent::Repaint)],
Self::EnterViAppend => vec![ReedlineOption::Edit(EditCommand::MoveRight {
select: false,
})],
Self::NewlineAbove => vec![ReedlineOption::Edit(EditCommand::InsertNewlineAbove)],
Self::NewlineBelow => vec![ReedlineOption::Edit(EditCommand::InsertNewlineBelow)],
Self::PasteAfter => vec![ReedlineOption::Edit(EditCommand::PasteCutBufferAfter)],
Self::PasteBefore => vec![ReedlineOption::Edit(EditCommand::PasteCutBufferBefore)],
Self::Undo => vec![ReedlineOption::Edit(EditCommand::Undo)],
Self::ChangeToLineEnd => vec![ReedlineOption::Edit(EditCommand::ClearToLineEnd)],
Self::DeleteToEnd => vec![ReedlineOption::Edit(EditCommand::CutToLineEnd)],
Self::AppendToEnd => vec![ReedlineOption::Edit(EditCommand::MoveToLineEnd {
select: false,
})],
Self::PrependToStart => vec![ReedlineOption::Edit(EditCommand::MoveToLineStart {
select: false,
})],
Self::RewriteCurrentLine => vec![ReedlineOption::Edit(EditCommand::Change {
target: MotionTarget::LineEdge(Direction::Forward),
granularity: Granularity::LineWise,
})],
Self::DeleteChar => {
if vi_state.mode == ViMode::Visual {
vec![ReedlineOption::Edit(EditCommand::CutSelection)]
} else {
vec![ReedlineOption::Edit(EditCommand::CutChar)]
}
}
Self::ReplaceChar(c) => {
vec![ReedlineOption::Edit(EditCommand::ReplaceChar(*c))]
}
Self::SubstituteCharWithInsert => {
if vi_state.mode == ViMode::Visual {
vec![ReedlineOption::Edit(EditCommand::CutSelection)]
} else {
vec![ReedlineOption::Edit(EditCommand::CutChar)]
}
}
Self::HistorySearch => vec![ReedlineOption::Event(ReedlineEvent::SearchHistory)],
Self::Switchcase => vec![ReedlineOption::Edit(EditCommand::SwitchcaseChar)],
Self::Delete | Self::Change => vec![ReedlineOption::Edit(EditCommand::CutSelection)],
Self::Yank => vec![ReedlineOption::Edit(EditCommand::CopySelection)],
Self::Incomplete => vec![ReedlineOption::Incomplete],
Self::RepeatLastAction => match &vi_state.previous {
Some(event) => vec![ReedlineOption::Event(event.clone())],
None => vec![],
},
Self::ChangeInsidePair { left, right } => {
vec![ReedlineOption::Edit(EditCommand::CutInsidePair {
left: *left,
right: *right,
})]
}
Self::DeleteInsidePair { left, right } => {
vec![ReedlineOption::Edit(EditCommand::CutInsidePair {
left: *left,
right: *right,
})]
}
Self::YankInsidePair { left, right } => {
vec![ReedlineOption::Edit(EditCommand::CopyInsidePair {
left: *left,
right: *right,
})]
}
Self::DeleteAroundPair { left, right } => {
vec![ReedlineOption::Edit(EditCommand::CutAroundPair {
left: *left,
right: *right,
})]
}
Self::YankAroundPair { left, right } => {
vec![ReedlineOption::Edit(EditCommand::CopyAroundPair {
left: *left,
right: *right,
})]
}
Self::ChangeTextObject { text_object } => {
vec![ReedlineOption::Edit(EditCommand::CutTextObject {
text_object: *text_object,
})]
}
Self::YankTextObject { text_object } => {
vec![ReedlineOption::Edit(EditCommand::CopyTextObject {
text_object: *text_object,
})]
}
Self::DeleteTextObject { text_object } => {
vec![ReedlineOption::Edit(EditCommand::CutTextObject {
text_object: *text_object,
})]
}
Self::SwapCursorAndAnchor => {
vec![ReedlineOption::Edit(EditCommand::SwapCursorAndAnchor)]
}
}
}
pub fn to_reedline_with_motion(
&self,
motion: &Motion,
vi_state: &mut Vi,
) -> Option<Vec<ReedlineOption>> {
match self {
Self::Delete => match motion {
Motion::Line => Some(vec![ReedlineOption::Edit(EditCommand::Cut {
target: MotionTarget::LineEdge(Direction::Forward),
granularity: Granularity::LineWise,
})]),
Motion::NextWord
| Motion::NextBigWord
| Motion::NextWordEnd
| Motion::NextBigWordEnd
| Motion::PreviousWord
| Motion::PreviousBigWord
| Motion::Start
| Motion::End => motion.target().map(|target| {
vec![ReedlineOption::Edit(EditCommand::Cut {
target,
granularity: Granularity::CharWise,
})]
}),
Motion::RightUntil(_)
| Motion::RightBefore(_)
| Motion::LeftUntil(_)
| Motion::LeftBefore(_) => motion.target().map(|target| {
vi_state.last_char_search = Some(target);
vec![ReedlineOption::Edit(EditCommand::Cut {
target,
granularity: Granularity::CharWise,
})]
}),
Motion::NonBlankStart => Some(vec![ReedlineOption::Edit(
EditCommand::CutFromLineNonBlankStart,
)]),
Motion::Left => Some(vec![ReedlineOption::Edit(EditCommand::Backspace)]),
Motion::Right => Some(vec![ReedlineOption::Edit(EditCommand::Delete)]),
Motion::Down | Motion::Up | Motion::FirstLine | Motion::LastLine => {
motion.target().map(|target| {
vec![ReedlineOption::Edit(EditCommand::Cut {
target,
granularity: Granularity::LineWise,
})]
})
}
Motion::ReplayCharSearch => vi_state.last_char_search.map(|target| {
vec![ReedlineOption::Edit(EditCommand::Cut {
target,
granularity: Granularity::CharWise,
})]
}),
Motion::ReverseCharSearch => vi_state.last_char_search.map(|target| {
vec![ReedlineOption::Edit(EditCommand::Cut {
target: target.reversed(),
granularity: Granularity::CharWise,
})]
}),
},
Self::Change => {
let op = match motion {
Motion::Line => Some(vec![ReedlineOption::Edit(EditCommand::Change {
target: MotionTarget::LineEdge(Direction::Forward),
granularity: Granularity::LineWise,
})]),
Motion::NextWord
| Motion::NextBigWord
| Motion::NextWordEnd
| Motion::NextBigWordEnd
| Motion::PreviousWord
| Motion::PreviousBigWord
| Motion::Start
| Motion::End => {
let target = match motion {
Motion::NextWord => Motion::NextWordEnd.target(),
Motion::NextBigWord => Motion::NextBigWordEnd.target(),
other => other.target(),
};
target.map(|target| {
vec![ReedlineOption::Edit(EditCommand::Cut {
target,
granularity: Granularity::CharWise,
})]
})
}
Motion::RightUntil(_)
| Motion::RightBefore(_)
| Motion::LeftUntil(_)
| Motion::LeftBefore(_) => motion.target().map(|target| {
vi_state.last_char_search = Some(target);
vec![ReedlineOption::Edit(EditCommand::Cut {
target,
granularity: Granularity::CharWise,
})]
}),
Motion::NonBlankStart => Some(vec![ReedlineOption::Edit(
EditCommand::CutFromLineNonBlankStart,
)]),
Motion::Left => Some(vec![ReedlineOption::Edit(EditCommand::Backspace)]),
Motion::Right => Some(vec![ReedlineOption::Edit(EditCommand::Delete)]),
Motion::Down | Motion::Up | Motion::FirstLine | Motion::LastLine => {
motion.target().map(|target| {
vec![ReedlineOption::Edit(EditCommand::Change {
target,
granularity: Granularity::LineWise,
})]
})
}
Motion::ReplayCharSearch => vi_state.last_char_search.map(|target| {
vec![ReedlineOption::Edit(EditCommand::Cut {
target,
granularity: Granularity::CharWise,
})]
}),
Motion::ReverseCharSearch => vi_state.last_char_search.map(|target| {
vec![ReedlineOption::Edit(EditCommand::Cut {
target: target.reversed(),
granularity: Granularity::CharWise,
})]
}),
};
op.map(|mut vec| {
vec.push(ReedlineOption::Event(ReedlineEvent::Repaint));
vec
})
}
Self::Yank => match motion {
Motion::Line => Some(vec![ReedlineOption::Edit(EditCommand::Copy {
target: MotionTarget::LineEdge(Direction::Forward),
granularity: Granularity::LineWise,
})]),
Motion::NextWord
| Motion::NextBigWord
| Motion::NextWordEnd
| Motion::NextBigWordEnd
| Motion::PreviousWord
| Motion::PreviousBigWord
| Motion::Start
| Motion::End => motion.target().map(|target| {
vec![ReedlineOption::Edit(EditCommand::Copy {
target,
granularity: Granularity::CharWise,
})]
}),
Motion::RightUntil(_)
| Motion::RightBefore(_)
| Motion::LeftUntil(_)
| Motion::LeftBefore(_) => motion.target().map(|target| {
vi_state.last_char_search = Some(target);
vec![ReedlineOption::Edit(EditCommand::Copy {
target,
granularity: Granularity::CharWise,
})]
}),
Motion::NonBlankStart => Some(vec![ReedlineOption::Edit(
EditCommand::CopyFromLineNonBlankStart,
)]),
Motion::Left => Some(vec![ReedlineOption::Edit(EditCommand::CopyLeft)]),
Motion::Right => Some(vec![ReedlineOption::Edit(EditCommand::CopyRight)]),
Motion::Down | Motion::Up | Motion::FirstLine | Motion::LastLine => {
motion.target().map(|target| {
vec![ReedlineOption::Edit(EditCommand::Copy {
target,
granularity: Granularity::LineWise,
})]
})
}
Motion::ReplayCharSearch => vi_state.last_char_search.map(|target| {
vec![ReedlineOption::Edit(EditCommand::Copy {
target,
granularity: Granularity::CharWise,
})]
}),
Motion::ReverseCharSearch => vi_state.last_char_search.map(|target| {
vec![ReedlineOption::Edit(EditCommand::Copy {
target: target.reversed(),
granularity: Granularity::CharWise,
})]
}),
},
_ => None,
}
}
}
fn char_to_text_object(c: char, scope: TextObjectScope) -> Option<TextObject> {
match c {
'w' => Some(TextObject {
scope,
object_type: TextObjectType::Word,
}),
'W' => Some(TextObject {
scope,
object_type: TextObjectType::BigWord,
}),
'b' => Some(TextObject {
scope,
object_type: TextObjectType::Brackets,
}),
'q' => Some(TextObject {
scope,
object_type: TextObjectType::Quote,
}),
_ => None,
}
}
fn bracket_pair_for(c: char) -> Option<(char, char)> {
match c {
'(' | ')' => Some(('(', ')')),
'[' | ']' => Some(('[', ']')),
'{' | '}' => Some(('{', '}')),
'<' | '>' => Some(('<', '>')),
'"' => Some(('"', '"')),
'$' => Some(('$', '$')),
'\'' => Some(('\'', '\'')),
'`' => Some(('`', '`')),
_ => None,
}
}