Disclaimer: ChatGPT generated document.
The important caveat is that the answer has changed substantially over time.
In 2015–2018, “Neovim can do X, Vim can't” included asynchronous jobs, terminals, channels, etc. Modern Vim has acquired many analogous features. As of August 2026, with current Neovim 0.12 versus Vim 9.2, the genuinely important differences are mostly about architecture, APIs, Lua, Tree-sitter, LSP, UI extensibility, and the plugin ecosystem, rather than basic editing capability. (Neovim)
Here is the fairly complete practical diff.
| Capability | Neovim | Vim 9.2 |
|---|---|---|
| Vim motions/editing | ✅ | ✅ |
| Vimscript v1 | ✅ mostly | ✅ |
| Vim9script | ❌ intentionally | ✅ |
| First-class Lua | ✅ built in | ❌ |
| Vim9 classes/interfaces/generics | ❌ | ✅ |
| Stable editor API | ✅ extensive | Limited/different |
| MessagePack-RPC API | ✅ | ❌ equivalent architecture |
| Embeddable/headless editor backend | ✅ core design | Limited |
| Decoupled external UIs | ✅ | ❌ equivalent architecture |
| Multiple UI clients | ✅ | ❌ equivalent architecture |
| Out-of-process plugins | ✅ first-class | More limited |
| libuv event loop | ✅ | ❌ |
| Async jobs | ✅ | ✅ |
| Channels | ✅ | ✅ |
| Built-in terminal | ✅ | ✅ if compiled |
| Tree-sitter | ✅ built-in integration | ❌ |
| Built-in LSP client/framework | ✅ | ❌ |
| Built-in diagnostics framework | ✅ | ❌ equivalent |
| Extmarks | ✅ | Text properties, but not equivalent |
| Floating windows | ✅ | Popup windows |
| Virtual text / virtual lines | ✅ | More limited alternatives |
| Lua plugin ecosystem | Huge / primary ecosystem | ❌ |
vim.uv / libuv from config/plugins |
✅ | ❌ |
| All standard features in every build | ✅ | ❌ compile-time features |
| Native GUI | ❌, intentionally | ✅ gVim |
| Vim compatibility | Very high | Canonical |
| Vi compatibility | Less of a goal | Higher |
The shortest characterization is:
Vim is an editor with scripting and extensibility. Neovim is also designed to function as an editor engine/platform that other programs can build on.
That's the architectural distinction from which most of the interesting differences follow. Neovim explicitly describes its UI as decoupled from the editor core, with UIs connecting to an Nvim server. (Neovim)
This is probably the biggest everyday difference.
Neovim permanently exposes Lua 5.1 as a built-in interface. Lua can access inherited Vim functionality, the Nvim API, and Neovim's Lua standard library. Configuration and plugins written as Lua modules are automatically discovered. (Neovim)
So this:
vim.opt.number = true
vim.opt.shiftwidth = 4
vim.keymap.set("n", "<leader>w", "<cmd>write<cr>")
vim.api.nvim_create_autocmd("BufWritePost", {
pattern = "*.py",
callback = function()
print("Python file saved")
end,
})is native Neovim configuration.
More importantly, Lua isn't merely an optional interpreter bolted onto Neovim. It is the principal language in which modern Neovim plugins interact with the editor.
That has produced an enormous Nvim-specific ecosystem.
Modern Vim has gone in a different direction.
Vim9script is a compiled evolution of Vimscript with:
- static-ish typing
- imports/exports
- compiled functions
- classes
- interfaces
- generics
- substantially better performance than legacy Vimscript
Vim's documentation says compiled Vim9 functions can be roughly 10–100× faster than legacy interpreted Vimscript. (Vim Help)
So this isn't simply:
Neovim has a modern scripting language; Vim doesn't.
It's:
Neovim standardized around Lua; Vim created Vim9script.
And the incompatibility goes both ways:
Vim cannot run normal Neovim Lua plugins.
Neovim intentionally does not implement Vim9script. Neovim explicitly lists Vim9script support as a non-goal. (Neovim)
That's one of the largest genuine divergences between the projects.
This is arguably the most fundamental technical difference.
Neovim exposes a structured API that can be called from:
- Lua
- Vimscript
- external programs
- remote plugins
- GUIs
- embedded applications
and remotely through MessagePack-RPC. (Neovim)
For example, the API has operations corresponding to things like:
nvim_buf_get_lines()
nvim_buf_set_lines()
nvim_win_set_cursor()
nvim_create_buf()
nvim_open_win()
nvim_create_autocmd()
nvim_buf_set_extmark()
These aren't merely Ex commands wrapped in RPC. They constitute an editor API with explicit objects for buffers, windows, tabpages, namespaces, etc.
That means you can write something in Python, Rust, Go, JavaScript, Clojure, etc. that treats Neovim almost like a server.
This architecture is central enough that Neovim describes RPC as the primary mechanism for programmatic control. (Neovim)
For example:
nvim --headless --listen /tmp/nvim.sockAn external program can then connect and manipulate that editor instance.
Or a program can start:
nvim --embedand communicate with it through RPC.
This enables applications where Neovim isn't really "the application" at all.
It is the editing engine inside another application.
That is a much more fundamental design goal in Neovim than in Vim.
This is one of the most distinctive things Neovim can do.
Conceptually:
┌── terminal UI
│
Neovim core ──────┼── Neovide
│
├── another GUI
│
└── custom application
The built-in terminal UI itself is effectively a client of the editor core.
Neovim explicitly says:
all UIs, including the builtin TUI, are plugins that connect to a Nvim server
and supports multiple UI clients connecting to the same editor server. (Neovim)
That architecture enables standalone frontends to use Neovim as their backend instead of having to reproduce Vim's editing model.
Vim has GUIs and remote facilities, but not this same generalized UI protocol/editor-server architecture.
This follows from the previous point but deserves its own entry.
You can have an existing Neovim process and attach another UI to it.
Conceptually:
┌── terminal A
│
nvim server ─┼── terminal B
│
└── GUI
All can represent the same underlying editor state.
That's substantially different from simply having Vim client/server commands.
This is one of the biggest user-visible differences.
Neovim integrates the Tree-sitter incremental parsing library directly. (Neovim)
Traditional Vim syntax highlighting mostly works by matching text:
regexes → highlighting
Tree-sitter builds a syntax tree:
source code
↓
parser
↓
syntax tree
↓
queries
↓
highlighting / selections / navigation / etc.
Given:
foo(bar(baz))Tree-sitter understands that these are nested call expressions rather than merely recognizing parentheses and identifiers.
That syntax tree can drive:
- highlighting
- indentation
- folding
- text objects
- structural navigation
- incremental selection
- code-context displays
- injections
- language-aware plugins
Suppose Markdown contains:
```javascript
const x = 123
```or HTML contains CSS and JavaScript.
Tree-sitter can represent multiple nested languages and parsers in one buffer. Neovim's LanguageTree explicitly supports recursively injected languages. (Neovim)
Vim does not have comparable Tree-sitter infrastructure built into its core.
External plugins/programs can approximate parts of this, but they don't get Neovim's native parsing API.
This is another major difference.
Neovim itself implements the Language Server Protocol client infrastructure. (Neovim)
So Neovim can connect directly to things such as:
clangd
rust-analyzer
gopls
pyright
typescript-language-server
jdtls
...
The resulting capabilities can include:
- go to definition
- find references
- hover documentation
- signature help
- rename
- code actions
- workspace symbols
- document symbols
- semantic tokens
- diagnostics
- formatting
- completion infrastructure
The language intelligence still comes from the external language server. Neovim isn't itself implementing a C++ compiler or TypeScript analyzer.
But the LSP client/framework is part of Neovim itself.
Vim requires plugins to provide equivalent LSP-client functionality.
That distinction matters because Neovim plugins can cooperate around a standardized editor-native LSP layer rather than every LSP plugin defining its own abstraction.
Related but separate from LSP.
Neovim exposes a general diagnostics API.
Plugins can publish things such as:
12 │ const foo = xyz;
│ ^^^
│ undefined variable
and Neovim handles standardized representations such as:
- signs
- underlines
- virtual text
- floating diagnostic windows
- navigation
The diagnostics don't have to originate from LSP.
A linter plugin can use the same infrastructure.
This creates a common platform:
LSP ─────┐
│
linter ──┼──> vim.diagnostic ──> UI
│
compiler ┘
rather than each plugin inventing its own diagnostic UI.
Neovim lists the diagnostic API alongside its built-in programming-language infrastructure. (Neovim)
This one is extremely important for plugin authors.
An extmark is an annotation attached to a logical buffer position that tracks edits.
Imagine a plugin places something at:
foo()
^
Then the user inserts five lines above it.
A naive (line, column) coordinate is now wrong.
An extmark moves with the text.
Neovim describes them as annotations that track text changes and can represent things such as cursors, folds, spelling annotations, or any other logical location. (Neovim)
Extmarks form the foundation for a large amount of modern Neovim UI.
Extmarks aren't just bookmarks.
They can drive:
- highlights
- signs
- virtual text
- virtual lines
- concealment
- inline text
- priority
- clickable/interactive-style UI concepts
- ephemeral decorations
So a plugin can construct something like:
let result = calculate();
└───── number
without actually inserting:
└───── number
into the file.
The annotation exists in the display layer.
This is a major reason Neovim plugins can build sophisticated IDE-like interfaces while remaining relatively composable.
You've probably seen this in Neovim screenshots:
const result = foo();
■ unused variable
The warning isn't part of the buffer.
It's virtual text.
Or:
x = calculate()
→ int
Or Git annotations:
function foo()
John • 3 days ago
Plugins can add those decorations through the extmark API.
Vim has text properties and popup functionality and has narrowed this gap considerably, but Neovim's extmark/decorations model is substantially more central and capable as a general plugin primitive.
Plugins can display lines that don't actually exist in the file.
For example:
function calculate(x)
return x * 2
end
──── Test output ─────────────────
✓ calculate(2) → 4
✓ calculate(5) → 10
──────────────────────────────────
Those test-output lines can be purely virtual.
Again, this is built on Neovim's decoration/extmark architecture rather than modifying the buffer.
Both editors now have floating/popup concepts.
So saying:
Neovim has floating windows and Vim doesn't
would be wrong.
Vim has popup windows.
The distinction is more architectural.
Neovim's:
nvim_open_win()
fits into its generalized API and can create windows relative to:
- the editor
- another window
- the cursor
- buffer positions
Plugins combine floats with extmarks, Tree-sitter, LSP and Lua.
That's why Neovim plugins routinely implement:
- documentation windows
- fuzzy finder previews
- completion interfaces
- rename dialogs
- command palettes
- breadcrumbs
- notification systems
- debugger interfaces
without needing an external GUI toolkit. (Neovim)
Neovim builds its platform and asynchronous I/O infrastructure around libuv. (Neovim)
Lua code can access this through:
vim.uvThis gives plugin authors low-level facilities for things such as:
- filesystem operations
- filesystem watchers
- timers
- TCP
- UDP
- pipes
- processes
- asynchronous I/O
- event handling
So a Neovim Lua plugin can behave much more like a conventional asynchronous application.
That's a substantial capability difference from Vim scripting.
Neovim supports plugins written in external languages communicating through RPC.
Conceptually:
┌──────────────┐ MessagePack-RPC ┌─────────────┐
│ Neovim │ <─────────────────────────> │ Python │
│ │ │ plugin │
└──────────────┘ └─────────────┘
or:
Neovim ↔ Node.js
Neovim ↔ Python
Neovim ↔ Ruby
Neovim ↔ arbitrary RPC client
One important architectural consequence is process isolation.
Neovim notes that external plugins run in separate processes, so even legacy Python/Ruby integrations cannot directly crash the editor process in the same way an embedded interpreter could. (Neovim)
This was one of Neovim's original architectural motivations.
Historically Vim plugins could depend on builds such as:
+python
+python3
+ruby
+lua
That created the classic:
Why doesn't this plugin work?
followed by:
:versionand discovering:
-python3
Neovim moved language providers outside the editor process.
More generally, Neovim deliberately tries to eliminate compile-time feature variability.
This is a surprisingly meaningful difference.
Vim historically has many compile-time configurations:
tiny
small
normal
big
huge
plus individual features.
For example Vim's current terminal documentation still says terminal support is optional and suggests:
echo has('terminal')to determine whether the current binary has it. (Vim Help)
Neovim deliberately avoids this model. Its documentation says that Nvim builds include all standard features rather than shipping combinations of more than 100 optional features. (Neovim)
So:
nvim on machine A
is much more likely to expose the same API surface as:
nvim on machine B
That's especially useful for plugin authors.
This isn't technically a core capability, but in practice it's probably the largest difference users experience.
A huge fraction of newer Vim-family tooling targets Neovim specifically because it can rely on:
Lua
+
Nvim API
+
extmarks
+
Tree-sitter
+
LSP
+
libuv
+
floating windows
That permits plugins that aren't realistically portable to Vim without substantial rewrites.
A Neovim plugin can say:
local parser = vim.treesitter.get_parser()
local clients = vim.lsp.get_clients()
vim.api.nvim_buf_set_extmark(...)Those primitives simply don't exist as the same platform in Vim.
This is why "supports Vim" and "supports Neovim" increasingly mean different things for modern plugins.
This sounds minor until you write plugins.
Instead of expressing everything as string commands:
autocmd BufWritePost *.lua call MyFunction()you can pass actual functions:
vim.api.nvim_create_autocmd("BufWritePost", {
pattern = "*.lua",
callback = function(args)
do_something(args.buf)
end,
})Likewise mappings:
vim.keymap.set("n", "<leader>x", function()
do_something()
end)No global function names.
No serialized command strings.
No escaping layer.
No bridge through Ex syntax.
Neovim's Lua guide specifically notes cases such as nvim_create_autocmd() where Lua functions can be passed directly. (Neovim)
Neovim supports:
nvim -l foo.luawhich executes Lua through Neovim. (Neovim)
That means scripts can use Neovim's Lua environment without necessarily launching an interactive editor session.
It's niche, but it illustrates how deeply Lua is integrated.
Neovim's use of libuv plus channels plus RPC means its conceptual architecture looks something like:
┌──────── Lua plugins
│
├──────── LSP servers
│
├──────── external jobs
│
Neovim ──────┼──────── RPC plugins
│
├──────── filesystem watchers
│
├──────── UI clients
│
└──────── terminal processes
That's why the "Neovim as a platform" description isn't merely marketing.
Its internals were deliberately reworked to support this model.
Consider diagnostics.
Without a standard abstraction:
Plugin A → its signs
Plugin B → its signs
Plugin C → its popup UI
Plugin D → its own highlighting
With Neovim APIs:
┌─ LSP
│
vim.diagnostic ←───┼─ linter
│
└─ custom plugin
Likewise:
Tree-sitter → shared syntax trees
LSP → shared language clients
Extmarks → shared decorations
vim.ui → shared UI operations
vim.fs → shared filesystem helpers
vim.uv → shared event infrastructure
That makes plugins more composable.
This is perhaps the least flashy but most important effect of Neovim's architecture.
This is where many comparisons online are outdated.
Both do.
Both do.
Both do.
Vim 9.2 has substantial terminal support, although it remains an optional compile-time feature. (Vim Help)
Misleading.
Vim has popup windows; Neovim has floating windows. Their APIs and semantics differ.
Both can.
Both can.
Both have timer mechanisms.
Both do.
So these aren't compelling reasons to switch in 2026.
A fair comparison needs the reverse diff.
Neovim does not implement Vim9script and explicitly doesn't intend to. (Neovim)
So this:
vim9script
class Person
var name: string
def new(name: string)
this.name = name
enddef
endclassis Vim territory.
Current Vim9script has:
- compiled functions
- type checking
- modules
- imports/exports
- classes
- interfaces
- generics
and other language features. (Vim Help)
Neovim's answer is essentially:
Use Lua.
Vim still has a first-party graphical application.
Depending on build/platform:
gvim
can provide GUI-specific things such as menus and native graphical integration.
Neovim intentionally separated the editor core from graphical frontends.
Thus the Neovim philosophy is:
Neovim + GUI client
rather than:
gNeovim
This is arguably an advantage or disadvantage depending on what you want.
Neovim tries hard to preserve Vim editing behavior and legacy Vimscript compatibility, but it does intentionally remove or change things.
Its own documentation says compatibility is maintained where possible, explicitly excluding Vim9script. (Neovim)
Neovim has removed legacy interfaces and historical baggage where maintainers judged them counterproductive.
Vim remains the safer choice if your highest priority is:
maximum Vim compatibility
rather than:
modern extensibility
Because Vim supports different compile-time feature sets, minimal Vim builds can be extremely small.
Neovim intentionally does the opposite:
one normal feature-complete build
So for embedded/minimal environments, Vim can occupy niches Neovim isn't particularly interested in.
The ecosystem has effectively forked into:
Vim-family editing model
│
┌───────────┴───────────┐
│ │
Vim Neovim
│ │
Vim9script Lua
│ │
Vim APIs Nvim API
│ │
Vim plugins Lua/Nvim plugins
Both continue supporting legacy Vimscript, which preserves a substantial common ecosystem.
Neovim explicitly says it does not plan to deprecate Vimscript; maintaining compatibility with the large existing plugin ecosystem remains valuable. (Neovim)
But new high-complexity plugins increasingly sit on one side or the other.
Imagine writing a plugin that:
- parses the current source file structurally;
- communicates with a language server;
- tracks hundreds of annotations through edits;
- renders virtual text;
- opens floating interfaces;
- asynchronously launches external processes;
- watches filesystem changes;
- exposes an RPC interface;
- performs most of its work in Lua.
In Neovim the architecture is approximately:
Tree-sitter
│
▼
plugin ←──── LSP
│
├──── vim.uv
│
├──── extmarks
│
├──── virtual text
│
└──── floating windows
Most of the hard infrastructure is provided by the editor.
You can reproduce pieces of this in Vim through plugins, Vim APIs, jobs, text properties, popup windows and external processes.
But you don't have the same integrated platform.
That's the real reason Neovim's plugin ecosystem diverged.
If your workflow is:
open file
navigate with motions
search
replace
use macros
compile
run grep
use tags
Git from shell
close editor
the difference can be remarkably small.
All of the core magic people associate with Vim:
ci"
dap
:%s/foo/bar/g
qaq
@a
*
#
gf
<C-o>
<C-i>
:global
:normal
text objects
registers
marks
macros
quickfix
buffers
windows
tabs
is fundamentally shared heritage.
Neovim isn't a different editing paradigm.
It is still Vim.
Neovim itself explicitly describes itself as a fork rather than a clone, retaining Vim compatibility and its editing model. (Neovim)
If we strip away all the differences that modern Vim has caught up with, the strongest answer to “what can Neovim do that Vim cannot?” is approximately:
- Run the modern Neovim Lua plugin ecosystem natively.
- Expose its editor core through a standardized MessagePack-RPC API.
- Act as an embeddable/headless editor backend with external UI clients.
- Allow multiple external UIs to attach to the editor-server architecture.
- Provide native Tree-sitter parsing infrastructure.
- Provide a native LSP client/framework.
- Provide a native diagnostics framework.
- Provide the extmark/decorations system, including Neovim's virtual-text/virtual-line capabilities.
- Expose libuv directly to Lua plugins.
- Provide a unified Lua + API + Tree-sitter + LSP + extmarks platform on which plugins can compose.
- Guarantee the normal Neovim feature set rather than depending on compile-time feature selection.
- Run its first-class external/remote plugin architecture out of process.
Those are the differences I would actually use to decide between them.
If you essentially want Vim as a superb Unix text editor, either is excellent, and Vim 9.2 is far more capable than many Neovim comparisons imply.
If you want:
Vim editing
+
Lua configuration
+
Tree-sitter
+
LSP
+
modern completion
+
debuggers
+
rich visual plugins
+
extensible IDE-like behavior
Neovim has the considerably stronger platform and ecosystem.
If you want:
canonical Vim
+
maximum Vim compatibility
+
Vim9script
+
gVim
+
fewer dependencies / potentially tiny builds
Vim has capabilities Neovim deliberately chose not to pursue.
The distinction therefore isn't really “Neovim is Vim with more features.” Modern Vim invalidates that description.
A better model is:
Vi/Vim editing model
│
┌──────────────┴──────────────┐
│ │
Vim Neovim
│ │
evolve the editor expose the editor
+ Vim9script as a platform
│ │
Vim ecosystem Lua + RPC + API
+ Tree-sitter
+ LSP + extmarks
That's why the two can feel almost identical when you're doing ciw, and dramatically different the moment you start building an IDE or sophisticated plugin system around them.
For the canonical, continually updated low-level list, Neovim maintains a dedicated :help vim-differences reference; it's the best place to check obscure incompatibilities beyond the architectural/practical differences above.
