ITADN
mrcjkb/haskell-tools.nvim
README.md
以下内容由 AI 翻译,如有问题请点此提交 issue 反馈

haskell-tools.nvim


探索文档 »

报告 Bug · 请求功能 · 提问

Neovim 中增强你的 Haskell 体验!

🦥

Neovim Lua Haskell Nix

GPL2 License Issues Build Status LuaRocks

[!NOTE]

:grey_question: 我需要 haskell-tools.nvim 吗

如果你刚开始学习 Haskell,nvim-lspconfig.hls 可能已经足够满足你的需求。 它提供了 LSP 支持的最小公分母。 此插件适用于那些希望拥有 额外功能 且特定于 Haskell 工具链的用户。

:pencil: 先决条件

必需

  • neovim >= 0.12

[!NOTE]

对于兼容旧版 Neovim 的版本, 请参阅 changelog 中的先前主要版本更新。

可选

:inbox_tray: 安装

使用 Neovim 内置的插件管理器:

vim.pack.add {{
  src = 'https://github.com/mrcjkb/haskell-tools.nvim',
  -- To avoid being surprised by breaking changes,
  -- I recommend you set a version range
  version = vim.version.range('^10')
}}

此插件可在 LuaRocks 上获取,并可使用 rocks.nvim 进行安装:

:Rocks install haskell-tools.nvim

使用 lazy.nvim 的示例:

{
  'mrcjkb/haskell-tools.nvim',
  -- To avoid being surprised by breaking changes,
  -- I recommend you set a version range
  version = '^10',
  -- This plugin implements proper lazy-loading (see :h lua-plugin-lazy).
  -- No need for lazy.nvim to lazy-load it.
  lazy = false,
}

[!TIP]

如果您希望避免破坏性变更,建议固定使用带标签的版本。

要手动生成文档,请使用 :helptags ALL

[!NOTE]

对于启用了 flakes 的 NixOS 用户,本项目以包和 overlay 的形式提供输出;您可以按需在 NixOS 或 home-manager 配置中使用它。 它也可以在 nixpkgs 中获取。

:zap: 快速设置

此插件会自动配置 haskell-language-server 内置 LSP 客户端,并与其他 haskell 工具集成。 有关更多信息,请参阅 Features 部分。

[!WARNING]

请勿调用 nvim-lspconfig.hls 设置或手动为 haskell-language-server 设置 lsp 客户端, 因为这样做可能会导致冲突。

这是一个开箱即用的文件类型插件, 因此无需调用 setup 函数或配置任何内容 即可使此插件正常工作。

您很可能希望添加一些键位映射。 大多数键位映射仅在 haskell 和/或 cabal 文件中有用, 因此建议您在 ~/.config/nvim/after/ftplugin/haskell.lua1 和/或 ~/.config/nvim/after/ftplugin/cabal.lua1 中定义它们。

一些建议:

-- ~/.config/nvim/after/ftplugin/haskell.lua
local ht = require('haskell-tools')
local bufnr = vim.api.nvim_get_current_buf()
local opts = { noremap = true, silent = true, buffer = bufnr, }
-- haskell-language-server relies heavily on codeLenses,
-- so auto-refresh (see advanced configuration) is enabled by default
vim.keymap.set('n', '<space>cl', vim.lsp.codelens.run, opts)
-- Hoogle search for the type signature of the definition under the cursor
vim.keymap.set('n', '<space>hs', ht.hoogle.hoogle_signature, opts)
-- Evaluate all code snippets
vim.keymap.set('n', '<space>ea', ht.lsp.buf_eval_all, opts)
-- Toggle a GHCi repl for the current package
vim.keymap.set('n', '<leader>rr', ht.repl.toggle, opts)
-- Toggle a GHCi repl for the current buffer
vim.keymap.set('n', '<leader>rf', function()
  ht.repl.toggle(vim.api.nvim_buf_get_name(0))
end, opts)
vim.keymap.set('n', '<leader>rq', ht.repl.quit, opts)

