Helix editor for Elixir Development

Hey Helix users :slight_smile: I am trying Helix, but I miss shortcuts to run the tests for one of these:

  • the line my cursor is currently on (mix test path/to/the/file.exs:line_number)
  • the file I have currently open and active (mix test path/to/file.exs)
  • everything (mix test)

Do you have an idea if this is even possible in Helix?

1 Like

It looks like it could be done with an approach like this: Helix and tmux integration - DEV Community

Are you guys enjoying Helix?

To me, it served me as a stepping stone into neovim.

Having total control of your IDE is amazing. There’s nothing in particular that would make someone jump from vscode, but it’s just the volume of tiny optimizations that makes it awesome.

I’ve actually switched over from neovim. I like the movement patterns better and not having to have to configure everything is great as well.
I’m using it in combination with https://zellij.dev/ and use similar things like that for testing.

For elixir most things work great. The only issue is that I haven’t got the tailwind integration with elixir working.

1 Like

I once took a class on Korean and the teacher said we were lucky in Spanish because we could start saying to the waitress

I want aaaaaa

And then, after thinking about it, say “coke”. The needed (I guess they still do), start with the subject.

That’s something I think helix got right. Start with the subject, you can visualize it, and then perform the action.

da” is an act of faith in vim, but once you truly believe in it, you can see the light

2 Likes

Did anyone figured out how to configure Helix to run ElixirLS debug adapter? Helix added support and looking at Helix editor for Elixir Development - #32 by darnahsan hx --health checks if DAP is working

2 Likes

Hi, have you got it working with tailwind ? If yes, could you share the config. thanks in advance

Not yet. I haven’t really looked further to it. I think the best way to debug would be to compare what other editors send to the tailwind lap and see what the difference is.

1 Like

There is a solution that works :tada:

you can find the discussion here:

[language-server.tailwind-heex]
command = "tailwindcss-language-server"
args = ["--stdio"]

[[language]]
name = "elixir"
language-id = "phoenix-heex"
language-servers = ["tailwind-heex", "elixir-ls"]
auto-format = true

[[language]]
name = "heex"
language-id = "phoenix-heex"
language-servers = ["tailwind-heex", "elixir-ls"]
auto-format = true
2 Likes

Has anyone got do…end completion working in Helix?

I can see there’s an issue related to indentation in the Issues tracker that would suggest this is working, but I can’t get it to action. I’ve updated elixir-ls, checked snippets is set to true, and updated hx.

edit: fixed, I was not being patient enough for the snippet prompt. Spoilt by VS Code for the simple return after the do keyword, but persisting with hx as the feel/speed in general is excellent.

I also added vscode-html-language-server in order to get html code completions:

[language-server.tailwind-heex]
command = "tailwindcss-language-server"
args = ["--stdio"]

[[language]]
name = "elixir"
language-id = "phoenix-heex"
language-servers = ["tailwind-heex", "elixir-ls", "vscode-html-language-server"]
auto-format = true

[[language]]
name = "heex"
language-id= "phoenix-heex"
language-servers = ["tailwind-heex", "elixir-ls", "vscode-html-language-server"]
comment-token = "<%!--"
auto-format = true

But there is one problem with this: the symbol picker shows not only elixir symbols but also arbitrary html symbols which makes it hard to work with. So I’ll probably remove it again.

I’ve figured out a great trick, to be able to run mix test with filename and line numbers as longs a Add command expansions %key{body} by ksdrar · Pull Request #6979 · helix-editor/helix · GitHub is not merged yet.

I’m using zellij, wezterm and helix of course.

  1. I use this script to parse the file and line number from the terminal:
        set -x

        status_line=$(wezterm cli get-text | rg -e "(?:NOR\s+|NORMAL|INS\s+|INSERT|SEL\s+|SELECT)\s+[\x{2800}-\x{28FF}]*\s+(\S*)\s[^│]* (\d+):*.*" -o --replace '$1 $2')
        filename=$(echo $status_line | awk '{ print $1}')
        line_number=$(echo $status_line | awk '{ print $2}')

        case "$1" in
          "file")
            mix test $filename
            ;;
          "line")
            mix test $filename:$line_number
            ;;
          "all")
            mix test
            ;;
        esac

Source: nix-hour/code/29/home.nix at dfe7bfde9dc117b3f2d583eb418f2376f141757a · tweag/nix-hour · GitHub

save it somewhere as executable.

  1. I add these key-bindings in helix:
[keys.normal."t"]
a = ":run-shell-command zellij run -d right -- helix-wezterm-script.sh all"
f = ":run-shell-command zellij run -d right -- helix-wezterm-script.sh file"
l = ":run-shell-command zellij run -d right -- helix-wezterm-script.sh line"

And now you can run tl in a test file and it will run the test on that line in split.

Hopefully this is useful to someone.

5 Likes

All seems good, but how do we git the dap to work?

$ hx --health elixir
Configured language server: elixir-ls
Binary for language server: /run/current-system/sw/bin/elixir-ls
Configured debug adapter: None
Highlight queries: ✓
Textobject queries: ✓
Indent queries: ✓

1 Like

Did anyone else have problems with elixir-ls and helix over the last few weeks and/or can land a helping hand of making this make sense?

Issue
On all three Macs of mine, elixir-ls seems to display quite erratic/unstable start behavior, which ofc could be caused by helix itself, or more probable, my setup.

In many of my projects, it launches and then, even without any interaction, assumes the project directory changed, shuts down, and then closes the stream. This persists for the same project over the course of many subsequent attempts once it failed. Cleaning _build and .elixir-ls doesn’t help

The most curios thing is, I can then close helix, change directories, switch back again, and now it seems to consistently work. Closing and reopening the terminal (doesn’t matter which) starts the cycle anew.
Edit: It even seems to work if I open the terminal, move to the project directory, move to another one, switch back, and only then open it in helix for the first time. Moving to another directory first, then the target directory, and then starting without moving away doesn’t necessarily work.

Lexical or next-ls work reliably, for comparison. Maybe they don’t monitor the ‘directory change’?

My setup

  • helix main-branch
  • elixir-ls 23.0 (happened at 22.x as well, installed via homebrew or installed from source)
  • Elixir 1.17.2-otp-27 (mise or brew-installed, depending on the Mac)
  • Erlang 27.0.1 (mise or brew-installed, depending on the Mac)
  • nushell, but switching to zsh doesn’t seem to help

Logs
An exemplary helix.log file for a failing start can be found below.

Summary

2024-08-06T00:27:20.511 helix_lsp::client [INFO] Using custom LSP config: {"elixirLS":{"dialyzerEnabled":false}}
2024-08-06T00:27:20.511 helix_lsp::transport [INFO] elixir-ls -> {"jsonrpc":"2.0","method":"initialize","params":{"capabilities":{"general":{"positionEncodings":["utf-8","utf-32","utf-16"]},"textDocument":{"codeAction":{"codeActionLiteralSupport":{"codeActionKind":{"valueSet":["","quickfix","refactor","refactor.extract","refactor.inline","refactor.rewrite","source","source.organizeImports"]}},"dataSupport":true,"disabledSupport":true,"isPreferredSupport":true,"resolveSupport":{"properties":["edit","command"]}},"completion":{"completionItem":{"deprecatedSupport":true,"insertReplaceSupport":true,"resolveSupport":{"properties":["documentation","detail","additionalTextEdits"]},"snippetSupport":true,"tagSupport":{"valueSet":[1]}},"completionItemKind":{}},"formatting":{"dynamicRegistration":false},"hover":{"contentFormat":["markdown"]},"inlayHint":{"dynamicRegistration":false},"publishDiagnostics":{"tagSupport":{"valueSet":[1,2]},"versionSupport":true},"rename":{"dynamicRegistration":false,"honorsChangeAnnotations":false,"prepareSupport":true},"signatureHelp":{"signatureInformation":{"activeParameterSupport":true,"documentationFormat":["markdown"],"parameterInformation":{"labelOffsetSupport":true}}}},"window":{"workDoneProgress":true},"workspace":{"applyEdit":true,"configuration":true,"didChangeConfiguration":{"dynamicRegistration":false},"didChangeWatchedFiles":{"dynamicRegistration":true,"relativePatternSupport":false},"executeCommand":{"dynamicRegistration":false},"fileOperations":{"didRename":true,"willRename":true},"inlayHint":{"refreshSupport":false},"symbol":{"dynamicRegistration":false},"workspaceEdit":{"documentChanges":true,"failureHandling":"abort","normalizesLineEndings":false,"resourceOperations":["create","rename","delete"]},"workspaceFolders":true}},"clientInfo":{"name":"helix","version":"24.7 (0a4432b1)"},"initializationOptions":{"elixirLS":{"dialyzerEnabled":false}},"processId":38927,"rootPath":"/Users/username/git/elixir/todo","rootUri":"file:///Users/username/git/elixir/todo","workspaceFolders":[{"name":"todo","uri":"file:///Users/username/git/elixir/todo"}]},"id":0}
2024-08-06T00:27:20.520 helix_lsp::transport [ERROR] elixir-ls err <- "Running /Users/username/.elixir-ls/release/launch.sh\n"
2024-08-06T00:27:20.525 helix_lsp::transport [ERROR] elixir-ls err <- "Preferred shell nu is not supported, continuing in POSIX shell\n"
2024-08-06T00:27:20.525 helix_lsp::transport [ERROR] elixir-ls err <- "Looking for ASDF install\n"
2024-08-06T00:27:20.525 helix_lsp::transport [ERROR] elixir-ls err <- "ASDF not found\n"
2024-08-06T00:27:20.525 helix_lsp::transport [ERROR] elixir-ls err <- "Looking for mise executable\n"
2024-08-06T00:27:20.528 helix_lsp::transport [ERROR] elixir-ls err <- "mise executable found in /opt/homebrew/bin/mise, activating\n"
2024-08-06T00:27:20.538 helix_lsp::transport [ERROR] elixir-ls err <- "/Users/username/.elixir-ls/release/launch.sh: line 56: set,MIX_ARCHIVES,/Users/username/.local/share/mise/installs/elixir/1.17.2-otp-27/.mix/archives: No such file or directory\n"
2024-08-06T00:27:20.538 helix_lsp::transport [ERROR] elixir-ls err <- "/Users/username/.elixir-ls/release/launch.sh: line 57: set,MIX_HOME,/Users/username/.local/share/mise/installs/elixir/1.17.2-otp-27/.mix: No such file or directory\n"
2024-08-06T00:27:20.539 helix_lsp::transport [ERROR] elixir-ls err <- "/Users/username/.elixir-ls/release/launch.sh: line 58: set,PATH,/Users/username/.local/share/solana/install/active_release/bin:/Users/username/.elixir-ls/release:/Users/username/.local/share/mise/installs/elixir/latest/bin:/Users/username/.local/share/mise/installs/elixir/latest/.mix/escripts:/Users/username/.local/share/mise/installs/erlang/latest/bin:/Users/username/.local/share/mise/shims:/Users/username/.opam/default/bin:/Users/username/elp:/opt/homebrew/bin:/Users/username/.cargo/bin:/Applications/kitty.app/Contents/MacOS:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin: No such file or directory\n"
2024-08-06T00:27:20.869 helix_lsp::transport [ERROR] elixir-ls err <- "Installing ElixirLS release v0.23.0\n"
2024-08-06T00:27:20.870 helix_lsp::transport [ERROR] elixir-ls err <- "Running in /Users/username/Git/Elixir/todo\n"
2024-08-06T00:27:21.147 helix_lsp::transport [ERROR] elixir-ls err <- "Install complete\n"
2024-08-06T00:27:21.149 helix_lsp::transport [ERROR] elixir-ls err <- "ELS_ELIXIR_OPTS is not supported in current shell\n"
2024-08-06T00:27:21.816 helix_lsp::transport [INFO] elixir-ls <- {"jsonrpc":"2.0","method":"window/logMessage","params":{"message":"Started ElixirLS v0.23.0","type":3}}
2024-08-06T00:27:21.816 helix_lsp::transport [INFO] elixir-ls <- {"jsonrpc":"2.0","method":"window/logMessage","params":{"message":"Running in /Users/username/Git/Elixir/todo","type":3}}
2024-08-06T00:27:21.816 helix_term::application [INFO] window/logMessage: LogMessageParams { typ: Info, message: "Started ElixirLS v0.23.0" }
2024-08-06T00:27:21.816 helix_term::application [INFO] window/logMessage: LogMessageParams { typ: Info, message: "Running in /Users/username/Git/Elixir/todo" }
2024-08-06T00:27:21.818 helix_lsp::transport [INFO] elixir-ls <- {"jsonrpc":"2.0","method":"window/logMessage","params":{"message":"ElixirLS built with elixir \"1.17.2\" on OTP \"27\"","type":3}}
2024-08-06T00:27:21.818 helix_term::application [INFO] window/logMessage: LogMessageParams { typ: Info, message: "ElixirLS built with elixir \"1.17.2\" on OTP \"27\"" }
2024-08-06T00:27:21.818 helix_lsp::transport [INFO] elixir-ls <- {"jsonrpc":"2.0","method":"window/logMessage","params":{"message":"Running on elixir \"1.17.2 (compiled with Erlang/OTP 27)\" on OTP \"27\"","type":3}}
2024-08-06T00:27:21.818 helix_term::application [INFO] window/logMessage: LogMessageParams { typ: Info, message: "Running on elixir \"1.17.2 (compiled with Erlang/OTP 27)\" on OTP \"27\"" }
2024-08-06T00:27:21.819 helix_lsp::transport [INFO] elixir-ls <- {"jsonrpc":"2.0","method":"window/logMessage","params":{"message":"Protocols are not consolidated","type":3}}
2024-08-06T00:27:21.819 helix_term::application [INFO] window/logMessage: LogMessageParams { typ: Info, message: "Protocols are not consolidated" }
2024-08-06T00:27:21.819 helix_lsp::transport [INFO] elixir-ls <- {"jsonrpc":"2.0","method":"telemetry/event","params":{"name":"eep48","measurements":{},"properties":{"elixir_ls.eep48":"true","elixir_ls.elixir_release":"1.17","elixir_ls.elixir_version":"1.17.2","elixir_ls.erts_version":"15.0.1","elixir_ls.mix_env":"dev","elixir_ls.mix_target":"host","elixir_ls.otp_release":"27"}}}
2024-08-06T00:27:21.819 helix_term::application [INFO] Ignoring Unhandled notification from Language Server
2024-08-06T00:27:21.835 helix_lsp::transport [INFO] elixir-ls <- {"jsonrpc":"2.0","method":"window/logMessage","params":{"message":"Elixir sources not found (checking in /home/runner/work/elixir/elixir). Code navigation to Elixir modules disabled.","type":3}}
2024-08-06T00:27:21.835 helix_lsp::transport [INFO] elixir-ls <- {"jsonrpc":"2.0","method":"telemetry/event","params":{"name":"elixir_sources","measurements":{},"properties":{"elixir_ls.elixir_release":"1.17","elixir_ls.elixir_sources":"false","elixir_ls.elixir_version":"1.17.2","elixir_ls.erts_version":"15.0.1","elixir_ls.mix_env":"dev","elixir_ls.mix_target":"host","elixir_ls.otp_release":"27"}}}
2024-08-06T00:27:21.835 helix_term::application [INFO] window/logMessage: LogMessageParams { typ: Info, message: "Elixir sources not found (checking in /home/runner/work/elixir/elixir). Code navigation to Elixir modules disabled." }
2024-08-06T00:27:21.835 helix_term::application [INFO] Ignoring Unhandled notification from Language Server
2024-08-06T00:27:21.835 helix_lsp::transport [INFO] elixir-ls <- {"jsonrpc":"2.0","method":"telemetry/event","params":{"name":"otp_sources","measurements":{},"properties":{"elixir_ls.elixir_release":"1.17","elixir_ls.elixir_version":"1.17.2","elixir_ls.erts_version":"15.0.1","elixir_ls.mix_env":"dev","elixir_ls.mix_target":"host","elixir_ls.otp_release":"27","elixir_ls.otp_sources":"true"}}}
2024-08-06T00:27:21.835 helix_term::application [INFO] Ignoring Unhandled notification from Language Server
2024-08-06T00:27:21.852 helix_lsp::transport [INFO] elixir-ls <- {"jsonrpc":"2.0","method":"telemetry/event","params":{"name":"dialyzer_support","measurements":{},"properties":{"elixir_ls.dialyzer_support":"true","elixir_ls.dialyzer_support_reason":"","elixir_ls.elixir_release":"1.17","elixir_ls.elixir_version":"1.17.2","elixir_ls.erts_version":"15.0.1","elixir_ls.mix_env":"dev","elixir_ls.mix_target":"host","elixir_ls.otp_release":"27"}}}
2024-08-06T00:27:21.852 helix_term::application [INFO] Ignoring Unhandled notification from Language Server
2024-08-06T00:27:21.855 helix_lsp::transport [INFO] elixir-ls <- {"id":0,"jsonrpc":"2.0","result":{"capabilities":{"codeActionProvider":true,"codeLensProvider":{"resolveProvider":false},"completionProvider":{"triggerCharacters":[".","@","&","%","^",":","!","-","~"]},"definitionProvider":true,"documentFormattingProvider":true,"documentOnTypeFormattingProvider":{"firstTriggerCharacter":"\n"},"documentSymbolProvider":{"label":"Elixir outline"},"executeCommandProvider":{"commands":["expandMacro:wjVcFAkudGzudz8zR6uQRfTBPztI687F","getExUnitTestsInFile:wjVcFAkudGzudz8zR6uQRfTBPztI687F","manipulatePipes:wjVcFAkudGzudz8zR6uQRfTBPztI687F","mixClean:wjVcFAkudGzudz8zR6uQRfTBPztI687F","restart:wjVcFAkudGzudz8zR6uQRfTBPztI687F","spec:wjVcFAkudGzudz8zR6uQRfTBPztI687F"]},"foldingRangeProvider":true,"hoverProvider":true,"implementationProvider":true,"macroExpansion":true,"referencesProvider":true,"selectionRangeProvider":true,"signatureHelpProvider":{"triggerCharacters":["(",","]},"textDocumentSync":{"change":2,"openClose":true,"save":{"includeText":true}},"workspace":{"workspaceFolders":{"changeNotifications":false,"supported":false}},"workspaceSymbolProvider":true},"serverInfo":{"name":"ElixirLS","version":"0.23.0"}}}
2024-08-06T00:27:21.855 helix_lsp::transport [INFO] elixir-ls <- {"capabilities":{"codeActionProvider":true,"codeLensProvider":{"resolveProvider":false},"completionProvider":{"triggerCharacters":[".","@","&","%","^",":","!","-","~"]},"definitionProvider":true,"documentFormattingProvider":true,"documentOnTypeFormattingProvider":{"firstTriggerCharacter":"\n"},"documentSymbolProvider":{"label":"Elixir outline"},"executeCommandProvider":{"commands":["expandMacro:wjVcFAkudGzudz8zR6uQRfTBPztI687F","getExUnitTestsInFile:wjVcFAkudGzudz8zR6uQRfTBPztI687F","manipulatePipes:wjVcFAkudGzudz8zR6uQRfTBPztI687F","mixClean:wjVcFAkudGzudz8zR6uQRfTBPztI687F","restart:wjVcFAkudGzudz8zR6uQRfTBPztI687F","spec:wjVcFAkudGzudz8zR6uQRfTBPztI687F"]},"foldingRangeProvider":true,"hoverProvider":true,"implementationProvider":true,"macroExpansion":true,"referencesProvider":true,"selectionRangeProvider":true,"signatureHelpProvider":{"triggerCharacters":["(",","]},"textDocumentSync":{"change":2,"openClose":true,"save":{"includeText":true}},"workspace":{"workspaceFolders":{"changeNotifications":false,"supported":false}},"workspaceSymbolProvider":true},"serverInfo":{"name":"ElixirLS","version":"0.23.0"}}
2024-08-06T00:27:21.855 helix_lsp::transport [INFO] elixir-ls -> {"jsonrpc":"2.0","method":"initialized","params":{}}
2024-08-06T00:27:21.855 helix_lsp::transport [INFO] elixir-ls <- {"jsonrpc":"2.0","method":"telemetry/event","params":{"name":"lsp_request","measurements":{"elixir_ls.lsp_request_time":12},"properties":{"elixir_ls.elixir_release":"1.17","elixir_ls.elixir_version":"1.17.2","elixir_ls.erts_version":"15.0.1","elixir_ls.lsp_command":"initialize","elixir_ls.mix_env":"dev","elixir_ls.mix_target":"host","elixir_ls.otp_release":"27"}}}
2024-08-06T00:27:21.855 helix_term::application [INFO] Ignoring Unhandled notification from Language Server
2024-08-06T00:27:21.855 helix_lsp::transport [INFO] elixir-ls -> {"jsonrpc":"2.0","method":"workspace/didChangeConfiguration","params":{"settings":{"elixirLS":{"dialyzerEnabled":false}}}}
2024-08-06T00:27:21.855 helix_lsp::transport [INFO] elixir-ls -> {"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"languageId":"elixir","text":"defmodule Todo.List do\n  require Logger\n\n  defstruct(\n    next_id: 1,\n    entries: %{}\n  )\n\n  defimpl Collectable, for: Todo.List do\n    def into(original), do: {original, &into_callback/2}\n\n    defp into_callback(todo_list, {:cont, entry}) do\n      Todo.List.add_entry(todo_list, entry)\n    end\n\n    defp into_callback(todo_list, :done), do: todo_list\n    defp into_callback(_todo_list, :halt), do: :ok\n  end\n\n  @spec new([map()]) :: struct()\n  def new(entries \\\\ []) do\n    Enum.reduce(\n      entries,\n      %Todo.List{},\n      fn entry, todo_list_acc -> add_entry(todo_list_acc, entry) end\n    )\n  end\n\n  @spec add_entry(struct(), map()) :: struct()\n  def add_entry(todo_list, entry) do\n    entry = Map.put(entry, :id, todo_list.next_id)\n\n    new_entries =\n      Map.put(\n        todo_list.entries,\n        todo_list.next_id,\n        entry\n      )\n\n    %Todo.List{todo_list | entries: new_entries, next_id: todo_list.next_id + 1}\n  end\n\n  @spec entries(struct(), struct()) :: list()\n  def entries(todo_list, date) do\n    todo_list.entries\n    |> Stream.filter(fn {_, entry} -> entry.date == date end)\n    |> Enum.map(fn {_, entry} -> entry end)\n  end\n\n  def update_entry(todo_list, %{} = new_entry) do\n    update_entry(todo_list, new_entry.id, fn _ -> new_entry end)\n  end\n\n  @spec update_entry(struct(), number(), function()) :: struct()\n  def update_entry(todo_list, entry_id, updater_fun) do\n    case Map.fetch(todo_list.entries, entry_id) do\n      :error ->\n        Logger.warning(\"couldn't find todo entry\")\n        todo_list\n\n      {:ok, old_entry} ->\n        new_entry = updater_fun.(old_entry)\n        new_entries = Map.put(todo_list.entries, new_entry.id, new_entry)\n        %Todo.List{todo_list | entries: new_entries}\n    end\n  end\n\n  @spec delete_entry(struct(), integer()) :: struct()\n  def delete_entry(todo_list, entry_id) do\n    %Todo.List{todo_list | entries: Map.delete(todo_list.entries, entry_id)}\n  end\n\n  def size(todo_list) do\n    Kernel.map_size(todo_list.entries)\n  end\nend\n","uri":"file:///Users/username/git/elixir/todo/lib/todo/list.ex","version":0}}}
2024-08-06T00:27:21.855 helix_lsp::transport [INFO] elixir-ls <- {"id":1,"jsonrpc":"2.0","method":"workspace/configuration","params":{"items":[{"scopeUri":"file:///Users/username/git/elixir/todo","section":"elixirLS"}]}}
2024-08-06T00:27:21.856 helix_lsp::transport [INFO] elixir-ls -> {"jsonrpc":"2.0","result":[{"dialyzerEnabled":false}],"id":1}
2024-08-06T00:27:21.857 helix_lsp::transport [INFO] elixir-ls <- {"jsonrpc":"2.0","method":"window/logMessage","params":{"message":"Received client configuration via workspace/configuration\n%{\"dialyzerEnabled\" => false}","type":3}}
2024-08-06T00:27:21.857 helix_term::application [INFO] window/logMessage: LogMessageParams { typ: Info, message: "Received client configuration via workspace/configuration\n%{\"dialyzerEnabled\" => false}" }
2024-08-06T00:27:21.865 helix_lsp::transport [INFO] elixir-ls <- {"jsonrpc":"2.0","method":"telemetry/event","params":{"name":"lsp_config","measurements":{},"properties":{"elixir_ls.autoBuild":"true","elixir_ls.autoInsertRequiredAlias":"true","elixir_ls.dialyzerEnabled":"false","elixir_ls.dialyzerFormat":"","elixir_ls.elixir_release":"1.17","elixir_ls.elixir_version":"1.17.2","elixir_ls.enableTestLenses":"false","elixir_ls.envVariables":"false","elixir_ls.erts_version":"15.0.1","elixir_ls.fetchDeps":"false","elixir_ls.languageServerOverridePath":"false","elixir_ls.mixEnv":"test","elixir_ls.mixTarget":"host","elixir_ls.mix_env":"test","elixir_ls.mix_target":"host","elixir_ls.otp_release":"27","elixir_ls.projectDir":"false","elixir_ls.signatureAfterComplete":"true","elixir_ls.suggestSpecs":"true"}}}
2024-08-06T00:27:21.866 helix_term::application [INFO] Ignoring Unhandled notification from Language Server
2024-08-06T00:27:21.866 helix_lsp::transport [INFO] elixir-ls <- {"jsonrpc":"2.0","method":"window/logMessage","params":{"message":"Client does not support workspace/didChangeConfiguration dynamic registration","type":3}}
2024-08-06T00:27:21.866 helix_term::application [INFO] window/logMessage: LogMessageParams { typ: Info, message: "Client does not support workspace/didChangeConfiguration dynamic registration" }
2024-08-06T00:27:21.866 helix_lsp::transport [INFO] elixir-ls <- {"jsonrpc":"2.0","method":"window/logMessage","params":{"message":"Registering for workspace/didChangeWatchedFiles notifications","type":3}}
2024-08-06T00:27:21.866 helix_term::application [INFO] window/logMessage: LogMessageParams { typ: Info, message: "Registering for workspace/didChangeWatchedFiles notifications" }
2024-08-06T00:27:21.866 helix_lsp::transport [INFO] elixir-ls <- {"id":2,"jsonrpc":"2.0","method":"client/registerCapability","params":{"registrations":[{"id":"FEE08593811F1B86A9F49329BF058742F28A55FE","method":"workspace/didChangeWatchedFiles","registerOptions":{"watchers":[{"globPattern":"**/*.ex"},{"globPattern":"**/*.exs"},{"globPattern":"**/*.erl"},{"globPattern":"**/*.hrl"},{"globPattern":"**/*.yrl"},{"globPattern":"**/*.xrl"},{"globPattern":"**/*.eex"},{"globPattern":"**/*.leex"},{"globPattern":"**/*.heex"},{"globPattern":"**/*.sface"}]}}]}}
2024-08-06T00:27:21.866 helix_lsp::transport [INFO] elixir-ls -> {"jsonrpc":"2.0","result":null,"id":2}
2024-08-06T00:27:21.866 helix_lsp::transport [INFO] elixir-ls <- {"jsonrpc":"2.0","method":"window/logMessage","params":{"message":"client/registerCapability succeeded","type":3}}
2024-08-06T00:27:21.866 helix_lsp::transport [INFO] elixir-ls <- {"jsonrpc":"2.0","method":"window/logMessage","params":{"message":"Received workspace/didChangeConfiguration","type":3}}
2024-08-06T00:27:21.866 helix_term::application [INFO] window/logMessage: LogMessageParams { typ: Info, message: "client/registerCapability succeeded" }
2024-08-06T00:27:21.866 helix_term::application [INFO] window/logMessage: LogMessageParams { typ: Info, message: "Received workspace/didChangeConfiguration" }
2024-08-06T00:27:21.866 helix_lsp::transport [INFO] elixir-ls <- {"jsonrpc":"2.0","method":"window/logMessage","params":{"message":"Received client configuration via workspace/didChangeConfiguration\n%{\"dialyzerEnabled\" => false}","type":3}}
2024-08-06T00:27:21.866 helix_term::application [INFO] window/logMessage: LogMessageParams { typ: Info, message: "Received client configuration via workspace/didChangeConfiguration\n%{\"dialyzerEnabled\" => false}" }
2024-08-06T00:27:21.868 helix_lsp::transport [INFO] elixir-ls <- {"jsonrpc":"2.0","method":"window/logMessage","params":{"message":"Starting build with MIX_ENV: test MIX_TARGET: host","type":3}}
2024-08-06T00:27:21.868 helix_term::application [INFO] window/logMessage: LogMessageParams { typ: Info, message: "Starting build with MIX_ENV: test MIX_TARGET: host" }
2024-08-06T00:27:21.870 helix_lsp::transport [INFO] elixir-ls <- {"jsonrpc":"2.0","method":"window/showMessage","params":{"message":"Project directory change detected. ElixirLS will restart","type":2}}
2024-08-06T00:27:21.870 helix_lsp::transport [INFO] elixir-ls <- {"jsonrpc":"2.0","method":"telemetry/event","params":{"name":"lsp_reload","measurements":{},"properties":{"elixir_ls.elixir_release":"1.17","elixir_ls.elixir_version":"1.17.2","elixir_ls.erts_version":"15.0.1","elixir_ls.lsp_reload_reason":"project_dir_changed","elixir_ls.mix_env":"test","elixir_ls.mix_target":"host","elixir_ls.otp_release":"27"}}}
2024-08-06T00:27:21.870 helix_term::application [WARN] unhandled window/showMessage: ShowMessageParams { typ: Warning, message: "Project directory change detected. ElixirLS will restart" }
2024-08-06T00:27:21.870 helix_term::application [INFO] Ignoring Unhandled notification from Language Server
2024-08-06T00:27:21.886 helix_lsp::transport [INFO] elixir-ls <- {"jsonrpc":"2.0","method":"window/logMessage","params":{"message":"Loaded DETS databases in 19ms","type":3}}
2024-08-06T00:27:21.887 helix_term::application [INFO] window/logMessage: LogMessageParams { typ: Info, message: "Loaded DETS databases in 19ms" }
2024-08-06T00:27:21.918 helix_lsp::transport [INFO] elixir-ls <- {"jsonrpc":"2.0","method":"window/logMessage","params":{"message":"Compiling 11 files (.ex)","type":4}}
2024-08-06T00:27:21.918 helix_term::application [INFO] window/logMessage: LogMessageParams { typ: Log, message: "Compiling 11 files (.ex)" }
2024-08-06T00:27:21.996 helix_lsp::transport [INFO] elixir-ls <- {"jsonrpc":"2.0","method":"window/logMessage","params":{"message":"Generated todo app","type":4}}
2024-08-06T00:27:21.996 helix_term::application [INFO] window/logMessage: LogMessageParams { typ: Log, message: "Generated todo app" }
2024-08-06T00:27:21.999 helix_lsp::transport [INFO] elixir-ls <- {"jsonrpc":"2.0","method":"window/logMessage","params":{"message":"Compile took 133 milliseconds","type":3}}
2024-08-06T00:27:21.999 helix_lsp::transport [INFO] elixir-ls <- {"jsonrpc":"2.0","method":"telemetry/event","params":{"name":"build","measurements":{"elixir_ls.build_time":133},"properties":{"elixir_ls.build_result":"mix_compile_ok","elixir_ls.elixir_release":"1.17","elixir_ls.elixir_version":"1.17.2","elixir_ls.erts_version":"15.0.1","elixir_ls.mix_env":"test","elixir_ls.mix_target":"host","elixir_ls.otp_release":"27"}}}
2024-08-06T00:27:21.999 helix_term::application [INFO] window/logMessage: LogMessageParams { typ: Info, message: "Compile took 133 milliseconds" }
2024-08-06T00:27:21.999 helix_term::application [INFO] Ignoring Unhandled notification from Language Server
2024-08-06T00:27:27.915 helix_lsp::transport [ERROR] elixir-ls err: <- StreamClosed
2024-08-06T00:27:29.809 helix_lsp::transport [INFO] elixir-ls -> {"jsonrpc":"2.0","method":"textDocument/didClose","params":{"textDocument":{"uri":"file:///Users/username/git/elixir/todo/lib/todo/list.ex"}}}
2024-08-06T00:27:29.809 helix_lsp::transport [ERROR] elixir-ls err: <- IO(Os { code: 32, kind: BrokenPipe, message: "Broken pipe" })

