To turn Vim or Neovim into a productive Python IDE in 2026, install Neovim, add a plugin manager (lazy.nvim), and wire up the Language Server Protocol (LSP) with Mason — using pyright (or basedpyright) for completion and type checking and Ruff for instant linting and formatting. Layer on nvim-treesitter for highlighting, nvim-cmp for autocompletion, telescope.nvim for fuzzy file search, and nvim-dap with debugpy for debugging. The result is a fast, keyboard-driven editor that runs anywhere — including over SSH on a server.
This guide is a 2026 refresh. The old Vundle/Pathogen + jedi-vim + pyflakes stack still works, but LSP-based tooling is now the mainstream, lower-maintenance path. We lead with the modern Neovim setup and cover classic Vim afterwards.
Key takeaways
- Neovim is the mainstream choice in 2026 — configured in Lua, with a huge plugin ecosystem built around LSP.
- LSP replaced the old plugins:
nvim-lspconfig+ Mason give you completion, go-to-definition, and diagnostics from real language servers. - Ruff lints and formats in milliseconds and has largely replaced the flake8 + isort + black combo (black is still a fine formatter if you prefer it).
- lazy.nvim is the modern plugin manager; vim-plug is the simplest option for classic Vim.
- Add Treesitter (syntax), nvim-cmp (completion UI), telescope.nvim (fuzzy find), and nvim-dap + debugpy (debugging).
- VS Code / Cursor are easier out of the box; Vim/Neovim win on speed, low resource use, SSH/remote work, and keyboard efficiency.
Should you use Neovim or classic Vim in 2026?
Use Neovim. It is configured in Lua, ships with a built-in LSP client, and is where almost all active plugin development now happens. Classic Vim (8.2+) is still excellent and worth knowing — it is preinstalled on virtually every Unix server, so the modal-editing skills transfer everywhere — but the richest Python tooling now assumes Neovim.
A practical rule: run Neovim as your daily driver locally, and keep a small, dependency-free Vim config for quick edits over SSH on machines where you cannot install plugins.
What does a modern Neovim Python setup include?
Think in terms of roles, then pick one plugin per role. Here is how the 2026 stack maps to the tools this post originally recommended back in 2018:
| Need | Modern plugin (Neovim) | What it replaces |
|---|---|---|
| Plugin manager | lazy.nvim | Vundle / Pathogen |
| Completion & diagnostics | nvim-lspconfig + Mason (pyright / pylsp) | jedi-vim, pyflakes |
| Lint + format | Ruff (ruff server) or black |
flake8 + isort + black |
| Syntax highlighting | nvim-treesitter | built-in regex syntax |
| Autocomplete UI | nvim-cmp (or blink.cmp) | omni-complete |
| Fuzzy file find | telescope.nvim | CtrlP |
| Debugging | nvim-dap + debugpy | (nothing) |
The config below lives in ~/.config/nvim/, split into init.lua plus a lua/plugins/ folder so each concern stays small and readable.
Step 1: Install lazy.nvim (the plugin manager)
lazy.nvim lazy-loads plugins for fast startup and manages them with a clean UI (:Lazy). Drop this bootstrap at the top of ~/.config/nvim/init.lua; it clones lazy.nvim on first launch, sets a few sensible Python defaults, then loads every spec under lua/plugins/.
If you develop on Windows, install Neovim with winget install Neovim.Neovim first — our guide to setting up a Python dev environment on Windows covers the toolchain around it.
-- ~/.config/nvim/init.lua
-- Bootstrap lazy.nvim (the modern Neovim plugin manager)
local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim"
if not (vim.uv or vim.loop).fs_stat(lazypath) then
vim.fn.system({
"git", "clone", "--filter=blob:none",
"https://github.com/folke/lazy.nvim.git",
"--branch=stable", lazypath,
})
end
vim.opt.rtp:prepend(lazypath)
-- Leader key + sensible Python defaults
vim.g.mapleader = " "
vim.opt.number = true
vim.opt.expandtab = true -- spaces, not tabs
vim.opt.shiftwidth = 4
vim.opt.tabstop = 4
vim.opt.softtabstop = 4
vim.opt.ignorecase = true
-- Load every spec from lua/plugins/*.lua
require("lazy").setup("plugins")Step 2: Add language servers with Mason
The Language Server Protocol (LSP) is the biggest change since this post first appeared. Instead of editor-specific plugins, a language server runs in the background and provides completion, diagnostics, hover docs, and go-to-definition. Mason is a package manager for those servers: run :Mason to browse and install them, and :checkhealth to diagnose problems.
For Python, use pyright (or the faster fork basedpyright) for type-aware completion, and Ruff as a dedicated server for lightning-fast linting and formatting. Prefer an all-Python server? Swap in python-lsp-server (pylsp). Save lua/plugins/lsp.lua with the following, then restart Neovim and let Mason install pyright and ruff.
-- lua/plugins/lsp.lua -- LSP via Mason + nvim-lspconfig
return {
{ "williamboman/mason.nvim", config = true },
{
"williamboman/mason-lspconfig.nvim",
dependencies = { "neovim/nvim-lspconfig", "hrsh7th/cmp-nvim-lsp" },
config = function()
require("mason-lspconfig").setup({
ensure_installed = { "pyright", "ruff" },
})
local lspconfig = require("lspconfig")
local caps = require("cmp_nvim_lsp").default_capabilities()
-- Type checking + completion
lspconfig.pyright.setup({
capabilities = caps,
settings = {
pyright = { disableOrganizeImports = true }, -- let Ruff sort imports
python = { analysis = { typeCheckingMode = "basic" } },
},
})
-- Ruff: ultra-fast lint + format (replaces flake8/isort/black)
lspconfig.ruff.setup({ capabilities = caps })
-- Format the buffer with the LSP on save
vim.api.nvim_create_autocmd("BufWritePre", {
pattern = "*.py",
callback = function() vim.lsp.buf.format() end,
})
end,
},
}Step 3: Completion, syntax highlighting, and fuzzy find
Three more plugins round out the IDE experience. nvim-treesitter parses your code for accurate highlighting and indentation; nvim-cmp is the popup completion menu that surfaces LSP suggestions as you type (the newer blink.cmp is a fast alternative); and telescope.nvim is the fuzzy finder that replaces CtrlP — press <leader>ff to jump to any file and <leader>fg to grep the project. Pair these habits with sound Python coding best practices and your edit-navigate loop gets very quick.
-- lua/plugins/editor.lua -- completion, syntax, fuzzy find
return {
-- Treesitter: accurate syntax highlighting & indentation
{
"nvim-treesitter/nvim-treesitter",
build = ":TSUpdate",
config = function()
require("nvim-treesitter.configs").setup({
ensure_installed = { "python", "html", "css", "javascript", "lua" },
highlight = { enable = true },
indent = { enable = true },
})
end,
},
-- Completion engine + LSP source
{
"hrsh7th/nvim-cmp",
dependencies = { "hrsh7th/cmp-nvim-lsp", "L3MON4D3/LuaSnip" },
config = function()
local cmp = require("cmp")
cmp.setup({
snippet = {
expand = function(args) require("luasnip").lsp_expand(args.body) end,
},
mapping = cmp.mapping.preset.insert({
["<CR>"] = cmp.mapping.confirm({ select = true }),
["<Tab>"] = cmp.mapping.select_next_item(),
}),
sources = { { name = "nvim_lsp" }, { name = "luasnip" } },
})
end,
},
-- Telescope: fuzzy file & symbol finder (replaces CtrlP)
{
"nvim-telescope/telescope.nvim",
dependencies = { "nvim-lua/plenary.nvim" },
keys = {
{ "<leader>ff", "<cmd>Telescope find_files<cr>", desc = "Find files" },
{ "<leader>fg", "<cmd>Telescope live_grep<cr>", desc = "Live grep" },
},
},
}How do I make Neovim use the right virtualenv?
The single most common Python-in-Vim problem is the LSP importing the wrong packages because it points at the system interpreter instead of your project's virtual environment. The fix is to tell pyright which python to use. The cleanest approach is to activate your venv before launching Neovim (so $VIRTUAL_ENV is set), then compute the interpreter path in your config. To switch environments without restarting, the linux-cultist/venv-selector.nvim plugin adds a :VenvSelect picker.
-- Use this pyright block in place of the plain pyright.setup in lsp.lua
local function python_path()
-- 1. Respect an already-activated virtualenv
if vim.env.VIRTUAL_ENV then
return vim.env.VIRTUAL_ENV .. "/bin/python"
end
-- 2. Fall back to a project-local .venv
local cwd = vim.fn.getcwd()
if vim.fn.filereadable(cwd .. "/.venv/bin/python") == 1 then
return cwd .. "/.venv/bin/python"
end
-- 3. System Python
return vim.fn.exepath("python3")
end
require("lspconfig").pyright.setup({
settings = {
python = { pythonPath = python_path() },
},
})How do I debug Python in Neovim?
Use nvim-dap (a Debug Adapter Protocol client) together with debugpy, Python's debug adapter. Install debugpy into your venv with pip install debugpy (or via Mason: :MasonInstall debugpy), then add nvim-dap-python. You get breakpoints, step-through execution, and variable inspection without leaving the editor — set a breakpoint with <leader>db and start a session with <leader>dc.
-- lua/plugins/dap.lua -- debugging with nvim-dap + debugpy
return {
{
"mfussenegger/nvim-dap-python",
dependencies = { "mfussenegger/nvim-dap" },
config = function()
local dap = require("dap")
-- Point at the debugpy in your active environment
require("dap-python").setup("python")
vim.keymap.set("n", "<leader>db", dap.toggle_breakpoint, { desc = "Breakpoint" })
vim.keymap.set("n", "<leader>dc", dap.continue, { desc = "Continue" })
end,
},
}Prefer classic Vim? Set it up with vim-plug
If you are on a stock Vim — say, editing on a remote server you do not control — vim-plug is the lightest modern plugin manager. This minimal ~/.vimrc gives you Python autocompletion (jedi-vim), asynchronous linting and formatting through ALE (which can drive Ruff, flake8, and black), fuzzy find (CtrlP), and a status line. Run :PlugInstall after saving. When you need to test a Django app running on that box from the outside, you can expose a local dev server publicly.
" ~/.vimrc -- classic Vim with vim-plug
" Install vim-plug first:
" curl -fLo ~/.vim/autoload/plug.vim --create-dirs \
" https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim
call plug#begin('~/.vim/plugged')
Plug 'davidhalter/jedi-vim' " Python autocompletion
Plug 'dense-analysis/ale' " async lint + fix (Ruff, flake8, black)
Plug 'ctrlpvim/ctrlp.vim' " fuzzy file finder
Plug 'vim-airline/vim-airline' " status line
call plug#end()
syntax on
filetype plugin indent on
set expandtab shiftwidth=4 tabstop=4 softtabstop=4
set autoindent number ignorecase
" Drive Ruff through ALE for fast lint + format on save
let g:ale_linters = { 'python': ['ruff'] }
let g:ale_fixers = { 'python': ['ruff', 'ruff_format'] }
let g:ale_fix_on_save = 1Neovim + LSP vs VS Code / Cursor: which should you use?
Let's be honest: VS Code and Cursor are easier to get started with — completion, debugging, and AI assistance work the moment you install them. Vim and Neovim ask for upfront configuration in exchange for speed, minimal resource use, and editing that works identically over SSH. Choose based on context, not dogma.
| Factor | Neovim + LSP | VS Code / Cursor |
|---|---|---|
| Out-of-the-box setup | Manual (Lua config) | Install and go |
| Startup & memory | Very light, near-instant | Heavier (Electron) |
| Remote / SSH editing | Native, excellent | Works via Remote-SSH |
| Keyboard efficiency | Best-in-class modal editing | Good with a Vim extension |
| AI assistance | Via plugins (Copilot, Codeium) | Built in (especially Cursor) |
| Learning curve | Steep | Gentle |
| Best fit | Power users, servers, low-spec machines | Beginners, fast onboarding |
Many teams use both: Neovim for fast, keyboard-driven work and remote sessions, VS Code or Cursor when a richer GUI or built-in AI pairing helps.
Frequently Asked Questions
Is Neovim better than Vim for Python development?
For most developers in 2026, yes. Neovim has a built-in LSP client, is configured in Lua, and hosts nearly all active Python plugin development, so completion, diagnostics, and debugging are easier to set up. Classic Vim still shines for quick edits on remote servers where you cannot install anything.
Do I still need jedi-vim if I use LSP?
No. nvim-lspconfig with pyright or python-lsp-server provides completion, go-to-definition, and hover docs through the Language Server Protocol, which supersedes jedi-vim's role. jedi-vim remains a reasonable choice only on classic Vim setups without an LSP client.
Should I use pyright or python-lsp-server (pylsp)?
Use pyright (or the basedpyright fork) when you want fast, accurate type checking and completion — it is the most popular choice. Choose pylsp if you prefer a pure-Python server you can extend with plugins or want flake8/mypy integration in one place. You can also run pyright for types and Ruff for linting side by side.
How does Ruff compare to flake8, isort, and black?
Ruff is a single Rust-based tool that lints, sorts imports, and formats — covering what flake8, isort, and (optionally) black did, but orders of magnitude faster. Many teams have replaced all three with Ruff; black is still a fine formatter if your project already standardizes on it. In Neovim, run Ruff as an LSP server via ruff server.
How do I make Neovim detect my virtualenv automatically?
Activate the virtualenv before launching Neovim so $VIRTUAL_ENV is set, then have your pyright config compute the interpreter from it (or from a project-local .venv). To switch environments mid-session, install venv-selector.nvim and use the :VenvSelect command. This ensures the LSP resolves imports against the correct packages.
Is Vim or Neovim still worth learning over VS Code or Cursor in 2026?
Yes, if you value speed, low resource use, and editing that works identically on any server over SSH. Modal editing also reduces hand movement and can make you faster once it clicks. VS Code and Cursor are the pragmatic pick when you want AI pairing and a full GUI with zero setup — many engineers keep both.
Build faster with the right tooling
A well-tuned Neovim or Vim setup is one piece of a productive Python workflow — the rest is architecture, testing, and shipping. MicroPyramid has built and maintained Python applications since 2014, across 50+ projects, using whatever editor and stack fits the job. If you want a team that moves fast on Django, FastAPI, and AI-powered features, explore our Python development services.