Updated NeoVim Configuration -- explicit AND minimal?
post

Can you have a configuration for NeoVim that is both explicit and minimal? I will let you be the judge.

Fans of the One True Editor know that it's flexibility in configuration and huge plugin ecosystem is both good and bad. Since I have some time on my hands I decided to go back and look at my current NeoVim setup and see what can be done.

Here is what I came up with as some ground rules:

  • use as much "native NeoVim functionality" as I can
  • keep the number of configuration files to the bare minimum (currently sitting at 1)
  • really think about what I want my workflow to be.

I'll break down what I've done so far.

First a reminder to install a Lua language server to I can edit this configuration file too and get hints if I make mistakes.

-- Make sure to install lua-language-server locally

Next, cover some basics:

-- General configuration
vim.o.autocomplete = true -- We generally want autocomplete
vim.g.mapleader = " "
vim.g.maplocalleader = "\\"
vim.o.expandtab = true
vim.o.smartindent = true
vim.o.autoindent = true
vim.o.number = true
vim.o.relativenumber = true
vim.o.cursorline = true
vim.o.signcolumn = "yes"
vim.o.signcolumn = "yes"
vim.diagnostic.config({ virtual_text = true })

I am using NeoVim's built-in package management.

For those wondering, here is a highlight of the packages:

  • Alabaster is a minimalist theme
  • Mason makes it easier to install language servers as I need them
  • the Debug Anything Protocol powers my XDebug configuration
  • Treesitter and Telescope seem to be turning into requirements for a lot of other packages
  • Snacks is for making some context-friendly key mappings
  • which-key created pop-up summaries of what is available for key mappings while using them
  • mini.nvim is a collection of very small plugins (I am only using the status bar one for now)

The PackChanged stuff is so any of the stuff Treesitter has installed gets updated whenever I open up NeoVim.

-- Package management 
vim.api.nvim_create_autocmd("PackChanged", {
  group = vim.api.nvim_create_augroup("treesitter_tsupdate", { clear = true }),
  callback = function(ev)
    local name, kind = ev.data.spec.name, ev.data.kind
    if name ~= "nvim-treesitter" then return end
    if kind ~= "install" and kind ~= "update" then return end

    if not ev.data.active then
      vim.cmd.packadd("nvim-treesitter")
    end

    vim.cmd("TSUpdate")
  end,
})

vim.pack.add({
        { src = "https://git.sr.ht/~p00f/alabaster.nvim" },
        { src = "https://github.com/neovim/nvim-lspconfig" },
        { src = "https://github.com/mason-org/mason.nvim" },
        { src = "https://github.com/mason-org/mason-lspconfig.nvim" },
        { src = "https://github.com/nvim-treesitter/nvim-treesitter" },
        { src = "https://github.com/mfussenegger/nvim-dap" },
        { src = "https://github.com/rcarriga/nvim-dap-ui" },
        { src = "https://github.com/theHamsta/nvim-dap-virtual-text" },
        { src = "https://github.com/nvim-treesitter/nvim-treesitter" },
        { src = "https://github.com/nvim-telescope/telescope-dap.nvim" },
        { src = "https://github.com/nvim-telescope/telescope.nvim" },
        { src = "https://github.com/folke/snacks.nvim" },
        { src = "https://github.com/nvim-lua/plenary.nvim" },
        { src = "https://github.com/pdaether/pest.nvim" },
        { src = "https://github.com/folke/which-key.nvim" },
        { src = "https://github.com/nvim-mini/mini.nvim" },
})

Explicitly set up the packages I installed:

require('mason').setup()
require('mason-lspconfig').setup({
        ensure_installed = { "intelephense", "lua_ls" }
})
require('nvim-treesitter').setup()
require('telescope').setup()
require('snacks').setup()
require('which-key').setup()
require('mini.statusline').setup()

DAP-specific stuff:

-- debugging set up 
local dap = require('dap')
require('telescope').load_extension('dap')

dap.adapters.php = {
    type = "executable",
    command = "node",
    args = { os.getenv("HOME") .. "/vscode-php-debug/out/phpDebug.js" }
}

dap.configurations.php = {
    {
        type = "php",
        request = "launch",
        name = "Listen for Xdebug",
        port = 9003
    },
    {
        type = "php",
        request = "launch",
        name = "Launch Pest (current file) with Xdebug",
        program = function()
                return vim.fn.getcwd() .. "/vendor/bin/pest"
        end,
        cwd = "${workspaceFolder}",
        args = { "${file}" },
        port = 9003,
        env = {
              XDEBUG_MODE = "debug",
              XDEBUG_SESSION = "pest",
        },
     },
}

Don't forget the colour scheme:

-- Colorscheme setup
vim.cmd('colorscheme alabaster')

LSP-specific configuration:

