{ lib, pkgs, config, ... }: let inherit (builtins) attrNames; inherit (lib.options) mkEnableOption mkOption; inherit (lib.types) listOf enum; inherit (lib.modules) mkIf mkMerge; inherit (lib.meta) getExe; inherit (lib.nvim.types) mkGrammarOption; inherit (lib.generators) mkLuaInline; inherit (lib.nvim.attrsets) mapListToAttrs; inherit (lib.nvim.dag) entryBefore; defaultServers = ["jdtls"]; servers = { jdtls = { enable = true; cmd = mkLuaInline /* lua */ '' { '${getExe (pkgs.julia.withPackages ["LanguageServer"])}', '--startup-file=no', '--history-file=no', '-e', [[ # Load LanguageServer.jl: attempt to load from ~/.julia/environments/nvim-lspconfig # with the regular load path as a fallback ls_install_path = joinpath( get(DEPOT_PATH, 1, joinpath(homedir(), ".julia")), "environments", "nvim-lspconfig" ) pushfirst!(LOAD_PATH, ls_install_path) using LanguageServer popfirst!(LOAD_PATH) depot_path = get(ENV, "JULIA_DEPOT_PATH", "") 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 @info "Running language server" VERSION pwd() project_path depot_path server = LanguageServer.LanguageServerInstance(stdin, stdout, project_path, depot_path) server.runlinter = true run(server) ]], } ''; filetypes = ["julia"]; root_markers = ["Project.toml" "JuliaProject.toml"]; on_attach = mkLuaInline /* lua */ '' function(_, bufnr) vim.api.nvim_buf_create_user_command(bufnr, 'LspJuliaActivateEnv', activate_julia_env, { desc = 'Activate a Julia environment', nargs = '?', complete = 'file', }) end ''; }; }; cfg = config.vim.languages.julia; in { options = { vim.languages.julia = { enable = mkEnableOption "Julia language support"; treesitter = { enable = mkEnableOption "Julia treesitter" // {default = config.vim.languages.enableTreesitter;}; package = mkGrammarOption pkgs "julia"; }; lsp = { enable = mkEnableOption "Java LSP support" // {default = config.vim.lsp.enable;}; servers = mkOption { description = '' Julia LSP Server to Use ::: {.note} The entirety of Julia is bundled with nvf, if you enable this option, since there is no way to provide only the LSP server. If you want to avoid that, you have to change [](#opt-vim.languages.julia.lsp.package) to use the Julia binary in {env}`PATH` (set it to `null`), and add the `LanguageServer` package to Julia in your devshells. ::: ''; type = listOf (enum (attrNames servers)); default = defaultServers; }; }; }; }; config = mkIf cfg.enable (mkMerge [ (mkIf cfg.treesitter.enable { vim.treesitter.enable = true; vim.treesitter.grammars = [cfg.treesitter.package]; }) (mkIf cfg.lsp.enable { vim.luaConfigRC.julia-util = entryBefore ["lsp-servers"] /* lua */ '' local function activate_julia_env(path) assert(vim.fn.has 'nvim-0.10' == 1, 'requires Nvim 0.10 or newer') local bufnr = vim.api.nvim_get_current_buf() local julials_clients = vim.lsp.get_clients { bufnr = bufnr, name = 'julials' } assert( #julials_clients > 0, 'method julia/activateenvironment is not supported by any servers active on the current buffer' ) local function _activate_env(environment) if environment then for _, julials_client in ipairs(julials_clients) do julials_client:notify('julia/activateenvironment', { envPath = environment }) end vim.notify('Julia environment activated: \n`' .. environment .. '`', vim.log.levels.INFO) end end if path then path = vim.fs.normalize(vim.fn.fnamemodify(vim.fn.expand(path), ':p')) local found_env = false for _, project_file in ipairs(root_files) do local file = vim.uv.fs_stat(vim.fs.joinpath(path, project_file)) if file and file.type then found_env = true break end end if not found_env then vim.notify('Path is not a julia environment: \n`' .. path .. '`', vim.log.levels.WARN) return end _activate_env(path) else local depot_paths = vim.env.JULIA_DEPOT_PATH and vim.split(vim.env.JULIA_DEPOT_PATH, vim.fn.has 'win32' == 1 and ';' or ':') or { vim.fn.expand '~/.julia' } local environments = {} vim.list_extend(environments, vim.fs.find(root_files, { type = 'file', upward = true, limit = math.huge })) for _, depot_path in ipairs(depot_paths) do local depot_env = vim.fs.joinpath(vim.fs.normalize(depot_path), 'environments') vim.list_extend( environments, vim.fs.find(function(name, env_path) return vim.tbl_contains(root_files, name) and string.sub(env_path, #depot_env + 1):match '^/[^/]*$' end, { path = depot_env, type = 'file', limit = math.huge }) ) end environments = vim.tbl_map(vim.fs.dirname, environments) vim.ui.select(environments, { prompt = 'Select a Julia environment' }, _activate_env) end end ''; vim.lsp.servers = mapListToAttrs (n: { name = n; value = servers.${n}; }) cfg.lsp.servers; }) ]); }