Configuring Neovim’s Built‑in LSP Client for Pyright – A Worked Lua Example
Learn how to attach the Pyright language server to Python files using only Neovim’s vim.lsp API, see what features you get, and avoid common setup pitfalls.
22 Jan 2026, 10:01 UTC

Quick answer: attach Pyright with vim.lsp.start_client
The useful takeaway is a minimal Lua snippet that starts the Pyright language server for every Python buffer and enables basic LSP features (diagnostics, go‑to‑definition, completion, hover). No external plugin manager is required; you only need Pyright installed and reachable from your shell’s $PATH.
Worked Lua configuration
Place the following code in your init.lua or a file sourced from it (e.g., lua/lsp/pyright.lua). It defines an autocommand that fires when the filetype is set to python and starts a client if one is not already attached.
-- lua/lsp/pyright.lua
local M = {}
function M.setup()
vim.api.nvim_create_autocmd('FileType', {
pattern = 'python',
callback = function()
-- Avoid attaching multiple clients to the same buffer
local clients = vim.lsp.get_active_clients({ bufnr = 0, name = 'pyright' })
if #clients > 0 then
return
end
-- Define the client settings
local config = {
name = 'pyright',
cmd = { 'pyright-langserver', '--stdio' }, -- adjust if pyright is elsewhere
root_dir = vim.fs.dirname(vim.fs.find({ 'pyproject.toml', 'setup.py', 'requirements.txt', '.git' }, { upward = true })[0]) or vim.loop.cwd(),
settings = {
pyright = {}
},
}
-- Start the client; this is asynchronous and returns a client ID
vim.lsp.start_client(config)
end,
})
end
return M
Then, in your init.lua, load the module:
require('lsp/pyright').setup()
How the mechanism works
Neovim ships with a built‑in LSP client accessible through the vim.lsp table. Calling vim.lsp.start_client spawns a new client process (the language server) and registers it with Neovim’s LSP subsystem. The cmd field tells Neovim which executable to run; using --stdio makes the server communicate over standard input/output, which is the default for most language servers.
The autocommand ensures that whenever you open a .py file (or set filetype=python), the callback runs. It first checks for an existing client named pyright in the current buffer to prevent duplicate attachments, which can cause duplicated diagnostics and conflicting key mappings. If none is found, it builds a minimal configuration and starts the server.
Once the client is active, Neovim automatically:
- Shows diagnostics as virtual text or in the location list.
- Provides
gd(go‑to definition),gi(go‑to implementation), andK(hover) mappings. - Offers completion via the built‑in
omnifuncor throughnvim-cmpif you have it installed.
Limits and common pitfalls
Even though the setup is straightforward, several practical limits can affect your experience:
- Server not found or misnamed: If
pyright-langserveris not in$PATHor you typed the command incorrectly,vim.lsp.start_clientfails silently. You will see no LSP features, and the buffer will behave as if no server is attached. Always verify the executable from a shell first (which pyright-langserver). - Multiple clients per buffer: Attaching more than one LSP client to the same buffer can produce duplicate diagnostics and cause key‑map collisions. The snippet above guards against this by checking for an existing client with the same name. If you need multiple servers (e.g., Pyright plus a linter), you must manage distinct client IDs or use a plugin like
nvim-lspconfigthat handles merging. - Synchronous start blocking the UI: Some language servers perform heavy initialization and may block Neovim’s UI if started synchronously. The built‑in start function is asynchronous, but if you wrap it in a function that waits for the server to ready (e.g., using
vim.wait), you risk freezing the editor. Keep the start call non‑blocking. - Large projects and latency: Pyright scales well, but in very large codebases you may notice latency if the server is not optimized or if many buffers share the same client instance. Each buffer gets its own client by default in this snippet; you can share a single client across buffers by removing the
bufnr = 0filter when checking for existing clients, but be aware that workspace settings then apply globally. - Incorrect root directory detection: The
root_dircalculation uses common project markers. If your project lacks those markers, Neovim may set the root to the current working directory, which can cause the server to miss configuration files (e.g.,pyrightconfig.json). Adjust theroot_dirlogic or set it explicitly to your project’s root.
Verification steps (no claimed test output)
To confirm that the configuration is working, you can perform the following checks inside Neovim:
- List active clients: Run
:lua vim.lsp.get_active_clients(). You should see at least one entry withname: "pyright"and a non‑nilcmd. - Trigger a diagnostic: Introduce a deliberate syntax error in a Python file (e.g., remove a colon after a function definition). After saving, the error should appear as virtual text inline or in the location list (
:lopen). - Check logs: The LSP client writes logs to a file whose path you can retrieve with
:echo vim.lsp.get_log_path(). Open that file and look for lines indicating the server started successfully; any error messages will help you diagnose missing executables or incorrect arguments. - Test a LSP feature: Position the cursor on a function name and press
gd(or run:lua vim.lsp.buf.definition()). If the server is working, Neovim will jump to the definition or show a preview.
If any of these steps fail, revisit the common pitfalls above—especially the executable path and duplicate client guard.
When to consider a plugin wrapper
The raw vim.lsp approach works for simple cases, but many users eventually adopt a plugin like nvim-lspconfig because it:
- Provides ready‑made server configurations with sensible defaults.
- Handles automatic client reuse across buffers.
- Offers helpers for server installation (via
mason.nvim) and logging.
If you find yourself repeatedly adjusting cmd, root_dir, or settings for multiple languages, migrating to such a wrapper can reduce boilerplate.
By following the snippet and verification steps above, you can get Pyright running in Neovim with only the built‑in LSP client, understand where the setup may break, and know how to troubleshoot the most frequent issues.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.