Home

Awesome

<h2 align="center"><img src="./images/icon.png" height="128"><br>VSCode Neovim</h2> <p align="center"><strong>VSCode Neovim Integration</strong></p> <p align=center> <a href="https://marketplace.visualstudio.com/items?itemName=asvetliakov.vscode-neovim"><img src="https://img.shields.io/visual-studio-marketplace/v/asvetliakov.vscode-neovim?color=%234c1&label=Visual%20Studio%20Marketplace"></a> <a href="https://github.com/asvetliakov/vscode-neovim/actions/workflows/build_test.yml"><img src="https://github.com/asvetliakov/vscode-neovim/workflows/Code%20Check%20&%20Test/badge.svg"></a> <a href="https://gitter.im/vscode-neovim/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge"><img src="https://badges.gitter.im/vscode-neovim/community.svg"></a> </p>

Neovim is a fork of Vim to allow greater extensibility and integration. This extension uses a fully embedded Neovim instance, no more half-complete Vim emulation! VSCode's native functionality is used for insert mode and editor commands, making the best use of both editors.

<strong>Table of Contents</strong>

🧰 Getting Started

Installation

Install the vscode-neovim extension.

Install Neovim 0.9.0 or greater (0.10 recommended).

Note: Though the extension strives to be as compatible as possible with older versions of Neovim, some older versions may have quirks that are not present anymore. In light of this, certain configuration settings are recommended in some older versions for the best experience. These can be found on the wiki.

[Optional] Set the Neovim path in the extension settings under "vscode-neovim.neovimExecutablePaths.win32/linux/darwin", respective to your system. For example, "C:\Neovim\bin\nvim.exe" or "/usr/local/bin/nvim".

Note: If you want to use Neovim from WSL, set the useWSL configuration toggle and specify the Linux path to the nvim binary. wsl.exe Windows binary and wslpath Linux binary are required for this. wslpath must be available through $PATH Linux env setting. Use wsl --list to check for the correct default Linux distribution.

Neovim configuration

Since many Vim plugins can cause issues in VSCode, it is recommended to start from an empty init.vim. For a guide for which types of plugins are supported, see troubleshooting.

Before creating an issue on Github, make sure you can reproduce the problem with an empty init.vim and no VSCode extensions.

To determine if Neovim is running in VSCode, add to your init.vim:

if exists('g:vscode')
    " VSCode extension
else
    " ordinary Neovim
endif

In lua:

if vim.g.vscode then
    -- VSCode extension
else
    -- ordinary Neovim
end

To conditionally activate plugins, the best solution is to use the LazyVim VSCode extra. However, packer.nvim and lazy.nvim have built-in support for cond = vim.g.vscode and vim-plug has a few solutions. See plugins in the wiki for tips on configuring Vim plugins.

VSCode Settings & Commands

You can view all available settings and commands by opening the vscode-neovim extension details pane, and navigating to the features tab.

💡 Tips and Features

VSCode specific differences

Troubleshooting

Performance

If you have any performance problems (cursor jitter usually) make sure you're not using vim plugins that increase latency and cause performance problems.

Make sure to disable unneeded plugins, as many of them don't make sense with VSCode. Specifically, you don't need any code highlighting, completion, LSP plugins, or plugins that spawn windows/buffers (nerdtree , fuzzy-finders, etc). Most navigation/textobject/editing plugins should be fine.

For example, make sure you're not using anything that renders decorators very often:

If you're not sure, disable all other extensions, reload VSCode window, and see if the problem persists before reporting it.

Composite escape keys

Set with compositeKeys and tweak with compositeTimeout.

Examples: add to your settings.json:

<kbd>jj</kbd> to escape

{
    "vscode-neovim.compositeKeys": {
        "jj": {
            "command": "vscode-neovim.escape",
        },
    },
}

<kbd>jk</kbd> to escape and save

{
    "vscode-neovim.compositeKeys": {
        "jk": {
            // Use lua to execute any logic
            "command": "vscode-neovim.lua",
            "args": [
                [
                    "local code = require('vscode')",
                    "code.action('vscode-neovim.escape')",
                    "code.action('workbench.action.files.save')",
                ],
            ],
        },
    },
}

