Skip to main content
This configuration provides comprehensive Lua support with lua_ls (the Lua language server) and special enhancements for Neovim plugin development.

Language server

The lua_ls language server is enabled:
-- From lua/plugins/lsp/init.lua:43
vim.lsp.enable({
  "lua_ls", -- Lua
})

lua_ls configuration

Custom configuration is in after/lsp/lua_ls.lua:
-- From after/lsp/lua_ls.lua:1
return {
  settings = {
    Lua = {
      diagnostics = {
        globals = { "vim" },
        disable = { "inject-field", "undefined-field", "missing-fields" },
      },
      runtime = { version = "LuaJIT" },
      workspace = {
        library = { vim.env.VIMRUNTIME },
        checkThirdParty = false,
      },
      telemetry = { enable = false },
    },
  },
}

Diagnostics

-- From after/lsp/lua_ls.lua:4
diagnostics = {
  globals = { "vim" },
  disable = { "inject-field", "undefined-field", "missing-fields" },
}
Configured globals:
  • vim - Neovim’s global API object
Disabled diagnostics:
  • inject-field - Can be noisy for dynamic tables
  • undefined-field - Often false positives with metatables
  • missing-fields - Annoying for partial table definitions

Runtime

-- From after/lsp/lua_ls.lua:8
runtime = { version = "LuaJIT" }
Configured for LuaJIT (used by Neovim), not standard Lua.

Workspace

-- From after/lsp/lua_ls.lua:9
workspace = {
  library = { vim.env.VIMRUNTIME },
  checkThirdParty = false,
}
Library: Includes Neovim’s runtime files for API definitions and documentation. Third-party checking: Disabled to avoid prompts about workspace configuration.

Telemetry

-- From after/lsp/lua_ls.lua:13
telemetry = { enable = false }
Telemetry is disabled for privacy.

Enhanced Neovim development

The lazydev.nvim plugin provides enhanced Lua development:
-- From lua/plugins/lsp/extras.lua:2
{
  "folke/lazydev.nvim",
  ft = "lua",
  opts = {
    library = {
      { path = "${3rd}/luv/library", words = { "vim%.uv" } },
      { path = "snacks.nvim", words = { "Snacks" } },
    },
  },
}

Features

Type definitions

Proper types for Neovim API, vim.uv (libuv), and plugins

Auto-completion

Intelligent completions for Neovim functions and modules

Documentation

Hover documentation from Neovim’s help system

Library support

Type support for common plugins like Snacks

Configured libraries

vim.uv (libuv):
{ path = "${3rd}/luv/library", words = { "vim%.uv" } }
Provides types for vim.uv - Neovim’s libuv bindings for async I/O. Snacks.nvim:
{ path = "snacks.nvim", words = { "Snacks" } }
Provides types for the Snacks plugin used in this config.

Formatting

Lua files are formatted with stylua:
-- From lua/plugins/formatter.lua:44
lua = { "stylua" }
Installation:
cargo install stylua
# or
brew install stylua

stylua configuration

The configuration includes a stylua.toml file in the root directory for consistent formatting. Format-on-save is enabled by default. Manually format with <leader>fb.

No linter configured

Lua doesn’t have a linter configured by default. lua_ls provides sufficient diagnostics for most use cases. If you need additional linting, you can add luacheck:
-- Add to lua/plugins/linting.lua
lua = { "luacheck" },
Installation:
brew install luacheck
# or
luarocks install luacheck

Common workflows

Exploring Neovim API

  1. Type vim.
  2. See autocomplete suggestions for all vim APIs
  3. Select a function
  4. Press K to see documentation

Checking vim.uv functions

  1. Type vim.uv.
  2. Autocomplete shows all libuv functions
  3. Hover over any function for documentation

Working with plugins

  1. Type plugin name (e.g., Snacks.)
  2. See plugin API in autocomplete
  3. Get type checking for plugin functions

Fixing diagnostics

If you see false positive diagnostics:
  1. Add to disabled diagnostics in after/lsp/lua_ls.lua:5
  2. Or use ---@diagnostic disable-next-line: <diagnostic-name> in code

Keymaps

All standard LSP keymaps work:
KeymapAction
KHover documentation (shows :help)
<C-k>Signature help
gdGo to definition
grFind references
giGo to implementation
gaCode action
<leader>rnRename symbol
<leader>fbFormat with stylua
]d / [dNext/prev diagnostic

Annotations

lua_ls supports LSP annotations for better type checking:

Type annotations

---@param name string The user's name
---@param age number The user's age
---@return string greeting A personalized greeting
function greet(name, age)
  return string.format("Hello %s, you are %d years old", name, age)
end

Class definitions

---@class User
---@field name string
---@field age number
---@field email string?

---@type User
local user = {
  name = "John",
  age = 30,
}

Generic types

---@generic T
---@param list T[]
---@return T?
function first(list)
  return list[1]
end

Disabling diagnostics

---@diagnostic disable-next-line: undefined-field
local value = some_table.dynamic_field

---@diagnostic disable: undefined-global
local x = undefined_var
---@diagnostic enable: undefined-global

Installation summary

1

Install lua_ls

brew install lua-language-server
2

Install stylua

cargo install stylua
# or
brew install stylua
3

Optional: Install luacheck

brew install luacheck
4

Verify installation

lua-language-server --version
stylua --version

Troubleshooting

lua_ls not starting

  1. Check it’s installed: lua-language-server --version
  2. Run :LspInfoCustom to see client status
  3. Check logs: :LspLog

Missing Neovim API completions

  1. Verify vim.env.VIMRUNTIME is set correctly
  2. Check lazydev.nvim is loaded: :Lazy
  3. Restart LSP: :LspRestart

False positive diagnostics

  1. Add diagnostic to disable list in after/lsp/lua_ls.lua:5
  2. Use ---@diagnostic disable-next-line: comments
  3. Consider if the diagnostic is actually valid

stylua not formatting

  1. Check it’s installed: stylua --version
  2. Run :ConformInfo to see formatter status
  3. Manually format: <leader>fb
  4. Check for stylua.toml in project root

Advanced configuration

Adding custom globals

If you have custom globals in your config:
-- In after/lsp/lua_ls.lua
diagnostics = {
  globals = { "vim", "my_global", "another_global" },
}

Adding workspace libraries

For plugin development:
-- In after/lsp/lua_ls.lua
workspace = {
  library = {
    vim.env.VIMRUNTIME,
    "${3rd}/luv/library",
    "/path/to/plugin",
  },
}

Configuring lazydev for more plugins

-- In lua/plugins/lsp/extras.lua
opts = {
  library = {
    { path = "${3rd}/luv/library", words = { "vim%.uv" } },
    { path = "snacks.nvim", words = { "Snacks" } },
    { path = "plenary.nvim", words = { "plenary" } },
    { path = "telescope.nvim", words = { "telescope" } },
  },
}

Next steps

TypeScript

TypeScript/JavaScript configuration

Go

Go language configuration

LSP setup

Detailed LSP configuration

Formatting

Learn more about formatters

Build docs developers (and LLMs) love