favicon here hometagsblog

Setting up Julia LSP for Helix

#setup #editor #helix #julia #lsp

Soc Virnyl Estela | 2023-05-19 | reading time: ~5min

The Julia programming language is a popular general programming language where the community leans more on scientific computing. And like most programming languages, Julia code can be written on most text editors such as IDEs.

Most editors today contain a set of tools such as git integration, built-in terminal, and a file picker and finder. Some editors that I tried are of the following:

Setting up Julia LSP for Helix§

Helix is a new editor I daily drive since a year ago. It follows a different style of editing than Vim/Neovim. It's model and keymaps are influenced from Kakoune which follows a select-action-execute model unlike the usual Vim action-select-execute model. I've been liking it so far!

As this post is about setting up LSP for Helix, setting up LSPs on other editors are pretty straight-forward e.g. Julia plugin on VSCode.

With helix, you have to write a simple configuration, specifically at languages.toml.

Writing the Julia LSP script for the LSP§

As helix does not have the option to pass the value of the current working directory of the file or buffer (maybe I am wrong, do correct me though!) unlike neovim's %:p:h, our script is like so:

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)

To understand what it's doing, the main focus of this script is actually the project_path variable. We first check if there is a JULIA_PROJECT environmental variable, explicitly set by the user. Base.load_path_expand achieves this. If there is none or if it is empty e.g. empty string, return nothing.

If the previous returns nothing, we use the Base.current_project(pwd()). And if it does not detect a Project.toml file from the current directory, we will fallback to Pkg.Types.Context().env.project_file and Base.active_project().

The project_path variable is very important as this will help us check which project you are in and where to run the LSP.

Julia's LSP is provided by LanguageServer.jl. But I don't like installing it on the global environment, hence, we have the ls_install_path variable. This ls_install_path variable assumes that you have installed the language server on one of the many DEPOT_PATH. Here, I assumed it has to be at ~/.julia/environments/helix-lsp. We need this variable because we need to add that path to the LOAD_PATH. You can check what LOAD_PATH is in the Julia REPL. Basically, it's just an array of paths where we load our environment for using and import statements. By default, it has a path to the global environment. This is why I have to add the popfirst! function since I don't want to use that one.

depot_path and symbol_server_path are optional but I like to make sure that JULIA_DEPOT_PATH exists.

Lastly, we then initialize the server by setting the LanguageServerInstance and setting the linter to true. We then run the run function with the server.

Adding it to language.toml§

Once we are done, we can finally either add it to a script file that can be executed within your PATH or just plain execute it like julia --project=@helix-lsp path/to/scriptfile.jl.

Here is a sample:

[[language]]
name = "julia"
scope = "source.julia"
injection-regex = "julia"
file-types = ["jl"]
roots = ["Project.toml", "Manifest.toml", "JuliaProject.toml"]
comment-token = "#"
language-server = { command = "julia", args = [
    "--project=@helix-lsp",
    "--sysimage=/home/uncomfy/.julia/environments/helix-lsp/languageserver.so",
    "--startup-file=no",
    "--history-file=no",
    "--quiet",
    "--sysimage-native-code=yes",
    "/home/uncomfy/.local/bin/julia-lsp.jl"
    ] }
indent = { tab-width = 4, unit = "    " }

It is up to you if you want to use PackageCompiler.jl to create a sysimage and make the LSP faster. Fortunately, you don't really need it since version 1.9.0 is very fast now.

There is runserver, why not use that?§

There are some gotchas with using runserver. If I can recall correctly, my issue was it cannot detect the correct paths inside helix and my script for neovim and kakoune is to detect where the file is located and if the location contains Project.toml or Manifest.toml and set it as the env_path. This can be seen with my kak-lsp julia config at line 168-170 on the default kak-lsp.toml

As you may noticed, it's not in the script we have discussed before this section. So I probably must have forgotten why I wrote that script only to end up that there is no way to check the path to the buffer or file. Therefore, I recommend to run helix within the root of your Julia project.

Let me know if you are not experiencing any issues with runserver since someone suggested that it works perfectly fine now. See https://github.com/helix-editor/helix/issues/669#issuecomment-1207489723

Articles from blogs I follow around the net

Perma-Vuln: D-Link DIR-859, CVE-2024-0769

Recently Sift caught an interesting payload. As it turns out, the exploit was CVE-2024-0769, which is now tagged here: D-Link DIR-859 Information Disclosure Attempt . This vulnerability is a path traversal leading to information disclosure. But, perhaps mo…

via GreyNoise LabsJune 25, 2024

Synergy Greg

Synergy Greg would like to see you in His office, it is the one down the hall, past the cubicles and dreary faces, uplifted only when He deigns to venture forth. You will know Him when you see Him, He is the one composed, of a thousand writhing forms,…

via LudicityJune 22, 2024

Status update, June 2024

Hi all! This status update will be shorter than usual because I had a lot less free time for my open-source projects than usual this month. Indeed, I recently joined SNCF Réseau (the company responsible for the French railway infrastructure) to work on OSR…

via emersionJune 18, 2024

Why People are Angry over Go 1.23 Iterators

NOTE: This is based on, but completely rewritten, from a Twitter post: https://x.com/TheGingerBill/status/1802645945642799423 TL;DR It makes Go feel too “functional” rather than being an unabashed imperative language. I recently saw a post on Twitter showi…

via Articles on gingerBillJune 17, 2024

My RSS feed has been upgraded ✨

I did some integration work to include posts written for other publications in my RSS feed. Apologies if you see some duplicated items! 📪

via Rob O'LearyJune 15, 2024

Programming at the edge with Fastly Compute

So you’ve heard about computing at the edge, and you’ve heard that Fastly let’s you run JavaScript, Go, Rust and any other language that compiles to Wasm at the edge… well, let’s take a look and while we’re at it let’s try and understand how caching works …

via Posts on integralistJune 12, 2024

Generated by openring-rs