Jumplist

VSCode's jumplist is used instead of Neovim's. This is to make VSCode native navigation (mouse click, jump to definition, etc) navigable through the jumplist.

Make sure to bind to workbench.action.navigateBack / workbench.action.navigateForward if you're using custom mappings. Marks (both upper & lowercased) should work fine.

Multiple cursors

Multiple cursors work in:

  1. Insert mode
  2. Visual line mode
  3. Visual block mode

To spawn multiple cursors from visual line/block modes type <kbd>ma</kbd>/<kbd>mA</kbd> or <kbd>mi</kbd>/<kbd>mI</kbd> (by default). The effect differs:

See gif in action:

multicursor

Note: The built-in multi-cursor support may not meet your needs. Please refer to the plugin vscode-multi-cursor.nvim for more multi-cursor features

Remote development

We intend to use vscode-neovim as a UI extension, so when you're using remote development, vscode-neovim is enabled in the Local Extension Host, and it should work out of the box.

If you prefer to use the remote environment's copy of Neovim, rather than the locally installed one, vscode-neovim should be installed in the Remote Extension Host. You can set the following in your VSCode settings.json:

{
    "remote.extensionKind": {
        "asvetliakov.vscode-neovim": ["workspace"]
    }
}

Note: You will need to install neovim in the remote environment.

For more information:

⚡️ API

Load the module:

local vscode = require('vscode')
<!-- prettier-ignore-start -->

[!TIP] The previously used module named "vscode-neovim" is now deprecated, so don't be confused if "vscode-neovim" is used in old discussions or other resources.

