Neovim integration for Mojo.
Centralizes filetype detection, Treesitter, LSP, formatting, and environment activationโdesigned so each piece can be swapped when Modular ships official tooling.
- What it provides
- Features
- Commands
- Keymaps
- Statusline
- Installation
- Setup
- Configuration
- Integrations
- Notes
.mojo,.mojoc, and๐ฅfiletype detection- Treesitter parser registration for Mojo
- Environment helpers for Pixi, virtualenv, and manual SDK paths
- LSP and formatter integration (native
vim.lsp.config/conform.nvim) - Terminal environment auto-activation
- Completion support (nvim-cmp / blink.cmp) with keywords, builtins, types, and snippets
- lualine.nvim statusline integration with SDK version display
MojoVersioncomponent for non-lualine statuslines- Debugging support โ nvim-dap (mojo-lldb-dap) + native terminal (mojo-lldb)
- Run current Mojo file with
:Mojo run/:Mojo dedicated - LSP lifecycle management (
:Mojo menu,:Mojo restart,:Mojo stop,:Mojo refresh) - 4-space indentation for Mojo files
- LazyVim, AstroNvim, NvChad, and kickstart.nvim adapter helpers
- EmmyLua type annotations (module:
Mojo-lang) - Adapter pattern for every integration โ swap any backend without changing config
.mojo, .mojoc, and ๐ฅ files are automatically recognized as mojo filetype. The plugin adds these to Neovim's filetype detection and triggers environment activation for each Mojo buffer.
Detects Pixi (pixi.toml / .pixi/) and virtualenv (.venv/) projects and activates them for LSP, formatting, and terminal buffers transparently. Also supports manual SDK path override via config.sdk_path or the $MOJO_SDK_PATH environment variable.
Registers the self-hosted Mojo parser grammar with nvim-treesitter. The grammar files live in tree-sitter/mojo/ โ no external parser repo required. Automatically checks for grammar updates and recompiles when needed, with :Mojo rebuild (or :MojoRebuildParser when commands.spread = true) for manual rebuilds.
Configures mojo-lsp-server via Neovim's native vim.lsp.config / vim.lsp.enable (0.11+) with environment-aware binary resolution (finds the binary in the active Pixi/venv environment). Supports custom root markers for project detection. No nvim-lspconfig dependency.
Configures mojo format via conform.nvim with environment-aware binary resolution. Provides consistent formatting across the editor.
Auto-activates the project environment in new shell terminal buffers. Detects shell terminals and applies the correct activation command before they start.
Provides keyword autocompletion for Mojo-specific keywords (54), builtin functions (42), standard library types (46), and snippets (13). Integrates with nvim-cmp and blink.cmp via dedicated adapters. Completion is context-aware โ it defers to LSP completions after . and :.
Execute the current Mojo file with :Mojo run (opens a terminal split) or :Mojo dedicated (opens or reuses a dedicated terminal buffer). Both commands resolve the mojo binary through the active environment and display a winbar with close instructions. If commands.spread = true, the individual :MojoRun and :MojoRunDedicated commands are also available.
Debug the current Mojo file with :Mojo debug (auto-selects backend), :Mojo debug-native (terminal via mojo-lldb), or :Mojo debug-dap (nvim-dap). The auto backend prefers DAP when mojo-lldb-dap is available, falling back to native mojo-lldb. Works with both pixi and uv projects.
Native debug (:MojoDebugNative) opens a terminal with LLDB keymaps: r (run), n (next), s (step), c (continue), v (frame variable), b (sync breakpoints), q/<Esc>/<CR> (close). Editor breakpoints placed via nvim-dap (<leader>db) or the :Mojo debug toggle_bp command are synced to LLDB on launch, save, and manual sync.
DAP debug (:MojoDebugDap) requires nvim-dap and provides four launch configurations: Debug Mojo File, Debug Mojo File (with args), Debug Binary, and Attach to Process.
macOS note: The plugin automatically removes quarantine and re-signs debug adapter binaries (mojo-lldb-dap / lldb-dap) on macOS. Both native and DAP debugging work on macOS with pixi and uv projects.
Sets 4-space indentation for Mojo files (matching Python-style conventions) via ftplugin/mojo.lua.
Commands are configured via the commands option. By default only the master :Mojo command is created (commands.spread = false). Set commands.spread = true to also register the individual commands.
| Command | Description |
|---|---|
:Mojo {subcommand} |
Master command with tab-completion (see below) |
:MojoMenu |
Open floating actions menu (restart/stop LSP, refresh SDK) |
:MojoRefreshSDK |
Clear SDK cache and re-detect environment |
:MojoRestartLSP |
Restart Mojo LSP server |
:MojoStopLSP |
Stop Mojo LSP server |
:MojoRun |
Run current .mojo file in a terminal split |
:MojoRunDedicated |
Run current .mojo file in a dedicated terminal buffer |
:MojoDebug |
Debug current .mojo file (auto-selects best backend) |
:MojoDebugNative |
Debug via mojo-lldb in terminal (dbg_native) |
:MojoDebugDap |
Debug via nvim-dap + mojo-lldb-dap (dbg_dap) |
:MojoRebuildParser |
Manually rebuild the self-hosted tree-sitter Mojo parser |
:Mojo subcommands: menu, run, dedicated, debug, debug-native, debug-dap, restart, stop, refresh, rebuild, keymaps, help. Press <Tab> after :Mojo to cycle through them.
Default keymaps for Mojo buffers. These can be overridden or disabled per-keymap via the keymaps config section โ set any entry to false to disable it, or change the LHS string to rebind.
| Keymap | Modes | Context | Description |
|---|---|---|---|
K |
Normal | FileType mojo |
Signature help inside parens, hover otherwise |
<leader>ca |
Normal, Visual | FileType mojo |
Code action (vim.lsp.buf.code_action) |
q, <Esc>, <CR> |
Normal, Terminal | :MojoRun buffer |
Close run terminal |
r n s c v |
Normal | Debug terminal | LLDB: run, next, step, continue, frame var |
b |
Normal | Debug terminal | Re-sync breakpoints from editor signs |
The :MojoMenu floating window also has numbered keymaps 1, 2, 3 mapped to each action, and q / <Esc> to close.
Shows environment name, SDK version, and tool status indicators (LSP, formatter, debugger, diagnostics). Indicators are clickable โ click on any to open the :MojoMenu floating window with restart/stop LSP and refresh SDK actions.
Status icons: ๓ฐฌ (active/green), โ (inactive/yellow), ๓ฐ
(error/red).
Highlight groups: MojoIcon, MojoText, MojoSep, MojoGood, MojoNeutral, MojoWarn, MojoErr.
Configure per-indicator with statusline options or use require("mojo.status").display() for non-lualine statuslines.
The lualine adapter auto-injects the Mojo component into lualine_x. If lualine loads after mojo.nvim, the adapter wraps lualine.setup to inject on first call.
require("lualine").setup({
sections = {
lualine_x = {
-- The Mojo component is injected here automatically.
-- Add your own sections around it.
},
},
})Icon and color options:
require("mojo").setup({
statusline = {
enabled = true,
icon = "๐ฅ", -- change the icon shown in the statusline
color = "#ff9e64", -- MojoText / MojoSep color
icon_color = "#ff6f00", -- MojoIcon color
show_env_name = true, -- show pixi/venv environment name
show_sdk_version = true, -- show SDK version (e.g. "1.0.0b2")
show_lsp = true, -- show LSP status indicator
show_fmt = true, -- show formatter status indicator
show_dbg = true, -- show debugger status indicator
show_diag = true, -- show diagnostic count
clickable = true, -- click to open :MojoMenu
},
})You can override the icon with any string, e.g. icon = "๓ฑตฎ" for a Nerd Font alternative.
lazy.nvim
{
"Sarctiann/mojo.nvim",
main = "mojo",
opts = {},
}packer.nvim
use {
"Sarctiann/mojo.nvim",
config = function()
require("mojo").setup({})
end,
}mini.deps
local add = MiniDeps.add
add({
source = "Sarctiann/mojo.nvim",
depends = {},
})
require("mojo").setup({})vim-plug
Plug 'Sarctiann/mojo.nvim'
lua << EOF
require("mojo").setup({})
EOFrequire("mojo").setup({
verbose = true, -- writes mojo-debug.log to cwd
})All features are enabled by default. Pass enabled = false to disable any feature.
Note: opts (lazy.nvim) and setup() accept the same config table.
All options and their defaults
{
filetype = { enabled = true },
terminal = {
enabled = true,
auto_activate = true,
delay_ms = 200,
},
treesitter = {
enabled = true,
adapter = nil, -- custom adapter function
},
lsp = {
enabled = true,
root_markers = { "pixi.toml", "pyproject.toml", ".pixi", ".venv" },
include_dirs = nil, -- extra include directories
filter_docstring_diagnostics = nil, -- filter diagnostics in docstrings
adapter = nil, -- custom adapter function
},
format = {
enabled = true,
formatter_name = "mojo",
adapter = nil, -- custom adapter function
},
completion = {
enabled = true,
adapter = nil, -- custom adapter function
},
keymaps = {
enabled = true,
signature_help = "K", -- LHS string, or false to disable
code_action = "<leader>ca", -- LHS string, or false to disable
},
commands = {
master = true, -- creates :Mojo {subcommand} with tab-completion
spread = false, -- creates individual :MojoMenu, :MojoRun, etc.
},
sdk_path = nil, -- or "/path/to/mojo/sdk"
statusline = {
enabled = true,
icon = "๐ฅ",
show_env_name = true,
show_sdk_version = true,
show_lsp = true,
show_dbg = true,
show_fmt = true,
show_diag = true,
clickable = true,
colored = true,
color = "#ff9e64",
icon_color = "#ff6f00",
adapter = nil, -- custom adapter function
},
debug = {
enabled = true,
auto_scroll = true,
auto_backend = nil, -- nil = auto, "native", "dap"
keymaps = {
toggle_breakpoint = "<leader>db",
clear_breakpoints = "<leader>dB",
start = "<leader>dr",
continue = "<leader>dc",
step_into = "<leader>ds",
step_over = "<leader>dn",
step_out = "<leader>do",
stop = "<leader>dt",
},
search_for = { -- searched in order; user can extend for custom envs
{ name = "lldb-dap", role = "dap" },
{ name = "_mojo-lldb-dap", role = "dap" },
{ name = "mojo-lldb-dap", role = "dap" },
{ name = "mojo-lldb", role = "native" },
{ name = "lldb", role = "native" },
},
adapter = nil, -- custom adapter function
},
verbose = false,
hooks = {},
}All integrations are optional โ the plugin degrades gracefully if the backend is not installed.
| Integration | Backend | Adapter |
|---|---|---|
| LSP | native vim.lsp.config |
lua/mojo/adapters/lspconfig.lua |
| Formatting | conform.nvim |
lua/mojo/adapters/conform.lua |
| Treesitter | nvim-treesitter |
lua/mojo/adapters/treesitter.lua |
| Completion | nvim-cmp / blink.cmp |
lua/mojo/adapters/nvim-cmp.lua / lua/mojo/adapters/blink.lua |
| Debugging | nvim-dap |
lua/mojo/adapters/dap.lua |
| Statusline | lualine.nvim |
lua/mojo/adapters/lualine.lua |
| Distribution | LazyVim | lua/mojo/adapters/lazyvim.lua |
AstroNvim, NvChad, and kickstart.nvim work by just adding { "Sarctiann/mojo.nvim" } to your plugins โ no adapter needed.
Each adapter can be replaced via its feature's adapter config field for custom behavior.
- The plugin does not ship the Mojo LSP binary or official toolchain
- Debugging is enabled by default; both native and DAP backends degrade gracefully when binaries not found. On macOS, the plugin automatically handles quarantine and code-signing for debug adapter binaries
- When
verbose = true, logs are written tomojo-debug.login the current working directory - The plugin auto-activates Pixi or venv project environments before Mojo LSP startup and in terminal buffers
- Treesitter is isolated behind
lua/mojo/treesitter.lua. The parser grammar is self-hosted intree-sitter/mojo/. The plugin auto-rebuilds the parser when the grammar source changes;:MojoRebuildParseris available for manual rebuilds - Mojo files use 4-space indentation (configured via
ftplugin/mojo.lua)
- Manual SDK path โ
config.sdk_pathor$MOJO_SDK_PATHenv var - Derived environment โ
.derived/bin/directory - Pixi environment โ
pixi.tomlor.pixi/directory - Virtual environment โ
.venv/bin/ - No environment โ falls back to system PATH
Detection results are cached per project root. Clear cache with :MojoRefreshSDK.
The plugin tracks LSP server exits and applies exponential backoff on restart (0s โ 5s โ 30s โ 60s) with a cap at 3 restarts. The counter resets after 30 seconds of stable running.
The plugin exposes a hooks system for extensibility. Currently available hook:
resolve_root(path, markers)โ override root directory detection (defaults tovim.fs.root())
Every feature accepts an optional adapter field in its config table. When set, the plugin calls adapter(opts) instead of its default adapter, allowing complete swapping of any backend without changing the rest of the config.
- telescope.nvim โ picks up
.mojo/.๐ฅfiles in standard pickers - trouble.nvim โ displays diagnostics from
mojo-lsp-serverautomatically - nvim-cmp / blink.cmp โ receives LSP completions from
mojo-lsp-servervia thenvim_lspsource - which-key.nvim โ discovers any Mojo-related keymaps you define
Adapter-based integration for other tools is tracked in docs/TODO.md.


