Repository: ynqa/jnv (language: rust)

Issue:
Native keyboard shortcuts for quickly navigating the query don't work
On macOS, I frequently use Option+Arrow Keys to jump through text inputs one "word" at a time. This works in the native shell as well as most text inputs. Would it be possible to support this in jnv's query input?

Thanks for making this! It's a neat tool that I've enjoyed with the minimal usage I've had so far, and I hope it's been a fun project to work on.



Discussion / hints:
@hu0p Thanks for your suggestions 📝 

I'm currently investigating the cause, but I've discovered that pressing Option+Arrow is evaluated as Esc by crossterm (my environment is MacOS using Iterm2).

On the other hand, the combination of Option+{b, f} is recognized correctly, so I'm planning to provide equivalent functionality with shortcuts for Option+{b, f}.
I also use iTerm 2. I forgot that I changed something in my configuration to make this work the same way. Sorry about that! Your approach sounds good to me. 

Fix patch:
diff --git a/Cargo.lock b/Cargo.lock
index 6c14d02..c15dcd2 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -67,9 +67,9 @@ checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247"
 
 [[package]]
 name = "autocfg"
-version = "1.1.0"
+version = "1.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
+checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80"
 
 [[package]]
 name = "autotools"
@@ -149,9 +149,9 @@ dependencies = [
 
 [[package]]
 name = "clap"
-version = "4.5.3"
+version = "4.5.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "949626d00e063efc93b6dca932419ceb5432f99769911c0b995f7e884c778813"
+checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0"
 dependencies = [
  "clap_builder",
  "clap_derive",
@@ -171,9 +171,9 @@ dependencies = [
 
 [[package]]
 name = "clap_derive"
-version = "4.5.3"
+version = "4.5.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "90239a040c80f5e14809ca132ddc4176ab33d5e17e49691793296e3fcb34d72f"
+checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64"
 dependencies = [
  "heck",
  "proc-macro2",
@@ -322,9 +322,9 @@ dependencies = [
 
 [[package]]
 name = "itoa"
-version = "1.0.10"
+version = "1.0.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
+checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
 
 [[package]]
 name = "j9"
@@ -350,7 +350,7 @@ dependencies = [
 
 [[package]]
 name = "jnv"
-version = "0.2.0"
+version = "0.2.1"
 dependencies = [
  "anyhow",
  "clap",
@@ -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",
@@ -570,9 +570,9 @@ dependencies = [
 
 [[package]]
 name = "regex-syntax"
-version = "0.8.2"
+version = "0.8.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
+checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56"
 
 [[package]]
 name = "rustc-hash"
@@ -636,9 +636,9 @@ dependencies = [
 
 [[package]]
 name = "serde_json"
-version = "1.0.114"
+version = "1.0.115"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0"
+checksum = "12dc5c46daa8e9fdf4f5e71b6cf9a53f2487da0e86e55808e2d35539666497dd"
 dependencies = [
  "indexmap",
  "itoa",
diff --git a/Cargo.toml b/Cargo.toml
index 4139a9e..2b39a57 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "jnv"
-version = "0.2.0"
+version = "0.2.1"
 authors = ["ynqa <un.pensiero.vano@gmail.com>"]
 edition = "2021"
 description = "JSON navigator and interactive filter leveraging jq"
@@ -10,10 +10,10 @@ readme = "README.md"
 
 [dependencies]
 anyhow = "1.0.80"
-clap = { version = "4.5.1", features = ["derive"] }
+clap = { version = "4.5.4", 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/README.md b/README.md
index 143c6bb..1270956 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,7 @@
 # jnv
 
+[![ci](https://github.com/ynqa/jnv/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/ynqa/jnv/actions/workflows/ci.yml)
+
 *jnv* is designed for navigating JSON,
 offering an interactive JSON viewer and `jq` filter editor.
 
@@ -87,6 +89,10 @@ jnv data.json
 | <kbd>Enter</kbd>     | Toggle expand/collapse in JSON viewer
 | <kbd>Ctrl + P</kbd>  | Expand all folds in JSON viewer
 | <kbd>Ctrl + N</kbd>  | Collapse all folds in JSON viewer
+| <kbd>Alt + B</kbd>   | Move the cursor to the previous nearest character within set(`.`,`\|`,`(`,`)`,`[`,`]`)
+| <kbd>Alt + F</kbd>   | Move the cursor to the next nearest character within set(`.`,`\|`,`(`,`)`,`[`,`]`)
+| <kbd>Ctrl + W</kbd>  | Erase to the previous nearest character within set(`.`,`\|`,`(`,`)`,`[`,`]`)
+| <kbd>Alt + D</kbd>   | Erase to the next nearest character within set(`.`,`\|`,`(`,`)`,`[`,`]`)
 
 ## Usage
 
diff --git a/src/jnv.rs b/src/jnv.rs
index 19a4497..86a3353 100644
--- a/src/jnv.rs
+++ b/src/jnv.rs
@@ -1,4 +1,4 @@
-use std::{cell::RefCell, rc::Rc};
+use std::{cell::RefCell, collections::HashSet, rc::Rc};
 
 use anyhow::Result;
 use gag::Gag;
@@ -83,6 +83,7 @@ impl Jnv {
                 active_char_style: StyleBuilder::new().bgc(Color::Magenta).build(),
                 inactive_char_style: StyleBuilder::new().build(),
                 edit_mode,
+                word_break_chars: HashSet::from(['.', '|', '(', ')', '[', ']']),
                 lines: Default::default(),
             },
             hint_message_renderer: text::Renderer {
diff --git a/src/jnv/keymap.rs b/src/jnv/keymap.rs
index cc48bab..34d3f61 100644
--- a/src/jnv/keymap.rs
+++ b/src/jnv/keymap.rs
@@ -69,6 +69,24 @@ pub fn default(event: &Event, renderer: &mut crate::jnv::render::Renderer) -> Re
             state: KeyEventState::NONE,
         }) => query_editor_after_mut.texteditor.move_to_tail(),
 
+        Event::Key(KeyEvent {
+            code: KeyCode::Char('b'),
+            modifiers: KeyModifiers::ALT,
+            kind: KeyEventKind::Press,
+            state: KeyEventState::NONE,
+        }) => query_editor_after_mut
+            .texteditor
+            .move_to_previous_nearest(&query_editor_after_mut.word_break_chars),
+
+        Event::Key(KeyEvent {
+            code: KeyCode::Char('f'),
+            modifiers: KeyModifiers::ALT,
+            kind: KeyEventKind::Press,
+            state: KeyEventState::NONE,
+        }) => query_editor_after_mut
+            .texteditor
+            .move_to_next_nearest(&query_editor_after_mut.word_break_chars),
+
         // Erase char(s).
         Event::Key(KeyEvent {
             code: KeyCode::Backspace,
@@ -83,6 +101,25 @@ pub fn default(event: &Event, renderer: &mut crate::jnv::render::Renderer) -> Re
             state: KeyEventState::NONE,
         }) => query_editor_after_mut.texteditor.erase_all(),
 
+        // Erase to the nearest character.
+        Event::Key(KeyEvent {
+            code: KeyCode::Char('w'),
+            modifiers: KeyModifiers::CONTROL,
+            kind: KeyEventKind::Press,
+            state: KeyEventState::NONE,
+        }) => query_editor_after_mut
+            .texteditor
+            .erase_to_previous_nearest(&query_editor_after_mut.word_break_chars),
+
+        Event::Key(KeyEvent {
+            code: KeyCode::Char('d'),
+            modifiers: KeyModifiers::ALT,
+            kind: KeyEventKind::Press,
+            state: KeyEventState::NONE,
+        }) => query_editor_after_mut
+            .texteditor
+            .erase_to_next_nearest(&query_editor_after_mut.word_break_chars),
+
         // Move up.
         Event::Key(KeyEvent {
             code: KeyCode::Up,


Test patch: