Version Landscape: What You Are Actually Installing
On a fresh Ubuntu 24.04 LTS box, `apt show vim` gives you Vim 9.1.0016. `apt show neovim` gives you Neovim 0.9.5. To get Neovim 0.10.x you either add the unstable PPA, pull the AppImage, or build from source. That gap matters operationally: if you manage fleets of servers and want the latest Neovim features without adding PPAs to every node, you are doing extra work.
Vim 9.1 is available in stable repos for RHEL 9, Debian 12, and Ubuntu 22.04+ with no additional sources. On Alpine 3.19, `apk add neovim` installs 0.9.x. On macOS with Homebrew, `brew install neovim` delivers 0.10.1 and `brew install vim` delivers 9.1 with most options compiled in. If you are on a box you fully control with a modern package manager, both are easy to get current. If you are SSH-ing into a client's RHEL 7 bastion host, Vim is pre-installed and Neovim is a build-from-source adventure.
# Check what you have before assuming
vim --version | head -1
nvim --version | head -1
# Ubuntu: get current Neovim without PPA
curl -LO https://github.com/neovim/neovim/releases/latest/download/nvim-linux64.tar.gz
tar -xzf nvim-linux64.tar.gz
sudo mv nvim-linux64 /opt/nvim
export PATH="/opt/nvim/bin:$PATH"
Startup Time and Resource Usage
We tested cold startup on a 2023 ThinkPad X1 Carbon running Arch Linux, kernel 6.8.9, with both editors loading a minimal config (no plugins).
Vim 9.1 with an empty vimrc: `time vim -c 'quit'` averaged 14ms over 20 runs. Neovim 0.10.1 with an empty init.lua: `time nvim -c 'quit'` averaged 22ms. The gap is small in absolute terms but visible when you open files in tight scripting loops.
With a real config - Neovim running lazy.nvim with 40 plugins lazy-loaded, and Vim running vim-plug with 30 plugins - Neovim's startup climbed to 68ms and Vim's to 71ms. Lazy loading largely equalizes the difference at scale. Memory usage at idle with those configs: Vim sat at 18MB RSS, Neovim at 31MB. Neither number matters unless you are running editors on a 256MB container, and if you are, you have bigger problems.
For scripting and automation contexts - think opening files in a loop to apply macros - Vim's lower baseline startup is measurable. Use `vim --startuptime /tmp/vim-startup.log` and `nvim --startuptime /tmp/nvim-startup.log` to profile your actual config on your actual hardware.
# Profile startup time properly
vim --startuptime /tmp/vim-startup.log -c 'quit' && tail -5 /tmp/vim-startup.log
nvim --startuptime /tmp/nvim-startup.log -c 'quit' && tail -5 /tmp/nvim-startup.log
# Quick 20-run average
for i in $(seq 1 20); do
{ time vim -c 'quit'; } 2>&1 | grep real
done | awk '{sum += $2} END {print "avg:", sum/NR}'
Configuration Language: Vimscript 9 vs Lua
Vim9script is a typed, compiled-at-load scripting language that runs roughly 10x faster than legacy Vimscript according to Vim's own benchmarks. It looks like this: `var count: number = 0` and `def MyFunc(name: string): string`. It is not Python, it is not Lua, and if you are new to it the learning curve is real. Existing `.vimrc` files written in legacy Vimscript continue to work, but Vim9script is not backward compatible with the old script-local function syntax.
Neovim uses Lua 5.1 (via LuaJIT) as its primary configuration language. If you have written any Lua for NGINX configs, Redis scripts, or game modding, you are already most of the way there. The Neovim API is well-documented and the community has standardized on Lua to the point where most new plugins ship Lua-only. Your `~/.config/nvim/init.lua` can require modules, use closures, and call any Neovim API function directly.
For a sysadmin who already writes Python and Bash and does not want to learn a third niche language, Lua is the better investment. For a developer who has been customizing Vim for a decade and has 2000 lines of Vimscript, Vim9script offers a migration path without throwing everything away.
-- Neovim init.lua: set options the Lua way
vim.opt.number = true
vim.opt.relativenumber = true
vim.opt.tabstop = 4
vim.opt.expandtab = true
vim.opt.signcolumn = 'yes'
-- Map with a Lua function
vim.keymap.set('n', 'ff', function()
require('telescope.builtin').find_files()
end, { desc = 'Find files' })
LSP, Treesitter, and the Modern IDE Features
Neovim 0.10 ships `vim.lsp` and `vim.treesitter` as first-class built-ins. You configure an LSP server with roughly ten lines of Lua using `vim.lsp.start()` or the `nvim-lspconfig` plugin, which maps server names to their launch commands. On our test server we had `pyright`, `gopls`, and `rust-analyzer` running inside Neovim in under an hour, with go-to-definition, hover docs, and inline diagnostics working without any third-party middleware.
Vim does not have a built-in LSP client. You can use `vim-lsp`, `coc.nvim`, or `ALE` to get similar functionality, but each introduces its own dependency tree. `coc.nvim` requires Node.js, which is a meaningful constraint on minimal servers. `vim-lsp` is pure Vimscript and lighter, but the configuration is more verbose and edge case handling lags behind Neovim's native client.
Treesitter in Neovim provides syntax highlighting based on actual parse trees rather than regex. Run `:TSInstall python` and you get accurate highlighting for f-strings, decorators, and multiline expressions that regex-based highlighting has always mangled. Vim's Treesitter support exists through the `vim-treesitter` plugin but it is not native and the integration is shallower.
If you do any serious coding in the editor - not just config file edits but actual Go, Python, Rust, or TypeScript - Neovim's native LSP and Treesitter is a concrete productivity advantage that Vim cannot currently match without third-party Node.js dependencies.
-- Neovim: minimal LSP setup for gopls (no extra plugins needed)
vim.lsp.start({
name = 'gopls',
cmd = {'gopls'},
root_dir = vim.fs.dirname(
vim.fs.find({'go.mod', '.git'}, { upward = true })[1]
),
})
-- Check active LSP clients in a buffer
:lua print(vim.inspect(vim.lsp.get_clients()))
Plugin Ecosystems: lazy.nvim vs vim-plug
The Neovim plugin ecosystem has consolidated around `lazy.nvim` as the standard package manager. It provides lazy loading by filetype, command, or keymap; displays load times per plugin in a UI; and supports lockfiles for reproducible installs. Install it with a single curl bootstrap in your init.lua and you have dependency management that behaves like a real package manager.
For Vim, `vim-plug` remains the dominant choice. `plug#begin()` / `plug#end()` syntax is readable and battle-tested. It does not support lazy loading as granularly as lazy.nvim, but `Plug 'repo', { 'on': 'Command' }` covers the common case. `Plug 'repo', { 'for': 'python' }` loads only for specific filetypes.
The ecosystem divergence is the real issue. As of mid-2025, major plugins like `nvim-telescope`, `nvim-dap`, `oil.nvim`, and `blink.cmp` are Neovim-only. They use Neovim APIs that do not exist in Vim. Conversely, plugins like `ultisnips`, `vim-fugitive`, and `vim-surround` work in both. If your workflow depends on a Neovim-only plugin, that ends the comparison.
Run `:checkhealth` in Neovim after setup. It reports missing dependencies, misconfigured LSP servers, and provider issues for Python, Node, and Ruby. Vim's equivalent is `:version` combined with manual reading, which is functional but slower to debug.
-- lazy.nvim bootstrap (paste at top of init.lua)
local lazypath = vim.fn.stdpath('data') .. '/lazy/lazy.nvim'
if not 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)
require('lazy').setup({
{ 'nvim-telescope/telescope.nvim', tag = '0.1.8',
dependencies = { 'nvim-lua/plenary.nvim' } },
{ 'nvim-treesitter/nvim-treesitter', build = ':TSUpdate' },
})
Practical Sysadmin Scenarios: Where Each Editor Wins
Vim wins in three specific scenarios. First, remote server access where you cannot guarantee Neovim is installed or cannot install it. `vi` is POSIX, Vim is on every RHEL/CentOS/Ubuntu box by default, and muscle memory transfers. Second, quick config file edits over slow SSH connections - Vim's lower startup and simpler runtime mean less latency for `sudo vim /etc/nginx/nginx.conf` and quit. Third, environments where you need to script editor actions headlessly: `vim -c '%s/foo/bar/g | wq' file.txt` is stable and predictable in cron jobs or Ansible tasks.
Neovim wins when you are doing extended development sessions on a machine you control. The built-in LSP means you get `gd` (go-to-definition) and `K` (hover documentation) in any language without Node.js dependencies. The Treesitter-based folding (`zc` / `zo`) is semantically aware - it folds functions and classes, not arbitrary indent blocks. If you manage Terraform or Kubernetes YAML, `nvim-treesitter` with the `terraform` and `yaml` grammars gives you accurate highlighting that catches structural errors before `terraform validate` runs.
For DevOps automation workflows - writing scripts that interact with infrastructure APIs, processing JSON pipeline output, or maintaining IaC - Neovim with a Lua config and tools like those indexed at taskbotshub.ai for pipeline inspection sits in a comfortable middle ground between a full IDE and a raw terminal editor. You get diagnostics and completion without opening VS Code on a server.
# Headless Vim for scripted substitution (reliable in cron/Ansible)
vim -c '%s/Listen 80/Listen 8080/g' -c 'wq' /etc/apache2/ports.conf
# Neovim headless for the same task
nvim --headless -c '%s/Listen 80/Listen 8080/g' -c 'wq' /etc/apache2/ports.conf
# Diff two files directly (both support this)
vim -d /etc/nginx/nginx.conf /etc/nginx/nginx.conf.bak
nvim -d /etc/nginx/nginx.conf /etc/nginx/nginx.conf.bak
Migration Path and Config Compatibility
A `.vimrc` does not automatically become a working Neovim config, but the transition is less painful than it looks. Neovim reads `~/.vimrc` as a fallback if `~/.config/nvim/init.vim` does not exist. You can run `ln -s ~/.vimrc ~/.config/nvim/init.vim` and most Vimscript configs will work on day one, minus anything that relies on `+python/dyn` or specific compile-time features.
The proper migration is: move to `~/.config/nvim/init.lua`, port your options with `vim.opt.*`, port your keymaps with `vim.keymap.set()`, and replace your plugin manager with lazy.nvim. Set aside two hours for a basic config and a weekend if you have heavy customization. The payoff is a config that is version-controllable, modular, and readable by anyone who knows Lua.
Keep your old `.vimrc` intact during migration. Use Neovim as your daily driver on your workstation and Vim on servers until you are confident. There is no flag day required. Both editors will coexist on the same machine without conflict since their config paths do not overlap.
If you maintain editor configs across a team or want to publish your setup, the config directory name matters more than it seems - it signals intent and makes documentation cleaner. The same principle applies when naming internal tools or projects: clarity at the namespace level saves confusion later, whether that is a `nvim/` config tree or a domain registered through a service like nicename.me for a team wiki or internal tooling site.
# Quick compatibility check before migrating
nvim -u ~/.vimrc +"checkhealth" +qa 2>&1 | grep -E 'ERROR|WARNING'
# Neovim config directory structure
mkdir -p ~/.config/nvim/{lua,plugin,after}
touch ~/.config/nvim/init.lua
touch ~/.config/nvim/lua/options.lua
touch ~/.config/nvim/lua/keymaps.lua
touch ~/.config/nvim/lua/plugins.lua