[!TIP]

[!IMPORTANT]

  • 请勿在 after/ftplugin/haskell.lua 中设置 vim.g.haskell_tools ,因为该文件在插件 初始化之后才被加载。

:star2: 功能

codeLens

  • 一次性评估所有代码片段

haskell-language-server 可以使用代码透镜评估代码片段。 haskell-tools.nvim 提供了一个 :Haskell hls evalAll (require('haskell-tools').lsp.buf_eval_all()) 快捷键,用于一次性评估所有代码片段。

evalAll

  • Hoogle 搜索签名

  • 搜索光标下的类型签名。

  • 如果无法确定类型签名,则回退到光标下的单词。

  • Telescope 键位映射:

    • <CR> 将选中的条目(<name> :: <signature>)复制到剪贴板。
    • <C-b> 在浏览器中打开所选条目的 Hackage URL。
    • <C-r> 用所选条目替换光标下的单词。
require('haskell-tools').hoogle.hoogle_signature()

hoogleSig

  • 由 Hoogle 驱动的 Hole-driven development

使用 <C-r> 键位映射, Hoogle 搜索 telescope 集成可用于填充 holes。

hoogleHole

  • GHCi repl

为当前项目 / 缓冲区启动一个 GHCi repl。

  • 自动检测适用于您项目的命令(cabal replstack ghcighci)。
  • 在内置处理器和 toggleterm.nvim 之间进行选择。
  • iron.nvim 动态创建 repl 命令 (参见 高级配置)。
  • 使用 lua API 在 Haskell 文件内与 repl 进行交互。

repl

  • 打开当前缓冲区的项目/包文件

commands

  • 悬停操作

此插件通过 :Haskell hover 命令添加以下悬停操作(如果可用):

  • Hoogle 搜索 (没有专门的 <Plug> 映射, 因为可能存在多个要搜索的签名)。
  • 在浏览器中打开文档(<Plug>HaskellHoverActionDocs)。
  • 在浏览器中打开源代码(<Plug>HaskellHoverActionSource)。
  • 跳转到定义(<Plug>HaskellHoverActionDefinition)。
  • 跳转到类型定义(<Plug>HaskellHoverActionTypeDefinition)。
  • 查找引用(<Plug>HaskellHoverActionReferences)。

您可以通过切换到悬停窗口并在相应行输入 <CR> 来调用它们, 或者使用相应 <Plug> 映射的键位映射。 如果您不希望为每个 <Plug> 映射创建单独的键位映射, 您也可以为 <Plug>HaskellHoverAction 创建一个键位映射,该映射接受 一个 <count> 前缀,作为要调用的悬停操作的(从 1 开始的)索引。

例如,如果您设置以下键位映射:

vim.keymap.set('n', '<space>a', '<Plug>HaskellHoverAction')

你可以使用 3<space>a 调用第三个悬停操作。

此外,默认的美化 Markdown 行为已被禁用。 并且悬停缓冲区的文件类型被设置为 markdown, 以便 nvim-treesitter 用户能够从代码片段的语法高亮中受益。

若要使用 haskell-tools 的悬停操作覆盖 Neovim 内置的悬停键位映射, 你可以在例如 ftplugin/haskell.lua 中添加以下内容:

vim.keymap.set('n', 'K', function() vim.cmd.Haskell { 'hover' } end)

hoverActions

  • 自动生成标签

在附加时,Neovim 的 LSP 客户端将设置 tagfunc 以查询语言服务器中可跳转的位置。 如果未找到位置,它将回退到 tags 文件。

如果安装了 fast-tags, 此插件将设置 autocmds 以自动生成标签:

  • 对于整个项目,在启动会话时。
  • 对于当前(子)包,在写入文件时。

此功能可以在 高级配置 中进行调整或禁用。

  • 自动发现 haskell-debug-adapter 配置

如果安装了 nvim-dap 插件, haskell-tools.nvim 将自动发现 haskell-debug-adapter 配置。

