iTranslated by AI

The content below is an AI-generated translation. This is an experimental feature, and may contain errors. View original article
👶

The Shortest LSP Setup for Neovim Beginners (v0.11, as of 2025-10-04)

に公開
2

There are many articles out there summarizing Neovim's LSP settings, and this is one of them.
However, existing articles are often outdated or are migration guides intended for users who have already configured LSP.
This article is a guide for those starting Neovim now, in October 2025.
While this article will eventually become outdated as well, I will explain the steps to set up LSP in the shortest way possible at this point in time.

For those already using older versions of Neovim, here are some migration articles:

Prior Knowledge

Now, let's start the configuration.
Here, we will configure the Lua language server (lua-language-server) necessary for writing Neovim settings. Please install lua-language-server on your machine in advance.
Also, to keep the explanation as simple as possible and reduce dependencies, I will explain the method using only Neovim's standard APIs and nvim-lspconfig (and a plugin manager).
For instructions on using mason.nvim or mason-lspconfig, please refer to other articles.

Directory Structure

Let's follow the standard Neovim directory structure. It starts from ~/.config/nvim/init.lua.

$ tree ~/.config/nvim/
.
├── after    # Automatically loaded after init.lua loading is complete
   └── lsp  # Language server overrides are automatically loaded
       └── lua_ls.lua
├── init.lua # Entry point
└── lua      # Loaded from init.lua
    ├── config
   ├── lazy.lua
   └── lsp.lua
    └── plugins
        └── nvim-lspconfig.nvim

Reference: Lua-guide - Neovim docs
https://neovim.io/doc/user/lua-guide.html#lua-guide-modules

Description of Each File

The following explains the contents of each file.

In init.lua, we load lazy.nvim and LSP-related configuration files.

init.lua
require("config.lazy")
require("config.lsp")

In lua/config/lazy.lua, configure lazy.nvim and define where plugin settings will be located. If you have already configured it in a different location, please adjust as necessary.

lua/config/lazy.lua
-- lazy.nvim configuration
-- Ref: https://lazy.folke.io/installation
-- ...

-- Setup lazy.nvim
require("lazy").setup({
  spec = {
    -- Automatically load lua files under lua/plugins/ as lazy.nvim Plugin Specs
    { import = "plugins" },
  },
})

For now, let's skip the LSP-related settings and write the nvim-lspconfig configuration first. By simply writing the Plugin Spec under the lua/plugins directory configured earlier, lazy.nvim will automatically handle installation and setup.

lua/plugins/nvim-lspconfig.lua
---@type LazyPluginSpec
return {
  "neovim/nvim-lspconfig",
  -- Lazy load when a buffer is read or a new file is created
  event = { "BufReadPre", "BufNewFile" },
}

Now that we've configured nvim-lspconfig, let's set up the LSP-related configuration we skipped earlier.

lua/config/lsp.lua
vim.lsp.enable({
  -- The preset configured in nvim-lspconfig as "lua_ls" will be loaded
  -- https://github.com/neovim/nvim-lspconfig/blob/master/lsp/lua_ls.lua
  "lua_ls",
  -- Other language server settings
  -- "gopls",
})

-- Called when a language server is attached
vim.api.nvim_create_autocmd("LspAttach", {
  group = vim.api.nvim_create_augroup("my.lsp", {}),
  callback = function(args)
    local client = assert(vim.lsp.get_client_by_id(args.data.client_id))
    local buf = args.buf

    -- Add settings to the default language server keybindings
    -- See https://neovim.io/doc/user/lsp.html#lsp-defaults
    -- The logic is to add settings if the language server client implements the functions defined in LSP

    if client:supports_method("textDocument/definition") then
      vim.keymap.set("n", "gd", vim.lsp.buf.definition, { buffer = buf, desc = "Go to definition" })
    end

    if client:supports_method("textDocument/hover") then
      vim.keymap.set("n", "<leader>k",
        function() vim.lsp.buf.hover({ border = "single" }) end,
        { buffer = buf, desc = "Show hover documentation" })
    end

    if client:supports_method("textDocument/completion") then
      vim.lsp.completion.enable(true, client.id, args.buf, { autotrigger = true })
    end

    -- Auto-format ("lint") on save.
    -- Usually not needed if server supports "textDocument/willSaveWaitUntil".
    if not client:supports_method("textDocument/willSaveWaitUntil")
        and client:supports_method("textDocument/formatting") then
      vim.api.nvim_create_autocmd("BufWritePre", {
        group = vim.api.nvim_create_augroup("my.lsp", { clear = false }),
        buffer = args.buf,
        callback = function()
          vim.lsp.buf.format({ bufnr = args.buf, id = client.id, timeout_ms = 1000 })
        end,
      })
    end

    if client:supports_method("textDocument/inlineCompletion") then
      vim.lsp.inline_completion.enable(true, { bufnr = buf })
      vim.keymap.set("i", "<Tab>", function()
        if not vim.lsp.inline_completion.get() then
          return "<Tab>"
        end
        -- close the completion popup if it's open
        if vim.fn.pumvisible() == 1 then
          return "<C-e>"
        end
      end, {
        expr = true,
        buffer = buf,
        desc = "Accept the current inline completion",
      })
    end
  end,
})

Up to this point, the configuration for the Lua language server is complete.
In this state, if you start Neovim, place the cursor on a symbol in a Lua file, and press <leader>k (<leader> is the space key by default), you should be able to see information provided by the language server.
This is also possible because nvim-lspconfig provides a preset for the Lua language server.

However, since the Lua language server is not specialized for Vim/Neovim, it will likely issue a warning because it lacks information about the vim keyword used in vim.lsp.enable. Finally, let's set up a configuration to clear this warning.

The after/ directory is a special directory that is loaded after the normal loading process is finished. By returning the language server settings under the after/lsp/ directory, Neovim will automatically call these settings. nvim-lspconfig is essentially a collection of pre-written settings for each language server that should originally be written here.

The global vim keyword is defined in Lua files bundled with Neovim. You can access the directory containing these Lua files via vim.env.VIMRUNTIME .. "/lua". For example, if you installed Neovim via Homebrew, this should point to something like /opt/homebrew/share/nvim/runtime/lua.

By creating after/lsp/lua_ls.lua and adding vim.env.VIMRUNTIME .. "/lua" to the settings.Lua.workspace.library array, you can load the definition of the vim keyword and clear the warning.

after/lsp/lua_ls.lua
---@type vim.lsp.Config
return {
  settings = {
    Lua = {
      workspace = {
        library = {
          vim.env.VIMRUNTIME .. "/lua",
        },
      },
    },
  },
}

With this, you have successfully cleared the warning.

Conclusion

Enjoy your Neovim life!

Discussion

あきもあきも

globalsの設定で警告を消すのも良いですが、Neovimに同梱されている型定義を読み込ませるとより正確な情報が得られてハッピーかもしれません

---@type vim.lsp.Config
return {
  settings = {
    Lua = {
      workspace = {
        library = {
          "/opt/homebrew/share/nvim/runtime/lua",
        },
      },
    },
  },
}

100%定義されているわけではないので、一部警告は出てしまいますが……

ras0qras0q

確かに、"/opt/homebrew/share/nvim/runtime/lua" (これは vim.env.VIMRUNTIMEでbrewの依存を剥がせそうです) でグローバルの vim が型定義されているのでそっちを使うのが良さそうですね。
実は自分の手元ではそれも併せて設定しているんですが、ここでは最小限のカスタムを行うために settings.Lua.diagnostics.globals だけを書いていました。
修正しておきます、ご指摘ありがとうございます!