Skip to content

About

Edit chezmoi-managed files in Neovim with template-aware highlight, diagnostic, preview, diff and merge.

Topics

Resources

Stars

4 stars

Watchers

0 watching

Forks

Repository files navigation

vimoi.nvim

Edit chezmoi templates in Neovim, preview their output, and bring static target changes back into source while preserving expressions.

简体中文 · Help

This project is in alpha. APIs and behavior may change.

Demos

Template-aware diff. Compare rendered output with the actual file, transfer hunks in both directions, and choose insertion positions. MP4

Template diff: synchronized scrolling, hunk transfer, and insertion choices

Static write-back. Bring target changes into the template in bulk while preserving expressions. MP4

Write static changes back to source while preserving template expressions

Live preview. Edit unsaved templates with synchronized scrolling, branch changes, undo, and error recovery. MP4

Live template preview: static edits, branch changes, and undo

Installation and configuration

Requires Neovim 0.11+ and chezmoi 2.x. The gotmpl Tree-sitter parser is recommended for full template highlighting, precise syntax-aware completion, template-reference navigation, and template block matching. Without it, an experimental fallback still supports preview, diff, static write-back, and basic completion: action detection uses line-local regular expressions and variable completion may include names from unrelated scopes. Chezmoi still validates template expressions. Host-language parsers are optional; rendered highlighting uses Vim syntax when no host parser is available. blink.cmp is optional unless you want Vimoi's completion source. Tested on Linux.

With lazy.nvim:

{
  "dirichy/vimoi.nvim",
  lazy = false,
  opts = {},
}

With another plugin manager, install the plugin and call require("vimoi").setup(). Set vim.g.mapleader first, then run :checkhealth vimoi after installation.

The defaults work without extra options. For example, customize lazy.nvim's opts:

opts = {
  split = "horizontal", -- Stacked windows
  keymaps = { readd = false }, -- Disable the default ReAdd mapping
}

See :help vimoi-setup for all options. Configure the plugin once at startup; restart Neovim after changing options.

Usage

Open a chezmoi-managed source or target, then use these commands and default mappings:

Command Key Purpose
:VimoiFiles <leader>cf Pick a managed file and open its source
:VimoiToggle <leader>ct Switch source / target, preserving unsaved edits
:VimoiPreview <leader>cp Preview the current template as you type, without saving
:VimoiDiff <leader>cd Compare rendered output with the actual target
:VimoiReAdd <leader>ca Bring target changes back into source in bulk
:VimoiCancel <leader>cx Cancel slow tasks

:VimoiFiles lists managed files and symlinks through vim.ui.select, reusing your configured picker UI. Search by target paths such as .config/nvim/init.lua; select an entry to edit its source. Use :VimoiToggle to switch to its target. To search by source names instead, set files = { path = "source" } in setup() (default: "target"). The list is refreshed each time and respects chezmoi's ignore rules. Cancel a slow query with :VimoiCancel.

Path arguments are also available; see :help vimoi-commands. Preview supports unnamed buffers and template fragments. Failed renders retain the previous output and mark it stale. Close either window to end a diff/preview.

Compare and write back

Use [c / ]c to navigate changes and zo / zc to show or hide context. In a template diff:

Current window do: get into this window dp: put into the other window
Source template Write target changes back to the template Put rendered output into the target
Target Get rendered template output Write target changes back to the template

Use the plugin's do / dp mappings or :VimoiDiffGet / :VimoiDiffPut; native :diffget / :diffput copy template source text. Plain files retain native do / dp behavior.

:VimoiReAdd merges identifiable static changes in bulk while preserving expressions; ambiguous positions prompt for a choice. Dynamic output needs manual source edits. For plain text files, it replaces source content with the entire current target buffer, including unsaved edits. Use u to undo.

Write-back changes buffers without saving or applying. Review and save with :write, then run chezmoi apply to deploy.

Plain source filetype detection, native diff, and write-back of unsaved target text

Plain file recording (MP4)

Highlighting and diagnostics

Template and host-language highlighting are enabled by default; inactive branches use comment colors. Rendered mode reads Tree-sitter or Vim syntax colors from complete output and maps them onto the template. Set highlight.mode = "injection" to highlight host fragments directly, or "disabled" to keep only template highlighting. Customize action backgrounds with highlight.template_bg. In injection mode, missing host parsers use Vim syntax directly on the template as a best-effort fallback.

Rendered host colors, inactive branches, and template action backgrounds

Full-resolution highlighting demo (MP4)

Injection highlighting (MP4) · Host highlighting disabled (MP4)

Template errors are checked as you edit. Host diagnostics reuse your enabled Neovim LSP or nvim-lint configuration, checking rendered output and mapping errors back to source. nvim-lint supports stdin linters only. Set diagnostics.host = false for template diagnostics only. Set render.debounce to change the default 500ms wait for structural or rendering-context changes; literal-only edits reuse the cached skeleton immediately. See :help vimoi-highlighting and :help vimoi-diagnostics.

Template errors and host diagnostics projected back to source, followed by repair

Full-resolution diagnostics demo (MP4)

Completion

Register the source in your blink.cmp configuration:

require("blink.cmp").setup({
  completion = {
    accept = {
      auto_brackets = {
        blocked_filetypes = { "gotmpl" },
      },
    },
  },
  sources = {
    default = { "lsp", "path", "snippets", "buffer", "vimoi" },
    providers = {
      vimoi = {
        name = "Vimoi",
        module = "vimoi.blink",
        score_offset = 100,
      },
    },
  },
})

Completes functions, data fields, local variables, paths, and block snippets. With no gotmpl parser, completion falls back to line-local action detection and buffer-wide variable-name matching, so the menu may contain extra candidates. blocked_filetypes prevents automatic parentheses on template function completions.

Control value masking in vimoi's setup options:

require("vimoi").setup({
  completion = {
    mask = function(key)
      return key:lower():match("password") ~= nil or key:lower():match("token") ~= nil
    end,
  },
})

completion.mask defaults to "hint": all values are masked, with a short See :help vimoi-completion-mask hint. Set true to mask without the hint, or false to show all values. A function receives the full field path without a leading dot (for example account.token); returning true masks that value without the hint. Masked values use [ masked], including in object previews. Masking a parent also masks all its descendants. Lists use one-based paths such as accounts.1.token. Previews are bounded and truncated; field-name masking only covers paths matched by your function. The cached data view replaces runtime-only fields before exposing it to consumers. Completion makes a private copy and fills fields that can be determined from the current managed buffer. Value documentation comes from that sanitized view; runtime fields such as .chezmoi.command, .chezmoi.args, .chezmoi.sourceFile, and .chezmoi.targetFile are shown as [runtime value] because they can differ during template execution.

Real completion menus for data, functions, variables, paths, and block snippets

Full-resolution completion demo (MP4)

Path navigation

In templates and symlink sources, gf follows includes, shared templates, globs, and symlink paths; gF also supports path:line[:column]. Multiple matches open a picker; managed targets open their source preferentially. Optional vim-matchup, with Tree-sitter matching enabled, provides % for template blocks. See :help vimoi-navigation for path rules.

Source/target switching preserves unsaved edits; gf/gF navigation and vim-matchup block matching

Navigation and switching recording (MP4)

Cancel tasks and refresh

Background tasks show progress after 1000ms. Literal edits rebuild from the cached skeleton immediately; structural or rendering-context changes use a 500ms debounce. Cancel running tasks with :VimoiCancel or <leader>cx, even before progress appears. In a focused waiting float, use q, Esc, or Ctrl-C. Canceled pending write-back does not continue.

Cancel a real slow render, continue editing, and open a working preview

Task cancellation recording (MP4)

Data, configuration, and discovered shared-template changes refresh automatically. Press <leader>cr if results remain stale. Template execution may invoke password helpers; waits can be canceled and have no default timeout.

Existing previews refresh after shared template and configuration data changes

Full-resolution dependency refresh demo (MP4)

Lua API

require("vimoi.api").diff_buffers(source_buf, target_buf, { filetype = "yaml" }) compares two loaded buffers, including unnamed and scratch buffers, with the same hunk operations and ReAdd. See :help vimoi-api for the full API.

Horizontal diff of unnamed scratch buffers, hunk write-back, undo, and ReAdd

Buffer API and horizontal splits recording (MP4)

Compared with chezmoi-template.nvim

Both support direct source editing, live preview, template and host highlighting, blink completion, and template navigation. Their focus differs. This comparison uses chezmoi-template.nvim source, checked on 2026-10-25.

Feature vimoi.nvim chezmoi-template.nvim
Compare and write back Template-aware native diff, bidirectional hunks, static ReAdd Preview annotations against the deployed file
Host highlighting Rendered output projection by default, optional injection Tree-sitter injection
Diagnostics Template checks as you edit, plus LSP / nvim-lint host checks Template execution checks on save
Completion Functions, data fields, locals, paths, block snippets; optional filtered data value docs Functions, data fields with value docs, sensitive-field masking, block snippets
Template formatting, target icons, transparent encryption Not provided Conform, mini.icons, optional decrypt/encrypt
File selection and deployment Managed-file picker and source / target toggle; deploy with the CLI Multiple file pickers, apply commands, apply on save by default
Default mappings Global shortcuts, can be disabled Opt-in source-buffer mappings
Neovim requirement 0.11+ 0.10+

Consider it for formatting, encryption, and deployment integrations. vimoi focuses on comparing rendered output and bringing static changes from actual files back into templates. See the implementation comparison and historical research (Chinese).

Scope and limitations

  • Changes to dynamic or repeated output and define / block bodies usually need manual source edits.
  • Custom template delimiters and templates producing different results under chezmoi cat and apply are unsupported.
  • Encrypted files, symlinks, and script sources are unsupported by plain text ReAdd.
  • Formatting, icons, file browsing, and apply commands are not provided; deploy with the chezmoi CLI.
  • Transparent editing of chezmoi-encrypted source is not provided. For repository encryption, we recommend git-crypt: unlocked templates are directly editable, and the local working tree is plaintext. See chezmoi transparent encryption.

See :help vimoi-limitations for further constraints.

Help

Run :checkhealth vimoi to check your setup, or :help vimoi.nvim for the full manual. Recording demos · Development notes · Roadmap

License

MIT, Copyright (c) 2026 dirichy.

About

Edit chezmoi-managed files in Neovim with template-aware highlight, diagnostic, preview, diff and merge.

Topics

Resources

Stars

4 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages