(Neo)vim and language servers

There is a thread about vim already, but I wanted to start one specifically about the language server.

What configuration do people use for integrating elixir-ls in vim? Are the integrations worth using?

Specifically, the elixir-ls readme suggests three options:

  1. ALE
  2. elixir-lsp/coc-elixir
  3. vim-lsp

I’ve already read Mitchell Hanberg (ALE), Hauleth (vim-lsp) and David Bernheisel (coc-elixir)'s blog posts - but they are from 2018, 2019 and 2020 respectively, and all recommend different implementations. I was wondering if there have been more developments in the last few years that may change the recommendations, and if there is any de-facto standard?

Personally I was thinking of trying vim-lsp, but thought I’d check for a consensus before I go down the rabbit hole.

2 Likes

If you’re using neovim, the neovim lsp is recommended since it’s built-in lsp integration for neovim since 0.5. It’s quite faster than the list above since it’s written in C + some Lua code (i believe). You just to install the neovim-lspconfig GitHub - neovim/nvim-lspconfig: Quickstart configurations for the Nvim LSP client to pre config lsp server that you want to use, elixir-ls already included.

But if you’re prefer vim-lsp, vim-lsp-settings GitHub - mattn/vim-lsp-settings: Auto configurations for Language Server for vim-lsp is recommended.

6 Likes

Thanks wingyplus - no preference! I will check out neovim-lspconfig.

1 Like

Another nice thing with using the built-in lsp client is that it integrates nicely with telescope , which makes it easy to navigate through documents/workspace symbols/diagnostics. Of course if you don’t use/like telescope, then it’s not really relevant.

Apart from that, if you’re ok with experimental features, nvim-treesitter is a good addition to the setup, but it does require to work with nvim HEAD, which hasn’t eaten any of my code up until now.

The only issue I faced with this setup, is I forgot to set the terminal to bash in my init.lua, and fish (my main terminal) doesn’t play well at all with vim/nvim and can render everything crippling slow.

2 Likes

Just wanted to mention I still didn’t get this set up as well as I’d like. Although the language-server is running, I don’t have auto-completion working yet, and the configuration for options you need to provide to nvim-lspconfig are quite overwhelming. I just asked a question over on Stack Overflow about how to get this set up. I’m hoping somebody will spell it out before I eventually get around to working it all out!

I started writing a quick start guide (as mentioned here) but ran into issues with lspconfig too, so ended up installing Spacevim, which works but feels slower than Spacemacs (so I uninstalled it). The reason I wanted to use Neovim is because I wanted something that felt really snappy!

So currently I am leaning towards Doom Emacs - it’s much easier to set-up, feels pretty solid, and Emacs actually has some really nice features (as mentioned in this DT thread). People (or at least the person in that vid) says that once you know the basics of Vim then it’s a good time to transition to Emacs in Evil mode (i.e Doom Emacs or Spacemacs).

I’d still be interested in trying to get a working ‘fast’ Neovim set up tho… perhaps we can start a thread in members-only to work on putting together a step-by-step guide?

3 Likes

I just made a guide on how to set up neovim for elixir development from scratch, hope that you will find it helpful!

8 Likes

After months of head-scratching and procrastinating over getting nvim-lspconfig set up property, I set up coc.nvim this morning in a couple of hours and it all works magically. Most of the defaults were what I wanted, and I only had to add about 5 lines of config. :h coc-nvim has all the documentation you need. It was also only a couple of commands to get language servers working with with json, typescript, etc. too. I wish I’d started with coc.nvim from the beginning!

Also: I tend to run two buffers side-by-side, and the way warnings are displayed with the built-in lsp support is that they are appended to the end of the line - often falling of the screen. coc.nvim’s implementation shows the warnings in a popup when you focus on them, which works better. The native version is probably configurable, but coc.vim’s defaults worked well for me.

3 Likes

That’s how I felt when I went to coc :lol:

I think my current fave is just using LunarVim… as mentioned in @TunkShif’s set-up from scratch guide: Neovim - Elixir Setup Configuration from Scratch Guide

1 Like

Still using LunarVim and I mostly love it but 1-2 times a month it spits out a random Lua error that I then go Google and almost always it’s an external plugin that changes stuff underneath. And since LunarVim is basically like a “NeoVim distro” of sorts – as in, it pre-packages stuff for you – then I can’t blame them.

But lately they are doing better. They started freezing some plugin versions and are trying to move to proper releases where everything is bundled in and guaranteed to work. They are not there yet but I like it that they’re trying.

But who knows, some afternoon I might randomly get a Lua error again, get pissed off and just make my own NeoVim config and put coc.nvim in it. The option is definitely on the table.

(EDIT: I haven’t fired LunarVim in 2 weeks and now I have 3 errors and warnings about future deprecations. Sigh)

1 Like

I can relate SO much to this. I’m also on lunar, and it is a great place to start with vim. I’m only using for 18 months or so. I did try to build my own config, but I am not patient enough. Those times that lunar changes underneath me are frustrating. And I don’t even try to upgrade regularly (I always forget the procedure, and it’s a mess integrating my own config into the default starter config).
But still, I like it, and I feel more and more confident using vim.
There is this kickstart project that’s also on my radar.
I should learn more about neovim and lua, but it feels so alien to me…

LSP native is great nowadays. I use a custom
init and don’t have that many issues. To be fair I know nothing about lunar, chad or whatever preset.

1 Like

give native neovim with their kickstart.nvim a chance, it’s a good basis to start

1 Like

short abstract of my init.lua :


local use = require('packer').use
require('packer').startup(function()
  use 'neovim/nvim-lspconfig' -- Collection of configurations for built-in LSP client
  use 'hrsh7th/cmp-nvim-lsp'
  use 'hrsh7th/cmp-buffer'
  use 'hrsh7th/cmp-path'
  use 'hrsh7th/cmp-cmdline'
  use 'hrsh7th/nvim-cmp'
  use 'hrsh7th/cmp-vsnip'
  use 'hrsh7th/vim-vsnip'
  use 'hrsh7th/cmp-nvim-lua'

  use { 'nvim-treesitter/nvim-treesitter', run = { 'TSUpdate' } }
  use 'nvim-treesitter/nvim-treesitter-textobjects'
 end)


local nvim_lsp = require('lspconfig')
local on_attach = function(_client, bufnr)
  vim.api.nvim_buf_set_option(bufnr, 'omnifunc', 'v:lua.vim.lsp.omnifunc')

  local opts = { noremap = true, silent = true }
  -- vim.api.nvim_buf_set_keymap(bufnr, 'n', 'gD', '<Cmd>lua vim.lsp.buf.declaration()<CR>', opts)
  -- vim q
end

local cmp = require 'cmp'

