From e3510a96fbd431eae25353431a70a82949646470 Mon Sep 17 00:00:00 2001 From: Nils Pukropp Date: Wed, 18 Mar 2026 18:38:35 +0100 Subject: [PATCH] added rename --- src/app.rs | 111 ++++++++++++++++++++++++++++++++++++++++++++------ src/config.rs | 2 + src/runner.rs | 24 ++++++++++- src/ui.rs | 17 ++++++-- 4 files changed, 138 insertions(+), 16 deletions(-) diff --git a/src/app.rs b/src/app.rs index 6f7425e..94cf9f5 100644 --- a/src/app.rs +++ b/src/app.rs @@ -8,6 +8,8 @@ pub enum Mode { Normal, /// Active text entry mode for modifying values. Insert, + /// Active text entry mode for modifying keys. + InsertKey, /// Active search mode for filtering keys. Search, } @@ -145,18 +147,91 @@ impl App { /// Updates the input buffer to reflect the value of the currently selected variable. pub fn sync_input_with_selected(&mut self) { if let Some(var) = self.vars.get(self.selected) { - let val = var.value.clone().unwrap_or_default(); + let val = match self.mode { + Mode::InsertKey => var.key.clone(), + _ => var.value.clone().unwrap_or_default(), + }; self.input = Input::new(val); } } - /// Commits the current text in the input buffer back to the selected variable's value. - pub fn commit_input(&mut self) { - if let Some(var) = self.vars.get_mut(self.selected) - && !var.is_group { - var.value = Some(self.input.value().to_string()); - var.status = crate::format::ItemStatus::Modified; + /// Commits the current text in the input buffer back to the selected variable's value or key. + /// Returns true if commit was successful, false if there was an error (e.g. collision). + pub fn commit_input(&mut self) -> bool { + match self.mode { + Mode::Insert => { + if let Some(var) = self.vars.get_mut(self.selected) + && !var.is_group { + var.value = Some(self.input.value().to_string()); + var.status = crate::format::ItemStatus::Modified; + } + true } + Mode::InsertKey => { + let new_key = self.input.value().trim().to_string(); + if new_key.is_empty() { + self.status_message = Some("Key cannot be empty".to_string()); + return false; + } + + let selected_var = self.vars[self.selected].clone(); + if selected_var.key == new_key { + return true; + } + + // Collision check: siblings share the same parent path + let parent_path = if selected_var.path.len() > 1 { + &selected_var.path[..selected_var.path.len() - 1] + } else { + &[] + }; + + let exists = self.vars.iter().enumerate().any(|(i, v)| { + i != self.selected + && v.path.len() == selected_var.path.len() + && v.path.starts_with(parent_path) + && v.key == new_key + }); + + if exists { + self.status_message = Some(format!("Key already exists: {}", new_key)); + return false; + } + + // Update selected item's key and path + let old_path = selected_var.path.clone(); + let mut new_path = parent_path.to_vec(); + new_path.push(PathSegment::Key(new_key.clone())); + + { + let var = self.vars.get_mut(self.selected).unwrap(); + var.key = new_key; + var.path = new_path.clone(); + var.status = crate::format::ItemStatus::Modified; + } + + // Update paths of all children if it's a group + if selected_var.is_group { + for var in self.vars.iter_mut() { + if var.path.starts_with(&old_path) && var.path.len() > old_path.len() { + let mut p = new_path.clone(); + p.extend(var.path[old_path.len()..].iter().cloned()); + var.path = p; + } + } + } + true + } + _ => true, + } + } + + /// Transitions the application into Insert Mode for keys. + pub fn enter_insert_key(&mut self) { + if !self.vars.is_empty() { + self.mode = Mode::InsertKey; + self.sync_input_with_selected(); + } } /// Transitions the application into Insert Mode with a specific variant. @@ -182,9 +257,17 @@ impl App { /// Commits the current input and transitions the application into Normal Mode. pub fn enter_normal(&mut self) { - self.commit_input(); - self.save_undo_state(); + if self.commit_input() { + self.save_undo_state(); + self.mode = Mode::Normal; + } + } + + /// Cancels the current input and transitions the application into Normal Mode. + pub fn cancel_insert(&mut self) { self.mode = Mode::Normal; + self.sync_input_with_selected(); + self.status_message = None; } /// Deletes the currently selected item. If it's a group, deletes all children. @@ -365,9 +448,13 @@ impl App { self.vars.insert(insert_pos, new_item); self.selected = insert_pos; - self.sync_input_with_selected(); - self.save_undo_state(); - self.enter_insert(InsertVariant::Start); + if is_array_item { + self.sync_input_with_selected(); + self.save_undo_state(); + self.enter_insert(InsertVariant::Start); + } else { + self.enter_insert_key(); + } self.status_message = None; } diff --git a/src/config.rs b/src/config.rs index dfe592a..479cb83 100644 --- a/src/config.rs +++ b/src/config.rs @@ -121,6 +121,7 @@ pub struct KeybindsConfig { pub delete_item: String, pub undo: String, pub redo: String, + pub rename: String, } impl Default for KeybindsConfig { @@ -144,6 +145,7 @@ pub struct KeybindsConfig { delete_item: "dd".to_string(), undo: "u".to_string(), redo: "U".to_string(), + rename: "r".to_string(), } } } diff --git a/src/runner.rs b/src/runner.rs index c113288..2b8507b 100644 --- a/src/runner.rs +++ b/src/runner.rs @@ -67,6 +67,7 @@ where match self.app.mode { Mode::Normal => self.handle_normal_mode(key), Mode::Insert => self.handle_insert_mode(key), + Mode::InsertKey => self.handle_insert_key_mode(key), Mode::Search => self.handle_search_mode(key), } } @@ -129,6 +130,7 @@ where (&self.config.keybinds.delete_item, "delete_item"), (&self.config.keybinds.undo, "undo"), (&self.config.keybinds.redo, "redo"), + (&self.config.keybinds.rename, "rename"), (&"a".to_string(), "add_missing"), (&":".to_string(), "command"), (&"q".to_string(), "quit"), @@ -168,6 +170,7 @@ where "delete_item" => self.app.delete_selected(), "undo" => self.app.undo(), "redo" => self.app.redo(), + "rename" => self.app.enter_insert_key(), "add_missing" => { self.add_missing_item(); } @@ -213,7 +216,26 @@ where /// Delegates key events to the `tui_input` handler during active editing. fn handle_insert_mode(&mut self, key: KeyEvent) -> io::Result<()> { match key.code { - KeyCode::Esc | KeyCode::Enter => { + KeyCode::Esc => { + self.app.cancel_insert(); + } + KeyCode::Enter => { + self.app.enter_normal(); + } + _ => { + self.app.input.handle_event(&Event::Key(key)); + } + } + Ok(()) + } + + /// Handles keys in InsertKey mode. + fn handle_insert_key_mode(&mut self, key: KeyEvent) -> io::Result<()> { + match key.code { + KeyCode::Esc => { + self.app.cancel_insert(); + } + KeyCode::Enter => { self.app.enter_normal(); } _ => { diff --git a/src/ui.rs b/src/ui.rs index 6433510..9eb638c 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -188,7 +188,9 @@ pub fn draw(f: &mut Frame, app: &mut App, config: &Config) { let mut extra_info = String::new(); if let Some(var) = current_var { - if var.is_group { + if matches!(app.mode, Mode::InsertKey) { + input_title = format!(" Rename Key: {} ", var.path_string()); + } else if var.is_group { input_title = format!(" Group: {} ", var.path_string()); } else { input_title = format!(" Editing: {} ", var.path_string()); @@ -199,7 +201,7 @@ pub fn draw(f: &mut Frame, app: &mut App, config: &Config) { } let input_border_color = match app.mode { - Mode::Insert => theme.border_active(), + Mode::Insert | Mode::InsertKey => theme.border_active(), Mode::Normal | Mode::Search => theme.border_normal(), }; @@ -259,6 +261,13 @@ pub fn draw(f: &mut Frame, app: &mut App, config: &Config) { .fg(theme.bg_normal()) .add_modifier(Modifier::BOLD), ), + Mode::InsertKey => ( + " RENAME ", + Style::default() + .bg(theme.bg_active()) + .fg(theme.bg_normal()) + .add_modifier(Modifier::BOLD), + ), Mode::Search => ( " SEARCH ", Style::default() @@ -282,6 +291,7 @@ pub fn draw(f: &mut Frame, app: &mut App, config: &Config) { if !app.selected_is_group() { parts.push(format!("{}/{}/{} edit", kb.edit, kb.edit_append, kb.edit_substitute)); } + parts.push(format!("{} rename", kb.rename)); if app.selected_is_missing() { parts.push(format!("{} add", "a")); // 'a' is currently hardcoded in runner } @@ -294,7 +304,8 @@ pub fn draw(f: &mut Frame, app: &mut App, config: &Config) { parts.push(format!("{} quit", kb.quit)); parts.join(" · ") } - Mode::Insert => "Esc normal · Enter commit".to_string(), + Mode::Insert => "Esc cancel · Enter commit".to_string(), + Mode::InsertKey => "Esc cancel · Enter rename".to_string(), Mode::Search => "Esc normal · type to filter".to_string(), } };