From e47f268b776a95b80cd0e55599f3647f949b1749 Mon Sep 17 00:00:00 2001 From: sjcobb Date: Thu, 26 Jun 2025 21:09:59 +0100 Subject: [PATCH] convert julia module --- modules/plugins/languages/julia.nix | 213 ++++++++++++++++++---------- 1 file changed, 140 insertions(+), 73 deletions(-) diff --git a/modules/plugins/languages/julia.nix b/modules/plugins/languages/julia.nix index 8c48b070..16c3164f 100644 --- a/modules/plugins/languages/julia.nix +++ b/modules/plugins/languages/julia.nix @@ -4,63 +4,82 @@ config, ... }: let - inherit (builtins) attrNames isList; + inherit (builtins) attrNames; inherit (lib.options) mkEnableOption mkOption; - inherit (lib.types) either listOf package str enum bool nullOr; + inherit (lib.types) listOf enum; inherit (lib.modules) mkIf mkMerge; - inherit (lib.strings) optionalString; + inherit (lib.meta) getExe; inherit (lib.nvim.types) mkGrammarOption; - inherit (lib.nvim.lua) expToLua; + inherit (lib.generators) mkLuaInline; + inherit (lib.nvim.attrsets) mapListToAttrs; + inherit (lib.nvim.dag) entryBefore; - defaultServer = "julials"; + defaultServers = ["jdtls"]; servers = { - julials = { - package = pkgs.julia.withPackages ["LanguageServer"]; - internalFormatter = true; - lspConfig = '' - lspconfig.julials.setup { - capabilities = capabilities, - on_attach = default_on_attach, - cmd = ${ - if isList cfg.lsp.package - then expToLua cfg.lsp.package - else '' - { - "${optionalString (cfg.lsp.package != null) "${cfg.lsp.package}/bin/"}julia", - "--startup-file=no", - "--history-file=no", - "--eval", - [[ - using LanguageServer - - 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) - ]] - } - '' - } - } - ''; + 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 + ''; }; }; @@ -76,11 +95,10 @@ in { }; lsp = { - enable = mkOption { - type = bool; - default = config.vim.lsp.enable; + enable = mkEnableOption "Java LSP support" // {default = config.vim.lsp.enable;}; + servers = mkOption { description = '' - Whether to enable Julia LSP support. + Julia LSP Server to Use ::: {.note} The entirety of Julia is bundled with nvf, if you enable this @@ -92,21 +110,8 @@ in { Julia in your devshells. ::: ''; - }; - - server = mkOption { - type = enum (attrNames servers); - default = defaultServer; - description = "Julia LSP server to use"; - }; - - package = mkOption { - description = '' - Julia LSP server package, `null` to use the Julia binary in {env}`PATH`, or - the command to run as a list of strings. - ''; - type = nullOr (either package (listOf str)); - default = servers.${cfg.lsp.server}.package; + type = listOf (enum (attrNames servers)); + default = defaultServers; }; }; }; @@ -119,8 +124,70 @@ in { }) (mkIf cfg.lsp.enable { - vim.lsp.lspconfig.enable = true; - vim.lsp.lspconfig.sources.julia-lsp = servers.${cfg.lsp.server}.lspConfig; + 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; }) ]); }