<!-- prettier-ignore-end -->
  1. vscode.action(): asynchronously executes a vscode command.
  2. vscode.call(): synchronously executes a vscode command.
  3. vscode.on(): defines a handler for some Nvim UI events.
  4. vscode.has_config(): checks if a vscode setting exists.
  5. vscode.get_config(): gets a vscode setting value.
  6. vscode.update_config(): sets a vscode setting.
  7. vscode.notify(): shows a vscode message (see also Nvim's vim.notify).
  8. vscode.to_op(): A helper for map-operator. See code_actions.lua for the usage
  9. g:vscode_clipboard: Clipboard provider using VSCode's clipboard API. Used by default when in WSL. See :h g:clipboard for more details. Usage: vim.g.clipboard = vim.g.vscode_clipboard
  10. vscode.eval(): evaluate javascript synchronously in vscode and return the result
  11. vscode.eval_async(): evaluate javascript asynchronously in vscode
  12. vscode.with_insert(): perform operations in insert mode.

vscode.action(name, opts)

Asynchronously executes a vscode command.

Parameters:

Example: open definition aside (default binding):

nnoremap <C-w>gd <Cmd>lua require('vscode').action('editor.action.revealDefinitionAside')<CR>

Example: find in files for word under cursor (see the vscode command definition for the expected parameter format):

nnoremap ? <Cmd>lua require('vscode').action('workbench.action.findInFiles', { args = { query = vim.fn.expand('<cword>') } })<CR>

Example: use in lua script:

-- Format current document
vscode.action("editor.action.formatDocument")

do -- Comment the three lines below the cursor
  local curr_line = vim.fn.line(".") - 1  -- 0-indexed
  vscode.action("editor.action.commentLine", {
    range = { curr_line + 1, curr_line + 3 },
  })
end

do -- Comment the previous line
  local curr_line = vim.fn.line(".") - 1 -- 0-indexed
  local prev_line = curr_line - 1
  if prev_line >= 0 then
    vscode.action("editor.action.commentLine", {
      range = { prev_line , prev_line },
    })
  end
end

do -- Find in files for word under cursor
  vscode.action("workbench.action.findInFiles", {
    args = { query = vim.fn.expand('<cword>') }
  })
end

Currently, two built-in actions are provided for testing purposes:

  1. _ping returns "pong"
  2. _wait waits for the specified milliseconds and then returns "ok"
do -- Execute _ping asynchronously and print the result
  vscode.action("_ping", {
    callback = function(err, res)
      if err == nil then
        print(res) -- outputs: pong
      end
    end,
  })
end

vscode.call(name, opts, timeout)

Synchronously executes a vscode command.

Parameters:

Returns: the result of the action

Example: format selection (default binding):

xnoremap = <Cmd>lua require('vscode').call('editor.action.formatSelection')<CR>
nnoremap = <Cmd>lua require('vscode').call('editor.action.formatSelection')<CR><Esc>
nnoremap == <Cmd>lua require('vscode').call('editor.action.formatSelection')<CR>

Example: use in lua script:

-- Execute _ping synchronously and print the result
print(vscode.call("_ping")) -- outputs: pong

-- Wait for 1 second and print the return value 'ok'
print(vscode.call("_wait", { args = { 1000 } })) -- outputs: ok

-- Wait for 2 seconds with a timeout of 1 second
print(vscode.call("_wait", { args = { 2000 } }), 1000)
-- error: Call '_wait' timed out

vscode.on(event, callback)

Currently no available events for user use.

vscode.has_config(name)

Check if configuration has a certain value.

Parameters:

Returns:

Examples:

-- Check if the configuration "not.exist" exists
print(vscode.has_config("not.exist"))
-- Should return: false

-- Check multiple configurations
vim.print(vscode.has_config({ "not.exist", "existing.config" }))
-- Should return: { false, true }

vscode.get_config(name)

Get configuration value.

Parameters:

Returns:

Examples:

-- Get the value of "editor.tabSize"
print(vscode.get_config("editor.tabSize")) -- a number

-- Get multiple configurations
vim.print(vscode.get_config({ "editor.fontFamily", "editor.tabSize" }))
-- Should return: { "the font family", "the editor tabSizse" }

vscode.update_config(name, value, target)

Update configuration value.

Parameters:

Examples:

-- Update the value of "editor.tabSize"
vscode.update_config("editor.tabSize", 16, "global")

-- Update multiple configurations
vscode.update_config({ "editor.fontFamily", "editor.tabSize" }, { "Fira Code", 14 })

vscode.notify(msg)

Show a vscode notification

You can set vscode.notify as your default notify function.

vim.notify = vscode.notify

vscode.eval(code[, opts, timeout])

Evaluate javascript inside vscode and return the result. The code is executed in an async function context (so await can be used). Use a return statement to return a value back to lua. Arguments passed from lua are available as the args variable. The evaluated code has access to the VSCode API through the vscode global.

Tips:

Parameters:

Returns:

Examples:

local current_file = vscode.eval("return vscode.window.activeTextEditor.document.fileName")
local current_tab_is_pinned = vscode.eval("return vscode.window.tabGroups.activeTabGroup.activeTab.isPinned")
vscode.eval("await vscode.env.clipboard.writeText(args.text)", { args = { text = "some text" } })

vscode.eval_async(code[, opts])

Like vscode.eval() but returns immediately and evaluates in the background instead.

Parameters:

vscode.with_insert(callback)

Perform operations in insert mode. If in visual mode, this function will preserve the selection after switching to insert mode.

Parameters:

Example: make editor.action.addSelectionToNextFindMatch work correctly in any mode.

vim.keymap.set({ "n", "x", "i" }, "<C-d>", function()
  vscode.with_insert(function()
    vscode.action("editor.action.addSelectionToNextFindMatch")
  end)
end)

select-next

Example: make "editor.action.refactor" work correctly on the selection and support snippet manipulation after entering VSCode snippet mode.

vim.keymap.set({ "n", "x" }, "<leader>r", function()
  vscode.with_insert(function()
    vscode.action("editor.action.refactor")
  end)
end)

refactor

Builtin module overrides

  1. vim.ui: use VSCode's UI components.
  2. vim.lsp.buf: execute corresponding VSCode LSP commands.

VimScript

Note: Since 1.0.0, vimscript functions are deprecated. Use the Lua api instead.

You can also use v:lua.require("vscode") to access the API from VimScript.

⌨️ Keybindings (shortcuts)

There are three types of default/user keybindings in vscode-neovim:

This document only mentions some special cases, it is not an exhaustive list of keybindings and commands. Use VSCode and Nvim features to see documentation and all defined shortcuts:

Keybinding Passthroughs

Every special (control/alt/non-alphanumerical) keyboard shortcut must be explicitly defined in VSCode to send to neovim. By default, only bindings that are used by Neovim by default are sent.

Note: if you want to pass additional control keys without adding a custom passthrough, see below.

To add a custom passthrough, for example <kbd>A-h</kbd> in normal mode, add to your keybindings.json:

{
    "command": "vscode-neovim.send",
    // the key sequence to activate the binding
    "key": "alt+h",
    // don't activate during insert mode
    "when": "editorTextFocus && neovim.mode != insert",
    // the input to send to Neovim
    "args": "<A-h>",
}

Insert mode control keys passthrough

Set by vscode-neovim.ctrlKeysForInsertMode.

Default: ["a", "d", "h", "j", "m", "o", "r", "t", "u", "w"]

Normal mode control keys passthrough

Set by ctrlKeysForNormalMode.

Default: ["a", "b", "d", "e", "f", "h", "i", "j", "k", "l", "m", "o", "r", "t", "u", "v", "w", "x", "y", "z", "/", "]"]

Cmdline mode special keys passthrough

Always enabled.

Disable passthrough for certain filetypes

Set by editorLangIdExclusions.

Disable keybindings defined by this extension in certain filetypes. Please note that this will not affect all keybindings.

Remove other vscode or passthrough keybindings

If the above configuration flags do not provide enough control, you can remove the keybindings by editing your keybindings.json or using the VSCode keybindings editor:

remove keybindings

Code navigation bindings

KeyVSCode Command
<kbd>=</kbd> / <kbd>==</kbd>editor.action.formatSelection
<kbd>gh</kbd> / <kbd>K</kbd>editor.action.showHover
<kbd>gd</kbd> / <kbd>C-]</kbd>editor.action.revealDefinition <br/> Also works in vim help.
<kbd>gf</kbd>editor.action.revealDeclaration
<kbd>gH</kbd>editor.action.referenceSearch.trigger
<kbd>gO</kbd>workbench.action.gotoSymbol
<kbd>C-w</kbd> <kbd>gd</kbd> / <kbd>C-w</kbd> <kbd>gf</kbd>editor.action.revealDefinitionAside
<kbd>gD</kbd>editor.action.peekDefinition
<kbd>gF</kbd>editor.action.peekDeclaration
<kbd>Tab</kbd>togglePeekWidgetFocus <br/> Switch between peek editor and reference list.
<kbd>C-n</kbd> / <kbd>C-p</kbd>Navigate lists, parameter hints, suggestions, quick-open, cmdline history, peek reference list

💡 To specify the default peek mode, modify editor.peekWidgetDefaultFocus in your settings.

Explorer/list navigation bindings

KeyVSCode Command
<kbd>j</kbd> or <kbd>k</kbd>list.focusDown/Up
<kbd>h</kbd> or <kbd>l</kbd>list.collapse/select
<kbd>Enter</kbd>list.select
<kbd>gg</kbd>list.focusFirst
<kbd>G</kbd>list.focusLast
<kbd>o</kbd>list.toggleExpand
<kbd>C-u</kbd> or <kbd>C-d</kbd>list.focusPageUp/Down
<kbd>zo</kbd> or <kbd>zO</kbd>list.expand
<kbd>zc</kbd>list.collapse
<kbd>zC</kbd>list.collapseAllToFocus
<kbd>za</kbd> or <kbd>zA</kbd>list.toggleExpand
<kbd>zm</kbd> or <kbd>zM</kbd>list.collapseAll
<kbd> / </kbd> or <kbd>Escape</kbd>list.toggleKeyboardNavigation

Explorer file manipulation bindings

KeyVSCode Command
<kbd>r</kbd>renameFile
<kbd>d</kbd>deleteFile
<kbd>y</kbd>filesExplorer.copy
<kbd>x</kbd>filesExplorer.cut
<kbd>p</kbd>filesExplorer.paste
<kbd>v</kbd>explorer.openToSide
<kbd>a</kbd>explorer.newFile
<kbd>A</kbd>explorer.newFolder
<kbd>R</kbd>workbench.files.action.refreshFilesExplorer

