| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Name | Name | Last commit date | ||
|---|---|---|---|---|
中文 | English
Project site: https://fangpin.github.io/remote-nvim.nvim/ Detailed docs: https://fangpin.github.io/remote-nvim.nvim/docs/
Adds support for remote development and devcontainers to Neovim (just like VSCode). Read in the FAQ at the end of this document why you would prefer using remote-nvim instead of SSH into remote + local neovim.
| Remote mode | Current support |
|---|---|
| SSH (using password) | Fully supported ✅ |
| SSH (using SSH key) | Fully supported ✅ |
| SSH (using ssh_config file) | Fully supported ✅ |
| Docker image 1 | Fully supported ✅ |
| Docker container 1 | Fully supported ✅ |
| Devcontainer 1 | Fully supported ✅ |
See #126 for the list of planned but not yet implemented features.
| Support level | OS |
|---|---|
| ✅ Supported | Linux, MacOS, FreeBSD 2 |
| 🟡 Not supported yet | Windows, WSL |
Connectivity to neovim repo on GitHub is not needed when using Offline mode (No GitHub) but it comes with it's own trade offs.
Connectivity to neovim repo on GitHub is not needed when using Offline mode.
Using lazy.nvim
{
"fangpin/remote-nvim.nvim",
version = "*", -- Pin to GitHub releases
dependencies = {
"nvim-lua/plenary.nvim", -- For standard functions
"MunifTanjim/nui.nvim", -- To build the plugin UI
"nvim-telescope/telescope.nvim", -- For picking b/w different remote methods
},
config = true,
}Ensure you have devpod >= 0.5.0 installed for any devcontainer-related features to work
If you use any other plugin manager, ensure that you call require("remote-nvim").setup().
Note
Run :checkhealth remote-nvim to ensure necesssary binaries are available. If missing, parts of the plugin might be broken.
Below is the default configuration. Set only things that you wish to change in your setup() call. Please read the associated comments before changing the value.
{
-- Configuration for devpod connections
devpod = {
binary = "devpod", -- Binary to use for devpod
docker_binary = "docker", -- Binary to use for docker-related commands
---@diagnostic disable-next-line:param-type-mismatch
ssh_config_path = utils.path_join(utils.is_windows, vim.fn.stdpath("data"), constants.PLUGIN_NAME, "ssh_config"), -- Path where devpod SSH configurations should be stored
search_style = "current_dir_only", -- How should devcontainers be searched
-- For dotfiles, see https://devpod.sh/docs/developing-in-workspaces/dotfiles-in-a-workspace for more information
dotfiles = {
path = nil, -- Path to your dotfiles which should be copied into devcontainers
install_script = nil -- Install script that should be called to install your dotfiles
},
gpg_agent_forwarding = false, -- Should GPG agent be forwarded over the network
container_list = "running_only", -- How should docker list containers ("running_only" or "all")
},
-- Configuration for SSH connections
ssh_config = {
ssh_binary = "ssh", -- Binary to use for running SSH command
rsync_binary = "rsync", -- Binary to use for running rsync copy commands
ssh_config_file_paths = { "$HOME/.ssh/config" }, -- Which files should be considered to contain the ssh host configurations. NOTE: `Include` is respected in the provided files.
-- These are useful for password-based SSH authentication.
-- It provides parsing pattern for the plugin to detect that an input is requested.
-- Each element contains the following attributes:
-- match - The string to match (plain matching is done)
-- type - Supports two values "plain"|"secret". Secret means when you provide the value, it should not be stored in the completion history of Neovim.
-- value - Default value for the prompt
-- value_type - "static"|"dynamic". For things like password, it would be needed for each new connection that the plugin initiates which could be obtrusive.
-- So, we save the value (only for current session's interval) to ease the process. If set to "dynamic", we do not save the value even for the session. You have to provide a fresh value each time.
ssh_prompts = {
{
match = "password:",
type = "secret",
value_type = "static",
value = "",
},
{
match = "continue connecting (yes/no/[fingerprint])?",
type = "plain",
value_type = "static",
value = "",
},
-- There are other values here which can be checked in lua/remote-nvim/init.lua
},
},
-- Path to the script that would be copied to the remote and called to ensure that neovim gets installed.
-- Default path is to the plugin's own ./scripts/neovim_install.sh file.
neovim_install_script_path = utils.path_join(
utils.is_windows,
vim.fn.fnamemodify(debug.getinfo(1).source:sub(2), ":h:h:h"),
"scripts",
"neovim_install.sh"
),
-- Modify the UI for the plugin's progress viewer.
-- type can be "split" or "popup". All options from https://github.com/MunifTanjim/nui.nvim/tree/main/lua/nui/popup and https://github.com/MunifTanjim/nui.nvim/tree/main/lua/nui/split are supported.
-- Note that some options like "border" are only available for "popup".
progress_view = {
type = "popup",
},
-- Offline mode configuration. For more details, see the "Offline mode" section below.
offline_mode = {
-- Should offline mode be enabled?
enabled = false,
-- Do not connect to GitHub at all. Not even to get release information.
no_github = false,
-- What path should be looked at to find locally available releases
cache_dir = utils.path_join(utils.is_windows, vim.fn.stdpath("cache"), constants.PLUGIN_NAME, "version_cache"),
},
-- Remote configuration
remote = {
app_name = "nvim", -- This directly maps to the value NVIM_APPNAME. If you use any other paths for configuration, also make sure to set this.
reconnect = {
enabled = false, -- Automatically relaunch the remote Neovim session if the SSH job exits unexpectedly.
max_attempts = 5, -- Maximum reconnect attempts for a single unexpected disconnect.
backoff_ms = 2000, -- Base delay between reconnect attempts. The delay increases by this value on each attempt.
},
detach = {
enabled = false, -- Launch SSH remote servers in detached mode so :RemoteDetach/:RemoteReattach can keep the server alive.
},
-- List of directories that should be copied over
copy_dirs = {
-- What to copy to remote's Neovim config directory
config = {
base = vim.fn.stdpath("config"), -- Path from where data has to be copied
dirs = "*", -- Directories that should be copied over. "*" means all directories. To specify a subset, use a list like {"lazy", "mason"} where "lazy", "mason" are subdirectories
-- under path specified in `base`.
compression = {
enabled = false, -- Should compression be enabled or not
additional_opts = {} -- Any additional options that should be used for compression. Any argument that is passed to `tar` (for compression) can be passed here as separate elements.
},
},
-- What to copy to remote's Neovim data directory
data = {
base = vim.fn.stdpath("data"),
dirs = {},
compression = {
enabled = true,
},
},
-- What to copy to remote's Neovim cache directory
cache = {
base = vim.fn.stdpath("cache"),
dirs = {},
compression = {
enabled = true,
},
},
-- What to copy to remote's Neovim state directory
state = {
base = vim.fn.stdpath("state"),
dirs = {},
compression = {
enabled = true,
},
},
},
},
-- You can supply your own callback that should be called to create the local client. The default implementation
-- starts a terminal remote UI with `nvim --remote-ui`.
-- Two arguments are passed to the callback:
-- port: Local port at which the remote server is available
-- workspace_config: Workspace configuration for the host. For all the properties available, see https://github.com/amitds1997/remote-nvim.nvim/blob/main/lua/remote-nvim/providers/provider.lua#L4
-- A sample implementation using WezTerm tab is at: https://github.com/amitds1997/remote-nvim.nvim/wiki/Configuration-recipes
client_callback = function(port, _)
require("remote-nvim.ui").float_term(("nvim --server localhost:%s --remote-ui"):format(port), function(exit_code)
if exit_code ~= 0 then
vim.notify(("Local client failed with exit code %s"):format(exit_code), vim.log.levels.ERROR)
end
end)
end,
-- Plugin log related configuration [PREFER NOT TO CHANGE THIS]
log = {
-- Where is the log file
filepath = utils.path_join(utils.is_windows, vim.fn.stdpath("state"), ("%s.log"):format(constants.PLUGIN_NAME)),
-- Level of logging
level = "info",
-- At what size, should we truncate the logs
max_size = 1024 * 1024 * 2, -- 2MB
},
}Tip
By default, this plugin launches your remote neovim client in a popup window. This mostly works fine. For a better experience though, it is recommended that you add a custom callback to launch your Neovim client in a separate tab/window for your terminal or GUI app.
This continues from the How to connect to saved host using SSH config file demo above.
RemoteStop.mp4Alternatively, just exit from the Neovim instance using which you launched the server.
Get information about any Remote Neovim launched session RemoteInfo.mp4 Delete this plugin's created resources from the remote machineThis deletes the resources created during the How to connect to saved host using SSH config file demo above.
RemoteCleanup.mp4 Delete saved configuration about a remote hostWe disabled connectivity to the host we connected to in How to connect to SSH server with password based auth to replicate this scenario.
RemoteConfigDel.mp4 Youtube video going over an older version of the plugin| Command | What does it do? |
|---|---|
| :RemoteStart | Connect to a remote instance. If remote neovim server is already running, allows users to launch local client? |
| :RemoteStop | Stop running Neovim server and close session |
| :RemoteInfo | Get information and Runtime diagnostics about any sessions created in the current Neovim run. Opens up the Progress Viewer. |
| :RemoteCleanup | Delete workspace and/or entire remote neovim setup from the remote instance. Also, cleanups the configuration for the remote resource. |
| :RemoteConfigDel | Delete record of remote instance that no longer exists from saved session records. Prefer :RemoteCleanup if you can still connect to the remote instance. |
| :RemoteLog | Open the plugin log file. This is most useful when debugging. :RemoteInfo should surface all information needed. If not, open an issue. |
| :RemoteClipboardCheck | Diagnose clipboard setup for an active remote session, including OSC 52 availability, provider state, and attached UI count. |
| :RemoteDetach | Detach an active SSH session while leaving the remote Neovim server running. |
| :RemoteReattach | Reconnect to a detached SSH session after validating the remote server. |
| :RemoteKillDetached | Kill or clear a detached SSH session record. |
For demos about the commands, see the demos section.
When creating an SSH workspace, remote-nvim asks for an optional remote working directory. If provided, the value is saved in the workspace record and reused on later :RemoteStart runs. The remote headless Neovim server is launched with that directory as its current directory, which helps tools like language servers discover project files such as go.mod.
Leave the prompt blank to keep the remote shell's default startup directory, usually $HOME.
remote.reconnect can automatically relaunch the current workspace when the remote Neovim SSH job exits unexpectedly, for example after a transient network drop. It does not trigger when you explicitly stop the session with :RemoteStop.
require("remote-nvim").setup({
remote = {
reconnect = {
enabled = true,
max_attempts = 5,
backoff_ms = 2000,
},
},
})This is a best-effort reconnect: the plugin relaunches the workspace and local client. It does not yet keep the remote Neovim server alive independently from the SSH tunnel.
SSH sessions can be detached explicitly with :RemoteDetach after enabling remote.detach.enabled. Detach stops the local forwarding job and client, but keeps the remote headless Neovim server running. Use :RemoteReattach to validate the saved PID and remote port, recreate the local tunnel, and launch the local client again.
Detached sessions are stored under stdpath("data")/remote-nvim/detached.json. If validation fails, the record is marked stale. Use :RemoteKillDetached to kill a detached server or clear a stale record.
Detach is SSH-only in this release. Devpod, Docker image, and Docker container sessions report a clear unsupported-provider warning.
The default local client is nvim --remote-ui, even when :RemoteStart is run from Neovide. This keeps the terminal UI behavior for scrolling, fonts, and rendering while still allowing remote yanks to reach the local clipboard.
To make that work, the remote server installs a small startup hook that uses a copy-only OSC 52 clipboard provider for terminal UI clients. This means yanking with yy, visual mode y, or any operation that writes to the + register can update the local clipboard without requiring clipboard tools on the remote host. Paste falls back to the most recent cached yank instead of querying the terminal clipboard, since many terminals do not support OSC 52 reads. The startup hook runs during startup and UI attach, then retries a few times so delayed user configuration and lazy-loaded plugins do not accidentally leave the remote server without the OSC 52 provider.
When the default client is launched from Neovide, remote-nvim also reloads the local Neovide clipboard provider before opening the nested terminal UI. This lets OSC 52 writes from the remote UI flow through the local terminal buffer into Neovide's system clipboard bridge.
For terminal clients without an existing local clipboard provider, remote-nvim installs a local OSC 52 fallback before opening the nested terminal UI. This lets Neovim's terminal buffer handle OSC 52 writes from the remote UI and forward them to terminals such as Warp that support OSC 52 clipboard writes.
If you override client_callback to launch a GUI client such as Neovide, the startup hook preserves Neovide's clipboard provider when it is available.
After connecting to a remote session, run :RemoteClipboardCheck. If multiple sessions are active, choose one from the picker or pass the host ID directly:
:RemoteClipboardCheck my-hostA healthy terminal-client setup should report:
To verify manually, yank text in the remote UI with yy or "+y. The text should appear in your local system clipboard. If diagnostics look healthy but the clipboard does not update, check whether your terminal or terminal multiplexer allows OSC 52 clipboard writes. In tmux, for example, enable clipboard forwarding with:
set -g set-clipboard onIf :RemoteClipboardCheck reports missing copy functions, missing OSC 52 support, or no attached UIs, restart the remote session so the startup hook can run again and check :RemoteLog for setup errors.
On repeated :RemoteStart runs, remote-nvim checks whether the selected remote Neovim binary is already installed and runnable. If it is, the plugin skips the chmod/install script path and goes straight to launching the remote server. Script checksum checks and configured Neovim config sync still run, so fresh hosts and local config changes continue to work.
The plugin sets the variable vim.g.remote_neovim_host to true on the remote Neovim instance. This can be used to add useful information regarding the remote system to your statusline.
Here's an example about adding a component in lualine.nvim to show the remote hostname when connected to a remote instance.
lualine_b = {
..., -- other components
{
function()
return vim.g.remote_neovim_host and ("Remote: %s"):format(vim.uv.os_gethostname()) or ""
end,
padding = { right = 1, left = 1 },
separator = { left = "", right = "" },
},
..., -- other componenents
}There are two types of offline modes available:
The plugin connects to neovim/neovim repo on GitHub twice. First time, it tries to fetch the latest releases available for Neovim that can be installed to the remote. The second time, on the remote machine, it connects to download the Neovim release.
On enabling this, Neovim release will be downloaded locally and then copied over to the remote. Plugin would connect to GitHub once to get the list of Neovim versions available. To enable this,
require("remote-nvim").setup({
-- Add your other configuration parameters as usual
offline_mode = {
enabled = true,
no_github = false,
},
})On enabling this, GitHub will not be connected with at all. This is useful for scenarions when you face connection issues with GitHub. This is an advanced scenario so make sure that you actually need it.
It assumes that you already have Neovim releases available locally along with their checksum files. Note that, release names are expected to follow a certain pattern. So, please use the provided script to download releases and drop them in the cache directory where the plugin would read from. If no releases are available, the plugin would not be able to proceed further.
Steps for downloading releases: This command is run from the plugin's root. You can run it from anywhere as long as you have the correct path to the script. Adjust script path as per where the plugin gets installed on your system. Alternatively, you can also clone the repo at a separate location and run this script from inside the cloned repo.
./scripts/neovim_download.sh -v <version> -d <cache-dir> -o <os-type> -a <arch-type> -t <release-type>
# <version> can be stable, nightly or any Neovim release provided like v0.9.4
# <cache-dir> is the path in which the Neovim release and it's checksum should be downloaded. This should be same as the cache_dir plugin configuration value else it won't be
# detected by the plugin. See configuration below.
# <os-type> specifies which OS's binaries should be downloaded. Supported values are "Linux" and "macOS"
# <arch-type> is the host's architecture. Can be `x86_64` or `arm64`
# <release-type> is type of release to download. Can be `binary` or `source`To enable this,
require("remote-nvim").setup({
-- Add your other configuration parameters as usual
offline_mode = {
enabled = true,
no_github = true,
-- Add this only if you want to change the path where the Neovim releases are downloaded/located.
-- Default location is the output of :lua= vim.fn.stdpath("cache") .. "/remote-nvim.nvim/version_cache"
-- cache_dir = <custom-path>,
},
})Above process would prevent the plugin (remote-nvim.nvim) from connecting to GitHub, but nothing is stopping the plugins defined in your configuration from connecting to the internet. To prevent this, you can copy your other Neovim directories onto the remote to prevent at least your plugin manager from doing so since all your dependencies would already be in their right locations. Note: some plugins such as nvim-treesitter might still connect to the internet and there is nothing this plugin can do to restrict that (and neither does this plugin aim to do that). In such cases, you have 3 alternatives:
To turn off the plugin only on remote instances, one simple condition would be to check if Neovim is running in headless mode (That's how this plugin launches your remote neovim instance).
With that out of the way, let's focus on how you can copy additional Neovim directories onto remote.
require("remote-nvim").setup({
remote = {
copy_dirs = {
data = {
base = vim.fn.stdpath("data"), -- Path from where data has to be copied. You can choose to copy entire path or subdirectories inside using `dirs`
dirs = { "lazy" }, -- Directories inside `base` to copy over. If this is set to string "*"; it means entire `base` should be copied over
compression = {
enabled = true, -- Should data be compressed before uploading
additional_opts = { "--exclude-vcs" }, -- Any arguments that can be passed to `tar` for compression can be specified here to improve your compression
},
},
-- cache = {
-- base = vim.fn.stdpath("cache"),
-- dirs = {},
-- compression = {
-- enabled = true,
-- },
-- },
-- state = {
-- base = vim.fn.stdpath("state"),
-- dirs = {},
-- compression = {
-- enabled = true,
-- },
-- },
},
},
})The above configuration indicates that the lazy directory inside your Neovim data directory should be copied over onto the remote in it's data directory. You can similarly specify what should be copied inside the data, state, cache or config directory on remote.
If specified directories are going to contain a lot of data, it's highly recommended to enable compression when uploading by setting compression.enabled to true for those particular uploads.
remote-nvim runs commands on the remote host over SSH and launches a headless Neovim server from that remote command environment. This is usually a non-interactive, non-login shell, so interactive startup files such as ~/.zshrc are not guaranteed to be loaded.
Put binary PATH setup that remote Neovim needs in a startup file that is loaded for non-interactive shells. For example, if zsh is your remote login shell and fzf is installed under ~/.fzf/bin, put only the path setup in ~/.zshenv:
export PATH="$HOME/.fzf/bin:$PATH"Keep fzf shell integration, key bindings, and completion setup in ~/.zshrc. Those parts are for interactive shell sessions; remote-nvim only needs the fzf executable to be visible to the remote headless Neovim process.
This plugins provide some additional nice-to have features on top:
You can read more in this Neovim discussion
A big thank you to the amazing Neovim community for Neovim and the plugins! ❤️
| Back | FazBrowse Home | New Git URL |