release/0.5.0 #15
95
src/app.rs
95
src/app.rs
@@ -8,6 +8,8 @@ pub enum Mode {
|
|||||||
Normal,
|
Normal,
|
||||||
/// Active text entry mode for modifying values.
|
/// Active text entry mode for modifying values.
|
||||||
Insert,
|
Insert,
|
||||||
|
/// Active text entry mode for modifying keys.
|
||||||
|
InsertKey,
|
||||||
/// Active search mode for filtering keys.
|
/// Active search mode for filtering keys.
|
||||||
Search,
|
Search,
|
||||||
}
|
}
|
||||||
@@ -145,18 +147,91 @@ impl App {
|
|||||||
/// Updates the input buffer to reflect the value of the currently selected variable.
|
/// Updates the input buffer to reflect the value of the currently selected variable.
|
||||||
pub fn sync_input_with_selected(&mut self) {
|
pub fn sync_input_with_selected(&mut self) {
|
||||||
if let Some(var) = self.vars.get(self.selected) {
|
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);
|
self.input = Input::new(val);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Commits the current text in the input buffer back to the selected variable's value.
|
/// Commits the current text in the input buffer back to the selected variable's value or key.
|
||||||
pub fn commit_input(&mut self) {
|
/// 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)
|
if let Some(var) = self.vars.get_mut(self.selected)
|
||||||
&& !var.is_group {
|
&& !var.is_group {
|
||||||
var.value = Some(self.input.value().to_string());
|
var.value = Some(self.input.value().to_string());
|
||||||
var.status = crate::format::ItemStatus::Modified;
|
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.
|
/// Transitions the application into Insert Mode with a specific variant.
|
||||||
@@ -182,10 +257,18 @@ impl App {
|
|||||||
|
|
||||||
/// Commits the current input and transitions the application into Normal Mode.
|
/// Commits the current input and transitions the application into Normal Mode.
|
||||||
pub fn enter_normal(&mut self) {
|
pub fn enter_normal(&mut self) {
|
||||||
self.commit_input();
|
if self.commit_input() {
|
||||||
self.save_undo_state();
|
self.save_undo_state();
|
||||||
self.mode = Mode::Normal;
|
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.
|
/// Deletes the currently selected item. If it's a group, deletes all children.
|
||||||
pub fn delete_selected(&mut self) {
|
pub fn delete_selected(&mut self) {
|
||||||
@@ -365,9 +448,13 @@ impl App {
|
|||||||
|
|
||||||
self.vars.insert(insert_pos, new_item);
|
self.vars.insert(insert_pos, new_item);
|
||||||
self.selected = insert_pos;
|
self.selected = insert_pos;
|
||||||
|
if is_array_item {
|
||||||
self.sync_input_with_selected();
|
self.sync_input_with_selected();
|
||||||
self.save_undo_state();
|
self.save_undo_state();
|
||||||
self.enter_insert(InsertVariant::Start);
|
self.enter_insert(InsertVariant::Start);
|
||||||
|
} else {
|
||||||
|
self.enter_insert_key();
|
||||||
|
}
|
||||||
self.status_message = None;
|
self.status_message = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -121,6 +121,7 @@ pub struct KeybindsConfig {
|
|||||||
pub delete_item: String,
|
pub delete_item: String,
|
||||||
pub undo: String,
|
pub undo: String,
|
||||||
pub redo: String,
|
pub redo: String,
|
||||||
|
pub rename: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for KeybindsConfig {
|
impl Default for KeybindsConfig {
|
||||||
@@ -144,6 +145,7 @@ pub struct KeybindsConfig {
|
|||||||
delete_item: "dd".to_string(),
|
delete_item: "dd".to_string(),
|
||||||
undo: "u".to_string(),
|
undo: "u".to_string(),
|
||||||
redo: "U".to_string(),
|
redo: "U".to_string(),
|
||||||
|
rename: "r".to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ where
|
|||||||
match self.app.mode {
|
match self.app.mode {
|
||||||
Mode::Normal => self.handle_normal_mode(key),
|
Mode::Normal => self.handle_normal_mode(key),
|
||||||
Mode::Insert => self.handle_insert_mode(key),
|
Mode::Insert => self.handle_insert_mode(key),
|
||||||
|
Mode::InsertKey => self.handle_insert_key_mode(key),
|
||||||
Mode::Search => self.handle_search_mode(key),
|
Mode::Search => self.handle_search_mode(key),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -129,6 +130,7 @@ where
|
|||||||
(&self.config.keybinds.delete_item, "delete_item"),
|
(&self.config.keybinds.delete_item, "delete_item"),
|
||||||
(&self.config.keybinds.undo, "undo"),
|
(&self.config.keybinds.undo, "undo"),
|
||||||
(&self.config.keybinds.redo, "redo"),
|
(&self.config.keybinds.redo, "redo"),
|
||||||
|
(&self.config.keybinds.rename, "rename"),
|
||||||
(&"a".to_string(), "add_missing"),
|
(&"a".to_string(), "add_missing"),
|
||||||
(&":".to_string(), "command"),
|
(&":".to_string(), "command"),
|
||||||
(&"q".to_string(), "quit"),
|
(&"q".to_string(), "quit"),
|
||||||
@@ -168,6 +170,7 @@ where
|
|||||||
"delete_item" => self.app.delete_selected(),
|
"delete_item" => self.app.delete_selected(),
|
||||||
"undo" => self.app.undo(),
|
"undo" => self.app.undo(),
|
||||||
"redo" => self.app.redo(),
|
"redo" => self.app.redo(),
|
||||||
|
"rename" => self.app.enter_insert_key(),
|
||||||
"add_missing" => {
|
"add_missing" => {
|
||||||
self.add_missing_item();
|
self.add_missing_item();
|
||||||
}
|
}
|
||||||
@@ -213,7 +216,26 @@ where
|
|||||||
/// Delegates key events to the `tui_input` handler during active editing.
|
/// Delegates key events to the `tui_input` handler during active editing.
|
||||||
fn handle_insert_mode(&mut self, key: KeyEvent) -> io::Result<()> {
|
fn handle_insert_mode(&mut self, key: KeyEvent) -> io::Result<()> {
|
||||||
match key.code {
|
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();
|
self.app.enter_normal();
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
|
|||||||
17
src/ui.rs
17
src/ui.rs
@@ -188,7 +188,9 @@ pub fn draw(f: &mut Frame, app: &mut App, config: &Config) {
|
|||||||
let mut extra_info = String::new();
|
let mut extra_info = String::new();
|
||||||
|
|
||||||
if let Some(var) = current_var {
|
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());
|
input_title = format!(" Group: {} ", var.path_string());
|
||||||
} else {
|
} else {
|
||||||
input_title = format!(" Editing: {} ", var.path_string());
|
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 {
|
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(),
|
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())
|
.fg(theme.bg_normal())
|
||||||
.add_modifier(Modifier::BOLD),
|
.add_modifier(Modifier::BOLD),
|
||||||
),
|
),
|
||||||
|
Mode::InsertKey => (
|
||||||
|
" RENAME ",
|
||||||
|
Style::default()
|
||||||
|
.bg(theme.bg_active())
|
||||||
|
.fg(theme.bg_normal())
|
||||||
|
.add_modifier(Modifier::BOLD),
|
||||||
|
),
|
||||||
Mode::Search => (
|
Mode::Search => (
|
||||||
" SEARCH ",
|
" SEARCH ",
|
||||||
Style::default()
|
Style::default()
|
||||||
@@ -282,6 +291,7 @@ pub fn draw(f: &mut Frame, app: &mut App, config: &Config) {
|
|||||||
if !app.selected_is_group() {
|
if !app.selected_is_group() {
|
||||||
parts.push(format!("{}/{}/{} edit", kb.edit, kb.edit_append, kb.edit_substitute));
|
parts.push(format!("{}/{}/{} edit", kb.edit, kb.edit_append, kb.edit_substitute));
|
||||||
}
|
}
|
||||||
|
parts.push(format!("{} rename", kb.rename));
|
||||||
if app.selected_is_missing() {
|
if app.selected_is_missing() {
|
||||||
parts.push(format!("{} add", "a")); // 'a' is currently hardcoded in runner
|
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.push(format!("{} quit", kb.quit));
|
||||||
parts.join(" · ")
|
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(),
|
Mode::Search => "Esc normal · type to filter".to_string(),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user