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

Mitos

Mitos is a terminal text editor built around selections. Written in Rust and based on Helix, it combines selection-first modal editing with multiple selections, tree-sitter syntax awareness, and built-in language-server support.

Start with the basics · Install Mitos · View the source

ms --tutor
Mitos editing Rust code with completion suggestions and documentation

At a glance

InterfaceTerminal
Editing modelSelection → action
ImplementationRust + Ratatui
LicenseMPL-2.0

What Mitos supports

Editing

  • Selection-first modal editing
  • Multiple selections as a core operation
  • Syntax-aware motions and textobjects
  • Registers, macros, search, and surround operations

Code intelligence

  • Incremental parsing and highlighting with tree-sitter
  • Built-in LSP client for completion and navigation
  • Diagnostics, formatting, rename, and code actions
  • Debug adapter support

Workflow

  • File, buffer, symbol, and diagnostic pickers
  • Shell commands and selection pipelines
  • Configurable keymaps, themes, and languages
  • Workspace trust for project-local configuration

Select first, then act

In Mitos, a cursor is a one-character selection. Motions change or extend that selection, and commands operate on what is selected. The target of an edit is visible before the edit happens.

Multiple selections use the same commands as a single selection. Select every match, press c, and type once to change all of them:

  1. Press x to select a line.
  2. Press s to select regular-expression matches.
  3. Press c to change every selection at once.

Learn more in Basics.

Where it fits

Repeated and structural edits

Multiple selections and tree-sitter textobjects make related edits explicit and let one command operate on every target.

Programming with language tools

Use completion, diagnostics, symbol navigation, formatting, and code actions through the built-in LSP client.

Terminal-based workflows

Open files from the shell, pipe selections through commands, and keep editing close to compilers, version control, and other tools.

Start here

  • Installation — install a release or build Mitos from source.
  • Basics — learn modes, movement, selections, and common commands.
  • Keymap — look up default bindings by mode and command.
  • Configuration — configure editor behavior, themes, keys, and languages.
  • Language servers — set up completion, diagnostics, formatting, and actions.
  • Commands — browse typable and static commands.

About Mitos

Mitos is an independent fork of Helix. It retains the upstream Git history and continues Helix’s selection-first editing model while developing as a separate project.

The editor is open source under the Mozilla Public License 2.0. Mitos is not endorsed by or affiliated with the Helix project or its maintainers.

Basics

Mitos is a modal editor: the same keys navigate, select, and edit depending on the current mode. This short walkthrough covers enough to edit a file without trying to memorize the entire keymap.

For a guided lesson inside the editor, run ms --tutor or enter :tutor from Mitos.

Open a file

After installing Mitos, open a file or directory from your shell:

ms README.md
ms .

Mitos starts in normal mode. Press Esc whenever you want to return to it.

Image previews

Open an image file the same way as a text file, for example ms screenshot.png. See Image previews for supported formats and terminal rendering.

Move around

Use the arrow keys or the home-row movement keys:

KeyMovement
h / j / k / lLeft / down / up / right
w / bNext / previous word start
eNext word end
Ctrl-u / Ctrl-dHalf a page up / down
gg / geStart / end of the file

You can prefix many movements with a count. For example, 5j moves down five visual lines.

Insert text

From normal mode, press i to insert before the selection or a to append after it. Type normally, then press Esc to finish the insertion.

Other useful entry points are I and A for the start and end of the line, and o / O for a new line below or above.

Select, then act

Mitos uses a selection → action editing model. A cursor is a one-character selection; movement changes that selection. Press v to enter select mode, where movement extends it, then apply an action:

KeyAction
dDelete the selection
cChange the selection and enter insert mode
yYank (copy) the selection
p / PPaste after / before the selection
u / UUndo / redo

Press x to select the current line. Syntax-aware textobjects, such as miw for the word around the cursor, are described in Textobjects.

Edit several matches at once

Multiple selections are a normal part of editing, not a separate mode. One simple workflow is:

  1. Press x to select a line.
  2. Press s, enter a regular expression, and press Enter.
  3. Mitos creates one selection for every match in the line.
  4. Press Alt-r, type the replacement, and press Enter to replace every selection.

The replacement prompt leaves the document untouched until Enter, then replaces all selections in one undo step. Escape cancels, and submitting empty text deletes the selections. Replacement text is literal, including strings such as $1. The new text stays selected, preserving the primary selection and each selection’s direction. Press . to repeat the committed replacement on the current selections.

Use c to change selections interactively in insert mode. r still replaces each selected grapheme with the next character: selecting hello and typing rx gives xxxxx, while Alt-r, x, Enter gives x.

Use , to keep only the primary selection. The Usage and Registers pages cover more ways to compose edits.

Search, save, and quit

Press / to search forward, then use n and N to move between matches. Press : to enter command mode:

CommandResult
:write or :wSave the current file
:quit or :qClose the current view
:write-quit or :wqSave and close
:quit! or :q!Close and discard unsaved changes

Where to go next

  • Read Usage for modes, buffers, selections, and motions.
  • Keep the Keymap open as a reference while learning.
  • Set up language servers for completion, diagnostics, and code actions.
  • Adjust editor behavior in Configuration.

Installation

Mitos runs on Linux, macOS, and Windows. The executable is called ms. Use the installer, download a release archive, or install the Debian package. You can also build from source.

Installer

Linux and macOS

curl -fsSL https://mitos.computer/install.sh | sh

The installer selects the right archive, verifies its SHA-256 checksum, and keeps the executable and runtime together in $XDG_DATA_HOME/mitos/ (default: ~/.local/share/mitos/). It links ms into ~/.local/bin, without sudo or changes to your shell files. If that directory is missing from your PATH, it prints the command to add it.

To install a specific version:

curl -fsSL https://mitos.computer/install.sh | sh -s -- --version 0.1.0

Rerun the installer to upgrade. Previous versions stay installed, so running it with an older --version switches back. Use --install-dir and --bin-dir to choose other directories. The installer leaves an existing ms from another installation alone. View the shell installer source.

Windows

Run in PowerShell 5.1 or newer:

& ([scriptblock]::Create((Invoke-RestMethod https://mitos.computer/install.ps1)))

To pin a version, add -Version 0.1.0:

& ([scriptblock]::Create((Invoke-RestMethod https://mitos.computer/install.ps1))) -Version 0.1.0

The installer verifies the ZIP’s checksum, keeps each version and its runtime under %LOCALAPPDATA%\Mitos\versions, and creates an ms.cmd launcher in %LOCALAPPDATA%\Mitos\bin. It adds that directory to your user Path and the current PowerShell session. No administrator access is needed. Use -InstallDir for another location, or -NoModifyPath to manage Path yourself. Rerun to upgrade or select another version. View the PowerShell installer source.

Uninstall

On Linux/macOS, remove the installer-created ~/.local/bin/ms symlink and ~/.local/share/mitos directory. On Windows, remove %LOCALAPPDATA%\Mitos and its bin entry from your user Path. Adjust these paths for custom installs. Your configuration and personal dictionaries remain in the Mitos configuration and state directories.

Release archives

Download the archive for your system from GitHub Releases:

SystemIntel / AMD (x86_64)ARM64 (aarch64)
Linuxmitos-vVERSION-x86_64-linux.tar.xzmitos-vVERSION-aarch64-linux.tar.xz
macOSmitos-vVERSION-x86_64-macos.tar.xzmitos-vVERSION-aarch64-macos.tar.xz
Windowsmitos-vVERSION-x86_64-windows.zipmitos-vVERSION-aarch64-windows.zip

VERSION is the release number, such as 0.1.0. Apple Silicon Macs use aarch64-macos; Intel Macs use x86_64-macos. Linux archives use glibc.

Extract the archive and keep runtime/ beside ms (ms.exe on Windows). It contains the themes, language queries, dictionaries, and compiled grammars. No Rust toolchain or grammar compilation is needed.

Linux and macOS

For example, install version 0.1.0 on an Apple Silicon Mac, using GitHub CLI:

gh release download v0.1.0 --repo mitos-editor/mitos \
  --pattern 'mitos-v0.1.0-aarch64-macos.tar.xz'
mkdir -p "$HOME/.local/share/mitos" "$HOME/.local/bin"
tar -xJf mitos-v0.1.0-aarch64-macos.tar.xz -C "$HOME/.local/share/mitos"
ln -sfn "$HOME/.local/share/mitos/mitos-v0.1.0-aarch64-macos/ms" \
  "$HOME/.local/bin/ms"

For another platform or release, replace the archive and directory names using the table above. You can also download the archive in your browser. Add ~/.local/bin to your shell’s PATH if it is not already there. Mitos follows the executable’s symlink to find the bundled runtime.

Windows

Extract the ZIP to a permanent directory, such as $HOME\Apps\Mitos\mitos-v0.1.0-x86_64-windows. Add that directory to your user Path through Environment Variables, then open a new terminal. Keep ms.exe and runtime/ together.

Debian and Ubuntu

On x86_64 Debian or Ubuntu, download the .deb asset from GitHub Releases, then install it from the download directory:

sudo apt install ./mitos_0.1.0-1_amd64.deb

Use the downloaded package’s filename if it differs. The package includes the runtime, shell completions, and desktop entry. ARM64 users can use the release archive or the installer. If your system’s glibc is too old, see building the Debian package.

Configuration after upgrading

Mitos loads known legacy settings without rewriting your configuration. Use :config-migrate or :config-migrate-workspace to update a file after confirmation, with a backup. Generated and read-only files can be migrated to stdout instead. See configuration migration for details. languages.toml is left unchanged.

Verify the installation

ms --version
ms --health

If the runtime is missing, keep it beside the executable or set MITOS_RUNTIME to its full path. See runtime configuration for the complete search order. Older runtime files in your user configuration can override those bundled with a release.

Language servers are installed separately. See language-server setup to enable completion, diagnostics, and other language features.

Package managers

Mitos is not yet published through third-party package managers. Use the hosted installers or release archives, or build from source.

Debian package

The x86_64 release includes a .deb package. See Debian and Ubuntu installation.

To build a Debian package locally, see Building the Debian package.

Building from source

Requirements:

Clone the Mitos GitHub repository into a directory of your choice. The examples in this documentation assume installation into either ~/src/ on Linux and macOS, or %userprofile%\src\ on Windows.

If you are using the musl-libc standard library instead of glibc the following environment variable must be set during the build to ensure tree-sitter grammars can be loaded correctly:

RUSTFLAGS="-C target-feature=-crt-static"
  1. Clone the repository:

    git clone https://github.com/mitos-editor/mitos
    cd mitos
    
  2. Compile from source:

    # Optimized release build
    cargo build --release --locked
    
    # Optimized for the current CPU
    cargo install \
       --profile opt \
       --config 'build.rustflags=["-C", "target-cpu=native"]' \
       --path crates/term \
       --locked
    

    The release build creates target/release/ms; the install command places ms in Cargo’s binary directory. Both commands construct the tree-sitter grammars in the local runtime folder. ms is short for Mitos.

💡 If you do not want to fetch or build grammars, set an environment variable MITOS_DISABLE_AUTO_GRAMMAR_BUILD

💡 Tree-sitter grammars can be fetched and compiled if not pre-packaged. Fetch grammars with ms --grammar fetch and compile them with ms --grammar build. This will install them in the runtime directory within the user’s mitos config directory (more details below).

💡 If you only want to build some grammars, see use-grammars

Configuring Mitos’s runtime files

Linux and macOS

The runtime directory is one below the Mitos source, so either export a MITOS_RUNTIME environment variable to point to that directory and add it to your ~/.bashrc or equivalent:

export MITOS_RUNTIME=~/src/mitos/runtime

Or, create a symbolic link:

ln -Tsf $PWD/runtime ~/.config/mitos/runtime

Windows

Either set the MITOS_RUNTIME environment variable to point to the runtime files using the Windows setting (search for Edit environment variables for your account) or use the setx command in Cmd:

setx MITOS_RUNTIME "%userprofile%\src\mitos\runtime"

💡 %userprofile% resolves to your user directory like C:\Users\Your-Name\ for example.

Or, create a symlink in %appdata%\mitos\ that links to the source code directory:

MethodCommand
PowerShellNew-Item -ItemType Junction -Target "runtime" -Path "$Env:AppData\mitos\runtime"
Cmdcd %appdata%\mitos
mklink /D runtime "%userprofile%\src\mitos\runtime"

💡 On Windows, creating a symbolic link may require running PowerShell or Cmd as an administrator.

Multiple runtime directories

When Mitos finds multiple runtime directories it will search through them for files in the following order:

  1. The workspace’s runtime/ directory when running through Cargo.
  2. runtime/ subdirectory of OS-dependent mitos user config directory.
  3. $MITOS_RUNTIME
  4. Distribution-specific fallback directory (set at build time with MITOS_DEFAULT_RUNTIME).
  5. runtime/ subdirectory of path to Mitos executable.

This order also sets the priority for selecting which file will be used if multiple runtime directories have files with the same name.

ms --grammar fetch and ms --grammar build write grammar sources and compiled grammars into the first runtime directory, normally the user config directory’s runtime/ subdirectory.

Note to packagers

If you are making a package of Mitos for end users, to provide a good out of the box experience, you should set the MITOS_DEFAULT_RUNTIME environment variable at build time (before invoking cargo build) to a directory which will store the final runtime files after installation. For example, say you want to package the runtime into /usr/lib/mitos/runtime. The rough steps a build script could follow are:

  1. export MITOS_DEFAULT_RUNTIME=/usr/lib/mitos/runtime
  2. cargo build --profile opt --locked
  3. cp -r runtime $BUILD_DIR/usr/lib/mitos/
  4. cp target/opt/ms $BUILD_DIR/usr/bin/ms

This way the resulting ms binary will always look for its runtime directory in /usr/lib/mitos/runtime if the user has no custom runtime in ~/.config/mitos or MITOS_RUNTIME.

Validating the installation

To make sure everything is set up as expected you should run the Mitos health check:

ms --health

The command reports runtime, clipboard, language-server, and grammar status.

Configure the desktop shortcut

If your desktop environment supports the XDG desktop menu you can configure Mitos to show up in the application menu by copying the provided .desktop file:

cp contrib/Mitos.desktop ~/.local/share/applications

It is recommended to convert the links in the .desktop file to absolute paths to avoid potential problems:

sed -i -e "s|Exec=ms %F|Exec=$(readlink -f ~/.cargo/bin/ms) %F|g" \
  ~/.local/share/applications/Mitos.desktop

To use another terminal than the system default, you can modify the .desktop file. For example, to use kitty:

sed -i "s|Exec=ms %F|Exec=kitty ms %F|g" ~/.local/share/applications/Mitos.desktop
sed -i "s|Terminal=true|Terminal=false|g" ~/.local/share/applications/Mitos.desktop

Building the Debian package

If the .deb file provided on the release page uses a libc version higher than that used by your Debian, Ubuntu, or Mint system, you can build the package from source to match your system’s dependencies.

Install cargo-deb, the tool used for building the .deb file:

cargo install cargo-deb

After cloning and entering the Mitos repository as previously described, use the following command to build the release binary and package it into a .deb file in a single step.

cargo deb -- --locked

💡 This locks you into the --release profile. But you can also build mitos in any way you like. As long as you leave a target/release/ms file, it will get packaged with cargo deb --no-build

💡 Don’t worry about the following:

warning: Failed to find dependency specification

Cargo deb just reports which packaged files it didn’t derive dependencies for. But so far the dependency deriving seams very good, even if some of the grammar files are skipped.

You can find the resulted .deb in target/debian/. It should contain everything it needs, including the

  • completions for bash, fish, zsh
  • .desktop file
  • launcher to the binary with the runtime

Usage

For a full interactive introduction to Mitos, refer to the tutor which can be accessed via the command ms --tutor or :tutor.

💡 Currently, not all functionality is fully documented, please refer to the key mappings list.

Modes

Mitos is a modal editor, meaning it has different modes for different tasks. The main modes are:

  • Normal mode: For navigation and editing commands. This is the default mode.
  • Insert mode: For typing text directly into the document. Access by typing i in normal mode.
  • Select/extend mode: For making selections and performing operations on them. Access by typing v in normal mode.

Buffers

Buffers are in-memory representations of files. You can have multiple buffers open at once. Use pickers or commands like :buffer-next and :buffer-previous to open buffers or switch between them.

Selection-first editing

Inspired by Kakoune, Mitos follows the selection → action model. This means that whatever you are going to act on (a word, a paragraph, a line, etc.) is selected first and the action itself (delete, change, yank, etc.) comes second. A cursor is simply a single width selection.

Multiple selections

Also inspired by Kakoune, multiple selections are a core mode of interaction in Mitos. For example, the standard way of replacing multiple instances of a word is to first select all instances (so there is one selection per instance) and then use the change action (c) to edit them all at the same time.

Motions

Motions are commands that move the cursor or modify selections. They’re used for navigation and text manipulation. Examples include w to move to the next word, or f to find a character. See the Movement section of the keymap for more motions.

Registers

In Mitos, registers are storage locations for text and other data, such as the result of a search. Registers can be used to cut, copy, and paste text, similar to the clipboard in other text editors. Usage is similar to Vim, with " being used to select a register.

User-defined registers

Mitos allows you to create your own named registers for storing text, for example:

  • "ay - Yank the current selection to register a.
  • "op - Paste the text in register o after the selection.

If a register is selected before invoking a change or delete command, the selection will be stored in the register and the action will be carried out:

  • "hc - Store the selection in register h and then change it (delete and enter insert mode).
  • "md - Store the selection in register m and delete it.

Default registers

Commands that use registers, like yank (y), use a default register if none is specified. These registers are used as defaults:

Register characterContains
/Last search
:Last executed command
"Last yanked text
@Last recorded macro

Special registers

Some registers have special behavior when read from and written to.

Register characterWhen readWhen written
_No values are returnedAll values are discarded
#Selection indices (first selection is 1, second is 2, etc.)This register is not writable
.Contents of the current selectionsThis register is not writable
%Name of the current fileThis register is not writable
+Reads from the system clipboardJoins and yanks to the system clipboard
*Reads from the primary clipboardJoins and yanks to the primary clipboard

When yanking multiple selections to the clipboard registers, the selections are joined with newlines. Pasting from these registers will paste multiple selections if the clipboard was last yanked to by the Mitos session. Otherwise the clipboard contents are pasted as one selection.

Surround

Mitos includes built-in functionality similar to vim-surround. The keymappings have been inspired from vim-sandwich:

Surround demo

Key SequenceAction
ms<char> (after selecting text)Add surround characters to selection
mr<char_to_replace><new_char>Replace the closest surround characters
md<char_to_delete>Delete the closest surround characters

You can use counts to act on outer pairs.

Surround can also act on multiple selections. For example, to change every occurrence of (use) to [use]:

  1. % to select the whole file
  2. s to split the selections on a search term
  3. Input use and hit Enter
  4. mr([ to replace the parentheses with square brackets

Multiple characters are currently not supported, but planned for future release.

Textobjects

Selecting and manipulating text with textobjects

In Mitos, textobjects are a way to select, manipulate and operate on a piece of text in a structured way. They allow you to refer to blocks of text based on their structure or purpose, such as a word, sentence, paragraph, or even a function or block of code.

Textobject demo Textobject tree-sitter demo

  • ma - Select around the object (va in Vim, <alt-a> in Kakoune)
  • mi - Select inside the object (vi in Vim, <alt-i> in Kakoune)
Key after mi or maTextobject selected
wWord
WWORD
pParagraph
(, [, ', etc.Specified surround pairs
mThe closest surround pair
fFunction
tType (or Class)
aArgument/parameter
cComment
TTest
gChange
sSpelling finding
x(X)HTML element

💡 f, t, etc. need a tree-sitter grammar active for the current document and a special tree-sitter query file to work properly. Only some grammars currently have the query file implemented. Contributions are welcome!

The s textobject uses spell-checking findings. Both mis and mas select the diagnosed word under the cursor; mIs and mAs select every finding fully contained in the current selection. Use [s and ]s to navigate to the previous or next finding. These commands also work in plain text and scratch buffers when spell checking is enabled.

Navigating between functions, classes, parameters, and other elements is possible using tree-sitter and textobject queries. For example to move to the next function use ]f, to move to previous type use [t, and so on.

Tree-sitter-nav-demo

For the full reference see the unimpaired section of the key bind documentation.

💡 This feature relies on tree-sitter textobjects and requires the corresponding query file to work properly.

Syntax-aware motions

Moving the selection with syntax-aware motions

Alt-p, Alt-o, Alt-i, and Alt-n (or Alt and arrow keys) allow you to move the selection according to its location in the syntax tree. For example, many languages have the following syntax for function calls:

func(arg1, arg2, arg3);

A function call might be parsed by tree-sitter into a tree like the following.

(call
  function: (identifier) ; func
  arguments:
    (arguments           ; (arg1, arg2, arg3)
      (identifier)       ; arg1
      (identifier)       ; arg2
      (identifier)))     ; arg3

Use :tree-sitter-subtree to view the syntax tree of the main selection. In a more intuitive tree format:

            ┌────┐
            │call│
      ┌─────┴────┴─────┐
      │                │
┌─────▼────┐      ┌────▼────┐
│identifier│      │arguments│
│  "func"  │ ┌────┴───┬─────┴───┐
└──────────┘ │        │         │
             │        │         │
   ┌─────────▼┐  ┌────▼─────┐  ┌▼─────────┐
   │identifier│  │identifier│  │identifier│
   │  "arg1"  │  │  "arg2"  │  │  "arg3"  │
   └──────────┘  └──────────┘  └──────────┘

If you have a selection that wraps arg1 (see the tree above), and you use Alt-n, it will select the next sibling in the syntax tree: arg2.

// before
func([arg1], arg2, arg3)
// after
func(arg1, [arg2], arg3);

Similarly, Alt-o will expand the selection to the parent node, in this case, the arguments node.

func[(arg1, arg2, arg3)];

There is also some nuanced behavior that prevents you from getting stuck on a node with no sibling. When using Alt-p with a selection on arg1, the previous child node will be selected. In the event that arg1 does not have a previous sibling, the selection will move up the syntax tree and select the previous element. As a result, using Alt-p with a selection on arg1 will move the selection to the “func” identifier.

Pickers

Using pickers

Mitos has a variety of pickers, which are interactive windows used to select various kinds of items. These include a file picker, global search picker, and more. Most pickers are accessed via keybindings in space mode. Pickers have their own keymap for navigation.

Filtering Picker Results

Most pickers perform fuzzy matching using fzf syntax. Two exceptions are the global search picker, which uses regex, and the workspace symbol picker, which passes search terms to the language server. Note that OR operations (|) are not currently supported.

If a picker shows multiple columns, you may apply the filter to a specific column by prefixing the column name with %. Column names can be shortened to any prefix, so %p, %pa or %pat all mean the same as %path. For example, a query of mitos %p .toml !lang in the global search picker searches for the term “mitos” within files with paths ending in “.toml” but not including “lang”.

You can insert the contents of a register using Ctrl-r followed by a register name. For example, one could insert the currently selected text using Ctrl-r-., or the directory of the current file using Ctrl-r-% followed by Ctrl-w to remove the last path section. The global search picker will use the contents of the search register if you press Enter without typing a filter. For example, pressing *-Space-/-Enter will start a global search for the currently selected text.

To keep navigating a picker’s current matched locations after closing it, press Ctrl-q to populate the quicklist.

File explorer

Space-e opens an interactive file explorer for browsing and opening files, rooted at the workspace; Space-. opens one rooted at the current buffer’s directory. Unlike the file picker, the explorer does not ignore most files by default; its ignore behaviour is configured separately in the [editor.file-explorer] section.

Supported images can be displayed in the preview pane. See Image previews for formats and rendering details.

Image previews

Mitos can preview PNG, JPEG, GIF, and WebP files directly in the terminal. Previews are read-only and fit the available pane. A caption shows the image format, dimensions in pixels, and file size.

Open an image

Open an image from your shell:

ms screenshot.png

Inside Mitos, use :open screenshot.png, the file picker (Space f), or the file explorer (Space e). The picker can also show an image in its preview pane before you open it.

Terminal rendering

Mitos detects the terminal’s image capabilities automatically. When detection fails, it falls back to rendering with half-block characters. The appearance and resolution therefore depend on your terminal.

Images resize to fit when the pane size changes. GIF and WebP previews show a still image; animation playback is not supported.

Preview limitations

Image previews do not provide image editing. Unsupported formats, files that cannot be decoded, or images that exceed the decoder’s limits show a binary-file placeholder instead.

The decoder limits image width and height to 32,768 pixels and its allocation budget to 128 MiB. A compressed file can be small on disk but still exceed the decoding budget. If a preview is unavailable, check the format and try a smaller copy of the image.

Quicklist

The quicklist keeps a picker’s matched file locations available after the picker closes. Unlike the jumplist, which records locations you leave, the quicklist is an explicit result set you can traverse in either direction.

Populating the quicklist

While a picker containing files or locations is open, press Ctrl-q to copy its current matched entries into the quicklist. The entries keep their current picker order, and the picker remains open. For example, you can filter global-search, diagnostics, or symbol results and capture only those matches.

The quicklist is editor-global and temporary. Capturing entries from another picker replaces its contents; capturing a picker with no matching locations clears it.

KeyAction
]qJump to the next entry, wrapping at the end
[qJump to the previous entry, wrapping at the beginning
]lJump to the next entry in the current file or unsaved buffer
[lJump to the previous entry in the current file or unsaved buffer
Space-qOpen the quicklist picker

The navigation commands accept a count, so 3]q jumps forward three entries. Quicklist jumps are added to the jumplist, allowing Ctrl-o to return to the location before the jump.

The quicklist picker shows every captured entry and marks the current one with *. Selecting an entry opens it and makes it the current quicklist position.

Typical workflow

  1. Open a picker that shows file locations, such as global search, diagnostics, symbols, or another location-based picker.
  2. Filter the picker until it contains the set of locations you want.
  3. Press Ctrl-q to copy the current matched items into the quicklist.
  4. Close the picker, then move through all collected locations with ]q and [q.
  5. Use ]l and [l to restrict navigation to the current file, or Space-q to browse the complete list.

Jumplist

Using the jumplist

To help with quick navigation, Mitos maintains a list of “jumps” called the jumplist. Whenever you make a significant movement (see next section), Mitos stores your selections from before the move as a jump. A jump serves as a kind of checkpoint, allowing you to jump to a separate location, make edits, and return to where you were with your previous selections. This way, the jumplist tracks both your previous location and your selections. You can manually save a jump by using Ctrl-s. To jump backward in the jumplist, use Ctrl-o; to go forward, use Ctrl-i. To view and select from the full jumplist, use Space-j to open the jumplist picker.

If you want to keep a picker’s current matched locations as a separate navigation list, use the quicklist instead.

What makes a jump

The following is a non-exhaustive list of which actions add a jump to the jumplist:

  • Switching buffers
    • Using the buffer picker, going to the next/previous buffer
    • Going to the last accessed/modified file
    • Making a new file (:new FILE)
    • Opening a file (:open FILE)
      • Includes :log-open, :config-open, :config-open-workspace, :tutor
    • Navigating by pickers, global search, or the file explorer
    • goto_file (gf)
  • Big in-file movements
    • select_regex (s)
    • split_regex (S)
    • search (/)
    • keep_selections and remove_selections (K and <A-K>)
    • goto_file_start (gg)
    • goto_file_end
    • goto_last_line (ge)
    • :goto 123 / :123 / 123G
    • goto_definition (gd)
    • goto_declaration (gD)
    • goto_type_definition (gy)
    • goto_reference (gr)
  • Other
    • Ctrl-s manually creates a jump
    • Trying to close a modified buffer can switch you to that buffer and create a jump
    • The debugger can create jumps as you jump stack frames

Keymap

💡 Mappings marked (LSP) require an active language server for the file.

💡 Mappings marked (TS) require a tree-sitter grammar for the file type.

⚠️ Some terminals’ default key mappings conflict with Mitos’s. If any of the mappings described on this page do not work as expected, check your terminal’s mappings to ensure they do not conflict. See the wiki for known conflicts.

Normal mode

Normal mode is the default mode when you launch mitos. You can return to it from other modes by pressing the Escape key.

Movement

NOTE: Unlike Vim, f, F, t and T are not confined to the current line.

Hereafter, <n> represents an integer by typing a sequence of digits.

KeyDescriptionCommand
h, LeftMove leftmove_char_left
j, DownMove downmove_visual_line_down
k, UpMove upmove_visual_line_up
l, RightMove rightmove_char_right
wMove next word startmove_next_word_start
bMove previous word startmove_prev_word_start
eMove next word endmove_next_word_end
WMove next WORD startmove_next_long_word_start
BMove previous WORD startmove_prev_long_word_start
EMove next WORD endmove_next_long_word_end
tFind till next charfind_till_char
fFind next charfind_next_char
TFind till previous chartill_prev_char
FFind previous charfind_prev_char
<n>G, <n>ggGo to line number <n>goto_line
Alt-.Repeat last motion (f, t, m, [ or ])repeat_last_motion
HomeMove to the start of the linegoto_line_start
EndMove to the end of the linegoto_line_end
Ctrl-b, PageUpMove page uppage_up
Ctrl-f, PageDownMove page downpage_down
Ctrl-uMove cursor and page half page uppage_cursor_half_up
Ctrl-dMove cursor and page half page downpage_cursor_half_down
Ctrl-iJump forward on the jumplistjump_forward
Ctrl-oJump backward on the jumplistjump_backward
Ctrl-sSave the current selection to the jumplistsave_selection

Changes

KeyDescriptionCommand
rReplace each selected grapheme with a characterreplace_char
Alt-rReplace selections with entered textreplace
RReplace with yanked textreplace_with_yanked
~Switch case of the selected textswitch_case
`Set the selected text to lower caseswitch_to_lowercase
Alt-`Set the selected text to upper caseswitch_to_uppercase
iInsert before selectioninsert_mode
aInsert after selection (append)append_mode
IInsert at the start of the lineinsert_at_line_start
AInsert at the end of the lineinsert_at_line_end
oOpen new line below selectionopen_below
OOpen new line above selectionopen_above
.Repeat last insert or prompted replacementN/A
uUndo changeundo
URedo changeredo
Alt-uMove backward in historyearlier
Alt-UMove forward in historylater
yYank selectionyank
pPaste after selectionpaste_after
PPaste before selectionpaste_before
" <reg>Select a register to yank to or paste fromselect_register
>Indent selectionindent
<Unindent selectionunindent
=Format selection (LSP)format_selections
dDelete selectiondelete_selection
Alt-dDelete selection, without yankingdelete_selection_noyank
cChange selection (delete and enter insert mode)change_selection
Alt-cChange selection (delete and enter insert mode, without yanking)change_selection_noyank
Ctrl-aIncrement object (number) under cursorincrement
Ctrl-xDecrement object (number) under cursordecrement
QStart/stop macro recording to the selected register (experimental)record_macro
qPlay back a recorded macro from the selected register (experimental)replay_macro
Ctrl-zSuspend Mitos and return to the shell (resume with fg)suspend

Shell

KeyDescriptionCommand
|Pipe each selection through shell command, replacing with outputshell_pipe
Alt-|Pipe each selection into shell command, ignoring outputshell_pipe_to
!Run shell command, inserting output before each selectionshell_insert_output
Alt-!Run shell command, appending output after each selectionshell_append_output
$Pipe each selection into shell command, keep selections where command returned 0shell_keep_pipe

Selection manipulation

KeyDescriptionCommand
sSelect all regex matches inside selectionsselect_regex
SSplit selection into sub selections on regex matchessplit_selection
Alt-sSplit selection on newlinessplit_selection_on_newline
Alt-minusMerge selectionsmerge_selections
Alt-_Merge consecutive selectionsmerge_consecutive_selections
&Align selection in columnsalign_selections
_Trim whitespace from the selectiontrim_selections
;Collapse selection onto a single cursorcollapse_selection
Alt-;Flip selection cursor and anchorflip_selections
Alt-:Ensures the selection is in forward directionensure_selections_forward
,Keep only the main selectionkeep_primary_selection
Alt-,Remove the main selectionremove_primary_selection
CCopy selection onto the next line (Add cursor below)copy_selection_on_next_line
Alt-CCopy selection onto the previous line (Add cursor above)copy_selection_on_prev_line
(Rotate main selection backwardrotate_selections_backward
)Rotate main selection forwardrotate_selections_forward
Alt-(Rotate selection contents backwardrotate_selection_contents_backward
Alt-)Rotate selection contents forwardrotate_selection_contents_forward
%Select entire fileselect_all
xSelect current line, if already selected, extend to next lineextend_line_below
XExtend selection to line bounds (line-wise selection)extend_to_line_bounds
Alt-xShrink selection to line bounds (line-wise selection)shrink_to_line_bounds
JJoin lines inside selectionjoin_selections
Alt-JJoin lines inside selection and select the inserted spacejoin_selections_space
KKeep selections matching the regexkeep_selections
Alt-KRemove selections matching the regexremove_selections
Ctrl-cComment/uncomment the selectionstoggle_comments
Alt-o, Alt-upExpand selection to parent syntax node (TS)expand_selection
Alt-i, Alt-downShrink syntax tree object selection (TS)shrink_selection
Alt-p, Alt-leftSelect previous sibling node in syntax tree (TS)select_prev_sibling
Alt-n, Alt-rightSelect next sibling node in syntax tree (TS)select_next_sibling
Alt-aSelect all sibling nodes in syntax tree (TS)select_all_siblings
Alt-I, Alt-Shift-downSelect all children nodes in syntax tree (TS)select_all_children
Alt-eMove to end of parent node in syntax tree (TS)move_parent_node_end
Alt-bMove to start of parent node in syntax tree (TS)move_parent_node_start

Search commands all operate on the / register by default. To use a different register, use "<char>.

KeyDescriptionCommand
/Search for regex patternsearch
?Search for previous patternrsearch
nSelect next search matchsearch_next
NSelect previous search matchsearch_prev
*Use current selection as the search pattern, automatically wrapping with \b on word boundariessearch_selection_detect_word_boundaries
Alt-*Use current selection as the search patternsearch_selection

Minor modes

These sub-modes are accessible from normal mode and typically switch back to normal mode after a command.

KeyDescriptionCommand
vEnter select (extend) modeselect_mode
gEnter goto modeN/A
mEnter match modeN/A
:Enter command modecommand_mode
zEnter view modeN/A
ZEnter sticky view modeN/A
Ctrl-wEnter window modeN/A
SpaceEnter space modeN/A

These modes (except command mode) can be configured by remapping keys.

View mode

Accessed by typing z in normal mode.

View mode is intended for scrolling and manipulating the view without changing the selection. The “sticky” variant of this mode (accessed by typing Z in normal mode) is persistent and can be exited using the escape key. This is useful when you’re simply looking over text and not actively editing it.

KeyDescriptionCommand
z, cVertically center the linealign_view_center
tAlign the line to the top of the screenalign_view_top
bAlign the line to the bottom of the screenalign_view_bottom
mAlign the line to the middle of the screen (horizontally)align_view_middle
j, downScroll the view downwardsscroll_down
k, upScroll the view upwardsscroll_up
Ctrl-f, PageDownMove page downpage_down
Ctrl-b, PageUpMove page uppage_up
Ctrl-uMove cursor and page half page uppage_cursor_half_up
Ctrl-dMove cursor and page half page downpage_cursor_half_down

Goto mode

Accessed by typing g in normal mode.

Jumps to various locations.

KeyDescriptionCommand
<n>gGo to line number <n>goto_file_start
gGo to the start of the filegoto_file_start
<n>|Go to column number <n>goto_column
|Go to the start of linegoto_column
eGo to the end of the filegoto_last_line
fGo to files/URLs in selectionsgoto_file
hGo to the start of the linegoto_line_start
lGo to the end of the linegoto_line_end
sGo to first non-whitespace character of the linegoto_first_nonwhitespace
tGo to the top of the screengoto_window_top
cGo to the middle of the screengoto_window_center
bGo to the bottom of the screengoto_window_bottom
dGo to definition (LSP)goto_definition
DGo to declaration (LSP)goto_declaration
yGo to type definition (LSP)goto_type_definition
rGo to references (LSP)goto_reference
iGo to implementation (LSP)goto_implementation
aGo to the last accessed/alternate filegoto_last_accessed_file
mGo to the last modified/alternate filegoto_last_modified_file
nGo to next buffergoto_next_buffer
pGo to previous buffergoto_previous_buffer
.Go to last modification in current filegoto_last_modification
jMove down textual (instead of visual) linemove_line_down
kMove up textual (instead of visual) linemove_line_up
wShow labels at each word and select the word that belongs to the entered labelsgoto_word

Match mode

Accessed by typing m in normal mode.

Please refer to the relevant sections for detailed explanations about surround and textobjects.

KeyDescriptionCommand
mGoto matching bracket (TS)match_brackets
s <char>Surround current selection with <char>surround_add
r <from><to>Replace surround character <from> with <to>surround_replace
d <char>Delete surround character <char>surround_delete
a <object>Select around textobjectselect_textobject_around
i <object>Select inside textobjectselect_textobject_inner
A <object>Select all textobjects around selectionsselect_all_textobjects_around
I <object>Select all textobjects inside selectionsselect_all_textobjects_inner

Window mode

Accessed by typing Ctrl-w in normal mode.

This layer is similar to Vim keybindings as Kakoune does not support windows.

KeyDescriptionCommand
w, Ctrl-wSwitch to next windowrotate_view
v, Ctrl-vVertical right splitvsplit
s, Ctrl-sHorizontal bottom splithsplit
t, Ctrl-tTranspose the two adjacent splitstranspose_view
fGo to files/URLs in selections in horizontal splitsgoto_file_hsplit
FGo to files/URLs in selections in vertical splitsgoto_file_vsplit
h, Ctrl-h, LeftMove to left splitjump_view_left
j, Ctrl-j, DownMove to split belowjump_view_down
k, Ctrl-k, UpMove to split abovejump_view_up
l, Ctrl-l, RightMove to right splitjump_view_right
q, Ctrl-qClose current windowwclose
o, Ctrl-oOnly keep the current window, closing all the otherswonly
HSwap window to the leftswap_view_left
JSwap window downwardsswap_view_down
KSwap window upwardsswap_view_up
LSwap window to the rightswap_view_right
ns, nCtrl-sNew horizontal split with a scratch bufferhsplit_new
nv, nCtrl-vNew vertical split with a scratch buffervsplit_new

Space mode

Accessed by typing Space in normal mode.

This layer is a kludge of mappings, mostly pickers.

KeyDescriptionCommand
fOpen file picker at LSP workspace rootfile_picker
FOpen file picker at current working directoryfile_picker_in_current_directory
eOpen file explorer at workspace rootfile_explorer
.Open file explorer at current buffer’s directoryfile_explorer_in_current_buffer_directory
bOpen buffer pickerbuffer_picker
jOpen jumplist pickerjumplist_picker
qOpen quicklist pickerquicklist_picker
gOpen changed file picker in workspacechanged_file_picker
GDebug (experimental)N/A
kShow documentation for item under cursor in a popup (LSP)hover
sOpen document symbol picker (LSP or TS)lsp_or_syntax_symbol_picker
SOpen workspace symbol picker (LSP or TS)lsp_or_syntax_workspace_symbol_picker
dOpen document diagnostics picker (LSP)diagnostics_picker
DOpen workspace diagnostics picker (LSP)workspace_diagnostics_picker
rRename symbol (LSP)rename_symbol
aApply code action (LSP)code_action
lShow code lenses on the current line (LSP)show_code_lenses_under_cursor
hSelect symbol references (LSP)select_references_to_symbol_under_cursor
'Open last fuzzy pickerlast_picker
wEnter window modeN/A
cComment/uncomment selectionstoggle_comments
CBlock comment/uncomment selectionstoggle_block_comments
Alt-cLine comment/uncomment selectionstoggle_line_comments
pPaste system clipboard after selectionspaste_clipboard_after
PPaste system clipboard before selectionspaste_clipboard_before
yYank selections to clipboardyank_to_clipboard
YYank main selection to clipboardyank_main_selection_to_clipboard
RReplace selections by clipboard contentsreplace_selections_with_clipboard
/Global search in workspace folderglobal_search
?Open command palettecommand_palette
BToggle inline Git blametoggle_inline_blame

💡 Global search displays results in a fuzzy picker, use Space + ' to bring it back up after opening a file.

Displays documentation for item under cursor. Remapping currently not supported.

KeyDescription
Ctrl-uScroll up
Ctrl-dScroll down
Completion Menu

Displays documentation for the selected completion item. Remapping currently not supported.

KeyDescription
Shift-Tab, Ctrl-p, UpPrevious entry
Tab, Ctrl-n, DownNext entry
EnterClose menu and accept completion
Ctrl-cClose menu and reject completion

Any other keypresses result in the completion being accepted.

Signature-help Popup

Displays the signature of the selected completion item. Remapping currently not supported.

KeyDescription
Alt-pPrevious signature
Alt-nNext signature

Unimpaired

These mappings are in the style of vim-unimpaired.

KeyDescriptionCommand
]dGo to next diagnosticgoto_next_diag
[dGo to previous diagnosticgoto_prev_diag
]sGo to next spelling findinggoto_next_spelling
[sGo to previous spelling findinggoto_prev_spelling
]DGo to last diagnostic in documentgoto_last_diag
[DGo to first diagnostic in documentgoto_first_diag
]qGo to next quicklist entrygoto_next_quicklist
[qGo to previous quicklist entrygoto_prev_quicklist
]lGo to next quicklist entry in current filegoto_next_file_quicklist
[lGo to previous quicklist entry in current filegoto_prev_file_quicklist
]fGo to next function (TS)goto_next_function
[fGo to previous function (TS)goto_prev_function
]tGo to next type definition (TS)goto_next_class
[tGo to previous type definition (TS)goto_prev_class
]aGo to next argument/parameter (TS)goto_next_parameter
[aGo to previous argument/parameter (TS)goto_prev_parameter
]cGo to next comment (TS)goto_next_comment
[cGo to previous comment (TS)goto_prev_comment
]eGo to next entry (TS)goto_next_entry
[eGo to previous entry (TS)goto_prev_entry
]TGo to next test (TS)goto_next_test
[TGo to previous test (TS)goto_prev_test
]pGo to next paragraphgoto_next_paragraph
[pGo to previous paragraphgoto_prev_paragraph
]gGo to next changegoto_next_change
[gGo to previous changegoto_prev_change
]GGo to last changegoto_last_change
[GGo to first changegoto_first_change
[xGo to next (X)HTML elementgoto_next_xml_element
]xGo to previous (X)HTML elementgoto_prev_xml_element
]SpaceAdd newline belowadd_newline_below
[SpaceAdd newline aboveadd_newline_above

Insert mode

Accessed by typing i in normal mode.

Insert mode bindings are minimal by default. Mitos is designed to be a modal editor, and this is reflected in the user experience and internal mechanics. Changes to the text are only saved for undos when escaping from insert mode to normal mode.

💡 New users are strongly encouraged to learn the modal editing paradigm to get the smoothest experience.

KeyDescriptionCommand
EscapeSwitch to normal modenormal_mode
Ctrl-sCommit undo checkpointcommit_undo_checkpoint
Ctrl-xAutocompletecompletion
Ctrl-rInsert a register contentinsert_register
Ctrl-w, Alt-BackspaceDelete previous worddelete_word_backward
Alt-d, Alt-DeleteDelete next worddelete_word_forward
Ctrl-uDelete to start of linekill_to_line_start
Ctrl-kDelete to end of linekill_to_line_end
Ctrl-h, Backspace, Shift-BackspaceDelete previous chardelete_char_backward
Ctrl-d, DeleteDelete next chardelete_char_forward
Ctrl-j, EnterInsert new lineinsert_newline
TabSmart tab (configurable)smart_tab
Shift-TabInsert tabinsert_tab

These keys are not recommended, but are included for new users less familiar with modal editors.

KeyDescriptionCommand
UpMove to previous linemove_line_up
DownMove to next linemove_line_down
LeftBackward a charmove_char_left
RightForward a charmove_char_right
PageUpMove one page uppage_up
PageDownMove one page downpage_down
HomeMove to line startgoto_line_start
EndMove to line endgoto_line_end_newline

As you become more comfortable with modal editing, you may want to disable some insert mode bindings. You can do this by editing your config.toml file.

[keys.insert]
up = "no_op"
down = "no_op"
left = "no_op"
right = "no_op"
pageup = "no_op"
pagedown = "no_op"
home = "no_op"
end = "no_op"

Select / extend mode

Accessed by typing v in normal mode.

Select mode echoes Normal mode, but changes any movements to extend selections rather than replace them. Goto motions are also changed to extend, so that vgl, for example, extends the selection to the end of the line.

Search is also affected. By default, n and N will remove the current selection and select the next instance of the search term. Toggling this mode before pressing n or N makes it possible to keep the current selection. Toggling it on and off during your iterative searching allows you to selectively add search terms to your selections.

Picker

Keys to use within picker. Remapping currently not supported. See the documentation page on pickers for more info. Prompt keybinds also work in pickers, except where they conflict with picker keybinds.

KeyDescription
Shift-Tab, Up, Ctrl-pPrevious entry
Tab, Down, Ctrl-nNext entry
PageUp, Ctrl-uPage up
PageDown, Ctrl-dPage down
HomeGo to first entry
EndGo to last entry
EnterOpen selected
Alt-EnterOpen selected in the background without closing the picker
Ctrl-sOpen horizontally
Ctrl-vOpen vertically
Ctrl-tToggle preview
Ctrl-qReplace the quicklist with the current matched entries
Escape, Ctrl-cClose picker

Prompt

Keys to use within prompt, Remapping currently not supported.

KeyDescription
Escape, Ctrl-cClose prompt
Alt-b, Ctrl-LeftBackward a word
Ctrl-b, LeftBackward a char
Alt-f, Ctrl-RightForward a word
Ctrl-f, RightForward a char
Ctrl-e, EndMove prompt end
Ctrl-a, HomeMove prompt start
Ctrl-w, Alt-Backspace, Ctrl-BackspaceDelete previous word
Alt-d, Alt-Delete, Ctrl-DeleteDelete next word
Ctrl-uDelete to start of line
Ctrl-kDelete to end of line
Backspace, Ctrl-h, Shift-BackspaceDelete previous char
Delete, Ctrl-dDelete next char
Ctrl-sInsert a word under doc cursor, may be changed to Ctrl-r Ctrl-w later
Ctrl-p, UpSelect previous history
Ctrl-n, DownSelect next history
Ctrl-rInsert the content of the register selected by following input char
TabSelect next completion item
BackTabSelect previous completion item
EnterOpen selected

Command line

The command line is used for executing typable commands like :write or :quit. Press : to activate the command line.

Typable commands optionally accept arguments. :write for example accepts an optional path to write the file contents. The command line also supports a quoting syntax for arguments, flags to modify command behaviors, and expansions - a way to insert values from the editor. Most commands support these features but some have custom parsing rules (see the exceptions below).

Quoting

By default, command arguments are split on tabs and space characters. :open README.md CHANGELOG.md for example should open two files, README.md and CHANGELOG.md. Arguments that contain spaces can be surrounded in single quotes (') or backticks (`) to prevent the space from separating the argument, like :open 'a b.txt'.

Double quotes may be used the same way, but double quotes expand their inner content. :echo "%{cursor_line}" for example may print 1 because of the expansion for the cursor_line variable. :echo '%{cursor_line}' though prints %{cursor_line} literally: content within single quotes or backticks is interpreted as-is.

On Unix systems the backslash character may be used to escape certain characters depending on where it is used. Within an argument which isn’t surround in quotes, the backslash can be used to escape the space or tab characters: :open a\ b.txt is equivalent to :open 'a b.txt'. The backslash may also be used to escape quote characters (', `, ") or the percent token (%) when used at the beginning of an argument. :echo \%sh{foo} for example prints %sh{foo} instead of invoking a foo shell command and :echo \"quote prints "quote. The backslash character is treated literally in any other situation on Unix systems and always on Windows: :echo \n always prints \n.

Flags

Command flags are optional switches that can be used to alter the behavior of a command. For example the :sort command accepts an optional --reverse (or -r for short) flag which causes the sort command to reverse the sorting direction. Typing the - character shows completions for the current command’s flags, if any.

The -- flag specifies the end of flags. All arguments after -- are treated as positional arguments: :open -- -a.txt opens a file called -a.txt.

Expansions

Expansions are patterns that Mitos recognizes and replaces within the command line. Mitos recognizes anything starting with a percent token (%) as an expansion, for example %sh{echo hi!}. Expansions are particularly useful when used in commands like :echo or :noop for executing simple scripts. For example:

[keys.normal]
# Show the current Git branch in the status area.
F12 = ":echo %sh{git branch --show-current}"

Expansions take the form %[<kind>]<open><contents><close>. In %sh{echo hi!}, for example, the kind is sh - the shell expansion - and the contents are “echo hi!”, with { and } acting as opening and closing delimiters. The following open/close characters are recognized as expansion delimiter pairs: (/), [/], {/} and </>. Plus the single characters ', " or | may be used instead: %{cursor_line} is equivalent to %<cursor_line>, %[cursor_line] or %|cursor_line|.

To escape a percent character instead of treating it as an expansion, use two percent characters consecutively. To execute a shell command like date -u +'%Y-%m-%d', double the percent characters: :echo %sh{date -u +'%%Y-%%m-%%d'}.

When no <kind> is provided, Mitos will expand a variable. For example %{cursor_line} can be used as in argument to insert the line number. :echo %{cursor_line} for instance may print 1 to the statusline.

The following variables are supported:

NameDescription
cursor_lineThe line number of the primary cursor in the currently focused document, starting at 1.
cursor_columnThe column number of the primary cursor in the currently focused document, starting at 1. This is counted as the number of grapheme clusters from the start of the line rather than bytes or codepoints.
buffer_nameThe relative path of the currently focused document. [scratch] is expanded instead for scratch buffers.
file_path_absoluteThe absolute path of the currently focused document. For scratch buffers this will default to the current working directory.
line_endingA string containing the line ending of the currently focused document. For example on Unix systems this is usually a line-feed character (\n) but on Windows systems this may be a carriage-return plus a line-feed (\r\n). The line ending kind of the currently focused document can be inspected with the :line-ending command.
current_working_directoryCurrent working directory
workspace_directoryNearest ancestor directory of the current working directory that contains .git, .svn, jj or .mitos
languageA string containing the language name of the currently focused document.
selectionA string containing the contents of the main selection of the currently focused document.
selection_line_startThe line number of the start of the main selection in the currently focused document, starting at 1.
selection_line_endThe line number of the end of the main selection in the currently focused document, starting at 1.

Aside from editor variables, the following expansions may be used:

  • Unicode %u{..}. The contents may contain up to six hexadecimal numbers corresponding to a Unicode codepoint value. For example :echo %u{25CF} prints ● to the statusline.
  • Shell %sh{..}. The contents are passed to the configured shell command. For example :echo %sh{echo "20 * 5" | bc} may print 100 on the statusline on when using a shell with echo and the bc calculator installed. Shell expansions are evaluated recursively. %sh{echo '%{buffer_name}:%{cursor_line}'} for example executes a command like echo 'README.md:1': the variables within the %sh{..} expansion are evaluated before executing the shell command.
  • Register %reg{..}. The contents should be a single character representing the register name. For example, :set-register a hello world followed by echo %reg{a} prints hello world to the statusline.
  • Positional argument %arg{..}. Within a custom command, the contents are a zero-based argument index. For example, %arg{0} expands to the first argument passed to the custom command. Missing arguments expand to an empty string.

As mentioned above, double quotes can be used to surround arguments containing spaces but also support expansions within the quoted content unlike single quotes or backticks. For example :echo "circle: %u{25CF}" prints circle: ● to the statusline while :echo 'circle: %u{25CF}' prints circle: %u{25CF}.

Note that expansions are only evaluated once the Enter key is pressed in command mode.

Exceptions

The following commands support expansions but otherwise pass the given argument directly to the shell program without interpreting quotes:

  • :insert-output
  • :append-output
  • :pipe
  • :pipe-to
  • :run-shell-command

For example executing :sh echo "%{buffer_name}:%{cursor_column}" would pass text like echo "README.md:1" as an argument to the shell program: the expansions are evaluated but not the quotes. As mentioned above, percent characters can be used in shell commands by doubling the percent character. To insert the output of a command like date -u +'%Y-%m-%d' use :insert-output date -u +'%%Y-%%m-%%d'.

The :set-option and :toggle-option commands use regular parsing for the first argument - the config option name - and parse the rest depending on the config option’s type. :set-option interprets the second argument as a string for string config options and parses everything else as JSON.

:toggle-option’s behavior depends on the JSON type of the config option supplied as the first argument:

  • Booleans: only the config option name should be provided. For example :toggle-option format.on-save will flip the format.on-save option.
  • Strings: the rest of the command line is parsed with regular quoting rules. For example :toggle-option indent-heuristic hybrid tree-sitter simple cycles through “hybrid”, “tree-sitter” and “simple” values on each invocation of the command.
  • Numbers, arrays and objects: the rest of the command line is parsed as a stream of JSON values. For example :toggle-option rulers [81] [51, 73] cycles through [81] and [51, 73].

When providing multiple values to :toggle-option there should be no duplicates. :toggle-option indent-heuristic hybrid simple tree-sitter simple for example would only toggle between “hybrid” and “tree-sitter” values.

:lsp-workspace-command works similarly to :toggle-option. The first argument (if present) is parsed according to normal rules. The rest of the line is parsed as JSON values. Unlike :toggle-option, string arguments for a command must be quoted. For example :lsp-workspace-command lsp.Command "foo" "bar".

Commands

Typable commands

Typable commands are used from command mode and may take arguments. Command mode can be activated by pressing :. The built-in typable commands are:

NameDescription
:exit, :x, :xitWrite changes to disk if the buffer is modified and then quit. Accepts an optional path (:exit some/path.txt).
:exit!, :x!, :xit!Force write changes to disk, creating necessary subdirectories, if the buffer is modified and then quit. Accepts an optional path (:exit! some/path.txt).
:quit, :qClose the current view.
:quit!, :q!Force close the current view, ignoring unsaved changes.
:open, :o, :edit, :eOpen a file from disk into the current view.
:buffer-close, :bc, :bcloseClose the current buffer.
:buffer-close!, :bc!, :bclose!Close the current buffer forcefully, ignoring unsaved changes.
:buffer-close-others, :bco, :bcloseotherClose all buffers but the currently focused one.
:buffer-close-others!, :bco!, :bcloseother!Force close all buffers but the currently focused one.
:buffer-close-all, :bca, :bcloseallClose all buffers without quitting.
:buffer-close-all!, :bca!, :bcloseall!Force close all buffers ignoring unsaved changes without quitting.
:buffer-next, :bn, :bnextGoto next buffer.
:buffer-previous, :bp, :bprevGoto previous buffer.
:write, :wWrite changes to disk. Accepts an optional path (:write some/path.txt).
:write!, :w!Force write changes to disk creating necessary subdirectories. Accepts an optional path (:write! some/path.txt).
:write-buffer-close, :wbcWrite changes to disk and closes the buffer. Accepts an optional path (:write-buffer-close some/path.txt).
:write-buffer-close!, :wbc!Force write changes to disk creating necessary subdirectories and closes the buffer. Accepts an optional path (:write-buffer-close! some/path.txt).
:new, :nCreate a new scratch buffer.
:format, :fmtFormat the file using an external formatter or language server.
:indent-styleSet the indentation style for editing. (‘t’ for tabs or 1-16 for number of spaces.)
:line-endingSet the document’s default line ending. Options: crlf, lf.
:earlier, :earJump back to an earlier point in edit history. Accepts a number of steps or a time span.
:later, :latJump to a later point in edit history. Accepts a number of steps or a time span.
:write-quit, :wqWrite changes to disk and close the current view. Accepts an optional path (:wq some/path.txt).
:write-quit!, :wq!Write changes to disk and close the current view forcefully. Accepts an optional path (:wq! some/path.txt).
:write-all, :waWrite changes from all buffers to disk.
:write-all!, :wa!Forcefully write changes from all buffers to disk creating necessary subdirectories.
:write-quit-all, :wqa, :xaWrite changes from all buffers to disk and close all views.
:write-quit-all!, :wqa!, :xa!Forcefully write changes from all buffers to disk, creating necessary subdirectories, and close all views (ignoring unsaved changes).
:quit-all, :qaClose all views.
:quit-all!, :qa!Force close all views ignoring unsaved changes.
:cquit, :cqQuit with exit code (default 1). Accepts an optional integer exit code (:cq 2).
:cquit!, :cq!Force quit with exit code (default 1) ignoring unsaved changes. Accepts an optional integer exit code (:cq! 2).
:themeChange the editor theme (show current theme if no name specified).
:yank-joinYank joined selections. A separator can be provided as first argument. Default value is newline.
:clipboard-yankYank main selection into system clipboard.
:clipboard-yank-joinYank joined selections into system clipboard. A separator can be provided as first argument. Default value is newline.
:primary-clipboard-yankYank main selection into system primary clipboard.
:primary-clipboard-yank-joinYank joined selections into system primary clipboard. A separator can be provided as first argument. Default value is newline.
:clipboard-paste-afterPaste system clipboard after selections.
:clipboard-paste-beforePaste system clipboard before selections.
:clipboard-paste-replaceReplace selections with content of system clipboard.
:primary-clipboard-paste-afterPaste primary clipboard after selections.
:primary-clipboard-paste-beforePaste primary clipboard before selections.
:primary-clipboard-paste-replaceReplace selections with content of system primary clipboard.
:show-clipboard-providerShow clipboard provider name in status bar.
:change-current-directory, :cdChange the current working directory.
:show-directory-stackShow the directory stack as a space-delimited string.
:push-directory, :pushdSave and then change the current directory.
:pop-directory, :popdRemove the top entry from the directory stack, and cd to the new top directory..
:show-directory, :pwdShow the current working directory.
:encodingSet encoding. Based on https://encoding.spec.whatwg.org.
:character-info, :charGet info about the character under the primary cursor.
:reload, :rlDiscard changes and reload from the source file.
:reload-all, :rlaDiscard changes and reload all documents from the source files.
:update, :uWrite changes only if the file has been modified.
:lsp-workspace-commandOpen workspace command picker
:lsp-restartRestarts the given language servers, or all language servers that are used by the current file if no arguments are supplied
:lsp-stopStops the given language servers, or all language servers that are used by the current file if no arguments are supplied
:tree-sitter-scopesDisplay tree sitter scopes, primarily for theming and development.
:tree-sitter-highlight-nameDisplay name of tree-sitter highlight scope under the cursor.
:tree-sitter-layersDisplay language names of tree-sitter injection layers under the cursor.
:debug-start, :dbgStart a debug session from a given template with given parameters.
:debug-remote, :dbg-tcpConnect to a debug adapter by TCP address and start a debugging session from a given template with given parameters.
:debug-evalEvaluate expression in current debug context.
:vsplit, :vsOpen the file in a vertical split.
:vsplit-new, :vnewOpen a scratch buffer in a vertical split.
:hsplit, :hs, :spOpen the file in a horizontal split.
:hsplit-new, :hnewOpen a scratch buffer in a horizontal split.
:tutorOpen the tutorial.
:goto, :gGoto line number.
:set-language, :langSet the language of current buffer (show current language if no value specified).
:set-spelling-language, :spellingSet the spell-checking languages for the current buffer (e.g. en_US); a word is flagged only when every language rejects it. Pass off to disable, or no value to show the current languages.
:set-option, :setSet a config option at runtime.
For example to disable smart case search, use :set search.smart-case false.
:toggle-option, :toggleToggle a config option at runtime.
For example to toggle smart case search, use :toggle search.smart-case.
:get-option, :getGet the current value of a config option.
:sortSort ranges in selection.
:reflowHard-wrap the current selection of lines to a given width.
:tree-sitter-subtree, :ts-subtreeDisplay the smallest tree-sitter subtree that spans the primary selection, primarily for debugging queries.
:config-reloadRefresh user config.
:config-openOpen the user config.toml file.
:config-migrateMigrate legacy user settings after confirmation, creating a backup.
:config-migrate-workspaceMigrate legacy trusted workspace settings after confirmation, creating a backup.
:config-open-workspaceOpen the workspace config.toml file.
:log-openOpen the mitos log file.
:insert-outputRun shell command, inserting output before each selection.
:append-outputRun shell command, appending output after each selection.
:pipe, :|Pipe each selection to the shell command.
:pipe-toPipe each selection to the shell command, ignoring output.
:run-shell-command, :sh, :!Run a shell command
:reset-diff-change, :diffget, :diffgReset the diff change at the cursor position.
:clear-registerClear given register. If no argument is provided, clear all registers.
:set-registerSet contents of the given register.
:redrawClear and re-render the whole UI
:move, :mvMove the current buffer and its corresponding file to a different path
:move!, :mv!Move the current buffer and its corresponding file to a different path creating necessary subdirectories
:yank-diagnosticYank diagnostic(s) under primary cursor to register, or clipboard by default
:read, :rLoad a file into buffer
:echoPrints the given arguments to the statusline.
:noopDoes nothing.
:workspace-trustAllow language servers and local config for the current workspace.
:workspace-untrustRevoke the current workspace’s trust grant or exclusion.
:workspace-excludeMark the current workspace as never-prompt. Never prompts for trust again.

Static Commands

Static commands take no arguments and can be bound to keys. Static commands can also be executed from the command picker (<space>?). The built-in static commands are:

NameDescriptionDefault keybinds
no_opDo nothing
move_char_leftMove leftnormal: h, <left>, insert: <left>
move_char_rightMove rightnormal: l, <right>, insert: <right>
move_line_upMove upnormal: gk
move_line_downMove downnormal: gj
move_visual_line_upMove upnormal: k, <up>, insert: <up>
move_visual_line_downMove downnormal: j, <down>, insert: <down>
extend_char_leftExtend leftselect: h, <left>
extend_char_rightExtend rightselect: l, <right>
extend_line_upExtend upselect: gk
extend_line_downExtend downselect: gj
extend_visual_line_upExtend upselect: k, <up>
extend_visual_line_downExtend downselect: j, <down>
copy_selection_on_next_lineCopy selection on next linenormal: C, select: C
copy_selection_on_prev_lineCopy selection on previous linenormal: <A-C>, select: <A-C>
move_next_word_startMove to start of next wordnormal: w
move_prev_word_startMove to start of previous wordnormal: b
move_next_word_endMove to end of next wordnormal: e
move_prev_word_endMove to end of previous word
move_next_long_word_startMove to start of next long wordnormal: W
move_prev_long_word_startMove to start of previous long wordnormal: B
move_next_long_word_endMove to end of next long wordnormal: E
move_prev_long_word_endMove to end of previous long word
move_next_sub_word_startMove to start of next sub word
move_prev_sub_word_startMove to start of previous sub word
move_next_sub_word_endMove to end of next sub word
move_prev_sub_word_endMove to end of previous sub word
move_parent_node_endMove to end of the parent nodenormal: <A-e>
move_parent_node_startMove to beginning of the parent nodenormal: <A-b>
extend_next_word_startExtend to start of next wordselect: w
extend_prev_word_startExtend to start of previous wordselect: b
extend_next_word_endExtend to end of next wordselect: e
extend_prev_word_endExtend to end of previous word
extend_next_long_word_startExtend to start of next long wordselect: W
extend_prev_long_word_startExtend to start of previous long wordselect: B
extend_next_long_word_endExtend to end of next long wordselect: E
extend_prev_long_word_endExtend to end of prev long word
extend_next_sub_word_startExtend to start of next sub word
extend_prev_sub_word_startExtend to start of previous sub word
extend_next_sub_word_endExtend to end of next sub word
extend_prev_sub_word_endExtend to end of prev sub word
extend_parent_node_endExtend to end of the parent nodeselect: <A-e>
extend_parent_node_startExtend to beginning of the parent nodeselect: <A-b>
find_till_charMove till next occurrence of charnormal: t
find_next_charMove to next occurrence of charnormal: f
extend_till_charExtend till next occurrence of charselect: t
extend_next_charExtend to next occurrence of charselect: f
till_prev_charMove till previous occurrence of charnormal: T
find_prev_charMove to previous occurrence of charnormal: F
extend_till_prev_charExtend till previous occurrence of charselect: T
extend_prev_charExtend to previous occurrence of charselect: F
repeat_last_motionRepeat last motionnormal: <A-.>, select: <A-.>
replace_charReplace each selected grapheme with a characternormal: r, select: r
replaceReplace selections with entered textnormal: <A-r>, select: <A-r>
switch_caseSwitch (toggle) casenormal: ~, select: ~
switch_to_uppercaseSwitch to uppercasenormal: <A-`>, select: <A-`>
switch_to_lowercaseSwitch to lowercasenormal: `, select: `
page_upMove page upnormal: <C-b>, Z<C-b>, z<C-b>, <pageup>, Z<pageup>, z<pageup>, select: <C-b>, Z<C-b>, z<C-b>, <pageup>, Z<pageup>, z<pageup>, insert: <pageup>
page_downMove page downnormal: <C-f>, Z<C-f>, z<C-f>, <pagedown>, Z<pagedown>, z<pagedown>, select: <C-f>, Z<C-f>, z<C-f>, <pagedown>, Z<pagedown>, z<pagedown>, insert: <pagedown>
half_page_upMove half page up
half_page_downMove half page down
page_cursor_upMove page and cursor up
page_cursor_downMove page and cursor down
page_cursor_half_upMove page and cursor half upnormal: <C-u>, Z<C-u>, z<C-u>, Z<backspace>, z<backspace>, select: <C-u>, Z<C-u>, z<C-u>, Z<backspace>, z<backspace>
page_cursor_half_downMove page and cursor half downnormal: <C-d>, Z<C-d>, z<C-d>, Z<space>, z<space>, select: <C-d>, Z<C-d>, z<C-d>, Z<space>, z<space>
select_allSelect whole documentnormal: %, select: %
select_regexSelect all regex matches inside selectionsnormal: s, select: s
split_selectionSplit selections on regex matchesnormal: S, select: S
split_selection_on_newlineSplit selection on newlinesnormal: <A-s>, select: <A-s>
merge_selectionsMerge selectionsnormal: <A-minus>, select: <A-minus>
merge_consecutive_selectionsMerge consecutive selectionsnormal: <A-_>, select: <A-_>
searchSearch for regex patternnormal: /, Z/, z/, select: /, Z/, z/
rsearchReverse search for regex patternnormal: ?, Z?, z?, select: ?, Z?, z?
search_nextSelect next search matchnormal: n, Zn, zn, select: Zn, zn
search_prevSelect previous search matchnormal: N, ZN, zN, select: ZN, zN
extend_search_nextAdd next search match to selectionselect: n
extend_search_prevAdd previous search match to selectionselect: N
search_selectionUse current selection as search patternnormal: <A-*>, select: <A-*>
search_selection_detect_word_boundariesUse current selection as the search pattern, automatically wrapping with \b on word boundariesnormal: *, select: *
make_search_word_boundedModify current search to make it word bounded
global_searchGlobal search in workspace foldernormal: <space>/, select: <space>/
extend_lineSelect current line, if already selected, extend to another line based on the anchor
extend_line_belowSelect current line, if already selected, extend to next linenormal: x, select: x
extend_line_aboveSelect current line, if already selected, extend to previous line
select_line_aboveSelect current line, if already selected, extend or shrink line above based on the anchor
select_line_belowSelect current line, if already selected, extend or shrink line below based on the anchor
extend_to_line_boundsExtend selection to line boundsnormal: X, select: X
shrink_to_line_boundsShrink selection to line boundsnormal: <A-x>, select: <A-x>
delete_selectionDelete selectionnormal: d, select: d
delete_selection_noyankDelete selection without yankingnormal: <A-d>, select: <A-d>
change_selectionChange selectionnormal: c, select: c
change_selection_noyankChange selection without yankingnormal: <A-c>, select: <A-c>
collapse_selectionCollapse selection into single cursornormal: ;, select: ;
flip_selectionsFlip selection cursor and anchornormal: <A-;>, select: <A-;>
ensure_selections_forwardEnsure all selections face forwardnormal: <A-:>, select: <A-:>
insert_modeInsert before selectionnormal: i, select: i
append_modeAppend after selectionnormal: a, select: a
command_modeEnter command modenormal: :, select: :
file_pickerOpen file pickernormal: <space>f, select: <space>f
file_picker_in_current_buffer_directoryOpen file picker at current buffer’s directory
file_picker_in_current_directoryOpen file picker at current working directorynormal: <space>F, select: <space>F
file_explorerOpen file explorer in workspace rootnormal: <space>e, select: <space>e
file_explorer_in_current_buffer_directoryOpen file explorer at current buffer’s directorynormal: <space>., select: <space>.
file_explorer_in_current_directoryOpen file explorer at current working directory
code_actionPerform code actionnormal: <space>a, select: <space>a
buffer_pickerOpen buffer pickernormal: <space>b, select: <space>b
jumplist_pickerOpen jumplist pickernormal: <space>j, select: <space>j
quicklist_pickerOpen quicklist pickernormal: <space>q, select: <space>q
symbol_pickerOpen symbol picker
syntax_symbol_pickerOpen symbol picker from syntax information
lsp_or_syntax_symbol_pickerOpen symbol picker from LSP or syntax informationnormal: <space>s, select: <space>s
blame_lineShow blame for the current line
toggle_inline_blameToggle inline Git blamenormal: <space>B, select: <space>B
changed_file_pickerOpen changed file picker in workspacenormal: <space>g, select: <space>g
changed_file_picker_in_repositoryOpen changed file picker in repository
select_references_to_symbol_under_cursorSelect symbol referencesnormal: <space>h, select: <space>h
workspace_symbol_pickerOpen workspace symbol picker
syntax_workspace_symbol_pickerOpen workspace symbol picker from syntax information
lsp_or_syntax_workspace_symbol_pickerOpen workspace symbol picker from LSP or syntax informationnormal: <space>S, select: <space>S
diagnostics_pickerOpen diagnostic pickernormal: <space>d, select: <space>d
workspace_diagnostics_pickerOpen workspace diagnostic pickernormal: <space>D, select: <space>D
last_pickerOpen last pickernormal: <space>', select: <space>'
insert_at_line_startInsert at start of linenormal: I, select: I
insert_at_line_endInsert at end of linenormal: A, select: A
open_belowOpen new line below selectionnormal: o, select: o
open_aboveOpen new line above selectionnormal: O, select: O
normal_modeEnter normal modenormal: <esc>, select: v, insert: <esc>
select_modeEnter selection extend modenormal: v
exit_select_modeExit selection modeselect: <esc>
goto_definitionGoto definitionnormal: gd, select: gd
goto_declarationGoto declarationnormal: gD, select: gD
add_newline_aboveAdd newline abovenormal: [<space>, select: [<space>
add_newline_belowAdd newline belownormal: ]<space>, select: ]<space>
goto_type_definitionGoto type definitionnormal: gy, select: gy
goto_implementationGoto implementationnormal: gi, select: gi
show_code_lenses_under_cursorShow code lenses under cursornormal: <space>l, select: <space>l
goto_file_startGoto line number <n> else file startnormal: gg
goto_file_endGoto file end
extend_to_file_startExtend to line number <n> else file startselect: gg
extend_to_file_endExtend to file end
goto_fileGoto files/URLs in selectionsnormal: gf, select: gf
goto_file_hsplitGoto files in selections (hsplit)normal: <C-w>f, <space>wf, select: <C-w>f, <space>wf
goto_file_vsplitGoto files in selections (vsplit)normal: <C-w>F, <space>wF, select: <C-w>F, <space>wF
goto_referenceGoto referencesnormal: gr, select: gr
goto_window_topGoto window topnormal: gt, select: gt
goto_window_centerGoto window centernormal: gc, select: gc
goto_window_bottomGoto window bottomnormal: gb, select: gb
goto_last_accessed_fileGoto last accessed filenormal: ga, select: ga
goto_last_modified_fileGoto last modified filenormal: gm, select: gm
goto_last_modificationGoto last modificationnormal: g., select: g.
goto_lineGoto linenormal: G, select: G
goto_last_lineGoto last linenormal: ge
extend_to_last_lineExtend to last lineselect: ge
goto_first_diagGoto first diagnosticnormal: [D, select: [D
goto_last_diagGoto last diagnosticnormal: ]D, select: ]D
goto_next_diagGoto next diagnosticnormal: ]d, select: ]d
goto_prev_diagGoto previous diagnosticnormal: [d, select: [d
goto_next_spellingGoto next spelling findingnormal: ]s, select: ]s
goto_prev_spellingGoto previous spelling findingnormal: [s, select: [s
goto_next_quicklistGoto next quicklist entrynormal: ]q, select: ]q
goto_prev_quicklistGoto previous quicklist entrynormal: [q, select: [q
goto_next_file_quicklistGoto next quicklist entry in current filenormal: ]l, select: ]l
goto_prev_file_quicklistGoto previous quicklist entry in current filenormal: [l, select: [l
goto_next_changeGoto next changenormal: ]g, select: ]g
goto_prev_changeGoto previous changenormal: [g, select: [g
goto_first_changeGoto first changenormal: [G, select: [G
goto_last_changeGoto last changenormal: ]G, select: ]G
goto_line_startGoto line startnormal: gh, <home>, select: gh, insert: <home>
goto_line_endGoto line endnormal: gl, <end>, select: gl
goto_columnGoto columnnormal: g|
extend_to_columnExtend to columnselect: g|
goto_next_bufferGoto next buffernormal: gn, select: gn
goto_previous_bufferGoto previous buffernormal: gp, select: gp
goto_line_end_newlineGoto newline at line endinsert: <end>
goto_first_nonwhitespaceGoto first non-blank in linenormal: gs, select: gs
trim_selectionsTrim whitespace from selectionsnormal: _, select: _
extend_to_line_startExtend to line startselect: <home>
extend_to_first_nonwhitespaceExtend to first non-blank in line
extend_to_line_endExtend to line endselect: <end>
extend_to_line_end_newlineExtend to line end
signature_helpShow signature help
smart_tabInsert tab if all cursors have all whitespace to their left; otherwise, run a separate command.insert: <tab>
insert_tabInsert tab charinsert: <S-tab>
insert_newlineInsert newline charinsert: <C-j>, <ret>
insert_char_interactiveInsert an interactively-chosen char
append_char_interactiveAppend an interactively-chosen char
delete_char_backwardDelete previous charinsert: <C-h>, <backspace>, <S-backspace>
delete_char_forwardDelete next charinsert: <C-d>, <del>
delete_word_backwardDelete previous wordinsert: <C-w>, <A-backspace>
delete_word_forwardDelete next wordinsert: <A-d>, <A-del>
kill_to_line_startDelete till start of lineinsert: <C-u>
kill_to_line_endDelete till end of lineinsert: <C-k>
undoUndo changenormal: u, select: u
redoRedo changenormal: U, select: U
earlierMove backward in historynormal: <A-u>, select: <A-u>
laterMove forward in historynormal: <A-U>, select: <A-U>
commit_undo_checkpointCommit changes to new checkpointinsert: <C-s>
yankYank selectionnormal: y, select: y
yank_to_clipboardYank selections to clipboardnormal: <space>y, select: <space>y
yank_to_primary_clipboardYank selections to primary clipboard
yank_joinedJoin and yank selections
yank_joined_to_clipboardJoin and yank selections to clipboard
yank_main_selection_to_clipboardYank main selection to clipboardnormal: <space>Y, select: <space>Y
yank_joined_to_primary_clipboardJoin and yank selections to primary clipboard
yank_main_selection_to_primary_clipboardYank main selection to primary clipboard
replace_with_yankedReplace with yanked textnormal: R, select: R
replace_selections_with_clipboardReplace selections by clipboard contentnormal: <space>R, select: <space>R
replace_selections_with_primary_clipboardReplace selections by primary clipboard
paste_afterPaste after selectionnormal: p, select: p
paste_beforePaste before selectionnormal: P, select: P
paste_clipboard_afterPaste clipboard after selectionsnormal: <space>p, select: <space>p
paste_clipboard_beforePaste clipboard before selectionsnormal: <space>P, select: <space>P
paste_primary_clipboard_afterPaste primary clipboard after selections
paste_primary_clipboard_beforePaste primary clipboard before selections
indentIndent selectionnormal: <gt>, select: <gt>
unindentUnindent selectionnormal: <lt>, select: <lt>
format_selectionsFormat selectionnormal: =, select: =
join_selectionsJoin lines inside selectionnormal: J, select: J
join_selections_spaceJoin lines inside selection and select spacesnormal: <A-J>, select: <A-J>
keep_selectionsKeep selections matching regexnormal: K, select: K
remove_selectionsRemove selections matching regexnormal: <A-K>, select: <A-K>
align_selectionsAlign selections in columnnormal: &, select: &
keep_primary_selectionKeep primary selectionnormal: ,, select: ,
remove_primary_selectionRemove primary selectionnormal: <A-,>, select: <A-,>
completionInvoke completion popupinsert: <C-x>
inline_completion_acceptAccept inline completion
inline_completion_dismissDismiss inline completion
inline_completion_nextCycle to next inline completion
inline_completion_prevCycle to previous inline completion
inline_completion_triggerTrigger inline completion
hoverShow docs for item under cursornormal: <space>k, select: <space>k
toggle_commentsComment/uncomment selectionsnormal: <C-c>, <space>c, select: <C-c>, <space>c
toggle_line_commentsLine comment/uncomment selectionsnormal: <space><A-c>, select: <space><A-c>
toggle_block_commentsBlock comment/uncomment selectionsnormal: <space>C, select: <space>C
rotate_selections_forwardRotate selections forwardnormal: ), select: )
rotate_selections_backwardRotate selections backwardnormal: (, select: (
rotate_selection_contents_forwardRotate selection contents forwardnormal: <A-)>, select: <A-)>
rotate_selection_contents_backwardRotate selections contents backwardnormal: <A-(>, select: <A-(>
reverse_selection_contentsReverse selections contents
expand_selectionExpand selection to parent syntax nodenormal: <A-o>, <A-up>, select: <A-o>, <A-up>
shrink_selectionShrink selection to previously expanded syntax nodenormal: <A-i>, <A-down>, select: <A-i>, <A-down>
select_next_siblingSelect next sibling in the syntax treenormal: <A-n>, <A-right>, select: <A-n>, <A-right>
select_prev_siblingSelect previous sibling the in syntax treenormal: <A-p>, <A-left>, select: <A-p>, <A-left>
select_all_siblingsSelect all siblings of the current nodenormal: <A-a>, select: <A-a>
select_all_childrenSelect all children of the current nodenormal: <A-I>, <S-A-down>, select: <A-I>, <S-A-down>
jump_forwardJump forward on jumplistnormal: <C-i>, <tab>, select: <C-i>, <tab>
jump_backwardJump backward on jumplistnormal: <C-o>, select: <C-o>
save_selectionSave current selection to jumplistnormal: <C-s>, select: <C-s>
jump_view_rightJump to right splitnormal: <C-w>l, <space>wl, <C-w><C-l>, <C-w><right>, <space>w<C-l>, <space>w<right>, select: <C-w>l, <space>wl, <C-w><C-l>, <C-w><right>, <space>w<C-l>, <space>w<right>
jump_view_leftJump to left splitnormal: <C-w>h, <space>wh, <C-w><C-h>, <C-w><left>, <space>w<C-h>, <space>w<left>, select: <C-w>h, <space>wh, <C-w><C-h>, <C-w><left>, <space>w<C-h>, <space>w<left>
jump_view_upJump to split abovenormal: <C-w>k, <C-w><up>, <space>wk, <C-w><C-k>, <space>w<up>, <space>w<C-k>, select: <C-w>k, <C-w><up>, <space>wk, <C-w><C-k>, <space>w<up>, <space>w<C-k>
jump_view_downJump to split belownormal: <C-w>j, <space>wj, <C-w><C-j>, <C-w><down>, <space>w<C-j>, <space>w<down>, select: <C-w>j, <space>wj, <C-w><C-j>, <C-w><down>, <space>w<C-j>, <space>w<down>
swap_view_rightSwap with right splitnormal: <C-w>L, <space>wL, select: <C-w>L, <space>wL
swap_view_leftSwap with left splitnormal: <C-w>H, <space>wH, select: <C-w>H, <space>wH
swap_view_upSwap with split abovenormal: <C-w>K, <space>wK, select: <C-w>K, <space>wK
swap_view_downSwap with split belownormal: <C-w>J, <space>wJ, select: <C-w>J, <space>wJ
transpose_viewTranspose splitsnormal: <C-w>t, <space>wt, <C-w><C-t>, <space>w<C-t>, select: <C-w>t, <space>wt, <C-w><C-t>, <space>w<C-t>
rotate_viewGoto next windownormal: <C-w>w, <space>ww, <C-w><C-w>, <space>w<C-w>, select: <C-w>w, <space>ww, <C-w><C-w>, <space>w<C-w>
rotate_view_reverseGoto previous window
hsplitHorizontal bottom splitnormal: <C-w>s, <space>ws, <C-w><C-s>, <space>w<C-s>, select: <C-w>s, <space>ws, <C-w><C-s>, <space>w<C-s>
hsplit_newHorizontal bottom split scratch buffernormal: <C-w>ns, <space>wns, <C-w>n<C-s>, <space>wn<C-s>, select: <C-w>ns, <space>wns, <C-w>n<C-s>, <space>wn<C-s>
vsplitVertical right splitnormal: <C-w>v, <space>wv, <C-w><C-v>, <space>w<C-v>, select: <C-w>v, <space>wv, <C-w><C-v>, <space>w<C-v>
vsplit_newVertical right split scratch buffernormal: <C-w>nv, <space>wnv, <C-w>n<C-v>, <space>wn<C-v>, select: <C-w>nv, <space>wnv, <C-w>n<C-v>, <space>wn<C-v>
wcloseClose windownormal: <C-w>q, <space>wq, <C-w><C-q>, <space>w<C-q>, select: <C-w>q, <space>wq, <C-w><C-q>, <space>w<C-q>
wonlyClose windows except currentnormal: <C-w>o, <space>wo, <C-w><C-o>, <space>w<C-o>, select: <C-w>o, <space>wo, <C-w><C-o>, <space>w<C-o>
select_registerSelect registernormal: ", select: "
insert_registerInsert registerinsert: <C-r>
copy_between_registersCopy between two registers
align_view_middleAlign view middlenormal: Zm, zm, select: Zm, zm
align_view_topAlign view topnormal: Zt, zt, select: Zt, zt
align_view_centerAlign view centernormal: Zc, Zz, zc, zz, select: Zc, Zz, zc, zz
align_view_bottomAlign view bottomnormal: Zb, zb, select: Zb, zb
scroll_upScroll view upnormal: Zk, zk, Z<up>, z<up>, select: Zk, zk, Z<up>, z<up>
scroll_downScroll view downnormal: Zj, zj, Z<down>, z<down>, select: Zj, zj, Z<down>, z<down>
match_bracketsGoto matching bracketnormal: mm, select: mm
surround_addSurround addnormal: ms, select: ms
surround_replaceSurround replacenormal: mr, select: mr
surround_deleteSurround deletenormal: md, select: md
select_textobject_aroundSelect around objectnormal: ma, select: ma
select_textobject_innerSelect inside objectnormal: mi, select: mi
select_all_textobjects_aroundSelect all textobjects around selectionsnormal: mA, select: mA
select_all_textobjects_innerSelect all textobjects inside selectionsnormal: mI, select: mI
goto_next_functionGoto next functionnormal: ]f, select: ]f
goto_prev_functionGoto previous functionnormal: [f, select: [f
goto_next_classGoto next type definitionnormal: ]t, select: ]t
goto_prev_classGoto previous type definitionnormal: [t, select: [t
goto_next_parameterGoto next parameternormal: ]a, select: ]a
goto_prev_parameterGoto previous parameternormal: [a, select: [a
goto_next_commentGoto next commentnormal: ]c, select: ]c
goto_prev_commentGoto previous commentnormal: [c, select: [c
goto_next_testGoto next testnormal: ]T, select: ]T
goto_prev_testGoto previous testnormal: [T, select: [T
goto_next_xml_elementGoto next (X)HTML elementnormal: ]x, select: ]x
goto_prev_xml_elementGoto previous (X)HTML elementnormal: [x, select: [x
goto_next_entryGoto next pairingnormal: ]e, select: ]e
goto_prev_entryGoto previous pairingnormal: [e, select: [e
goto_next_paragraphGoto next paragraphnormal: ]p, select: ]p
goto_prev_paragraphGoto previous paragraphnormal: [p, select: [p
dap_launchLaunch debug targetnormal: <space>Gl, select: <space>Gl
dap_restartRestart debugging sessionnormal: <space>Gr, select: <space>Gr
dap_toggle_breakpointsToggle breakpointsnormal: <space>Gb, select: <space>Gb
dap_continueContinue program executionnormal: <space>Gc, select: <space>Gc
dap_pausePause program executionnormal: <space>Gh, select: <space>Gh
dap_step_inStep innormal: <space>Gi, select: <space>Gi
dap_step_outStep outnormal: <space>Go, select: <space>Go
dap_nextStep to nextnormal: <space>Gn, select: <space>Gn
dap_variablesList variablesnormal: <space>Gv, select: <space>Gv
dap_terminateEnd debug sessionnormal: <space>Gt, select: <space>Gt
dap_edit_conditionEdit breakpoint condition on current linenormal: <space>G<C-c>, select: <space>G<C-c>
dap_edit_logEdit breakpoint log message on current linenormal: <space>G<C-l>, select: <space>G<C-l>
dap_switch_threadSwitch current threadnormal: <space>Gst, select: <space>Gst
dap_switch_stack_frameSwitch stack framenormal: <space>Gsf, select: <space>Gsf
dap_enable_exceptionsEnable exception breakpointsnormal: <space>Ge, select: <space>Ge
dap_disable_exceptionsDisable exception breakpointsnormal: <space>GE, select: <space>GE
shell_pipePipe selections through shell commandnormal: |, select: |
shell_pipe_toPipe selections into shell command ignoring outputnormal: <A-|>, select: <A-|>
shell_insert_outputInsert shell command output before selectionsnormal: !, select: !
shell_append_outputAppend shell command output after selectionsnormal: <A-!>, select: <A-!>
shell_keep_pipeFilter selections with shell predicatenormal: $, select: $
suspendSuspend and return to shellnormal: <C-z>, select: <C-z>
rename_symbolRename symbolnormal: <space>r, select: <space>r
incrementIncrement item under cursornormal: <C-a>, select: <C-a>
decrementDecrement item under cursornormal: <C-x>, select: <C-x>
record_macroRecord macronormal: Q, select: Q
replay_macroReplay macronormal: q, select: q
command_paletteOpen command palettenormal: <space>?, select: <space>?
goto_wordJump to a two-character labelnormal: gw
extend_to_wordExtend to a two-character labelselect: gw
goto_next_tabstopGoto next snippet placeholder
goto_prev_tabstopGoto next snippet placeholder
rotate_selections_firstMake the first selection your primary one
rotate_selections_lastMake the last selection your primary one

Language servers

Mitos has built-in support for the Language Server Protocol, providing IDE-like features — diagnostics, completion, goto-definition, rename and more, for any language that has a server configured. Language servers are separate programs you install yourself: the language support page lists which languages ship with a server configured, and the language server configuration wiki has per-server installation notes.

Run ms --health <language> to check whether Mitos found the configured server for a language. After changing a server’s configuration, :lsp-restart reloads it and :lsp-stop stops it; :lsp-workspace-command runs a command the server exposes for the workspace.

Features

Most features are bound by default (see the keymap); the relevant keys are listed below.

FeatureDefault key / command
Diagnostics — shown inline and in the gutternavigate with [d / ]d; Space-d / Space-D open the document / workspace pickers
Completionautomatic while typing; Ctrl-x triggers it manually
Hover documentationSpace-k
Signature help (parameter hints)automatic while typing arguments
Goto definition / declaration / type / references / implementationgd / gD / gy / gr / gi
Rename symbolSpace-r
Code actionsSpace-a
Document / workspace symbolsSpace-s / Space-S
Format document:format, or set format.on-save to format on save
Inlay hintsenable with lsp.inlay-hints.enable (see below)

Configuration

The [editor.lsp] section of config.toml groups LSP behaviour into feature subsections, including inlay-hints.enable, signature-help.auto-trigger, document-highlight.auto-trigger, and completion.snippets. Set enable = false to disable LSP entirely. How much diagnostic detail is shown inline at the cursor is configured separately in [editor.diagnostics.inline].

Which server(s) a language uses, the server’s command and arguments, and any language-specific settings are configured in languages.toml (see Language Server configuration). A language may use several servers: for each request the first server in the list that supports it is used, and a server’s features can be limited with only-features / except-features.

Debugging (DAP)

Mitos has experimental support for the Debug Adapter Protocol (DAP) for setting breakpoints, stepping through code, and inspecting variables. Install a debug adapter separately and configure it for the language you want to debug.

Configure an adapter

Debug adapters are configured per language in languages.toml. Some languages already have an adapter and launch templates configured. For example, Rust uses lldb-dap by default; its executable must be available on PATH.

To configure or override an adapter, add a language entry to your user languages.toml. This example matches the built-in Rust binary template:

[[language]]
name = "rust"

[language.debugger]
name = "lldb-dap"
transport = "stdio"
command = "lldb-dap"

[[language.debugger.templates]]
name = "binary"
request = "launch"
completion = [{ name = "binary", completion = "filename" }]
args = { program = "{0}" }

Each template defines a launch or attach request and adapter-specific arguments. completion defines the input prompts; {0}, {1}, and so on are replaced with the supplied values. Mitos sets the debug target’s cwd to the editor’s current working directory.

See the debugger configuration reference for TCP transport, attach templates, and the complete list of fields. Project-local configuration in .mitos/languages.toml is subject to workspace trust.

Start a session

  1. Build the program you want to debug, with debug information enabled.
  2. Open a source file in Mitos. Its language determines which adapter and templates are used.
  3. Press Space G b on a line to toggle a breakpoint.
  4. Press Space G l, choose a template, and fill in its prompts.
  5. When execution pauses, step through the code or inspect variables using the commands below.

G is uppercase. Press the keys in sequence. The debug menu stays active after a command; while it is active, use the suffix keys such as n or c directly. Press Esc to leave the menu.

You can also start a template from the command line. For a Rust binary built with cargo build, for example:

:debug-start binary target/debug/my-program

Replace my-program with your executable’s name. To connect to an already running TCP debug adapter, use :debug-remote <ip>:<port> <template> [parameters...], for example :debug-remote 127.0.0.1:4711 binary target/debug/my-program.

Debugging commands

These bindings are available in normal and select mode:

KeysAction
Space G lSelect a template and start debugging
Space G bToggle breakpoints on selected lines
Space G cContinue execution
Space G hPause execution
Space G nStep over
Space G iStep in
Space G oStep out
Space G vList variables
Space G s tSwitch thread
Space G s fSwitch stack frame
Space G rRestart the session
Space G tEnd the session
Space G Ctrl-cEdit the breakpoint condition on the current line
Space G Ctrl-lEdit the breakpoint log message on the current line
Space G e / Space G EEnable / disable exception breakpoints

Use :debug-eval <expression> to evaluate an expression in the current debug context. Supported expressions and debugging capabilities depend on the adapter.

Troubleshooting

  • No debug adapter available for language: check the current file’s language and its [language.debugger] configuration.
  • Failed to start debug client: check the adapter’s executable path, arguments, and transport.
  • No debug config with given name: use a template name defined in [[language.debugger.templates]].
  • Workspace is not trusted: use :workspace-trust to allow debugging in a workspace you trust. See workspace trust.

Spell checking

Mitos can spell-check documents and surface misspellings as diagnostics. Corrections and “add to dictionary” are offered as code actions, alongside any from language servers.

Spell checking is opt-in. Mitos will use tree-sitter to check only requested regions of a language to avoid noisy diagnostics for keywords. Multiple dictionaries may be configured at once. A word is accepted if any configured dictionary allows it.

Enabling

There are several ways to turn spell checking on:

  1. Per buffer, with the command:

    :set-spelling-language <language>
    

    Pass several languages to check against all of them (:set-spelling-language en_US en_GB), or off to disable checking for the buffer. With no argument it reports the current language(s). This choice overrides the settings below and persists across config reloads for that buffer.

  2. Per path, with an .editorconfig spelling_language key:

    [*.md]
    spelling_language = en_US
    
  3. Per language, in languages.toml (see Languages):

    [[language]]
    name = "markdown"
    spelling.languages = ["en_US"]
    
  4. Globally, in config.toml under [editor.spelling]:

    [editor.spelling]
    languages = ["en_US"]
    

Scope

What gets checked is controlled per language by a tree-sitter spellcheck.scm query: a node captured @spell is checked, and @nospell excludes part of one. Most languages inject a shared comment grammar, so their comments are checked with no language-specific query. The bundled queries also cover:

LanguageChecked textExcluded text
MarkdownProse, headings, lists, and visible link textInline code and link destinations
PythonModule, class, and function docstrings, including after leading commentsOrdinary strings, bytes, f-strings, and escape sequences
HTMLVisible text, including headings and link labelsTags, attributes, entities, code elements (code, pre, kbd, samp), scripts, and styles
JSX / TSXJSX text, including nested JSX inside expressionsComponent names, attributes, expressions, and code elements
Git commit messagesSubject, body, and breaking-change descriptionsConventional prefixes, trailers, template comments, and diffs
reStructuredTextHeadings, paragraphs, lists, and quotationsLiteral code, interpreted roles, references, and directive bodies
TypstProse, headings, emphasis, and content blocksCode, ordinary strings, math, labels, references, URLs, and escapes

Python docstrings are treated as prose; examples and markup inside them are not parsed separately. reStructuredText directive bodies are left unchecked because their syntax nodes do not distinguish prose from code.

Coverage depends on the queries present in your runtime directories. Strings and identifiers are checked only where a language’s query opts them in. With syntax parsing, text outside @spell captures is left unchecked. Plain text and files without a syntax tree are checked in full.

See Adding spellcheck queries to extend coverage to a new language.

Use ]s to select the next spelling finding and [s to select the previous one. These commands skip other diagnostics, support counts such as 3]s, and stop at the first or last finding without wrapping. In select mode they extend the selection; with multiple cursors each cursor moves independently.

The s textobject selects the diagnosed word under the cursor with mis or mas. Use mIs or mAs to select all spelling findings fully contained in the current selection. Both inside and around variants use the finding’s exact range.

Correcting findings

Move onto a misspelled word (or select it with ]s, [s, or mis) and press Space-a to open code actions. Choose a Replace ... action and press Enter to replace the whole word. Press u to undo the correction, or Escape to dismiss the menu without changing the text.

Spelling actions work without a language server. With multiple selections, the menu offers corrections for findings overlapping the primary selection. The same menu offers Add ... to dictionary to accept a word permanently.

Ignoring a word for this session

Choose Ignore 'word' for this session (language) from Space-a to suppress that word until Mitos exits. Matching is case-insensitive and applies to the whole word, including in other open buffers and buffers opened later that use the chosen dictionary. Other misspellings continue to be checked.

With multiple dictionaries, choose the language the ignore should apply to. A buffer ignores the word if any of its configured dictionaries has a session ignore for it. The ignore survives edits, configuration reloads, and toggling spell checking off and back on. It does not change the document or write to your personal dictionary or configuration files.

Ignoring a word permanently

Choose Ignore 'word' forever (language) from Space-a to keep ignoring it after restarting Mitos, across projects using that dictionary. Matching is case-insensitive and applies to the whole word, just like a session ignore.

Persistent ignores are stored as UTF-8 text, one word per line, in <config>/spelling/<language>.ignore, for example ~/.config/mitos/spelling/en_US.ignore on Linux. They are loaded when the dictionary is first used. Remove a word from that file and restart Mitos to check it again, or add words there by hand.

Ignore forever suppresses findings without adding words to the dictionary’s suggestion vocabulary. Use Add ... to dictionary when you also want the word available as a correction suggestion. A failed save reports an error and leaves the word’s checking behavior unchanged.

Dictionaries

Dictionaries are Hunspell .aff/.dic pairs loaded from dictionaries/<language>/<language>.{aff,dic} in the runtime directories. ms --health lists those directories.

Mitos bundles only en_US. To add another language, drop its Hunspell files into a runtime directory, named after the language code you reference in the config. For example, for de_DE:

~/.config/mitos/runtime/dictionaries/de_DE/de_DE.aff
~/.config/mitos/runtime/dictionaries/de_DE/de_DE.dic

Hunspell dictionaries for most languages are distributed with LibreOffice and by the various aspell/Hunspell projects.

Personal dictionary

The “Add to dictionary” code action accepts a word permanently. It is appended to a personal dictionary, one word per line, under Mitos’s state directory:

<state>/dictionaries/<language>.txt

(<state> is e.g. ~/.local/state/mitos on Linux.) The file is namespaced per language, so a word added for one language is not accepted in another. You can edit it by hand; entries are loaded the next time that language’s dictionary is read.

Tuning

Spell checking produces false positives on names, jargon, and code-like tokens. URLs and email addresses are skipped automatically. Beyond that, three knobs (settable globally under [editor.spelling] and per language in languages.toml) reduce noise:

KeyDescription
wordsExtra accepted words, matched case-insensitively.
ignore-regexesTokens matching any of these are not checked (e.g. "^[A-Z0-9_]+$").
min-word-lengthTokens shorter than this are not checked.

When both global and per-language settings are present, languages and min-word-length are replaced by the language’s value, while words and ignore-regexes are added to the global lists. See [editor.spelling] for the full reference.

Language support

The following languages and Language Servers are supported. To use Language Server features, you must first configure the appropriate Language Server.

You can check the language support in your installed Mitos version with ms --health.

Also see the Language Configuration docs and the Adding Languages guide for more language configuration information.

LanguageSyntax HighlightingTreesitter TextobjectsAuto IndentCode Navigation TagsRainbow BracketsDefault language servers
ada✓✓ada_language_server
adl✓✓✓
agda✓
alloy✓
amber✓✓✓✓✓amber-lsp
astro✓astro-ls
awk✓✓awk-language-server
bash✓✓✓✓✓bash-language-server
basic✓✓✓✓
bass✓bass
batch✓✓✓✓✓
beancount✓beancount-language-server
bibtex✓texlab
bicep✓bicep-langserver
bitbake✓bitbake-language-server
blade✓✓✓
blueprint✓blueprint-compiler
bovex
c✓✓✓✓✓clangd
c-sharp✓✓✓✓✓roslyn-language-server, OmniSharp, csharp-ls
c3✓c3-lsp
cabalhaskell-language-server-wrapper
caddyfile✓✓✓
cairo✓✓✓cairo-language-server
capnp✓✓
cel✓
chuck✓
circom✓circom-lsp
clarity✓✓✓✓✓clarinet
clojure✓✓✓✓✓clojure-lsp
cmake✓✓✓✓neocmakelsp, cmake-language-server
codeql✓✓codeql
comment✓
common-lisp✓✓✓✓cl-lsp
concerto✓✓✓
cpon✓✓
cpp✓✓✓✓✓clangd
cross-config✓✓✓taplo, tombi
crystal✓✓✓✓✓crystalline, ameba-ls
css✓✓✓✓vscode-css-language-server
csv✓
cue✓cue
cylc✓✓✓
cython✓✓✓
d✓✓✓serve-d
dart✓✓✓✓✓dart
dbml✓
debian✓debian-lsp
devicetree✓dts-lsp
dhall✓✓dhall-lsp-server
diff✓
djot✓
docker-bake✓✓✓✓✓docker-language-server
docker-compose✓✓✓docker-compose-langserver, yaml-language-server, docker-language-server
dockerfile✓✓docker-langserver, docker-language-server
dot✓dot-language-server
doxyfile✓✓✓✓
droolsdrools-lsp
dtd✓
dune✓
dunstrc✓
earthfile✓✓✓earthlyls
ebnf✓ebnfer
edoc✓
eex✓
eiffel✓✓✓eiffel-language-server
ejs✓
elisp✓✓
elixir✓✓✓✓✓elixir-ls, expert
elm✓✓✓✓elm-language-server
elvish✓elvish
embedded-perl✓
env✓✓
erb✓
erlang✓✓✓✓✓erlang_ls, elp
esdl✓
fennel✓✓fennel-ls
fga✓✓✓✓✓
fidl✓
fish✓✓✓fish-lsp
flatbuffers✓
forth✓forth-lsp
fortran✓✓fortls
freebasic✓✓✓✓
fsharp✓✓fsautocomplete
gas✓✓asm-lsp
gdscript✓✓✓✓✓ncat
gemini✓
gherkin✓
ghostty✓ghostty-ls
git-attributes✓
git-cliff-config✓✓✓taplo, tombi
git-commit✓✓commit-lsp
git-config✓✓✓
git-ignore✓
git-notes✓
git-rebase✓
github-action✓✓✓✓actions-languageserver, yaml-language-server, zizmor
gitlab-ci✓✓✓✓✓yaml-language-server, gitlab-ci-ls
gjs✓✓✓✓typescript-language-server, vscode-eslint-language-server, ember-language-server
gleam✓✓✓gleam
glimmer✓ember-language-server
glsl✓✓✓✓✓glsl_analyzer, glsld
gn✓
gnuplot✓
go✓✓✓✓✓gopls, golangci-lint-langserver
go-format-string✓✓
godot-resource✓✓✓
gomod✓gopls
gotmpl✓gopls
gowork✓gopls
gpr✓ada_language_server
graphql✓✓✓graphql-lsp
gren✓✓
groovy✓✓
gts✓✓✓✓typescript-language-server, vscode-eslint-language-server, ember-language-server
hare✓✓hare-lsp
haskell✓✓✓✓haskell-language-server-wrapper
haskell-literate✓haskell-language-server-wrapper
haskell-persistent✓
haxe✓✓
hcl✓✓✓✓terraform-ls
hdl✓hdls
heex✓✓elixir-ls, expert
helm✓helm_ls
hocon✓✓✓
hoon✓
hosts✓
html✓✓✓✓vscode-html-language-server, superhtml
htmldjango✓djlsp, vscode-html-language-server, superhtml
hurl✓✓✓
hy✓hyuga
hyprlang✓✓✓hyprls
idrisidris2-lsp
iex✓
ini✓✓
ink✓✓✓
inko✓✓✓✓
janet✓✓✓
java✓✓✓✓✓jdtls
javascript✓✓✓✓✓typescript-language-server
jinja✓
jjconfig✓✓✓taplo, tombi
jjdescription✓
jjrevset✓
jjtemplate✓
jq✓✓jq-lsp
jsdoc✓
json✓✓✓✓vscode-json-language-server
json-ld✓✓✓✓vscode-json-language-server
json5✓✓✓✓
jsonc✓✓✓✓vscode-json-language-server
jsonnet✓jsonnet-language-server
jsx✓✓✓✓✓typescript-language-server
julia✓✓✓✓✓julia
just✓✓✓✓just-lsp
kcl✓kcl-language-server
kconfig✓✓
kdl✓✓✓✓✓
klog✓
koka✓✓koka
kotlin✓✓✓✓✓kotlin-language-server
koto✓✓✓✓koto-ls
latex✓✓✓texlab
ld✓✓
ldif✓
lean✓lake
ledger✓
less✓✓✓vscode-css-language-server
llvm✓✓✓
llvm-mir✓✓✓
llvm-mir-yaml✓✓
log✓
lpf✓
lua✓✓✓✓✓lua-language-server
lua-format-string✓
luap✓
luau✓✓✓luau-lsp
mail✓✓✓
make✓✓
markdoc✓markdoc-ls
markdown✓✓marksman, markdown-oxide, rumdl
markdown-rustdoc✓
markdown.inline✓
matlab✓✓✓
mermaid✓
meson✓✓mesonlsp
metamath✓✓mm-lsp-server
mintmint
miseconfig✓✓✓taplo, tombi
mojo✓✓✓pixi
moonbit✓✓✓✓moonbit-lsp
move✓✓
msbuild✓✓
nasm✓✓asm-lsp
nearley✓✓
nestedtext✓✓✓
nginx✓nginx-language-server
nickel✓✓nls
nim✓✓✓✓nimlangserver
nix✓✓✓✓✓nil, nixd
nu✓✓✓nu, nu-lint
nunjucks✓
ocaml✓✓✓✓ocamllsp
ocaml-interface✓ocamllsp
odin✓✓✓ols
ohm✓✓✓
opencl✓✓✓clangd
openscad✓openscad-lsp
org✓
p✓
pascal✓✓pasls
passwd✓
pem✓
penrose✓✓
perl✓✓✓✓✓perlnavigator
pest✓✓✓pest-language-server
php✓✓✓✓✓intelephense
php-only✓✓
picat✓✓✓✓✓
pip-requirements✓
pkgbuild✓✓✓termux-language-server, bash-language-server
pkl✓✓pkl-lsp
po✓✓
pod✓
ponylang✓✓✓pony-lsp
powershell✓✓
prisma✓✓prisma-language-server
prolog✓✓swipl
properties✓✓
protobuf✓✓✓✓buf, pb, protols
proverif✓
prql✓
ptx✓✓
pug✓
puppet✓✓puppet-languageserver
purescript✓✓purescript-language-server
python✓✓✓✓✓ty, ruff, jedi-language-server, pylsp, zuban
qml✓✓✓qmlls
qmv
quarto✓✓
quint✓quint-language-server
r✓✓✓✓✓R
racket✓✓✓racket
regex✓✓
rego✓regols
rescript✓✓rescript-language-server
ripple✓✓✓ripple-language-server
rmarkdown✓✓R
robot✓✓robotcode, robotframework_ls
robots.txt✓✓✓
ron✓✓✓✓ron-lsp
rpmspec✓
rshtml✓✓rshtml-analyzer, vscode-html-language-server, superhtml
rst✓✓
ruby✓✓✓✓✓ruby-lsp, solargraph
rust✓✓✓✓✓rust-analyzer
rust-format-args✓
rust-format-args-macro✓✓✓✓
sage✓✓
scala✓✓✓✓✓metals
scfg✓
scheme✓✓✓✓✓
scss✓✓✓vscode-css-language-server
sgf✓✓✓
shellcheckrc✓✓
slang✓✓✓slangd
slint✓✓✓slint-lsp
slisp✓✓
smali✓✓smalisp
smithy✓cs
sml✓millet-ls
snakemake✓✓pylsp
solidity✓✓✓solc
sourcepawn✓✓sourcepawn-studio
spade✓✓✓swim
spicedb✓✓
sql✓✓✓
ssh_client_config✓
starlark✓✓✓✓starpls, buck2
strace✓
strictdoc✓✓
styx✓✓✓✓styx
supercollider✓
svelte✓✓✓✓✓svelteserver
sway✓✓✓forc
swift✓✓✓✓✓sourcekit-lsp
systemd✓✓systemd-lsp
systemverilog✓svlangserver, verible-verilog-ls
t32✓
tablegen✓✓✓
tact✓✓✓
task✓
tcl✓✓
teal✓teal-language-server
templ✓✓✓✓templ
tera✓
textproto✓✓✓
tfvars✓✓terraform-ls
thrift✓
tilt✓✓✓✓tilt
tlaplus✓
todotxt✓
tolk✓✓
toml✓✓✓✓✓taplo, tombi
tql✓✓
tsq✓✓ts_query_ls
tsx✓✓✓✓✓typescript-language-server
twig✓
typescript✓✓✓✓✓typescript-language-server
typespec✓✓✓tsp-server
typst✓✓✓tinymist
ungrammar✓
unison✓✓✓✓✓
uxntal✓
v✓✓✓v-analyzer
vala✓✓vala-language-server
varlink✓varlink-language-server
vento✓
verilog✓✓verible-verilog-ls
vhdl✓vhdl_ls
vhs✓
vim✓✓
vue✓✓✓vue-language-server
wast✓
wat✓wat_server
webc✓
werk✓
wesl✓✓
wgsl✓✓✓✓✓wgsl-analyzer
wikitext✓wikitext-lsp
wit✓✓
woodpecker-ci✓✓✓✓✓yaml-language-server
wren✓✓✓
xit✓
xml✓✓✓✓
xtc✓
xwiki✓
yaml✓✓✓✓yaml-language-server, ansible-language-server
yara✓yls
yuck✓✓✓
zig✓✓✓✓✓zls

Workspace trust

Mitos has several features that can execute arbitrary code:

  • Language servers (LSP)
  • Debug adapters (DAP)
  • Local workspace configuration (.mitos/config.toml, .mitos/languages.toml)
  • Git integration (filters and other commands in a repository’s .git/config)

To protect against malicious projects (a checked-out PR, a freshly cloned repository, etc.) Mitos gates these behind explicit per-workspace trust. By default language servers start automatically (their binaries come from $PATH, not from the workspace) and debug adapters may be launched, but loading .mitos/config.toml or .mitos/languages.toml and trusting a repository’s .git/config requires opting in. Note that debug adapters are never started automatically — you launch them yourself — but the same trust level still gates whether they may run. The model is intentionally similar to direnv: you run :workspace-trust once per workspace and Mitos remembers across sessions.

Granting trust

When Mitos opens a file inside a workspace it has never seen before, a modal trust prompt asks:

  • Trust — allow the workspace permanently.
  • Never — exclude the workspace; never prompt again.

<Esc> (or any other dismissal) caches “untrusted for this session” so the prompt doesn’t re-fire for every file you open in the workspace. The next time you start Mitos in that workspace, it’ll prompt again.

A small [⚠] indicator appears in the bottom-right of the editor (next to the macro-recording [@]) whenever the workspace is in restricted mode and running :workspace-trust would change observable behavior — i.e. when there’s a local config to load or an LSP that would start.

You can also run :workspace-trust / :workspace-untrust / :workspace-exclude directly from the typed command prompt.

Revoking trust

Run :workspace-untrust to revoke a workspace’s trust grant. The next time you open a file in that workspace, you’re back to the untrusted hint.

Detecting changes after trust was granted

When you trust a workspace, Mitos records a hash of every file under .mitos/. If those files change afterwards (a malicious checkout, an inadvertent rebase, etc.) Mitos detects the mismatch on the next open and reports the workspace as stale:

Workspace `.mitos/` config changed since `:workspace-trust`. Local config
not loaded. Run `:workspace-trust` to re-allow.

In the stale state, language servers continue to run (they use the globally-configured binaries on $PATH, which are unchanged), but .mitos/config.toml and .mitos/languages.toml are not loaded. Run :workspace-trust again to re-pin the new hash.

Storage

Trust grants live in data_dir()/workspace_trust/, one small file per workspace. The filename is the SHA-256 of the workspace’s absolute path; the contents look like:

path = /home/user/proj1
hash = sha256:abc123...
excluded = false
  • Linux, macOS: ~/.local/share/mitos/workspace_trust/
  • Windows: %AppData%\Roaming\mitos\workspace_trust\

The one-file-per-workspace shape is safe under multiple concurrent Mitos instances — different workspaces never write the same file.

Configuration

Settings live under [editor.workspace-trust]:

KeyValuesDefaultEffect
level"none", "servers", "insecure""servers"What is auto-trusted in every workspace. See below.
prompttrue, falsetrueWhether to surface the modal popup. The [⚠] indicator is shown regardless.
trustedlist of glob patterns[]Workspaces matching a pattern are trusted without a grant. Discouraged; see below.

Default: trust servers, prompt before loading workspace config.

[editor.workspace-trust]
level = "servers"
prompt = true

Language servers start automatically in every workspace — their binaries come from $PATH and are not workspace-controlled — and debug adapters you launch are allowed to run. The modal only appears when opening a file in a workspace whose .mitos/config.toml or .mitos/languages.toml would unlock something. Trust everything else with one keystroke per workspace, deny with another.

Maximum security: never prompt, trust each workspace by hand.

[editor.workspace-trust]
level = "none"
prompt = false

Nothing trusts implicitly: language servers, debug adapters, local config, and git Trust::Full are all off until you run :workspace-trust. The popup never appears; the [⚠] indicator in the bottom-right is your only signal that the current workspace is restricted. Suited to users who would rather grant trust as a deliberate action than dismiss a dialog.

Warning

level = "insecure" is highly discouraged. It implicitly trusts every workspace you open, which defeats the protection entirely: a checked-out PR with a malicious .mitos/config.toml would get its configuration loaded and any language server it defines launched, with no prompt and no indicator. Only set this if you accept full responsibility for what’s in every project directory you cd into.

Trusting workspaces by path (discouraged)

If you keep all your repositories under a predictable layout, you can trust them in bulk with glob patterns instead of granting each workspace individually:

[editor.workspace-trust]
trusted = [
  "~/src/github.com/me/*",
  "~/work/repos/*",
]

A workspace whose path matches a pattern is trusted for everything, just as if you had run :workspace-trust in it. ~ and environment variables are expanded.

Warning

This is weaker than an explicit grant and is discouraged. It skips the .mitos/ change detection entirely (a malicious checkout under a matched directory is never flagged as stale), and it trusts any repository that later lands under a matching path — including one you clone into ~/src/github.com/me/ from an untrusted source. Prefer granting trust per workspace; reach for this only if the prompts are genuinely disruptive to your workflow. An explicit :workspace-exclude still overrides a matching pattern.

Git trust

Workspace trust also gates how Mitos opens git repositories. Untrusted workspaces are opened in gix’s Trust::Reduced mode; trusted workspaces use Trust::Full.

Under Trust::Reduced, gix still runs the full filter pipeline (so built-in conversions like core.autocrlf keep working) but ignores configuration coming from the untrusted, repository-local .git/config. That means filter.*.clean / filter.*.smudge drivers and similar keys that would otherwise execute external programs are dropped until you trust the workspace.

Mitos forces this trust level explicitly rather than letting gix infer it from .git directory ownership — a malicious .git/config in a directory you happen to own is still treated as untrusted until you run :workspace-trust.

Configuration

This page is the complete reference for user configuration. It lists every setting accepted by config.toml and languages.toml; the linked chapters explain the larger features and show more examples.

Configuration files and precedence

The user configuration directory is:

  • Linux and macOS: ~/.config/mitos
  • Windows: %AppData%\mitos

The main file is config.toml. Open it from Mitos with :config-open, then apply changes with :config-reload. On Unix, sending USR1 to the Mitos process also reloads it, for example with pkill -USR1 ms.

Pass -c <path> or --config <path> to use another config.toml:

ms --config path/to/custom-config.toml

A project may override both files with .mitos/config.toml and .mitos/languages.toml. Project values are merged over user values, which are merged over built-in defaults. Project files are loaded only after the workspace is trusted; see Workspace trust.

Important

[editor.workspace-trust] is always read from the user config.toml. A project cannot weaken the trust policy that decides whether its own files may be loaded.

Unknown config.toml editor keys are rejected. This makes misspelled settings fail visibly instead of being silently ignored.

Structure and compatibility

Settings are grouped by feature. For example, use editor.lsp.messages.enable and editor.completion.trigger-delay. Feature tables can be written explicitly or with TOML dotted keys. Editor durations are milliseconds; language-server request timeouts are seconds.

Legacy paths remain accepted, including editor.lsp.display-messages, editor.auto-format, and the old boolean or string feature shorthands. Each file is normalized before layering, so a project override wins regardless of which spelling is used. Defining the same logical setting with both spellings in one file is an error. Tables merge recursively and ordinary arrays replace the earlier value, including an explicit empty array.

Runtime commands use editor-relative paths, for example :toggle lsp.inlay-hints.enable and :set completion.trigger-delay 100. Terminal settings use terminal.*, such as :toggle terminal.true-color. Legacy command paths remain accepted; completions show canonical paths.

Configuration migration

Mitos accepts known legacy settings and converts them in memory before merging configuration layers. Startup and :config-reload leave configuration files unchanged, including files managed by Home Manager or other dotfile managers.

To update a file to the current feature sections, run:

  • :config-migrate for your user config.toml, or the file selected with -c or --config for this session.
  • :config-migrate-workspace for the current trusted project’s .mitos/config.toml.

Each command opens a confirmation dialog naming the file. Cancel is selected initially; choose Migrate to rewrite it. Escape also cancels. User and workspace migration are separate actions. Project migration changes the project’s working tree. Untrusted, stale, or excluded project configuration cannot be migrated.

Migration preserves values, comments, keybindings, and custom commands. All loaded configuration layers must validate before any rewrite. Each changed file receives an adjacent backup named config.toml.bak; existing backups are preserved with numbered suffixes such as config.toml.bak.1. Replacements are atomic and preserve file permissions and symlinks. For symlinks, the target file is updated and the backup is placed beside that target. The dialog shows the target, and a status message reports the updated file and backup path. Save or discard unsaved edits to an open configuration buffer before migrating.

Already migrated files are left unchanged, and missing files are not created. Read-only files are not rewritten. If a backup or rewrite fails, the loaded settings remain usable through compatibility aliases.

For generated or read-only configuration, export the migrated TOML:

ms --migrate-config ~/.config/mitos/config.toml --stdout > /tmp/mitos-config.toml

This command reads only the specified file, prints its migrated contents (or the original contents if no migration is needed), and creates no backups. Inspect the output and update the source managed by Home Manager or your dotfile manager. Export to a different file; redirecting stdout to the input file would truncate it.

An existing explicit workspace trust grant is updated only when the migration and its backup explain every change to the trusted configuration fingerprint. Other changes still require review and renewed trust; migration never grants trust to an untrusted project. languages.toml is unchanged.

config.toml reference

config.toml accepts five top-level entries:

KeyTypeDefaultDescription
themestring or table{ light = "modus_operandi", dark = "modus_vivendi" }Theme name, or { light = "...", dark = "...", fallback = "..." }. fallback is optional and defaults to dark. See Themes.
editortableSee belowShared editor behavior and UI settings.
terminaltableSee belowTerminal capabilities, keyboard protocol, and external launcher.
keystableBuilt-in keymapPer-mode key bindings under [keys.normal], [keys.insert], and [keys.select]. Entries are merged with the built-in keymap. See Key remapping.
commandstable{}User-defined command-mode commands. See Custom commands.

Example:

theme = "onedark"

[editor]
line-number = "relative"
mouse.enable = false
icons = true # Requires a Nerd Font.

[editor.cursor.shape]
insert = "bar"
normal = "block"
select = "underline"

[keys.normal]
C-s = ":write"

[commands]
":wq" = [":write", ":quit"]

[editor]

For examples and behavioral details, see the Editor chapter.

KeyType / accepted valuesDefaultDescription
welcome-screenbooleantrueShow the welcome screen when Mitos starts without a file.
shellarray of stringsUnix: ["sh", "-c"]; Windows: ["cmd", "/C"]Command prefix used to execute shell commands.
line-number"absolute" or "relative""absolute"Line-number display mode. Relative numbers become absolute in insert mode and when the view is unfocused.
guttersarray or table["diagnostics", "spacer", "line-numbers", "spacer", "diff"]Gutter layout and options. See [editor.gutters].
auto-pairstable{ enable = true }Insert matching delimiters. See [editor.auto-pairs].
spellingtableDisabledDictionaries, accepted words, and token filters. See [editor.spelling].
text-widthinteger80Width used by :reflow and optionally soft wrapping.
idle-timeoutmilliseconds250Idle delay used by editor UI timers.
continue-commentsbooleantrueContinue a line comment when Enter creates a new line inside it.
auto-infobooleantrueDisplay contextual information popups.
iconsbooleanfalseShow UI icons. The glyphs require a Nerd Font.
file-pickertableSee belowFile-picker and global-search traversal. See [editor.file-picker].
file-explorertableSee belowFile-explorer traversal. See [editor.file-explorer].
buffer-pickertableSee belowInitial selection in the buffer picker. See [editor.buffer-picker].
statuslinetableSee belowStatusline layout and labels. See [editor.statusline].
searchtableSee belowSearch matching and wrap behavior. See [editor.search].
lsptableSee belowGlobal LSP behavior. See [editor.lsp].
rulersarray of integers[]Columns at which to draw rulers; a language may override this.
whitespacetableSee belowVisible whitespace rendering. See [editor.whitespace].
breadcrumbtableSee belowBreadcrumb navigation bar. See [editor.breadcrumb].
indent-guidestableSee belowVertical indentation guides. See [editor.indent-guides].
soft-wraptableSee belowSoft wrapping. See [editor.soft-wrap].
jump-label-alphabetstring of unique characters"abcdefghijklmnopqrstuvwxyz"Alphabet used to generate two-character jump labels. Earlier characters are used first.
inline-blametableSee belowGit blame rendered at the end of buffer lines. See [editor.inline-blame].
workspace-trusttableSee belowImplicit workspace trust policy. See [editor.workspace-trust].
bufferlinetableSee belowSee [editor.bufferline].
clipboardtableSee belowSee [editor.clipboard].
completiontableSee belowSee [editor.completion].
cursortableSee belowSee [editor.cursor].
diagnosticstableSee belowSee [editor.diagnostics].
filestableSee belowSee [editor.files].
formattableSee belowSee [editor.format].
indenttableSee belowSee [editor.indent].
inline-completiontableSee belowSee [editor.inline-completion].
mousetableSee belowSee [editor.mouse].
rainbow-bracketstableSee belowSee [editor.rainbow-brackets].
scrolltableSee belowSee [editor.scroll].

[editor.scroll]

KeyType / accepted valuesDefaultDescription
paddinginteger5Minimum screen lines kept above and below the cursor when possible.
wheel-linesinteger3Lines moved by one scroll-wheel step.

[editor.mouse]

KeyType / accepted valuesDefaultDescription
enablebooleantrueEnable mouse input.
yank-registercharacter"*"Register used for selections made with the mouse.
middle-click-pastebooleantruePaste the primary selection on middle click.

[editor.clipboard]

KeyType / accepted valuesDefaultDescription
default-yank-registercharacter"\""Register used by yank and paste commands when none is selected explicitly.
providerprovider name or custom tableAuto-detectedClipboard integration. See [editor.clipboard.provider].

[editor.cursor]

KeyType / accepted valuesDefaultDescription
highlight-linebooleanfalseHighlight every line containing a cursor.
highlight-columnbooleanfalseHighlight every column containing a cursor.
shapetableAll modes "block"Cursor shape by mode. See [editor.cursor.shape].

[editor.completion]

KeyType / accepted valuesDefaultDescription
auto-triggerbooleantrueShow completion automatically.
wordtable{ enable = true, trigger-length = 7 }Complete words from open buffers. See [editor.completion.word].
trigger-delaymilliseconds250Delay after typing a word character before completion is shown; use 5 for effectively immediate completion.
preview-insertbooleantrueTemporarily insert the selected completion item while browsing the menu.
trigger-lengthinteger2Minimum word length that triggers automatic completion.
replacebooleanfalseAsk LSP completions to replace the whole word instead of only the text before the cursor.

Path completion is configured by path.enable (default true); word completion has its own [editor.completion.word] table.

[editor.completion.path]

KeyType / accepted valuesDefaultDescription
enablebooleantrueComplete filesystem paths recognized at the cursor.

[editor.format]

KeyType / accepted valuesDefaultDescription
on-savebooleantrueFormat on save when the current language also enables auto-format.

[editor.files]

KeyType / accepted valuesDefaultDescription
auto-savetableDisabledSave on focus loss and/or after a delay. See [editor.files.auto-save].
reloadtableSee belowReload buffers when files change externally. See [editor.files.reload].
watchtableSee belowNative file watching, traversal, and Git refreshes. See [editor.files.watch].
default-line-ending"native", "lf", "crlf", "ff", "cr", or "nel""native"Line ending for new documents. The last three values require the unicode-lines build feature.
editor-configbooleantrueApply supported settings from .editorconfig files.

Saving, autosave, reloads, and native watching are configured in the save, auto-save, reload, and watch subsections.

[editor.inline-completion]

KeyType / accepted valuesDefaultDescription
trigger-delaymilliseconds150Delay after editing before requesting LSP ghost text.
auto-triggerbooleantrueRequest LSP ghost text automatically in insert mode.

[editor.bufferline]

KeyType / accepted valuesDefaultDescription
show"never", "always", or "multiple""never"When to show open buffers at the top of the editor.

[editor.files.save]

KeyType / accepted valuesDefaultDescription
insert-final-newlinebooleantrueAdd a final line ending on write when missing.
atomicbooleantrueWrite through a temporary file and replace the original. Safer against interruption, but may confuse file watchers.
trim-final-newlinesbooleanfalseRemove line endings after the final line ending on write.
trim-trailing-whitespacebooleanfalseRemove whitespace before line endings on write.

[editor.indent]

The smart-tab subsection controls syntax-aware Tab behavior.

KeyType / accepted valuesDefaultDescription
smart-tabtableSee belowSyntax-aware Tab behavior. See [editor.indent.smart-tab].
heuristic"simple", "tree-sitter", or "hybrid""hybrid"Indentation strategy. Unavailable strategies fall back from hybrid to tree-sitter to simple.

[editor.diagnostics]

KeyType / accepted valuesDefaultDescription
inlinetableSee belowDiagnostics rendered within buffer text. See [editor.diagnostics.inline].
end-of-linetable{ severity = "hint" }End-of-line diagnostic severity.

[editor.diagnostics.end-of-line]

KeyType / accepted valuesDefaultDescription
severity"disable", "hint", "info", "warning", or "error""hint"Minimum severity shown at the end of a line.

[editor.rainbow-brackets]

KeyType / accepted valuesDefaultDescription
enablebooleanfalseColor matching bracket levels. The language needs a rainbows.scm query.

[editor.breadcrumb]

KeyType / accepted valuesDefaultDescription
enablebooleanfalseShow the breadcrumb bar.
path"full", "file", or "none""full"Include the full path, only the file name, or no path before symbols.

[editor.clipboard.provider]

Set provider to "pasteboard", "wayland", "x-clip", "x-sel", "win32-yank", "tmux", "windows", "termux", "termcode", or "none". Availability is platform- and build-dependent. Without an explicit value, Mitos selects a usable provider from the current platform and environment.

A custom provider uses this shape:

[editor.clipboard.provider.custom]
yank = { command = "copy-command", args = ["--clipboard"] }
paste = { command = "paste-command", args = ["--clipboard"] }
yank-primary = { command = "copy-command", args = ["--primary"] }   # optional
paste-primary = { command = "paste-command", args = ["--primary"] } # optional

yank and paste are required. Every command has a required command string and optional args array (default []). Mitos sends yanked text to stdin and reads pasted text from stdout. yank-primary and paste-primary are optional; they handle the primary selection where the platform has one.

[editor.statusline]

Each view has its own statusline by default. Set global = true to show a single statusline at the bottom of the editor for the focused view.

KeyTypeDefault
globalbooleanfalse
leftarray of element names["mode", "spinner", "branch", "file-name", "read-only-indicator", "file-modification-indicator"]
rightarray of element names["diagnostics", "selections", "register", "position", "file-encoding"]
separatorstring"│"
mode.colorbooleanfalse
mode.normalstring"NOR"
mode.insertstring"INS"
mode.selectstring"SEL"
diagnosticsseverity array["warning", "error"]
workspace-diagnosticsseverity array["warning", "error"]

Valid elements and their behavior are listed in the statusline section.

[editor.lsp]

KeyTypeDefaultDescription
enablebooleantrueEnable LSP integration globally.
workspace-rootsarray of paths[]Workspace-relative directories that stop LSP root discovery.
progress.enablebooleanfalseShow $/progress messages below the statusline.
messages.enablebooleantrueShow window/showMessage messages below the statusline.
signature-help.auto-triggerbooleantrueOpen signature help automatically.
signature-help.show-documentationbooleantrueShow documentation in signature help.
inlay-hints.enablebooleanfalseRender inlay hints. The server may also require configuration.
document-highlight.auto-triggerbooleantrueHighlight references to the symbol at the cursor.
inlay-hints.length-limitnon-zero integer or unsetUnsetTruncate displayed inlay hints to this length.
color-swatches.enablebooleantrueRender swatches beside document colors.
code-lenses.enablebooleantrueShow code lens labels at the end of each line, resolving visible lenses automatically. Labels use space left by diagnostics and are clipped without wrapping. Gutter indicators and Space+l provide access to the full commands.
completion.snippetsbooleantrueAdvertise and insert LSP snippet completions. A server restart is required after changing it.
references.include-declarationbooleantrueInclude a symbol’s declaration in reference results.

The dotted paths above are feature subsections. For example:

[editor.lsp.messages]
enable = true

[editor.lsp.signature-help]
auto-trigger = true
show-documentation = false

[editor.lsp.inlay-hints]
enable = true
length-limit = 40

[editor.cursor.shape]

normal, insert, and select each accept "block", "bar", "underline", or "hidden"; all default to "block". Terminals can change only the primary cursor’s shape.

[editor.files.reload]

Mitos reloads unmodified buffers when their files change externally, preserving undo history and updating selections in every split. If a buffer has unsaved changes, a prompt offers Enter to reload or Esc to keep the buffer. The same external change prompts only once. Deleted files keep their open buffers; recreating the file can trigger another reload.

KeyDefaultDescription
enabletrueAutomatically reload files changed on disk.
prompt-if-modifiedtruePrompt before reloading a buffer with unsaved changes. When disabled, show a warning instead.
poll.enabletruePeriodically check open files outside native watcher coverage and Git metadata.
poll.interval30000Polling interval in milliseconds, with a minimum of 100.

Files outside watched roots, hidden or ignored files, and files beyond the depth limit are checked when the terminal regains focus and by periodic polling. This also provides a fallback when a native watcher cannot start. Focus checks remain active when poll.enable is false. Git refreshes use the same polling schedule and can continue when buffer auto-reload is disabled.

[editor.files.reload]
enable = true
prompt-if-modified = true

[editor.files.reload.poll]
enable = true
interval = 30000

[editor.files.watch]

Native file events also notify language servers registered for file creation, changes, and deletion. Relative LSP patterns may add watch roots outside the current workspace. Recursive watching defaults to workspaces so starting Mitos in a home directory does not watch the entire home directory.

KeyDefaultDescription
enabletrueEnable native recursive file watching. Buffer polling and focus checks can still run when disabled.
watch-vcstrueRefresh branch names and diff gutters when Git HEAD or branch references change, including linked worktrees.
require-workspacetrueAutomatically watch the working directory only when a workspace is found. Explicit LSP roots are still allowed.
hiddentrueExclude hidden paths, apart from editor/build configuration and the Git metadata needed for refreshes.
ignoretrueRead .ignore files.
git-ignoretrueRead .gitignore files, including nested files.
git-globaltrueRead Git’s global ignore file.
max-depth10Maximum path depth below a watch root. Deeper open files use polling.

The global Mitos ignore file and .mitos/ignore also apply. For watcher-specific rules, use filesentryignore in the Mitos configuration directory or .mitos/filesentryignore in the workspace. These use gitignore syntax and take priority over the other filters. Changes to workspace ignore files refresh the watcher’s filters. Configuration reloads and :cd update watch coverage.

[editor.files.watch]
enable = true
watch-vcs = true
require-workspace = true
hidden = true
ignore = true
git-ignore = true
git-global = true
max-depth = 10

[editor.file-picker]

KeyTypeDefaultDescription
hiddenbooleantrueIgnore hidden files.
follow-symlinksbooleantrueFollow symbolic links.
deduplicate-linksbooleantrueHide links to files already present in results.
parentsbooleantrueRead ignore files from parent directories.
ignorebooleantrueRead .ignore files.
git-ignorebooleantrueRead .gitignore files.
git-globalbooleantrueRead Git’s global excludes file.
git-excludebooleantrueRead .git/info/exclude.
max-depthinteger or unsetUnsetMaximum directory recursion depth.

[editor.file-explorer]

KeyTypeDefaultDescription
hiddenbooleanfalseIgnore hidden files.
follow-symlinksbooleanfalseFollow symbolic links.
parentsbooleanfalseRead ignore files from parent directories.
ignorebooleanfalseRead .ignore files.
git-ignorebooleanfalseRead .gitignore files.
git-globalbooleanfalseRead Git’s global excludes file.
git-excludebooleanfalseRead .git/info/exclude.

[editor.buffer-picker]

KeyType / accepted valuesDefaultDescription
start-position"current" or "previous""current"Initially select the current buffer or the previously focused buffer.

[editor.auto-pairs]

enable defaults to true. Omit pairs to use the built-in delimiter pairs. A pairs table maps each single opening character to its closing character; an empty map enables no pairs. enable = false disables pairing while retaining any configured pairs.

[editor.auto-pairs]
enable = true
pairs = { '(' = ')', '[' = ']', '{' = '}', '"' = '"' }

Legacy booleans and direct character maps remain accepted.

[editor.files.auto-save]

KeyTypeDefaultDescription
focus-lostbooleanfalseSave when terminal focus moves away from Mitos.
after-delay.enablebooleanfalseSave after no edits occur for the configured delay.
after-delay.timeoutmilliseconds3000Delay used when after-delay.enable is true.

The legacy shorthand editor.auto-save = true enables only focus-lost; it does not enable delayed saving.

[editor.search]

KeyTypeDefaultDescription
smart-casebooleantrueSearch case-insensitively unless the pattern contains an uppercase character.
wrap-aroundbooleantrueContinue at the other end of the document after the last match.

[editor.whitespace]

KeyType / accepted valuesDefaultDescription
render"none", "all", or table"none"Render all whitespace uniformly, or configure kinds separately.
render.default"none" or "all""none"Fallback for omitted per-kind rendering values.
render.space"none" or "all"Inherits defaultRender ordinary spaces.
render.nbsp"none" or "all"Inherits defaultRender non-breaking spaces.
render.nnbsp"none" or "all"Inherits defaultRender narrow non-breaking spaces.
render.tab"none" or "all"Inherits defaultRender tabs.
render.newline"none" or "all"Inherits defaultRender line endings.
characters.spacecharacter"·"Glyph for a space.
characters.nbspcharacter"⍽"Glyph for a non-breaking space.
characters.nnbspcharacter"␣"Glyph for a narrow non-breaking space.
characters.tabcharacter"→"Glyph at the start of a tab.
characters.tabpadcharacter" "Glyph used to fill the rest of a tab.
characters.newlinecharacter"⏎"Glyph for a line ending.

[editor.indent-guides]

KeyTypeDefaultDescription
renderbooleanfalseRender vertical indent guides.
charactercharacter"│"Guide glyph.
skip-levelsinteger0Indentation levels to omit before drawing guides.

[editor.gutters]

KeyTypeDefaultDescription
layoutarray["diagnostics", "spacer", "line-numbers", "spacer", "diff"]Ordered gutter components. Accepted names are diagnostics, line-numbers, spacer, diff, and code-action-hint.
line-numbers.min-widthinteger3Minimum width of the line-number gutter.

diagnostics, diff, spacer, and code-action-hint currently have no component-specific settings.

[editor.soft-wrap]

KeyTypeDefaultDescription
enablebooleanfalseWrap lines wider than the view.
max-wrapinteger20Maximum free columns used to find a word boundary. Also limited to one quarter of the viewport.
max-indent-retaininteger40Maximum indentation carried onto wrapped lines. Also limited to one quarter of the viewport.
wrap-indicatorstring"↪ "Text shown before continuation lines.
wrap-at-text-widthbooleanfalseWrap at editor.text-width when it is narrower than the viewport.

[editor.indent.smart-tab]

KeyTypeDefaultDescription
enablebooleantrueMove to the end of the parent syntax node when non-whitespace precedes the cursor; otherwise insert indentation.
supersede-menubooleanfalseGive smart-tab precedence over completion-menu navigation.

[editor.inline-blame]

KeyTypeDefaultDescription
showstring"never"Display blame on "cursor-line", "all-lines", or hide it with "never".
formatstring"{author}, {time-ago} • {title} • {commit}"Blame message format. See inline blame formatting.

Press Space + B to toggle inline blame off and on, preserving your choice of "all-lines" or "cursor-line". If you have not chosen a display mode, turning it on uses "cursor-line". Blank lines and lines with uncommitted changes do not show inline blame.

[editor.diagnostics.inline]

Severity settings accept "disable", "hint", "info", "warning", or "error".

KeyTypeDefaultDescription
cursor-lineseverity"warning"Minimum severity rendered inline on the primary cursor line. Disabled in insert mode and delayed briefly after moving lines.
other-linesseverity"disable"Minimum severity rendered inline on other lines.
min-diagnostic-widthinteger40Minimum columns reserved for diagnostic text; inline diagnostics are hidden if the view cannot provide this plus the prefix.
prefix-leninteger1Horizontal bars rendered before diagnostic text.
max-wrapinteger20Maximum free columns used to wrap diagnostic text.
max-diagnosticsinteger10Maximum inline diagnostics rendered for one line.

[editor.spelling]

Global options for spell checking. See the Spell checking chapter for the full feature details.

KeyDescriptionDefault
languagesThe dictionaries to check every document against (e.g. ["en_US"]). Empty disables spell checking.[]
wordsExtra accepted words, matched case-insensitively, in addition to the dictionaries.[]
ignore-regexesTokens matching any of these regexes are not checked (for example "^[A-Z0-9_]+$" to skip CONSTANTS).[]
min-word-lengthTokens shorter than this are not checked.1

Per-language settings in languages.toml override these: languages and min-word-length replace the global value, while words and ignore-regexes are added to the global lists.

Example:

[editor.spelling]
languages = ["en_US"]
words = ["Mitos", "tokio"]
ignore-regexes = ["^[A-Z0-9_]+$"]

[editor.completion.word]

KeyTypeDefaultDescription
enablebooleantrueComplete words found in open buffers.
trigger-lengthnon-zero integer7Word length at which this completion source activates.

[terminal]

KeyType / accepted valuesDefaultDescription
true-colorbooleanfalseForce true-color support when detection produces a false negative.
undercurlbooleanfalseForce undercurl support when detection produces a false negative.
keyboard.kitty-protocol"auto", "enabled", or "disabled""auto"Policy for the Kitty enhanced keyboard protocol.
launchertableEnvironment-dependentExternal terminal or multiplexer command. See below.

[terminal.launcher]

KeyTypeDefaultDescription
commandstringEnvironment-dependentExternal terminal or multiplexer executable.
argsarray of strings[]Arguments placed before the command Mitos asks the terminal to run.

Mitos automatically uses a tmux split inside tmux, a WezTerm split when its Unix socket is available, Windows Terminal when found on Windows, or Conhost as the Windows fallback. On other terminals this setting is unset unless configured.

[editor.workspace-trust]

KeyType / accepted valuesDefaultDescription
level"none", "servers", or "insecure""servers"What may run without a per-workspace grant. servers permits LSP and DAP processes but still gates project config and repository-local Git configuration.
promptbooleantrueShow the trust modal. The restricted-workspace indicator is independent of this setting.
trustedarray of glob strings[]Trust matching workspace paths without a grant. This skips .mitos change detection and should be used cautiously.

This table is user-scope only. See Workspace trust for the security model and recommended configurations.

[keys]

[keys.normal], [keys.insert], and [keys.select] are maps whose keys are key names and whose values are a static command, a :typable-command, a key macro beginning with @, a sequence of commands, or another table forming a minor mode. There is no fixed list of entries: omitted bindings retain their built-in value. See Key remapping for key syntax and examples.

[commands]

Each key defines a command-mode command. Its value is one command string, an array of command strings, or a table with these settings:

KeyTypeDefaultDescription
commandsarray of stringsRequiredBuilt-in typable/static commands to execute in order. A key macro may be used only by itself.
descstring or unsetUnsetMarkdown description displayed in command help.
acceptsstring or unsetUnsetArgument placeholder shown in command help.
completerbuilt-in typable command or unsetUnsetReuse that command’s argument completion.

See Custom commands for argument expansion, visibility, and built-in-command shadowing.

languages.toml reference

languages.toml has three top-level entries: [[language]] definitions, [language-server.<name>] definitions, and [[grammar]] sources. The optional use-grammars selector affects grammar fetch/build commands. User and trusted project entries are merged over the built-in language definitions.

Defaults below are schema fallbacks for a newly defined language. A built-in language may already set a different effective value before user and project overrides are merged.

See Languages for file detection, root selection, LSP feature routing, formatters, and complete examples.

languages.toml retains its existing keys, including auto-format, path-completion, and word-completion. The feature-table restructuring applies to config.toml for now.

[[language]]

KeyType / defaultDescription
namestring; requiredLanguage name and merge key.
language-idstring; defaults to nameLanguage identifier sent to language servers.
scopestring; required for a new languageTextMate-style scope such as source.rust.
file-typesarray; required for a new languageFile extensions or { glob = "..." } matchers.
shebangsstring array; []Interpreter names recognized in shebangs.
rootsglob array; []Markers used to choose an LSP working directory.
comment-tokensstring or string array; unsetLine-comment tokens. comment-token is accepted as a compatibility alias.
block-comment-tokenstable or table array; unset{ start = "...", end = "..." } pairs for block comments.
text-widthinteger; inherits editorLanguage override for editor.text-width.
soft-wraptable; inherits editorLanguage override using the same keys as editor.soft-wrap.
auto-formatboolean; inherits editorPermit formatting this language on save. Global editor.format.on-save must also be enabled.
code-actions-on-savestring array; unsetLSP code-action kinds run in order on save.
formattertable; unsetExternal formatter. command is required; args defaults to [].
path-completionboolean; inherits editorOverride editor.completion.path.enable.
spellingtable; inherits editorlanguages and min-word-length replace global values; words and ignore-regexes extend them. See spell checking.
word-completiontable; inherits editorOverride enable and/or trigger-length from editor.completion.word.
diagnostic-severity"hint", "info", "warning", or "error"; "hint"Minimum accepted diagnostic severity.
grammarstring; defaults to nameTree-sitter grammar name.
injection-regexregex string; unsetMatch this language at tree-sitter injection sites.
language-serversarray; []Server names or { name = "...", only-features = [...], except-features = [...] } entries.
indenttable; unsetRequires tab-width from 1 through 16 and unit, the inserted indentation string.
debuggertable; unsetDebug adapter definition. See below.
auto-pairsboolean or character map; inherits editorLanguage-specific delimiter pairs.
rulersinteger array; inherits editorLanguage-specific ruler columns.
workspace-lsp-rootspath array; inherits editorLanguage-specific workspace-relative LSP search ceilings.
persistent-diagnostic-sourcesstring array; []Diagnostic sources whose unchanged diagnostics Mitos may track across edits.
rainbow-bracketsboolean; inherits editorLanguage override for rainbow brackets.

[language.debugger]

See Debugging (DAP) for setup, launch templates, and debugging commands.

KeyType / defaultDescription
namestring; requiredAdapter name.
transport"stdio" or "tcp"; requiredHow Mitos communicates with the adapter.
commandstring; ""Adapter executable.
argsstring array; []Adapter arguments.
port-argstring; unsetFor TCP, an argument template whose {} is replaced with an ephemeral local port.
templatesarray; requiredLaunch/attach templates exposed by Mitos.
quirks.absolute_pathsboolean; falseTreat paths received from the adapter as absolute. This key retains its underscore spelling.

Each [[language.debugger.templates]] has required name, request, and adapter-specific args values. Optional completion entries are either a string or { name = "...", completion = "...", default = "..." }; all three fields in that detailed form are optional.

[language-server.<name>]

KeyType / defaultDescription
commandstring; requiredServer executable. It must be available on PATH unless an absolute path is used.
argsstring array; []Server arguments.
environmentstring map; {}Environment variables supplied to the server.
configtable; unsetServer-specific initialization/settings object.
timeoutseconds; 20Maximum request duration.
required-root-patternsglob array; unsetStart the server only if at least one pattern exists in the selected LSP root. This validates a root; it does not select one.

[[grammar]] and use-grammars

KeyTypeDescription
use-grammars.onlystring arrayFetch/build only the named grammars. Mutually exclusive with except.
use-grammars.exceptstring arrayFetch/build every grammar except those named. Mutually exclusive with only.
grammar.namestringGrammar name.
grammar.source.gitURLGit repository containing the grammar.
grammar.source.revstringCommit or tag to fetch. Required with git.
grammar.source.subpathpathOptional subdirectory within a multi-grammar repository.
grammar.source.pathpathLocal grammar directory, as an alternative to the Git source keys.

use-grammars must appear before the array-of-table declarations in TOML. When omitted, every configured grammar is fetched and built.

Inline completions

Language servers supporting textDocument/inlineCompletion can suggest ghost text in insert mode. Mitos uses the first configured server with the inline-completion feature. Suggestions leave the document unchanged until accepted and disappear when you edit, move the cursor, leave insert mode, or switch documents. Requests pause while the completion popup is previewing an item.

Add bindings to config.toml to accept, dismiss, request, or cycle suggestions:

[keys.insert]
C-y = "inline_completion_accept"
C-e = "inline_completion_dismiss"
C-g = "inline_completion_trigger"
A-n = "inline_completion_next"
A-p = "inline_completion_prev"

There are no default bindings. Set editor.inline-completion-auto-trigger = false to request suggestions only with inline_completion_trigger. Plain text suggestions can span multiple lines; snippet suggestions and multiple cursors are not supported. Ghost text uses the ui.virtual.inline-completion theme scope, falling back to ui.virtual.inlay-hint.

Editor

This chapter explains editor settings in depth. For the complete, canonical list of every setting and default, see the config.toml reference.

[editor] Section

KeyDescriptionDefault
scroll.paddingNumber of lines of padding around the edge of the screen when scrolling5
mouse.enableEnable mouse modetrue
mouse.yank-registerWhich register to use for mouse yanks. *
mouse.middle-click-pasteMiddle click paste supporttrue
clipboard.default-yank-registerDefault register used for yank/paste'"'
scroll.wheel-linesNumber of lines to scroll per scroll wheel step3
shellShell to use when running external commandsUnix: ["sh", "-c"]
Windows: ["cmd", "/C"]
line-numberLine number display: absolute simply shows each line’s number, while relative shows the distance from the current line. When unfocused or in insert mode, relative will still show absolute line numbers"absolute"
cursor.highlight-lineHighlight all lines with a cursorfalse
cursor.highlight-columnHighlight all columns with a cursorfalse
continue-commentsif mitos should automatically add a line comment token if you create a new line inside a comment.true
guttersGutters to display: Available are diagnostics and diff and line-numbers and spacer and code-action-hint, note that diagnostics also includes other features like breakpoints, 1-width padding will be inserted if gutters is non-empty["diagnostics", "spacer", "line-numbers", "spacer", "diff"]
completion.auto-triggerEnable automatic pop up of auto-completiontrue
completion.path.enableEnable filepath completion. Show files and directories if an existing path at the cursor was recognized, either absolute or relative to the current opened document or current working directory (if the buffer is not yet saved). Defaults to true.true
format.on-saveEnable automatic formatting on save1true
idle-timeoutTime in milliseconds since last keypress before idle timers trigger.250
completion.trigger-delayTime in milliseconds after typing a word character before completions are shown, set to 5 for instant.250
completion.preview-insertWhether to apply completion item instantly when selectedtrue
completion.trigger-lengthThe min-length of word under cursor to trigger autocompletion2
completion.replaceWhether to make completions always replace the entire word and not just the part before the cursorfalse
auto-infoWhether to display info boxestrue
iconsWhether to show icons in the UI. Requires a Nerd Fontfalse
rulersList of column positions at which to display the rulers. Can be overridden by language specific rulers in languages.toml file[]
bufferline.showRenders a line at the top of the editor displaying open buffers. Can be always, never or multiple (only shown if more than one buffer is in use)"never"
statusline.mode.colorWhether to color the mode indicator with different colors depending on the mode itselffalse
text-widthMaximum line length. Used for the :reflow command and soft-wrapping if soft-wrap.wrap-at-text-width is set80
lsp.workspace-rootsDirectories relative to the workspace root that are treated as LSP roots. Should only be set in .mitos/config.toml[]
files.default-line-endingThe line ending to use for new documents. Can be native, lf, crlf, ff, cr or nel. native uses the platform’s native line ending (crlf on Windows, otherwise lf)."native"
files.save.insert-final-newlineWhether to automatically insert a trailing line-ending on write if missingtrue
files.save.atomicWhether to use atomic operations to write documents to disk. This prevents data loss if the editor is interrupted while writing the file, but may confuse some file watching/hot reloading programs.true
files.save.trim-final-newlinesWhether to automatically remove line-endings after the final one on writefalse
files.save.trim-trailing-whitespaceWhether to automatically remove whitespace preceding line endings on writefalse
indent.heuristicHow the indentation for a newly inserted line is computed: simple just copies the indentation level from the previous line, tree-sitter computes the indentation based on the syntax tree and hybrid combines both approaches. If the chosen heuristic is not available, a different one will be used as a fallback (the fallback order being hybrid -> tree-sitter -> simple)."hybrid"
jump-label-alphabetThe characters that are used to generate two character jump labels. Characters at the start of the alphabet are used first."abcdefghijklmnopqrstuvwxyz"
diagnostics.end-of-line.severityMinimum severity of diagnostics to render at the end of the line. Set to disable to disable entirely. Refer to the setting about diagnostics.inline for more details"hint"
clipboard.providerWhich API to use for clipboard interaction. One of pasteboard (MacOS), wayland, x-clip, x-sel, win32-yank, termux, tmux, windows, termcode, none, or a custom command set.Platform and environment specific.
files.editor-configWhether to read settings from EditorConfig filestrue
welcome-screenWhether to show the welcome screen when Mitos starts without a filetrue
rainbow-brackets.enableWhether to render rainbow colors for matching brackets. Requires tree-sitter rainbows.scm queries for the language.false

[editor.breadcrumb] Section

Allows configuration of the breadcrumb navigation bar.

SettingDescriptionDefault
enableWhether to enable the breadcrumb navigation barfalse
pathHow much path information to show"full"
[editor.breadcrumb]
enable = true
# full: crates > term > src > commands > typed.rs > TypableCommand > name
# file: typed.rs > TypableCommand > name
# none: TypableCommand > name
path = "full" # one of: full, file, none

The bar is displayed at the top of the view.

[editor.clipboard] Section

Mitos can be configured either to use a builtin clipboard configuration or to use a provided command.

For instance, setting it to use OSC 52 termcodes, the configuration would be:

[editor]
clipboard.provider = "termcode"

Alternatively, Mitos can be configured to use arbitrary commands for clipboard integration:

[editor.clipboard.provider.custom]
yank = { command = "cat",  args = ["test.txt"] }
paste = { command = "tee",  args = ["test.txt"] }
yank-primary = { command = "cat",  args = ["test-primary.txt"] } # optional
paste-primary = { command = "tee",  args = ["test-primary.txt"] } # optional

For custom commands the contents of the yank/paste is communicated over stdin/stdout.

[editor.statusline] Section

Allows configuring the statusline at the bottom of each view. Set global = true to show a single statusline at the bottom of the editor for the focused view. Use :toggle statusline.global to switch between the two layouts at runtime.

The configuration distinguishes between two areas of the status line:

[ ... ... LEFT ... ... | ... ... RIGHT ... ... ]

Statusline elements can be defined as follows:

[editor.statusline]
left = ["mode", "spinner"]
right = ["diagnostics", "selections", "position", "file-encoding", "file-line-ending", "file-type"]
separator = "│"
mode.normal = "NORMAL"
mode.insert = "INSERT"
mode.select = "SELECT"
diagnostics = ["warning", "error"]
workspace-diagnostics = ["warning", "error"]

The [editor.statusline] key takes the following sub-keys:

KeyDescriptionDefault
globalShow one statusline for the focused view instead of one per viewfalse
leftA list of elements aligned to the left of the statusline["mode", "spinner", "branch", "file-name", "read-only-indicator", "file-modification-indicator"]
rightA list of elements aligned to the right of the statusline["diagnostics", "selections", "register", "position", "file-encoding"]
separatorThe character used to separate elements in the statusline"│"
mode.normalThe text shown in the mode element for normal mode"NOR"
mode.insertThe text shown in the mode element for insert mode"INS"
mode.selectThe text shown in the mode element for select mode"SEL"
diagnosticsA list of severities which are displayed for the current buffer["warning", "error"]
workspace-diagnosticsA list of severities which are displayed for the workspace["warning", "error"]

The following statusline elements can be configured:

KeyDescription
modeThe current editor mode (mode.normal/mode.insert/mode.select)
spinnerA progress spinner indicating LSP activity
branchThe current branch name or detached commit hash of the opened workspace
file-nameThe path/name of the opened file
file-absolute-pathThe absolute path/name of the opened file
file-base-nameThe basename of the opened file
current-working-directoryThe current working directory
file-modification-indicatorThe indicator to show whether the file is modified (a [+] appears when there are unsaved changes)
file-encodingThe encoding of the opened file if it differs from UTF-8
file-line-endingThe file line endings (CRLF or LF)
file-indent-styleThe file indentation style
read-only-indicatorAn indicator that shows [readonly] when a file cannot be written
total-line-numbersThe total line numbers of the opened file
file-typeThe type of the opened file
diagnosticsThe number of warnings and/or errors
workspace-diagnosticsThe number of warnings and/or errors on workspace
selectionsThe main selection index out of the number of active selections
primary-selection-lengthThe number of characters currently in the main selection
positionThe cursor position
position-percentageThe cursor position as a percentage of the total number of lines
separatorThe string defined in editor.statusline.separator (defaults to "│")
spacerInserts a space between elements (multiple/contiguous spacers may be specified)
registerThe current selected register
code-action-hintIndicator for when code actions are available

[editor.lsp] Section

KeyDescriptionDefault
enableEnables LSP integration. Setting to false will completely disable language servers regardless of language settings.true
messages.enableDisplay LSP window/showMessage messages below statusline2true
progress.enableDisplay LSP progress messages below statusline2false
signature-help.auto-triggerEnable automatic popup of signature help (parameter hints)true
document-highlight.auto-triggerAutomatically highlight symbol references at the cursortrue
inlay-hints.enableDisplay inlay hints3false
inlay-hints.length-limitMaximum displayed length (non-zero number) of inlay hintsUnset by default
color-swatches.enableShow color swatches next to colorstrue
signature-help.show-documentationDisplay docs under signature help popuptrue
completion.snippetsEnables snippet completions. Requires a server restart (:lsp-restart) to take effect after :config-reload/:set.true
references.include-declarationInclude declaration in the goto references popup.true

Terminal capability overrides are configured separately under [terminal]: true-color, undercurl, and keyboard.kitty-protocol. See the terminal configuration reference.

[terminal.launcher] Section

Configures the external terminal command used by features such as DAP runInTerminal requests.

KeyDescriptionDefault
commandExternal terminal or multiplexer executableEnvironment-dependent
argsArguments placed before the command Mitos asks the terminal to run[]

Mitos automatically uses a tmux split inside tmux, a WezTerm split when its Unix socket is available, Windows Terminal when found on Windows, or Conhost as the Windows fallback. On other terminals this setting is unset unless configured.

[editor.inline-blame] Section

Inline Git blame shows who last changed a line and the associated commit information at the end of the line. It is available for files tracked by Git and is hidden by default.

To show blame for the current line, add this to your config.toml:

[editor.inline-blame]
show = "cursor-line"
KeyDescriptionDefault
showWhich lines display inline blame"never"
formatText shown in the blame message"{author}, {time-ago} • {title} • {commit}"

show can be one of the following:

  • "all-lines": Display blame on all visible nonempty lines.
  • "cursor-line": Display blame on the primary cursor’s line.
  • "never": Hide inline blame.

Press Space + B to toggle inline blame off and on. The toggle preserves your choice of "all-lines" or "cursor-line". If you have not chosen a display mode, turning it on uses "cursor-line".

Blank lines and lines with uncommitted changes do not show inline blame.

To show the current line’s blame in the status area, run blame_line from the command palette (Space + ?).

format

Use format to choose which commit details appear in the blame message. For example:

[editor.inline-blame]
show = "cursor-line"
format = "{author}: {title}"

The following placeholders are available:

  • {author}: Commit author name.
  • {date}: Commit author date.
  • {time-ago}: Relative time since the commit.
  • {title}: Commit title.
  • {body}: Commit body.
  • {commit}: Abbreviated commit hash.
  • {email}: Commit author email.

[editor.cursor.shape] Section

Defines the shape of cursor in each mode. Valid values for these options are block, bar, underline, or hidden.

💡 Due to limitations of the terminal environment, only the primary cursor can change shape.

KeyDescriptionDefault
normalCursor shape in normal mode"block"
insertCursor shape in insert mode"block"
selectCursor shape in select mode"block"

[editor.file-picker] Section

Set options for file picker and global search. Ignoring a file means it is not visible in the Mitos file picker and global search.

All git related options are only enabled in a git repository.

KeyDescriptionDefault
hiddenEnables ignoring hidden filestrue
follow-symlinksFollow symlinks instead of ignoring themtrue
deduplicate-linksIgnore symlinks that point at files already shown in the pickertrue
parentsEnables reading ignore files from parent directoriestrue
ignoreEnables reading .ignore filestrue
git-ignoreEnables reading .gitignore filestrue
git-globalEnables reading global .gitignore, whose path is specified in git’s config: core.excludesfile optiontrue
git-excludeEnables reading .git/info/exclude filestrue
max-depthSet with an integer value for maximum depth to recurseUnset by default

Ignore files can be placed locally as .ignore or put in your home directory as ~/.ignore. They support the usual ignore and negative ignore (unignore) rules used in .gitignore files.

Additionally, you can use Mitos-specific ignore files by creating a local .mitos/ignore file in the current workspace or a global ignore file located in your Mitos config directory:

  • Linux and Mac: ~/.config/mitos/ignore
  • Windows: %AppData%\mitos\ignore

Example:

# unignore in file picker and global search
!.github/
!.gitignore
!.gitattributes

[editor.file-explorer] Section

In addition to the options for the file picker and global search, a similar set of options is presented to configure the file explorer separately. However, unlike the file picker, the defaults are set to avoid ignoring most files.

Note that the ignore files consulted by the file explorer when ignore is set to true are the same ones used by the file picker, including the aforementioned Mitos-specific ignore files.

KeyDescriptionDefault
hiddenEnables ignoring hidden filesfalse
follow-symlinksFollow symlinks instead of ignoring themfalse
parentsEnables reading ignore files from parent directoriesfalse
ignoreEnables reading .ignore filesfalse
git-ignoreEnables reading .gitignore filesfalse
git-globalEnables reading global .gitignore, whose path is specified in git’s config: core.excludesfile optionfalse
git-excludeEnables reading .git/info/exclude filesfalse

[editor.buffer-picker] Section

Set options for buffer picker.

KeyDescriptionDefault
start-positionControls behavior for which buffer is initially selectedcurrent

Example

[editor.buffer-picker]
start-position = "previous"

[editor.auto-pairs] Section

Automatic delimiter pairing uses an enable flag and an optional pairs map. Omit the map to use the built-in pairs, or provide opening and closing characters:

[editor.auto-pairs]
enable = true
pairs = { '(' = ')', '[' = ']', '{' = '}', '"' = '"' }

Set enable = false to disable pairing. The same table is supported per language. See the configuration reference for defaults and compatibility shorthands.

[editor.files.auto-save] Section

Control auto save behavior.

KeyDescriptionDefault
focus-lostEnable automatic saving on the focus moving away from Mitos. Requires focus event support from your terminalfalse
after-delay.enableEnable automatic saving after files.auto-save.after-delay.timeout milliseconds have passed since last edit.false
after-delay.timeoutTime in milliseconds since last edit before auto save timer triggers.3000

[editor.search] Section

Search specific options.

KeyDescriptionDefault
smart-caseEnable smart case regex searching (case-insensitive unless pattern contains upper case characters)true
wrap-aroundWhether the search should wrap after depleting the matchestrue

[editor.whitespace] Section

Options for rendering whitespace with visible characters. Use :set whitespace.render all to temporarily enable visible whitespace.

KeyDescriptionDefault
renderWhether to render whitespace. May either be all or none, or a table with sub-keys space, nbsp, nnbsp, tab, and newline"none"
charactersLiteral characters to use when rendering whitespace. Sub-keys may be any of tab, space, nbsp, nnbsp, newline or tabpadSee example below

Example

[editor.whitespace]
render = "all"
# or control each character
[editor.whitespace.render]
space = "all"
tab = "all"
nbsp = "none"
nnbsp = "none"
newline = "none"

[editor.whitespace.characters]
space = "·"
nbsp = "⍽"
nnbsp = "␣"
tab = "→"
newline = "⏎"
tabpad = "·" # Tabs will look like "→···" (depending on tab width)

[editor.indent-guides] Section

Options for rendering vertical indent guides.

KeyDescriptionDefault
renderWhether to render indent guidesfalse
characterLiteral character to use for rendering the indent guide"│"
skip-levelsNumber of indent levels to skip0

Example:

[editor.indent-guides]
render = true
character = "╎" # Some characters that work well: "▏", "┆", "┊", "⸽"
skip-levels = 1

[editor.gutters] Section

For simplicity, editor.gutters accepts an array of gutter types, which will use default settings for all gutter components.

[editor]
gutters = ["diff", "diagnostics", "line-numbers", "spacer"]

To customize the behavior of gutters, the [editor.gutters] section must be used. This section contains top level settings, as well as settings for specific gutter components as subsections.

KeyDescriptionDefault
layoutA vector of gutters to display["diagnostics", "spacer", "line-numbers", "spacer", "diff"]

Example:

[editor.gutters]
layout = ["diff", "diagnostics", "line-numbers", "spacer"]

[editor.gutters.line-numbers] Section

Options for the line number gutter

KeyDescriptionDefault
min-widthThe minimum number of characters to use3

Example:

[editor.gutters.line-numbers]
min-width = 1

[editor.gutters.diagnostics] Section

Currently unused

[editor.gutters.diff] Section

The diff gutter option displays colored bars indicating whether a git diff represents that a line was added, removed or changed. These colors are controlled by the theme attributes diff.plus, diff.minus and diff.delta.

Other diff providers will eventually be supported by a future plugin system.

There are currently no options for this section.

[editor.gutters.spacer] Section

Currently unused

[editor.gutters.code-action-hint] Section

The code-action-hint gutter option displays an indicator for whether a code action is available at current selection.

There are currently no options for this section.

[editor.soft-wrap] Section

Options for soft wrapping lines that exceed the view width:

KeyDescriptionDefault
enableWhether soft wrapping is enabled.false
max-wrapMaximum free space left at the end of the line.20
max-indent-retainMaximum indentation to carry over when soft wrapping a line.40
wrap-indicatorText inserted before soft wrapped lines, highlighted with ui.virtual.wrap"↪ "
wrap-at-text-widthSoft wrap at text-width instead of using the full viewport size.false

Example:

[editor.soft-wrap]
enable = true
max-wrap = 25 # increase value to reduce forced mid-word wrapping
max-indent-retain = 0
wrap-indicator = ""  # set wrap-indicator to "" to hide it

[editor.indent.smart-tab] Section

Options for navigating and editing using tab key.

KeyDescriptionDefault
enableIf set to true, then when the cursor is in a position with non-whitespace to its left, instead of inserting a tab, it will run move_parent_node_end. If there is only whitespace to the left, then it inserts a tab as normal. With the default bindings, to explicitly insert a tab character, press Shift-tab.true
supersede-menuNormally, when a menu is on screen, such as when auto complete is triggered, the tab key is bound to cycling through the items. This means when menus are on screen, one cannot use the tab key to trigger the indent.smart-tab command. If this option is set to true, the indent.smart-tab command always takes precedence, which means one cannot use the tab key to cycle through menu items. One of the other bindings must be used instead, such as arrow keys or C-n/C-p.false

Due to lack of support for S-tab in some terminals, the default keybindings don’t fully embrace smart-tab editing experience. If you enjoy smart-tab navigation and a terminal that supports the Enhanced Keyboard protocol, consider setting extra keybindings:

[keys.normal]
tab = "move_parent_node_end"
S-tab = "move_parent_node_start"

[keys.insert]
S-tab = "move_parent_node_start"

[keys.select]
tab = "extend_parent_node_end"
S-tab = "extend_parent_node_start"

[editor.diagnostics.inline] Section

Options for rendering diagnostics inside the text like shown below

fn main() {
  let foo = bar;
            └─ no such value in this scope
}
KeyDescriptionDefault
cursor-lineThe minimum severity that a diagnostic must have to be shown inline on the line that contains the primary cursor. Set to disable to not show any diagnostics inline. This option does not have any effect when in insert-mode and will only take effect 350ms after moving the cursor to a different line."warning"
other-linesThe minimum severity that a diagnostic must have to be shown inline on a line that does not contain the cursor-line. Set to disable to not show any diagnostics inline."disable"
min-diagnostic-widthMinimum width reserved for diagnostic text. Inline diagnostics are hidden when the view cannot provide this width plus the prefix.40
prefix-lenHow many horizontal bars ─ are rendered before the diagnostic text.1
max-wrapEquivalent of the editor.soft-wrap.max-wrap option for diagnostics.20
max-diagnosticsMaximum number of diagnostics to render inline for a given line10

The allowed values for cursor-line and other-lines are: error, warning, info, hint.

The (first) diagnostic with the highest severity that is not shown inline is rendered at the end of the line (as long as its severity is higher than the diagnostics.end-of-line.severity config option):

fn main() {
  let baz = 1;
  let foo = bar; a local variable with a similar name exists: baz
            └─ no such value in this scope
}

[editor.spelling] Section

Spell checking is opt-in. See the editor.spelling configuration reference for settings and Spell checking for dictionaries, corrections, and personal word lists.

[editor.completion.word] Section

Options for controlling completion of words from open buffers.

KeyDescriptionDefault
enableWhether word completion is enabledtrue
trigger-lengthNumber of word characters to type before triggering completion7

Example:

[editor.completion.word]
enable = true
# Set the trigger length lower so that words are completed more often
trigger-length = 4

[editor.workspace-trust] Section

Controls implicit workspace trust. See the workspace trust chapter for the full feature.

KeyDescriptionDefault
levelThe default level of trust for every workspace."servers"
promptWhether to show a modal when opening a file in an untrusted workspace.true
trustedGlob patterns whose matching workspaces are trusted without a grant.[]

Example:

[editor.workspace-trust]
# Even if `false`, the statusline `[⚠]` indicator is still shown.
prompt = false

# "none":     prompt for every workspace.
# "servers":  trust LSP and DAP launches but still gate local config and git;
#             .mitos/config.toml, .mitos/languages.toml, etc. need :workspace-trust.
# "insecure": trust everything (discouraged).
level = "servers"

# Discouraged: skips .mitos/ change detection and trusts anything that lands
# under a matching path. `~` and environment variables are expanded.
trusted = ["~/src/github.com/me/*"]

  1. In most cases, you also need to enable the auto-format setting under languages.toml. You can find the reasoning here. ↩

  2. By default, a progress spinner is shown in the statusline beside the file path. ↩ ↩2

  3. You may also have to activate them in the language server config for them to appear, not just in Mitos. Inlay hints in Mitos are still being improved on and may be a little bit laggy/janky under some circumstances. Please report any bugs you see so we can fix them! ↩

Themes

To use a theme add theme = "<name>" to the top of your config.toml file, or select it during runtime using :theme <name>.

Without a configured theme, Mitos uses modus_operandi for light mode and modus_vivendi for dark mode. If the terminal does not report a preference, Mitos uses modus_vivendi. Terminals without true-color support use the built-in ANSI default theme instead. To always use the terminal’s ANSI palette, set theme = "default".

Separate themes can be configured for light and dark modes. On terminals supporting mode 2031 dark/light detection, the theme mode is detected from the terminal and updates when the terminal follows a system appearance change. On other terminals, Mitos uses the configured fallback theme, or the dark theme when no fallback is specified.

[theme]
dark = "modus_vivendi"
light = "modus_operandi"
## Optional. Used if the terminal doesn't declare a preference.
## Defaults to the theme set for `dark` if not specified.
# fallback = "modus_vivendi"

Creating a theme

Create a file with the name of your theme as the file name (i.e mytheme.toml) and place it in your themes directory (i.e ~/.config/mitos/themes or %AppData%\mitos\themes on Windows). The directory might have to be created beforehand.

💡 The names “default” and “base16_default” are reserved for built-in themes and cannot be overridden by user-defined themes.

Overview

Each line in the theme file is specified as below:

key = { fg = "#ffffff", bg = "#000000", underline = { color = "#ff0000", style = "curl"}, modifiers = ["bold", "italic"] }

Where key represents what you want to style, fg specifies the foreground color, bg the background color, underline the underline style/color, and modifiers is a list of style modifiers. bg, underline and modifiers can be omitted to defer to the defaults.

To specify only the foreground color:

# Valid hex-color format is `#RGB` or `#RRGGBB`
# (each letter is a nibble)
key = "#ffffff"

If the key contains a dot '.', it must be quoted to prevent it being parsed as a dotted key.

"key.key" = "#fff"

Color values must be either a CSS hex RGB string or a name declared in the palette.

💡 Note that Mitos doesn’t support transparency (alpha channel).

For inspiration, you can find the built-in ANSI theme base16_terminal.toml here and user-submitted themes here.

The details of theme creation

Color palettes

It’s recommended to define a palette of named colors, and refer to them in the configuration values in your theme. To do this, add a table called palette to your theme file:

"ui.background" = "white"
"ui.text" = "black"

[palette]
white = "#ffffff"
black = "#000000"

Keep in mind that the [palette] table includes all keys after its header, so it should be defined after the normal theme options.

The default palette uses the terminal’s default 16 colors, and the colors names are listed below. The [palette] section in the config file takes precedence over it and is merged into the default palette.

Color Name
default
black
red
green
yellow
blue
magenta
cyan
gray
light-red
light-green
light-yellow
light-blue
light-magenta
light-cyan
light-gray
white

Modifiers

The following values may be used as modifier, provided they are supported by your terminal emulator.

Modifier
bold
dim
italic
underlined
slow_blink
rapid_blink
reversed
hidden
crossed_out

💡 The underlined modifier is deprecated and only available for backwards compatibility. Its behavior is equivalent to setting underline.style="line".

Underline style

One of the following values may be used as a value for underline.style, providing it is supported by your terminal emulator.

Modifier
line
curl
dashed
dotted
double_line

Inheritance

Extend other themes by setting the inherits property to an existing theme.

inherits = "boo_berry"

# Override the theming for "keyword"s:
"keyword" = { fg = "gold" }

# Override colors in the palette:
[palette]
berry = "#2A2A4D"

Rainbow

The rainbow key is used for rainbow highlight for matching brackets. The key is a list of styles.

rainbow = ["#ff0000", "#ffa500", "#fff000", { fg = "#00ff00", modifiers = ["bold"] }]

Colors from the palette and modifiers may be used.

Scopes

The following is a list of scopes available to use for styling:

Syntax highlighting

These keys match tree-sitter scopes.

When determining styling for a highlight, the longest matching theme key will be used. For example, if the highlight is function.builtin.static, the key function.builtin will be used instead of function.

This list is also the reference for the capture names used in highlights.scm queries (@function, @type, …) — see Adding languages. When several captures cover the same text the last matching pattern in the file wins; when they sit on nested nodes the innermost node wins regardless of order, so capture the leaf you mean (put @function on the called identifier, not a node wrapping it). A method call is @function.method; a field access with no call is @variable.other.member.

We use a similar set of scopes as Sublime Text. See also TextMate scopes.

  • attribute - Class attributes, HTML tag attributes

  • type - Types

    • builtin - Primitive types provided by the language (int, usize)
    • parameter - Generic type parameters (T)
    • enum
      • variant - Enum variants
  • constructor - Constructors, struct/record literals, type names in value position

  • constant (TODO: constant.other.placeholder for %v)

    • builtin Special constants provided by the language (true, false, nil etc)
      • boolean
    • character
      • escape
    • numeric (numbers)
      • integer
      • float
  • string (TODO: string.quoted.{single, double}, string.raw/.unquoted)?

    • regexp - Regular expressions
    • special
      • path
      • url
      • symbol - Erlang/Elixir atoms, Ruby symbols, Clojure keywords
  • comment - Code comments

    • line - Single line comments (//)
      • documentation - Line documentation comments (e.g. /// in Rust)
    • block - Block comments (e.g. (/* */)
      • documentation - Block documentation comments (e.g. /** */ in Rust)
    • unused - Unused variables and patterns, e.g. _ and _foo
  • variable - Variables

    • mutable - Mutable variables (e.g. marked with mut in Rust)
    • builtin - Reserved language variables (self, this, super, etc.)
      • mutable - Mutable language variables (e.g. mut self in Rust)
    • parameter - Function parameters
      • mutable - Mutable function parameters (e.g. marked with mut in Rust)
    • other
      • member - Fields of composite data types (e.g. structs, unions)
        • private - Private fields that use a unique syntax (currently just ECMAScript-based languages)
  • label - .class, #id in CSS, etc.

  • punctuation

    • delimiter - Commas, colons
    • bracket - Parentheses, angle brackets, etc.
    • special - String interpolation brackets.
  • keyword

    • control
      • conditional - if, else
      • repeat - for, while, loop
      • import - import, export
      • return
      • exception
    • operator - or, in
    • directive - Preprocessor directives (#if in C)
    • function - fn, func
    • storage - Keywords describing how things are stored
      • type - The type of something, class, function, var, let, etc.
      • modifier - Storage modifiers like static, mut, const, ref, etc.
  • operator - ||, +=, >

  • function - Function definitions and calls

    • public - Public function definitions
    • builtin - Language built-in functions
    • method - Method definitions and calls (obj.method())
      • public - Public method definitions
      • private - Private methods that use a unique syntax (currently just ECMAScript-based languages)
    • macro - Macro invocations (e.g. println! in Rust)
    • special (preprocessor in C)
  • tag - Tags (e.g. <body> in HTML)

    • builtin
  • namespace - Modules and namespaces (e.g. std::collections, package names)

  • special - derive in Rust, bolded query-match in pickers (includes file explorer), etc. See also #2380

  • markup

    • heading
      • marker
      • 1, 2, 3, 4, 5, 6 - heading text for h1 through h6
    • list
      • unnumbered
      • numbered
      • checked
      • unchecked
    • bold
    • italic
    • strikethrough
    • link
      • url - URLs pointed to by links
      • label - non-URL link references
      • text - URL and image descriptions in links
    • quote
    • raw
      • inline
      • block
  • diff - version control changes

    • plus - additions
      • gutter - gutter indicator
    • minus - deletions
      • gutter - gutter indicator
    • delta - modifications
      • moved - renamed or moved files/changes
      • conflict - merge conflicts
      • gutter - gutter indicator
  • embedded - Interpolated expressions embedded in a string template (${…})

Interface

These scopes are used for theming the editor interface:

  • markup
    • normal
      • completion - for completion doc popup UI
      • hover - for hover popup UI
    • heading
      • completion - for completion doc popup UI
      • hover - for hover popup UI
    • raw
      • inline
        • completion - for completion doc popup UI
        • hover - for hover popup UI
KeyNotes
ui.background
ui.background.separatorPicker separator below input line
ui.cursor
ui.cursor.normal
ui.cursor.insert
ui.cursor.select
ui.cursor.matchMatching bracket etc.
ui.cursor.primaryCursor with main selection
ui.cursor.primary.normal
ui.cursor.primary.insert
ui.cursor.primary.select
ui.debug.breakpointBreakpoint indicator, found in the gutter
ui.debug.activeIndicator for the line at which debugging execution is paused at, found in the gutter
ui.gutterGutter
ui.gutter.selectedGutter for the line the cursor is on
ui.linenrLine numbers
ui.linenr.selectedLine number for the line the cursor is on
ui.statuslineStatusline
ui.statusline.inactiveStatusline (unfocused document)
ui.statusline.normalStatusline mode during normal mode (only if editor.statusline.mode.color is enabled)
ui.statusline.insertStatusline mode during insert mode (only if editor.statusline.mode.color is enabled)
ui.statusline.selectStatusline mode during select mode (only if editor.statusline.mode.color is enabled)
ui.statusline.separatorSeparator character in statusline
ui.bufferlineStyle for the buffer line
ui.bufferline.activeStyle for the active buffer in buffer line
ui.bufferline.backgroundStyle for bufferline background
ui.popupDocumentation popups (e.g. Space + k)
ui.popup.infoPrompt for multiple key options
ui.picker.headerHeader row area in pickers with multiple columns
ui.picker.header.columnColumn names in pickers with multiple columns
ui.picker.header.column.activeThe column name in pickers with multiple columns where the cursor is entering into.
ui.windowBorderlines separating splits
ui.helpDescription box for commands
ui.textDefault text style, command prompts, panel borders and titles, popup text, etc.
ui.text.focusThe currently selected line in the picker
ui.text.inactiveSame as ui.text but when the text is inactive (e.g. suggestions)
ui.text.infoThe key: command text in ui.popup.info boxes
ui.text.directoryDirectory names in prompt completion
ui.text.symlinkSymlink names in prompt completion
ui.virtual.rulerRuler columns (see the editor.rulers config)
ui.virtual.whitespaceVisible whitespace characters
ui.virtual.indent-guideVertical indent width guides
ui.virtual.inlay-hintDefault style for inlay hints of all kinds
ui.virtual.inlay-hint.parameterStyle for inlay hints of kind parameter (language servers are not required to set a kind)
ui.virtual.inlay-hint.typeStyle for inlay hints of kind type (language servers are not required to set a kind)
ui.virtual.wrapSoft-wrap indicator (see the editor.soft-wrap config)
ui.virtual.jump-labelStyle for virtual jump labels
ui.virtual.inline-completionLSP ghost text; falls back to ui.virtual.inlay-hint
ui.virtual.code-lensEnd-of-line code lens labels; falls back to ui.virtual.inlay-hint
ui.virtual.inline-blameInline blame indicator (see the editor.inline-blame config)
ui.menuCode and command completion menus
ui.menu.selectedSelected autocomplete item
ui.menu.scrollfg sets thumb color, bg sets track color of scrollbar
ui.selectionFor selections in the editing area
ui.selection.primary
ui.highlightHighlighted lines in the picker preview
ui.highlight.framelineLine at which debugging execution is paused at
ui.cursorline.primaryThe line of the primary cursor (if cursorline is enabled)
ui.cursorline.secondaryThe lines of any other cursors (if cursorline is enabled)
ui.cursorcolumn.primaryThe column of the primary cursor (if cursorcolumn is enabled)
ui.cursorcolumn.secondaryThe columns of any other cursors (if cursorcolumn is enabled)
warningDiagnostics warning (gutter)
warning.diagnostic.inlineThe inline diagnostic for the warning severity
errorDiagnostics error (gutter)
error.diagnostic.inlineThe inline diagnostic for the error severity
infoDiagnostics info (gutter)
info.diagnostic.inlineThe inline diagnostic for the warning severity
hintDiagnostics hint (gutter)
hint.diagnostic.inlineThe inline diagnostic for the hint severity
diagnosticDiagnostics fallback style (editing area)
diagnostic.hintDiagnostics hint (editing area)
diagnostic.infoDiagnostics info (editing area)
diagnostic.warningDiagnostics warning (editing area)
diagnostic.errorDiagnostics error (editing area)
diagnostic.unnecessaryDiagnostics with unnecessary tag (editing area)
diagnostic.deprecatedDiagnostics with deprecated tag (editing area)
diagnostic.spellingSpelling diagnostics in the editing area (falls back to the hint diagnostic style).
tabstopSnippet placeholder

Key remapping

There are three kinds of commands that can be used in keymaps:

  • Static commands: commands like move_char_right which are usually bound to keys and used for movement and editing. A list of static commands is available in the Keymap documentation and in the source code in crates/term/src/commands.rs at the invocation of static_commands! macro.
  • Typable commands: commands that can be executed from command mode (:), for example :write!. See the Commands documentation for a list of available typeable commands or the TypableCommandList declaration in the source code at crates/term/src/commands/typed.rs.
  • Macros: sequences of keys that are executed in order. These keybindings start with @ and then list any number of keys to be executed. For example @miw can be used to select the surrounding word. For now, macro keybindings are not allowed in keybinding sequences due to limitations in the way that command sequences are executed. Modifier keys (e.g. Alt+o) can be used like "<A-o>", e.g. "@miw<A-o>"

To remap keys, create a config.toml file in your mitos configuration directory (default ~/.config/mitos on Linux systems) with a structure like this:

💡 To set a modifier + key as a keymap, type A-X = ... or C-X = ... for Alt + X or Ctrl + X. Combine with Shift using a dash, e.g. C-S-esc. Within macros, wrap them in <>, e.g. <A-X> and <C-X> to distinguish from the A or C keys.

# At most one section each of 'keys.normal', 'keys.insert' and 'keys.select'
[keys.normal]
C-s = ":w" # Maps Ctrl-s to the typable command :w which is an alias for :write (save file)
C-o = ":open ~/.config/mitos/config.toml" # Maps Ctrl-o to opening of the mitos config file
a = "move_char_left" # Maps the 'a' key to the move_char_left command
w = "move_line_up" # Maps the 'w' key move_line_up
"C-S-esc" = "extend_line" # Maps Ctrl-Shift-Escape to extend_line
g = { a = "code_action" } # Maps `ga` to show possible code actions
"ret" = ["open_below", "normal_mode"] # Maps the enter key to open_below then re-enter normal mode
"A-x" = "@x<A-d>" # Maps Alt-x to a macro selecting the whole line and deleting it without yanking it

# Label a nested menu and its typable commands, macros, or command sequences
[keys.normal.space.f]
label = "File"
f = "file_picker"
s = { label = "Save", command = ":write" }
c = { label = "Edit Config", command = ":open ~/.config/mitos/config.toml" }
w = { label = "Delete word", command = "@wd" }
h = { label = "Split left", command = ["vsplit", "normal_mode", "swap_view_left"] }

[keys.insert]
"A-x" = "normal_mode"     # Maps Alt-X to enter normal mode
j = { k = "normal_mode" } # Maps `jk` to exit insert mode

label sets the menu title or the description shown for a command binding. Menus can be nested and labeled at any depth. Labels also override the names of built-in menus while keeping their other bindings. Trusted workspace labels override global labels; omitting a label or setting it to an empty string keeps the existing menu title or command description.

The command field accepts a command string or an array of commands. A binding cannot contain both command and nested keybindings. Individual static commands keep their built-in descriptions; sequences may contain static and typable commands and have their own label. Macros cannot be used in command sequences. These labels apply to editor mode maps (normal, insert, and select).

Minor modes

Minor modes are accessed by pressing a key (usually from normal mode), giving access to dedicated bindings. Bindings can be modified or added by nesting definitions.

[keys.insert.j]
k = "normal_mode" # Maps `jk` to exit insert mode

[keys.normal.g]
a = "code_action" # Maps `ga` to show possible code actions

# invert `j` and `k` in view mode
[keys.normal.z]
j = "scroll_up"
k = "scroll_down"

# create a new minor mode bound to `+`
[keys.normal."+"]
m = ":run-shell-command make"
c = ":run-shell-command cargo build"
t = ":run-shell-command cargo test"

Component keys

Prompts, pickers, menus, and popups have their own action maps. Hover pages and signature overloads can also be remapped. Each table accepts the actions listed below; action names are local to that component.

[keys.picker]
C-j = "move_down"
C-k = "move_up"
C-space = "accept"
C-n = "unbound" # Release this key to the picker's prompt.

[keys.buffer-picker]
C-x = "close_buffer" # Close the selected buffer and keep the picker open.

[keys.prompt]
C-j = "accept"
C-x = { h = "move_start", l = "move_end" }
A-backspace = ["delete_word_backward", "delete_word_backward"]

[keys.menu]
C-j = "move_down"
C-k = "move_up"

[keys.popup]
C-j = "scroll_down"
C-k = "scroll_up"

[keys.signature-help]
A-j = "next"
A-k = "previous"

[keys.hover]
A-j = "next"
A-k = "previous"

Bindings merge over the existing defaults. Trusted workspace configuration overrides global configuration, including individual keys in nested maps. :config-reload applies changes to open components as well.

no_op consumes a key without performing an action. unbound removes its component action and lets the usual fallback handle the key: printable text in a prompt, the embedded prompt in a picker, or the next component for menus and popups. Nested content has priority over its container: completion menus use keys.menu before keys.popup. To release C-d from both layers to an editor binding, set C-d = "unbound" in both tables.

Nested maps wait for another key. Escape or an invalid continuation cancels the sequence and consumes that key. Prefix state belongs to each component instance. Pasting into a prompt cancels its prefix and inserts the pasted text. Action lists run in order and stop when an action closes the component, starts register selection, or performs an item action. Editor commands and macros belong to the editor mode maps and cannot be used as component actions.

Picker actions take priority over prompt actions. Prompt editing bindings apply to a picker’s query when the picker leaves that key unbound; use picker actions to accept or cancel a picker. Smart Tab still supersedes default menu Tab navigation when enabled, while explicit accept or no_op bindings take effect. Insert-mode popups normally pass Escape through to the editor; Escape cancels pending component sequences first, and an explicit popup no_op binding consumes it. To remap Escape inside an insert-mode menu or page selector, also set esc = "unbound" in keys.popup so the container passes it to its content.

TableActions
keys.promptaccept, cancel, move_left, move_right, move_word_left, move_word_right, move_start, move_end, delete_backward, delete_forward, delete_word_backward, delete_word_forward, delete_to_start, delete_to_end, insert_word, insert_register, history_previous, history_next, complete_previous, complete_next, exit_selection
keys.pickeraccept, accept_keep_open, accept_horizontal_split, accept_vertical_split, cancel, move_up, move_down, move_start, move_end, page_up, page_down, toggle_preview, populate_quicklist
keys.buffer-pickerAll keys.picker actions, plus close_buffer
keys.menuaccept, cancel, move_up, move_down, page_up, page_down
keys.popupcancel, scroll_up, scroll_down
keys.hover, keys.signature-helpprevious, next

All component tables also accept no_op and unbound. Popup scrolling and menu paging move by half a page. Picker paging moves by a full page.

keys.buffer-picker inherits keys.picker and accepts all shared picker actions plus close_buffer. Buffer-specific bindings override shared bindings, including individual keys in nested maps. A workspace override replaces a global binding within the same table; buffer-specific bindings take priority over shared bindings from either configuration file.

close_buffer operates on the selected row. It waits for pending saves, refuses to discard unsaved changes, and reports errors while leaving the picker open. On success, it refreshes the buffer list, keeps the query, and selects the nearest remaining row. It also ends the current action sequence so subsequent actions cannot use a selection that is still being refreshed. The action is available only in keys.buffer-picker; it has no default binding. The picker accepts another row once the refreshed results are ready; query editing and cancellation remain available while the matcher runs.

Special keys and modifiers

Ctrl, Shift and Alt modifiers are encoded respectively with the prefixes C-, S- and A-.

The Super key - the Windows/Linux key or the Command key on Mac keyboards - is also supported when using a terminal emulator that supports the enhanced keyboard protocol. The super key is encoded with prefixes Meta-, Cmd- or Win-. These are all synonyms for the super modifier - binding a key with a Win- modifier will mean it can be used with the Windows/Linux key or the Command key.

[keys.normal]
C-s = ":write" # Ctrl and 's' to write
Cmd-s = ":write" # Cmd or Win or Meta and 's' to write

Special keys are encoded as follows:

Key nameRepresentation
Backspace"backspace"
Space"space"
Return/Enter"ret"
Left"left"
Right"right"
Up"up"
Down"down"
Home"home"
End"end"
Page Up"pageup"
Page Down"pagedown"
Tab"tab"
Delete"del"
Insert"ins"
Null"null"
Escape"esc"
Less Than (<)"lt"
Greater Than (>)"gt"

Keys can be disabled by binding them to the no_op command.

All other keys such as ?, !, - etc. can be used literally:

[keys.normal]
"?" = ":write"
"!" = ":write"
"-" = ":write"

Note: - can’t be used when combined with a modifier, for example Alt + - should be written as A-minus. A-- is not accepted.

Custom commands

Custom commands define short command-mode (:) commands in config.toml. A custom command can run one or more built-in typable or static commands, or a single key macro.

[commands]
":wq" = [":write", ":quit"]
":f" = ":format"
":W" = ":write!"
":hints" = ":toggle lsp.inlay-hints.enable"

Typable commands start with :. Static commands, such as move_char_right, use their bare names. Key macros start with @, for example @miw. A macro must be the only item in a custom command because command sequences and macros use different execution models.

The available typable and static commands are listed in the Commands and Keymap documentation.

Mapped typable commands must be built-ins; custom commands cannot invoke other custom commands.

Arguments

Use %arg{n} to pass a positional argument to a mapped typable command. Argument indexes start at zero.

[commands]
":case" = ":pipe xargs ccase --to %arg{0}"

Running :case snake executes :pipe xargs ccase --to snake. Missing arguments expand to an empty string. See Command line for the general expansion syntax.

Descriptions and completion

Use the detailed form to document accepted arguments and inherit completion from a built-in command:

[commands.":wcd!"]
commands = [":write! %arg{0}", ":cd %sh{ %arg{0} | path dirname }"]
desc = "Force save the buffer, then change to its directory"
accepts = "<path>"
completer = ":write"

The description supports Markdown and is shown in the command prompt. Here :wcd! also uses :write’s path completion.

Shadowing built-ins

Custom commands take precedence over built-ins. Prefix a command with ^ to bypass a custom definition:

[commands]
":w" = ":write!"

With this configuration, :w force-writes while :^w retains the built-in :write behavior.

Hidden commands

Commands whose configuration key starts with : appear in command-mode completion and documentation. Omit that prefix to keep a command usable but hidden:

[commands]
"0" = ":goto 1"

The command remains available as :0 but does not appear in the command list.

Global and workspace command tables are merged in the same way as the rest of the configuration. A workspace definition overrides a global command with the same name, and workspace commands are only loaded for trusted workspaces.

Languages

Language-specific settings and settings for language servers are configured in languages.toml files.

The main configuration reference contains the canonical list of every accepted key. This chapter explains how the language, server, formatter, debugger, and grammar sections work together.

languages.toml files

There are three possible locations for a languages.toml file:

  1. In the Mitos source code, which lives in the Mitos repository. It provides the default configurations for languages and language servers.

  2. In your configuration directory. This overrides values from the built-in language configuration. For example, to disable auto-formatting for Rust:

    # in <config_dir>/mitos/languages.toml
    
    [language-server.mylang-lsp]
    command = "mylang-lsp"
    
    [[language]]
    name = "rust"
    auto-format = false
    
  3. In a .mitos folder in your project. Language configuration may also be overridden local to a project by creating a languages.toml file in a .mitos folder. Its settings will be merged with the language configuration in the configuration directory and the built-in configuration.

Language configuration

Each language is configured by adding a [[language]] section to a languages.toml file. For example:

[[language]]
name = "mylang"
scope = "source.mylang"
injection-regex = "mylang"
file-types = ["mylang", "myl"]
comment-tokens = "#"
indent = { tab-width = 2, unit = "  " }
formatter = { command = "mylang-formatter" , args = ["--stdin"] }
language-servers = [ "mylang-lsp" ]

These configuration keys are available:

KeyDescription
nameThe name of the language
language-idThe language-id for language servers, checkout the table at TextDocumentItem for the right id
scopeA string like source.js that identifies the language. Currently, we strive to match the scope names used by popular TextMate grammars and by the Linguist library. Usually source.<name> or text.<name> in case of markup languages
injection-regexregex pattern that will be tested against a language name in order to determine whether this language should be used for a potential language injection site.
file-typesThe filetypes of the language, for example ["yml", "yaml"]. See the file-type detection section below.
shebangsThe interpreters from the shebang line, for example ["sh", "bash"]
rootsA set of marker files used for LSP working directory selection. Mitos starts at the file, walks upward, and remembers the topmost i.e. last directory that contains a marker file. For example Cargo.lock, yarn.lock
auto-formatWhether to autoformat this language when saving
diagnostic-severityMinimal severity of diagnostic for it to be displayed. (Allowed values: error, warning, info, hint)
comment-tokensThe tokens to use as a comment token, either a single token "//" or an array ["//", "///", "//!"] (the first token will be used for commenting). Also configurable as comment-token for backwards compatibility
block-comment-tokensThe start and end tokens for a multiline comment either an array or single table of { start = "/*", end = "*/"}. The first set of tokens will be used for commenting, any pairs in the array can be uncommented
indentThe indent to use. Has sub keys unit (the text inserted into the document when indenting; usually set to N spaces or "\t" for tabs) and tab-width (the number of spaces rendered for a tab)
language-serversThe Language Servers used for this language. See below for more information in the section Configuring Language Servers for a language
grammarThe tree-sitter grammar to use (defaults to the value of name)
formatterThe formatter for the language, it will take precedence over the lsp when defined. The formatter must be able to take the original file as input from stdin and write the formatted file to stdout. The filename of the current buffer can be passed as argument by using the %{buffer_name} expansion variable. See below for more information in the Configuring the formatter command
soft-wrapeditor.softwrap
text-widthMaximum line length. Used for the :reflow command and soft-wrapping if soft-wrap.wrap-at-text-width is set, defaults to editor.text-width
rulersOverrides the editor.rulers config key for the language.
path-completionOverrides the editor.completion.path.enable config key for the language.
word-completionOverrides the editor.completion.word configuration for the language.
spellingLayers over editor.spelling: languages and min-word-length replace the global value; words and ignore-regexes extend it.
auto-pairsOverrides the editor.auto-pairs configuration for the language.
debuggerDefines the language’s debug adapter, transport, and launch/attach templates. See Debugging (DAP) and the complete reference.
workspace-lsp-rootsDirectories (relative to the workspace root) that stop the upward root search early. Meant for project-specific hard overrides in a local .mitos/config.toml;
persistent-diagnostic-sourcesAn array of LSP diagnostic sources assumed unchanged when the language server resends the same set of diagnostics. Mitos can track the position for these diagnostics internally instead. Useful for diagnostics that are recomputed on save.
rainbow-bracketsOverrides the editor.rainbow-brackets.enable config key for the language
code-actions-on-saveList of LSP code actions to be run in order on save, for example ["source.organizeImports"]

Project and LSP root selection

This is the model Mitos uses:

  • The workspace root is found once by walking upward from the current working directory and picking the first directory that contains .git, .svn, .jj, or .mitos. If none are found, the current working directory is the workspace root.
  • Root markers (roots) are used only for LSP root selection and are found by starting at the file, not the folder Mitos was opened in.
  • We use the topmost directory that has a root marker (we stop the search at the workspace root).
  • In most cases, root markers are enough. In some repos there are multiple nested root markers and whether you want the inner or outer one is project specific. For these situations, use workspace-lsp-roots to stop the search early in a particular directory.
  • workspace-lsp-roots is meant to be set in the project-specific config: $PROJECT/.mitos/config.toml.

Interaction with required-root-patterns (a language_server configuration key):

  • After the LSP root is selected, Mitos checks for required-root-patterns in that directory. If none match, the server is not started.
  • This is validation, not root detection.

File-type detection and the file-types key

Mitos determines which language configuration to use based on the file-types key from the above section. file-types is a list of strings or tables, for example:

file-types = ["toml", { glob = "Makefile" }, { glob = ".git/config" }, { glob = ".github/workflows/*.yaml" } ]

When determining a language configuration to use, Mitos searches the file-types with the following priorities:

  1. Glob: values in glob tables are checked against the full path of the given file. Globs are standard Unix-style path globs (e.g. the kind you use in Shell) and can be used to match paths for a specific prefix, suffix, directory, etc. In the above example, the { glob = "Makefile" } config would match files with the name Makefile, the { glob = ".git/config" } config would match config files in .git directories, and the { glob = ".github/workflows/*.yaml" } config would match any yaml files in .github/workflow directories. Note that globs should always use the Unix path separator / even on Windows systems; the matcher will automatically take the machine-specific separators into account. If the glob isn’t an absolute path or doesn’t already start with a glob prefix, */ will automatically be added to ensure it matches for any subdirectory.
  2. Extension: if there are no glob matches, any file-types string that matches the file extension of a given file wins. In the example above, the "toml" config matches files like Cargo.toml or languages.toml.

Configuring the formatter command

Command line expansions are supported in the arguments of the formatter command. In particular, the %{buffer_name} variable can be passed as argument to the formatter:

formatter = { command = "mylang-formatter" , args = ["--stdin", "--stdin-filename", "%{buffer_name}"] }

Configuring code actions on save

The code-actions-on-save key lists LSP code actions to run (in order) when the buffer is saved. The available actions depend on available language servers, below are common examples and may need adjusting for your setup.

# Python with ruff
[[language]]
name = "python"
code-actions-on-save = ["source.organizeImports", "source.fixAll"]

# TS/JS with tsserver
[[language]]
name = "typescript"
code-actions-on-save = ["source.addMissingImports.ts"]

# TS/JS with eslint
[[language]]
name = "javascript"
code-actions-on-save = ["source.fixAll.eslint", "source.organizeImports"]

Language Server configuration

Language servers are configured separately in the table language-server in the same file as the languages languages.toml

For example:

[language-server.mylang-lsp]
command = "mylang-lsp"
args = ["--stdio"]
config = { provideFormatter = true }
environment = { "ENV1" = "value1", "ENV2" = "value2" }

[language-server.efm-lsp-prettier]
command = "efm-langserver"

[language-server.efm-lsp-prettier.config]
documentFormatting = true
languages = { typescript = [ { formatCommand ="prettier --stdin-filepath ${INPUT}", formatStdin = true } ] }

These are the available options for a language server.

KeyDescription
commandThe name or path of the language server binary to execute. Binaries must be in $PATH
argsA list of arguments to pass to the language server binary
configLanguage server initialization options
timeoutThe maximum time a request to the language server may take, in seconds. Defaults to 20
environmentAny environment variables that will be used when starting the language server { "KEY1" = "Value1", "KEY2" = "Value2" }
required-root-patternsA list of glob patterns to look for in the working directory of the lsp. The language server is only started if at least one of them is found.

A format sub-table within config can be used to pass extra formatting options to Document Formatting Requests. For example, with typescript:

[language-server.typescript-language-server]
# pass format options according to https://github.com/typescript-language-server/typescript-language-server#workspacedidchangeconfiguration omitting the "[language].format." prefix.
config = { format = { "semicolons" = "insert", "insertSpaceBeforeFunctionParenthesis" = true } }

Configuring Language Servers for a language

The language-servers attribute in a language tells mitos which language servers are used for this language.

They have to be defined in the [language-server] table as described in the previous section.

Different languages can use the same language server instance, e.g. typescript-language-server is used for javascript, jsx, tsx and typescript by default.

The definition order of language servers affects the order in the results list of code action menu.

In case multiple language servers are specified in the language-servers attribute of a language, it’s often useful to only enable/disable certain language-server features for these language servers.

As an example, efm-lsp-prettier of the previous example is used only with a formatting command prettier, so everything else should be handled by the typescript-language-server (which is configured by default). The language configuration for typescript could look like this:

[[language]]
name = "typescript"
language-servers = [ { name = "efm-lsp-prettier", only-features = [ "format" ] }, "typescript-language-server" ]

or equivalent:

[[language]]
name = "typescript"
language-servers = [ { name = "typescript-language-server", except-features = [ "format" ] }, "efm-lsp-prettier" ]

Each requested LSP feature is prioritized in the order of the language-servers array. For example, the first goto-definition supported language server (in this case typescript-language-server) will be taken for the relevant LSP request (command goto_definition). The features diagnostics, code-action, code-lens, completion, document-symbols and workspace-symbols are an exception to that rule, as they are working for all language servers at the same time and are merged together, if enabled for the language. If no except-features or only-features is given, all features for the language server are enabled. If a language server itself doesn’t support a feature, the next language server array entry will be tried (and so on).

The list of supported features is:

  • format
  • goto-declaration
  • goto-definition
  • goto-type-definition
  • goto-reference
  • goto-implementation
  • signature-help
  • hover
  • document-highlight
  • completion
  • inline-completion
  • code-action
  • code-lens
  • document-links
  • workspace-command
  • document-symbols
  • workspace-symbols
  • diagnostics
  • pull-diagnostics
  • rename-symbol
  • inlay-hints
  • document-colors
  • call-hierarchy

Tree-sitter grammar configuration

The source for a language’s tree-sitter grammar is specified in a [[grammar]] section in languages.toml. For example:

[[grammar]]
name = "mylang"
source = { git = "https://github.com/example/mylang", rev = "a250c4582510ff34767ec3b7dcdd3c24e8c8aa68" }

Grammar configuration takes these keys:

KeyDescription
nameThe name of the tree-sitter grammar
sourceThe method of fetching the grammar - a table with a schema defined below

Where source is a table with either these keys when using a grammar from a git repository:

KeyDescription
gitA git remote URL from which the grammar should be cloned
revThe revision (commit hash or tag) which should be fetched
subpathA path within the grammar directory which should be built. Some grammar repositories host multiple grammars (for example tree-sitter-typescript and tree-sitter-ocaml) in subdirectories. This key is used to point ms --grammar build to the correct path for compilation. When omitted, the root of repository is used

For a grammar already available locally, use source = { path = "..." } instead. Local sources are built but are not fetched by ms --grammar fetch.

Choosing grammars

You may use a top-level use-grammars key to control which grammars are fetched and built when using ms --grammar fetch and ms --grammar build.

# Note: this key must come **before** the [[language]] and [[grammar]] sections
use-grammars = { only = [ "rust", "c", "cpp" ] }
# or
use-grammars = { except = [ "yaml", "json" ] }

When omitted, all grammars are fetched and built.

Troubleshooting

Start with Mitos’ built-in health check:

ms --health

It reports the runtime paths Mitos searches, clipboard support, configured languages, and tools it could not find. For one language, run ms --health <language>; for example, ms --health rust.

Language features are missing

Language servers are separate programs and must be installed on your system. Check the configured server and whether its executable is available with:

ms --health <language>

After installing or reconfiguring a server, use :lsp-restart. See Language servers and the generated language support table for the server Mitos expects.

If the workspace shows the restricted-mode indicator, local .mitos/languages.toml settings may be blocked. Review the files and run :workspace-trust if you want Mitos to load them. See Workspace trust for the security model.

Themes or syntax files are missing

The ms binary depends on runtime files for themes, queries, and language configuration. Run ms --health to see the searched locations. When using a custom build or unpacking a release manually, point Mitos at the runtime:

export MITOS_RUNTIME=/path/to/mitos/runtime

The installation and building from source pages describe the normal runtime layout.

Configuration does not load

Open the active user configuration with :config-open, then reload it with :config-reload. Syntax errors are shown when Mitos starts or reloads the file.

Project configuration belongs in .mitos/config.toml or .mitos/languages.toml and is subject to workspace trust. You can also test a specific file independently with ms --config <file>.

A key binding does not work

Check the default keymap and your remappings first. Terminal emulators and multiplexers can intercept combinations before Mitos receives them, so test the same binding in a plain terminal session if possible.

Collect logs

Run Mitos with -v, -vv, or -vvv for progressively more detailed logging. Inside the editor, :log-open opens the current log file. To choose a separate path for a reproducible report, start Mitos with --log <file>.

When reporting a problem, include the output of ms --version, the relevant ms --health check, your operating system and terminal, and the smallest steps that reproduce it.

Ecosystem

This section has information related to the wider Mitos ecosystem.

Migrating from Vim

Mitos’s editing model is strongly inspired from Vim and Kakoune, and a notable difference from Vim (and the most striking similarity to Kakoune) is that Mitos follows the selection → action model. This means that whatever you are going to act on (a word, a paragraph, a line, etc.) is selected first and the action itself (delete, change, yank, etc.) comes second. A cursor is simply a single width selection.

Note: As Mitos is inspired by Vim and Kakoune, the keybindings are similar but also have some differences. The content of this page is inspired by Kakoune Wiki.

NOTE: Unlike vim, f, F, t and T are not confined to the current line.

Delete/Change Commands

delete a word:

  • vim: dw
  • mitos: wd

change a word:

  • vim: cw
  • mitos: ec or wc (includes the whitespace after the word)

delete a character:

  • vim: x
  • mitos: d or ;d (; reduces the selection to a single char)

copy a line:

  • vim: yy
  • mitos: Xy (X extends all selections to whole lines)

global replace:

  • vim: :%s/word/replacement/g<ret>
  • mitos: %sword<ret><A-r>replacement<ret>

Explanation: % selects the entire buffer, s opens a regex prompt, and <ret> selects each match. Alt-r opens the replacement prompt. Type the replacement and press <ret> to apply it to all selections in one undo step. Replacement text is literal; $1 does not expand a regex capture.

go to first line:

  • vim: gg
  • mitos: gg

go to last line:

  • vim: G
  • mitos: ge

go to line start:

  • vim: 0
  • mitos: gh

go to line first non-blank character:

  • vim: ^
  • mitos: gs

go to line end:

  • vim: $
  • mitos: gl

jump to matching bracket:

  • vim: %
  • mitos: mm

Line Deletes

delete to line end:

  • vim: D
  • mitos: vgld or t<ret>d

Note: v is used along with gl (go to line end), because gl does not select text. t<ret> selects “’til” the newline represented by <ret>.

delete entire line:

  • vim: dd
  • mitos: xd

Note: x selects the entire line under the cursor

auto complete:

  • vim: C-p
  • mitos: C-x

comment lines:

  • vim: gc
  • mitos: Space-c

search for the word under the cursor:

  • vim: *
  • mitos: A-o*n (if there’s a tree-sitter grammar or LSP) or be*n

Explanation: if there’s a grammar or LSP, A-o expands selection to the parent syntax node (which would be the word in our case). Then * uses the current selection as the search pattern, and n goes to the next occurrence. b selects to the beginning of the word, and e selects to the end of the word, effectively selecting the whole word.

block selection:

  • vim: C-v, then expand your selection vertically and horizontally
  • mitos: There’s no “block selection” mode, so instead you’d use multiple cursors. Expand your block selection vertically by adding new cursors on the line below with C, and horizontally using standard movements

search “foo” and replace with “bar” in the current selection:

  • vim: :s/foo/bar/g<ret>
  • mitos: sfoo<ret><A-r>bar<ret>,

Explanation: s opens a regex prompt and selects all matches inside the selection. Alt-r opens the replacement prompt; enter bar and press <ret> to apply it once to each selection. Keep only the main selection with ,.

File actions

select the whole file:

  • vim: ggVG
  • mitos: %

reload a file from disk:

  • vim: :e<ret>
  • mitos: :reload<ret> (or :reload-all<ret> to reload all the buffers)

run shell command:

  • vim: :!command
  • mitos: :sh command (or !command to insert its output into the buffer)

setting a bookmark (bookmarking a location):

  • vim: ma to set bookmark with name a. Use `a to go back to this bookmarked location.
  • mitos: there are no named bookmarks, but you can save a location in the jumplist with C-s, then jump back to that location by opening the jumplist picker with <space>-j, or back in the jumplist with C-o and forward with C-i

Mitos allows some limited movement in insert mode without switching to normal mode.

Unlike Vim, under Mitos, the cursor shape is the same (block) in insert mode and normal mode by default. This can be adjusted in configuration:

[editor.cursor.shape]
insert = "bar"

TODO: Mention textobjects, surround, registers

Mitos mode in other software

Compatible editing modes in other software

Mitos inherits Helix’s keymap and interaction model. Existing Helix-mode integrations may therefore be useful to Mitos users, although these external projects are Helix-branded and are not maintained by Mitos.

Other editors

EditorPlugin or feature providing Helix editingComments
Vimhelix.vim config
IntelliJ IDEA / Android StudioIdeaVim plugin + helix.idea.vim configMinimum recommended version is IdeaVim 2.19.0.
Visual StudioVsVim plugin + helix.vs.vim config
Visual Studio CodeDance extension, or its Helix forkThe Helix fork has diverged.
Visual Studio CodeHelix for VS Code extension
ZedNative via keybindings (issue)
CodeMirrorcodemirror-helix
Lite XLlite-modal-hx

Shells

ShellPlugin or feature providing Helix editing
FishFeature request
Fishfish-helix
Zshhelix-zsh or zsh-helix-mode
NushellFeature request

Other software

SoftwarePlugin or feature providing Helix editingComments
ObsidianObsidian-HelixUses codemirror-helix listed above.

Guides

This section contains guides for adding new languages to Mitos: language and grammar configuration, and the tree-sitter query files that drive highlighting, power indentation, textobjects, symbol tags, and other features.

Adding languages

Adding new languages to Mitos

In order to add a new language to Mitos, you will need to follow the steps below.

Language configuration

  1. Add a new [[language]] entry in the languages.toml file and provide the necessary configuration for the new language. For more information on language configuration, refer to the language configuration section of the documentation. A new language server can be added by extending the [language-server] table in the same file.
  2. If you are adding a new language or updating an existing language server configuration, run the command cargo xtask docgen to update the Language Support documentation.

💡 If you are adding a new Language Server configuration, make sure to update the Language Server Wiki with the installation instructions.

Grammar configuration

  1. If a tree-sitter grammar is available for the new language, add a new [[grammar]] entry to the languages.toml file.
  2. If you are testing the grammar locally, you can use the source.path key with an absolute path to the grammar. However, before submitting a pull request, make sure to switch to using source.git.
  3. Pin a revision that includes the generated src/parser.c and any scanner sources. Some grammars, including Perl, keep generated parsers on a separate release branch; development commits may not contain them.
  4. After changing a grammar revision, run cargo xtask update-nix-grammar-sources with Nix installed and commit the updated nix/grammar-sources.json alongside languages.toml. Nix builds use the locked archive revision and hash.

Queries

  1. In order to provide syntax highlighting and indentation for the new language, you will need to add queries.

  2. Create a new directory for the language with the path runtime/queries/<name>/.

  3. Refer to the tree-sitter website for more information on writing queries.

  4. The highlight captures (@function, @type, …) and how they resolve are documented on the themes page: match the most specific scope that fits, capture the leaf node you mean, and remember that the last matching pattern (and the innermost node) wins.

  5. Mitos loads several query files from that directory; only highlights.scm is required:

    FilePurposeGuide
    highlights.scmsyntax highlightinghighlights.md
    injections.scmembed other languages in regions (strings, code fences)injection.md
    indents.scmindentationindent.md
    textobjects.scmtextobjects and navigation (mif, ]f, …)textobject.md
    locals.scmscope tracking so locals highlight distinctlylocals.md
    tags.scmdocument/workspace symbol pickerstags.md
    rainbows.scmrainbow bracketsrainbow_bracket_queries.md

    A query file may reuse another language’s with ; inherits: <lang> on the first line. Run cargo xtask query-check [language] to check that the queries are valid against the grammar.

Common issues

  • If you encounter errors when running Mitos after switching branches, you may need to update the tree-sitter grammars. Run the command ms --grammar fetch to fetch the grammars and ms --grammar build to build any out-of-date grammars.
  • If a parser is causing a segfault, or you want to remove it, make sure to remove the compiled parser located at runtime/grammars/<name>.so.
  • If you are attempting to add queries and Mitos is unable to locate them, ensure that the environment variable MITOS_RUNTIME is set to the location of the runtime folder you’re developing in.
  • Validate queries with cargo xtask query-check [language] (every query file must compile against the grammar). highlight-check and indent-check additionally run the real highlighter and indenter over the test fixtures catch mistakes.

Adding highlight queries

highlights.scm queries assign a highlight scope (@function, @type, @keyword, …) to nodes in the syntax tree; the theme then maps each scope to a colour. Highlighting is the one query file that every language needs.

Query files should be placed in runtime/queries/{language}/highlights.scm when contributing to Mitos.

Scopes

The full list of highlight scopes, and what each is for, is documented on the themes page. Match the most specific scope that fits the node — for example a method call is @function.method while a plain field access is @variable.other.member.

A query file may reuse another language’s with ; inherits: <lang> on the first line (for example tsx inherits typescript, which inherits ecma). An inherited file is compiled against each inheriting grammar, so every capture must be valid there too.

Precedence

Two rules decide which capture wins when more than one matches the same text:

  1. Same span: last match wins. Among captures covering the same bytes, the pattern that appears later in the file wins. Put a generic rule before the specific one that should override it.
  2. Nested nodes: innermost wins. When a parent and a child node both cover the text, the child’s capture wins, regardless of file order.

A common consequence of rule 2: capture the leaf you mean. A call captured on a wrapping node loses to a base (identifier) @variable on the inner identifier, so put @function on the identifier itself.

Where the grammar can’t distinguish a scope, casing is often used as a heuristic:

((identifier) @constant
 (#match? @constant "^[A-Z][A-Z_]*$"))

Testing

cargo xtask query-check [language] confirms the queries are valid against the grammar. cargo xtask highlight-check [language] runs the real highlighter over the fixtures in tests/query/highlights/<language-id>/<name>.<ext>, where a caret comment line (// ^ @capture) asserts the winning scope at the column above it; this catches the precedence mistakes that query-check cannot see. cargo xtask highlight-check --dump <language> <file> prints the winning capture per span for an arbitrary file.

Adding locals queries

locals.scm queries teach Mitos about variable scopes and definitions so that local variables can be highlighted distinctly from global ones. When a @local.reference identifier is resolved to a @local.definition, it inherits the highlight class of that definition rather than the class assigned by highlights.scm.

Query files should be placed in runtime/queries/{language}/locals.scm when contributing to Mitos. Like other query files, locals.scm may reuse another language’s with ; inherits: <lang> on the first line. Locals are resolved in their own pass with their own precedence, independently of highlights.scm.

Captures

@local.scope

Marks a node as a scope boundary. Definitions are only visible to references that appear inside the same scope or a nested one. Typical scope nodes are function bodies and blocks.

[
  (function_definition)
  (block)
] @local.scope

@local.definition.*

Marks a name node as introducing a local symbol. The suffix after @local.definition. becomes the highlight class applied to any reference that resolves to this definition. For example, @local.definition.variable.parameter causes matching references to be highlighted as variable.parameter.

(function_item
  (parameters
    (parameter
      pattern: (identifier) @local.definition.variable.parameter)))

Common suffixes mirror the highlight classes in highlights.scm: variable, variable.parameter, variable.builtin, variable.mutable, function, namespace, type, constant, etc.

@local.reference

Marks an identifier node as a potential reference to a local definition. Mitos searches enclosing scopes for a matching definition and, if found, highlights the reference with that definition’s class instead.

(identifier) @local.reference

Discard captures

Any capture in locals.scm that is not @local.scope, @local.reference, or @local.definition.* acts as a discard. It prevents a @local.reference from being resolved at that node without affecting the highlights.scm result. This is useful for identifier nodes that look like references but should not be treated as variable references, for example keyword argument names or struct field names in struct literals.

; Keyword argument names in a call are not variable references.
(keyword_argument
  name: (identifier) @_)

Later patterns in a query file have higher precedence than earlier ones. A discard pattern must appear after the @local.reference pattern it is intended to override. Placing it later ensures it takes precedence and cancels the reference resolution for nodes it matches.

The convention is to use a capture name beginning with an underscore (e.g. @_, @_keyword) to make the discard intent clear, but any non-@local.* name works.

How definitions and references are matched

Mitos matches references to definitions by comparing the text of the reference node against the text of definitions visible from the current scope. Definitions in inner scopes shadow those in outer scopes. If no definition is found the reference is left with its highlights.scm highlight.

Relationship with highlights.scm

The locals system runs alongside highlights.scm, not instead of it. highlights.scm always determines the baseline highlight for a node. locals.scm can override that highlight only when a @local.reference successfully resolves to a @local.definition, and only for that specific resolution. Non-@local.* captures in locals.scm (i.e. discards) have no effect on highlights.scm results.

Adding textobject queries

Mitos supports textobjects that are language specific, such as functions, classes, etc. These textobjects require an accompanying tree-sitter grammar and a textobjects.scm query file to work properly. Tree-sitter allows us to query the source code syntax tree and capture specific parts of it. The queries are written in a lisp dialect. More information on how to write queries can be found in the official tree-sitter documentation.

Query files should be placed in runtime/queries/{language}/textobjects.scm when contributing to Mitos. Note that to test the query files locally you should put them under your local runtime directory (~/.config/mitos/runtime on Linux for example).

The following captures are recognized:

Capture Name
function.inside
function.around
class.inside
class.around
test.inside
test.around
parameter.inside
parameter.around
comment.inside
comment.around
entry.inside
entry.around
xml-element.inside
xml-element.around

Example query files can be found in the mitos GitHub repository.

Queries for textobject based navigation

Tree-sitter based navigation in Mitos is done using captures in the following order:

  • object.movement
  • object.around
  • object.inside

For example if a function.around capture has been already defined for a language in its textobjects.scm file, function navigation should also work automatically. function.movement should be defined only if the node captured by function.around doesn’t make sense in a navigation context.

Adding indent queries

Mitos uses tree-sitter to correctly indent new lines. This requires a tree-sitter grammar and an indent.scm query file placed in runtime/queries/{language}/indents.scm.

The indent level of a line is the number of @indent scopes that contain it. An @indent capture on a node opens a scope spanning the lines after the node’s first line through its last line; a line is indented once for every such scope wrapping it. (A few capture types adjust this — @outdent cancels a level, @align aligns to a column, etc. (see Capture types.)

Note that it matters where these scopes begin: multiple scopes that open on the same physical line only increase the indent by 1 (the same-line rule). See Capture types.

By default, Mitos uses the hybrid indentation heuristic. This means that indent queries are not used to compute the expected absolute indentation of a line but rather the expected difference in indentation between the new and an already existing line. This difference is then added to the actual indentation of the already existing line. Since this makes errors in the indent queries harder to find, it is recommended to disable it when testing via :set indent-heuristic tree-sitter. The rest of this guide assumes that the tree-sitter heuristic is used.

Indent queries

When Mitos is inserting a new line through o, O, or <ret>, to determine the indent level for the new line, the query in indents.scm is run on the document. The starting position of the query is the end of the line above where a new line will be inserted.

For o, the inserted line is the line below the cursor, so that starting position of the query is the end of the current line.

fn need_hero(some_hero: Hero, life: Life) -> {
    matches!(some_hero, Hero { // ←─────────────────╮
        strong: true,//←╮  ↑  ↑                     │
        fast: true,  // │  │  ╰── query start       │
        sure: true,  // │  ╰───── cursor            ├─ traversal 
        soon: true,  // ╰──────── new line inserted │  start node
    }) &&            //                             │
//  ↑                                               │
//  ╰───────────────────────────────────────────────╯
    some_hero > life
}

For O, the newly inserted line is the current line, so the starting position of the query is the end of the line above the cursor.

fn need_hero(some_hero: Hero, life: Life) -> { // ←─╮
    matches!(some_hero, Hero { // ←╮          ↑     │
        strong: true,//    ↑   ╭───╯          │     │
        fast: true,  //    │   │ query start ─╯     │
        sure: true,  //    ╰───┼ cursor             ├─ traversal
        soon: true,  //        ╰ new line inserted  │  start node
    }) &&            //                             │
    some_hero > life //                             │
} // ←──────────────────────────────────────────────╯

From this starting node, the syntax tree is walked up to the root, collecting every @indent/@outdent/@align/… capture on an ancestor. Each @indent ancestor whose scope contains the line counts for one indent level (scopes opening on the same line collapse to one); the capture types describe the adjustments.

Capture types

  • @indent: Open an indent scope on this node — every line it contains is indented one more level. Scopes that open on the same line collapse: multiple @indent beginning on one line only add 1. A node captured by both @indent and @outdent contributes nothing (its level is cancelled) — used e.g. for a nested else if that should not stack a second level. By default the scope opens at the node’s own first line; see the header scope to open it at the parent (header) line instead.
  • @outdent: Decrease by 1 the indent of the line on which this (usually a closing token like }/)/], or a keyword like else) begins.
  • @indent.always: Like @indent but does not collapse — multiple on the same line each add a level. The net level contribution is @indent.always − @outdent.always.
  • @outdent.always: Like @outdent but stacks, the counterpart to @indent.always.
  • @align (default scope all): Align everything inside this node to some anchor. The anchor is given by the start of the node captured by @anchor in the same pattern. Every pattern with an @align should contain exactly one @anchor. Indent (and outdent) for nodes below (in terms of their starting line) the @align node is added to the indentation required for alignment.
  • @extend: Extend the range of this node to the end of the line and to lines that are indented more than the line that this node starts on. This is useful for languages like Python, where for the purpose of indentation some nodes (like functions or classes) should also contain indented lines that follow them.
  • @extend.prevent-once: Prevents the first extension of an ancestor of this node. For example, in Python a return expression always ends the block that it is in. Note that this only stops the extension of the next @extend capture. If multiple ancestors are captured, only the extension of the innermost one is prevented. All other ancestors are unaffected (regardless of whether the innermost ancestor would actually have been extended).
  • @opaque: Mark a literal body such as a string, heredoc, or block comment. Lines that begin inside the captured node keep their existing indentation instead of being reindented, so the contents of multi-line literals are left untouched.

@indent / @outdent

Consider this example:

fn shout(things: Vec<Thing>) {
    //                       ↑
    //                       ├───────────────────────╮ indent level
    //                    @indent                    ├┄┄┄┄┄┄┄┄┄┄┄┄┄┄
    //                                               │
    let it_all = |out| { things.filter(|thing| { //  │      1
    //                 ↑                       ↑     │
    //                 ├───────────────────────┼─────┼┄┄┄┄┄┄┄┄┄┄┄┄┄┄
    //              @indent                 @indent  │
    //                                               │      2
        thing.can_do_with(out) //                    │
    })}; //                                          ├┄┄┄┄┄┄┄┄┄┄┄┄┄┄
  //↑↑↑                                              │      1
} //╰┼┴──────────────────────────────────────────────┴┄┄┄┄┄┄┄┄┄┄┄┄┄┄
// 3x @outdent
((block) @indent)
["}" ")"] @outdent

Note how on the second line two blocks open on the same line: since both scopes begin on that line they collapse, for a net increase of 1. On the last line, the three block scopes all contain it, but it begins with three @outdent } tokens that cancel them, so the line lands back at the enclosing level.

Same-line collapse

The collapse above is a deliberate, load-bearing invariant:

A line is indented by one level per physical line on which a containing @indent scope opens, not one level per scope. Several @indent scopes that open on the same line together add a single level.

This is what makes method/builder chains flatten instead of staircasing. Grammars typically nest a chain so each .method() link is a call inside a member_expression inside the previous link, and several of those nodes begin on the receiver’s line:

let x = thing       // ← chain opens here
    .foo()          // each link aligned one level in,
    .bar()          // not progressively deeper
    .baz();
(call_expression) @indent

Even though many call_expression/member_expression scopes contain the .bar() line, they all open on the thing line, so they collapse to a single level and the continuation lines line up. Without the collapse, every link would add a level and the chain would stair-step to the right.

If you instead want each scope to count even when several open on one line (for example YAML’s “list item and map both start on the same line”) opt out with @indent.always (described below), which does not collapse.

@extend / @extend.prevent-once

For an example of where @extend can be useful, consider Python, which is whitespace-sensitive.

]
  (parenthesized_expression)
  (function_definition)
  (class_definition)
] @indent

class Hero:
    def __init__(self, strong, fast, sure, soon):#  ←─╮
        self.is_strong = strong #                     │
        self.is_fast = fast     # ╭─── query start    │
        self.is_sure = sure     # │ ╭─ cursor         │
        self.is_soon = soon     # │ │                 │
        #     ↑            ↑      │ │                 │
        #     │            ╰──────╯ │                 │
        #     ╰─────────────────────╯                 │
        #                                             ├─ traversal
    def need_hero(self, life):         #              │  start node
        return (                       #              │
            self.is_strong             #              │
            and self.is_fast           #              │
            and self.is_sure           #              │
            and self.is_soon           #              │
            and self > life            #              │
        ) # ←─────────────────────────────────────────╯

Without braces to catch the scope of the function, the smallest descendant of the cursor on a line feed ends up being the entire inside of the class. Because of this, it will miss the entire function node and its indent capture, leading to an indent level one too small.

To address this case, @extend tells mitos to “extend” the captured node’s span to the line feed and every consecutive line that has a greater indent level than the line of the node.

(parenthesized_expression) @indent

]
  (function_definition)
  (class_definition)
] @indent @extend

class Hero:
    def __init__(self, strong, fast, sure, soon):#  ←─╮
        self.is_strong = strong #                     │
        self.is_fast = fast     # ╭─── query start    ├─ traversal
        self.is_sure = sure     # │ ╭─ cursor         │  start node
        self.is_soon = soon     # │ │ ←───────────────╯
        #     ↑            ↑      │ │                 
        #     │            ╰──────╯ │
        #     ╰─────────────────────╯
    def need_hero(self, life):
        return (
            self.is_strong
            and self.is_fast
            and self.is_sure
            and self.is_soon
            and self > life
        )

Furthermore, there are some cases where extending to everything with a greater indent level may not be desirable. Consider the need_hero function above. If our cursor is on the last line of the returned expression.

class Hero:
    def __init__(self, strong, fast, sure, soon):
        self.is_strong = strong
        self.is_fast = fast
        self.is_sure = sure
        self.is_soon = soon

    def need_hero(self, life):
        return (
            self.is_strong
            and self.is_fast
            and self.is_sure
            and self.is_soon
            and self > life
        ) # ←─── cursor
    #←────────── where cursor should go on new line

In Python, the are a few tokens that will always end a scope, such as a return statement. Since the scope ends, so should the indent level. But because the function span is extended to every line with a greater indent level, a new line would just continue on the same level. And an @outdent would not help us here either, since it would cause everything in the parentheses to become outdented as well.

To help, we need to signal an end to the extension. We can do this with @extend.prevent-once.

(parenthesized_expression) @indent

]
  (function_definition)
  (class_definition)
] @indent @extend

(return_statement) @extend.prevent-once

Brace-less bodies

A brace-less single-statement body — if (cond) with its statement on the next line and no {} — is a body the indent must wrap, but the body node’s own first line is the line that needs the indent (there is no separate opening line). Capture the body and give it the header scope, which opens the scope at the header (the captured node’s parent) line instead of the body’s own line, so the body line is contained:

(if_statement
  consequence: (_) @indent
  (#not-kind-eq? @indent "compound_statement")
  (#set! "scope" "header"))
(while_statement
  body: (_) @indent
  (#not-kind-eq? @indent "compound_statement")
  (#set! "scope" "header"))

The query already names exactly the brace-less body (the consequence: / body: field, with #not-kind-eq? skipping the braced form the surrounding block indents), so the engine just opens that capture’s scope at its parent — it does not itself inspect node kinds or field names. This works for a multi-line brace-less body too (e.g. one returning a lambda). Give else and do / while their own pattern (on the alternative / body field) so the trailing else / while keyword line is not indented along with the body.

@indent.always / @outdent.always

As mentioned before, normally if there is more than one @indent or @outdent capture on the same line, they are combined.

Sometimes, there are cases when you may want to ensure that every indent capture is additive, regardless of how many occur on the same line. Consider this example in YAML.

  - foo: bar
# ↑ ↑
# │ ╰─────────────── start of map
# ╰───────────────── start of list element
    baz: quux # ←─── cursor
    # ←───────────── where the cursor should go on a new line
    garply: waldo
  - quux:
      bar: baz
    xyzzy: thud
    fred: plugh

In YAML, you often have lists of maps. In these cases, the syntax is such that the list element and the map both start on the same line. But we really do want to start an indentation for each of these so that subsequent keys in the map hang over the list and align properly. This is where @indent.always helps.

((block_sequence_item) @item @indent.always @extend
  (#not-one-line? @item))

((block_mapping_pair
    key: (_) @key
    value: (_) @val
    (#not-same-line? @key @val)
  ) @indent.always @extend
)

Predicates

In some cases, an S-expression cannot express exactly what pattern should be matched. For that, tree-sitter allows for predicates to appear anywhere within a pattern, similar to how #set! declarations work:

(some_kind
  (child_kind) @indent
  (#predicate? arg1 arg2 ...)
)

The number of arguments depends on the predicate that’s used. Each argument is either a capture (@name) or a string ("some string"). The following predicates are supported by tree-sitter:

  • #eq?/#not-eq?: The first argument (a capture) must/must not be equal to the second argument (a capture or a string).

  • #match?/#not-match?: The first argument (a capture) must/must not match the regex given in the second argument (a string).

  • #any-of?/#not-any-of?: The first argument (a capture) must/must not be one of the other arguments (strings).

Additionally, we support some custom predicates for indent queries:

  • #not-kind-eq?: The kind of the first argument (a capture) must not be equal to the second argument (a string).

  • #same-line?/#not-same-line?: The captures given by the 2 arguments must/must not start on the same line.

  • #one-line?/#not-one-line?: The captures given by the fist argument must/must span a total of one line.

The header scope

By default an @indent scope opens at the captured node’s own first line, so the lines inside it are indented and its first line is not. Sometimes the node you must capture is the line that needs indenting — a brace-less body such as the statement after if (cond) (see Brace-less bodies). For this, set scope to header:

(if_statement
  consequence: (_) @indent
  (#not-kind-eq? @indent "compound_statement")
  (#set! "scope" "header"))

header opens the scope at the captured node’s parent (the if_statement header) line instead of the node’s own line, so the body’s first line is contained and indented. The query selects exactly the body, so the engine just honours the annotation — it does not match on node kinds or field names itself.

(Older queries used tail / all scopes to control whether a node’s first line was included; under the containment model those are no longer needed and have been removed. header is the one scope the engine reads.)

Testing

cargo xtask indent-check [language] checks the queries against the fixtures in tests/indent/<language-id>.<ext> in both modes: re-indenting each line and simulating a newline typed after it, so a rule that is correct one way but wrong the other is caught.

Adding injection queries

Writing language injection queries allows one to highlight a specific node as a different language. In addition to the standard language injection options used by tree-sitter, there are a few Mitos specific extensions that allow for more control.

Injection drives more than highlighting: within an injected region Mitos also uses the injected language’s own indentation, textobjects, and comment tokens — so, for example, editing JavaScript inside an HTML <script> indents and comments as JavaScript.

An example of a simple query that would highlight all strings as bash in Nix:

((string_expression (string_fragment) @injection.content)
  (#set! injection.language "bash"))

Another example is this query, which highlights links in comments and keywords like “TODO”, by reusing the dedicated “comment” language:

((comment) @injection.content
  (#set! injection.language "comment"))

Capture Types

  • @injection.language (standard): The captured node may contain the language name used to highlight the node captured by @injection.content.

  • @injection.content (standard): Marks the content to be highlighted as the language captured with @injection.language et al.

  • @injection.filename (extension): The captured node may contain a filename with a file-extension known to Mitos, highlighting @injection.content as that language. This uses the language extensions defined in both the default languages.toml distributed with Mitos, as well as user defined languages.

  • @injection.shebang (extension): The captured node may contain a shebang used to choose a language to highlight as. This also uses the shebangs defined in the default and user languages.toml.

Settings

  • injection.combined (standard): Indicates that all the matching nodes in the tree should have their content parsed as one nested document.

  • injection.language (standard): Forces the captured content to be highlighted as the given language

  • injection.include-children (standard): Indicates that the content node’s entire text should be re-parsed, including the text of its child nodes. By default, child nodes’ text will be excluded from the injected document.

  • injection.include-unnamed-children (extension): Same as injection.include-children but only for unnamed child nodes.

Predicates

  • #eq? (standard): The first argument (a capture) must be equal to the second argument (a capture or a string).

  • #match? (standard): The first argument (a capture) must match the regex given in the second argument (a string).

  • #any-of? (standard): The first argument (a capture) must be one of the other arguments (strings).

Adding tags queries

See tree-sitter’s documentation on Code Navigation Systems for more background on tags queries.

Mitos provides LSP-like features such as document and workspace symbol pickers out-of-the-box for languages with tags.scm queries based on syntax trees. To be analyzed a language must have a tree-sitter grammar and a tags.scm query file which pattern matches interesting nodes from syntax trees.

Query files should be placed in runtime/queries/{language}/tags.scm when contributing to Mitos. You may place these under your local runtime directory (~/.config/mitos/runtime in Linux for example) for the sake of testing.

Captures

@definition.*

Marks a node as a symbol definition. The following definition captures are recognized:

Capture name
definition.class
definition.constant
definition.enum
definition.field
definition.function
definition.interface
definition.macro
definition.module
definition.section
definition.struct
definition.type

Captures outside this list are ignored by the symbol pickers, so map a language-specific construct onto the closest listed kind rather than inventing a new one.

@name

Marks the name identifier node within a match. @definition.* should capture the entire definition node, and @name should capture the name identifier within that same match:

(function_definition
  name: (identifier) @name) @definition.function

(class_definition
  name: (identifier) @name) @definition.class

@reference.*

Marks a node as a call site or type reference. These are used by workspace symbol search to locate usages. @reference.call and @reference.class are the common variants. As with definitions, @name captures the identifier:

(call
  function: (identifier) @name) @reference.call

Example query files can be found in the Mitos GitHub repository.

Adding rainbow bracket queries

Mitos uses rainbows.scm tree-sitter query files to provide rainbow bracket functionality.

Tree-sitter queries are documented in the tree-sitter online documentation. If you’re writing queries for the first time, be sure to check out the section on syntax highlighting queries and on query syntax.

Rainbow queries have two captures: @rainbow.scope and @rainbow.bracket. @rainbow.scope should capture any node that increases the nesting level while @rainbow.bracket should capture any bracket nodes. Put another way: @rainbow.scope switches to the next rainbow color for all nodes in the tree under it while @rainbow.bracket paints captured nodes with the current rainbow color.

For an example, let’s add rainbow queries for the tree-sitter query (TSQ) language itself. These queries will go into a runtime/queries/tsq/rainbows.scm file in the repository root.

First we’ll add the @rainbow.bracket captures. TSQ only has parentheses and square brackets:

["(" ")" "[" "]"] @rainbow.bracket

The ordering of the nodes within the alternation (square brackets) is not taken into consideration.

Angle brackets need care: < and > are usually comparison operators too, so capturing them globally would rainbow-colour every comparison. Capture them only within the generic node instead, which also marks that node as a scope:

(type_arguments ["<" ">"] @rainbow.bracket) @rainbow.scope

Note: Why are these nodes quoted? Most syntax highlights capture text surrounded by parentheses. These are named nodes and correspond to the names of rules in the grammar. Brackets are usually written in tree-sitter grammars as literal strings, for example:

{
  // ...
  arguments: $ => seq("(", repeat($.argument), ")"),
  // ...
}

Nodes written as literal strings in tree-sitter grammars may be captured in queries with those same literal strings.

Then we’ll add @rainbow.scope captures. The easiest way to do this is to view the grammar.js file in the tree-sitter grammar’s repository. For TSQ, that file is here. As we scroll down the grammar.js, we see that the (alternation), (L36) (group) (L57), (named_node) (L59), (predicate) (L87) and (wildcard_node) (L97) nodes all contain literal parentheses or square brackets in their definitions. These nodes are all direct parents of brackets and happen to also be the nodes we want to change to the next rainbow color, so we capture them as @rainbow.scope.

[
  (group)
  (named_node)
  (wildcard_node)
  (predicate)
  (alternation)
] @rainbow.scope

This strategy works as a rule of thumb for most programming and configuration languages. Markup languages can be trickier and may take additional experimentation to find the correct nodes to use for scopes and brackets.

The :tree-sitter-subtree command shows the syntax tree under the primary selection in S-expression format and can be a useful tool for determining how to write a query.

Properties

The rainbow.include-children property may be applied to @rainbow.scope captures. By default, all @rainbow.bracket captures must be direct descendant of a node captured with @rainbow.scope in a syntax tree in order to be highlighted. The rainbow.include-children property disables that check and allows @rainbow.bracket captures to be highlighted if they are direct or indirect descendants of some node captured with @rainbow.scope.

For example, this property is used in the HTML rainbow queries.

For a document like <a>link</a>, the syntax tree is:

(element                   ; <a>link</a>
  (start_tag               ; <a>
    (tag_name))            ; a
  (text)                   ; link
  (end_tag                 ; </a>
    (tag_name)))           ; a

If we want to highlight the <, > and </ nodes with rainbow colors, we capture them as @rainbow.bracket:

["<" ">" "</"] @rainbow.bracket

And we capture (element) as @rainbow.scope because (element) nodes nest within each other: they increment the nesting level and switch to the next color in the rainbow.

(element) @rainbow.scope

But this combination of @rainbow.scope and @rainbow.bracket will not highlight any nodes. <, > and </ are children of the (start_tag) and (end_tag) nodes. We can’t capture (start_tag) and (end_tag) as @rainbow.scope because they don’t nest other elements. We can fix this case by removing the requirement that <, > and </ are direct descendants of (element) using the rainbow.include-children property.

((element) @rainbow.scope
 (#set! rainbow.include-children))

With this property set, <, >, and </ will highlight with rainbow colors even though they aren’t direct descendents of the (element) node.

rainbow.include-children is not necessary for the vast majority of programming languages. It is only necessary when the node that increments the nesting level (changes rainbow color) is not the direct parent of the bracket node.

Adding Spellcheck Queries

Mitos uses spellcheck.scm tree-sitter query files to decide which parts of a document are spell checked. See Spell checking for info about the feature.

Tree-sitter queries are documented in the tree-sitter online documentation. If you’re writing queries for the first time, be sure to check out the section on syntax highlighting queries and on query syntax.

Spellcheck queries have two captures:

  • @spell marks a node whose text should be checked.
  • @nospell excludes a node (or part of one) from a surrounding @spell capture.

Queries go in a runtime/queries/<language>/spellcheck.scm file. A language with no query has no checked regions of its own; injected languages can still provide queries. Files without a syntax tree, including plain text, are checked in full.

Checking comments

Most languages inject a shared comment grammar into their comments, and that grammar has a query (runtime/queries/comment/spellcheck.scm) capturing comment text. So you usually do not need a query just to check a language’s comments.

Choosing prose regions

Prefer nodes that represent prose over capturing an entire document or every string. For example, Python’s query selects the first string statement in a module, class, or function; JSX’s query selects rendered text while leaving expressions and property values alone.

Use @nospell on enclosing code regions when their children can otherwise match @spell. HTML’s pre and code elements are examples. Exclusions also apply to captures from injected languages, so a code block can exclude comments that an injected grammar would otherwise check.

Add coverage cases to crates/core/tests/spellcheck.rs for both prose and nearby code, metadata, and escapes. Validate the query against the configured grammar with cargo xtask query-check <language>, then run cargo test -p core --test spellcheck. Query compilation alone cannot show whether the intended text is selected.

An example

To check the contents of strings in a language, capture the string’s text node:

(string_content) @spell

Use @nospell to carve out parts that aren’t prose. For example, to check a string but skip an interpolation or escape inside it:

(string (string_content) @spell)
(escape_sequence) @nospell

The :tree-sitter-subtree command shows the syntax tree under the primary selection and is the easiest way to find the node names to capture.