cmp.setup({
  snippet = {
    -- REQUIRED - you must specify a snippet engine
    expand = function(args)
      vim.fn["vsnip#anonymous"](args.body) -- For `vsnip` users.
      -- require('luasnip').lsp_expand(args.body) -- For `luasnip` users.
      -- require('snippy').expand_snippet(args.body) -- For `snippy` users.
      -- vim.fn["UltiSnips#Anon"](args.body) -- For `ultisnips` users.
    end,
  },
  window = {
    completion = {
      winhighlight = "Normal:Normal,FloatBorder:Pmenu,Search:None",
      col_offset = -3,
      side_padding = 0,
    },
    -- completion = cmp.config.window.bordered(),
    -- documentation = cmp.config.window.bordered(),
  },
  formatting = {
    fields = { "kind", "abbr", "menu" },
    format = function(entry, vim_item)
      if vim_item.kind == 'Color' and entry.completion_item.documentation then -- tailwind swatches
        local _, _, r, g, b = string.find(entry.completion_item.documentation, '^rgb%((%d+), (%d+), (%d+)')
        if r then
          local color = string.format('%02x', r) .. string.format('%02x', g) .. string.format('%02x', b)
          local group = 'Tw_' .. color
          if vim.fn.hlID(group) < 1 then
            vim.api.nvim_set_hl(0, group, { bg = '#' .. color })
            -- vim.api.nvim_set_hl(0, CmpItemKindColor, {bg = '#' .. color})
          end
          local kind = require("lspkind").cmp_format({ mode = "symbol_text", maxwidth = 50 })(entry, vim_item)
          local strings = vim.split(kind.kind, "%s", { trimempty = true })
          kind.kind = " " .. (strings[1] or "") .. " "
          kind.kind_hl_group = group
          kind.menu = "    (" .. (strings[2] or "") .. ")"
          return kind
        end
      end
      local kind = require("lspkind").cmp_format({ mode = "symbol_text", maxwidth = 50 })(entry, vim_item)
      local strings = vim.split(kind.kind, "%s", { trimempty = true })
      kind.kind = " " .. (strings[1] or "") .. " "
      kind.menu = "    (" .. (strings[2] or "") .. ")"

      return kind
    end,
  },
  mapping = cmp.mapping.preset.insert({
    ['<C-b>'] = cmp.mapping.scroll_docs(-4),
    ['<C-f>'] = cmp.mapping.scroll_docs(4),
    ['<C-Space>'] = cmp.mapping.complete(),
    ['<C-e>'] = cmp.mapping.abort(),
    ['<CR>'] = cmp.mapping.confirm({ select = true }), -- Accept currently selected item. Set `select` to `false` to only confirm explicitly selected items.
  }),
  sources = cmp.config.sources({
    { name = 'nvim_lsp' },
    { name = 'vsnip' }, -- For vsnip users.
    { name = 'nvim_lua' },
    -- { name = 'luasnip' }, -- For luasnip users.
    -- { name = 'ultisnips' }, -- For ultisnips users.
    -- { name = 'snippy' }, -- For snippy users.
  }, {
    { name = 'buffer' },
  })
})

-- Set configuration for specific filetype.
--
cmp.setup.filetype('gitcommit', {
  sources = cmp.config.sources({
    { name = 'cmp_git' }, -- You can specify the `cmp_git` source if you were installed it.
  }, {
    { name = 'buffer' },
  })
})

