Integrating JET.jl with neovim

JET.jl is an experimental code analyzer for Julia: https://github.com/aviatesk/JET.jl.

Edit: I’ve made the following into a plugin for neovim: https://github.com/kdheepak/JET.nvim

The following post shows how to integrate JET.jl with null-ls.nvim.

My current version of neovim:

$ nvim --version | head -1
NVIM v0.6.0-dev+366-g270cc1d70

Add the following lua block to your neovim configuration:

local null_ls = require("null-ls")
local helpers = require("null-ls.helpers")

local jet_julia = {
  method = null_ls.methods.DIAGNOSTICS,
  filetypes = { "julia" },
  generator = null_ls.generator({
    command = "jet",
    to_stdin = true,
    from_stderr = true,
    timeout = 15000, -- arbitrary large number. Depending on how long JET takes, you may need to increase this number
    format = "line",
    check_exit_code = function(code)
      return code <= 1
    end,
    args = function(params)
      local filename = params.bufname
      return { filename }
    end,
    on_output = helpers.diagnostics.from_patterns({
      {
        pattern = [[(%d+):(.*)]],
        groups = { "row", "message" },
      },
    }),
  }),
}

null_ls.register(jet_julia)

This will set up null-ls to launch a command line process called jet.

Next, create the following script called jet. I store this in ~/local/bin/jet:

#!/usr/bin/env julia --project=~/.julia/environments/nvim-null-ls

using JET

filename = ARGS[1]

function print_reports(io, result)

    reports = JET.get_reports(result)
    postprocess = JET.gen_postprocess(result.res.actual2virtual)

    JET.with_bufferring(:color => get(io, :color, false)) do io
        toplevel_linfo_hash = hash(:dummy)
        wrote_linfos = Set{UInt64}()
        for report in reports
            new_toplevel_linfo_hash = hash(first(report.vst))
            if toplevel_linfo_hash != new_toplevel_linfo_hash
                toplevel_linfo_hash = new_toplevel_linfo_hash
                wrote_linfos = Set{UInt64}()
            end
            print_report(io, report)
        end
    end |> postprocess |> (x->print(io::IO,x))

end

function print_report(io, report::JET.InferenceErrorReport, depth = 1)
    if length(report.vst) == depth # error here
        return print_error_report(io, report)
    end
    print_report(io, report, depth + 1)
end

function print_error_report(io, report::JET.InferenceErrorReport)
    frame = report.vst[1]
    printstyled(io, string(frame.line), ": ")
    printstyled(io, report.msg, ": "; color = JET.ERROR_COLOR)
    JET.print_signature(io, report.sig,
                    (; annotate_types = true); # always annotate types for errored signatures
                    bold = true,
                    )
end

function print_error_report(io, report)
    printstyled(io, report.msg, "\n"; color = JET.ERROR_COLOR)
end

r = report_file(filename)
print_reports(stderr, r)

This must be available in your PATH. You will also have to make it executable, i.e. chmod +x ~/local/bin/jet.

This script uses a shebang to run Julia with a project environment. You can create this environment anywhere and modify the project path in the script above to the appropriate location. Make sure you install JET.jl in that environment.

Finally, open any Julia file. The following is a couple of screenshots from the demo.jl file in the JET repository.


6 Likes

See this FAQ for Linux. I have to use that modify version at least.

1 Like

Ah, thanks for sharing, I didn’t know about that. I’ve made a git repository over here:

I’ll update this going forward.

1 Like

If I make many quick (well, not that quick, just quick enough for JET not to terminate before the next edit) this deadlocks somehow. I see multiple idle JET processes in top, and no messages in nvim.

That’s annoying. Maybe the timeout is too large for it to work this way? I’ll try to reproduce. We might have to open an issue on the null-ls repo for suggestions.

@fredrikekre I changed the plugin to make it work using a infinite while loop. JET uses Revise to reload the file, so this works on save.

This is still not ideal though. I believe Neovim sends publishDiagnostics on every change, and that is forwarded to the script. However, since the file is not saved, the script just sends back the last known errors back to neovim. On save, once Revise does its thing, JET reruns and the script sends back updated diagnostics to neovim. So at least right now only one Julia process is spawned. I’m sure there are better ways to do this.

Ideally, I’d like the script to know if the file has changed or not before running report_file from JET. Perhaps there is a function in Revise that can help here.