fix: make the template workflow real, and the UI legible
Version Check / check-version (pull_request) Successful in 10s
Version Check / check-version (pull_request) Successful in 10s
The headline flow — fill a config from its template — did not work. Three bugs, each individually enough to make the tool feel unfinished: - `add_missing` was unreachable. `find_binding` returned the first match in a linear array and `edit_append` was also bound to `a`, 17 entries earlier. Bindings now resolve through one table built from the config, and `KeybindsConfig::conflicts()` reports any key bound twice instead of letting one silently shadow the other. `add_missing` moves to `<Space>`, with `A` to take every missing default at once. - Item status was ignored on write. Every writer fell back to `template_value`, so "(missing)" keys landed in the output regardless and saving was close to a copy. Unaccepted entries are now omitted. - Parse errors were swallowed, then the good file was overwritten. A malformed target opened as an empty editor and `:w` truncated it. Parse failures are now fatal, reported with context, before the TUI starts. Also: - Split "differs from the template" (ItemStatus::Overridden) from "you edited this" (ConfigItem::dirty). A real config differs from its example on nearly every line, so the old Modified flag painted the whole list orange and carried no signal. - Splice template-only entries in at their natural position instead of appending them, so a missing nested key no longer strands itself at the bottom of the tree at depth 3. - Preserve .env comments and blank lines across a save. - Header naming the output file and the template it is compared against, plus missing/changed counts. Previously nothing on screen said which files were in play or where `:w` would write. - `?` opens a two-column keybinding reference built from the user's own config, scrollable on short terminals. The status bar drops to a few contextual hints; the old one joined 11+ hints into an unwrapped line that truncated at 80 columns. - Foldable groups, which the `+ ` prefix had always implied. - Search covers values as well as keys, reports `match n/m`, and `Esc` restores the pre-search cursor. - Quitting with unsaved changes is refused; added `:q!`, `:x`, `:w <path>`. - Status messages expire after 4s instead of permanently replacing hints. - Cancelling a rename on a just-created item removes it again. - Editing a Number field with text demotes the type instead of failing obscurely at write time. - Undo-tree branches are reachable via `g-`/`g+` and reported on undo. - Poll instead of blocking, so resizes repaint; drop the unused mouse capture that was costing terminal text selection for nothing. - `--format` accepts xml, ini and properties, which were already supported. 36 tests, up from 13. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C5mrbEFmwv2A4cdzbcSiS1
This commit is contained in:
1 parent
2e974e0503
commit
e8634c17a2
16 files changed
+2218
-915
No files matched your search
+19
-51
@@ -1,4 +1,4 @@
|
||||
use super::{ConfigItem, FormatHandler, ItemStatus, ValueType, PathSegment};
|
||||
use super::{ConfigItem, FormatHandler, ValueType, PathSegment};
|
||||
use java_properties::{LineContent, PropertiesIter, PropertiesWriter};
|
||||
use std::fs::File;
|
||||
use std::io::{BufReader, BufWriter};
|
||||
@@ -27,17 +27,7 @@ impl FormatHandler for PropertiesHandler {
|
||||
current_path.push(PathSegment::Key(part.to_string()));
|
||||
|
||||
if groups.insert(current_path.clone()) {
|
||||
vars.push(ConfigItem {
|
||||
key: part.to_string(),
|
||||
path: current_path.clone(),
|
||||
value: None,
|
||||
template_value: None,
|
||||
default_value: None,
|
||||
depth: i,
|
||||
is_group: true,
|
||||
status: ItemStatus::Present,
|
||||
value_type: ValueType::Null,
|
||||
});
|
||||
vars.push(ConfigItem::group(*part, current_path.clone(), i));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,17 +35,13 @@ impl FormatHandler for PropertiesHandler {
|
||||
let last_key = parts.last().unwrap_or(&"").to_string();
|
||||
final_path.push(PathSegment::Key(last_key.clone()));
|
||||
|
||||
vars.push(ConfigItem {
|
||||
key: last_key,
|
||||
path: final_path,
|
||||
value: Some(value.clone()),
|
||||
template_value: Some(value.clone()),
|
||||
default_value: Some(value.clone()),
|
||||
depth: parts.len().saturating_sub(1),
|
||||
is_group: false,
|
||||
status: ItemStatus::Present,
|
||||
value_type: ValueType::String,
|
||||
});
|
||||
vars.push(ConfigItem::leaf(
|
||||
last_key,
|
||||
final_path,
|
||||
value,
|
||||
parts.len().saturating_sub(1),
|
||||
ValueType::String,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,11 +55,8 @@ impl FormatHandler for PropertiesHandler {
|
||||
let mut prop_writer = PropertiesWriter::new(writer);
|
||||
|
||||
for var in vars {
|
||||
if !var.is_group {
|
||||
let val = var.value.as_deref()
|
||||
.or(var.template_value.as_deref())
|
||||
.unwrap_or("");
|
||||
prop_writer.write(&var.path_string(), val)?;
|
||||
if var.should_write() {
|
||||
prop_writer.write(&var.path_string(), var.write_value())?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,34 +69,19 @@ impl FormatHandler for PropertiesHandler {
|
||||
mod tests {
|
||||
use super::*;
|
||||
use tempfile::NamedTempFile;
|
||||
use std::io::Write;
|
||||
|
||||
#[test]
|
||||
fn test_group_rename_write() {
|
||||
let handler = PropertiesHandler;
|
||||
let mut vars = vec![
|
||||
ConfigItem {
|
||||
key: "server".to_string(),
|
||||
path: vec![PathSegment::Key("server".to_string())],
|
||||
value: None,
|
||||
template_value: None,
|
||||
default_value: None,
|
||||
depth: 0,
|
||||
is_group: true,
|
||||
status: ItemStatus::Present,
|
||||
value_type: ValueType::Null,
|
||||
},
|
||||
ConfigItem {
|
||||
key: "port".to_string(),
|
||||
path: vec![PathSegment::Key("server".to_string()), PathSegment::Key("port".to_string())],
|
||||
value: Some("8080".to_string()),
|
||||
template_value: Some("8080".to_string()),
|
||||
default_value: Some("8080".to_string()),
|
||||
depth: 1,
|
||||
is_group: false,
|
||||
status: ItemStatus::Present,
|
||||
value_type: ValueType::String,
|
||||
}
|
||||
ConfigItem::group("server", vec![PathSegment::Key("server".to_string())], 0),
|
||||
ConfigItem::leaf(
|
||||
"port",
|
||||
vec![PathSegment::Key("server".to_string()), PathSegment::Key("port".to_string())],
|
||||
"8080",
|
||||
1,
|
||||
ValueType::String,
|
||||
),
|
||||
];
|
||||
|
||||
// Rename "server" to "srv"
|
||||
|
||||
Reference in new issue
Block a user