Happy for any thoughts, I am somewhat flabbergasted!

1 Like

I’ve had problems on Neovim, and have jumped to lexical

I may be giving Hx a try this week but ran into an issue configuring elixir-ls. I downloaded the most recent release from GH and extracted to ~/.local/bin/elixir-ls and then made it available via $PATH. Running the command elixir-ls starts the language server and Hx can see it.

What else is needed to complete the configuration for formatter and queries?

➜  ~ hx --health elixir
Configured language servers:
  ✓ elixir-ls: /home/frigidcode/.local/bin/elixir-ls/elixir-ls
Configured debug adapter: None
Configured formatter: None
Highlight queries: ✘
Textobject queries: ✘
Indent queries: ✘
1 Like

This is what I have in my languages.toml file and it’s working great. Try this and see.

[[language]]
name = “elixir”
language-id = “elixir”
language-servers = [ “elixir-ls” ]
auto-format = true

[[language]]
name = “heex”
language-id = “phoenix-heex”
language-servers = [ “elixir-ls”, “tailwindcss-ls”, “vscode-html-language-server” ]
auto-format = true

Nadda, I tried your config earlier and no dice. I installed and build the grammars but nothing yet.

[[language]]
name = "elixir"
language-id = "elixir"
language-servers = [ "elixir-ls" ]
auto-format = true
➜  git:(main) ✗ hx --health elixir
Configured language servers:
  ✓ elixir-ls: /home/xxxx/.local/bin/elixir-ls/elixir-ls
