I will give that a try this weekend. Thank you for pointing it out. @kevinschweikert @adamu
Assuming you have already figured out how to install plugins, the instructions for configuring elixirls can be found here. The only required config flag is cmd, which is the path where you installed elixirls. If you have elixirls in $PATH, you can just use "elixir-ls" as the command - the brew package installs it this way.
Itâs hard to give specific help because the nature of such an open ecosystem is that there are like 30 different valid ways to do something - for example, which of the several popular plugin managers are you using? Are you using lua or vimscript? What is your completions plugin? And so on.
I agree with Dimi - using a distro might make life a lot easier for you. Have a look at what people are using here:
I started writing instructions by updating my blog post on setting up an Elixir (and Ruby) dev environment, but I havenât got around to finishing it yet. If you want to have a look itâs here (if anyone can spot any mistakes please let me know!)
Just to provide another perspective, I am personally not a fan of the number of deps that would be pulled in by a distro and prefer to manage my (very short) plugin list myself. Really this is the whole reason I use nvim - otherwise I would just be using Zed or whatever ![]()
11 posts were split to a new topic: Pros/cons of using a code editor distro vs managing your own plugins (split thread)
I am looking for config help to avoid unnecessary dependencies from distros. My focus is solely on working with Elixir and Phoenix code.
Personalization and other settings can come later. Right now, I just want to set up ElixirLS with auto-completion and Dialyzer working. Thatâs my main priority.
For a minimal setup you need a plugin manager, something to configure the lsp, and a completions plugin for autocomplete. If youâre not sure what to go with, a simple setup would be Plug for plugins, nvim-lspconfig to run the LSP, and nvim-cmp for completions. You need cmp-nvim-lsp from the same guy as nvim-cmp to get completions from the LSP, and you probably also want cmp-buffer to get completions from the current file (âbufferâ in vim terms).
If you follow the setup guides from each of these you should be able to piece together a basic init.vim. Again there are many different ways to do things. If you get stuck on something just ask on here.
vim-plug, nvim-lspconfig, nvim-cmp
In addition to vim stuff you also need to have a working elixir-ls installation. If youâre on a Mac the easiest way is brew, otherwise follow the elixir-ls installation instructions.
I use the lazy-vim package manager.
You can get inspiration from my Neovim config.
ElixirLS autocompletions runs via the elixir-tools.nvim plugin.
I have not tried the Dialyzer config in ElixirLS.
I recently switched from nvim-cmp to blink. GitHub - Saghen/blink.cmp: Performant, batteries-included completion plugin for Neovim
I find the performance to be better and things like the included snippet support let me drop a handful of packages in the migration.
Neovim version 0.11 just released.
One of the highlights is âSimpler LSP setup and configurationâ.
FWIW, native support for packages is in neovim master branch since last week, in case anyone is brave enough to try that out ![]()
A tracking issue is available here: `vim.pack` improvements · Issue #34763 · neovim/neovim · GitHub
I think I finally have a working setup using the built-in LSP integration.
Things seem to have changed quite a lot since this guide was written, most notably that nvim-lspconfig and nvim-cmp no longer seem to be required. I still had to install nvim-treesitter, though. Here are a few notes from my setup, in case it helps anyone else.
- Install
nvim-treesitter. Also donât forget to install the prerequisite CLI via for examplebrew install tree-sitter-cli. At the time of writing, they are in the process of a complete rewrite on themainbranch, and themasterone will become unsupported. Also, the docs state that we should run:TSUpdatewhen updating the plugin, so I set that up too. Using vimplug:Plug 'nvim-treesitter/nvim-treesitter', { 'branch': 'main', 'build': ':TSUpdate' } require'nvim-treesitter'.install { 'elixir', 'eex', 'heex' } - Set up ElixirLS. We need to supply the
cmdto tell it where itâs installed, androot_markersso neovim can find the project root.vim.lsp.config('elixirls', { cmd = { "/path/to/language_server.sh" }, -- required to make language server aware of other files root_markers = { 'mix.exs', '.git' }, }) vim.lsp.enable('elixirls') - Set up an autocmd to start tree-sitter when an elixir file is opened. I also set up a sub-autocmd to configure some LSP-related buffer settings when the LSP attaches to the buffer: enabling completion and auto-format on save. I was stuck here for a while, because while in most places the
patternfor an autocmd is something like*.ex, for theFileTypeautocmd itâs the detected filetype name, e.g.elixir
vim.api.nvim_create_autocmd('FileType', { pattern = { 'elixir', 'eex', 'heex' }, callback = function(ft_args) vim.treesitter.start() -- https://github.com/nvim-treesitter/nvim-treesitter/tree/main?tab=readme-ov-file#indentation vim.bo.indentexpr = "v:lua.require'nvim-treesitter'.indentexpr()" vim.api.nvim_create_autocmd('LspAttach', { buffer = ft_args.buf, callback = function(args) local client = assert(vim.lsp.get_client_by_id(args.data.client_id)) -- Enable auto-completion vim.lsp.completion.enable(true, client.id, args.buf, {autotrigger = true}) -- autoformat on save -- Apparently not needed if server supports "textDocument/willSaveWaitUntil". vim.api.nvim_create_autocmd('BufWritePre', { buffer = args.buf, callback = function() vim.lsp.buf.format({ bufnr = args.buf, id = client.id, timeout_ms = 1000 }) end, }) end, }) end, }) - Turn on âvirtual linesâ for diagnostics, so warnings and error messages show up below the line (otherwise itâs necessary to press CTRL+W d to view the warning - which you can still do if the warning scrolls off the edge of the screen and you want to view it in a popup).
vim.diagnostic.config({ virtual_lines = true }) - Set up any custom keymappings in addition to the defaults. For me I set
gdto go to definition,grrto find references, andCtrl+mto show the diagnostic in a popup.vim.keymap.set('n', 'gd', function() vim.lsp.buf.definition() end) vim.keymap.set('n', 'grr', function() vim.lsp.buf.references() end) vim.keymap.set('n', '<C-m>', function() vim.diagnostic.open_float() end) - Finally I added some vimscript to customise the autocompletion popup. The key bindings shouldnât be necessary because neovim apparently already has these tab/shift+tab bindings, but for some reason it wasnât working for me (also they are in vimscript, not lua, because I edited some existing code rather than figuring out how to do a conditional binding in luaâŠ)
set completeopt+=menuone,noselect,popup imap <expr> <enter> pumvisible() ? "<C-y>" : "<enter>" imap <expr> <tab> pumvisible() ? "<C-n>" : "<tab>" imap <expr> <S-tab> pumvisible() ? "<C-p>" : "<S-tab>"
Assembled config:
" Install tree-sitter with plugin manager
Plug 'nvim-treesitter/nvim-treesitter', { 'branch': 'main', 'build': ':TSUpdate' }
lua << LUA
vim.lsp.config('elixirls', {
cmd = { "/path/to/language_server.sh" },
-- required to make language server aware of other files
root_markers = { 'mix.exs', '.git' },
})
vim.lsp.enable('elixirls')
require'nvim-treesitter'.install { 'elixir', 'eex', 'heex' }
-- start tree-sitter and lsp for elixir files
vim.api.nvim_create_autocmd('FileType', {
pattern = { 'elixir', 'eex', 'heex' },
callback = function(ft_args)
vim.treesitter.start()
-- https://github.com/nvim-treesitter/nvim-treesitter/tree/main?tab=readme-ov-file#indentation
vim.bo.indentexpr = "v:lua.require'nvim-treesitter'.indentexpr()"
vim.api.nvim_create_autocmd('LspAttach', {
buffer = ft_args.buf,
callback = function(args)
local client = assert(vim.lsp.get_client_by_id(args.data.client_id))
-- Enable auto-completion
vim.lsp.completion.enable(true, client.id, args.buf, {autotrigger = true})
-- autoformat on save
-- Apparently not needed if server supports "textDocument/willSaveWaitUntil".
vim.api.nvim_create_autocmd('BufWritePre', {
buffer = args.buf,
callback = function()
vim.lsp.buf.format({ bufnr = args.buf, id = client.id, timeout_ms = 1000 })
end,
})
end,
})
end,
})
-- Enable warning/error messsages in virtual lines
vim.diagnostic.config({ virtual_lines = true })
vim.keymap.set('n', 'gd', function() vim.lsp.buf.definition() end)
vim.keymap.set('n', 'grr', function() vim.lsp.buf.references() end)
vim.keymap.set('n', '<C-m>', function() vim.diagnostic.open_float() end)
LUA
set completeopt+=menuone,noselect,popup
" Set up tab/enter navigation for autocomplete popup
" apparently this is already the default for snippets, no idea why it's not working without this
" https://github.com/neovim/neovim/pull/27339/files#diff-e682fc63c5b105e3eab956c380e8abf951a1b0d14fea03ae0f079bd490686e77
" have no idea what <expr> does but think it allows the ? operator
imap <expr> <enter> pumvisible() ? "<C-y>" : "<enter>"
imap <expr> <tab> pumvisible() ? "<C-n>" : "<tab>"
imap <expr> <S-tab> pumvisible() ? "<C-p>" : "<S-tab>"
Hmm, indeed, nvim-lspconfig doesnât seem necessary at all. The built-in Neovim configuration you showed is sufficient. However, for me personally, itâs still better to keep using nvim-cmp rather than the built-in omnifunc. nvim-cmp feels faster, but I might be subjective here, I only made a quick comparison. Still, its menu, with colored and grouped items, looks nicer than omnifuncâs.
Iâve finally been experimenting with Neovim. Iâm trying to build my own minimal config for what would make me comfortable. This thread has been a big help to me in getting past the initial steps and figuring out which plugins were key. Hereâs my attempt to give back a little.
The one thing I didnât see mentioned in here that I think is really important is the mason.nvim plugin. This is a plugin that automates the installation of the various LSP servers. I think this is far superior to doing something like brew install elixir-ls. If we do that install with Homebrew, we also get a copy of Erlang and a copy of Elixir. However, most of us are probably managing the languages we want with a tool like asdf. Given that, you need to take care to ensure that you are always using the executable you think youâre using. mason.nvim removes that concern because it is going to install the language server using whatever version of Erlang and Elixir that Neovim can see and execute. You can see my config using mason.nvim and another plugin to tie it into the LSP plugin. Note that I am not yet using Neovimâs built-in LSP functionality.
I found it helpful to keep my own version of ElixirLS and git-pull-recompile it once in a while. Youâd need to tell your plugin to use it
cmd = "/home/am/Proyectos/Other/elixir-ls/releases/language_server.sh",
settings = elixirls.settings {
enableTestLenses = true,
dialyzerEnabled = true,
fetchDeps = false,
suggestSpecs = false,
autoInsertRequiredAlias = false,
languageServerOverridePath = "/home/am/Proyectos/Other/elixir-ls/releases",
},
Gosh, the code markdown here does not allow lua. Itâs Iâm time-travelled to 1992.
Switched to lazyvim distribution about a year ago from chad - very happy with it so far, no complaints






















