use bevy::prelude::Resource;
use super::error::VimError;
const MAX_COMMAND_CHARS: usize = 256;
#[derive(Clone, Debug, Default, Eq, PartialEq, Resource)]
pub struct VimCommandState {
active_command: Option<VimCommandText>,
}
impl VimCommandState {
pub fn start(&mut self) {
self.active_command = Some(VimCommandText::new());
}
pub fn start_with(&mut self, text: &str) {
let mut command = VimCommandText::new();
command.push_str(text);
self.active_command = Some(command);
}
pub fn cancel(&mut self) {
self.active_command = None;
}
#[must_use]
pub const fn is_active(&self) -> bool {
self.active_command.is_some()
}
#[must_use]
pub const fn active_command(&self) -> Option<&VimCommandText> {
self.active_command.as_ref()
}
pub fn push_text(&mut self, text: &str) {
if let Some(command) = &mut self.active_command {
command.push_str(text);
}
}
pub fn backspace(&mut self) {
if let Some(command) = &mut self.active_command {
command.pop_char();
}
}
pub fn submit(&mut self) -> Result<VimCommand, VimError> {
let Some(command_text) = self.active_command.take() else {
return Err(VimError::NotAnEditorCommand);
};
command_text.parse()
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct VimCommandText {
text: String,
}
impl VimCommandText {
#[must_use]
pub const fn new() -> Self {
Self {
text: String::new(),
}
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.text
}
pub fn push_str(&mut self, text: &str) {
let remaining = MAX_COMMAND_CHARS.saturating_sub(self.text.chars().count());
self.text.extend(text.chars().take(remaining));
}
pub fn pop_char(&mut self) {
let _removed = self.text.pop();
}
fn parse(self) -> Result<VimCommand, VimError> {
parse_command(self.text.trim())
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum VimCommand {
Write,
Quit(QuitPolicy),
WriteQuit(WriteQuitPolicy),
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum QuitPolicy {
PreserveChanges,
Force,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum WriteQuitPolicy {
AlwaysWrite,
WriteIfModified,
}
fn parse_command(text: &str) -> Result<VimCommand, VimError> {
match text {
"w" | "write" => Ok(VimCommand::Write),
"q" | "quit" => Ok(VimCommand::Quit(QuitPolicy::PreserveChanges)),
"q!" | "quit!" => Ok(VimCommand::Quit(QuitPolicy::Force)),
"wq" | "wq!" => Ok(VimCommand::WriteQuit(WriteQuitPolicy::AlwaysWrite)),
"x" | "xit" | "exit" => Ok(VimCommand::WriteQuit(WriteQuitPolicy::WriteIfModified)),
_ => Err(VimError::NotAnEditorCommand),
}
}
#[cfg(test)]
mod tests {
use super::{QuitPolicy, VimCommand, VimCommandState, VimCommandText, WriteQuitPolicy};
use crate::vim::VimError;
#[test]
fn parses_common_write_and_quit_commands() {
assert_eq!(
VimCommandText::new().parse(),
Err(VimError::NotAnEditorCommand)
);
let mut command = VimCommandText::new();
command.push_str("wq");
assert_eq!(
command.parse(),
Ok(VimCommand::WriteQuit(WriteQuitPolicy::AlwaysWrite))
);
let mut command = VimCommandText::new();
command.push_str("q!");
assert_eq!(command.parse(), Ok(VimCommand::Quit(QuitPolicy::Force)));
}
#[test]
fn command_state_submits_and_clears_active_command() {
let mut state = VimCommandState::default();
state.start();
state.push_text("write");
assert_eq!(state.submit(), Ok(VimCommand::Write));
assert!(!state.is_active());
}
}