diff --git a/docs/manual/release-notes/rl-0.9.md b/docs/manual/release-notes/rl-0.9.md index ffdccb8e..805bacf1 100644 --- a/docs/manual/release-notes/rl-0.9.md +++ b/docs/manual/release-notes/rl-0.9.md @@ -39,6 +39,9 @@ `setupOpts.strategies.chat.adapter`, rename them to `setupOpts.interactions.chat.adapter`. +- Haskell LSP now defaults to haskell-language-server, haskell-tools based LSP + support is moved to `vim.languages.haskell.extensions.haskell-tools` + [Snoweuph](https://github.com/snoweuph) - Remove `mind.nvim`. This plugin doesn't exist anymore. The original author @@ -201,6 +204,13 @@ - Updated nix language plugin to use pkgs.nixfmt instead of pkgs.nixfmt-rfc-style +[dathegreat](https://github.com/dathegreat): + +- Fixed invalid keys in the haskell-tools configuration +- Split haskell configuration into `lsp/presets/haskell-language-server.nix` and + `languages/haskell.nix` +- Made the haskell LSP and formatter configurable + [alfarel](https://github.com/alfarelcynthesis): [obsidian.nvim]: https://github.com/obsidian-nvim/obsidian.nvim diff --git a/modules/plugins/languages/haskell.nix b/modules/plugins/languages/haskell.nix index 32f45151..737b511d 100644 --- a/modules/plugins/languages/haskell.nix +++ b/modules/plugins/languages/haskell.nix @@ -1,66 +1,35 @@ { config, lib, + options, pkgs, ... }: let - inherit (builtins) isList attrNames; - inherit (lib.types) either package enum listOf str; + inherit (builtins) attrNames; + inherit (lib) genAttrs; + inherit (lib.types) either package enum listOf str nullOr; inherit (lib.options) mkEnableOption mkOption literalExpression; - inherit (lib.strings) optionalString; inherit (lib.modules) mkIf mkMerge; - inherit (lib.nvim.types) mkGrammarOption; - inherit (lib.nvim.dag) entryAfter; + inherit (config.vim.lib) mkMappingOption; inherit (lib.nvim.lua) toLuaObject; - inherit (lib.meta) getExe'; + inherit (lib.nvim.binds) addDescriptionsToMappings; + inherit (lib.nvim.types) mkGrammarOption deprecatedSingleOrListOf mkPluginSetupOption luaInline; + inherit (lib.nvim.attrsets) mapListToAttrs; + inherit (lib.meta) getExe; inherit (lib.generators) mkLuaInline; inherit (pkgs) haskellPackages; cfg = config.vim.languages.haskell; - defaultServers = ["hls"]; - servers = { - hls = { - enable = false; - cmd = [(getExe' pkgs.haskellPackages.haskell-language-server "haskell-language-server-wrapper") "--lsp"]; - filetypes = ["haskell" "lhaskell"]; - on_attach = - mkLuaInline - /* - lua - */ - '' - function(client, bufnr) - local ht = require("haskell-tools") - local opts = { noremap = true, silent = true, buffer = bufnr } - vim.keymap.set('n', 'cl', vim.lsp.codelens.run, opts) - vim.keymap.set('n', 'hs', ht.hoogle.hoogle_signature, opts) - vim.keymap.set('n', 'ea', ht.lsp.buf_eval_all, opts) - vim.keymap.set('n', 'rr', ht.repl.toggle, opts) - vim.keymap.set('n', 'rf', function() - ht.repl.toggle(vim.api.nvim_buf_get_name(0)) - end, opts) - vim.keymap.set('n', 'rq', ht.repl.quit, opts) - end - ''; - root_dir = - mkLuaInline - /* - lua - */ - '' - function(bufnr, on_dir) - local fname = vim.api.nvim_buf_get_name(bufnr) - on_dir(util.root_pattern('hie.yaml', 'stack.yaml', 'cabal.project', '*.cabal', 'package.yaml')(fname)) - end - ''; - settings = { - haskell = { - formattingProvider = "ormolu"; - cabalFormattingProvider = "cabal-fmt"; - }; - }; - }; + defaultServers = ["haskell-language-server"]; + servers = ["haskell-language-server"]; + + defaultFormat = ["ormolu"]; + formats = { + ormolu = {command = getExe haskellPackages.ormolu;}; + fourmolu = {command = getExe haskellPackages.fourmolu;}; + stylish-haskell = {command = getExe haskellPackages.stylish-haskell;}; + floskell = {command = getExe haskellPackages.floskell;}; }; in { options.vim.languages.haskell = { @@ -84,12 +53,26 @@ in { defaultText = literalExpression "config.vim.lsp.enable"; }; servers = mkOption { - type = listOf (enum (attrNames servers)); + type = listOf (enum servers); default = defaultServers; description = "Haskell LSP server to use"; }; }; + format = { + enable = + mkEnableOption "Haskell formatting" + // { + default = config.vim.languages.enableFormat; + defaultText = literalExpression "config.vim.languages.enableFormat"; + }; + type = mkOption { + type = deprecatedSingleOrListOf "vim.languages.haskell.format.type" (enum (attrNames formats)); + default = defaultFormat; + description = "Haskell formatter to use"; + }; + }; + dap = { enable = mkEnableOption "DAP support for Haskell" @@ -103,9 +86,82 @@ in { description = "Haskell DAP package or command to run the Haskell DAP"; }; }; + + extensions = { + haskell-tools = { + enable = mkEnableOption "haskell-tools.nvim"; + + mappings = { + codeLensRun = mkMappingOption "Run code lens [haskell-tools.nvim]" "cl"; + hoogleSignature = mkMappingOption "Hoogle signature [haskell-tools.nvim]" "hs"; + evalAll = mkMappingOption "Evaluate all [haskell-tools.nvim]" "ea"; + replToggle = mkMappingOption "Toggle REPL [haskell-tools.nvim]" "rr"; + replToggleFile = mkMappingOption "Toggle REPL for current file [haskell-tools.nvim]" "rf"; + replQuit = mkMappingOption "Quit REPL [haskell-tools.nvim]" "rq"; + }; + + setupOpts = mkPluginSetupOption "haskell-tools.nvim" { + hls = { + cmd = mkOption { + type = nullOr (listOf str); + default = [ + "${pkgs.haskellPackages.haskell-language-server}/bin/haskell-language-server" + "--lsp" + ]; + description = '' + Command for haskell-language-server. + + > [!NOTE] + > + > Since HLS is very sensitive about the GHC version, there's a + > very high chance that the default HLS we use is not + > compatible with your project. It is highly recommended to set + > this option to `null` and install HLS separately in a + > `devShell`. + ''; + }; + on_attach = mkOption { + type = nullOr luaInline; + description = "Function to run when HLS is attached. When null, mappings from the mappings option are used."; + default = null; + defaultText = literalExpression "Generated from vim.languages.haskell.extensions.haskell-tools.mappings"; + }; + }; + + dap = { + cmd = mkOption { + type = nullOr (listOf str); + default = null; + description = "Debug adapter command"; + }; + }; + }; + }; + }; }; config = mkIf cfg.enable (mkMerge [ + { + assertions = [ + { + assertion = !(cfg.lsp.enable && cfg.extensions.haskell-tools.enable); + message = '' + vim.languages.haskell: haskell-tools.nvim manages the LSP directly and + is incompatible with vim.languages.haskell.lsp.enable. Disable one or + the other. See https://github.com/mrcjkb/haskell-tools.nvim/blob/fe9ed6e6adfa6311e06c84569d8536190f172030/doc/haskell-tools.txt#L22 + ''; + } + { + assertion = !(cfg.dap.enable && !cfg.extensions.haskell-tools.enable); + message = '' + vim.languages.haskell: DAP support requires haskell-tools.nvim, which + handles adapter registration and launch configuration discovery. + Enable vim.languages.haskell.extensions.haskell-tools to use DAP. + ''; + } + ]; + } + (mkIf cfg.treesitter.enable { vim.treesitter = { enable = true; @@ -113,34 +169,62 @@ in { }; }) - (mkIf (cfg.dap.enable || cfg.lsp.enable) { + (mkIf cfg.lsp.enable { + vim.lsp = { + presets = genAttrs cfg.lsp.servers (_: {enable = true;}); + servers = genAttrs cfg.lsp.servers (_: { + filetypes = ["haskell" "lhaskell"]; + }); + }; + }) + + (mkIf cfg.format.enable { + vim.formatter.conform-nvim = { + enable = true; + setupOpts = { + formatters_by_ft = { + haskell = cfg.format.type; + lhaskell = cfg.format.type; + }; + formatters = + mapListToAttrs (name: { + inherit name; + value = formats.${name}; + }) + cfg.format.type; + }; + }; + }) + + (mkIf cfg.extensions.haskell-tools.enable { vim = { + extraPackages = [haskellPackages.cabal-fmt]; startPlugins = ["haskell-tools-nvim"]; - luaConfigRC.haskell-tools-nvim = - entryAfter - ["lsp-servers"] - '' - vim.g.haskell_tools = { - ${optionalString cfg.lsp.enable '' - -- LSP - tools = { - hover = { - enable = true, - }, - }, - hls = ${toLuaObject servers.hls}, - ''} - ${optionalString cfg.dap.enable '' - dap = { - cmd = ${ - if isList cfg.dap.package - then toLuaObject cfg.dap.package - else ''{"${cfg.dap.package}/bin/haskell-debug-adapter"}'' - }, - }, - ''} - } - ''; + globals.haskell_tools = cfg.extensions.haskell-tools.setupOpts; + languages.haskell.extensions.haskell-tools.setupOpts = { + hls = { + on_attach = let + htCfg = cfg.extensions.haskell-tools; + keymapDefinitions = options.vim.languages.haskell.extensions.haskell-tools.mappings; + mappings = addDescriptionsToMappings htCfg.mappings keymapDefinitions; + mkBinding = binding: action: + if binding.value != null + then "vim.keymap.set('n', ${toLuaObject binding.value}, ${action}, {buffer=bufnr, noremap=true, silent=true, desc=${toLuaObject binding.description}})" + else ""; + in + mkLuaInline '' + function(client, bufnr) + local ht = require("haskell-tools") + ${mkBinding mappings.codeLensRun "vim.lsp.codelens.run"} + ${mkBinding mappings.hoogleSignature "ht.hoogle.hoogle_signature"} + ${mkBinding mappings.evalAll "ht.lsp.buf_eval_all"} + ${mkBinding mappings.replToggle "function() vim.cmd('Haskell repl toggle') end"} + ${mkBinding mappings.replToggleFile "function() vim.cmd('Haskell repl toggle ' .. vim.api.nvim_buf_get_name(0)) end"} + ${mkBinding mappings.replQuit "function() vim.cmd('Haskell repl quit') end"} + end + ''; + }; + }; }; }) ]); diff --git a/modules/plugins/lsp/presets/default.nix b/modules/plugins/lsp/presets/default.nix index f5f71c35..8c43b61e 100644 --- a/modules/plugins/lsp/presets/default.nix +++ b/modules/plugins/lsp/presets/default.nix @@ -22,6 +22,7 @@ ./gopls.nix ./harper.nix ./helm-ls.nix + ./haskell-language-server.nix ./intelephense.nix ./jdt-language-server.nix ./jinja-lsp.nix diff --git a/modules/plugins/lsp/presets/haskell-language-server.nix b/modules/plugins/lsp/presets/haskell-language-server.nix new file mode 100644 index 00000000..572ed13f --- /dev/null +++ b/modules/plugins/lsp/presets/haskell-language-server.nix @@ -0,0 +1,42 @@ +{ + config, + lib, + pkgs, + ... +}: let + inherit (lib.meta) getExe'; + inherit (lib.modules) mkIf; + inherit (lib.nvim.types) mkLspPresetEnableOption; + + cfg = config.vim.lsp.presets.haskell-language-server; +in { + options.vim.lsp.presets.haskell-language-server = { + enable = mkLspPresetEnableOption "haskell-language-server" "Haskell" []; + }; + + config = mkIf cfg.enable { + vim.lsp.servers.haskell-language-server = { + enable = true; + cmd = [ + (getExe' (pkgs.symlinkJoin { + name = "haskell-language-server-wrapper"; + paths = [pkgs.haskellPackages.haskell-language-server]; + meta.mainProgram = "haskell-language-server-wrapper"; + buildInputs = [pkgs.makeBinaryWrapper]; + postBuild = '' + wrapProgram $out/bin/haskell-language-server-wrapper \ + --prefix PATH : ${pkgs.haskellPackages.cabal-fmt}/bin + ''; + }) "haskell-language-server-wrapper") + "--lsp" + ]; + root_markers = ["hie.yaml" "stack.yaml" "cabal.project" "*.cabal" "package.yaml"]; + settings = { + haskell = { + # cabal-fmt is an external tool; it is wrapped into the LSP binary's PATH above + cabalFormattingProvider = "cabal-fmt"; + }; + }; + }; + }; +}