NeoVim cannot start lsp because clangd is not found

I am running neovim stock except with https://github.com/nvim-lua/kickstart.nvim/blob/2510c29d62d39d63bb75f1a613d2ae628a2af4ee/init.lua as my init.lua, and the clangd and rust-analyzer LSPs enabled. This uses mason-lspconfig.
I am getting the error:

Spawning language server with cmd: '/home/ben/.local/share/nvim/mason/bin/clangd' failed. The language server is either not installed, missing from PATH, or not executable.

I have clang and llvm installed inside /etc/nixos/configuration.nix.

clang version 16.0.6
Target: x86_64-unknown-linux-gnu
Thread model: posix
InstalledDir: /nix/store/5ql31bfsgynanf794wnl2wlbs23q7b8h-clang-16.0.6/bin
1 Like

Could we see your config? Also curious if you have vimPlugins.clangd_extensions-nvim installed?

( Better version in edit )

Summary

This has worked for me, a little crude but it works

I added

clang-tools # clangd nvim

to my packages

then ran “which clangd”
output:


➜  ~ which clangd
/run/current-system/sw/bin/clangd
➜  ~

In my config I put

local system_name = vim.loop.os_uname().sysname
if system_name == "Linux" then
  -- Check if the Linux distribution is NixOS
  local file = io.open("/etc/os-release", "r")
  if file then
    local content = file:read("*all")
    file:close()
    if string.find(content, "ID=nixos") then
      require'lspconfig'.clangd.setup{
        cmd = { "/run/current-system/sw/bin/clangd" },
      }
    end
  end
end

( I use this config on other OS’s too hence the nixos check )

Edit:
I’ve tidied this up a bit + removed the need for hard-coding paths

Ensure “clang-tools” is installed in packages

lsp.preset("recommended")
require('mason').setup({})
local system_name = vim.loop.os_uname().sysname
local ensure_installed_list = { 'tsserver', 'denols', 'gopls', 'rust_analyzer' }

if system_name == "Linux" then
    local file = io.open("/etc/os-release", "r")
    if file then
        local content = file:read("*all")
        file:close()
        if string.find(content, "ID=nixos") then
            require'lspconfig'.clangd.setup{}
        else
            table.insert(ensure_installed_list, 'clangd')
        end
    end
else
    table.insert(ensure_installed_list, 'clangd')
end

require('mason-lspconfig').setup({
    ensure_installed = ensure_installed_list,
    handlers = {        lsp.default_setup,}
})
local cmp = require('cmp')
local cmp_action = require('lsp-zero').cmp_action()
local nvim_lsp = require('lspconfig')
nvim_lsp.denols.setup {
    on_attach = on_attach,
    root_dir = nvim_lsp.util.root_pattern("deno.json", "deno.jsonc"),}

Edit 2:
Specifically for your config you’d want something like this around like 590
( Ensure that clangd is not uncommented on line 556 as the differing mason & nixos paths causes this issue )

require('mason').setup()

-- You can add other tools here that you want Mason to install
-- for you, so that they are available from within Neovim.
local ensure_installed = vim.tbl_keys(servers or {})
vim.list_extend(ensure_installed, {
  'stylua', -- Used to format Lua code
})

local system_name = vim.loop.os_uname().sysname

if system_name == "Linux" then
  local file = io.open("/etc/os-release", "r")
  if file then
    local content = file:read("*all")
    file:close()
    if string.find(content, "ID=nixos") then
      require'lspconfig'.clangd.setup{}
    else
      table.insert(ensure_installed, 'clangd')
    end
  end
else
  table.insert(ensure_installed, 'clangd')
end

require('mason-tool-installer').setup { ensure_installed = ensure_installed }

require('mason-lspconfig').setup {
  handlers = {
    function(server_name)
      local server = servers[server_name] or {}
      -- This handles overriding only values explicitly passed
      -- by the server configuration above. Useful when disabling
      -- certain features of an LSP (for example, turning off formatting for tsserver)
      server.capabilities = vim.tbl_deep_extend('force', {}, capabilities, server.capabilities or {})
      require('lspconfig')[server_name].setup(server)
    end,
  },
}
1 Like

Thanks a lot! I was looking for a way to do exactly this.

I just split the code for better reusability and readability:

-- Get system information
local system_name = vim.loop.os_uname().sysname

if system_name == 'Linux' then
  local file = io.open('/etc/os-release', 'r')
  if file then
    local content = file:read '*all'
    file:close()
    if string.find(content, 'ID=nixos') then
      vim.g.system_id = 'nixos'
    end
  end
end

I also extracted the function that sets up the servers to re-use it for NixOS.

require('mason').setup()

local setup_servers = function(server_name)
  local server = servers[server_name] or {}
  server.capabilities = vim.tbl_deep_extend('force', {}, capabilities, server.capabilities or {})
  lspconfig[server_name].setup(server)
end

local ensure_installed = vim.tbl_keys(servers or {})
local extra_tools = {
  'stylua', -- Used to format Lua code
}

if vim.g.system_id == 'nixos' then
  for _, server_name in pairs(ensure_installed) do
    setup_servers(server_name)
  end
else
  vim.list_extend(ensure_installed, extra_tools)
  require('mason-tool-installer').setup { ensure_installed = ensure_installed }
  require('mason-lspconfig').setup { handlers = { setup_servers } }
end
1 Like