Neovim setup for Odin
Recently I have been experimenting with Neovim.
This post is about how to set it up for Odin programming.
I will cover:
- syntax highlighting
- building/testing/formatting Odin code from Neovim
- LSP
- autocomplete
- debugging
Prerequisites
- Install Odin
- Install ols and odinfmt (LSP + formatting)
- Install
tree-sitter-cli(for installing Odin Tree-sitter grammar) - Install latest llvm and make sure lldb-dap is accessible (for debugging)
- Set up lazy.nvim
Syntax highlighting
I recommend using Tree-sitter for syntax highlighting.
For Odin syntax highlighting, Odin grammar should be installed.
The simplest way is to use nvim-treesitter.
Add nvim-treesitter to lazy.nvim plugins and install it:
{
"nvim-treesitter/nvim-treesitter",
lazy = false,
build = ":TSUpdate",
opts = {},
config = function()
vim.api.nvim_create_autocmd("FileType", {
pattern = "odin",
callback = function()
vim.treesitter.start()
end,
})
end,
}
Then execute :TSIntall odin.
Building/testing/formatting
I have a plugin for this: odin.nvim.
Add it to lazy.nvim and install it:
{
"cephei8/odin.nvim",
lazy = false,
opts = {},
}
Now it will be possible to build/test/format from Neovim, e.g. execute :Odin build.
Per-project command overrides
:Odin build does odin build . from the project root directory (by default detected to be the one containing .git or ols.json).
It will likely be necessary to customize the build command on per-project basis - e.g. you may want to build with odin build . -debug etc.
For this purpose, build command can be overriden using per-project .nvim.lua file - Neovim will detect and load it, and overrides can be done there.
So,
(1) add vim.opt.exrc = true to your config (e.g. init.lua), so that Neovim will load .nvim.lua
(2) create .nvim.lua file in your project root
(3) add the following to .nvim.lua file:
require("odin").setup({
commands = {
build = { "odin", "build", ".", "-debug" },
},
})
For convenience, I create keybindings for build/test commands (this goes to init.lua):
vim.keymap.set("n", "<leader>ob", "<cmd>Odin build<cr>", { desc = "Odin build" })
vim.keymap.set("n", "<leader>ot", "<cmd>Odin test<cr>", { desc = "Odin test" })
Alternative to using odin.nvim
Before I created odin.nvim, I used overseer.nvim.
It is possible to create a custom task that runs build, parses output and redirects errors to quickfix list.
For running build/test tasks it is basically the same, it is just that overseer.nvim is a generic task runner, whereas odin.nvim is a purpose-build plugin.
LSP
Setting up ols (language server for Odin) is simple with nvim-lspconfig.
Add it to lazy.nvim and install it:
{
"neovim/nvim-lspconfig",
config = function()
vim.lsp.enable("ols")
end
}
I use the default Neovim keybindings for LSP, except that I also define the following ones:
vim.keymap.set("n", "gd", vim.lsp.buf.definition, { desc = "LSP definition" })
vim.keymap.set("n", "gD", vim.lsp.buf.declaration, { desc = "LSP declaration" })
vim.keymap.set({ "n", "v" }, "grf", function()
vim.lsp.buf.format({ async = true })
end, { desc = "LSP format" })
Autocomplete
blink.cmp is a good completion plugin.
Add it to lazy.nvim and install it:
{
"saghen/blink.cmp",
dependencies = {},
version = "1.*",
opts = {
keymap = { preset = "default" },
appearance = {
nerd_font_variant = "mono",
},
completion = { documentation = { auto_show = false } },
sources = {
default = { "lsp", "path", "snippets", "buffer" },
},
fuzzy = { implementation = "prefer_rust_with_warning" },
},
opts_extend = { "sources.default" },
}
To make it work with LSP, modify the nvim-lspconfig lazy.nvim plugin entry:
{
"neovim/nvim-lspconfig",
opts = {
servers = {
ols = {},
},
},
config = function(_, opts)
for server, config in pairs(opts.servers) do
config.capabilities = require("blink.cmp").get_lsp_capabilities(config.capabilities)
vim.lsp.config(server, config)
vim.lsp.enable(server)
end
end,
}
Debugging
First of all, install nvim-dap and nvim-dap-ui (lazy.nvim):
{ "mfussenegger/nvim-dap" },
{ "rcarriga/nvim-dap-ui", dependencies = { "mfussenegger/nvim-dap", "nvim-neotest/nvim-nio" } },
Set up dap-ui and create keybindings for convenience (in init.lua):
local dap = require("dap")
vim.keymap.set("n", "<leader>dc", dap.continue, { desc = "DAP continue" })
vim.keymap.set("n", "<leader>db", dap.toggle_breakpoint, { desc = "DAP toggle breakpoint" })
vim.keymap.set("n", "<leader>dB", dap.set_breakpoint, { desc = "DAP set breakpoint" })
vim.keymap.set("n", "<leader>di", dap.step_into, { desc = "DAP step into" })
vim.keymap.set("n", "<leader>do", dap.step_over, { desc = "DAP step over" })
vim.keymap.set("n", "<leader>dO", dap.step_out, { desc = "DAP step out" })
vim.keymap.set("n", "<leader>dt", dap.terminate, { desc = "DAP terminate" })
vim.keymap.set("n", "<leader>dp", dap.pause, { desc = "DAP pause" })
local dapui = require("dapui")
dapui.setup()
dap.listeners.before.event_initialized.dapui_config = function() dapui.open() end
dap.listeners.before.event_terminated.dapui_config = function() dapui.close() end
dap.listeners.before.event_exited.dapui_config = function() dapui.close() end
vim.keymap.set("n", "<leader>du", dapui.toggle, { desc = "DAP UI toggle" })
vim.keymap.set({ "n", "v" }, "<leader>de", dapui.eval, { desc = "DAP UI eval" })
Now, in project’s .nvim.lua add debug configuration (.nvim.lua file was explained in Per-project command overrides section):
local dap = require("dap")
local lldb_dap = vim.fn.exepath("lldb-dap")
dap.adapters.lldb = {
type = "executable",
command = lldb_dap,
}
dap.configurations.odin = {
{
type = "lldb",
request = "launch",
name = "Odin Debug",
program = root .. "/app",
cwd = root,
args = {},
stopOnEntry = false,
preRunCommands = {
"command script import " .. root .. "/dev-util/odin_lldb.py",
},
},
}
Note that the configuration uses dev-util/odin_lldb.py from the project root - it adds LLDB formatter for Odin types (slices, strings etc.).
It is optional but recommended, as it improves debugging experience.
The formatters were described in LLDB formatters section from Doom Emacs setup for Odin post.
Start debugging by executing :DapContinue.
Conclusion
Enjoy Odin programming in Neovim!