Configured debug adapter: None
Configured formatter: None
Highlight queries: ✘
Textobject queries: ✘
Indent queries: ✘

I removed the helix install which was done using asdf and downloaded the pre-built binary from GH. Now tree-sitter highlighting works, I haven’t tested the elixir lsp yet.

Does/Did anyone else have problems with the formatter applying parentheses to DSLs (Ecto, Ash, …) using helix (and elixir-ls) - and/or ideas on how to resolve this?

Example:

postgres do
    table "tracks"
    repo Tunez.Repo
  end

turns to

postgres do
    table("tracks")
    repo(Tunez.Repo)
  end

Happens for both auto-format or using the helix’s :format cmd.
The formatter.ex is (presumably) correctly set up and respected by other editors where this doesn’t happen.

For completeness’ sake, my language config:

[[language]]
name = "elixir"
scope = "source.elixir"
file-types = ["ex", "exs", "heex"]
shebangs = ["elixir"]
roots = ["mix.exs"]
diagnostic-severity = "hint"
comment-token = "#"
indent = { tab-width = 2, unit = "  " }
language-servers = ["elixir-ls"]
auto-format = true

[language-server.elixir-ls]
command = "elixir-ls"
config = { elixirLS.dialyzerEnabled = true }

Other info (but happens for years, so no recent incompatibility)

  • macOS 15.5
  • helix main-branch
  • Elixir 18.4/OTP 28
  • elixir-ls 0.28.1