Reviewed-on: #18
mould
mould is a modern Terminal User Interface (TUI) tool designed for interactively generating and editing configuration files from templates. Whether you are setting up a .env file from an example, creating a docker-compose.override.yml, or editing nested JSON, YAML, TOML, XML, INI, or Properties configurations, mould provides a fast, Vim-inspired workflow to get your environment ready.
Features
- Universal Format Support: Read, edit, and write
.env,JSON,YAML,TOML,XML,INI, andPropertiesfiles seamlessly. - Tree View Navigation: Edit nested data structures in a beautiful, depth-colored tree view.
- Smart Template Discovery: Rule-based resolver automatically discovers relationships (e.g.,
.env.examplevs.env,config.template.propertiesvsconfig.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.
- 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 withi, search with/, and save with:w. - Modern UI: A polished, rounded interface featuring a semantic Catppuccin Mocha palette with support for terminal transparency.
- Highly Configurable: Customize keybindings and semantic themes via a simple TOML user configuration.
- Neovim Integration: Comes with a built-in Neovim plugin for seamless in-editor configuration management.
Installation
1. CLI Application
Ensure you have Rust and Cargo installed, then run:
# Install directly from the local path
cargo install --path .
Alternatively, you can build from source:
git clone https://git.narl.io/nvrl/mould-rs
cd mould-rs
cargo build --release
# The executable will be located in target/release/mould
2. Neovim Plugin
If you want to use mould directly inside Neovim, the repository includes a built-in Lua plugin that opens mould in a floating terminal buffer and synchronizes the file upon exit.
Using mini.deps:
add({
source = 'https://git.narl.io/nvrl/mould-rs',
checkout = 'main',
})
Usage
Provide an input template file to start editing. mould is smart enough to figure out if it's looking at a template or an active file, and will search for its counterpart to provide live diffing.
mould .env.example
mould docker-compose.yml
mould config.template.json -o config.json
If you just run mould in a directory, it will automatically look for common template patterns like .env.example or docker-compose.yml.
Inside Neovim
Open any configuration file in Neovim and run :Mould. It will launch a floating window targeting your active buffer.
Keybindings (Default)
-
Normal Mode
j/Down: Move selection downk/Up: Move selection upgg: Jump to the topG: Jump to the bottomi: 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 changeU: Redo the reverted changea: Add missing value from template to active config/: Search for configuration keysn/N: Jump to next / previous search match:worEnter: Save the current configuration:qorq: Quit the application:wq: Save and quitEsc: Clear current command prompt
-
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.
Configuration
mould can be configured using a config.toml file located in your user configuration directory:
- Linux/macOS:
~/.config/mould/config.toml - Windows:
%AppData%\mould\config.toml
Example configuration:
[theme]
transparent = false
bg_normal = "#1e1e2e"
bg_highlight = "#89b4fa"
bg_active = "#a6e3a1"
bg_search = "#cba6f7"
fg_normal = "#cdd6f4"
fg_dimmed = "#6c7086"
fg_highlight = "#1e1e2e"
fg_warning = "#f38ba8"
fg_modified = "#fab387"
fg_accent = "#b4befe"
border_normal = "#45475a"
border_active = "#a6e3a1"
tree_depth_1 = "#b4befe"
tree_depth_2 = "#cba6f7"
tree_depth_3 = "#89b4fa"
tree_depth_4 = "#fab387"
[keybinds]
down = "j"
up = "k"
edit = "i"
edit_append = "a"
edit_substitute = "s"
save = ":w"
quit = ":q"
normal_mode = "Esc"
search = "/"
next_match = "n"
previous_match = "N"
jump_top = "gg"
jump_bottom = "G"
append_item = "o"
prepend_item = "O"
delete_item = "dd"
undo = "u"
redo = "U"
rename = "r"
append_group = "alt+o"
prepend_group = "alt+O"
toggle_group = "t"
Development & Architecture
mould is written in Rust and architected to decouple the file format parsing from the UI representation. This allows the TUI to render complex, nested configuration files in a unified tree-view.
Core Architectural Principles:
- 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. - Non-linear Undo/Redo: A robust undo tree allows users to revert and re-apply changes across complex branching edit histories.
Key Components
-
State Management & Undo Tree (
src/app.rs&src/undo.rs)- The central state is maintained in the
Appstruct, which tracks the currently loaded configuration variables, application modes, and user input buffer. - It integrates an UndoTree, providing non-linear, branching history tracking for complex edits (additions, deletions, nested renaming). Each significant state change (
save_undo_state) pushes a snapshot to this tree.
- The central state is maintained in the
-
Unified Data Model (
src/format/mod.rs)- Regardless of the underlying file format (JSON, YAML, .env, etc.), all data is translated into a flattened
Vec<ConfigItem>. - A
ConfigItemrepresents a single configuration entry. It contains:key: The display key (e.g.,portor[0]).path: AVec<PathSegment>(composed ofPathSegment::Key(String)for object keys andPathSegment::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.value_type: (ValueType::String,Number,Bool,Null) to ensure type preservation during writes.
- Regardless of the underlying file format (JSON, YAML, .env, etc.), all data is translated into a flattened
-
Format Handlers (
src/format/*)- Each file format has a dedicated handler (
EnvHandler,IniHandler,HierarchicalHandler,PropertiesHandler) implementing theFormatHandlertrait. HierarchicalHandler: A generalized processor leveragingserde_json::Valueas an intermediary layer to parse and write nestedJSON,YAML,TOML, and evenXML(viaquick-xmltransposition). This allows complex structures to be flattened for editing and then re-nested accurately.- These handlers are responsible for translating between the file's native format and the
Vec<ConfigItem>internal representation.
- Each file format has a dedicated handler (
-
Template Resolver (
src/resolver.rs)- Automatically determines template-active file pairings without explicit user instruction.
- Uses hardcoded exact rules (e.g.,
compose.yml->compose.override.yml) and generic fallback rules to strip.exampleor.templatesuffixes to find target output files dynamically.
-
Terminal UI & Event Loop (
src/ui.rs&src/runner.rs)- UI Rendering (
src/ui.rs): Powered by theratatuilibrary. 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 bycrossterm. It intercepts raw keyboard events, resolves multi-key sequences (likeddorgg), delegates character input to thetui-inputbackend during active editing, and dispatches actions to mutate theAppstate. It includes logic to prevent "one-key-behind" issues and manage complex keybindings likealt+o.
- UI Rendering (
-
Neovim Plugin (
lua/mould/init.lua)- Implements a Lua wrapper that calculates terminal geometries and launches the CLI
mouldbinary inside an ephemeral, floating terminal buffer, ensuring automatic Neovimchecktimesynchronization uponmould's exit.
- Implements a Lua wrapper that calculates terminal geometries and launches the CLI
Development Process Highlights:
- Iterative Refinement: Features like key renaming, group creation, and advanced undo/redo were developed iteratively, responding to user feedback and progressively enhancing the core data model and interaction logic.
- Robust Error Handling: Key functions (
commit_input,enter_insert_key) include collision detection and validation to ensure data integrity during user modifications. - Modality-driven Design: The introduction of
InsertKeymode and careful handling ofInsertVariantdemonstrates a commitment to a precise, context-aware user experience, minimizing ambiguity during editing. - Debugging and Performance: Issues like UI "hangs" were traced to subtle interactions in key event processing and input buffer management, leading to refactored key sequence logic for improved responsiveness.
License
This project is licensed under the MIT License. See the LICENSE file for details.