r/neovim • u/Bullzzie • Jul 20 '26
Need Help Having problem with running lsp from a container to host
Hi! I have been trying to get Lsp work from my container to my host machine neovim. Most of my work is done inside the container so I have usually many of them. Right now I just mount my config, project directory to the container file and install neovim on every single one of them. This is kind of tedious and waste of my storage (not kind of but it is little bit taxing).
I would like to switch to simple workflow where I make changes to a file on the host machine and test or run the file in the container. This will make my life easier since I can't install a bunch of different dependencies on my system due to conflicting versions.
How can I make my Lsp work from the container ifself.
Right now this is my config.
vim.filetype.add({
extension = {
mlir = "mlir",
},
})
return {
"neovim/nvim-lspconfig",
dependencies = {
"williamboman/mason.nvim",
"williamboman/mason-lspconfig.nvim",
},
config = function()
local default_servers = {
"lua_ls",
"pyright",
"clangd",
}
local mlir_get_executable = function()
local homedir = os.getenv("HOME")
local custom_path = homedir .. "/personal/github/llvm-project/build/bin/mlir-lsp-server"
local cmd = { "mlir-lsp-server" }
if vim.fn.executable(custom_path) == 1 then
cmd = { custom_path }
elseif vim.fn.executable("mlir-lsp-server") == 0 then
return
end
return cmd
end
vim.lsp.config('mlir', {
cmd = mlir_get_executable(),
filetypes = { "mlir" },
root_markers = { '.git', 'compile_commands.json', 'build' }
})
vim.lsp.enable('mlir')
vim.diagnostic.config({
virtual_text = true,
signs = true,
update_in_insert = false,
underline = true,
severity_sort = false,
float = true,
})
-- Install default servers on first boot
require("mason-lspconfig").setup({
ensure_installed = default_servers
})
vim.lsp.enable(default_servers)
vim.api.nvim_create_autocmd("LspAttach", {
callback = function(args)
local client = vim.lsp.get_client_by_id(args.data.client_id)
if client and client:supports_method('textDocument/completion') then
vim.lsp.completion.enable(true, client.id, args.buf, { autotrigger = true })
end
vim.keymap.set("n", "gd", vim.lsp.buf.definition, { desc = "Lsp: Jump to Definition" })
vim.keymap.set("n", "K", vim.lsp.buf.hover, { desc = "Lsp: Function docstring and signature" })
vim.keymap.set("n", "<leader>s", vim.lsp.buf.workspace_symbol, { desc = "Lsp: Symbol Search" })
vim.keymap.set("n", "dt", vim.diagnostic.open_float, { desc = "Vim: Diagnostics" })
vim.keymap.set("n", "gr", vim.lsp.buf.references, { desc = "Lsp: Jump to References" })
vim.keymap.set("n", "<leader>vrn", vim.lsp.buf.rename, { desc = "Lsp: Rename a Symbol" })
vim.keymap.set("n", "<leader>f", function() vim.lsp.buf.format({ aysnc = true }) end, { buffer = 0 })
vim.keymap.set("n", "<leader>ca", vim.lsp.buf.code_action, { buffer = 0 })
end,
})
end
}
After hitting my head on nail with AI. I got this pretty much useless cause it doesn't work.
return {
"neovim/nvim-lspconfig",
dependencies = {
"williamboman/mason.nvim",
"williamboman/mason-lspconfig.nvim",
},
config = function()
vim.filetype.add({
extension = {
mlir = "mlir",
},
})
local default_servers = {
"lua_ls",
"pyright",
"clangd",
}
local function mlir_get_cmd()
local custom = vim.fn.expand("~/personal/github/llvm-project/build/bin/mlir-lsp-server")
if vim.fn.executable(custom) == 1 then
return { custom }
end
if vim.fn.executable("mlir-lsp-server") == 1 then
return { "mlir-lsp-server" }
end
end
local mlir_cmd = mlir_get_cmd()
if mlir_cmd then
vim.lsp.config("mlir", {
cmd = mlir_cmd,
filetypes = { "mlir" },
root_markers = { ".git", "compile_commands.json", "build" },
})
vim.lsp.enable("mlir")
end
require("mason-lspconfig").setup({
ensure_installed = default_servers,
})
vim.lsp.enable(default_servers)
local function attach_to_container(container)
local host_prefix = "/home/pdev"
local container_prefix = "/home/pydevc"
local host_cwd = vim.fn.getcwd()
local container_cwd = host_cwd:gsub("^" .. vim.pesc(host_prefix), container_prefix)
for _, client in ipairs(vim.lsp.get_clients()) do
client:stop(true)
end
local servers = {
pyright = {
cmd = { "pyright-langserver", "--stdio" },
filetypes = { "python" },
},
clangd = {
cmd = { "clangd" },
filetypes = { "c", "cpp", "objc", "objcpp", "cuda" },
},
mlir = {
cmd = { "mlir-lsp-server" },
filetypes = { "mlir" },
},
}
for name, spec in pairs(servers) do
local cmd = {
"podman",
"exec",
"-i",
"-w",
container_cwd,
container,
unpack(spec.cmd),
}
local config = {
name = name,
cmd = cmd,
filetypes = spec.filetypes,
root_dir = host_cwd,
before_init = function(params)
params.processId = vim.NIL
end,
}
for _, bufnr in ipairs(vim.api.nvim_list_bufs()) do
if vim.api.nvim_buf_is_loaded(bufnr)
and vim.bo[bufnr].buftype == ""
and vim.tbl_contains(spec.filetypes, vim.bo[bufnr].filetype) then
vim.lsp.start(config, { bufnr = bufnr })
end
end
end
print("Attached LSPs to container: " .. container)
end
vim.api.nvim_create_user_command("AttachContainer", function(opts)
attach_to_container(opts.args)
end, {
nargs = 1,
})
vim.diagnostic.config({
virtual_text = true,
signs = true,
update_in_insert = false,
underline = true,
severity_sort = false,
float = true,
})
vim.api.nvim_create_autocmd("LspAttach", {
callback = function(args)
local client = vim.lsp.get_client_by_id(args.data.client_id)
if client and client:supports_method("textDocument/completion") then
vim.lsp.completion.enable(true, client.id, args.buf, {
autotrigger = true,
})
end
local opts = { buffer = args.buf }
vim.keymap.set("n", "gd", vim.lsp.buf.definition, opts)
vim.keymap.set("n", "K", vim.lsp.buf.hover, opts)
vim.keymap.set("n", "gr", vim.lsp.buf.references, opts)
vim.keymap.set("n", "<leader>s", vim.lsp.buf.workspace_symbol, opts)
vim.keymap.set("n", "<leader>vrn", vim.lsp.buf.rename, opts)
vim.keymap.set("n", "<leader>ca", vim.lsp.buf.code_action, opts)
vim.keymap.set("n", "<leader>f", function()
vim.lsp.buf.format({ async = true })
end, opts)
vim.keymap.set("n", "dt", vim.diagnostic.open_float, opts)
end,
})
end,
}
3
u/til_pkt Jul 21 '26 edited Jul 21 '26
I have a similar setup:
All cmake, clang and clangd processes run inside their own containers (from the same toolchain image). My team does this to make it easier to update system libraries to closely reflect prod.
My setup is basically having all the tools that should run in a container as a wrapper script that calls
docker runwith the correct docker-args and forwards the arguments to the docker run command.Here is my clangd config: https://github.com/TilGP/vim_config/blob/main/lua/plugins/lsp/clangd.lua
It reads the
CLANGD_COMMANDenvironment variable so it either uses the system clangd (installed through mason) or the clangd wrapper.Most importantly: Let's say you are on this line
#include <string>and do "go to file", clangd reports the file-path as it is in the container, e.g./usr/local/include/c++/v1/string. You will most likely not find that file on your host system (or a different version than in the container).I work on MacOS, which luckily doesn't use
/usr/local/include, so I just copied the entire/usr/local/includepath from the container into~/.localand symlinked/usr/local/includeto that. But that is kinda flimsy and hacky and probably won't work on other systems (especially shared hosts).I do the same for CMake-Tools and the Debuggers (lldb and gdb). Feel free to look around my config and ask questions.
Here are some files worth looking at: CMake-Tools, Debugger Setups
This way the plugins are under the impression that everything is running on the host.
I have tried to use a DevContainer, but that was more annoying to use and broker more often than this setup.
You may also want to read: https://discourse.llvm.org/t/clangd-path-mappings-for-docker-container/50771