-- Use buffer source for `/` and `?` (if you enabled `native_menu`, this won't work anymore).
cmp.setup.cmdline({ '/', '?' }, {
  mapping = cmp.mapping.preset.cmdline(),
  sources = {
    { name = 'buffer' }
  }
})

-- Use cmdline & path source for ':' (if you enabled `native_menu`, this won't work anymore).
cmp.setup.cmdline(':', {
  mapping = cmp.mapping.preset.cmdline(),
  sources = cmp.config.sources({
    { name = 'path' }
  }, {
    { name = 'cmdline' }
  })
})

-- Set up lspconfig.
local capabilities = require('cmp_nvim_lsp').default_capabilities()

-- Enable the following language servers
local servers = { 'clangd', 'rust_analyzer', 'pyright', 'tsserver', 'hls', 'ocamllsp' }
for _, lsp in ipairs(servers) do
  nvim_lsp[lsp].setup { on_attach = on_attach, capabilities = capabilities }
end

nvim_lsp['elixirls'].setup {
  -- Unix
  cmd = { os.getenv("HOME") .. "/elixir-ls/language_server.sh" },
  on_attach = on_attach,
  capabilities = capabilities
}

nvim_lsp['lua_ls'].setup {
  on_attach = on_attach,
  capabilities = capabilities,
  settings = {
    Lua = {
      runtime = {
        -- Tell the language server which version of Lua you're using (most likely LuaJIT in the case of Neovim)
        version = 'LuaJIT',
      },
      diagnostics = {
        -- Get the language server to recognize the `vim` global
        globals = { 'vim' },
      },
      -- Do not send telemetry data containing a randomized but unique identifier
      telemetry = {
        enable = false,
      },
    },
  },
}


nvim_lsp.tailwindcss.setup({
  capabilities = capabilities,
  filetypes = { "html", "elixir", "eelixir", "heex" },
  init_options = {
    userLanguages = {
      elixir = "html-eex",
      eelixir = "html-eex",
      heex = "html-eex",
    },
  },
  settings = {
    tailwindCSS = {
      experimental = {
        classRegex = {
          'class[:]\\s*"([^"]*)"',
        },
      },
    },
  },
})

nvim_lsp.emmet_ls.setup({
  -- on_attach = on_attach,
  capabilities = capabilities,
  filetypes = { 'html', 'typescriptreact', 'javascriptreact', 'css', 'sass', 'scss', 'less', 'elixir', 'eelixir', 'heex' },
  init_options = {
    userLanguages = {
      elixir = "html-eex",
      eelixir = "html-eex",
      heex = "html-eex",
    },
    html = {
      options = {
        -- For possible options, see: https://github.com/emmetio/emmet/blob/master/src/config.ts#L79-L267
        ["bem.enabled"] = true,
      },
    },
  }
})
require("lsp-colors").setup({
  Error = "#db4b4b",
  Warning = "#e0af68",
  Information = "#0db9d7",
  Hint = "#10B981"
})
require("trouble").setup()


require('nvim-lightbulb').update_lightbulb()

-- Map :Format to vim.lsp.buf.formatting()
vim.cmd([[ command! Format execute 'lua vim.lsp.buf.format({async = true})' ]])

-- Set completeopt to have a better completion experience
vim.o.completeopt = "menuone,noinsert"

3 Likes

Thanks a lot, the screenshots look convincing!

Maybe we should make a separate thread though, we’re kind of going off-topic – my fault, sorry.

When we do that I’ll love some more screenshots, seriously. I am on the precipice of taking my NeoVim setup in my hands and need motivation. :smiley:

Thanks for the recommendation, I even bookmarked it.

My problem is that I am soooooo burned out. I know that redoing your editor from scratch is quite a lot of work and I can procrastinate it endlessly.

…At the same time though, using a prebuilt “distro” like LunarVim comes at a price – things change beneath your feet and you can’t do much about it. It’s kind of like buying the much cheaper car but ending up spending so much on repairs for it that it turns it out it’s more expensive than the car you thought was expensive.

So yep, I am very likely to indeed redo my NeoVim setup from scratch. I’ll ask for advice down the road, if that’s okay. I admit I can do 100% of it alone but I’ll likely take the somewhat lazy route and will try to take some shortcuts.

Yep, prebuilt configs come at a price since you need to learn them and every single plugin they use.
That’s actually the reason I’m not sharing my whole init.lua: I have a crapload of leader based mappings that are ok for me because they each map to a single key on some layer on my keyboard. Wouldn’t be useful in general.
Although for anyone using GitHub - HiPhish/nvim-ts-rainbow2: Rainbow delimiters for Neovim through Tree-sitter
I do have a basic query:

(map "{" @opening 
      "}" @closing
     ) @container 
(arguments "(" @opening 
      ")" @closing
     ) @container 
(bitstring
  "<<" @opening
  ">>" @closing) @container
(interpolation
  "#{" @opening
  "}" @closing) @container
(sigil
  (sigil_name) @opening
  (sigil_modifiers) @closing) @container
(do_block "do" @opening
        "end" @closing) @container
(list "[" @opening 
      "]" @closing
     ) @container 
(tuple "{" @opening
       "}" @closing
) @container

1 Like

If you don’t mind its minimalism, I recommend Helix. It’s also a modal editor, but my config is ~20 lines (and that’s relatively big when compared to others). It just works and I go months without even thinking about it. One of its distinct pros compared to LVim and re Elixir specifically it’s that markdown in the docstrings is actually themed.

2 Likes