Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Config Reference

The complete reference for vig’s KDL configuration: every node the config file accepts, with its form, default, merge rule and errors. It is organized for lookup — one section per top-level node, then the page block elements, then every page with its panes and actions.

If you are reading about the config for the first time, start with Configuration Basics (locations, layers, merge model) and Config Recipes (worked examples). This chapter assumes those and aims for completeness.

Conventions used below:

  • Form — what the node looks like. All values are quoted strings unless noted; the one exception is projects-board 2 (a bare integer).
  • Default — the built-in value, as shipped in assets/default.kdl (vig config dump prints it).
  • Merge — what happens when your config states the node.
  • Complete, loadable examples are shown as kdl blocks — vig’s test suite loads each one exactly the way a user config is loaded. Fragments and error demonstrations are marked as ignored and annotated with the error they produce.

Top level at a glance

NodeDefaultMerge rule
theme"base16-eighties.dark"replaced
icons"nerd"replaced
image-preview"auto"replaced
markdown-preview"render"replaced
procs-refresh-interval"2s"replaced
procs-history"120"replaced
github-poll-interval"5s"replaced
github-auto-refresh"on"replaced
projects-poll-interval"30s"replaced
projects-boardabsent (all linked boards)replaced
pagesall seven pagesreplaced wholesale
repo-config"on"replaced (user config only)
appCtrl+c quit, 17 page switchmerged per key
pagesee Pages and panesper element, see below

A config stating every top-level node (each at its default here, so this loads and changes nothing):

theme "base16-eighties.dark"
icons "nerd"
image-preview "auto"
markdown-preview "render"
procs-refresh-interval "2s"
procs-history "120"
github-poll-interval "5s"
github-auto-refresh "on"
projects-poll-interval "30s"
pages "git" "github" "files" "docker" "procs" "worktrees" "projects"
repo-config "on"
app {
    "Ctrl+c" "Quit"
}

Anything else at the top level is an error:

colors "red"
// → unknown top-level block "colors" (expected `theme`, `icons`,
//   `image-preview`, `markdown-preview`, `procs-refresh-interval`,
//   `procs-history`, `github-poll-interval`, `github-auto-refresh`,
//   `projects-poll-interval`, `projects-board`, `pages`, `repo-config`,
//   `app`, or `page`)

Top-level nodes

theme

The syntax highlighting theme used by the diff views (Git and Worktrees) and the Files preview.

  • Formtheme "<name>"
  • Default"base16-eighties.dark"
  • Merge — replaces the default.

Only the themes bundled with syntect are available; run vig config themes to list them (* marks the active one): InspiredGitHub, Solarized (dark), Solarized (light), base16-eighties.dark, base16-mocha.dark, base16-ocean.dark, base16-ocean.light. Only foreground colors are taken from the theme, so the light themes are readable mainly on a light terminal background.

theme "Solarized (dark)"
theme "Solarised (dark)"
// → unknown theme "Solarised (dark)"; available: InspiredGitHub, ...

icons

File-type icons in the Files view.

  • Formicons "<mode>""nerd" or "none"
  • Default"nerd"
  • Merge — replaces the default.

"nerd" shows Nerd Font glyphs by file type and needs a Nerd Font in your terminal; "none" shows plain names (recipe).

icons "none"

image-preview

How the Files view renders image previews (PNG / JPEG / GIF / WebP).

  • Formimage-preview "<mode>""auto", "halfblocks" or "none"
  • Default"auto"
  • Merge — replaces the default.

"auto" probes the terminal for a graphics protocol (Kitty, iTerm2, Sixel) and falls back to unicode halfblocks; "halfblocks" skips the detection and always uses halfblocks; "none" renders no image at all (the preview still shows the image’s metadata line).

image-preview "halfblocks"

markdown-preview

How the Files view previews Markdown files (.md / .markdown).

  • Formmarkdown-preview "<mode>""render" or "raw"
  • Default"render"
  • Merge — replaces the default.

"render" shows the rendered form (headings, emphasis, lists, code, GFM tables fitted to the pane width); "raw" shows the plain syntax-highlighted text. m toggles between the two for the session either way.

markdown-preview "raw"

procs-refresh-interval

How often the Procs view re-reads the process list and the listening ports while it is shown. Sampling pauses on the other views.

  • Formprocs-refresh-interval "<duration>" — a number with s or ms ("2s", "1.5s", "500ms"), quoted; at least "250ms"
  • Default"2s"
  • Merge — replaces the default.
procs-refresh-interval "5s"
procs-refresh-interval "100ms"
// → bad procs-refresh-interval "100ms"; expected a duration such as
//   "2s" or "500ms" (at least 250ms)

procs-history

How many samples the Procs view’s history graphs keep — the system CPU / memory area charts in the graphs pane and the per-process history charts in the detail pane. One sample is taken per refresh interval, so this and procs-refresh-interval multiply into a time span.

  • Formprocs-history "<n>" — a quoted number between "10" and "10000"
  • Default"120" (4 minutes of history at the default "2s")
  • Merge — replaces the default.
procs-history "300"
procs-history "5"
// → bad procs-history "5"; expected a sample count between 10 and 10000

github-poll-interval

How often the GitHub page polls while something is active — the Workflow Runs column while a run is queued or in progress, a PR’s checks in watch mode (w), and the log of a running job — and how often it checks the issue / PR lists for changes (one conditional request whose 304 answer costs no API points; the lists are re-fetched only when it changes). Polling pauses while another page is shown.

  • Formgithub-poll-interval "<duration>" — a number with s or ms, quoted; at least "2s", so a config cannot burn through the API quota
  • Default"5s"
  • Merge — replaces the default.

Rate-limit handling is built in on top of this setting: when GitHub rejects a request as rate-limited, the page suspends all its polling with an exponential backoff (30s, 60s, … capped at 10 minutes) and shows ⚠ GitHub rate limited (resets in Nm) in the status bar. The reset time comes from one gh api rate_limit call (that endpoint is not rate-limited). r retries immediately; a successful fetch clears the backoff. See Troubleshooting for the full story.

github-poll-interval "10s"

projects-poll-interval

How often the Projects page asks GitHub whether the shown board changed while the page is displayed. Each probe reads only the project’s updatedAt (one GraphQL point — item moves and field edits bump it) and the board is re-fetched only when it moved. Probing pauses while another page is shown.

  • Formprojects-poll-interval "<duration>" — a number with s or ms, quoted; at least "5s", since every probe costs a point
  • Default"30s" (120 points an hour with the page open, 20 when idle)
  • Merge — replaces the default.

The probe follows github-auto-refresh and its idle / low-quota scaling; a rate-limited answer pauses it with the same backoff as the GitHub page.

projects-poll-interval "60s"
projects-poll-interval "2s"
// → bad projects-poll-interval "2s"; expected a duration such as "30s" or "60s" (at least 5000ms)

github-auto-refresh

Whether vig refreshes GitHub data on its own: the GitHub page’s polling (runs, watch mode, a running job’s log, the issue / PR list change check) and the Projects page’s stale re-fetch when the page is shown again plus its board change probe.

  • Formgithub-auto-refresh "<mode>""on" or "off"
  • Default"on"
  • Merge — replaces the default.

"off" leaves only the manual r refresh — for shared tokens, CI terminals or a machine running many vig instances. Even when on, automatic refreshes back off by themselves: after 10 minutes without a key press every interval is ×6 (idle in the header), under 20 % of the hourly GraphQL quota ×2, and under 5 % they stop (refresh stopped). The header also shows ⚠ api N left below 1,500 points.

github-auto-refresh "off"

projects-board

Pins the Projects page to one board. The single argument is either a board title (a string, matched case-insensitively against the projects linked to the repository) or a project number — the config’s one bare integer:

  • Formprojects-board "<title>" or projects-board <number>
  • Default — absent: every linked project is available and p / P cycle through them
  • Merge — replaces the default.
projects-board "Roadmap"
projects-board 2

When set, the page shows only that board: p / P no longer cycle (pressing them shows board pinned by config (projects-board) in the status bar) and the header shows the title without the (i/n) counter. When no linked project matches the pin, the board pane shows a notice naming it.

projects-board "Roadmap" 2
// → bad projects-board (one argument required); expected exactly one
//   argument, a board title (`projects-board "Roadmap"`) or a project
//   number (`projects-board 2`)

