Neovim plugin for lspcontainers - developed weekly live at The Alt-F4 Stream on Twitch.
IMPORTANT: everything below is a work-in-progress and subject to change at any time
Provide a simple method for running language servers in Docker containers using neovim/nvim-lspconfig. This plugin expects the same language server names from neovim/nvim-lspconfig. See neovim/nvim-lspconfig/doc/server_configurations.md for a complete list of servers.
-
Install latest Docker Engine for your operating system
-
Install
lspconfigandlspcontainersvia package manager
-
via
packermanageruse 'neovim/nvim-lspconfig' use 'lspcontainers/lspcontainers.nvim'
-
via
plugmanagerPlug 'neovim/nvim-lspconfig' Plug 'lspcontainers/lspcontainers.nvim'
- Setup the language of your choice from Supported LSPs
You can add configurations to more languages by providing an image and the command to start the container by adding the following options to lspcontainers commands:
NOTE: LspContainers makes no attempt to modify LspConfig. It is up to the end user to correctly configure LspConfig.
lspconfig.html.setup {
on_attach = on_attach,
capabilities = capabilities,
cmd = lspcontainers.command('html', {
image = "lspcontainers/html-language-server:1.4.0",
cmd = function (runtime, volume, image)
return {
runtime,
"container",
"run",
"--interactive",
"--rm",
"--volume",
volume,
image
}
end,
}),
root_dir = require'lspconfig/util'.root_pattern(".git", vim.fn.getcwd()),
}The cmd option of the lspcontainers config also allows modification of the command that is used to start the container for possibly mounting additional volumes or similar things.
In some circumstances a language server may need the root_dir path synced with the Docker container. To sync up the volume mount with the lspconfig's root_dir, use on_new_config:
local server = "lua_ls"
require'lspconfig'[server].setup{
on_new_config = function(new_config, new_root_dir)
new_config.cmd = require'lspcontainers'.command(server, { root_dir = new_root_dir })
end
}You can either mount a path on host or a docker volume
You can create a volume (docker_volume) and mount it at path (workdir).
docker create volume persistent_volume_projectsrequire'lspconfig'.omnisharp.setup {
capabilities = capabilities,
before_init = before_init_process_id_nil,
cmd = require'lspcontainers'.command(
'omnisharp',
{
workdir = /projects
docker_volume = 'persistent_volume_projects',
}
),
on_new_config = on_new_config,
on_attach = on_attach,
root_dir = require'lspconfig/util'.root_pattern("*.sln", vim.fn.getcwd()),
}You can mount a volume from your host by just assigning workdir to match the host path.
require'lspconfig'.omnisharp.setup {
capabilities = capabilities,
before_init = before_init_process_id_nil,
cmd = require'lspcontainers'.command(
'omnisharp',
{
workdir = /home/[UserName]/projects
}
),
on_new_config = on_new_config,
on_attach = on_attach,
root_dir = require'lspconfig/util'.root_pattern("*.sln", vim.fn.getcwd()),
}If you are using podman instead of docker it is sufficient to just specify "podman" as container_runtime:
lspconfig.gopls.setup {
on_attach = on_attach,
capabilities = capabilities,
cmd = lspcontainers.command('gopls', {
container_runtime = "podman",
}),
root_dir = require'lspconfig/util'.root_pattern(".git", vim.fn.getcwd()),
}By default, LSPs that don't require network access run with network=none.
This means the container has no network access. If you have a special case
where an LSP needs network access, you can specify this explicitly:
cmd = lspcontainers.command('mylsp', {
-- ...
network = "bridge",
}),If you find that an LSP that commonly requires network access doesn't have this
by default, please open a PR updating its default (see init.lua).
The LSP spec allows a client to send its process id to a language server, so that the server can exit immediately when it detects that the client is no longer running.
This feature fails to work properly on a containerized language server because the host and the container do not share the container namespace by default.
A container can share a process namespace with the host by passing the
--pid=host flag to docker/podman, although it should be noted that this
somewhat reduces isolation.
It is also possible to simply disable the process id detection. This can be
done with the following before_init function:
require'lspconfig'.bashls.setup {
before_init = function(params)
params.processId = vim.NIL
end,
cmd = require'lspcontainers'.command('bashls'),
}
This is required for several LSPs, and they will exit immediately if this is not specified.
Below is a list of supported language servers for configuration with nvim-lspconfig. Follow a link to find documentation for that config.
- bashls
- clangd
- denols
- dockerls
- gopls
- graphql
- html
- intelephense
- jsonls
- omnisharp
- powershell_es
- prismals
- pylsp
- pyright
- rust_analyzer
- solargraph
- lemminx
- lua_ls
- svelte
- tailwindcss
- terraformls
- tsserver
- vuels
- yamlls
require'lspconfig'.bashls.setup {
before_init = function(params)
params.processId = vim.NIL
end,
cmd = require'lspcontainers'.command('bashls'),
root_dir = require'lspconfig/util'.root_pattern(".git", vim.fn.getcwd()),
...
}require'lspconfig'.clangd.setup {
cmd = require'lspcontainers'.command('clangd'),
...
}require'lspconfig'.denols.setup {
before_init = function(params)
params.processId = vim.NIL
end,
cmd = require'lspcontainers'.command('denols'),
root_dir = require'lspconfig/util'.root_pattern("deno.json", vim.fn.getcwd()),
...
}require'lspconfig'.dockerls.setup {
before_init = function(params)
params.processId = vim.NIL
end,
cmd = require'lspcontainers'.command('dockerls'),
root_dir = require'lspconfig/util'.root_pattern(".git", vim.fn.getcwd()),
...
}require'lspconfig'.gopls.setup {
cmd = require'lspcontainers'.command('gopls'),
...
}require'lspconfig'.graphql.setup {
before_init = function(params)
params.processId = vim.NIL
end,
cmd = require'lspcontainers'.command('graphql'),
root_dir = require'lspconfig/util'.root_pattern(".git", vim.fn.getcwd()),
...
}require'lspconfig'.html.setup {
before_init = function(params)
params.processId = vim.NIL
end,
cmd = require'lspcontainers'.command('html'),
root_dir = require'lspconfig/util'.root_pattern(".git", vim.fn.getcwd()),
...
}require'lspconfig'.intelephense.setup {
before_init = function(params)
params.processId = vim.NIL
end,
cmd = require'lspcontainers'.command('intelephense'),
root_dir = require'lspconfig/util'.root_pattern("composer.json", ".git", vim.fn.getcwd()),
...
}require'lspconfig'.jsonls.setup {
before_init = function(params)
params.processId = vim.NIL
end,
cmd = require'lspcontainers'.command('jsonls'),
root_dir = require'lspconfig/util'.root_pattern(".git", vim.fn.getcwd()),
...
}require'lspconfig'.omnisharp.setup {
before_init = function(params)
params.processId = vim.NIL
end,
cmd = require'lspcontainers'.command('omnisharp'),
root_dir = require'lspconfig/util'.root_pattern("*.sln", "*.csproj", vim.fn.getcwd()),
...
}require'lspconfig'.powershell_es.setup {
before_init = function(params)
params.processId = vim.NIL
end,
on_new_config = function(new_config, new_root_dir)
new_config.cmd = require'lspcontainers'.command(server, {root_dir = new_root_dir})
end,
cmd = require'lspcontainers'.command(server),
filetypes = {"ps1", "psm1", "psd1"},
...
}require'lspconfig'.prismals.setup {
before_init = function(params)
params.processId = vim.NIL
end,
cmd = require'lspcontainers'.command('prismals'),
root_dir = require'lspconfig/util'.root_pattern(".git", vim.fn.getcwd()),
...
}require'lspconfig'.pylsp.setup {
cmd = require'lspcontainers'.command('pylsp'),
...
}require'lspconfig'.pyright.setup {
before_init = function(params)
params.processId = vim.NIL
end,
cmd = require'lspcontainers'.command('pyright'),
root_dir = require'lspconfig/util'.root_pattern(".git", vim.fn.getcwd()),
...
}require'lspconfig'.rust_analyzer.setup {
cmd = require'lspcontainers'.command('rust_analyzer'),
...
}require'lspconfig'.solargraph.setup {
cmd = require'lspcontainers'.command('solargraph'),
...
}require'lspconfig'.lemminx.setup {
cmd = require'lspcontainers'.command('lemminx'),
...
}require'lspconfig'.lua_ls.setup {
cmd = require'lspcontainers'.command('lua_ls'),
...
}require'lspconfig'.svelte.setup {
before_init = function(params)
params.processId = vim.NIL
end,
cmd = require'lspcontainers'.command('svelte'),
root_dir = require'lspconfig/util'.root_pattern(".git", vim.fn.getcwd()),
...
}require'lspconfig'.tailwindcss.setup {
before_init = function(params)
params.processId = vim.NIL
end,
cmd = require'lspcontainers'.command('tailwindcss'),
filetypes = { "django-html", "htmldjango", "gohtml", "html", "markdown", "php", "css", "postcss", "sass", "scss", "stylus", "javascript", "javascriptreact", "rescript", "typescript", "typescriptreact", "vue", "svelte" },
root_dir = require'lspconfig/util'.root_pattern("tailwind.config.js", "tailwind.config.ts", "postcss.config.js", "postcss.config.ts", "package.json", "node_modules", ".git", vim.fn.getcwd()),
...
}require'lspconfig'.terraformls.setup {
cmd = require'lspcontainers'.command('terraformls'),
filetypes = { "hcl", "tf", "terraform", "tfvars" },
...
}require'lspconfig'.tsserver.setup {
before_init = function(params)
params.processId = vim.NIL
end,
cmd = require'lspcontainers'.command('tsserver'),
root_dir = require'lspconfig/util'.root_pattern(".git", vim.fn.getcwd()),
...
}require'lspconfig'.yamlls.setup {
before_init = function(params)
params.processId = vim.NIL
end,
cmd = require'lspcontainers'.command('yamlls'),
root_dir = require'lspconfig/util'.root_pattern(".git", vim.fn.getcwd()),
...
}require'lspconfig'.vuels.setup {
before_init = function(params)
params.processId = vim.NIL
end,
cmd = require'lspcontainers'.command('vuels'),
root_dir = require'lspconfig/util'.root_pattern(".git", vim.fn.getcwd()),
...
}To contribute to LSPs, please see the lspcontainers/dockerfiles repository.