diff --git a/.gitignore b/.gitignore index 6f38f4a..3d84a77 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,5 @@ target/ # Ignore GIF files in the tapes directory tapes/*.gif + +.vscode diff --git a/Cargo.lock b/Cargo.lock index 6c14d02..931afb0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -503,9 +503,9 @@ dependencies = [ [[package]] name = "promkit" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfa31233c1a91cf9b5e8753a57b59a3bcca559f45f38da508857fb866635ab92" +checksum = "a5d06099a0a47b6bd7414d6692596b754a12ec4537fc46b72c7363a88fee66d9" dependencies = [ "anyhow", "crossterm", diff --git a/Cargo.toml b/Cargo.toml index 4139a9e..fb819d4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,7 @@ anyhow = "1.0.80" clap = { version = "4.5.1", features = ["derive"] } gag = "1.0.0" j9 = "0.1.3" -promkit = "0.3.2" +promkit = "0.3.3" radix_trie = "0.2.1" # The profile that 'cargo dist' will build with diff --git a/src/jnv.rs b/src/jnv.rs index 19a4497..5518a95 100644 --- a/src/jnv.rs +++ b/src/jnv.rs @@ -18,6 +18,7 @@ use promkit::{ text, text_editor, Prompt, PromptSignal, Renderer, }; +mod editing; mod keymap; mod render; mod trie; @@ -83,6 +84,7 @@ impl Jnv { active_char_style: StyleBuilder::new().bgc(Color::Magenta).build(), inactive_char_style: StyleBuilder::new().build(), edit_mode, + word_break_chars: Default::default(), lines: Default::default(), }, hint_message_renderer: text::Renderer { @@ -292,6 +294,8 @@ impl Jnv { let suggest_clone = rc_self_clone.borrow().suggest.clone(); let suggest_renderer_clone = rc_self_clone.borrow().suggest_renderer.clone(); let json_renderer_clone = rc_self_clone.borrow().json_renderer.clone(); + let input_json_stream_clone = rc_self_clone.borrow().input_json_stream.clone(); + Ok(Prompt::try_new( Box::new(self::render::Renderer { keymap: keymap_clone, @@ -302,6 +306,7 @@ impl Jnv { suggest: suggest_clone, suggest_snapshot: Snapshot::::new(suggest_renderer_clone), json_snapshot: Snapshot::::new(json_renderer_clone), + input_json_stream: input_json_stream_clone }), Box::new( move |event: &Event, diff --git a/src/jnv/editing.rs b/src/jnv/editing.rs new file mode 100644 index 0000000..e75fb20 --- /dev/null +++ b/src/jnv/editing.rs @@ -0,0 +1,119 @@ +use gag::Gag; +use promkit::serde_json::Value; + +pub fn add_to_nearest_array_index(renderer: &mut crate::jnv::render::Renderer, value: isize) -> bool { + let query_editor_after_mut = renderer.query_editor_snapshot.after_mut(); + let cursor = query_editor_after_mut.texteditor.position(); + let query = query_editor_after_mut + .texteditor + .text_without_cursor() + .to_string(); + let mut previous = None; + let mut previous_distance = usize::MAX; + let mut pos = 0; + + while pos < query.len() { + match find_array_index(&query, pos) { + Some((start, end)) => { + let distance = distance_from(start, end, cursor); + if distance < previous_distance { + previous = Some((start, end)); + previous_distance = distance; + } + if distance == 0 { + pos = query.len() + } else { + pos = end + 1; + } + } + None => { + pos += 1 + } + } + } + match previous { + None => false, + Some((start, end)) => { + let before = &query[0..start + 1]; // ends with '[' + let after = &query[end..]; // starts with ']' + let number = &query[start + 1..end]; + let current_value: isize = match number.parse() { + Ok(value) => value, + Err(_) => return false, + }; + let mut new_value = current_value + value; + + // array bound checking and cycling + let array = &before[..before.len() - 1]; + let len = query_array_length(&renderer.input_json_stream, array); + if len == -1 { + return false; + } + + if current_value == 0 && new_value == -1 { + new_value = len - 1; + } + if new_value > 0 && new_value >= len { + new_value = 0; + } else if new_value < -len { + new_value = -1; + } + let new_query = format!("{before}{new_value}{after}"); + query_editor_after_mut.texteditor.replace(&new_query); + let mut npos = query_editor_after_mut.texteditor.position(); + while npos > cursor { + query_editor_after_mut.texteditor.backward(); + npos = query_editor_after_mut.texteditor.position(); + } + true + } + } +} + +fn query_array_length(input_json_stream: &[Value], array: &str) -> isize { + let stream_len = input_json_stream.len(); + if stream_len == 0 || stream_len > 1 { + // honestly, don't now how to handle it + return -1; + } + let query_len = format!("{array} | length"); + let v = &input_json_stream[0]; + + let _ignore_err = Gag::stderr().unwrap(); + + match j9::run(&query_len, &v.to_string()) { + Ok(ret) => { + if ret.is_empty() { + -1 + } else { + ret[0].parse().unwrap() + } + } + Err(_e) => -1, + } +} + +fn distance_from(start: usize, end: usize, cursor: usize) -> usize { + if start <= cursor && cursor < end { + 0 + } else if cursor < start { + start - cursor + } else { + cursor - end + } +} +fn find_array_index(query: &str, start: usize) -> Option<(usize, usize)> { + let iter = query.chars().enumerate().skip(start); + let mut iter = iter.skip_while(|(_, ch)| *ch != '['); + let start = match iter.next() { + None => return None, + Some((pos, _)) => pos, + }; + let mut iter = iter.skip_while(|(i, ch)| *i == start + 1 && *ch == '-'); + let _minus = iter.next().is_some(); + let mut iter = iter.skip_while(|(_, ch)| ch.is_ascii_digit()); + match iter.next() { + Some((end, ']')) => Some((start, end)), + _ => None + } +} diff --git a/src/jnv/keymap.rs b/src/jnv/keymap.rs index cc48bab..cf54214 100644 --- a/src/jnv/keymap.rs +++ b/src/jnv/keymap.rs @@ -1,3 +1,4 @@ +use super::editing::add_to_nearest_array_index; use promkit::{ crossterm::event::{Event, KeyCode, KeyEvent, KeyEventKind, KeyEventState, KeyModifiers}, listbox::Listbox, @@ -163,6 +164,23 @@ pub fn default(event: &Event, renderer: &mut crate::jnv::render::Renderer) -> Re json_after_mut.stream.collapse_all(); } + Event::Key(KeyEvent { + code: KeyCode::Down, + modifiers: KeyModifiers::SHIFT, + kind: KeyEventKind::Press, + state: KeyEventState::NONE, + }) => { + add_to_nearest_array_index(renderer, 1); + } + Event::Key(KeyEvent { + code: KeyCode::Up, + modifiers: KeyModifiers::SHIFT, + kind: KeyEventKind::Press, + state: KeyEventState::NONE, + }) => { + add_to_nearest_array_index(renderer, -1); + } + // Input char. Event::Key(KeyEvent { code: KeyCode::Char(ch), diff --git a/src/jnv/render.rs b/src/jnv/render.rs index 6a275c4..a5d6fb0 100644 --- a/src/jnv/render.rs +++ b/src/jnv/render.rs @@ -1,6 +1,5 @@ use promkit::{ - impl_as_any, impl_cast, json, keymap::KeymapManager, listbox, pane::Pane, snapshot::Snapshot, - suggest::Suggest, text, text_editor, + impl_as_any, impl_cast, json, keymap::KeymapManager, listbox, pane::Pane, serde_json::Value, snapshot::Snapshot, suggest::Suggest, text, text_editor }; #[derive(Clone)] @@ -11,6 +10,7 @@ pub struct Renderer { pub suggest: Suggest, pub suggest_snapshot: Snapshot, pub json_snapshot: Snapshot, + pub input_json_stream: Vec } impl_as_any!(Renderer);