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
@@ -5,10 +5,12 @@
|
||||
## Features
|
||||
|
||||
- **Universal Format Support**: Read, edit, and write `.env`, `JSON`, `YAML`, `TOML`, `XML`, `INI`, and `Properties` files seamlessly.
|
||||
- **Tree View Navigation**: Edit nested data structures in a beautiful, depth-colored tree view.
|
||||
- **Tree View Navigation**: Edit nested data structures in a depth-colored tree view, with foldable groups so a large `docker-compose.yml` stays readable.
|
||||
- **Smart Template Discovery**: Rule-based resolver automatically discovers relationships (e.g., `.env.example` vs `.env`, `config.template.properties` vs `config.properties`) and highlights missing keys.
|
||||
- **Strict Type Preservation**: Intelligently preserves data types (integers, booleans, strings) during edit-save cycles, ensuring your configuration stays valid.
|
||||
- **Add Missing Keys**: Instantly pull missing keys and their default values from your template into your active configuration with a single keystroke.
|
||||
- **Nothing Written Behind Your Back**: Keys that exist only in the template are marked `(missing)` and stay out of the output until you pull them in with `<Space>` (or `A` for all of them).
|
||||
- **Comment Preservation**: `.env` comments and blank lines survive a save, so the documentation in your `.env.example` is not the price of using the tool.
|
||||
- **Discoverable**: A header names the file being written and the template it is compared against; `?` opens the full keybinding reference, built from *your* config.
|
||||
- **Advanced Undo/Redo Engine**: Features a tree-based undo/redo history that ensures you never lose changes during complex branching edits. It properly tracks all modifications, including nested renames and item additions/deletions.
|
||||
- **Vim-inspired Workflow**: Navigate with `j`/`k`, `gg`/`G`, edit with `i`, search with `/`, and save with `:w`.
|
||||
- **Modern UI**: A polished, rounded interface featuring a semantic Catppuccin Mocha palette with support for terminal transparency.
|
||||
@@ -69,36 +71,59 @@ Open any configuration file in Neovim and run `:Mould`. It will launch a floatin
|
||||
|
||||
### Keybindings (Default)
|
||||
|
||||
- **Normal Mode**
|
||||
- `j` / `Down`: Move selection down
|
||||
- `k` / `Up`: Move selection up
|
||||
- `gg`: Jump to the top
|
||||
- `G`: Jump to the bottom
|
||||
- `i`: Edit value (cursor at start). If selected is a group, enters rename mode.
|
||||
- `a`: Edit value (cursor at end)
|
||||
- `s` / `S`: Substitute value (clear and edit)
|
||||
- `r`: Rename the current key or group. (Cannot rename array indices).
|
||||
- `o`: Append a new item (as a sibling or array element). Enters rename mode for non-array items.
|
||||
- `O`: Prepend a new item (as a sibling or array element). Enters rename mode for non-array items.
|
||||
- `alt+o`: Append a new group/object as a child. Enters rename mode for the new group.
|
||||
- `alt+O`: Prepend a new group/object as a child. Enters rename mode for the new group.
|
||||
- `t`: Toggle the selected item between a group/object and a standard variable.
|
||||
- `dd`: Delete the currently selected variable or group (removes all nested children).
|
||||
- `u`: Undo the last change
|
||||
- `U`: Redo the reverted change
|
||||
- `a`: Add missing value from template to active config
|
||||
- `/`: Search for configuration keys
|
||||
- `n` / `N`: Jump to next / previous search match
|
||||
- `:w` or `Enter`: Save the current configuration
|
||||
- `:q` or `q`: Quit the application
|
||||
- `:wq`: Save and quit
|
||||
- `Esc`: Clear current command prompt
|
||||
Press `?` inside `mould` for the same list, rendered from your own configuration.
|
||||
|
||||
- **Insert / Rename Modes**
|
||||
- Type your value/key string.
|
||||
- Arrow keys: Navigate within the input field
|
||||
- `Enter`: Commit the value/key and return to Normal Mode. If renaming, checks for key collisions.
|
||||
- `Esc`: Cancel edits and return to Normal Mode. Reverts changes to the current field.
|
||||
**Navigate**
|
||||
|
||||
- `j` / `k` (or arrow keys): Move the selection
|
||||
- `gg` / `G`: Jump to the top / bottom
|
||||
- `Tab` or `za`: Fold or unfold the selected group
|
||||
- `zM` / `zR`: Fold / unfold everything
|
||||
- `/`: Search over keys *and* values. `Enter` keeps the query for `n` / `N`; `Esc` cancels and restores the cursor
|
||||
- `n` / `N`: Jump to the next / previous match
|
||||
|
||||
**Edit**
|
||||
|
||||
- `i`: Edit the value with the cursor at the start. On a group, renames it instead
|
||||
- `a`: Edit the value with the cursor at the end
|
||||
- `s` / `S`: Replace the value (clears it first)
|
||||
- `r`: Rename the current key or group (array indices cannot be renamed)
|
||||
|
||||
**Structure**
|
||||
|
||||
- `o` / `O`: Add an item below / above (as a sibling, or as an array element)
|
||||
- `alt+o` / `alt+O`: Add a child group below / above
|
||||
- `t`: Toggle the selected item between a group and a plain value
|
||||
- `dd`: Delete the selected item and everything nested under it
|
||||
|
||||
**Template**
|
||||
|
||||
- `Space`: Take the template default for the selected `(missing)` entry
|
||||
- `A`: Take every missing template default at once
|
||||
|
||||
**History**
|
||||
|
||||
- `u` / `U`: Undo / redo
|
||||
- `g-` / `g+`: Switch between alternative branches of the undo tree
|
||||
|
||||
**Session**
|
||||
|
||||
- `?`: Toggle the keybinding reference
|
||||
- `:`: Open the command prompt
|
||||
- `q`: Quit (refused while there are unsaved changes)
|
||||
|
||||
**Commands**
|
||||
|
||||
- `:w`: Write the output file
|
||||
- `:w <path>`: Write somewhere else, and keep writing there
|
||||
- `:q`: Quit; `:q!` quits and discards changes
|
||||
- `:wq` or `:x`: Write and quit
|
||||
|
||||
**Insert / Rename Modes**
|
||||
|
||||
- Type your value or key. Arrow keys navigate within the field
|
||||
- `Enter`: Commit and return to Normal mode. Renames are checked for collisions
|
||||
- `Esc`: Cancel. An item you just created and never named is removed again
|
||||
|
||||
---
|
||||
|
||||
@@ -131,14 +156,17 @@ tree_depth_3 = "#89b4fa"
|
||||
tree_depth_4 = "#fab387"
|
||||
|
||||
[keybinds]
|
||||
# A binding may list alternatives separated by commas.
|
||||
# Named keys are written in angle brackets: <Space>, <Tab>.
|
||||
down = "j"
|
||||
up = "k"
|
||||
edit = "i"
|
||||
edit_append = "a"
|
||||
edit_substitute = "s"
|
||||
edit_substitute = "s,S"
|
||||
save = ":w"
|
||||
quit = ":q"
|
||||
normal_mode = "Esc"
|
||||
quit_key = "q"
|
||||
command = ":"
|
||||
search = "/"
|
||||
next_match = "n"
|
||||
previous_match = "N"
|
||||
@@ -149,10 +177,18 @@ prepend_item = "O"
|
||||
delete_item = "dd"
|
||||
undo = "u"
|
||||
redo = "U"
|
||||
undo_branch_prev = "g-"
|
||||
undo_branch_next = "g+"
|
||||
rename = "r"
|
||||
append_group = "alt+o"
|
||||
prepend_group = "alt+O"
|
||||
toggle_group = "t"
|
||||
add_missing = "<Space>"
|
||||
accept_all = "A"
|
||||
toggle_fold = "<Tab>,za"
|
||||
fold_all = "zM"
|
||||
unfold_all = "zR"
|
||||
help = "?"
|
||||
```
|
||||
|
||||
---
|
||||
@@ -165,7 +201,8 @@ toggle_group = "t"
|
||||
|
||||
- **Separation of Concerns**: Clear boundaries between UI rendering, application state, input handling, and file format logic.
|
||||
- **Unified Data Model**: All parsed configuration data is normalized into a single `Vec<ConfigItem>` structure, simplifying application logic across different file types.
|
||||
- **Vim-inspired Modality**: The application operates in distinct modes (`Normal`, `Insert`, `InsertKey`, `Search`), each with specific keybinding behaviors, enabling efficient interaction.
|
||||
- **Vim-inspired Modality**: The application operates in distinct modes (`Normal`, `Insert`, `InsertKey`, `Search`, `Help`), each with specific keybinding behaviors, enabling efficient interaction.
|
||||
- **One Keybinding Table**: Every Normal-mode key resolves through `KeybindsConfig::resolved()`, so a rebind takes effect in the dispatcher, the status-bar hints and the help overlay at once. `KeybindsConfig::conflicts()` reports any key bound to two actions rather than letting one silently shadow the other.
|
||||
- **Non-linear Undo/Redo**: A robust undo tree allows users to revert and re-apply changes across complex branching edit histories.
|
||||
|
||||
### Key Components
|
||||
@@ -181,8 +218,9 @@ toggle_group = "t"
|
||||
* `path`: A `Vec<PathSegment>` (composed of `PathSegment::Key(String)` for object keys and `PathSegment::Index(usize)` for array indices) that defines its full hierarchical location.
|
||||
* `value`: `Option<String>` holding the actual value.
|
||||
* `is_group`: A boolean indicating if this item is a structural node (object or array).
|
||||
* `status`: (`ItemStatus::Present`, `MissingFromActive`, `Modified`) reflecting comparison with a template.
|
||||
* `status`: (`ItemStatus::Present`, `MissingFromActive`, `Overridden`) reflecting comparison with a template. Whether *you* edited an entry is tracked separately by `dirty`, so a config that legitimately differs from its example file does not render as if every line had just been touched.
|
||||
* `value_type`: (`ValueType::String`, `Number`, `Bool`, `Null`) to ensure type preservation during writes.
|
||||
* `collapsed` / `leading_trivia`: fold state, and the comment lines that preceded the entry in the source file.
|
||||
|
||||
3. **Format Handlers (`src/format/*`)**
|
||||
* Each file format has a dedicated handler (`EnvHandler`, `IniHandler`, `HierarchicalHandler`, `PropertiesHandler`) implementing the `FormatHandler` trait.
|
||||
@@ -195,7 +233,7 @@ toggle_group = "t"
|
||||
|
||||
5. **Terminal UI & Event Loop (`src/ui.rs` & `src/runner.rs`)**
|
||||
* **UI Rendering (`src/ui.rs`)**: Powered by the `ratatui` library. Renders a flexible layout consisting of a styled hierarchical list, an active input field for editing, and a dynamic status bar.
|
||||
* **Event Runner (`src/runner.rs`)**: Powered by `crossterm`. It intercepts raw keyboard events, resolves multi-key sequences (like `dd` or `gg`), delegates character input to the `tui-input` backend during active editing, and dispatches actions to mutate the `App` state. It includes logic to prevent "one-key-behind" issues and manage complex keybindings like `alt+o`.
|
||||
* **Event Runner (`src/runner.rs`)**: Powered by `crossterm`. It polls for events rather than blocking, so terminal resizes and expiring status messages repaint without waiting for a keystroke. It resolves multi-key sequences (like `dd` or `gg`), delegates character input to the `tui-input` backend during active editing, and dispatches actions to mutate the `App` state.
|
||||
|
||||
6. **Neovim Plugin (`lua/mould/init.lua`)**
|
||||
* Implements a Lua wrapper that calculates terminal geometries and launches the CLI `mould` binary inside an ephemeral, floating terminal buffer, ensuring automatic Neovim `checktime` synchronization upon `mould`'s exit.
|
||||
|
||||
Reference in new issue
Block a user