Search code examples
autocompleteelixirneovimnvim-lspconfigelixirls

How do I setup elixir-ls using nvim-lspconfig with autocompletion in neovim?


I would like to set up the Elixir language server in Neovim using the built-in language server client and nvim-lspconfig.

Documentation for this seems to be spread out in multiple places:

  1. nvim-lspconfig README
  2. nvim-lspconfig wiki about autocomplete
  3. nvim-lspconfig elixir-ls server configuration documentation
  4. elixir-ls installation instructions

I am a little overwhelmed and have made multiple attempts to do this, but always give up without success. I also found a useful looking guide: How to Set Up Neovim for Elixir Development, but it makes quite a few assumptions, seems to eroneously do some configuration twice, and also switches config format halfway through, so wasn't a usable summary for me (after following the instructions, documentation popups were not working, and I was unable to scroll inside the autocomplete popups - I also had a lot of copy/pasted config I didn't understand).

So far I understand the required steps are:

  1. Install neovim
  2. Install elixir-ls manually (it doesn't seem to be possible currently to install via asdf due to a lack of ability to ask elixir-ls for its version)
  3. Install required neovim plugins: nvim-lspconfig + whatever is required for autocomplete
  4. Set up necessary config for nvim-lspconfig and autocomplete.

I have managed to do up to part-way through step 3, but have not sucesfully worked out the required dependencies and configuration for autocomplete.

What do I need to do to have a working elixir-ls setup in neovim, with autocomplete, using nvim-lspconfig and neovim's built-in language server client?


Solution

  • I managed to get everything working with the combination of some plugins for completion in neovim. They are:

    This is the same list of plugins from the nvim-cmp recommendation of neovim's Wiki.

    I'm using packer to manage the plugins. I'm also installing the plugins before configuring them. Here is the installation lines:

    use 'neovim/nvim-lspconfig'
    use 'nvim-treesitter/completion-treesitter' -- Only if you are using TS
    use 'hrsh7th/nvim-cmp' -- Autocompletion plugin
    use 'hrsh7th/cmp-nvim-lsp' -- LSP source for nvim-cmp
    use 'saadparwaiz1/cmp_luasnip' -- Snippets source for nvim-cmp
    use 'L3MON4D3/LuaSnip' -- Snippets plugin
    

    And then the configuration part:

    -- Add additional capabilities supported by nvim-cmp
    local capabilities = vim.lsp.protocol.make_client_capabilities()
    capabilities = require('cmp_nvim_lsp').update_capabilities(capabilities)
    
    -- Configure ElixirLS as the LSP server for Elixir.
    require'lspconfig'.elixirls.setup{
      cmd = { "/home/my-user/path-to/elixir-ls/release/language_server.sh" },
      -- on_attach = custom_attach, -- this may be required for extended functionalities of the LSP
      capabilities = capabilities,
      flags = {
        debounce_text_changes = 150,
      },
      elixirLS = {
        dialyzerEnabled = false,
        fetchDeps = false,
      };
    }
    
    local luasnip = require 'luasnip'
    -- nvim-cmp
    local cmp = require 'cmp'
    
    cmp.setup {
      snippet = {
        expand = function(args)
          require('luasnip').lsp_expand(args.body)
        end,
      },
      mapping = {
        ['<C-p>'] = cmp.mapping.select_prev_item(),
        ['<C-n>'] = cmp.mapping.select_next_item(),
        ['<C-d>'] = cmp.mapping.scroll_docs(-4),
        ['<C-f>'] = cmp.mapping.scroll_docs(4),
        ['<C-Space>'] = cmp.mapping.complete(),
        ['<C-e>'] = cmp.mapping.close(),
        ['<CR>'] = cmp.mapping.confirm {
          behavior = cmp.ConfirmBehavior.Replace,
          select = true,
        },
        ['<Tab>'] = function(fallback)
          if cmp.visible() then
            cmp.select_next_item()
          elseif luasnip.expand_or_jumpable() then
            luasnip.expand_or_jump()
          else
            fallback()
          end
        end,
        ['<S-Tab>'] = function(fallback)
          if cmp.visible() then
            cmp.select_prev_item()
          elseif luasnip.jumpable(-1) then
            luasnip.jump(-1)
          else
            fallback()
          end
        end,
      },
      sources = {
        { name = 'nvim_lsp' },
        { name = 'luasnip' },
      },
    }
    

    You can find all my neovim config in my dotfiles repo: https://github.com/philss/dotfiles

    For reference, here is the nvim version I'm using (installed from source):

    NVIM v0.7.0-dev+864-g2818de8b7
    Build type: RelWithDebInfo
    LuaJIT 2.1.0-beta3
    Features: +acl +iconv +tui