Neovim + LanguageServer.jl

Your comment made me think about why it fails for me as I’m setting the server path in the local config. I found the problem which is happening because I insist on printing out bayes formula when I start julia. The PR for fixing julials config in nvim-lspconfig is here: Fixed a small gotcha where the server_path is set to nonsense if you print stuff in your startup.jl by DoktorMike · Pull Request #1017 · neovim/nvim-lspconfig · GitHub

What about GitHub - hrsh7th/nvim-compe: Auto completion Lua plugin for nvim ?
Does this do autocomplete for Julia?

I’m trying to figure out if I should go with lspconfig or coc; do you have any comments on the pros and cons?

2 Likes

My current setup is using nvim-compe + built in lsp for neovim. I like this set up for other languages. For Julia, I have disabled lsp because it hangs for me on some projects and I haven’t had time to debug it. On small files and small projects though, things seem to work well with this set up. It was quite a bit of configuration (nvim-compe + lspsaga + lspconfig + other plugins) though.

I’m guessing coc is more straightforward, but I haven’t used it extensively and not tried it in a long time. So I’m not sure what the advantages or the differences are exactly. If others have thoughts, feel free to chime in.

2 Likes

Hi all,
Looking for a little help. :LspInstall julials results in “Could not find language server for julials

I have included the following snippet
require'lspconfig'.julials.setup{}
in my init.lua

Somehow lspinstall isn’t aware of the lspconfig julials server config. Anyone come across this before?

From the listed servers in nvim-lspinstall/lua/lspinstall/servers at main · kabouzeid/nvim-lspinstall · GitHub nvim-lspinstall doesn’t look to have one for julials.

(The recommended install method in https://github.com/neovim/nvim-lspconfig/blob/master/lua/lspconfig/julials.lua#L44 works well though.)

Okay, great! I had already stumbled onto this file, but wasn’t sure how to implement.

The line you referenced will install LanguageServer.jl & SymbolServer.jl but that alone will not allow me to do something like :LspInstall julials.

I even tried taking the entire config from nvim-lspconfig/julials.lua and integrating with my config but there is still no language server attaching to the buffer and no change when trying to LspInstall.

How does Neovim know that there is a julials option that should then kick off the command to start the LanguageServer.jl?

Could be worth doing a :checkhealth if you’ve not already to see whether there’s anything obvious that’s causing it. Do any other language servers work in your setup?

I struggled with the default settings for a while before just customising it enough to work for me with (the relevant parts):

local util = require "lspconfig/util"

local cmd = {
  "julia",
  "--startup-file=no",
  "--history-file=no",
  "/home/mike/.config/nvim/lua/mike/lsp.jl"
}

require"lspconfig".julials.setup {
    cmd = cmd,
    on_new_config = function(new_config, _)
        local server_path = vim.fn.system "julia --startup-file=no -q -e 'print(dirname(dirname(Base.find_package(\"LanguageServer\"))))'"
        local new_cmd = vim.deepcopy(cmd)
        table.insert(new_cmd, 2, "--project=" .. server_path)
        new_config.cmd = new_cmd
    end,
    filetypes = {"julia"},
    root_dir = function(fname)
        return util.find_git_ancestor(fname) or vim.fn.getcwd()
    end
}

and lsp.jl (since I didn’t like passing it all as an argument):

using LanguageServer
using LanguageServer.SymbolServer

let env = Base.load_path(),
    dir = pwd(),
    depot_path = get(ENV, "JULIA_DEPOT_PATH", ""),
    project_path = dirname(something(Base.current_project(pwd()), Base.load_path_expand(LOAD_PATH[2])))

    @info env dir project_path depot_path

    server = LanguageServer.LanguageServerInstance(stdin, stdout, project_path, depot_path)
    server.runlinter = true
    run(server)
end

(Hopefully that might help you out a bit.)

2 Likes

Helped me out A TON!

Thank you! I had many of the parts, but couldn’t find the glue!

One more question - Not really sure if this is a LanguageServer.jl or a Neovim-LspConfig issue, but could any of this be shared upstream in order to get things working “out of the box” for others?

PS - You were right to ask about :checkhealth because that was my first thought, but didn’t find anything seriously broken.

2 Likes

Glad it’s working now.

Yes, things should be made to just work with the defaults. I hadn’t spent the time to workout whether it was just my config that was causing issues so haven’t reported anything upstream yet.

2 Likes

Thanks for sharing that configuration @mike. I used that to try out LanguageServer for the first time just now. I made some modifications to your setup that I believe are strict improvements (borderline bugfixes?).

init.vim:

lua << EOF
local cmd = {
    "julia",
    "--startup-file=no",
    "--history-file=no",
    "/home/fredrik/.config/nvim/lsp-julia/run.jl"
}
require'lspconfig'.julials.setup{
    cmd = cmd,
    -- Why do I need this? Shouldn't it be enough to override cmd on the line above?
    on_new_config = function(new_config, _)
        new_config.cmd = cmd
    end,
    filetypes = {"julia"},
}
EOF

And in /home/fredrik/.config/nvim/lsp-julia/ I have

$ tree ~/.config/nvim/lsp-julia/
/home/fredrik/.config/nvim/lsp-julia/
├── Manifest.toml
├── Project.toml
└── run.jl

where ~/.config/nvim/lsp-julia/Project.toml contains the LS deps

[deps]
LanguageServer = "2b0e0bc5-e4fd-59b4-8912-456d1b03d8d7"
SymbolServer = "cf896787-08d5-524d-9de7-132aaa0cb996"

and ~/.config/nvim/lsp-julia/run.jl is

# Load LanguageServer from the project next to this file
## Save old load path
old_load_path = copy(LOAD_PATH)
push!(empty!(LOAD_PATH), @__DIR__)
## Load packages
using LanguageServer, SymbolServer
## Restore old load path
append!(empty!(LOAD_PATH), old_load_path)

# Figure out the active project
## This works if you are *not* using a Pkg.activate based workflow
## e.g. if you set up LOAD_PATH manually or set JULIA_PROJECT=@. in
## .bashrc or equivalent
project_path = Base.active_project()
## This works if you are using a Pkg.activated based workflow
# project_path = something(Base.current_project(pwd()), Base.active_project())

# Depot path for the server to index
depot_path = get(ENV, "JULIA_DEPOT_PATH", "")

# Start the server
@info "Running julia language server" project_path depot_path
server = LanguageServer.LanguageServerInstance(stdin, stdout, project_path, depot_path)
server.runlinter = true
run(server)

The major improvements compared to your version is that (i) LanguageServer is running in a proper environment defined by the Project.toml and Manifest.toml in ~/.config/nvim/lsp-julia/Project.toml and (ii) it is independent of the environment (e.g. JULIA_LOAD_PATH).

7 Likes

Nice, thanks @fredrikekre. That does look like it should improve things.

Most things seems to work for me, but I get

import JSON     ■ Missing reference: JSON

for all package importsl. I verified from the logs that the server is running in the correct project where JSON is installed. Am I missing something?

Yeah, it’s not getting the right imports for me either with those changes. Not 100% sure what needs adjusted though.

Are you saying it works with your configuration from Neovim + LanguageServer.jl - #47 by mike ?

Yup, my current config picks them all up fine.

Okay, strange. I get the same errors with your setup.

Here’s the diff in my dotfiles that’s working for me:

I think the project_path in the set up by @mike is not matching the one in the built in lsp. The diff I’ve shared above uses the same set up (from @fredrikekre’s repo) and changes the project_path to match the one in the built in LSP.

There’s also some changes I made to make the diff portable (i.e. using vim.fn.expand to expand the home directory) and an example of how to get detailed LSP log messages (vim.lsp.set_log_level('trace')).

I’m sharing here in case others are interested in trying it out. Once we figure out exactly what is going on we can update the nvim-lspconfig to do the right thing.

Here’s a link to the repo I’m using for this test:

It’s very simple with just a couple of dependencies:

   6   │ [deps]
   7   │ JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
   8   │ JSON3 = "0f8b85d8-7281-11e9-16c2-39a750bddbf1"
───────┴──────────────────────────────────────────────────────────────────────────
4 Likes

Thanks @kdheepak, that config works nicely.

I found the problems:

  • I was using Julia 1.6.2 which SymbolServer didn’t like (julia-vscode/SymbolServer.jl#224).
  • I was passing the path to the Project.toml file, which SymbolServer didn’t like since it unconditionally appended Project.toml to the path and thus looked for things in .../Project.toml/Project.toml (julia-vscode/SymbolServer.jl#227).

Here is my new configuration: fredrikekre/.dotfiles. Relevant extract:

init.vim:

lua << EOF
local cmd = {
    "julia",
    "--startup-file=no",
    "--history-file=no",
    vim.fn.expand("~/.config/nvim/lsp-julia/run.jl")
}
require'lspconfig'.julials.setup{
    cmd = cmd,
    -- Why do I need this? Shouldn't it be enough to override cmd on the line above?
    on_new_config = function(new_config, _)
        new_config.cmd = cmd
    end,
    filetypes = {"julia"},
}
-- vim.lsp.set_log_level("debug")
EOF

~/.config/nvim/lsp-julia/run.jl:

# Load LanguageServer from the project next to this file
## Save old load path
old_load_path = copy(LOAD_PATH)
push!(empty!(LOAD_PATH), @__DIR__)
## Load packages
using LanguageServer, SymbolServer
## Restore old load path
append!(empty!(LOAD_PATH), old_load_path)

# Figure out the active project
## This configuration is a good default
project_path = let
    dirname(something(
        ## 1. Finds an explicitly set project (JULIA_PROJECT)
        Base.load_path_expand((
            p = get(ENV, "JULIA_PROJECT", nothing);
            p === nothing ? nothing : isempty(p) ? nothing : p
        )),
        ## 2. Look for a Project.toml file in the current working directory,
        ##    or parent directories, with $HOME as an upper boundary
        Base.current_project(),
        ## 3. First entry in the load path
        get(Base.load_path(), 1, nothing),
        ## 4. Fallback to default global environment,
        ##    this is more or less unreachable
        Base.load_path_expand("@v#.#"),
    ))
end

# Depot path for the server to index (empty uses default).
depot_path = get(ENV, "JULIA_DEPOT_PATH", "")

# Start the server
@info "Running julia language server" VERSION project_path depot_path
server = LanguageServer.LanguageServerInstance(stdin, stdout, project_path, depot_path)
server.runlinter = true
run(server)
7 Likes

This is working perfectly for me! :point_up: