Once neovim 0.12 released, along with the new vim.pack built-in plugin manager, I decided to recreate my config from scratch. I used lazy.nvim previously, and separated all of my dependency configuration into separate modules. But minimalism has always appealed to me, so I was intent on using the builtin solution once I became aware of it. There was also a lot of stuff in the old config that I wasn't really using anymore.
In this post I want to go over everything in my new config, from plugin management to my complex niche vimwiki configuration. This config contains everything I need to use neovim for all of my personal and professional development tasks, and comes in under 250 lines of configuration, which I think is pretty good for what I get out of it.
Note: I use GNU Stow to manage symlinks of my config files, and store them in a git repository. I'll link it here if I decide to make it public but eat shit for now I guess.
Config Breakdown
Contents:
- Plugin Management
- Options
- Color Scheme
- Wildmenu
- Treesitter
- LSP + Diagnostics
- Markdown + Vimwiki
- Conform (Formatters)
- Telescope
Plugin Management
-- #region Plugin Management
vim.api.nvim_create_autocmd("PackChanged", {
callback = function(ev)
if ev.data.spec.name == "telescope-fzf-native.nvim" and ev.data.kind ~=
"delete" then vim.fn.jobstart({"make"}, {cwd = ev.data.path}) end
end
})
vim.pack.add({
"https://github.com/neanias/everforest-nvim",
{
src = "https://github.com/nvim-treesitter/nvim-treesitter",
version = 'main'
}, "https://github.com/neovim/nvim-lspconfig",
"https://github.com/vimwiki/vimwiki", "https://github.com/lervag/vimtex",
"https://github.com/MeanderingProgrammer/render-markdown.nvim",
"https://github.com/tbabej/taskwiki", "https://github.com/3rd/image.nvim",
"https://github.com/stevearc/overseer.nvim",
"https://github.com/stevearc/conform.nvim",
"https://github.com/nvim-lua/plenary.nvim",
"https://github.com/nvim-telescope/telescope.nvim",
"https://github.com/nvim-telescope/telescope-fzf-native.nvim",
"https://github.com/nvim-telescope/telescope-file-browser.nvim"
})
require("overseer").setup()
-- #endregion Plugin ManagementPackChanged Autocmd
The first section of this config sets up an Autocmd which runs specific tasks whenever a plugin is changed. In this version of the config, it's used to build telescope-fzf-native.nvim which provides a significant performance boost for telescope.
Plugin Summary
I tried to keep the list of plugins used in this config to the minimum necessary, but over time started wanting some extra comfort, so some of these may not be particularly useful to everyone.
- everforest-nvim: Color scheme
- nvim-treesitter: Advanced syntax highlighting and indentation via TreeSitter
- nvim-lspconfig: Ready-to-use configs for neovim's built-in LSP client
- vimwiki: Note organization
- vimtex: LaTeX support
- render-markdown.nvim: Advanced markdown concealment (good for reading notes with lots of math or whatever)
- taskwiki: Taskwarrior integration for vimwiki
- image.nvim: Inline image display (again used in vimwiki/markdown)
- overseer.nvim: Task runner compatible with .vscode/tasks.json format
- conform.nvim: Support for many formatters for various languages
- plenary.nvim: Library that many other plugins depend on
- telescope.nvim: Pickers for everything, from files to LSP symbols
- telescope-fzf-native.nvim: Native fzf extension for telescope that provides a performance boost
- telescope-file-browser.nvim: A file browser extension for telescope
And that's everything I need right now! Configuration for most languages is done via LSP configuration, so there aren't many language-specific plugins that I need.
I run the overseer setup function up here because there's no extra config work that I need to do for that plugin.
Options
-- #region Options
vim.o.number = true
vim.o.shiftwidth = 4
vim.o.tabstop = 4
vim.o.expandtab = true
vim.o.mousemodel = 'extend'
vim.o.clipboard = 'unnamedplus'
vim.o.foldmarker = '#region,#endregion'
vim.o.foldmethod = "marker"
vim.o.complete = ".,o"
vim.o.completeopt = "fuzzy,menuone,noselect"
vim.o.autocomplete = true
vim.o.pumheight = 7
vim.g.mapleader = ','
vim.g.maplocalleader = ','
vim.o.wildmenu = true
vim.o.wildmode = "noselect:lastused,full"
vim.o.wildoptions = "pum,fuzzy"
vim.g.nocompatible = true
vim.o.conceallevel = 2
vim.g.netrw_keepdir = 0
vim.g.netrw_winsize = 30
vim.g.netrw_banner = 0
vim.g.vimwiki_global_ext = 0
vim.g.markdown_fenced_languages = {'tex'}
vim.g.tex_flavor = 'latex'
vim.cmd [[filetype plugin on]]
vim.cmd [[syntax enable]]
-- #endregion OptionsI set quite a few options, so instead of listing what each one does I'll just summarize a few of the features that are enabled here:
- Line numbers
- Tab expansion
- System clipboard synchronization
- Folding on region markers
- Concealment
I also configure some settings for autocompletion and wildmenu popups.
Color Scheme
-- #region Color Scheme
require("everforest").setup({})
vim.cmd [[colorscheme everforest]]
-- #endregion Color SchemeThis is pretty self-explanatory, I just set the colorscheme to everforest.
Wildmenu
-- #region Wildmenu
vim.api.nvim_create_autocmd('CmdlineChanged', {
pattern = {':', '/', '?'},
callback = function() vim.fn.wildtrigger() end
})
vim.keymap.set('c', '<Up>', function()
return vim.fn.wildmenumode() == 1 and '<C-E><Up>' or '<Up>'
end, {expr = true})
vim.keymap.set('c', '<Down>', function()
return vim.fn.wildmenumode() == 1 and '<C-E><Down>' or '<Down>'
end, {expr = true})
-- #endregion WildmenuI set up a trigger to start wildmenu completion when the cmdline changes, along with some keybinds for navigating it.
Treesitter
-- #region Treesitter
local nts = require("nvim-treesitter")
require("nvim-treesitter.config").setup({
highlight = {enable = true, disable = {"latex", "markdown"}}
})
local ts_langs = {
"c", "rust", "lua", "gitignore", "toml", "json", "html", "typescript",
"javascript", "bash", "make", "cmake", "python", "tsx", "yaml", "odin",
"markdown", "markdown_inline", "latex"
}
nts.setup()
nts.install(ts_langs)
vim.api.nvim_create_autocmd('PackChanged',
{callback = function() nts.update() end})
vim.api.nvim_create_autocmd('FileType', {
pattern = ts_langs,
callback = function()
vim.treesitter.start()
vim.bo.indentexpr = "v:lua.require'nvim-treesitter'.indentexpr()"
end
})
-- #endregion TreesitterI pre-emptively install the treesitter parsers for pretty much every language I could concievably be using. I update it whenever the pack changes and I autostart treesitter whenever I enter a supported filetype.
LSP + Diagnostics
-- #region LSP + Diagnostics
vim.lsp.config['crates'] = {
cmd = {'crates-lsp'},
filetypes = {'toml'},
root_markers = {'Cargo.toml', '.git'}
}
vim.lsp.config['basedpyright'] = {
settings = {
basedpyright = {
-- Using Ruff's import organizer
disableOrganizeImports = true
},
python = {
analysis = {
-- Ignore all files for analysis to exclusively use Ruff for linting
ignore = {'*'}
}
}
}
}
vim.lsp.enable({
"crates", "rust_analyzer", "lua_ls", "basedpyright", "ruff", "ts_ls",
"html", "cssls"
})
vim.diagnostic.config({
virtual_text = true,
signs = true,
underline = true,
update_in_insert = false,
severity_sort = true
})
vim.api.nvim_create_autocmd("LspAttach", {
callback = function(ev)
vim.lsp.completion.enable(true, ev.data.client_id, ev.buf, {})
end
})
vim.api.nvim_set_keymap('n', '<leader>ca',
'<cmd>lua vim.lsp.buf.code_action()<CR>',
{noremap = true, silent = true})
vim.api.nvim_set_keymap('n', '<leader>e',
'<cmd>lua vim.diagnostic.open_float()<CR>',
{noremap = true, silent = true})
-- #endregion LSP + DiagnosticsThe out-of-the-box lspconfig for most of the language servers I use works fine, so I can just enable them using vim.lsp.enable. I need to configure basedpyright to not provide linting or import organization, both of which are provided by the ruff formatter which I configure later.
I also had to set up my own config for crates-lsp.
Markdown + Vimwiki
-- #region Markdown + Vimwiki
vim.g.vimwiki_list = {
{path = '~/work-vimwiki/', auto_tags = 1, syntax = 'markdown', ext = 'md'},
{path = '~/vimwiki/', auto_tags = 1, syntax = 'markdown', ext = 'md'}
}
require('render-markdown').setup({
completions = {lsp = {enabled = true}},
file_types = {'markdown', 'vimwiki'},
heading = {icons = {}},
render_modes = true
})
vim.treesitter.language.register('markdown', 'vimwiki')
local function is_in_mathzone()
-- Try Tree-sitter check first (for standard Markdown)
local has_ts, node = pcall(vim.treesitter.get_node)
if has_ts and node then
while node do
local type = node:type()
if type == "inline_formula" or type == "displayed_equation" or type ==
"math_environment" then return true end
node = node:parent()
end
-- Secondary fallback check: inspect the raw capture strings under the cursor
-- This accurately catches new @markup.math.latex style injection layers
local captures = vim.treesitter.get_captures_at_cursor(0)
for _, capture in ipairs(captures) do
if capture:match("math") or capture:match("latex") then
return true
end
end
end
-- Try Vim Syntax stack fallback (for Vimwiki buffers)
local line = vim.fn.line(".")
local col = vim.fn.col(".")
local syn_stack = vim.fn.synstack(line, col)
for _, id in ipairs(syn_stack) do
local name = vim.fn.synIDattr(id, "name")
if name == "textSnipTEX" or name == "VimwikiMath" or name ==
"VimwikiEqIn" then return true end
end
return false
end
vim.api.nvim_create_autocmd("FileType", {
pattern = {"vimwiki", "markdown"},
callback = function()
vim.g.vimtex_compiler_enabled = 0
vim.g.vimtex_view_automatic = 0
vim.g.vimtex_mappings_enabled = 0
vim.fn['vimtex#init']()
if not _G.conditional_vimtex_omnifunc then
_G.conditional_vimtex_omnifunc =
function(findstart, base)
if is_in_mathzone() then
return
vim.fn['vimtex#complete#omnifunc'](findstart, base)
else
return findstart == 1 and -1 or {}
end
end
end
vim.bo.omnifunc = "v:lua.conditional_vimtex_omnifunc"
end
})
require("image").setup({
backend = "kitty",
processor = "magick_cli",
integrations = {
markdown = {
filetypes = {"markdown", "vimwiki"} -- markdown extensions (ie. quarto) can go here
}
}
})
-- #endregion VimwikiThis is probably the most complex part of my configuration, and it's the longest by a fair bit.
First, I configure vimwiki. I maintain 2 separate wiki folders, one for work and another for my personal projects. Both are synchronized to the devices that need them via syncthing.
Next, I set up the markdown rendering/concealment plugin, making sure to enable it for the vimwiki filetype along with regular markdown files (like the ones I write these blog posts in).
Then, I register vimwiki files to use the markdown treesitter parser. I kind of forget exactly what this does for me but it might help with the next bit.
The meat of this section provides LaTeX autocompletions while inside a math block in a markdown file. Determining whether I'm inside a math context is different for standard markdown and vimwiki buffers, so some branching fallback checks are used to figure it out.
Lastly, I enable inline image rendering, which again needs to work for markdown and vimwiki filetypes.
Conform (Formatters)
-- #region Conform (Formatters)
require("conform").setup({
formatters_by_ft = {
javascript = {"prettier"},
html = {"html_beautify"},
htmldjango = {"djangofmt"},
lua = {"lua-format"}
},
format_on_save = {
-- These options will be passed to conform.format()
timeout_ms = 500,
lsp_format = "fallback"
}
})
-- #endregion Conform (Formatters)Conform provides an easy way to set up format-on-save for a variety of languages and formatters. I'll definitely be adding more formatters as I run into the need for them (I should probably see about moving ruff over here).
Telescope
-- #region Telescope
local telescope = require("telescope")
telescope.setup()
telescope.load_extension("fzf")
telescope.load_extension("file_browser")
vim.api.nvim_set_keymap('n', '<leader>;',
'<cmd>lua require("telescope").extensions.file_browser.file_browser()<CR>',
{noremap = true, silent = true})
vim.api.nvim_set_keymap('n', '<leader>ff',
'<cmd>lua require("telescope.builtin").find_files()<CR>',
{noremap = true, silent = true})
-- #endregion TelescopeLastly I have telescope. Telescope is something I honestly avoided pulling in for a while but its utility is great. It provides a ton of built-in pickers for all kinds of things. I'll be adding some more keybinds for some of the other ones I'll use frequently, but for now I've set ones up for the file browser extension, along with a fuzzy picker for files.
And with that, we've covered everything in the current version of my neovim config, the very one with which I am writing up this post now! It's not super flashy, but it handles all of my general needs for an IDE well. I'll probably keep expanding upon this as I go on, maybe add grammar checking via languagetool or DAP integration for debugging, but for now I'm pretty happy with what I've got.