pages

Which pages are enabled, in tab order. The position in the list is the page’s slot — the number shown in the header (1:Git, 2:GitHub, …) and the position Tab cycling reaches.

  • Formpages "<name>" "<name>" ... — names from git, github, files, docker, procs, worktrees, projects; at least one, no repeats
  • Default — all seven, in that order
  • Merge — replaces the default list wholesale.

Pages you leave out are disabled — not started, no tab, no background polling:

pages "git" "files" "worktrees"

gives a three-tab vig with 1:Git 2:Files 3:Worktrees.

Keys are bindings onto pages, not slots: app { "<key>" "page:<name>" } keeps addressing a page by name wherever it sits, so the built-in 17 still switch to the same pages after reordering. Built-in keys of disabled pages are dropped; a binding in your app { } block to a page that is not listed in pages is an error (see app).

pages "git" "filez"
// → pages: unknown page "filez"; expected one of: git, github, files,
//   docker, procs, worktrees, projects

pages "git" "git"
// → pages: page "git" listed twice

pages
// → pages must list at least one page

The actions page of v0.7.0 was folded into the github page (its Workflow Runs column) in v0.8.0. A config that still lists it is rejected with a message saying so:

pages "git" "actions"
// → pages: page "actions" was folded into the "github" page (v0.8.0);
//   remove it from pages / app bindings

repo-config

Whether the repository-local .vig.kdl layer (Configuration Basics) is read at all.

  • Formrepo-config "on" or repo-config "off"
  • Default"on"
  • Merge — replaces the default; only the user config’s value counts.

With "off" the .vig.kdl file is never loaded and the trust dialog never appears. The switch is read before the repo layer is merged, so a .vig.kdl cannot turn itself on or off — a .vig.kdl that contains repo-config at all (even "on") is rejected: repo-config can only be set in the user config, not in .vig.kdl.

repo-config "off"

app

Global key bindings that work on every page.

  • Formapp { "<key>" "<action>" ... }
  • Default"Ctrl+c" "Quit" and "1""7" bound to the seven pages by name
  • Merge — merged per key: a key you set replaces the default binding for that key; keys you do not mention keep theirs.
ActionMeaning
"Quit"Quit vig
"page:<name>"Switch to that page — page:git, page:github, page:files, page:docker, page:procs, page:worktrees, page:projects. The page must be listed in pages.
"None"Remove the binding for that key
app {
    "q" "Quit"            // quit from anywhere, not only from a pane
    "Ctrl+g" "page:git"   // jump to the Git view
    "7" "None"            // unbind the built-in Projects switch
}

A page: binding of your own naming a page that exists but is not enabled is an error (built-in bindings of disabled pages are silently dropped instead):

pages "git" "files"
app {
    "d" "page:docker"
    // → app block: "d" "page:docker": page "docker" is not listed in
    //   `pages` (git, files)
}

Keys

A key is written as a string, in one of three forms:

  • A single character"j", "G", "/", "]". Case matters: "g" and "G" are different keys.
  • A named key"Enter", "Esc", "Tab", "BackTab", "Space", "Backspace", "Delete", "Up", "Down", "Left", "Right", "Home", "End", "PageUp", "PageDown". A few aliases are accepted: "Return" / "CR" for Enter, "Escape" for Esc, "S-Tab" for BackTab, "BS" for Backspace, "Del" for Delete.
  • A Ctrl+ combination"Ctrl+d", "Ctrl+u", "Ctrl+c". Ctrl is the only supported modifier; there are no Alt+ or function keys.

Binding a key to the reserved action "None" removes it — in app { } and in any pane’s keys { } block alike.

Presets

A preset is a named bundle of standard bindings, expanded in place inside a pane’s keys { } block. Two exist:

PresetExpands to
navj/DownNav.MoveDown, k/UpNav.MoveUp, Ctrl+dNav.HalfPageDown, Ctrl+uNav.HalfPageUp, gNav.JumpTop, GNav.JumpBottom
search/Search.Start, nSearch.Next, NSearch.Prev

