diff --git a/src/iota/chained_command.rs b/src/iota/chained_command.rs new file mode 100644 index 0000000..3c25ac7 --- /dev/null +++ b/src/iota/chained_command.rs @@ -0,0 +1,163 @@ +use std::str; +use textobject::{Offset, Kind, Anchor}; +use command::Command; + +// Current chainable commands +pub enum ChainableCmds { + Quit, + Write, +} +// Holds our chained command structure +pub struct ChainedCmdBuilder { + quit: bool, + save: bool, + line_j: Option, +} +// Parses our input +pub struct ChainedCmdParser<'a> { + buffer: &'a [u8], + len: usize, + pos: usize, + is_arg: bool, +} + +// A safe version of memcmp. May not be necessary, but there +// was some performance issues with built in solution in the +// past +fn memeq<'a, T: PartialEq>(a: &'a [T], b: &'a [T]) -> bool { + if a.len() != b.len() { + return false; + } + + for i in 0..a.len() { + if a[i] != b[i] { + return false; + } + } + + true +} + +impl ChainedCmdBuilder { + pub fn new() -> ChainedCmdBuilder { + ChainedCmdBuilder { + quit: false, + save: false, + line_j: None, + } + } + // Parses a chainable command + pub fn parse(&mut self, cmd: &str) -> Vec { + let mut parser = ChainedCmdParser::new(cmd); + + while parser.pos <= parser.len { + match parser.buffer[parser.pos] { + b'q' => { + // our command is quit + self.quit = true; + if parser.pos < parser.len { + parser.cmd(ChainableCmds::Quit); + } + } + b'w' => { + // Our command is write + self.save = true; + if parser.pos < parser.len { + parser.cmd(ChainableCmds::Write); + } + } + b'1' | b'2' | b'3' | b'4' | b'5' | b'6' | b'7' | b'8' | b'9' | b'0' => { + let line_j: usize = parser.arg().parse().ok().expect("Need usize"); + self.line_j = Some(line_j); // This should always be set + } + b' ' => { + // Arg is coming, this will be useful in the future + parser.is_arg = true; + } + _ => { + continue; + } + } + + parser.is_arg = false; + parser.pos += 1; + } + + self.build_commands() + } + + // Builds our final vec of commands + fn build_commands(&mut self) -> Vec { + let mut res = vec![]; + + if self.line_j.is_some() { + res.push(Command::movement(Offset::Absolute(self.line_j.unwrap()), + Kind::Line(Anchor::Start))); + } + if self.save { + res.push(Command::save_buffer()); + } + if self.quit { + res.push(Command::exit_editor()); + } + if res.is_empty() { + res.push(Command::noop()); + } + + return res; + } +} + +impl<'a> ChainedCmdParser<'a> { + pub fn new(cmd: &'a str) -> ChainedCmdParser<'a> { + ChainedCmdParser { + buffer: cmd.as_bytes(), + len: cmd.len() - 1, + pos: 0, + is_arg: false, + } + } + + // This is a little hokey, but basically checks if + // our command is long and moves our position + fn cmd(&mut self, cmd: ChainableCmds) { + match cmd { + ChainableCmds::Quit => { + let l_cmd = "quit".as_bytes(); + match self.len >= l_cmd.len() { + true => { + match memeq(&self.buffer[self.pos..l_cmd.len()], &l_cmd) { + true => self.pos += l_cmd.len() - 1, + false => return, + } + } + false => return, + } + } + ChainableCmds::Write => { + let l_cmd = "write".as_bytes(); + match self.len >= l_cmd.len() { + true => { + match memeq(&self.buffer[self.pos..l_cmd.len()], &l_cmd) { + true => self.pos += l_cmd.len() - 1, + false => return, + } + } + false => return, + } + } + } + } + + // Parses an arg from a chainable command + fn arg(&mut self) -> &str { + let start = self.pos; + while self.buffer[self.pos] != b' ' { + if self.pos + 1 > self.len { + break; + } + self.pos += 1; + } + str::from_utf8(&self.buffer[start..self.pos + 1]).unwrap_or("") + } +} diff --git a/src/iota/editor.rs b/src/iota/editor.rs index 63088de..aa1301c 100644 --- a/src/iota/editor.rs +++ b/src/iota/editor.rs @@ -12,7 +12,7 @@ use overlay::{Overlay, OverlayEvent}; use buffer::Buffer; use command::Command; use command::{Action, BuilderEvent, Operation, Instruction}; - +use chained_command::ChainedCmdBuilder; /// The main Editor structure /// @@ -72,20 +72,27 @@ impl<'e, T: Frontend> Editor<'e, T> { }; let mut remove_overlay = false; - let command = match self.view.overlay { - Overlay::None => self.mode.handle_key_event(key), + let mut command = BuilderEvent::Incomplete; + + match self.view.overlay { + Overlay::None => { command = self.mode.handle_key_event(key) }, _ => { let event = self.view.overlay.handle_key_event(key); match event { OverlayEvent::Finished(response) => { remove_overlay = true; - self.handle_overlay_response(response) + let commands = self.handle_overlay_response(response); + for cmd in commands { + if let BuilderEvent::Complete(c) = cmd { + self.handle_command(c); + } + } } - _ => { BuilderEvent::Incomplete } + _ => { command = BuilderEvent::Incomplete } } } - }; + } if remove_overlay { self.view.overlay = Overlay::None; @@ -102,34 +109,33 @@ impl<'e, T: Frontend> Editor<'e, T> { /// In most cases, we will just want to convert the response directly to /// a Command, however in some cases we will want to perform other actions /// first, such as in the case of Overlay::SavePrompt. - fn handle_overlay_response(&mut self, response: Option) -> BuilderEvent { - // FIXME: This entire method neext to be updated + fn handle_overlay_response(&mut self, response: Option) -> Vec { + let mut commands = vec![]; + match response { Some(data) => { match self.view.overlay { - // FIXME: this is just a temporary fix Overlay::Prompt { ref data, .. } => { - match &**data { - // FIXME: need to find a better system for these commands - // They should be chainable - // ie: wq - save & quit - // They should also take arguments - // ie w file.txt - write buffer to file.txt - "q" | "quit" => BuilderEvent::Complete(Command::exit_editor()), - "w" | "write" => BuilderEvent::Complete(Command::save_buffer()), - - _ => BuilderEvent::Incomplete + let mut builder = ChainedCmdBuilder::new(); + let cmds: Vec = builder.parse(&**data); + + if cmds.len() < 1 { + commands.push(BuilderEvent::Incomplete); + } else { + for cmd in cmds { + commands.push(BuilderEvent::Complete(cmd)); + } } } Overlay::SavePrompt { .. } => { if data.is_empty() { - BuilderEvent::Invalid + commands.push(BuilderEvent::Invalid); } else { let path = PathBuf::from(&*data); self.view.buffer.lock().unwrap().file_path = Some(path); - BuilderEvent::Complete(Command::save_buffer()) + commands.push(BuilderEvent::Complete(Command::save_buffer())); } } @@ -139,14 +145,16 @@ impl<'e, T: Frontend> Editor<'e, T> { self.buffers.push(buffer.clone()); self.view.set_buffer(buffer.clone()); self.view.clear(&mut self.frontend); - BuilderEvent::Complete(Command::noop()) + commands.push(BuilderEvent::Complete(Command::noop())); } - _ => BuilderEvent::Incomplete, + _ => commands.push(BuilderEvent::Incomplete), } } - None => BuilderEvent::Incomplete + None => commands.push(BuilderEvent::Incomplete) } + + commands } /// Handle resize events diff --git a/src/iota/lib.rs b/src/iota/lib.rs index 900e79e..db294c1 100644 --- a/src/iota/lib.rs +++ b/src/iota/lib.rs @@ -35,6 +35,7 @@ mod overlay; mod command; mod textobject; mod iterators; +mod chained_command; #[cfg(feature="syntax-highlighting")] mod syntax;