dap

[!NOTE]

haskell-debug-adapter 是 Haskell 调试适配器的一种实验性设计与实现。

  • Planned

对于计划中的功能,请参阅 issues

:gear: 高级配置

要修改默认配置,请设置 vim.g.haskell_tools

vim.g.haskell_tools = {
  ---@type haskell-tools.tools.Opts
  tools = {
    -- ...
  },
  ---@type haskell-tools.lsp.ClientOpts
  ---You can also configure these via `:h vim.lsp.config`,
  --- with the "haskell-tools" key.
  hls = {
    ---@param client number The LSP client ID.
    ---@param bufnr number The buffer number
    ---@param ht HaskellTools = require('haskell-tools')
    on_attach = function(client, bufnr, ht)
      -- Set keybindings, etc. here.
    end,
    -- ...
  },
  ---@type haskell-tools.dap.Opts
  dap = {
    -- ...
  },
}

[!TIP]

vim.g.haskell_tools 也可以是一个返回 表的函数。

如何按项目动态加载不同的 haskell-language-server 配置

默认情况下,此插件会在项目根目录中查找 hls.json2 文件, 并尝试加载它。 如果该文件不存在,或无法解码, 则使用 hls.default_settings

您可以通过 hls.settings 配置更改此行为:

vim.g.haskell_tools = {
  -- ...
  hls = {
    ---@param project_root string Path to the project root
    settings = function(project_root)
      local ht = require('haskell-tools')
      return ht.lsp.load_hls_settings(project_root, {
        settings_file_pattern = 'hls.json'
      })
    end,
  },
}

如何禁用单个代码透镜

某些代码透镜可能比其他代码透镜更有用。 例如,如果你倾向于导入所有内容或使用自定义预lude,importLens 可能会令人烦恼。 可以通过在相应的插件配置中禁用它们来关闭单个代码透镜:

hls = {
  settings = {
    haskell = {
      plugin = {
        class = { -- missing class methods
          codeLensOn = false,
        },
        importLens = { -- make import lists fully explicit
          codeLensOn = false,
        },
        refineImports = { -- refine imports
          codeLensOn = false,
        },
        tactics = { -- wingman
          codeLensOn = false,
        },
        moduleName = { -- fix module names
          globalOn = false,
        },
        eval = { -- evaluate code snippets
          globalOn = false,
        },
        ['ghcide-type-lenses'] = { -- show/add missing type signatures
          globalOn = false,
        },
      },
    },
  },
},

[!NOTE]

或者,您可以 按项目动态启用/禁用不同的代码透镜

在 Cabal 文件上启动 haskell-language-server

自版本 1.9.0.0 起,haskell-language-server 可以在 Cabal 文件上启动, 但它并不支持其在 Haskell 文件上拥有的所有功能。 您可以在 ~/.config/nvim/after/ftplugin/cabal.lua 中添加 cabal 特定的键位映射等。

配置 iron.nvim 以使用 haskell-tools.nvim

依赖于 iron.nvim/#300

local iron = require("iron.core")
iron.setup {
  config = {
    repl_definition = {
      haskell = {
        command = function(meta)
          local file = vim.api.nvim_buf_get_name(meta.current_bufnr)
          -- call `require` in case iron is set up before haskell-tools
          return require('haskell-tools').repl.mk_repl_cmd(file)
        end,
      },
    },
  },
}

创建 haskell-debug-adapter 启动配置

此插件有两种方式检测 haskell-debug-adapter 启动配置:

  1. 自动检测,通过解析 Cabal 或 Stack 项目文件。
  2. 通过加载项目根目录下的 launch.json 文件。

可用函数和命令

要查看完整概览,请在 Neovim 中输入 :help haskell-tools

LSP

命令描述
:Haskell hls evalAll评估注释中的所有代码片段
:Haskell hls start为当前缓冲区启动 HLS 客户端
:Haskell hls stop停止当前缓冲区的 HLS 客户端
:Haskell hls restart重启当前缓冲区的 HLS 客户端
local ht = require('haskell-tools')
--- Start or attach the LSP client.
ht.lsp.start()

--- Callback for dynamically loading haskell-language-server settings
--- Falls back to the `hls.default_settings` if no file is found
--- or one is found, but it cannot be read or decoded.
--- @param project_root string? The project root
ht.lsp.load_hls_settings(project_root)

--- Evaluate all code snippets in comments
ht.lsp.buf_eval_all()

Hoogle

local ht = require('haskell-tools')
--- Run a hoogle signature search for the value under the cursor
ht.hoogle.hoogle_signature()

Repl

命令描述参数
:Haskell repl toggle {file?}切换 GHCi replfilepath (可选)
:Haskell repl quit退出当前 repl
:Haskell repl load {file?}将文件加载到当前 replfilepath (可选)
:Haskell repl reload重新加载当前 repl
:Haskell repl paste_type {register?}查询 repl 中 {register} 的类型
:Haskell repl cword_type查询 repl 中光标下单词的类型
:Haskell repl paste_info {register?}查询 repl 中 {register} 的信息
:Haskell repl cword_info {register?}查询 repl 中光标下单词的信息
local ht = require('haskell-tools')
--- Toggle a GHCi repl for the current project
ht.repl.toggle()

--- Toggle a GHCi repl for `file`
--- @param file string Path to a Haskell file
ht.repl.toggle(file)

--- Quit the repl
ht.repl.quit()

--- Paste a command to the repl from register `reg`.
--- @param reg string? Register to paste from (:h registers), defaults to '"'.
ht.repl.paste(reg)

--- Query the repl for the type of register `reg`, and paste it to the repl.
--- @param reg string? Register to paste from (:h registers), defaults to '"'.
ht.repl.paste_type(reg)

--- Query the repl for the type of word under the cursor
ht.repl.cword_type()

--- Query the repl for info on register `reg`.
--- @param reg string? Register to paste from (:h registers), defaults to '"'.
ht.repl.paste_info(reg)

--- Query the repl for info on the word under the cursor
ht.repl.cword_info()

--- Load a file into the repl
--- @param file string The absolute file path
ht.repl.load_file(file)

--- Reload the repl
ht.repl.reload()

项目

命令描述
:Haskell projectFile打开当前缓冲区的项目文件(cabal.project 或 stack.yaml)
:Haskell packageYaml打开当前缓冲区的 package.yaml 文件
:Haskell packageCabal打开当前缓冲区的 *.cabal 文件
local ht = require('haskell-tools')
--- Open the project file for the current buffer (cabal.project or stack.yaml)
ht.project.open_project_file()

--- Open the package.yaml file for the current buffer
ht.project.open_package_yaml()

--- Open the *.cabal file for the current buffer
ht.project.open_package_cabal()

--- Search for files within the current (sub)package
--- @param opts table Optional telescope.nvim `find_files` options
ht.project.telescope_package_files(opts)
--- Live grep within the current (sub)package
--- @param opts table Optional telescope.nvim `live_grep` options
ht.project.telescope_package_grep(opts)

使用 Hoogle 回退的跳转至定义

命令描述
:Haskell definitionvim.lsp.buf.definition(),但如果未找到定义,则回退到 Hoogle 搜索

标签

以下函数依赖于 fast-tags

local ht = require('haskell-tools')

-- Generate tags for the whole project
---@param path string? An optional file path, defaults to the current buffer
---@param opts table Optional options:
---       opts.refresh boolean
---       - Whether to refresh tags if they have already been generated for a project
ht.tags.generate_project_tags(path, opts)

-- Generate tags for the whole project
---@param path string? An optional file path, defaults to the current buffer
ht.tags.generate_package_tags(path)

[!NOTE]

默认情况下,如果检测到 fast-tagshaskell-tools 将自动生成项目和包 标签。

DAP

local ht = require('haskell-tools')

