diff --git a/.github/workflows/docgen.yml b/.github/workflows/docgen.yml new file mode 100644 index 00000000..05abf322 --- /dev/null +++ b/.github/workflows/docgen.yml @@ -0,0 +1,35 @@ +name: Generate docs + +on: + push: + branches-ignore: + - master + +jobs: + docs: + name: Generate docs + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3.0.2 + - uses: cachix/install-nix-action@v17 + - uses: cachix/cachix-action@v10 + with: + name: mrcjkb + authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}' + + - name: Generating docs + run: nix run ".#docgen" + + - name: Update documentation + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + COMMIT_MSG: | + docs(generated): update doc/haskell-tools.txt + skip-checks: true + run: | + git config user.email "actions@github" + git config user.name "Github Actions" + git remote set-url origin https://x-access-token:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git + git add doc/ + # Only commit and push if we have changes + git diff --quiet && git diff --staged --quiet || (git commit -m "${COMMIT_MSG}"; git push origin HEAD:${GITHUB_REF}) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d2f5712..2ebe454a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Ability to temporarily set the log level via `ht.log.set_level(level)`. - `tools.repl.auto_focus` option. +- Vimdocs ### Fixed - repl.toggleterm: Do not close on failure. - repl: Quote file names. diff --git a/README.md b/README.md index 958dd1e9..b44a77b7 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,7 @@ It is also available on `nixpkgs`. ## Quick Setup -This plugin automatically configures the `haskell-language-server` neovim client and integrates with other haskell tools. +This plugin automatically configures the `haskell-language-server` builtin LSP client and integrates with other haskell tools. See the [Features](#features) section for more info. > :warning: diff --git a/doc/haskell-tools.txt b/doc/haskell-tools.txt new file mode 100644 index 00000000..7bea0c47 --- /dev/null +++ b/doc/haskell-tools.txt @@ -0,0 +1,386 @@ +============================================================================== +Table of Contents *haskell-tools.contents* + +Introduction ··························································· |intro| +The haskell-tools module ······································· |haskell-tools| +haskell-tools configuration ····························· |haskell-tools.config| +haskell-tools LSP client setup ····························· |haskell-tools.lsp| +haskell-tools Hoogle search ····························· |haskell-tools.hoogle| +haskell-tools GHCi REPL module ···························· |haskell-tools.repl| +haskell-tools Project module ··························· |haskell-tools.project| +haskell-tools fast-tags module ···························· |haskell-tools.tags| +haskell-tools Logging ······································ |haskell-tools.log| + +============================================================================== +Introduction *intro* + +This plugin automatically configures the `haskell-language-server` builtin LSP client +and integrates with other haskell tools. + +Warning: +Do not call the `lspconfig.hls` setup or set up the lsp manually, +as doing so may cause conflicts. + + +============================================================================== +The haskell-tools module *haskell-tools* + +HaskellTools *HaskellTools* + + Fields: ~ + {config} (HaskellToolsConfig) + + +ht.setup() *ht.setup* + + See: ~ + |haskell-tools.config for the default configuration.| + |lspconfig-keybindings for suggested keybindings by `nvim-lspconfig`.| + + +============================================================================== +haskell-tools configuration *haskell-tools.config* + +HaskellToolsConfig *HaskellToolsConfig* + + Fields: ~ + {hls_log} (string) The path to the haskell-language-server log file + {defaults} (HTOpts) The default configuration options + {options} (HTOpts) The configuration options as applied by `setup()` + {setup} (function) + + +HTOpts *HTOpts* + + Fields: ~ + {tools} (ToolsOpts) haskell-tools plugin options + {hls} (HaskellLspClientOpts) haskell-language-server client options + + +ToolsOpts *ToolsOpts* + + Fields: ~ + {codeLens} (CodeLensOpts) LSP client codeLens options + {hoogle} (HoogleOpts) Hoogle options + {hover} (HoverOpts) LSP client hover options + {definition} (DefinitionOpts) LSP client definition options + {repl} (ReplOpts) GHCi REPL options + {tags} (FastTagsOpts) Options for generating tags using fast-tags + {log} (HTLogOpts) Logging options + + +CodeLensOpts *CodeLensOpts* + + Fields: ~ + {autoRefresh} (boolean) (default: `true`) Whether to auto-refresh code-lenses + + +HoogleOpts *HoogleOpts* + + Fields: ~ + {mode} (string) 'auto', 'telescope-local', 'telescope-web' or 'browser' + + +HoverOpts *HoverOpts* + + Fields: ~ + {disable} (boolean) (default: `false`) Whether to disable haskell-tools hover and use the builtin lsp's default handler + {border} (table) + + +DefinitionOpts *DefinitionOpts* + + Fields: ~ + {hoogle_signature_fallback} (boolean) (default:`false`) Configure `vim.lsp.definition` to fall back to hoogle search (does not affect `vim.lsp.tagfunc`) + + +ReplOpts *ReplOpts* + + Fields: ~ + {handler} (string) 'builtin': Use the simple builtin repl. 'toggleterm': Use akinsho/toggleterm.nvim + {builtin} (table) Configuration for the builtin repl + {auto_focus} (boolean) @field builtin.create_repl_window function How to create the repl window + + +ReplView *ReplView* + + Fields: ~ + {create_repl_split} (function) Create the REPL in a horizontally split window + {create_repl_vsplit} (function) Create the REPL in a vertically split window + {create_repl_tabnew} (function) Create the REPL in a new tab + {create_repl_cur_win} (function) Create the REPL in the current window + + +FastTagsOpts *FastTagsOpts* + + Fields: ~ + {enable} (boolean) Enabled by default if the `fast-tags` executable is found + {package_events} (table) autocmd Events to trigger package tag generation + + +HTLogOpts *HTLogOpts* + + Fields: ~ + {level} (integer|string) The log level + + See: ~ + |vim.log.levels| + + +HaskellLspClientOpts *HaskellLspClientOpts* + + Fields: ~ + {debug} (boolean) Whether to enable debug logging + {on_attach} (function) Callback to execute when the client attaches to a buffer + {cmd} (table) The command to start the server with + {filetypes} (table) List of file types to attach the client to + {capabilities} (table) LSP client capabilities + {settings} (table) The server config + + See: ~ + |https://haskell-language-server.readthedocs.io/en/latest/configuration.html.| + + +config.defaults *config.defaults* + + Type: ~ + (HTOpts) + + +config.options *config.options* + + Type: ~ + (HTOpts) + + +config.setup() *config.setup* + + +============================================================================== +haskell-tools LSP client setup *haskell-tools.lsp* + +HaskellToolsLspClient *HaskellToolsLspClient* + + Fields: ~ + {setup} (function) + + +lsp.setup() *lsp.setup* + Setup the LSP client. Called by the haskell-tools setup. + + +============================================================================== +haskell-tools Hoogle search *haskell-tools.hoogle* + +HaskellToolsHoogle *HaskellToolsHoogle* + + Fields: ~ + {hoogle_signature} (function) Hoogle search for a symbol's type signature + {setup} (function) + {handler} (unknown) The internal handler + + +hoogle.hoogle_signature() *hoogle.hoogle_signature* + @param options table? Includes the `search_term` and options to pass to the telescope picker (if available) + + +hoogle.setup() *hoogle.setup* + Setup the Hoogle module. Called by the haskell-tools setup. + + +============================================================================== +haskell-tools GHCi REPL module *haskell-tools.repl* + +HaskellToolsRepl *HaskellToolsRepl* + + Fields: ~ + {mk_repl_cmd} (function) + {buf_mk_repl_cmd} (function) + {setup} (function) + {handler} (unknown) The internal repl handler + {toggle} (function) + {quit} (function) + {paste} (function) + {paste_type} (function) + {cword_type} (function) + {paste_info} (function) + {cword_info} (function) + {load_file} (function) + {reload} (function) + + +repl.mk_repl_cmd() *repl.mk_repl_cmd* + Create the command to create a repl for a file. + If `file` is `nil`, create a repl the nearest package. + @param file string? The file to create the repl for + @return table? command + + +repl.buf_mk_repl_cmd() *repl.buf_mk_repl_cmd* + Create the command to create a repl for the current buffer. + @return table? command + + +repl.setup() *repl.setup* + Set up this module. Called by the haskell-tools setup. + + +repl.paste() *repl.paste* + Paste from register `reg` to the REPL + @param reg string?: register (defaults to '"') + + +repl.paste_type() *repl.paste_type* + Query the REPL for the type of register `reg` + @param reg string? register (defaults to '"') + + +repl.cword_type() *repl.cword_type* + Query the REPL for the type of word under the cursor + + +repl.paste_info() *repl.paste_info* + Query the REPL for info on register `reg` + @param reg string? register (defaults to '"') + + +repl.cword_info() *repl.cword_info* + Query the REPL for the type of word under the cursor + + +repl.load_file() *repl.load_file* + Load a file into the REPL + @param filepath string The absolute file path + + +repl.reload() *repl.reload* + Reload the repl + + +============================================================================== +haskell-tools Project module *haskell-tools.project* + + The following commands are available: + + * `:HsProjectFile` - Open the project file for the current buffer (cabal.project or stack.yaml). + * `:HsPackageYaml` - Open the package.yaml file for the current buffer. + * `:HsPackageCabal` - Open the *.cabal file for the current buffer. + +HaskellToolsProject *HaskellToolsProject* + + Fields: ~ + {setup} (function) + {open_package_yaml} (function) + {open_package_cabal} (function) + {open_project_file} (function) + {telescope_package_grep} (function) + + +project.setup() *project.setup* + Set up this module. Called by the haskell-tools setup. + + +project.open_package_yaml() *project.open_package_yaml* + Open the package.yaml of the package containing the current buffer. + + +project.open_package_cabal() *project.open_package_cabal* + Open the *.cabal file of the package containing the current buffer. + + +project.open_project_file() *project.open_project_file* + Open the current buffer's project file (cabal.project or stack.yaml). + + +project.telescope_package_grep({opts}) *project.telescope_package_grep* + Live grep the current package with Telescope. + Available if nvim-telescope/telescope.nvim is installed. + + Parameters: ~ + {opts} (table) Telescope options + + +project.telescope_package_files({opts}) *project.telescope_package_files* + Find file in the current package with Telescope + Available if nvim-telescope/telescope.nvim is installed. + + Parameters: ~ + {opts} (table) Telescope options + + +============================================================================== +haskell-tools fast-tags module *haskell-tools.tags* + +HaskellToolsTags *HaskellToolsTags* + + Fields: ~ + {generate_project_tags} (function) + + +GenerateProjectTagsOpts *GenerateProjectTagsOpts* + + Fields: ~ + {refresh} (boolean) Whether to refresh the tags if they have already been generated + + +tags.generate_project_tags() *tags.generate_project_tags* + for the project (default: true) + Generates tags for the current project + @param path string? File path + @param opts GenerateProjectTagsOpts? Options + + +tags.generate_package_tags() *tags.generate_package_tags* + + +tags.setup() *tags.setup* + Setup the tags module. Called by the haskell-tools setup. + + +============================================================================== +haskell-tools Logging *haskell-tools.log* + +HaskellToolsLogger *HaskellToolsLogger* + + Fields: ~ + {debug} (function) Log a debug message + {info} (function) Log an info message + {warn} (function) Log a warning message + {error} (function) Log an error message + {set_level} (function) Set the log level + {get_logfile} (function) Get the haskell-tools log file + {nvim_open_logfile} (function) Open the haskell-tools log file + {get_hls_logfile} (function) Get the haskell-language-server log file + {nvim_open_hls_logfile} (function) Open the haskell-language-server log file + {setup} (function) + + +log.get_logfile() *log.get_logfile* + Get the haskell-tools.nvim log file path. + + Returns: ~ + (string) filepath + + +log.nvim_open_logfile() *log.nvim_open_logfile* + Open the haskell-tools.nvim log file. + + +log.setup() *log.setup* + Set up the log module. Called by the haskell-tools setup. + + +log.get_hls_logfile() *log.get_hls_logfile* + Get the haskell-language-server log file + + +log.nvim_open_hls_logfile() *log.nvim_open_hls_logfile* + + +log.set_level() *log.set_level* + Set the log level + @param level (string|integer) The log level + @see vim.log.levels + + +vim:tw=78:ts=8:noet:ft=help:norl: diff --git a/flake.nix b/flake.nix index 1dc19481..41c1095a 100644 --- a/flake.nix +++ b/flake.nix @@ -118,9 +118,13 @@ haskell-tools = shellFor system; }); - packages = perSystem (system: rec { - default = haskell-tools-nvim; + packages = perSystem (system: let + pkgs = pkgsFor system; haskell-tools-nvim = haskell-tools-nvim-for system; + docgen = pkgs.callPackage ./nix/docgen.nix {}; + in { + default = haskell-tools-nvim; + inherit docgen haskell-tools-nvim; }); checks = perSystem (system: let diff --git a/lua/haskell-tools/config.lua b/lua/haskell-tools/config.lua index e444c508..c12a65de 100644 --- a/lua/haskell-tools/config.lua +++ b/lua/haskell-tools/config.lua @@ -1,6 +1,74 @@ +---@mod haskell-tools.config haskell-tools configuration + +---@class HaskellToolsConfig +---@field hls_log string The path to the haskell-language-server log file +---@field defaults HTOpts The default configuration options +---@field options HTOpts The configuration options as applied by `setup()` +---@field setup function + +---@class HTOpts +---@field tools ToolsOpts haskell-tools plugin options +---@field hls HaskellLspClientOpts haskell-language-server client options + +---@class ToolsOpts +---@field codeLens CodeLensOpts LSP client codeLens options +---@field hoogle HoogleOpts Hoogle options +---@field hover HoverOpts LSP client hover options +---@field definition DefinitionOpts LSP client definition options +---@field repl ReplOpts GHCi REPL options +---@field tags FastTagsOpts Options for generating tags using fast-tags +---@field log HTLogOpts Logging options + +---@class CodeLensOpts +---@field autoRefresh boolean (default: `true`) Whether to auto-refresh code-lenses + +---@class HoogleOpts +---@field mode string 'auto', 'telescope-local', 'telescope-web' or 'browser' + +---@class HoverOpts +---@field disable boolean (default: `false`) Whether to disable haskell-tools hover and use the builtin lsp's default handler +---@field border table? The hover window's border. Set to `nil` to disable. +---@field stylize_markdown boolean (default: `false`) The builtin LSP client's default behaviour is to stylize markdown. Setting this option to false sets the file type to markdown and enables treesitter syntax highligting for Haskell snippets if nvim-treesitter is installed +---@field auto_focus boolean (default: `false`) Whether to automatically switch to the hover window + +---@class DefinitionOpts +---@field hoogle_signature_fallback boolean (default:`false`) Configure `vim.lsp.definition` to fall back to hoogle search (does not affect `vim.lsp.tagfunc`) + +---@class ReplOpts +---@field handler string 'builtin': Use the simple builtin repl. 'toggleterm': Use akinsho/toggleterm.nvim +---@field builtin table Configuration for the builtin repl +---@field builtin.create_repl_window function How to create the repl window +---@field auto_focus boolean? Whether to auto-focus the repl on toggle or send. The default value of `nil` means the handler decides. + +---@class ReplView +---@field create_repl_split function Create the REPL in a horizontally split window +---@field create_repl_vsplit function Create the REPL in a vertically split window +---@field create_repl_tabnew function Create the REPL in a new tab +---@field create_repl_cur_win function Create the REPL in the current window + +---@class FastTagsOpts +---@field enable boolean Enabled by default if the `fast-tags` executable is found +---@field package_events table autocmd Events to trigger package tag generation + +---@class HTLogOpts +---@field level integer|string The log level +---@see vim.log.levels + +---@class HaskellLspClientOpts +---@field debug boolean Whether to enable debug logging +---@field on_attach function Callback to execute when the client attaches to a buffer +---@field cmd table The command to start the server with +---@field filetypes table List of file types to attach the client to +---@field capabilities table LSP client capabilities +---@field settings table The server config +---@see https://haskell-language-server.readthedocs.io/en/latest/configuration.html. +---@comment To print all options that are available for your haskell-language-server version, run `haskell-language-server-wrapper generate-default-config` + local deps = require('haskell-tools.deps') +---@type HaskellToolsConfig local config = { + -- TODO: (breaking) Move to log options hls_log = vim.fn.stdpath('log') .. '/' .. 'haskell-language-server.log', } @@ -13,27 +81,21 @@ local selection_range_capabilities = deps.if_available('lsp-selection-range', fu end, {}) local capabilities = vim.tbl_deep_extend('keep', ht_capabilities, cmp_capabilities, selection_range_capabilities) -local defaults = { - -- haskell-language-server config +---@type HTOpts +config.defaults = { + ---@type ToolsOpts tools = { + ---@type CodeLensOpts codeLens = { - -- Whether to automatically display/refresh codeLenses autoRefresh = true, }, + ---@type HoogleOpts hoogle = { - -- 'auto': Choose a mode automatically, based on what is available. - -- 'telescope-local': Force use of a local installation. - -- 'telescope-web': The online version (depends on curl). - -- 'browser': Open hoogle search in the default browser. mode = 'auto', - -- -- TODO: Fall back to a hoogle search if goToDefinition fails - -- goToDefinitionFallback = false, }, - -- Hover with actions + ---@type HoverOpts hover = { - -- Whether to disable haskell-tools hover and use the builtin lsp's default handler disable = false, - -- Set to nil to disable border = { { '╭', 'FloatBorder' }, { '─', 'FloatBorder' }, @@ -44,21 +106,15 @@ local defaults = { { '╰', 'FloatBorder' }, { '│', 'FloatBorder' }, }, - -- Stylize markdown (the builtin lsp's default behaviour). - -- Setting this option to false sets the file type to markdown and enables - -- Treesitter syntax highligting for Haskell snippets if nvim-treesitter is installed stylize_markdown = false, - -- Whether to automatically switch to the hover window auto_focus = false, }, + ---@type DefinitionOpts definition = { - -- Configure vim.lsp.definition to fall back to hoogle search - -- (does not affect vim.lsp.tagfunc) hoogle_signature_fallback = false, }, + ---@type ReplOpts repl = { - -- 'builtin': Use the simple builtin repl - -- 'toggleterm': Use akinsho/toggleterm.nvim handler = 'builtin', builtin = { create_repl_window = function(view) @@ -66,20 +122,19 @@ local defaults = { return view.create_repl_split { size = vim.o.lines / 3 } end, }, - -- Can be overriden to either `true` or `false`. The default behaviour depends on the handler. auto_focus = nil, }, - -- Set up autocmds to generate tags (using fast-tags) - -- e.g. so that `vim.lsp.tagfunc` can fall back to Haskell tags + ---@type FastTagsOpts tags = { enable = vim.fn.executable('fast-tags') == 1, - -- Events to trigger package tag generation package_events = { 'BufWritePost' }, }, + ---@type HTLogOpts log = { level = vim.log.levels.WARN, }, }, + ---@type HaskellLspClientOpts hls = { debug = false, on_attach = function(...) end, @@ -181,14 +236,17 @@ local defaults = { }, } +---@type HTOpts config.options = { hls = {}, } +---Set the options of this plugin. Called by the haskell-tools setup. +---@param opts HTOpts? function config.setup(opts) - config.options = vim.tbl_deep_extend('force', {}, defaults, opts or {}) + config.options = vim.tbl_deep_extend('force', {}, config.defaults, opts or {}) if config.options.hls.debug then - table.insert(config.option.hls.cmd, '--debug') + table.insert(config.options.hls.cmd, '--debug') end end diff --git a/lua/haskell-tools/deps.lua b/lua/haskell-tools/deps.lua index 3d4df8f1..0ed5ce04 100644 --- a/lua/haskell-tools/deps.lua +++ b/lua/haskell-tools/deps.lua @@ -1,9 +1,18 @@ +---@mod haskell-tools.deps + +---@brief [[ + +---WARNING: This is not part of the public API. +---Breaking changes to this module will not be reflected in the semantic versioning of this plugin. + +---@brief ]] + local deps = {} --- @param module name: The name of the module --- @param on_available: function(module) --- @param on_not_available: (optonal) function() | anything --- @return the return value of on_available or on_not_available +--- @param modname string The name of the module +--- @param on_available any? Callback. Can be a function that takes the module name as an argument or a value. +--- @param on_not_available any? Callback to execute if the module is not available. Can be a function or a value. +--- @return any result Return value of on_available or on_not_available function deps.if_available(modname, on_available, on_not_available) local has_mod, mod = pcall(require, modname) if has_mod and type(on_available) == 'function' then @@ -20,7 +29,10 @@ function deps.if_available(modname, on_available, on_not_available) return on_not_available end ---@return unknown +---Require a module or fail +---@param modname string +---@param plugin_name string +---@return unknown function deps.require_or_err(modname, plugin_name) return deps.if_available(modname, function(mod) return mod @@ -29,34 +41,38 @@ function deps.require_or_err(modname, plugin_name) end) end ---@return boolean +---@param modname string The name of the module +---@return boolean function deps.has(modname) return deps.if_available(modname, true, false) end ---@return boolean +---@return boolean function deps.has_telescope() return deps.has('telescope') end ---@return unknown +---@return unknown function deps.require_telescope(modname) return deps.require_or_err(modname, 'nvim-telescope/telescope.nvim') end ---@return unknown +---@return unknown function deps.require_plenary(modname) return deps.require_or_err(modname, 'nvim-lua/plenary.nvim') end +---@return unknown function deps.require_lspconfig(modname) return deps.require_or_err(modname, 'neovim/nvim-lspconfig') end +---@return unknown function deps.require_toggleterm(modname) return deps.require_or_err(modname, 'akinsho/toggleterm') end +---@return unknown function deps.require_iron(modname) return deps.require_or_err(modname, 'hkupty/iron.nvim') end diff --git a/lua/haskell-tools/hoogle.lua b/lua/haskell-tools/hoogle.lua index 55a4e1aa..4fd13a97 100644 --- a/lua/haskell-tools/hoogle.lua +++ b/lua/haskell-tools/hoogle.lua @@ -1,3 +1,5 @@ +---@mod haskell-tools.hoogle haskell-tools Hoogle search + local ht = require('haskell-tools') local hoogle_web = require('haskell-tools.hoogle.web') local hoogle_local = require('haskell-tools.hoogle.local') @@ -5,6 +7,12 @@ local deps = require('haskell-tools.deps') local ht_util = require('haskell-tools.util') local lsp_util = vim.lsp.util +---@class HaskellToolsHoogle +---@field hoogle_signature function Hoogle search for a symbol's type signature +---@field setup function +---@field handler unknown The internal handler + +---@type HaskellToolsHoogle local hoogle = { handler = nil, } @@ -61,8 +69,7 @@ local function lsp_hoogle_signature(options) return vim.lsp.buf_request(0, 'textDocument/hover', params, mk_lsp_hoogle_signature_handler(options)) end --- @param table --- @field string?: search_term - an optional search_term to search for +--- @param options table? Includes the `search_term` and options to pass to the telescope picker (if available) function hoogle.hoogle_signature(options) options = options or {} ht.log.debug { 'Hoogle signature search options', options } @@ -80,6 +87,7 @@ function hoogle.hoogle_signature(options) end end +--- Setup the Hoogle module. Called by the haskell-tools setup. function hoogle.setup() ht.log.debug('Hoogle setup...') hoogle_web.setup() diff --git a/lua/haskell-tools/hoogle/local.lua b/lua/haskell-tools/hoogle/local.lua index db1a69d2..5e47bb7f 100644 --- a/lua/haskell-tools/hoogle/local.lua +++ b/lua/haskell-tools/hoogle/local.lua @@ -1,3 +1,12 @@ +---@mod haskell-tools.hoogle.local + +---@brief [[ + +---WARNING: This is not part of the public API. +---Breaking changes to this module will not be reflected in the semantic versioning of this plugin. + +---@brief ]] + local ht = require('haskell-tools') local deps = require('haskell-tools.deps') diff --git a/lua/haskell-tools/hoogle/util.lua b/lua/haskell-tools/hoogle/util.lua index 795c0ba3..d8d8e317 100644 --- a/lua/haskell-tools/hoogle/util.lua +++ b/lua/haskell-tools/hoogle/util.lua @@ -1,3 +1,12 @@ +---@mod haskell-tools.hoogle.util + +---@brief [[ + +---WARNING: This is not part of the public API. +---Breaking changes to this module will not be reflected in the semantic versioning of this plugin. + +---@brief ]] + local deps = require('haskell-tools.deps') local util = require('haskell-tools.util') local actions = deps.require_telescope('telescope.actions') diff --git a/lua/haskell-tools/hoogle/web.lua b/lua/haskell-tools/hoogle/web.lua index 718523b7..6079d62a 100644 --- a/lua/haskell-tools/hoogle/web.lua +++ b/lua/haskell-tools/hoogle/web.lua @@ -1,3 +1,12 @@ +---@mod haskell-tools.hoogle.web + +---@brief [[ + +---WARNING: This is not part of the public API. +---Breaking changes to this module will not be reflected in the semantic versioning of this plugin. + +---@brief ]] + local ht = require('haskell-tools') local deps = require('haskell-tools.deps') local util = require('haskell-tools.util') diff --git a/lua/haskell-tools/init.lua b/lua/haskell-tools/init.lua index 794649d8..8136edfb 100644 --- a/lua/haskell-tools/init.lua +++ b/lua/haskell-tools/init.lua @@ -1,3 +1,30 @@ +---@toc haskell-tools.contents + +---@mod intro Introduction + +---@brief [[ +---This plugin automatically configures the `haskell-language-server` builtin LSP client +---and integrates with other haskell tools. +--- +---Warning: +---Do not call the `lspconfig.hls` setup or set up the lsp manually, +---as doing so may cause conflicts. +--- +---@brief ]] + +---@mod haskell-tools The haskell-tools module + +---@class HaskellTools +---@field config HaskellToolsConfig? The configuration module +---@field log HaskellToolsLogger? The logging module +---@field lsp HaskellToolsLspClient? The LSP module +---@field hoogle HaskellToolsHoogle? The Hoogle module +---@field repl HaskellToolsRepl? The GHCi repl module +---@field project HaskellToolsProject? The project module +---@field tags HaskellToolsTags? The tags module + +---Entry-point into this plugin's public API. +---@type HaskellTools local ht = { config = nil, log = nil, @@ -8,6 +35,23 @@ local ht = { tags = nil, } +---Sets up the plugin. +---Must be called before using this plugin's API. +--- +---@param opts HTOpts? the plugin configuration. +---@usage [[ +---local ht = require('haskell-tools') +---local def_opts = { noremap = true, silent = true, } +---ht.setup { +--- hls = { +--- on_attach = function(client, bufnr) +--- --- Set keybindings, etc. here. +--- end, +--- }, +--- } +---@usage ]] +---@see haskell-tools.config for the default configuration. +---@see lspconfig-keybindings for suggested keybindings by `nvim-lspconfig`. function ht.setup(opts) local config = require('haskell-tools.config') ht.config = config diff --git a/lua/haskell-tools/log.lua b/lua/haskell-tools/log.lua index b1d93062..9fb601de 100644 --- a/lua/haskell-tools/log.lua +++ b/lua/haskell-tools/log.lua @@ -1,5 +1,20 @@ +---@mod haskell-tools.log haskell-tools Logging + local ht = require('haskell-tools') +---@class HaskellToolsLogger +---@field debug function Log a debug message +---@field info function Log an info message +---@field warn function Log a warning message +---@field error function Log an error message +---@field set_level function Set the log level +---@field get_logfile function Get the haskell-tools log file +---@field nvim_open_logfile function Open the haskell-tools log file +---@field get_hls_logfile function Get the haskell-language-server log file +---@field nvim_open_hls_logfile function Open the haskell-language-server log file +---@field setup function + +---@type HaskellToolsLogger local log = {} local LARGE = 1e9 @@ -14,21 +29,21 @@ local logpath = vim.fn.stdpath('log') vim.fn.mkdir(logpath, 'p') local logfilename = logpath .. '/haskell-toolls.log' --- Get the haskell-tools.nvim log filename. --- @return string +---Get the haskell-tools.nvim log file path. +---@return string filepath function log.get_logfile() return logfilename end --- Open the haskell-tools.nvim log file. +---Open the haskell-tools.nvim log file. function log.nvim_open_logfile() vim.cmd('e ' .. log.get_logfile()) end local logfile, openerr --- @private --- Opens log file. Returns true if file is open, false on error --- @return boolean +--- @private +--- Opens log file. Returns true if file is open, false on error +--- @return boolean local function open_logfile() -- Try to open file only once if logfile then @@ -57,11 +72,17 @@ local function open_logfile() return true end +---Set up the log module. Called by the haskell-tools setup. function log.setup() local config = ht.config + if not config then + error('haskell-tools.setup() has not been called.') + end local opts = config.options.tools.log local hls_log = config.hls_log + + --- Get the haskell-language-server log file function log.get_hls_logfile() return hls_log end @@ -71,9 +92,9 @@ function log.setup() vim.cmd('e ' .. log.get_hls_logfile()) end - -- Set the log level - -- @param (string | int) the log level - -- @see vim.log.levels + --- Set the log level + --- @param level (string|integer) The log level + --- @see vim.log.levels function log.set_level(level) local log_levels = vim.deepcopy(vim.log.levels) vim.tbl_add_reverse_lookup(log_levels) diff --git a/lua/haskell-tools/lsp.lua b/lua/haskell-tools/lsp.lua index e91c9d37..287edd51 100644 --- a/lua/haskell-tools/lsp.lua +++ b/lua/haskell-tools/lsp.lua @@ -1,6 +1,12 @@ +---@mod haskell-tools.lsp haskell-tools LSP client setup + local ht = require('haskell-tools') local deps = require('haskell-tools.deps') +---@class HaskellToolsLspClient +---@field setup function + +---@type HaskellToolsLspClient local lsp = {} -- GHC can leave behind corrupted files if it does not exit cleanly. @@ -61,6 +67,7 @@ local function setup_hover() require('haskell-tools.lsp.hover').setup() end +--- Setup the LSP client. Called by the haskell-tools setup. function lsp.setup() setup_lsp() setup_definition() diff --git a/lua/haskell-tools/project-util.lua b/lua/haskell-tools/project-util.lua index 266c895d..58bf727b 100644 --- a/lua/haskell-tools/project-util.lua +++ b/lua/haskell-tools/project-util.lua @@ -1,3 +1,12 @@ +---@mod haskell-tools.project_util + +---@brief [[ + +---WARNING: This is not part of the public API. +---Breaking changes to this module will not be reflected in the semantic versioning of this plugin. + +---@brief ]] + local ht = require('haskell-tools') local deps = require('haskell-tools.deps') diff --git a/lua/haskell-tools/project.lua b/lua/haskell-tools/project.lua index 81330c8c..061e24e2 100644 --- a/lua/haskell-tools/project.lua +++ b/lua/haskell-tools/project.lua @@ -1,7 +1,26 @@ +---@mod haskell-tools.project haskell-tools Project module + local ht = require('haskell-tools') local project_util = require('haskell-tools.project-util') local deps = require('haskell-tools.deps') +---@brief [[ +--- The following commands are available: +--- +--- * `:HsProjectFile` - Open the project file for the current buffer (cabal.project or stack.yaml). +--- * `:HsPackageYaml` - Open the package.yaml file for the current buffer. +--- * `:HsPackageCabal` - Open the *.cabal file for the current buffer. +---@brief ]] + +---@class HaskellToolsProject +---@field setup function +---@field open_package_yaml function +---@field open_package_cabal function +---@field open_project_file function +---@field telescope_package_grep function? +---@field telescope_package_files function? + +---@type HaskellToolsProject local project = {} local function telescope_package_search(callback, opts) @@ -50,8 +69,11 @@ local commands = { }, } +--- Set up this module. Called by the haskell-tools setup. function project.setup() ht.log.debug('project.setup') + + --- Open the package.yaml of the package containing the current buffer. function project.open_package_yaml() vim.schedule(function() local file = vim.api.nvim_buf_get_name(0) @@ -66,6 +88,7 @@ function project.setup() end) end + --- Open the *.cabal file of the package containing the current buffer. function project.open_package_cabal() vim.schedule(function() local file = vim.api.nvim_buf_get_name(0) @@ -83,6 +106,7 @@ function project.setup() end) end + --- Open the current buffer's project file (cabal.project or stack.yaml). function project.open_project_file() vim.schedule(function() local file = vim.api.nvim_buf_get_name(0) @@ -103,16 +127,23 @@ function project.setup() end deps.if_available('telescope.builtin', function(t) + --- Live grep the current package with Telescope. + --- Available if nvim-telescope/telescope.nvim is installed. + ---@param opts table Telescope options function project.telescope_package_grep(opts) opts = vim.tbl_deep_extend('keep', { prompt_title_prefix = 'Package live grep' }, opts or {}) telescope_package_search(t.live_grep, opts) end + --- Find file in the current package with Telescope + --- Available if nvim-telescope/telescope.nvim is installed. + ---@param opts table Telescope options function project.telescope_package_files(opts) opts = vim.tbl_deep_extend('keep', { prompt_title_prefix = 'Package file search' }, opts or {}) telescope_package_search(t.find_files, opts) end end) + --- Available if nvim-telescope/telescope.nvim is installed. for _, command in ipairs(commands) do vim.api.nvim_create_user_command(unpack(command)) end diff --git a/lua/haskell-tools/repl.lua b/lua/haskell-tools/repl.lua index 08e1edf4..93bf79ff 100644 --- a/lua/haskell-tools/repl.lua +++ b/lua/haskell-tools/repl.lua @@ -1,8 +1,29 @@ +---@mod haskell-tools.repl haskell-tools GHCi REPL module + +---@bruief [[ +---Tools for interaction with a GHCi REPL +---@bruief ]] + local ht = require('haskell-tools') local project = require('haskell-tools.project-util') local ht_util = require('haskell-tools.util') --- Tools for interaction with a ghci repl +---@class HaskellToolsRepl +---@field mk_repl_cmd function +---@field buf_mk_repl_cmd function +---@field setup function +---@field handler unknown The internal repl handler +---@field toggle function +---@field quit function +---@field paste function +---@field paste_type function +---@field cword_type function +---@field paste_info function +---@field cword_info function +---@field load_file function +---@field reload function + +---@type HaskellToolsRepl local repl = {} -- Extend a repl command for `file`. @@ -62,10 +83,10 @@ local function mk_stack_repl_cmd(file) end) end --- Create the command to create a repl for a file. --- If `file` is `nil`, create a repl the nearest package. --- @param string | nil: file --- @return table | nil +--- Create the command to create a repl for a file. +--- If `file` is `nil`, create a repl the nearest package. +--- @param file string? The file to create the repl for +--- @return table? command function repl.mk_repl_cmd(file) local chk_path = file if not chk_path then @@ -94,13 +115,14 @@ function repl.mk_repl_cmd(file) return nil end --- Create the command to create a repl for the current buffer. --- @return table | nil +--- Create the command to create a repl for the current buffer. +--- @return table? command function repl.buf_mk_repl_cmd() local file = vim.api.nvim_buf_get_name(0) return repl.mk_repl_cmd(file) end +--- Set up this module. Called by the haskell-tools setup. function repl.setup() local opts = ht.config.options.tools.repl local handler = {} @@ -115,15 +137,16 @@ function repl.setup() builtin.setup(repl.mk_repl_cmd, opts) handler = builtin end - -- Toggle a GHCi repl - -- @param string?: optional file path + + --- Toggle a GHCi REPL + --- @param filepath string?: optional file path repl.toggle = handler.toggle - -- Quit the repl + --- Quit the REPL repl.quit = handler.quit - -- Paste from register `reg` to the repl - -- @param string?: register (defaults to '"') + --- Paste from register `reg` to the REPL + --- @param reg string?: register (defaults to '"') function repl.paste(reg) local data = vim.fn.getreg(reg or '"') if vim.endswith(data, '\n') then @@ -151,42 +174,40 @@ function repl.setup() handler.send_cmd(cmd .. ' ' .. cword) end - -- Query the repl for the type of register `reg` - -- @param string?: register (defaults to '"') + --- Query the REPL for the type of register `reg` + --- @param reg string? register (defaults to '"') function repl.paste_type(reg) handle_reg(':t', reg) end - -- Query the repl for the type of word under the cursor - -- @param string?: register (defaults to '"') + --- Query the REPL for the type of word under the cursor function repl.cword_type() handle_cword(':t') end - -- Query the repl for info on register `reg` - -- @param string?: register (defaults to '"') + --- Query the REPL for info on register `reg` + --- @param reg string? register (defaults to '"') function repl.paste_info(reg) handle_reg(':i', reg) end - -- Query the repl for the type of word under the cursor - -- @param string?: register (defaults to '"') + --- Query the REPL for the type of word under the cursor function repl.cword_info() handle_cword(':i') end - -- Load a file into the repl - -- @param string: absolute file path - function repl.load_file(file) - if vim.fn.filereadable(file) == 0 then - local err_msg = 'File: ' .. file .. ' does not exist or is not readable.' + --- Load a file into the REPL + --- @param filepath string The absolute file path + function repl.load_file(filepath) + if vim.fn.filereadable(filepath) == 0 then + local err_msg = 'File: ' .. filepath .. ' does not exist or is not readable.' ht.log.error(err_msg) vim.notify(err_msg, vim.log.levels.ERROR) end - handler.send_cmd(':l ' .. file) + handler.send_cmd(':l ' .. filepath) end - -- Reload the repl + --- Reload the repl function repl.reload() handler.send_cmd(':r') end diff --git a/lua/haskell-tools/repl/builtin.lua b/lua/haskell-tools/repl/builtin.lua index 8c336824..fc965456 100644 --- a/lua/haskell-tools/repl/builtin.lua +++ b/lua/haskell-tools/repl/builtin.lua @@ -1,3 +1,12 @@ +---@mod haskell-tools.repl.builtin + +---@brief [[ + +---WARNING: This is not part of the public API. +---Breaking changes to this module will not be reflected in the semantic versioning of this plugin. + +---@brief ]] + local ht = require('haskell-tools') -- Utility functions for the ghci repl module. @@ -184,18 +193,19 @@ end function builtin.setup(mk_repl_cmd, options) ht.log.debug { 'repl.builtin setup', options } builtin.go_back = options.auto_focus ~= true - -- @param string?: Optional path of the file to load into the repl - function builtin.toggle(file, opts) + ---@param filepath string path of the file to load into the repl + ---@param opts table? + function builtin.toggle(filepath, opts) opts = opts or vim.empty_dict() local cur_win = vim.api.nvim_get_current_win() - if file and not vim.endswith(file, '.hs') then - local err_msg = 'haskell-tools.repl.builtin: Not a Haskell file: ' .. file + if filepath and not vim.endswith(filepath, '.hs') then + local err_msg = 'haskell-tools.repl.builtin: Not a Haskell file: ' .. filepath ht.log.error(err_msg) vim.notify(err_msg, vim.log.levels.ERROR) return end local function mk_repl_cmd_wrapped() - return mk_repl_cmd(file) + return mk_repl_cmd(filepath) end local create_or_toggle_callback = options.builtin.create_repl_window(view) create_or_toggle_callback(mk_repl_cmd_wrapped) diff --git a/lua/haskell-tools/repl/toggleterm.lua b/lua/haskell-tools/repl/toggleterm.lua index 91cb8ad5..73515da1 100644 --- a/lua/haskell-tools/repl/toggleterm.lua +++ b/lua/haskell-tools/repl/toggleterm.lua @@ -1,3 +1,12 @@ +---@mod haskell-tools.repl.toggleterm + +---@brief [[ + +---WARNING: This is not part of the public API. +---Breaking changes to this module will not be reflected in the semantic versioning of this plugin. + +---@brief ]] + local ht = require('haskell-tools') local deps = require('haskell-tools.deps') @@ -42,16 +51,16 @@ function toggleterm.setup(mk_repl_cmd, opts) return Terminal:new(terminal_opts) end - -- @param string?: Optional path of the file to load into the repl - function toggleterm.toggle(file) + --- @param filepath string? Path of the file to load into the repl + function toggleterm.toggle(filepath, _) opts = opts or vim.empty_dict() - if file and not vim.endswith(file, '.hs') then - local err_msg = 'haskell-tools.repl.toggleterm: Not a Haskell file: ' .. file + if filepath and not vim.endswith(filepath, '.hs') then + local err_msg = 'haskell-tools.repl.toggleterm: Not a Haskell file: ' .. filepath ht.log.error(err_msg) vim.notify(err_msg, vim.log.levels.ERROR) return end - local cmd = table.concat(mk_repl_cmd(file) or {}, ' ') + local cmd = table.concat(mk_repl_cmd(filepath) or {}, ' ') if cmd == '' then local err_msg = 'haskell-tools.repl.toggleterm: Could not create a repl command.' ht.log.error(err_msg) diff --git a/lua/haskell-tools/tags.lua b/lua/haskell-tools/tags.lua index 7961cd76..cec98472 100644 --- a/lua/haskell-tools/tags.lua +++ b/lua/haskell-tools/tags.lua @@ -1,7 +1,15 @@ +---@mod haskell-tools.tags haskell-tools fast-tags module + +---@class HaskellToolsTags +---@field generate_project_tags function? +---@field generate_package_tags function? +---@field setup function? + local ht = require('haskell-tools') local deps = require('haskell-tools.deps') local project_util = require('haskell-tools.project-util') +---@type HaskellToolsTags local tags = {} local _state = { @@ -12,11 +20,13 @@ local _state = { local function setup_fast_tags(config) local Job = deps.require_plenary('plenary.job') - -- Generates tags for the current project - -- @param path string?: file path - -- @param opts table? - -- @field refresh boolean: whether to regenerate the tags if they have already been generated - -- for the project (default: true) + ---@class GenerateProjectTagsOpts + ---@field refresh boolean Whether to refresh the tags if they have already been generated + --- for the project (default: true) + + --- Generates tags for the current project + --- @param path string? File path + --- @param opts GenerateProjectTagsOpts? Options function tags.generate_project_tags(path, opts) path = path or vim.api.nvim_buf_get_name(0) opts = vim.tbl_extend('force', { refresh = true }, opts or {}) @@ -43,6 +53,8 @@ local function setup_fast_tags(config) end end + --- Generate tags for the package containing `path` + ---@param path string? File path function tags.generate_package_tags(path) path = path or vim.api.nvim_buf_get_name(0) _state.fast_tags_generating = true @@ -97,6 +109,7 @@ local function setup_fast_tags(config) }) end +--- Setup the tags module. Called by the haskell-tools setup. function tags.setup() ht.log.debug('tags.setup') local config = ht.config.options.tools.tags diff --git a/lua/haskell-tools/util.lua b/lua/haskell-tools/util.lua index 5354b2e3..0a55ff28 100644 --- a/lua/haskell-tools/util.lua +++ b/lua/haskell-tools/util.lua @@ -1,3 +1,12 @@ +---@mod haskell-tools.util + +---@brief [[ + +---WARNING: This is not part of the public API. +---Breaking changes to this module will not be reflected in the semantic versioning of this plugin. + +---@brief ]] + local ht = require('haskell-tools') local deps = require('haskell-tools.deps') local Job = deps.require_plenary('plenary.job') diff --git a/lua/telescope/_extensions/ht.lua b/lua/telescope/_extensions/ht.lua index 54d9c3af..2ddd12a8 100644 --- a/lua/telescope/_extensions/ht.lua +++ b/lua/telescope/_extensions/ht.lua @@ -1,5 +1,36 @@ +---@mod haskell-tools.telescope-extension haskell-tools Telescope extension + local deps = require('haskell-tools.deps') +---@brief [[ +--- If `telescope.nvim` is installed, `haskell-tools` will register the `ht` extenstion +--- with the following commands: +--- +--- * `:Telescope ht package_files` - Search for files within the current (sub)package. +--- * `:Telescope ht package_hsfiles` - Search for Haskell files within the current (sub)package. +--- * `:Telescope ht package_grep` - Live grep within the current (sub)package. +--- * `:Telescope ht package_hsgrep` - Live grep Haskell files within the current (sub)package. +--- * `:Telescope ht hoogle_signature` - Run a Hoogle search for the type signature under the cursor. +--- +--- To load the extension, call +--- +--- > +--- require('telescope').load_extension('ht') +--- < +--- +--- In Lua, you can access the extension with +--- +--- > +--- local telescope = require('telescope') +--- telescope.extensions.ht.package_files() +--- telescope.extensions.ht.package_hsfiles() +--- telescope.extensions.ht.package_grep() +--- telescope.extensions.ht.package_hsgrep() +--- telescope.extensions.ht.hoogle_signature() +--- < +--- +---@brief ]] + return deps.if_available('telescope', function(telescope) local ht_extension = require('telescope._extensions.ht.extension') return telescope.register_extension(ht_extension) diff --git a/nix/docgen.nix b/nix/docgen.nix new file mode 100644 index 00000000..0a295fb2 --- /dev/null +++ b/nix/docgen.nix @@ -0,0 +1,11 @@ +{pkgs, ...}: +pkgs.writeShellApplication { + name = "docgen"; + runtimeInputs = with pkgs; [ + lemmy-help + ]; + text = '' + mkdir -p doc + lemmy-help lua/haskell-tools/{init,config,lsp,hoogle,repl,project,tags,log}.lua lua/telescope/_extensions/ht.lua > doc/haskell-tools.txt + ''; +}