The Nav.* and Search.* actions can also be bound individually, in any pane that has the corresponding preset in its defaults. Two rules govern presets (why): presets expand first and explicit bindings in the same pane win for the same key; and when your keys merge into a pane, preset lines are appended, never replaced.

page "git" {
    pane "diff_view" {
        keys {
            "J" "Nav.HalfPageDown"   // bind a preset action explicitly
            "n" "None"               // remove a preset-provided binding
        }
    }
}

The page block

page "<name>" {
    layout { <split | place | slot> }     // replaced wholesale
    tabs "<pane>" "<pane>" ...            // replaced wholesale
    bind select="<pane>" detail="<pane>"  // all bind lines replaced together
    pane "<name>" { keys { ... } }        // keys merged per key
}

Page names and pane names are fixed — you can rearrange, resize and rebind them, but not add new ones. Every block is optional; a page block only changes what it states.

layout

Exactly one root element, of three kinds, nested to any depth:

  • split direction="horizontal" { <children> } — lays its children side by side; direction="vertical" stacks them. Each child may take size="...".
  • place "<pane>" — shows a pane.
  • slot "<name>" ... — one area that shows different panes depending on focus; see Slots below.

Sizes"30" (exactly 30 cells), "40%" (percentage), "min:20" (at least 20 cells, take the leftovers). Omitted means "min:0".

Merge — a layout you write replaces the page’s whole layout; there is no partial layout merge. Start from the page’s block in vig config dump and edit (recipes).

Rules, both checked at startup:

  • Each pane may be placed at most once — counting place lines and, for a slot, each distinct pane the slot can show.
  • At least one pane must be placed.
  • A pane the layout leaves out is inactive: it gets no area, Tab cycling and focus skip it, and bind lines naming it are ignored. The built-in Projects page ships this way — see Page projects.
page "git" {
    layout {
        split direction="vertical" {
            place "diff_view"
            place "diff_view"
        }
    }
}
// → page "git": layout places pane "diff_view" more than once

page "git" { layout { } }
// → page "git" layout is empty

Slots

A slot is a layout area that shows different panes at different times: whichever case matches the currently focused pane wins. Two forms, which can be combined in one slot:

  • Single casethen= on the slot plus a triggers child:

    slot "main" size="min:3" then="git_log" default="diff_view" {
        triggers "branch_list" "reflog" "git_log"
    }
    

    Shows git_log while branch_list, reflog or git_log has focus, and diff_view otherwise (the Git view’s bottom area).

  • Multi casewhen children, each listing trigger panes and naming the pane to show; the first when whose triggers include the focused pane wins, default= shows when none does:

    slot "detail" size="min:3" default="issue_detail" {
        when "pr_list" "pr_detail" then="pr_detail"
        when "run_list" "run_detail" then="run_detail"
    }
    

    The GitHub view’s detail area. Note each when names its then pane among its own triggers, so moving focus into the detail does not switch the area away from it.

The slot’s name ("main", "detail") is just a label. For the at-most-once placement rule, a slot counts each distinct pane it can show as placed once. A worked walk-through is in the slot recipes.

tabs

The panes cycled by Tab / BackTab, in order.

  • Formtabs "<pane>" "<pane>" ...
  • Merge — replaced wholesale when present.

Panes the layout does not place are skipped, so the default tabs stays valid under your layout — you only need to restate tabs to change the cycle order or to include a pane the default order lacks.

bind

Which detail pane a selection pane drives — e.g. selecting a file in file_tree loads it into diff_view.

  • Formbind select="<pane>" detail="<pane>", repeatable
  • Merge — one user bind line replaces all of the page’s default bind lines.

A bind naming an unplaced pane is ignored — and starts applying on its own once a layout places the pane (this is how the Projects page’s list pane comes alive; see Page projects).

pane and keys

page "git" {
    pane "file_tree" {
        keys {
            "o" "ExpandOrOpen"   // add or override a binding
            "Space" "None"       // remove one
        }
    }
}
  • Merge — per key, on top of the pane’s default keys (including expanded presets). preset lines are appended.

Each pane accepts its own set of actions, listed per page below. The pane named view is special: it is not a real pane but the holder of the page-wide keys (quit, help, refresh, tab and pane cycling), and it can never be placed in a layout. The help overlay (?) is generated from the merged keymap, so it always reflects your bindings.

