From e91c14961933b8449392475869650d16763320c0 Mon Sep 17 00:00:00 2001 From: Jeremy Wall Date: Thu, 21 Nov 2024 15:45:22 -0500 Subject: [PATCH] feat: command mode command implementations parts of #3 --- Cargo.lock | 6 +++ Cargo.toml | 1 + src/ui/cmd.rs | 99 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/ui/mod.rs | 12 ++++++ src/ui/test.rs | 45 +++++++++++++++++++++++ 5 files changed, 163 insertions(+) create mode 100644 src/ui/cmd.rs create mode 100644 src/ui/test.rs diff --git a/Cargo.lock b/Cargo.lock index f9705cd..372f131 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1399,6 +1399,7 @@ dependencies = [ "futures", "ironcalc", "ratatui", + "slice-cursor", "thiserror", "tui-prompts", "tui-textarea", @@ -1455,6 +1456,11 @@ dependencies = [ "autocfg", ] +[[package]] +name = "slice-cursor" +version = "0.1.0" +source = "git+https://dev.zaphar.net/zaphar/slice-cursor-rs.git#562a78eb3f06ac2a9729af7aa211a070f8ed9c39" + [[package]] name = "smallvec" version = "1.13.2" diff --git a/Cargo.toml b/Cargo.toml index 1cda053..ff3a622 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,3 +16,4 @@ ratatui = "0.29.0" thiserror = "1.0.65" tui-textarea = "0.7.0" tui-prompts = "0.5.0" +slice-cursor = { git = "https://dev.zaphar.net/zaphar/slice-cursor-rs.git" } diff --git a/src/ui/cmd.rs b/src/ui/cmd.rs new file mode 100644 index 0000000..980c705 --- /dev/null +++ b/src/ui/cmd.rs @@ -0,0 +1,99 @@ +//! Command mode command parsers. +use slice_cursor::{Cursor, Seekable, Span, SpanRange, StrCursor}; + +#[derive(Debug, PartialEq, Eq)] +pub enum Cmd<'a> { + Write(Option<&'a str>), + InsertRow(usize), + InsertColumns(usize), + Edit(&'a str), + Help(Option<&'a str>), +} + +pub fn parse<'cmd, 'i: 'cmd>(input: &'i str) -> Result>, &'static str> { + let cursor = StrCursor::new(input); + // try consume write command. + if let Some(cmd) = try_consume_write(cursor.clone()) { + return Ok(Some(cmd)); + } + // try consume insert-row command. + if let Some(cmd) = try_consume_insert_row(cursor.clone())? { + return Ok(Some(cmd)); + } + // try consume insert-col command. + if let Some(cmd) = try_consume_insert_column(cursor.clone()) { + return Ok(Some(cmd)); + } + // try consume edit command. + if let Some(cmd) = try_consume_edit(cursor.clone()) { + return Ok(Some(cmd)); + } + // try consume help command. + if let Some(cmd) = try_consume_help(cursor.clone()) { + return Ok(Some(cmd)); + } + Ok(None) +} + +const WRITE: &'static str = "write"; + +pub fn try_consume_write<'cmd, 'i: 'cmd>(mut input: StrCursor<'i>) -> Option> { + let prefix_len = WRITE.len(); + let full_length = dbg!(input.span(..).len()); + let arg = if full_length >= prefix_len && input.span(..prefix_len) == WRITE { + input.seek(prefix_len); + // Should we check for whitespace? + input.span(prefix_len..) + } else if full_length >= 2 && input.span(..2) == "w " { + input.span(2..) + // Should we check for whitespace? + } else { + return None; + } + .trim(); + return Some(Cmd::Write(if arg.is_empty() { None } else { Some(arg) })); +} + +const IR: &'static str = "ir"; +const INSERT_ROW: &'static str = "insert-row"; + +pub fn try_consume_insert_row<'cmd, 'i: 'cmd>( + mut input: StrCursor<'i>, +) -> Result>, &'static str> { + let prefix_len = INSERT_ROW.len(); + let second_prefix_len = IR.len(); + let full_length = input.span(..).len(); + let arg = + if full_length >= prefix_len && input.span(..prefix_len) == INSERT_ROW { + input.seek(prefix_len); + // Should we check for whitespace? + input.span(prefix_len..) + } else if full_length >= second_prefix_len && input.span(..second_prefix_len) == IR { + input.span(second_prefix_len..) + // Should we check for whitespace? + } else { + return Ok(None); + } + .trim(); + return Ok(Some(Cmd::InsertRow(if arg.is_empty() { + 1 + } else { + if let Ok(count) = arg.parse() { + count + } else { + return Err("You must pass in a non negative number for the row count"); + } + }))); +} + +pub fn try_consume_insert_column<'cmd, 'i: 'cmd>(mut input: StrCursor<'i>) -> Option> { + todo!("insert-column not yet implemented") +} + +pub fn try_consume_edit<'cmd, 'i: 'cmd>(mut input: StrCursor<'i>) -> Option> { + todo!("edit not yet implemented") +} + +pub fn try_consume_help<'cmd, 'i: 'cmd>(mut input: StrCursor<'i>) -> Option> { + todo!("help not yet implemented") +} diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 48d4529..7931ec9 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -12,6 +12,10 @@ use ratatui::{ use tui_prompts::{State, Status, TextPrompt, TextState}; use tui_textarea::{CursorMove, TextArea}; +mod cmd; +#[cfg(test)] +mod test; + #[derive(Default, Debug, PartialEq)] pub enum Modality { #[default] @@ -197,6 +201,14 @@ impl<'ws> Workspace<'ws> { if cmd.is_empty() { return Ok(true); } + match cmd.as_str() { + "w" | "write" => { + self.save_file()?; + }, + _ => { + // noop? + } + } Ok(false) } diff --git a/src/ui/test.rs b/src/ui/test.rs new file mode 100644 index 0000000..eafd0b2 --- /dev/null +++ b/src/ui/test.rs @@ -0,0 +1,45 @@ +use super::cmd::{parse, Cmd}; + +#[test] +fn test_write_cmd() { + let input = "write foo.xlsx"; + let result = parse(input); + assert!(result.is_ok()); + let output = result.unwrap(); + assert!(output.is_some()); + let cmd = output.unwrap(); + assert_eq!(cmd, Cmd::Write(Some("foo.xlsx"))); +} + +#[test] +fn test_short_write_cmd() { + let input = "w foo.xlsx"; + let result = parse(input); + assert!(result.is_ok()); + let output = result.unwrap(); + assert!(output.is_some()); + let cmd = output.unwrap(); + assert_eq!(cmd, Cmd::Write(Some("foo.xlsx"))); +} + +#[test] +fn test_insert_row_cmd() { + let input = "insert-row 1"; + let result = parse(input); + assert!(result.is_ok()); + let output = result.unwrap(); + assert!(output.is_some()); + let cmd = output.unwrap(); + assert_eq!(cmd, Cmd::InsertRow(1)); +} + +#[test] +fn test_insert_row_cmd_short() { + let input = "ir 1"; + let result = parse(input); + assert!(result.is_ok()); + let output = result.unwrap(); + assert!(output.is_some()); + let cmd = output.unwrap(); + assert_eq!(cmd, Cmd::InsertRow(1)); +}