What’s New in Neovim 0.13

· .dotfiles ·

13 min read Original article ↗

Neovim 0.13 is still under development, but it is already shaping up to be one of the more interesting Neovim releases in a while.

The current nightly adds native multiple cursors, better support for persistent remote sessions, filesystem-watcher-based autoread, structured concurrency for Lua, improvements to built-in LSP completion, terminal image support, a redesigned command-line window, continued improvements to vim.pack, and even an experimental WebAssembly build.

This guide is based on the current 0.13 nightly, so some details may still change before the final release.

It is organized around the changes that matter most in day-to-day use, with practical examples and a migration checklist at the end.

The most important additions in 0.13 are:

  • Native multiple cursors

  • Persistent Neovim sessions with :detach! and :connect

  • Real-time external file detection using filesystem watchers

  • Structured concurrency with vim.async

  • Improved built-in LSP completion

  • Image rendering with vim.ui.img

  • A redesigned command-line window

  • More complete built-in plugin management

  • Major Treesitter performance improvements

  • Better undo behavior

  • Lua functions directly usable as option values

  • An experimental WebAssembly build

Official source: :help news.

Probably the biggest user-facing addition in Neovim 0.13 is native multiple cursor support.

Until now, multiple cursors usually required plugins such as vim-visual-multi.

Neovim can now manage multiple cursors directly.

The basic mappings are:

Q               Add or remove a cursor
[count]Q        Add cursors to search matches
Ctrl-LeftMouse  Toggle a cursor with the mouse
q=              Toggle follow mode
Ctrl-L          Clear cursors
gQ              Restore cleared cursors

One useful workflow is searching for something and then placing cursors on the matches.

For example:

/foo
5Q

This places cursors on matching locations so they can be edited together.

There is also a Lua API:

vim.api.nvim_mcursor()

The implementation is integrated into Neovim’s editor model rather than layered on top through a plugin.

There is an important compatibility change here.

Q now creates a multiple cursor instead of replaying the last recorded register.

If you want the previous behavior:

vim.keymap.set('n', 'Q', function()
  local reg = vim.fn.reg_recorded()

  return reg == '' and '' or ('@' .. reg)
end, { expr = true })

If you already have something mapped to Q, this is one of the first things to review when testing 0.13.

Neovim’s client/server architecture continues to become more useful in 0.13.

A particularly interesting addition is:

:detach!

This marks the current UI as detachable.

If the terminal unexpectedly disappears, Neovim can continue running.

That includes situations such as:

  • accidentally closing your terminal

  • losing an SSH connection

  • a remote terminal session disappearing

  • temporarily disconnecting from a development server

Conceptually:

Laptop
   │
   │ SSH
   ▼
Server
   │
   └── Neovim
          │
          ├── SSH disconnects
          │
          └── Neovim keeps running

Later, another UI can reconnect using:

:connect

This makes Neovim behave a little more like a persistent development service instead of something completely tied to one terminal process.

For anyone doing development over SSH, this is a very useful direction.

There is also:

:%detach

which detaches all UIs except the current one.

Neovim has had 'autoread' for a long time, but external file changes were traditionally detected through things such as:

:checktime

or events such as FocusGained.

Neovim 0.13 changes this significantly.

'autoread' now uses filesystem watchers to detect changes in real time.

That means this:

External process
       │
       ▼
 modifies file
       │
       ▼
filesystem watcher
       │
       ▼
    Neovim

instead of waiting for focus changes or manually calling :checktime.

This is especially useful now that more developers have other processes modifying their code.

For example:

Codex / Claude / AI Agent
            │
            ▼
       modifies file
            │
            ▼
         Neovim

The editor can react to those changes much sooner.

You can inspect file watcher support through:

:checkhealth

where file watcher information is now shown in the Performance section.

Neovim 0.13 introduces:

vim.async

This provides structured concurrency primitives for Lua.

It includes things such as:

  • task handles

  • await helpers

  • cooperative cancellation

  • completion-order iteration

  • semaphores

Historically, asynchronous Neovim plugins often relied on combinations of:

vim.schedule()
vim.defer_fn()
vim.uv
coroutines
callbacks

or custom abstractions built around them.

vim.async provides a more consistent model for asynchronous Lua code.

This will probably matter more to plugin authors than normal users initially, but it gives plugin developers a much cleaner foundation for things such as:

  • network requests

  • background processes

  • concurrent filesystem work

  • LSP tooling

  • AI integrations

  • task runners

It is one of the more important architectural additions in 0.13.

Neovim 0.12 significantly expanded the built-in LSP and completion stack.

0.13 continues that work.

Neovim’s built-in LSP completion now supports commitCharacters.

Language servers can tell the editor that certain characters should accept the current completion item.

For example, imagine the completion menu contains:

user.name

and . is a commit character.

Typing:

user.

can accept user and immediately insert the ..

This behavior is common in full IDEs but previously wasn’t fully supported by Neovim’s built-in completion.

Neovim now supports:

CompletionItem.preselect

when 'completeopt' contains:

preselect

Built-in LSP completion also gains support for nested snippets.

These functions now respect 'switchbuf':

vim.lsp.buf.declaration()
vim.lsp.buf.definition()
vim.lsp.buf.type_definition()
vim.lsp.buf.implementation()

They can also be called with arbitrary positions instead of always operating on the current cursor position.

vim.lsp.formatexpr() can now fall back to:

textDocument/formatting

when formatting the entire buffer if the server does not support:

textDocument/rangeFormatting

Overall, Neovim continues moving more completion and LSP behavior into the core editor.

Neovim 0.13 adds:

vim.ui.img

for displaying images in supported terminals.

You can check whether your current terminal supports the necessary functionality with:

:checkhealth vim.health

This could eventually make several plugin workflows much simpler.

For example:

Markdown
   │
   └── image preview

File browser
   │
   └── image preview

Documentation
   │
   └── diagrams

Picker
   │
   └── image thumbnails

Terminal image protocols have become much more common in modern terminals, and Neovim now has a built-in abstraction that plugins can use instead of every plugin implementing terminal detection and image rendering itself.

The command-line window opened using:

q:
q/
q?

has been substantially redesigned.

Previously, the command-line window operated inside a special nested editor state.

That created a lot of strange restrictions.

For example, some API calls could fail with:

E11: Invalid in command-line window

In 0.13, the command-line window is implemented as a more normal buffer + window.

That means you can do things such as:

  • create windows

  • navigate windows

  • switch tabpages

  • use inccommand

  • call APIs that previously failed inside the command-line window

This also removes several special cases that asynchronous plugins previously needed around CmdwinLeave.

It is mostly an architectural cleanup, but it makes another historically unusual part of Vim behave more like the rest of Neovim.

Neovim 0.12 introduced the built-in plugin manager:

vim.pack

0.13 continues building it out.

There are now commands for managing installed packages:

:packupdate
:packdel

vim.pack.get() can also:

  • report the revision of pending updates

  • fetch new updates before calculating its output

There is now support for a plugin manifest:

vim.pack-manifest

and a new option:

'packlockfile'

which controls the location of the package lockfile.

This means vim.pack is gradually becoming a more complete package-management system rather than only an API for cloning packages.

It still does not try to replace every feature of plugin managers such as lazy.nvim, especially around advanced lazy loading, but the built-in option continues becoming viable for simpler configurations.

There is also a significant performance improvement in Treesitter highlighting.

For large files with a lot of Treesitter injections, Neovim reports approximately 50% to 100% better highlighting performance by reusing edited child-tree ranges.

Injection-heavy files commonly include things such as:

  • Markdown containing code blocks

  • HTML containing JavaScript and CSS

  • templating languages

  • embedded SQL

  • documentation files containing multiple languages

Those are exactly the files where Treesitter can become expensive.

Neovim also adds:

vim.treesitter.select()

for starting or adjusting visual selections based on syntax-tree nodes.

Visual-mode selection can also expand to sibling Treesitter nodes.

The Treesitter parser for:

diff

is now bundled with Neovim.

This is a smaller change, but one that should be noticeable in normal editing.

Undo now restores the cursor to its original position.

Consider:

diw

Previously, undoing a change could move the cursor to the beginning of the changed text.

In 0.13:

edit
  │
  ▼
undo
  │
  ▼
original cursor position

Mark behavior around undo has also been corrected.

If you manually move a mark after making an edit, undo no longer incorrectly restores an older position for that mark.

These are small details, but they make undo feel more predictable.

Another useful Lua improvement is that Lua functions and closures can now be assigned directly to function-based options.

For example:

vim.o.operatorfunc = function()
  print('operator')
end

The same applies to expression options such as:

vim.wo.foldexpr = function()
  -- calculate fold
end

and:

vim.bo.indentexpr = function()
  -- calculate indentation
end

This removes some of the awkwardness of creating globally accessible functions just so Vim options can call them.

Reading the option from Lua returns the function itself.

This should simplify a lot of modern Lua configs and plugins.

0.13 includes several smaller Lua additions.

Neovim can create directories directly:

vim.fs.mkdir('/tmp/example')

Including parent directories:

vim.fs.mkdir('/tmp/foo/bar/baz', {
  parents = true,
})

Generate a filesystem-safe name from arbitrary text:

local name = vim.fs.slug('My Project: Version 2')

Checks both normal Lua nil and vim.NIL:

if vim.isnil(value) then
  -- ...
end

Returns the first argument that isn’t nil.

Similar to pcall(), except it returns nil when the call fails.

The position API continues expanding and can now convert between:

  • buffer positions

  • offsets

  • marks

You can also get the current cursor position with:

vim.pos.cursor()

Neovim now exposes a logging interface:

vim.log

Iterators now support:

iter:count()

These APIs are not individually huge, but they continue reducing the amount of utility code plugin authors need to maintain themselves.

0.13 includes a number of smaller editor improvements.

There is now:

:bcd

for setting a buffer-local current directory.

Neovim now effectively supports:

global directory
tab-local directory
window-local directory
buffer-local directory

You can also unset scoped directories using:

:lcd!
:tcd!
:bcd!

gf and <cfile> now understand:

file://...

URIs.

You can open Neovim log files with:

:log
:uptime

shows how long the current Neovim process has been running.

This becomes slightly more interesting now that Neovim sessions can survive UI disconnections.

Two new visual text objects are available:

al
il

They select:

al → whole buffer
il → current line without surrounding whitespace

'scrollback' can now be used with prompt buffers to limit the amount of history kept above the prompt.

Neovim 0.12 introduced:

:restart

0.13 changes its behavior.

:restart now saves and restores the current session, including things such as:

  • buffers

  • windows

  • window layout

So:

:restart

becomes closer to restarting the editor without throwing away your working environment.

If you want the older behavior:

:restart!

restarts without saving and restoring the session.

There is also a new normal-mode command:

ZR

for restarting Neovim.

This one is more experimental, but technically very interesting.

Neovim can now be compiled into a WebAssembly binary using Zig:

zig build -Dtarget=wasm32-emscripten

That binary can run inside a web browser.

Neovim also includes a basic browser demo.

Conceptually:

Neovim
   │
   ▼
 WebAssembly
   │
   ▼
 Browser

This opens up some interesting possibilities:

  • browser-based Neovim

  • online development environments

  • documentation demos

  • educational environments

  • embedded editors

  • remote development interfaces

The feature is explicitly experimental, so I would not build anything important around it yet.

But architecturally, it is one of the more unusual additions in 0.13.

Several new events have been added.

CmdAtom

fires after an atomic user action.

That includes things such as:

  • motions

  • mappings

  • operators

  • insert sessions

  • Visual actions

  • Ex commands

This could be useful for plugins that need to understand editing activity without reacting to every individual keypress.

There are now:

TextPutPre
TextPutPost

events that fire before and after text is put.

Additional events include:

SessionWritePre
TabMoved
DirReadPost

DirReadPost is especially interesting for directory browsing plugins because it fires after a directory listing has been rendered, allowing the listing to be filtered or sorted.

Editing a local directory now opens it as:

filetype=directory

This provides a more standardized foundation for directory browsing.

The old zip plugin has also been replaced.

The new built-in:

zip

plugin is a read-only archive browser built on top of the same directory infrastructure.

If you need the old implementation:

:packadd old-zip

This seems to be part of a broader effort to make filesystem browsing a first-class primitive instead of relying entirely on legacy Vimscript plugins.

Several new options were added.

:set winpinned

prevents a window from being closed unless it is explicitly targeted.

This could be useful for persistent:

  • sidebars

  • terminals

  • AI chat windows

  • diagnostics views

  • documentation panes

This improves vertical cursor centering near the end of a file.

Controls how preview windows are displayed.

Controls aspects of the experimental ui2 message interface, including:

  • pager key

  • message timeout

Controls where vim.pack stores its lockfile.

A few smaller UI changes are also worth mentioning.

Floating window border highlights now combine correctly with a window-local Normal background instead of falling back to the global background.

Several built-in menus now delegate to:

vim.ui.select()

including:

:browse oldfiles
:recover
:tselect
z=

This means plugins that replace vim.ui.select() can provide a consistent picker interface for more built-in Neovim features.

There is also a new:

Dimmed

highlight group for UI elements that should be visually de-emphasized.

Because 0.13 changes some fairly old editor behavior, there are several things worth checking before upgrading.

If you rely on:

Q

to replay the most recently recorded register, you will need to remap it.

Q now controls multiple cursors.

Interactive Ex mode is no longer entered using:

gQ

gQ is now used as part of the multiple cursor workflow.

Interactive Ex mode can instead be entered using:

:exmode

or:

1q:

Interactive Ex mode is now implemented using the command-line window.

Non-interactive:

nvim -es

also behaves more like a script host and less like traditional POSIX Ex mode.

If you have shell scripts built around Neovim’s Ex behavior, test them.

The event:

BufModifiedSet

has been removed.

Use:

OptionSet

with pattern:

modified

instead.

This:

vim.opt.wildignore + '*.o' + '*.obj'

is no longer supported.

Use a table:

vim.opt.wildignore + {
  '*.o',
  '*.obj',
}

These are deprecated in favor of:

vim.api.nvim_win_resize()

which supports resizing from a specified anchor edge.

The behavior of:

current_line

for diagnostic virtual text and virtual lines is now tied to CursorHold.

Make sure 'updatetime' is reasonable if you rely on it.

Previously:

client.attached_buffers[buf]

contained a boolean.

It now stores the buffer’s:

languageId

string.

Plugin authors inspecting this table directly should update their assumptions.

Logs moved under:

stdpath('state') .. '/logs'

These Vimscript functions were removed:

ctxget()
ctxpop()
ctxpush()
ctxset()
ctxsize()

Use:

nvim_get_context()
nvim_load_context()

instead.

Here are a few things worth trying when testing the nightly.

vim.o.autoread = true

External file changes can now be detected through filesystem watchers.

vim.o.autocomplete = true
vim.o.completeopt = 'menu,menuone,preselect'

This allows the built-in completion stack to take advantage of LSP preselection.

Instead of creating a globally accessible function:

vim.wo.foldexpr = function()
  return vim.treesitter.foldexpr()
end
:bcd ~/projects/my-project

Before working over SSH:

:detach!

If the UI disappears unexpectedly, the Neovim process can continue running and another UI can reconnect later.

vim.wo.winpinned = true

Useful for a window you do not want normal window-management commands to accidentally close.

After upgrading to 0.13, you may be able to remove or rethink some existing plugins and custom code.

If Neovim’s built-in implementation covers your workflow, a dedicated multiple-cursor plugin may no longer be necessary.

Configs that rely on combinations of:

FocusGained
BufEnter
CursorHold
:checktime

just to detect external file changes may be able to become much simpler because 'autoread' now uses filesystem watchers.

Plugin authors may eventually be able to replace some custom coroutine, callback, semaphore, and cancellation infrastructure with:

vim.async

Plugins that implement their own terminal image abstraction may eventually be able to use:

vim.ui.img

instead.

Plugins with special CmdwinEnter / CmdwinLeave handling should review whether those workarounds are still necessary.

If you are already experimenting with vim.pack, the addition of:

:packupdate
:packdel

plus manifests and configurable lockfiles makes the built-in package manager more practical.

That does not mean every user should immediately remove lazy.nvim, but the gap continues to shrink for simpler configurations.

Neovim 0.12 was largely about making more IDE functionality available directly inside Neovim.

0.13 continues that direction, but it also starts changing some deeper parts of how the editor works.

The biggest practical additions are:

  1. native multiple cursors

  2. persistent and reconnectable Neovim sessions

  3. real-time filesystem watching

  4. vim.async for structured concurrency

  5. better built-in LSP completion

  6. continued vim.pack improvements

  7. better Treesitter performance

  8. image support through vim.ui.img

The combination I find most interesting is not any single feature.

It is this:

AI Agent
    │
    ├── modifies code
    │
    ▼
filesystem watcher
    │
    ▼
 Neovim process
    │
    ├── persistent session
    │
SSH connection disappears
    │
    ▼
 Neovim keeps running
    │
    ▼
 reconnect later

Neovim is becoming increasingly well suited to a development workflow where the editor is not the only thing modifying code and where the editor process itself may live on another machine.

That feels particularly relevant as remote development and coding agents become more common.

0.13 is still under development, so I would not rebuild a production configuration around every new API yet.

But there is already enough here to make the nightly worth experimenting with.

  • :help news

  • :help multicursor

  • :help vim.async

  • :help vim.ui.img

  • :help vim.pack

  • :help lsp-completion

  • :help cmdwin

  • :help autoread

  • :help :detach

  • :help :connect

  • :help deprecated-0.13