adamu

adamu

(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.

Most Liked

TunkShif

TunkShif

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

wingyplus

wingyplus

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.

AstonJ

AstonJ

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?

adamu

adamu

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.

krstfk

krstfk

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"

Where Next?

Popular in Dev Env & Tools Top

AstonJ
I’ve been reinstalling macOS after trying out Tahoe, and when I went to migrate from a Time Machine backup I got an error I’ve not see be...
New
aziz
I’m happy to finally present to you the best Sublime Text package for Elixir, templates and more! :partying_face: :confetti_ball: Elixi...
New
JoeZMar
After the keyboard thread has convinced me to purchase a UHK I wanted to make one about the mouse. Switching to a programmable keyboard h...
New
AstonJ
Welcome to our thread for Linux users :smiley: Mac users please use this thread Windows users please use this thread For those who dis...
New
AstonJ
Welcome to our thread for Mac users :smiley: Windows users please use this thread Linux users please use this thread For those who dis...
New
Dusty
New England download page New England repo I am a long-time dark theme user, but I recently developed an interest in coding on a light ...
New
kayoj
Hello I have been testing different environments for Elixir development &amp; noticed something odd. When I run the Elixir formatter loc...
#ai
New
AstonJ
How fast is your internet connection? (And how much do you pay?) You can test via www.speedtest.net and to embed results into your post...
New
AstonJ
Just noticed mine has gotten quite unwieldy and should probably be split into multiple files - but curious how big everyone else’s is! (...
New
AndyL
For development and prototyping, I’d like to retain a basic ability to perform LLM inference on my own hardware, using open source models...
#ai
New

Other popular topics Top

vonH
In asking this question I am more interested about the expressiveness of the language itself and less concerned about the availability of...
New
aalberti333
As the title describes, I’m trying to run Enum.map() over a list of key/value pairs, where the value is a map. My data looks like this: ...
New
axelson
This post is a wiki (feel free to hit the edit button near the bottom right of this post to add your own changes!) This post collects co...
239 45766 226
New
KronicDeth
Elixir plugin for JetBrain’s IntelliJ Platform (including Rubymine) This is a plugin that adds support for Elixir to JetBrains IntelliJ...
289 35421 110
New
quazar
How to set Jason to encode all fields in ecto schema, I don’t care about security and implementing only is taking long list of attributes...
New
minhajuddin
I have seen a lot of code which picks the first element from a list using Enum.at(0) instead of List.first. Is there a reason why people ...
New
Fl4m3Ph03n1x
About me? ( if you have nothing better to do than reading about some random guy in the internet :stuck_out_tongue: ) Hello all, this is ...
New
Qqwy
Original source of discussion: This topic on the Pragmatic Programmers' Functional Web Development with Elixir, OTP, and Phoenix forum. ...
New
aesmail
Hello guys, I have finally made it. I created an admin interface for a framework. It’s been on my todo list for years and with the curre...
New
lanycrost
Hi everyone! I need implement if…else if…else condition from my elixir code, and anymore of this control flow structures not work proper...
New

We're in Beta

About us Mission Statement