diff --git a/src/app.rs b/src/app.rs index df32417..5d1faa4 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,4 +1,4 @@ -use crate::format::ConfigItem; +use crate::format::{ConfigItem, PathSegment}; use tui_input::Input; /// Represents the current operating mode of the application. @@ -195,21 +195,17 @@ impl App { let selected_path = self.vars[self.selected].path.clone(); let is_group = self.vars[self.selected].is_group; - // Identify if the item being removed is an array item - let array_info = parse_index(&selected_path); - // 1. Identify all items to remove let mut to_remove = Vec::new(); to_remove.push(self.selected); if is_group { - let prefix_dot = format!("{}.", selected_path); - let prefix_bracket = format!("{}[", selected_path); for (i, var) in self.vars.iter().enumerate() { if i == self.selected { continue; } - if var.path.starts_with(&prefix_dot) || var.path.starts_with(&prefix_bracket) { + // An item is a child if its path starts with the selected path + if var.path.starts_with(&selected_path) { to_remove.push(i); } } @@ -222,17 +218,19 @@ impl App { } // 3. Re-index subsequent array items if applicable - if let Some((base, removed_idx)) = array_info { - let base = base.to_string(); + if let Some(PathSegment::Index(removed_idx)) = selected_path.last() { + let base_path = &selected_path[..selected_path.len() - 1]; + for var in self.vars.iter_mut() { - if var.path.starts_with(&base) { - // We need to find the index segment that matches this array - if let Some((b, i, suffix)) = find_array_segment(&var.path, &base) - && b == base && i > removed_idx { + if var.path.starts_with(base_path) && var.path.len() >= selected_path.len() { + // Check if the element at the level of the removed index is an index + if let PathSegment::Index(i) = var.path[selected_path.len() - 1] + && i > *removed_idx { let new_idx = i - 1; - var.path = format!("{}[{}]{}", base, new_idx, suffix); - // Also update key if it matches the old index exactly - if var.key == format!("[{}]", i) { + var.path[selected_path.len() - 1] = PathSegment::Index(new_idx); + + // If this was an array element itself (not a child property), update its key + if var.path.len() == selected_path.len() { var.key = format!("[{}]", new_idx); } } @@ -254,14 +252,15 @@ impl App { } self.save_undo_state(); - let (base, idx, depth) = { + let (base_path, idx, depth) = { let selected_item = &self.vars[self.selected]; if selected_item.is_group { return; } let path = &selected_item.path; - if let Some((base, idx)) = parse_index(path) { - (base.to_string(), idx, selected_item.depth) + + if let Some(PathSegment::Index(idx)) = path.last() { + (path[..path.len() - 1].to_vec(), *idx, selected_item.depth) } else { return; } @@ -276,21 +275,23 @@ impl App { // 1. Shift all items in this array that have index >= new_idx for var in self.vars.iter_mut() { - if var.path.starts_with(&base) - && let Some((b, i)) = parse_index(&var.path) - && b == base && i >= new_idx { - var.path = format!("{}[{}]", base, i + 1); - // Also update key if it was just the index - if var.key == format!("[{}]", i) { + if var.path.starts_with(&base_path) && var.path.len() > base_path.len() + && let PathSegment::Index(i) = var.path[base_path.len()] + && i >= new_idx { + var.path[base_path.len()] = PathSegment::Index(i + 1); + if var.path.len() == base_path.len() + 1 { var.key = format!("[{}]", i + 1); } } } // 2. Insert new item + let mut new_path = base_path; + new_path.push(PathSegment::Index(new_idx)); + let new_item = ConfigItem { key: format!("[{}]", new_idx), - path: format!("{}[{}]", base, new_idx), + path: new_path, value: Some("".to_string()), template_value: None, default_value: None, @@ -299,6 +300,7 @@ impl App { status: crate::format::ItemStatus::Modified, value_type: crate::format::ValueType::String, }; + self.vars.insert(insert_pos, new_item); self.selected = insert_pos; self.sync_input_with_selected(); @@ -313,7 +315,7 @@ impl App { pub fn selected_is_array(&self) -> bool { self.vars.get(self.selected) - .map(|v| !v.is_group && v.path.contains('[')) + .map(|v| !v.is_group && matches!(v.path.last(), Some(PathSegment::Index(_)))) .unwrap_or(false) } @@ -344,30 +346,4 @@ impl App { self.status_message = Some("Nothing to undo".to_string()); } } -} - -fn parse_index(path: &str) -> Option<(&str, usize)> { - if let Some(end) = path.rfind(']') { - let segment = &path[..=end]; - if let Some(start) = segment.rfind('[') - && let Ok(idx) = segment[start + 1..end].parse::() { - // Return the base and index - return Some((&path[..start], idx)); - } - } - None -} - -/// Helper to find an array segment in a path given a base prefix. -fn find_array_segment<'a>(path: &'a str, base: &str) -> Option<(&'a str, usize, &'a str)> { - if !path.starts_with(base) { - return None; - } - let remaining = &path[base.len()..]; - if remaining.starts_with('[') - && let Some(end) = remaining.find(']') - && let Ok(idx) = remaining[1..end].parse::() { - return Some((&path[..base.len()], idx, &remaining[end + 1..])); - } - None -} +} \ No newline at end of file diff --git a/src/format/env.rs b/src/format/env.rs index ad2b534..d02eacf 100644 --- a/src/format/env.rs +++ b/src/format/env.rs @@ -1,4 +1,4 @@ -use super::{ConfigItem, FormatHandler, ItemStatus, ValueType}; +use super::{ConfigItem, FormatHandler, ItemStatus, ValueType, PathSegment}; use std::fs; use std::io::Write; use std::path::Path; @@ -18,9 +18,10 @@ impl FormatHandler for EnvHandler { if let Some((key, val)) = line.split_once('=') { let parsed_val = val.trim().trim_matches('"').trim_matches('\'').to_string(); + let key_str = key.trim().to_string(); vars.push(ConfigItem { - key: key.trim().to_string(), - path: key.trim().to_string(), + key: key_str.clone(), + path: vec![PathSegment::Key(key_str)], value: Some(parsed_val.clone()), template_value: Some(parsed_val.clone()), default_value: Some(parsed_val), @@ -116,7 +117,7 @@ mod tests { let file = NamedTempFile::new().unwrap(); let vars = vec![ConfigItem { key: "KEY1".to_string(), - path: "KEY1".to_string(), + path: vec![PathSegment::Key("KEY1".to_string())], value: Some("value1".to_string()), template_value: None, default_value: None, diff --git a/src/format/hierarchical.rs b/src/format/hierarchical.rs index c6b7464..8a6d592 100644 --- a/src/format/hierarchical.rs +++ b/src/format/hierarchical.rs @@ -1,4 +1,4 @@ -use super::{ConfigItem, FormatHandler, FormatType, ItemStatus, ValueType}; +use super::{ConfigItem, FormatHandler, FormatType, ItemStatus, ValueType, PathSegment}; use serde_json::{Map, Value}; use std::fs; use std::path::Path; @@ -153,26 +153,33 @@ fn json_to_xml(value: &Value) -> String { } } -// remove unused get_xml_root_name -// fn get_xml_root_name(content: &str) -> Option { ... } +fn flatten(value: &Value, current_path: Vec, key_name: Option, depth: usize, vars: &mut Vec) { + let mut next_path = current_path.clone(); + + if let Some(ref k) = key_name { + if !current_path.is_empty() { + // It's a key in an object, so append to path + next_path.push(PathSegment::Key(k.clone())); + } else { + // First element, maybe root + if !k.is_empty() { + next_path.push(PathSegment::Key(k.clone())); + } + } + } -fn flatten(value: &Value, prefix: &str, depth: usize, key_name: &str, vars: &mut Vec) { - let path = if prefix.is_empty() { - key_name.to_string() - } else if key_name.is_empty() { - prefix.to_string() - } else if key_name.starts_with('[') { - format!("{}{}", prefix, key_name) - } else { - format!("{}.{}", prefix, key_name) + let display_key = match next_path.last() { + Some(PathSegment::Key(k)) => k.clone(), + Some(PathSegment::Index(i)) => format!("[{}]", i), + None => "".to_string(), }; match value { Value::Object(map) => { - if !path.is_empty() { + if !next_path.is_empty() { vars.push(ConfigItem { - key: key_name.to_string(), - path: path.clone(), + key: display_key, + path: next_path.clone(), value: None, template_value: None, default_value: None, @@ -182,16 +189,16 @@ fn flatten(value: &Value, prefix: &str, depth: usize, key_name: &str, vars: &mut value_type: ValueType::Null, }); } - let next_depth = if path.is_empty() { depth } else { depth + 1 }; + let next_depth = if next_path.is_empty() { depth } else { depth + 1 }; for (k, v) in map { - flatten(v, &path, next_depth, k, vars); + flatten(v, next_path.clone(), Some(k.clone()), next_depth, vars); } } Value::Array(arr) => { - if !path.is_empty() { + if !next_path.is_empty() { vars.push(ConfigItem { - key: key_name.to_string(), - path: path.clone(), + key: display_key, + path: next_path.clone(), value: None, template_value: None, default_value: None, @@ -201,16 +208,17 @@ fn flatten(value: &Value, prefix: &str, depth: usize, key_name: &str, vars: &mut value_type: ValueType::Null, }); } - let next_depth = if path.is_empty() { depth } else { depth + 1 }; + let next_depth = if next_path.is_empty() { depth } else { depth + 1 }; for (i, v) in arr.iter().enumerate() { - let array_key = format!("[{}]", i); - flatten(v, &path, next_depth, &array_key, vars); + let mut arr_path = next_path.clone(); + arr_path.push(PathSegment::Index(i)); + flatten(v, arr_path, None, next_depth, vars); } } Value::String(s) => { vars.push(ConfigItem { - key: key_name.to_string(), - path: path.clone(), + key: display_key, + path: next_path.clone(), value: Some(s.clone()), template_value: Some(s.clone()), default_value: Some(s.clone()), @@ -223,8 +231,8 @@ fn flatten(value: &Value, prefix: &str, depth: usize, key_name: &str, vars: &mut Value::Number(n) => { let s = n.to_string(); vars.push(ConfigItem { - key: key_name.to_string(), - path: path.clone(), + key: display_key, + path: next_path.clone(), value: Some(s.clone()), template_value: Some(s.clone()), default_value: Some(s.clone()), @@ -237,8 +245,8 @@ fn flatten(value: &Value, prefix: &str, depth: usize, key_name: &str, vars: &mut Value::Bool(b) => { let s = b.to_string(); vars.push(ConfigItem { - key: key_name.to_string(), - path: path.clone(), + key: display_key, + path: next_path.clone(), value: Some(s.clone()), template_value: Some(s.clone()), default_value: Some(s.clone()), @@ -250,8 +258,8 @@ fn flatten(value: &Value, prefix: &str, depth: usize, key_name: &str, vars: &mut } Value::Null => { vars.push(ConfigItem { - key: key_name.to_string(), - path: path.clone(), + key: display_key, + path: next_path.clone(), value: Some("".to_string()), template_value: Some("".to_string()), default_value: Some("".to_string()), @@ -268,7 +276,7 @@ impl FormatHandler for HierarchicalHandler { fn parse(&self, path: &Path) -> anyhow::Result> { let value = self.read_value(path)?; let mut vars = Vec::new(); - flatten(&value, "", 0, "", &mut vars); + flatten(&value, Vec::new(), Some("".to_string()), 0, &mut vars); Ok(vars) } @@ -286,50 +294,52 @@ impl FormatHandler for HierarchicalHandler { } } -fn insert_into_value(root: &mut Value, path: &str, new_val_str: &str, value_type: ValueType) { - let mut parts = path.split('.'); - let last_part = match parts.next_back() { - Some(p) => p, - None => return, - }; +fn insert_into_value(root: &mut Value, path: &[PathSegment], new_val_str: &str, value_type: ValueType) { + if path.is_empty() { + return; + } let mut current = root; - for part in parts { - let (key, idx) = parse_array_key(part); - if !current.is_object() { - *current = Value::Object(Map::new()); - } - let map = current.as_object_mut().unwrap(); + + // Traverse all but the last segment + for i in 0..path.len() - 1 { + let segment = &path[i]; + let next_segment = &path[i + 1]; - let next_node = map.entry(key.to_string()).or_insert_with(|| { - if idx.is_some() { - Value::Array(Vec::new()) - } else { - Value::Object(Map::new()) + match segment { + PathSegment::Key(key) => { + if !current.is_object() { + *current = Value::Object(Map::new()); + } + let map = current.as_object_mut().unwrap(); + + let next_node = map.entry(key.clone()).or_insert_with(|| { + match next_segment { + PathSegment::Index(_) => Value::Array(Vec::new()), + PathSegment::Key(_) => Value::Object(Map::new()), + } + }); + current = next_node; } - }); - - if let Some(i) = idx { - if !next_node.is_array() { - *next_node = Value::Array(Vec::new()); + PathSegment::Index(idx) => { + if !current.is_array() { + *current = Value::Array(Vec::new()); + } + let arr = current.as_array_mut().unwrap(); + while arr.len() <= *idx { + match next_segment { + PathSegment::Index(_) => arr.push(Value::Array(Vec::new())), + PathSegment::Key(_) => arr.push(Value::Object(Map::new())), + } + } + current = &mut arr[*idx]; } - let arr = next_node.as_array_mut().unwrap(); - while arr.len() <= i { - arr.push(Value::Object(Map::new())); - } - current = &mut arr[i]; - } else { - current = next_node; } } - let (final_key, final_idx) = parse_array_key(last_part); - if !current.is_object() { - *current = Value::Object(Map::new()); - } - let map = current.as_object_mut().unwrap(); - - // Use the preserved ValueType instead of aggressive inference + // Handle the final segment + let final_segment = &path[path.len() - 1]; + let final_val = match value_type { ValueType::Number => { if let Ok(n) = new_val_str.parse::() { @@ -355,31 +365,24 @@ fn insert_into_value(root: &mut Value, path: &str, new_val_str: &str, value_type _ => Value::String(new_val_str.to_string()), }; - if let Some(i) = final_idx { - let next_node = map - .entry(final_key.to_string()) - .or_insert_with(|| Value::Array(Vec::new())); - if !next_node.is_array() { - *next_node = Value::Array(Vec::new()); + match final_segment { + PathSegment::Key(key) => { + if !current.is_object() { + *current = Value::Object(Map::new()); + } + let map = current.as_object_mut().unwrap(); + map.insert(key.clone(), final_val); } - let arr = next_node.as_array_mut().unwrap(); - while arr.len() <= i { - arr.push(Value::Null); + PathSegment::Index(idx) => { + if !current.is_array() { + *current = Value::Array(Vec::new()); + } + let arr = current.as_array_mut().unwrap(); + while arr.len() <= *idx { + arr.push(Value::Null); + } + arr[*idx] = final_val; } - arr[i] = final_val; - } else { - map.insert(final_key.to_string(), final_val); - } -} - -fn parse_array_key(part: &str) -> (&str, Option) { - if part.ends_with(']') && part.contains('[') { - let start_idx = part.find('[').unwrap(); - let key = &part[..start_idx]; - let idx = part[start_idx + 1..part.len() - 1].parse::().ok(); - (key, idx) - } else { - (part, None) } } @@ -401,7 +404,7 @@ mod tests { } }); - flatten(&json, "", 0, "", &mut vars); + flatten(&json, Vec::new(), Some("".to_string()), 0, &mut vars); assert_eq!(vars.len(), 6); let mut root = Value::Object(Map::new()); @@ -411,7 +414,6 @@ mod tests { } } - // When unflattening, it parses bool back let unflattened_json = serde_json::to_string(&root).unwrap(); assert!(unflattened_json.contains("\"8080:80\"")); assert!(unflattened_json.contains("true")); @@ -420,7 +422,6 @@ mod tests { #[test] fn test_type_preservation() { let mut vars = Vec::new(); - // A JSON with various tricky types let json = serde_json::json!({ "port_num": 8080, "port_str": "8080", @@ -430,7 +431,7 @@ mod tests { "float_str": "42.42" }); - flatten(&json, "", 0, "", &mut vars); + flatten(&json, Vec::new(), Some("".to_string()), 0, &mut vars); let mut root = Value::Object(Map::new()); for var in vars { @@ -439,7 +440,6 @@ mod tests { } } - // Validate that types are exactly preserved after re-assembling let unflattened = root.as_object().unwrap(); assert!(unflattened["port_num"].is_number(), "port_num should be a number"); @@ -471,7 +471,7 @@ server: "; let yaml_val: Value = serde_yaml::from_str(yaml_str).unwrap(); let mut vars = Vec::new(); - flatten(&yaml_val, "", 0, "", &mut vars); + flatten(&yaml_val, Vec::new(), Some("".to_string()), 0, &mut vars); let mut root = Value::Object(Map::new()); for var in vars { @@ -482,7 +482,6 @@ server: let unflattened_yaml = serde_yaml::to_string(&root).unwrap(); assert!(unflattened_yaml.contains("port: 8080")); - // Serde YAML might output '8080' or "8080" assert!(unflattened_yaml.contains("port_str: '8080'") || unflattened_yaml.contains("port_str: \"8080\"")); assert!(unflattened_yaml.contains("enabled: true")); } @@ -495,12 +494,11 @@ port = 8080 port_str = \"8080\" enabled = true "; - // parse to toml Value, then convert to serde_json Value to reuse the same flatten path let toml_val: toml::Value = toml::from_str(toml_str).unwrap(); let json_val: Value = serde_json::to_value(toml_val).unwrap(); let mut vars = Vec::new(); - flatten(&json_val, "", 0, "", &mut vars); + flatten(&json_val, Vec::new(), Some("".to_string()), 0, &mut vars); let mut root = Value::Object(Map::new()); for var in vars { @@ -509,7 +507,6 @@ enabled = true } } - // Convert back to TOML let toml_root: toml::Value = serde_json::from_value(root).unwrap(); let unflattened_toml = toml::to_string(&toml_root).unwrap(); @@ -525,7 +522,7 @@ enabled = true let json_val = xml_to_json(xml_str).unwrap(); let mut vars = Vec::new(); - flatten(&json_val, "", 0, "", &mut vars); + flatten(&json_val, Vec::new(), Some("".to_string()), 0, &mut vars); let mut root = Value::Object(Map::new()); for var in vars { @@ -534,7 +531,6 @@ enabled = true } } - println!("Reconstructed root: {:?}", root); let unflattened_xml = json_to_xml(&root); assert!(unflattened_xml.contains("8080")); diff --git a/src/format/ini.rs b/src/format/ini.rs index 29583d1..6888dc8 100644 --- a/src/format/ini.rs +++ b/src/format/ini.rs @@ -1,4 +1,4 @@ -use super::{ConfigItem, FormatHandler, ItemStatus, ValueType}; +use super::{ConfigItem, FormatHandler, ItemStatus, ValueType, PathSegment}; use ini::Ini; use std::path::Path; @@ -15,7 +15,7 @@ impl FormatHandler for IniHandler { if !section_name.is_empty() { vars.push(ConfigItem { key: section_name.to_string(), - path: section_name.to_string(), + path: vec![PathSegment::Key(section_name.to_string())], value: None, template_value: None, default_value: None, @@ -28,9 +28,9 @@ impl FormatHandler for IniHandler { for (key, value) in prop { let path = if section_name.is_empty() { - key.to_string() + vec![PathSegment::Key(key.to_string())] } else { - format!("{}.{}", section_name, key) + vec![PathSegment::Key(section_name.to_string()), PathSegment::Key(key.to_string())] }; vars.push(ConfigItem { @@ -58,11 +58,14 @@ impl FormatHandler for IniHandler { .or(var.template_value.as_deref()) .unwrap_or(""); - if let Some((section, key)) = var.path.split_once('.') { - conf.with_section(Some(section)).set(key, val); - } else { - conf.with_section(None::).set(&var.path, val); - } + if var.path.len() == 2 { + if let (PathSegment::Key(section), PathSegment::Key(key)) = (&var.path[0], &var.path[1]) { + conf.with_section(Some(section)).set(key, val); + } + } else if var.path.len() == 1 + && let PathSegment::Key(key) = &var.path[0] { + conf.with_section(None::).set(key, val); + } } } conf.write_to_file(path)?; @@ -84,8 +87,8 @@ mod tests { let handler = IniHandler; let vars = handler.parse(file.path()).unwrap(); - assert!(vars.iter().any(|v| v.path == "server" && v.is_group)); - assert!(vars.iter().any(|v| v.path == "server.port" && v.value.as_deref() == Some("8080"))); - assert!(vars.iter().any(|v| v.path == "database.host" && v.value.as_deref() == Some("localhost"))); + assert!(vars.iter().any(|v| v.path_string() == "server" && v.is_group)); + assert!(vars.iter().any(|v| v.path_string() == "server.port" && v.value.as_deref() == Some("8080"))); + assert!(vars.iter().any(|v| v.path_string() == "database.host" && v.value.as_deref() == Some("localhost"))); } } diff --git a/src/format/mod.rs b/src/format/mod.rs index a76c706..afd44c3 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -20,10 +20,25 @@ pub enum ValueType { Null, } +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum PathSegment { + Key(String), + Index(usize), +} + +impl std::fmt::Display for PathSegment { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + PathSegment::Key(k) => write!(f, "{}", k), + PathSegment::Index(i) => write!(f, "[{}]", i), + } + } +} + #[derive(Debug, Clone)] pub struct ConfigItem { pub key: String, - pub path: String, + pub path: Vec, pub value: Option, pub template_value: Option, pub default_value: Option, @@ -33,6 +48,25 @@ pub struct ConfigItem { pub value_type: ValueType, } +impl ConfigItem { + pub fn path_string(&self) -> String { + let mut s = String::new(); + for (i, segment) in self.path.iter().enumerate() { + match segment { + PathSegment::Key(k) => { + if i > 0 { + s.push('.'); + } + s.push_str(k); + } + PathSegment::Index(idx) => { + s.push_str(&format!("[{}]", idx)); + } + } + } + s + } +} #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum FormatType { Env, diff --git a/src/format/properties.rs b/src/format/properties.rs index a53567d..4ca0b7d 100644 --- a/src/format/properties.rs +++ b/src/format/properties.rs @@ -1,4 +1,4 @@ -use super::{ConfigItem, FormatHandler, ItemStatus, ValueType}; +use super::{ConfigItem, FormatHandler, ItemStatus, ValueType, PathSegment}; use java_properties::{LineContent, PropertiesIter, PropertiesWriter}; use std::fs::File; use std::io::{BufReader, BufWriter}; @@ -21,13 +21,10 @@ impl FormatHandler for PropertiesHandler { if let LineContent::KVPair(path, value) = line.consume_content() { // Add groups based on dot notation let parts: Vec<&str> = path.split('.').collect(); - let mut current_path = String::new(); + let mut current_path = Vec::new(); for (i, part) in parts.iter().enumerate().take(parts.len().saturating_sub(1)) { - if !current_path.is_empty() { - current_path.push('.'); - } - current_path.push_str(part); + current_path.push(PathSegment::Key(part.to_string())); if groups.insert(current_path.clone()) { vars.push(ConfigItem { @@ -44,9 +41,13 @@ impl FormatHandler for PropertiesHandler { } } + let mut final_path = current_path.clone(); + let last_key = parts.last().unwrap_or(&"").to_string(); + final_path.push(PathSegment::Key(last_key.clone())); + vars.push(ConfigItem { - key: parts.last().unwrap_or(&"").to_string(), - path: path.clone(), + key: last_key, + path: final_path, value: Some(value.clone()), template_value: Some(value.clone()), default_value: Some(value.clone()), @@ -72,7 +73,7 @@ impl FormatHandler for PropertiesHandler { let val = var.value.as_deref() .or(var.template_value.as_deref()) .unwrap_or(""); - prop_writer.write(&var.path, val)?; + prop_writer.write(&var.path_string(), val)?; } } @@ -95,7 +96,7 @@ mod tests { let handler = PropertiesHandler; let vars = handler.parse(file.path()).unwrap(); - assert!(vars.iter().any(|v| v.path == "server" && v.is_group)); - assert!(vars.iter().any(|v| v.path == "server.port" && v.value.as_deref() == Some("8080"))); + assert!(vars.iter().any(|v| v.path_string() == "server" && v.is_group)); + assert!(vars.iter().any(|v| v.path_string() == "server.port" && v.value.as_deref() == Some("8080"))); } } diff --git a/src/ui.rs b/src/ui.rs index 629e26b..6433510 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -189,9 +189,9 @@ pub fn draw(f: &mut Frame, app: &mut App, config: &Config) { if let Some(var) = current_var { if var.is_group { - input_title = format!(" Group: {} ", var.path); + input_title = format!(" Group: {} ", var.path_string()); } else { - input_title = format!(" Editing: {} ", var.path); + input_title = format!(" Editing: {} ", var.path_string()); if let Some(t_val) = &var.template_value { extra_info = format!(" [Template: {}]", t_val); }