---@param bufnr integer The buffer number
---@param opts table? Optional
---@param opts.autodetect: (boolean)
--- Whether to auto-detect launch configurations
---@param opts.settings_file_pattern: (string)
--- File name or pattern to search for. Defaults to 'launch.json'
ht.dap.discover_configurations(bufnr, opts)

[!NOTE]

haskell-tools.nvim 将自动发现 DAP 启动配置, 前提是 nivm-dap 已安装且调试适配器服务器可执行。 通常无需手动调用此函数。

Telescope 扩展

如果 telescope.nvim 已安装, haskell-tools.nvim 将注册 ht 扩展, 并提供以下命令:

命令描述
:Telescope ht package_files在当前(子)包中搜索文件
:Telescope ht package_hsfiles在当前(子)包中搜索 Haskell 文件
:Telescope ht package_grep在当前(子)包中进行实时 grep
:Telescope ht package_hsgrep在当前(子)包中对 Haskell 文件进行实时 grep
:Telescope ht hoogle_signature对光标下的类型签名执行 Hoogle 搜索

要加载该扩展,请调用

require('telescope').load_extension('ht')

[!IMPORTANT]

如果你延迟加载此插件, 请确保它在注册 Telescope 扩展之前加载。

:staphoscope: 故障排除

要进行健康检查,请运行 :checkhealth haskell-tools

LSP 功能不工作

如果 hls 无法显示诊断信息,或在文件顶部显示错误诊断, 你应该首先检查能否使用 cabal 或 stack 编译你的项目。 如果有编译错误,打开无法编译的文件, hls 应该能够显示这些文件的错误诊断信息。

检查你正在使用的 hls 和 GHC 的版本 (例如,通过调用 haskell-language-server-wrapper --probe-toolshaskell-language-server --probe-tools)。 有时,某些功能需要一些时间才能在最新的 GHC 版本中实现。 你可以在 hls 文档 中查看特定 GHC 版本的支持情况。

最小配置

要在临时目录中使用最小配置来排查此插件的问题, 你可以尝试 minimal.lua

nvim -u minimal.lua

[!NOTE]

如果你使用 Nix,你可以运行 nix run "github:mrcjkb/haskell-tools.nvim#nvim-minimal-stable"。 或者 nix run "github:mrcjkb/haskell-tools.nvim#nvim-minimal-nightly"

如果你无法使用最小配置复现你的问题, 它可能是由另一个插件引起的。 在这种情况下,将额外的插件及其配置添加到 minimal.lua, 直到你可以复现它。

[!NOTE]

此插件仅在 Linux 上进行了测试。 它应该在 MacOS 上正常工作,基本功能在 Windows 上也应该可以工作 (自版本 1.9.5 起),但我无法自行测试这一点。 依赖外部工具的功能,例如 hooglefast-tagsghci 可能在非 Unix 类操作系统上失效。

日志

要启用调试日志,请将日志级别设置为 DEBUG3

vim.g.haskell_tools = {
  tools = { -- haskell-tools options
    log = {
      level = vim.log.levels.DEBUG,
    },
  },
}

你还可以通过调用以下命令临时设置日志级别

命令参数
:Haskell log setLeveldebug error warn info trace off 之一

:lua require('haskell-tools').log.set_level(vim.log.levels.DEBUG)

您可以通过调用以下命令找到日志文件

-- haskell-tools.nvim log
:lua =require('haskell-tools').log.get_logfile()
-- haskell-language-server logs
:lua =require('haskell-tools').log.get_hls_logfile()

或调用

:lua require('haskell-tools').log.nvim_open_logfile() -- or :Haskell log openLog
:lua require('haskell-tools').log.nvim_open_hls_logfile() -- or :Haskell log openHlsLog

以下是我推荐用于 neovim 中 Haskell(以及 nix)开发的其他插件:

Footnotes

  1. See :help base-directories 2

  2. haskell-language-server 可以使用 generate-default-config CLI 参数 生成 此类文件。

  3. See :help vim.log.levels: