Configuration

rtdvi reads TOML or JSON — pick whichever you prefer. The file extension drives the format. Search order:

  1. $RTDVI_CONFIG (if set, treated as a file path — extension still chooses the format)
  2. $XDG_CONFIG_HOME/rtdvi/config.toml, then .../config.json
  3. $HOME/.config/rtdvi/config.toml, then .../config.json

At a given directory .toml is tried before .json. If none of these exist, defaults apply and rtdvi runs silently.

The runtime :config command lets you inspect, reload, and convert between formats — see Runtime tooling below.

Schema

# Runtime options (mostly editing knobs).
[options]
tab_width   = 4       # default: 4; tab display width and indent size
expandtab   = true    # default: true; Tab key inserts spaces to next tab stop
                      # (Shift+Tab always inserts a literal tab regardless)
autoindent  = true    # default: true; copy indent of previous line on Enter/o/O
smartindent = true    # default: true; language-aware extra indent rules:
                      #   +1 level after { / control keywords / Python :/Lua then
                      #   auto-dedent { and } on blank lines
                      #   Backspace snaps to previous tab stop in leading whitespace
                      #   requires autoindent = true
paste       = false   # default: false; insert verbatim (no autoindent), toggle :paste/:nopaste
textwidth   = 80      # default: 80; wrap width for `gq` comment reflow
number      = false   # default: false; show line numbers in gutter
leader    = "\\"      # default: "\\"; the <leader> key in user keymaps
highlight_trailing_whitespace = false  # default: false; paint trailing spaces/tabs red
highlight_tabs                = false  # default: false; paint every tab cell red
diagnostic_virtual_text       = false  # default: false; show LSP diagnostics inline after each line

# System-clipboard register: "<this>yy yanks to the OS clipboard,
# "<this>p pastes from it. Default is "q". Set to a space to disable.
system_clipboard_register = "q"
# Optional explicit clipboard commands. When omitted, rtdvi auto-detects:
#   $WAYLAND_DISPLAY  → wl-copy / wl-paste --no-newline
#   $DISPLAY          → xclip -selection clipboard [ -o ]
#   macOS             → pbcopy / pbpaste
# clipboard_copy_cmd  = ["wl-copy"]
# clipboard_paste_cmd = ["wl-paste", "--no-newline"]

# Filetype globs. Tried BEFORE the built-in extension table.
# Right-hand side is either a vim-style filetype name OR a MIME type.
[filetypes]
"*.cpp"       = "c++"           # vim-style, normalised to "cpp"
"*.md"        = "text/markdown" # MIME, normalised to "markdown"
"Cargo.toml"  = "rust"          # exact-name overrides
"*.zsh"       = "sh"
"BUILD"       = "make"

# Keymaps. Each entry binds a key sequence to an action in a single mode.
# `keys` uses the vim notation: `<C-x>`, `<Esc>`, `<Enter>`, plain chars.
[[keymaps]]
mode = "normal"
keys = "<Space>w"
action = "delete_word_forward"

[[keymaps]]
mode = "visual"
keys = "y"
action = "visual_yank"

# Modes accepted: "normal" / "n", "insert" / "i", "visual" / "v",
# "vline" / "V", "vblock" / "C-v".

# Language servers. One block per server. The block name is the server
# name (used as the cache key, so two filetypes pointing at the same
# config = same process).
[lsp.clangd]
cmd = ["clangd", "-j=2", "--background-index"]
filetypes = ["c", "cpp", "objc", "objcpp"]
root_markers = [".git", "compile_commands.json", "compile_flags.txt"]

[lsp.rust-analyzer]
cmd = ["rust-analyzer"]
filetypes = ["rust"]
# root_markers defaults to [".git"] if omitted.

What the options do

FieldDefaultMeaning
options.tab_width4Tab display width and indent size. Controls >>, <<, autoindent step, and smart-backspace snap distance.
options.expandtabtrueIf true, Tab inserts spaces to the next tab_width boundary. If false, inserts a literal \t. Shift+Tab always inserts a literal \t regardless.
options.autoindenttrueCopy the indentation of the previous line when opening a new line with Enter, o, or O. Disabling this gives completely unindented new lines.
options.smartindenttrueLanguage-aware indent on top of autoindent. Adds one extra level after {, control keywords (if/for/while/…), Python/Lua block openers; auto-dedents { and } typed on blank lines; snaps Backspace to the previous tab stop inside leading whitespace. Has no effect when autoindent = false.
options.pastefalsePaste mode: insert typed text verbatim, disabling autoindent, smartindent, expandtab, smart-backspace, and brace-dedent. Toggle at runtime with :paste / :nopaste (also :set paste / :set nopaste). Mainly a fallback — terminals with bracketed paste (most modern ones) get verbatim paste automatically, no toggle needed.
options.numberfalseShow line numbers in a left gutter.
options.textwidth80Wrap width (display columns) for gq comment reflow. See Formatting.
options.leader\Character <leader> expands to in user [[keymaps]] entries. Set to "," or " " to taste.
options.highlight_trailing_whitespacefalsePaint the run of spaces/tabs after the last non-whitespace char on a line with a red background. See whitespace-marks.md.
options.highlight_tabsfalsePaint every tab character (anywhere on the line) with a red background. Useful in spaces-only projects.
options.diagnostic_virtual_textfalseAppend LSP diagnostic messages inline after the end of each affected line. The marker is coloured by severity (red/yellow/blue/cyan); the message text is dim. Only the first line of each message is shown; the highest-severity diagnostic wins when multiple fall on the same line.
options.system_clipboard_register'q'The "<letter> register that routes yank/paste through the system clipboard. See registers.md. Set to ' ' (space) to disable.
options.clipboard_copy_cmdNoneCommand + args that copy stdin to the OS clipboard. None ⇒ auto-detect (wl-copy, xclip, pbcopy).
options.clipboard_paste_cmdNoneCommand + args that print the OS clipboard. None ⇒ auto-detect (wl-paste --no-newline, xclip -o, pbpaste).
filetypes (map){}Glob → filetype/MIME override. Longest pattern wins.
keymaps (array)[]Extra key bindings layered on top of the defaults.
lsp (map){}One block per server; see lsp.md.
formatters (map){}filetype → argv external formatter for gq on code when no LSP range formatting is available. Empty = disabled. See Formatting.

Formatting with gq

gq formats a line range — never the whole file. Targets:

KeysRange
gqq / gqgqthe current line ({count}gqq for N lines)
gq (visual / visual-line / visual-block)the selection

What it does depends on the content of the range, mirroring vim/neovim:

# External formatter fallback (used only when the LSP can't range-format).
[formatters]
rust   = ["rustfmt", "--emit", "stdout"]
python = ["black", "-q", "-"]
go     = ["gofmt"]

If a range is code, has no LSP range formatting, and no [formatters] entry, gq reports gq: no formatter for <filetype> and leaves the text untouched.

Note on external formatters: tools like rustfmt expect a complete, valid unit of code. Formatting an arbitrary partial selection may fail (the formatter exits non-zero and the text is left as-is). Select whole items, or rely on the LSP path, for reliable code formatting.

The <leader> key

<leader> (or <Leader> — case-insensitive) in a keys value is expanded to options.leader at config-load time. Vim's default is \; that's also rtdvi's default, which is why \m toggles the under-cursor highlight out of the box.

[options]
leader = ","

[[keymaps]]
mode = "normal"
keys = "<leader>m"
action = "highlight_toggle_word_under_cursor"
# After leader expansion this binds ",m".

Multi-character leaders work but aren't recommended — the trie resolver treats them as a literal sequence, so a leader = "gp" would shadow Vim's gp motion.

Filetype rules in detail

For every buffer, rtdvi picks the filetype in this order:

  1. The buffer's own override (set by :set syntax=…) — wins unconditionally.
  2. A glob in [filetypes] matching the basename of the buffer's path (longest pattern first).
  3. The built-in basename/extension table (Cargo.toml → rust, Makefile → make, *.rs → rust, …).
  4. mime_guess extension lookup mapped back to a filetype.
  5. "generic" fallback.

Both vim-style names (c++, cpp, js) and MIME types (text/markdown, text/x-c++src) are accepted as right-hand sides and normalised internally.

Keymap reference

Built-in action names you can bind via [[keymaps]]. Full list of actions is in src/ — search for reg.register("…", …).

Selected highlights:

Runtime tooling

The :config ex command exposes four sub-commands:

CommandEffect
:config show [toml|json]Open the active config in a fresh vertical split (a scratch buffer with the matching filetype, so syntax highlighting picks it up). Format defaults to whatever was loaded from disk (or TOML on defaults). Close the split with :close / <C-w>c.
:config pathPrint the file currently loaded plus the full search list (with exists / missing next to each candidate).
:config convert <toml|json> [path] (alias conv)Serialise the active config to disk. Without a path, writes next to the currently loaded file with the new extension; on a fresh install, writes to the canonical default location.
:config load [path]Re-read the active config file. With a path, switches to that file (format inferred from extension).

Examples:

:config show               " print current settings
:config show json          " same but as JSON
:config path               " where is my config?
:config convert json       " mirror config.toml → config.json
:config conv toml ~/rtdvi.toml
:config load               " reload after editing the file externally
:config load ~/other.json  " try a different config without restarting

:config load and :config convert reuse the same format inference, so picking a .json extension just works in both directions.

JSON example

The TOML example above expressed as JSON:

{
  "options": {
    "tab_width": 4,
    "number": true,
    "leader": ","
  },
  "filetypes": {
    "*.cpp": "c++",
    "*.md":  "text/markdown"
  },
  "keymaps": [
    { "mode": "normal", "keys": "<leader>w", "action": "delete_word_forward" }
  ],
  "lsp": {
    "clangd": {
      "cmd": ["clangd", "-j=2", "--background-index"],
      "filetypes": ["c", "cpp", "objc", "objcpp"],
      "root_markers": [".git", "compile_commands.json"]
    }
  }
}

Schema is identical — only the surface syntax changes. Run :config convert json to generate this file from your existing TOML.