Lazyvim is a good base for Neovim configs

2024-09-21

druskus

I have been using Neovim in different flavors for several years. I have used basic Vim, Neovim, Plug, Packer, even aniseed (and also it's successor nfnl).

Using Neovim as an IDE is not straightforward, after a basic setup there are still a myriad of small issues. Back in the day, around Neovim 0.6, LSPs were constantly breaking, Treesitter was always finding random ways to crash and the APIs changed so very frequently. Over the last years, the situation has improved. Neovim APIs have been stabilized. LSPs work better than ever, and plugin managers have become faster and better. We probably owe this to plugin authors like Folke, who does a great job at standardizing the way plugins manage their APIs. He is also the author of lazy.nvim - a spiritual successor to Packer which greatly improves upon it.

The best feature of lazy.nvim is the ability to cleanly separate plugin configurations by topic, you can declare a plugin twice, and be sure that in the end it's parameters will be merged in a sane way.

While the situation has greatly improved, it is still far from being an "out of the box, batteries included" editor.

LazyVim is Folke's "Neovim distribution". It comes with default options for most the popular plugins. Thanks to the use of lazy.nvim's configuration override mechanism, it can provide sane defaults without having to re-export a bunch of settings.

# My Vim config

At first I discarded LazyVim as I thought it would not be for me. I find it easier to do things from scratch, rather than undoing whatever defaults someone thought were good. However, I was pleasantly surprised with how easy it was to override LazyVim's built in settings. I simply went through the list of installed plugins with :Lazy and created a file to disable all of the ones I did not specifically want.

return {
  { "folke/noice.nvim",                    enabled = false },
  { "MunifTanjim/nui.nvim",                enabled = false },
  { "folke/tokyonight.nvim",               enabled = false },
  { "nvimdev/dashboard-nvim",              enabled = false },
  { "lucas-reineke/indent-blankline.nvim", enabled = false },
  { "folke/flash.nvim",                    enabled = false },
  { "folke/todo-comments.nvim",            enabled = false },
  { "nvim-neo-tree/neo-tree.nvim",         enabled = false },
  { "folke/persistence.nvim",              enabled = false },
  { "rafamadriz/friendly-snippets",        enabled = false },
  { "garymjr/nvim-snippets",               enabled = false },

  -- Some of these I might want 
  { "folke/trouble.nvim",                  enabled = false },
  { "echasnovski/mini.ai",                 enabled = false },
  { "echasnovski/mini.icons",              enabled = false },
  { "echasnovski/mini.pairs",              enabled = false },
  { "stevearc/dressing.nvim",              enabled = false },
  { "stevearc/conform.nvim",               enabled = false },
  { "MagicDuck/grug-far.nvim",             enabled = false }, 
  { "rcarriga/nvim-notify",                enabled = false },
  { "windwp/nvim-ts-autotag",              enabled = false },
  { "Bilal2453/luvit-meta",                enabled = false },
}

For the options, it was the same. I just copied the options from my previous config, which are very complete.

vim.g.mapleader = " "                  -- Set space as the leader key
vim.g.maplocalleader = ","             -- Set comma as the local leader key
vim.g.netrw_banner = 0                 -- Disable netrw banner
vim.g.markdown_recommended_style = 0   -- Fix vim ignores shoftabstop in markdown

local opt = vim.opt
opt.autowrite = true                   -- Auto-save before certain actions
opt.exrc = true                        -- Allow local .vimrc files in directories
opt.completeopt = ""                   -- Disable built-in completion behavior
opt.conceallevel = 3                   -- Hide markup characters in files like markdown
opt.confirm = true                     -- Confirm to save changes before closing
opt.cursorline = true                  -- Highlight the current line
opt.expandtab = true                   -- Convert tabs to spaces
opt.formatoptions = "jcroqlnt"         -- Set format options for comments and text wrapping
opt.grepformat = "%f:%l:%c:%m"         -- Format for showing grep results
opt.grepprg = "rg --vimgrep"           -- Use ripgrep for searching
opt.ignorecase = true                  -- Ignore case in search patterns
opt.inccommand = "nosplit"             -- Live preview of substitution changes
opt.laststatus = 0                     -- Hide the status line initially
opt.mouse = "a"                        -- Enable mouse support
opt.number = true                      -- Show line numbers
opt.pumblend = 10                      -- Set popup menu transparency
opt.pumheight = 10                     -- Limit popup menu height
opt.relativenumber = true              -- Show relative line numbers
opt.scrolloff = 4                      -- Minimal lines to keep above and below cursor
opt.sessionoptions = { "buffers", "curdir", "tabpages", "winsize" }  -- Session persistence settings
opt.shiftround = true                  -- Round indent to multiple of `shiftwidth`
opt.shiftwidth = 2                     -- Number of spaces to use for indentation
opt.shortmess:append({
  I = true,                            -- Suppress intro message
  W = true,                            -- Suppress "written" message when saving a file
  c = true                             -- Suppress completion messages
})
opt.showmode = false                   -- Don't show mode in command line (like -- INSERT --)
opt.sidescrolloff = 8                  -- Columns to keep left/right of the cursor during horizontal scroll
opt.smartcase = true                   -- Override `ignorecase` if search contains uppercase letters
opt.smartindent = true                 -- Smart auto-indentation
opt.spelllang = { "en" }               -- Set language for spell check to English
opt.splitbelow = true                  -- Force all horizontal splits to go below current window
opt.splitright = true                  -- Force all vertical splits to go to the right
opt.tabstop = 2                        -- Number of spaces tabs count for
opt.termguicolors = true               -- Enable 24-bit RGB colors in the terminal
opt.timeoutlen = 300                   -- Time to wait for a mapped sequence to complete
opt.undofile = true                    -- Enable persistent undo
opt.undolevels = 10000                 -- Maximum number of undo levels
opt.updatetime = 200                   -- Faster completion (default is 4000ms)
opt.wildmode = "longest:full,full"     -- Command-line completion mode
opt.winminwidth = 5                    -- Minimum window width
opt.wrap = false                       -- Don't wrap lines by default
opt.compatible = false                 -- Disable 'compatible' mode to use modern features
opt.hidden = true                      -- Allow switching buffers without saving
opt.encoding = "utf-8"                 -- Set file encoding to UTF-8
opt.autoindent = true                  -- Copy indent from current line when starting a new line
opt.incsearch = true                   -- Show search matches as you type
opt.ruler = false                      -- Don't show ruler (line and column info)
opt.switchbuf = "usetab"               -- Reuse existing tabs for switching buffers
opt.smarttab = true                    -- Make tab behavior context-sensitive
opt.copyindent = true                  -- Copy indent from previous line
opt.previewheight = 38                 -- Set height for preview windows
opt.softtabstop = -1                   -- Use `shiftwidth` for tab size in editing
opt.backspace = "indent,eol,start"     -- Allow backspacing over everything in insert mode
opt.swapfile = false                   -- Disable swap file creation
opt.foldcolumn = "0"                   -- No fold column on the left
opt.signcolumn = "yes"                 -- Always show the sign column
opt.laststatus = 3                     -- Global status line
opt.shell = "zsh"                      -- Set default shell to zsh
opt.cmdheight = 1                      -- Set height of command line to 1
opt.showcmd = false                    -- Hide command in progress in command line
opt.cmdheight = 0                      -- Hide command line when not in use
opt.splitkeep = "screen"               -- Keep view stable when splitting
opt.statuscolumn = ""                  -- Customize status column
                                       -- LazyVim's default: [[%!v:lua.require'lazyvim.util'.ui.statuscolumn()]]

Finally for the autocommands, I find that the default autocmds provided by LazyVim are sensible, I just added a few more on top.

-- LazyVim's pre-defined autocmds are:
-- 1. Automatically reload the file when it changes (triggered on certain events like focus gain).
-- 2. Highlights text when yanked (copied).
-- 3. Resizes all window splits if the Vim window is resized.
-- 4. Automatically returns to the last cursor location when reopening a buffer, except for certain file types (e.g., gitcommit).
-- 5. Binds <q> to close certain filetypes (like help, LSP info, and test panels) for easier quitting.
-- 6. Man files opened inline are set as unlisted to prevent clutter in buffer lists.
-- 7. Enables word wrap and spell checking for text-related filetypes (markdown, gitcommit, etc.).
-- 8. Disables JSON file concealment for better readability.
-- 9. Automatically creates missing directories when saving a file, ensuring that any intermediate directories are created if needed.
-- 10. Adds custom filetype detection logic to handle large files ("bigfile"), disables certain animations, and adjusts syntax highlighting to improve performance.


local function augroup(name)
  return vim.api.nvim_create_augroup("drusk_" .. name, { clear = true })
end

-- Enable wrap and spell check for gitcommit and markdown filetypes
vim.api.nvim_create_autocmd("FileType", {
  group = augroup("wrap_spell"),
  pattern = { "gitcommit", "markdown" },
  callback = function()
    vim.opt_local.wrap = true
    vim.opt_local.spell = true
  end,
})

-- Disable line numbers and enter insert mode when opening a terminal
vim.api.nvim_create_autocmd("TermOpen", {
  group = augroup("term_open"),
  callback = function()
    vim.opt_local.number = false
    vim.opt_local.relativenumber = false
    vim.cmd("startinsert")
  end,
})

-- Set filetype to "helm" for specific YAML template and helm-related files
vim.api.nvim_create_autocmd({ "BufNewFile", "BufRead" }, {
  pattern = { "*/templates/*.yaml", "*/templates/*.tpl", "*.gotmpl", "helmfile*.yaml" },
  callback = function()
    vim.opt_local.filetype = "helm"
  end,
})

# Plus of using LazyVim

Everything seems to work and be stable. I have had no crashes or error messages thus far. I am hoping that some of the advantages of using LazyVim will be:

  1. Avoiding the common pitfalls. Since the LazyVim community is pretty big, most things just work. Someone else has figured out how to glue plugins together, and how to fix common issues.

  2. Better Updates. Sometimes it's easier to start from scratch rather than update a config. Yes, your package manager updates your plugins, but sometimes stuff stops working or requires a change. I hate that.

  3. Actually good defaults. The configs are in the website, and are often exactly what I am looking for. It is very easy to override settings and it is very easy to follow.

  4. The best lazy loading I have ever had. No weird ordering bugs.