diff --git a/Cargo.toml b/Cargo.toml index 0530bc0..97e0a8c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,4 +30,4 @@ tempfile = "3.27.0" authors = ["Nils Pukropp "] edition = "2024" name = "mould" -version = "0.4.1" +version = "0.4.2" diff --git a/README.md b/README.md index 92f6ba3..a30551c 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,9 @@ mould config.template.json -o config.json - `k` / `Up`: Move selection up - `gg`: Jump to the top - `G`: Jump to the bottom - - `i`: Edit the value of the currently selected key (Enter Insert Mode) + - `i`: Edit value (cursor at start) + - `A`: Edit value (cursor at end) + - `S`: Substitute value (clear and edit) - `o`: Append a new item to the current array - `O`: Prepend a new item to the current array - `dd`: Delete the currently selected variable or group diff --git a/config.example.toml b/config.example.toml index 2b70a63..7e3310b 100644 --- a/config.example.toml +++ b/config.example.toml @@ -31,6 +31,8 @@ tree_depth_4 = "#fab387" down = "j" up = "k" edit = "i" +edit_append = "A" +edit_substitute = "S" save = ":w" quit = ":q" normal_mode = "Esc" diff --git a/src/app.rs b/src/app.rs index c8e7195..fc4f797 100644 --- a/src/app.rs +++ b/src/app.rs @@ -11,6 +11,12 @@ pub enum Mode { Search, } +pub enum InsertVariant { + Start, + End, + Substitute, +} + /// The core application state, holding all configuration variables and UI status. pub struct App { /// The list of configuration variables being edited. @@ -149,12 +155,25 @@ impl App { } } - /// Transitions the application into Insert Mode. - pub fn enter_insert(&mut self) { + /// Transitions the application into Insert Mode with a specific variant. + pub fn enter_insert(&mut self, variant: InsertVariant) { if let Some(var) = self.vars.get(self.selected) { if !var.is_group { self.mode = Mode::Insert; self.status_message = None; + match variant { + InsertVariant::Start => { + use tui_input::InputRequest; + self.input.handle(InputRequest::GoToStart); + } + InsertVariant::End => { + use tui_input::InputRequest; + self.input.handle(InputRequest::GoToEnd); + } + InsertVariant::Substitute => { + self.input = Input::new(String::new()); + } + } } } } @@ -182,9 +201,13 @@ impl App { to_remove.push(self.selected); if is_group { - let prefix = format!("{}.", selected_path); + let prefix_dot = format!("{}.", selected_path); + let prefix_bracket = format!("{}[", selected_path); for (i, var) in self.vars.iter().enumerate() { - if var.path.starts_with(&prefix) { + if i == self.selected { + continue; + } + if var.path.starts_with(&prefix_dot) || var.path.starts_with(&prefix_bracket) { to_remove.push(i); } } @@ -279,9 +302,26 @@ impl App { self.vars.insert(insert_pos, new_item); self.selected = insert_pos; self.sync_input_with_selected(); - self.mode = Mode::Insert; + self.enter_insert(InsertVariant::Start); self.status_message = None; } + + /// Status bar helpers + pub fn selected_is_group(&self) -> bool { + self.vars.get(self.selected).map(|v| v.is_group).unwrap_or(false) + } + + pub fn selected_is_array(&self) -> bool { + self.vars.get(self.selected) + .map(|v| !v.is_group && v.path.contains('[')) + .unwrap_or(false) + } + + pub fn selected_is_missing(&self) -> bool { + self.vars.get(self.selected) + .map(|v| v.status == crate::format::ItemStatus::MissingFromActive) + .unwrap_or(false) + } } fn parse_index(path: &str) -> Option<(&str, usize)> { diff --git a/src/config.rs b/src/config.rs index ab3084d..1072de3 100644 --- a/src/config.rs +++ b/src/config.rs @@ -106,6 +106,8 @@ pub struct KeybindsConfig { pub down: String, pub up: String, pub edit: String, + pub edit_append: String, + pub edit_substitute: String, pub save: String, pub quit: String, pub normal_mode: String, @@ -125,6 +127,8 @@ impl Default for KeybindsConfig { down: "j".to_string(), up: "k".to_string(), edit: "i".to_string(), + edit_append: "A".to_string(), + edit_substitute: "S".to_string(), save: ":w".to_string(), quit: ":q".to_string(), normal_mode: "Esc".to_string(), diff --git a/src/runner.rs b/src/runner.rs index ec738d1..7112e68 100644 --- a/src/runner.rs +++ b/src/runner.rs @@ -1,4 +1,4 @@ -use crate::app::{App, Mode}; +use crate::app::{App, InsertVariant, Mode}; use crate::config::Config; use crate::format::FormatHandler; use crossterm::event::{self, Event, KeyCode, KeyEvent}; @@ -117,6 +117,8 @@ where (&self.config.keybinds.down, "down"), (&self.config.keybinds.up, "up"), (&self.config.keybinds.edit, "edit"), + (&self.config.keybinds.edit_append, "edit_append"), + (&self.config.keybinds.edit_substitute, "edit_substitute"), (&self.config.keybinds.search, "search"), (&self.config.keybinds.next_match, "next_match"), (&self.config.keybinds.previous_match, "previous_match"), @@ -147,7 +149,9 @@ where match action { "down" => self.app.next(), "up" => self.app.previous(), - "edit" => self.app.enter_insert(), + "edit" => self.app.enter_insert(InsertVariant::Start), + "edit_append" => self.app.enter_insert(InsertVariant::End), + "edit_substitute" => self.app.enter_insert(InsertVariant::Substitute), "search" => { self.app.mode = Mode::Search; self.app.search_query.clear(); diff --git a/src/ui.rs b/src/ui.rs index ef584a4..3f1f591 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -269,14 +269,30 @@ pub fn draw(f: &mut Frame, app: &mut App, config: &Config) { ), }; - let status_msg = app - .status_message - .as_deref() - .unwrap_or_else(|| match app.mode { - Mode::Normal => " navigation | i: edit | /: search | :w: save | :q: quit ", - Mode::Insert => " Esc: back to normal | Enter: commit ", - Mode::Search => " Esc: back to normal | type to filter ", - }); + let status_msg = if let Some(msg) = &app.status_message { + msg.clone() + } else { + match app.mode { + Mode::Normal => { + let mut parts = vec!["j/k move", "gg/G jump", "/ search"]; + if !app.selected_is_group() { + parts.push("i/A/S edit"); + } + if app.selected_is_missing() { + parts.push("a add"); + } + if app.selected_is_array() { + parts.push("o/O array"); + } + parts.push("dd del"); + parts.push(":w save"); + parts.push(":q quit"); + parts.join(" · ") + } + Mode::Insert => "Esc normal · Enter commit".to_string(), + Mode::Search => "Esc normal · type to filter".to_string(), + } + }; let status_line = Line::from(vec![ Span::styled(mode_str, mode_style),