Helix + LanguageServer.jl + JuliaFormatter.jl

I’m trying to move from vscode to helix to save my RAM for Julia.

I’m on helix 25.07.01, julia 1.12.1. I haven’t configured anything except

$ cat ~/.config/helix/config.toml 
theme = "autumn_night"

$ cat ~/.config/helix/languages.toml 
[[language]]
name = "julia"
auto-format = true

If I open a julia file in helix I can see the language server is running

$ pgrep -laf julia
4072615 /usr/bin/julia --startup-file=no --history-file=no --quiet -e using LanguageServer; runserver()

and it’s doing its job: it knows about Julia keywords and types

But it’s unreliable around formatting.

It’s slow to format on the first save. It takes long enough that I can easily cancel it by changing the text. And it seems that if I am making lots of small changes and saving quickly, it never gets a chance to fully compile so it never formats my code.

The = button gives an error the first time I try to use it.

No configured language server supports range formatting

I have to wait, and then on the second time it works. This error is really confusing to me because it says “no support” but then a second later clearly there is support

If I press = multiple times in a row while it’s trying to figure itself out I’m able to lock the session up hard

Am I doing something wrong? neovim people, do you ever have these kinds of problems?


I’ve read the sysimage advice (repeated here and here). I tried

$ time julia --startup-file=no --project=@helix-lsp -e 'import Pkg; Pkg.add(["LanguageServer", "PackageCompiler", "JuliaFormatter"]); Pkg.update();using PackageCompiler; create_sysimage([:LanguageServer, :JuliaFormatter], sysimage_path=dirname(Pkg.Types.Context().env.project_file) * "/languageserver.so")'

I added this to language.toml:

[language-server.julia]
timeout = 60
command = "julia"
args = [
  "--project=@helix-lsp",
  "--startup-file=no",
  "--history-file=no",
  "--quiet",
  "-J/home/kousu/.julia/environments/helix-lsp/languageserver.so",
  "--sysimage-native-code=yes",
  "-e",
  """"
  import Pkg
project_path = let
    dirname(something(
        Base.load_path_expand((
            p = get(ENV, "JULIA_PROJECT", nothing);
            isnothing(p) ? nothing : isempty(p) ? nothing : p
        )),
        Base.current_project(pwd()),
        Pkg.Types.Context().env.project_file,
        Base.active_project(),
    ))
    end
ls_install_path = joinpath(get(DEPOT_PATH, 1, joinpath(homedir(), ".julia")), "environments", "helix-lsp");
pushfirst!(LOAD_PATH, ls_install_path);
using LanguageServer;
popfirst!(LOAD_PATH);
depot_path = get(ENV, "JULIA_DEPOT_PATH", "")
symbol_server_path = joinpath(homedir(), ".cache", "helix", "julia_lsp_symbol_server")
mkpath(symbol_server_path)
server = LanguageServer.LanguageServerIn`stance(stdin, stdout, project_path, depot_path, nothing, symbol_server_path, true)
server.runlinter = true
run(server)
"""
]

And I can see this is the version running now

$ pgrep -laf julia
23425 /usr/bin/julia --project=@helix-lsp --startup-file=no --history-file=no --quiet -J/home/kousu/.julia/environments/helix-lsp/languageserver.so --sysimage-native-code=yes -e "   import Pkg project_path = let     dirname(something(         Base.load_path_expand((             p = get(ENV, "JULIA_PROJECT", nothing);             isnothing(p) ? nothing : isempty(p) ? nothing : p         )),         Base.current_project(pwd()),         Pkg.Types.Context().env.project_file,         Base.active_project(),     ))     end ls_install_path = joinpath(get(DEPOT_PATH, 1, joinpath(homedir(), ".julia")), "environments", "helix-lsp"); pushfirst!(LOAD_PATH, ls_install_path); using LanguageServer; popfirst!(LOAD_PATH); depot_path = get(ENV, "JULIA_DEPOT_PATH", "") symbol_server_path = joinpath(homedir(), ".cache", "helix", "julia_lsp_symbol_server") mkpath(symbol_server_path) server = LanguageServer.LanguageServerInstance(stdin, stdout, project_path, depot_path, nothing, symbol_server_path, true) server.runlinter = true run(server) 

But it’s still slow.

What am I doing wrong? How do I debug this (can I see a log of what the language server is loading?). How do I make it faster?


I just tried to see if vscode has the same first-load problem and it does. It won’t format my code until after it finished with this:

Screenshot From 2025-11-09 00-37-14

and then gives me this error about it crashing:

Once it gives that error, though, formatting is fast.

1 Like

I used the experimental juliac to build a statically linked formatter:

$ cat jlfmt.jl 
using JuliaFormatter
function @main(ARGS::Vector{String})::Cint
    try
        for f in ARGS
            if f == "-"
                print(format_text(read(stdin, String)))
            else
                format_file(f)
            end
        end
        return 0
    catch e
        @error "err:" e
        return 1
    end
end

This didn’t work unfortunately, there were too many functions that can’t be trimmed yet I guess

julia /usr/share/julia/juliac/juliac.jl --experimental --output-exe ./jlfmt --trim ./jlfmt.jl 

This did

julia /usr/share/julia/juliac/juliac.jl --output-exe ./jlfmt ./jlfmt.jl 

It unfortunately took minutes and made a very large binary

$ ls -lh jlfmt
-rwxr-xr-x 1 kousu kousu 264M Nov  9 01:27 jlfmt

but it worked in the end.

Here’s my config now:

$ cat ~/.config/helix/config.toml 
theme = "autumn_night"

$ cat ~/.config/helix/languages.toml 
[[language]]
name = "julia"
auto-format = true
formatter = { command = "/home/kousu/src/jlfmt", args = ["-"] }

With this, formatting on save or with = works instantly.

This doesn’t seem like a good solution. The language server is supposed to handle formatting. I wish to get it to a point where it does, out of the box.

Unfortunately, format-on-save swallows errors silently; if I mangle the code enough it can’t be formatted then = shows an error (but only the first line of the error, and quickly) and format-on-save doesn’t at all.

1 Like

My go-to configuration would be similar to the one outlined here: GitHub - milanglacier/Helix-Zellij-AI-REPL-Workflow: workflow integration for Helix, Zellij, AI Code Completion, AI CLIs, and REPLs built around two minimal shell scripts with nearly zero dependencies.
I’d like to slightly modify it by using Gemini models with Crush by Charm instead of Claude and Aider and in general making it as simple as possible with a Julia focus. This is a new setup for me, so I don’t have much code to share. I was also wondering whether MuxDisplay.jl by @goerz might work with it, or if it could be adapted to fit such a configuration. I don’t have much experience with serious coding in modal editors, however, I’d like to transition to such a workflow.

Not sure about the --quiet flag, however, you should be able to see the logs in Helix with :log-open.