-- LSP configuration
vim.keymap.set("n", "gl", vim.diagnostic.open_float)
vim.api.nvim_create_autocmd('LspAttach', {
    group = vim.api.nvim_create_augroup('my.lsp', {}),
    callback = function(ev)
        local client = assert(vim.lsp.get_client_by_id(ev.data.client_id))
        if client:supports_method('textDocument/completion') then
            vim.lsp.completion.enable(true, client.id, ev.buf, {autotrigger = true})
        end

end,
})

All lazy programmers love auto completion:

-- Autocompletion
vim.opt.complete:append('o')
vim.opt.completeopt = { 'menuone', 'noselect' }
vim.o.pumheight = 5
vim.o.pumborder = 'rounded'

Key mappings (I use which-key so I never have to really remember anything other than the first letter of the combo)

-- DAP keymaps via snacks.nvim
local map = require("snacks.keymap").set

map("n", "<F5>",  function() require("dap").continue() end,  { desc = "DAP Continue" })
map("n", "<F10>", function() require("dap").step_over() end, { desc = "DAP Step Over" })
map("n", "<F11>", function() require("dap").step_into() end, { desc = "DAP Step Into" })
map("n", "<F12>", function() require("dap").step_out() end, { desc = "DAP Step Out" })

map("n", "<Leader>b",  function() require("dap").toggle_breakpoint() end, { desc = "DAP Toggle Breakpoint" })
map("n", "<Leader>B",  function() require("dap").set_breakpoint() end,    { desc = "DAP Set Breakpoint" })
map("n", "<Leader>lp", function()
  require("dap").set_breakpoint(nil, nil, vim.fn.input("Log point message: "))
end, { desc = "DAP Log Point" })

map("n", "<Leader>dr", function() require("dap").repl.open() end,  { desc = "DAP REPL Open" })
map("n", "<Leader>dl", function() require("dap").run_last() end,   { desc = "DAP Run Last" })

map({ "n", "v" }, "<Leader>dh", function()
  require("dap.ui.widgets").hover()
end, { desc = "DAP Hover Widgets" })

map({ "n", "v" }, "<Leader>dp", function()
  require("dap.ui.widgets").preview()
end, { desc = "DAP Preview Widgets" })

map("n", "<Leader>df", function()
  local widgets = require("dap.ui.widgets")
  widgets.centered_float(widgets.frames)
end, { desc = "DAP Frames" })

map("n", "<Leader>ds", function()
  local widgets = require("dap.ui.widgets")
  widgets.centered_float(widgets.scopes)
end, { desc = "DAP Scopes" })

-- telescope keymaps
local builtin = require('telescope.builtin')
vim.keymap.set('n', '<leader>ff', builtin.find_files, { desc = 'Telescope find files' })
vim.keymap.set('n', '<leader>fg', builtin.live_grep, { desc = 'Telescope live grep' })
vim.keymap.set('n', '<leader>fb', builtin.buffers, { desc = 'Telescope buffers' })
vim.keymap.set('n', '<leader>fh', builtin.help_tags, { desc = 'Telescope help tags' })

Pest for PHP tests:

-- Settings for running Pest tests 
require('pest-nvim').setup({
        pest_cmd = 'vendor/bin/pest',
        window_size = 25,
})

That's all that is in there for now.

I go through cycles of installing all sorts of plugins and then trimming them down. This time around I am going to start with what I think is a good base set and then carefully add in more stuff as I need it.

Here is the whole thing if you want to just copy-and-paste

-- Make sure to install lua-language-server locally

-- General configuration
vim.o.autocomplete = true -- We generally want autocomplete
vim.g.mapleader = " "
vim.g.maplocalleader = "\\"
vim.o.expandtab = true
vim.o.smartindent = true
vim.o.autoindent = true
vim.o.number = true
vim.o.relativenumber = true
vim.o.cursorline = true
vim.o.signcolumn = "yes"
vim.o.signcolumn = "yes"
vim.diagnostic.config({ virtual_text = true })

-- Package management 
vim.api.nvim_create_autocmd("PackChanged", {
  group = vim.api.nvim_create_augroup("treesitter_tsupdate", { clear = true }),
  callback = function(ev)
    local name, kind = ev.data.spec.name, ev.data.kind
    if name ~= "nvim-treesitter" then return end
    if kind ~= "install" and kind ~= "update" then return end

    -- If TSUpdate relies on the plugin being loaded, ensure it is.
    if not ev.data.active then
      vim.cmd.packadd("nvim-treesitter")
    end

    vim.cmd("TSUpdate")
  end,
})