Pages and panes

For each page: its panes, each pane’s bindable actions, and the built-in key for each action. Nav.* and Search.* (see Presets) are additionally available in every pane whose defaults include the corresponding preset — below, panes with nav and search presets are marked. Esc is an action of every interactive pane (leave the pane / clear the search). Run vig config dump to see every default binding in its KDL form.

Page git

Panes: file_tree, branch_list, git_log, reflog, diff_view — all placed by the default layout (git_log and diff_view share the main slot).

PaneActionDefault keyMeaning
viewQuit / Help / Refreshq / ? / rpage-wide
PrevTab / NextTabh / lmove between the sidebar panes
CyclePaneForward / CyclePaneBackwardTab / BackTabcycle the tabs panes
OpenEditoreopen the selected file in $EDITOR
file_tree (nav, search)ToggleDirSpaceexpand / collapse a directory
ExpandOrOpenEnter, Rightexpand a directory / open a file’s diff
FocusDiffifocus the diff view
branch_list (nav, search)OpenActionMenuEnterswitch / safe-delete / set as diff base
FocusLogifocus the git log
git_log (nav, search)YankHashycopy the commit hash
YankUrlYcopy the commit URL
OpenGitHuboopen the commit on GitHub
FocusRefloghfocus the reflog
reflog (nav, search)SetDiffBaseEnterdiff the working tree against this entry
FocusLogifocus the git log
diff_view (nav, search)ScrollLeft / ScrollRighth, Left / l, Righthorizontal scroll
EnterNormalModeivim-style Normal mode (cursor, yank, visual)

Page github

Panes: issue_list, pr_list, run_list (the three columns) and issue_detail, pr_detail, run_detail (sharing the detail slot).

PaneActionDefault keyMeaning
viewQuit / Help / Refreshq / ? / rpage-wide
PrevTab / NextTabh / lmove between the columns
CyclePaneForward / CyclePaneBackwardTab / BackTabcycle columns and detail
issue_list, pr_list, run_list (nav, search)OpenDetaili, Enteropen the detail view
SwitchTabTab (issues) / BackTab (PRs, runs)column-local tab switch
OpenBrowseroopen the item in the browser
CopyUrlycopy the item URL
issue_detail, pr_detail (nav)FocusBody / FocusRighth / lbody ↔ right-hand sub-panes
CycleForward / CycleBackwardTab / BackTabcycle the sub-panes
ToggleWatchwwatch mode: auto-refresh the open item
OpenItemoopen in the browser
CopyUrlycopy the item URL
run_detail (nav, search)FocusBody / FocusRighth / lJobs ↔ Log sub-panes
CycleForward / CycleBackwardTab / BackTabcycle the sub-panes
OpenLogi, Entershow the selected job’s log
NextFailed / PrevFailed] / [jump between failed steps
OpenItemoopen the run / job in the browser
CopyUrlycopy the run URL

In run_detail, Nav.JumpBottom (G) also resumes following a running job’s log.

Page files

Panes: parent_dir, dir_list, preview — all placed. parent_dir is display-only: it has a pane block with no keys and accepts none.

PaneActionDefault keyMeaning
viewQuit / Help / Refreshq / ? / rpage-wide
CyclePaneForward / CyclePaneBackwardTab / BackTabcycle dir_list and preview
OpenEditoreopen the selected file in $EDITOR
OpenDefaultoopen with the OS default app
OpenWithOopen with an app you name
dir_list (nav, search)Enterl, Right, Enterenter directory / focus preview
Parenth, Left, Backspacego to the parent directory
FocusPreviewifocus the preview
preview (nav, search)ScrollLeft / ScrollRighth, Left / l, Rightscroll long lines horizontally
Backback to the file list (unbound by default: Esc / Tab get there)

Page docker

Panes: containers, images, detail, logs — all placed.

PaneActionDefault keyMeaning
viewQuit / Help / Refreshq / ? / rpage-wide
CyclePaneForward / CyclePaneBackwardTab / BackTabcycle the panes
containers (nav, search)OpenDetaili, Enterfocus the inspect summary
FocusLogslfocus the log tail
images (nav, search)OpenDetaili, Enterfocus the inspect summary
detail (nav)Backhback to the list
logs (nav, search)Backhback to the list

In logs, Nav.JumpBottom (G) also resumes following the tail.

Page procs

Panes: processes, ports, detail, graphs — all placed.

PaneActionDefault keyMeaning
viewQuit / Help / Refreshq / ? / rpage-wide
CyclePaneForward / CyclePaneBackwardTab / BackTabcycle the panes
processes (nav, search)FocusDetaili, l, Enterfocus the process detail
CycleSortssort by CPU → MEM → PID
TogglePerCorecCPU graph: history ⇄ per-core bars
ports (nav, search)JumpToProcessEnterjump to the owning process
detail (nav)Backh, Leftback to the process list
graphsTogglePerCorecCPU graph: history ⇄ per-core bars

Page worktrees

Panes: worktrees, stashes, preview — all placed.

PaneActionDefault keyMeaning
viewQuit / Help / Refreshq / ? / rpage-wide
CyclePaneForward / CyclePaneBackwardTab / BackTabcycle the panes
worktrees, stashes (nav, search)FocusPreviewi, l, Enterfocus the preview
preview (nav, search)ScrollLeft / ScrollRighth, Left / l, Righthorizontal scroll
EnterNormalModeiNormal mode in the stash diff
NextFile / PrevFile] / [next / previous file in a stash
BackBackspaceback to the list

Page projects

Panes: projects, board, detail. The built-in layout places only board and detail; the projects list pane is defined but not placedp / P cycle the linked projects instead, and a top-level projects-board pins one board and disables them. To get the list back, place it — the built-in bind select="projects" detail="board" then applies on its own (recipe):

page "projects" {
    layout {
        split direction="horizontal" {
            place "projects" size="22%"
            split direction="vertical" size="min:30" {
                place "board" size="60%"
                place "detail" size="min:5"
            }
        }
    }
    tabs "projects" "board" "detail"
}
PaneActionDefault keyMeaning
viewQuit / Help / Refreshq / ? / rpage-wide
NextProject / PrevProjectp / Pcycle the linked projects
CyclePaneForward / CyclePaneBackwardTab / BackTabcycle the panes
projects (nav, search)OpenBoardi, l, Entershow the selected project’s board
OpenBrowseroopen the project in the browser
CopyUrlycopy the project URL
board (nav, search)PrevColumn / NextColumnh, Left / l, Rightmove between columns (table mode: sort column)
ToggleTabletboard ⇄ table mode
CycleSortscycle the sort column (table mode)
NextView / PrevViewv / Vcycle the project’s saved views
ToggleLaneSpacecollapse / expand the selected swimlane
ZoomIn / ZoomOut+ / -zoom the roadmap time scale
OpenDetaili, Enterfocus the item detail
OpenBrowseroopen the item in the browser
CopyUrlycopy the item URL
detail (nav)Backh, Leftback to the board
OpenBrowseroopen the item in the browser

On the board, Esc returns to the project list when it is placed.

Startup errors

Any problem in the user config stops vig from starting and prints a message naming the file — vig never silently falls back to the defaults when a config file is present. The categories:

  • Syntax errors — reported with file:line:column and the parser’s message, e.g. for a page "git" { left unclosed:

    Error: failed to parse config file /home/you/.config/vig/config.kdl
      /home/you/.config/vig/config.kdl:1:12: No closing '}' for child block
    
  • Unknown names — top-level blocks, pages, panes, themes, icon / image-preview modes, preset names, keys and actions are all validated; each error lists what was expected. Typos cannot go unnoticed.

  • Structural errors — a layout that places a pane twice or places nothing, a slot without then= / when cases, a bind without select= / detail=.

  • Value errors — an interval below its minimum or without a unit, a procs-history out of range, a projects-board with the wrong argument shape, a repo-config other than "on" / "off".

  • Cross-references — your app binding to a page not listed in pages, or to the removed actions page.

The repository-local .vig.kdl layer is the one exception: it degrades instead of aborting (ignored .vig.kdl: <reason> in the status bar). See Troubleshooting for reading the messages in practice.