Hover widget manipulation bindings

KeyVSCode Command
<kbd>K</kbd>editor.action.showHover
<kbd>h</kbd>editor.action.scrollLeftHover
<kbd>j</kbd>editor.action.scrollDownHover
<kbd>k</kbd>editor.action.scrollUpHover
<kbd>l</kbd>editor.action.scrollRightHover
<kbd>gg</kbd>editor.action.goToTopHover
<kbd>G</kbd>editor.action.goToBottomHover
<kbd>C-f</kbd>editor.action.pageDownHover
<kbd>C-b</kbd>editor.action.pageUpHover

📟 Neovim Commands

Default commands and bindings are available for file/scroll/window/tab management.

File management

See vscode-file-commands.vim for file commands reference.

The extension aliases various Nvim commands (:edit, :enew, :find, :quit, etc.) to equivalent vscode commands. Also their normal-mode equivalents (where applicable) such as <kbd>C-w q</kbd>, etc.

Tab management

See vscode-tab-commands.vim for tab commands reference.

The extension aliases various Nvim tab commands (:tabedit, :tabnew, :tabfind, :tabclose, :tabnext, :tabprevious, :tabfirst, :tablast) to equivalent vscode commands. Also their normal-mode equivalents (where applicable) such as <kbd>gt</kbd>, etc.

Buffer/window management

See vscode-window-commands.vim for file commands reference.

The extension aliases various Nvim buffer/window commands (:split, :vsplit, :new, :vnew, :only) to equivalent vscode commands. Also their normal-mode equivalents (where applicable) such as <kbd>C-w s</kbd>, etc.

💡 Split size distribution is controlled by workbench.editor.splitSizing setting. By default, it's distribute, which is equal to vim's equalalways and eadirection = 'both' (default).

To use VSCode command 'Increase/decrease current view size' instead of separate bindings for width and height:

<details> <summary>Copy this into init.vim</summary>
function! s:manageEditorSize(...)
    let count = a:1
    let to = a:2
    for i in range(1, count ? count : 1)
        call VSCodeNotify(to ==# 'increase' ? 'workbench.action.increaseViewSize' : 'workbench.action.decreaseViewSize')
    endfor
endfunction

" Sample keybindings. Note these override default keybindings mentioned above.
nnoremap <C-w>> <Cmd>call <SID>manageEditorSize(v:count, 'increase')<CR>
xnoremap <C-w>> <Cmd>call <SID>manageEditorSize(v:count, 'increase')<CR>
nnoremap <C-w>+ <Cmd>call <SID>manageEditorSize(v:count, 'increase')<CR>
xnoremap <C-w>+ <Cmd>call <SID>manageEditorSize(v:count, 'increase')<CR>
nnoremap <C-w>< <Cmd>call <SID>manageEditorSize(v:count, 'decrease')<CR>
xnoremap <C-w>< <Cmd>call <SID>manageEditorSize(v:count, 'decrease')<CR>
nnoremap <C-w>- <Cmd>call <SID>manageEditorSize(v:count, 'decrease')<CR>
xnoremap <C-w>- <Cmd>call <SID>manageEditorSize(v:count, 'decrease')<CR>
</details>

🎨 Highlights

There are two ways to customize highlight colors:

  1. Set colors in nvim

    Note: Due to the support for the syntax option requiring processing of syntax highlights, all built-in highlight groups may be overridden or cleared. Therefore, please do not link any highlights to the built-in highlight groups.

  2. Set colors in vscode

🧰 Developing

Please see CONTRIBUTING.md for details on how to contribute to this project.

❤️ Credits & External Resources