vim.pack.add({
        { src = "https://git.sr.ht/~p00f/alabaster.nvim" },
        { src = "https://github.com/neovim/nvim-lspconfig" },
        { src = "https://github.com/mason-org/mason.nvim" },
        { src = "https://github.com/mason-org/mason-lspconfig.nvim" },
        { src = "https://github.com/nvim-treesitter/nvim-treesitter" },
        { src = "https://github.com/mfussenegger/nvim-dap" },
        { src = "https://github.com/rcarriga/nvim-dap-ui" },
        { src = "https://github.com/theHamsta/nvim-dap-virtual-text" },
        { src = "https://github.com/nvim-treesitter/nvim-treesitter" },
        { src = "https://github.com/nvim-telescope/telescope-dap.nvim" },
        { src = "https://github.com/nvim-telescope/telescope.nvim" },
        { src = "https://github.com/folke/snacks.nvim" },
        { src = "https://github.com/nvim-lua/plenary.nvim" },
        { src = "https://github.com/pdaether/pest.nvim" },
        { src = "https://github.com/folke/which-key.nvim" },
        { src = "https://github.com/nvim-mini/mini.nvim" },
})

require('mason').setup()
require('mason-lspconfig').setup({
        ensure_installed = { "intelephense", "lua_ls" }
})
require('nvim-treesitter').setup()
require('telescope').setup()
require('snacks').setup()
require('which-key').setup()
require('mini.statusline').setup()

-- debugging set up 
local dap = require('dap')
require('telescope').load_extension('dap')

dap.adapters.php = {
    type = "executable",
    command = "node",
    args = { os.getenv("HOME") .. "/vscode-php-debug/out/phpDebug.js" }
}

dap.configurations.php = {
    {
        type = "php",
        request = "launch",
        name = "Listen for Xdebug",
        port = 9003
    },
    {
        type = "php",
        request = "launch",
        name = "Launch Pest (current file) with Xdebug",
        program = function()
                return vim.fn.getcwd() .. "/vendor/bin/pest"
        end,
        cwd = "${workspaceFolder}",
        args = { "${file}" },
        port = 9003,
        env = {
              XDEBUG_MODE = "debug",
              XDEBUG_SESSION = "pest",
        },
     },
}

-- Colorscheme setup
vim.cmd('colorscheme alabaster')

-- LSP configuration
vim.keymap.set("n", "gl", vim.diagnostic.open_float)
vim.api.nvim_create_autocmd('LspAttach', {
    group = vim.api.nvim_create_augroup('my.lsp', {}),
    callback = function(ev)
        local client = assert(vim.lsp.get_client_by_id(ev.data.client_id))
        if client:supports_method('textDocument/completion') then
            vim.lsp.completion.enable(true, client.id, ev.buf, {autotrigger = true})
        end

end,
})

-- Autocompletion
vim.opt.complete:append('o')
vim.opt.completeopt = { 'menuone', 'noselect' }
vim.o.pumheight = 5
vim.o.pumborder = 'rounded'

-- DAP keymaps via snacks.nvim
local map = require("snacks.keymap").set

map("n", "<F5>",  function() require("dap").continue() end,  { desc = "DAP Continue" })
map("n", "<F10>", function() require("dap").step_over() end, { desc = "DAP Step Over" })
map("n", "<F11>", function() require("dap").step_into() end, { desc = "DAP Step Into" })
map("n", "<F12>", function() require("dap").step_out() end, { desc = "DAP Step Out" })

map("n", "<Leader>b",  function() require("dap").toggle_breakpoint() end, { desc = "DAP Toggle Breakpoint" })
map("n", "<Leader>B",  function() require("dap").set_breakpoint() end,    { desc = "DAP Set Breakpoint" })
map("n", "<Leader>lp", function()
  require("dap").set_breakpoint(nil, nil, vim.fn.input("Log point message: "))
end, { desc = "DAP Log Point" })

map("n", "<Leader>dr", function() require("dap").repl.open() end,  { desc = "DAP REPL Open" })
map("n", "<Leader>dl", function() require("dap").run_last() end,   { desc = "DAP Run Last" })

map({ "n", "v" }, "<Leader>dh", function()
  require("dap.ui.widgets").hover()
end, { desc = "DAP Hover Widgets" })

map({ "n", "v" }, "<Leader>dp", function()
  require("dap.ui.widgets").preview()
end, { desc = "DAP Preview Widgets" })

map("n", "<Leader>df", function()
  local widgets = require("dap.ui.widgets")
  widgets.centered_float(widgets.frames)
end, { desc = "DAP Frames" })

map("n", "<Leader>ds", function()
  local widgets = require("dap.ui.widgets")
  widgets.centered_float(widgets.scopes)
end, { desc = "DAP Scopes" })

-- Settings for running Pest tests 
require('pest-nvim').setup({
        pest_cmd = 'vendor/bin/pest',
        window_size = 25,
})

-- telescope keymaps
local builtin = require('telescope.builtin')
vim.keymap.set('n', '<leader>ff', builtin.find_files, { desc = 'Telescope find files' })
vim.keymap.set('n', '<leader>fg', builtin.live_grep, { desc = 'Telescope live grep' })
vim.keymap.set('n', '<leader>fb', builtin.buffers, { desc = 'Telescope buffers' })
vim.keymap.set('n', '<leader>fh', builtin.help_tags, { desc = 'Telescope help tags' })

Categories: tools, NeoVim