Skip to content

Commit

Permalink
Merge pull request #18 from MrcJkb/7-hoogle-search
Browse files Browse the repository at this point in the history
hoogle search
  • Loading branch information
mrcjkb authored Oct 25, 2022
2 parents e142b95 + 0e0bee7 commit aaa2672
Show file tree
Hide file tree
Showing 11 changed files with 451 additions and 21 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

## [1.0.0] - 2022-10-25
### Added
- Hoogle search (BREAKING CHANGE: Depends on [`plenary.nvim`](https://github.com/nvim-lua/plenary.nvim))
- Hoogle search for signature under cursor (telescope-local, telescope-web or browser)
- Automatic registration of selection range capabilities if [nvim-lsp-selection-range](https://github.com/camilledejoye/nvim-lsp-selection-range) is loaded.

## [0.2.0] - 2022-10-18
Expand Down
76 changes: 60 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,17 @@ Supercharge your Haskell experience in [neovim](https://neovim.io/)!

## Prerequisites

### Required

* `neovim >= 0.8`
* [`nvim-lspconfig`](https://github.com/neovim/nvim-lspconfig)
* [`plenary.nvim`](https://github.com/nvim-lua/plenary.nvim)
* [`haskell-language-server`](https://haskell-language-server.readthedocs.io/en/latest/installation.html)

### Optional

* [`telescope.nvim`](https://github.com/nvim-telescope/telescope.nvim)
* A local [`hoogle`](https://github.com/ndmitchell/hoogle/blob/master/docs/Install.md) installation

## Installation

Expand All @@ -28,9 +35,14 @@ use {
'MrcJkb/haskell-tools.nvim',
requires = {
'neovim/nvim-lspconfig',
}
'nvim-lua/plenary.nvim',
'nvim-telescope/telescope.nvim', -- optional
},
-- tag = 'x.y.z' -- [^1]
}
```
[^1] It is suggested to use the [latest release tag](./CHANGELOG.md)
and to update it manually, if you would like to avoid breaking changes.

## Quick Setup

Expand All @@ -41,20 +53,26 @@ This plugin automatically configures the haskell-language-server NeoVim client.
To get started quickly with the default setup, add the following to your NeoVim config:

```lua
-- See nvim-lspconfig's suggested configuration for keymaps, etc.
local on_attach = function(_, bufnr)
-- haskell-language-server relies heavily on codeLenses,
-- so auto-refresh (see advanced configuration) is enabled by default
vim.keymap.set('n', '<space>ca', vim.lsp.codelens.run)
end
local ht = require('haskell-tools')

require('haskell-tools').setup {
local opts = { noremap = true, silent = true, buffer = bufnr }
ht.setup {
hls = {
on_attach = on_attach,
-- See nvim-lspconfig's suggested configuration for keymaps, etc.
on_attach = function(client, bufnr)
-- haskell-language-server relies heavily on codeLenses,
-- so auto-refresh (see advanced configuration) is enabled by default
vim.keymap.set('n', '<space>ca', vim.lsp.codelens.run, opts)
vim.keymap.set('n', '<space>hs', ht.hoogle.hoogle_signature, opts)
-- default_on_attach(client, bufnr) -- if defined, see nvim-lspconfig
end,
},
}
```

If using a local `hoogle` installation, [follow these instructions](https://github.com/ndmitchell/hoogle/blob/master/docs/Install.md#generate-a-hoogle-database)
to generate a database.

## Features

- [x] Basic haskell-language-server functionality on par with `nvim-lspconfig.hls`
Expand All @@ -68,18 +86,36 @@ require('haskell-tools').setup {
- [x] Automatically refreshes code lenses by default, which haskell-language-server heavily relies on. [Can be disabled.](#advanced-configuration)
- [x] The following code lenses are currently supported:

##### [Show/Add type signatures for bindings without type signatures](https://haskell-language-server.readthedocs.io/en/latest/features.html#add-type-signature)
#### [Show/Add type signatures for bindings without type signatures](https://haskell-language-server.readthedocs.io/en/latest/features.html#add-type-signature)
[![asciicast](https://asciinema.org/a/zC88fqMhPq25lHFYgEF6OxMgk.svg)](https://asciinema.org/a/zC88fqMhPq25lHFYgEF6OxMgk?t=0:04)

##### [Evaluate code snippets in comments](https://haskell-language-server.readthedocs.io/en/latest/features.html#evaluation-code-snippets-in-comments)
#### [Evaluate code snippets in comments](https://haskell-language-server.readthedocs.io/en/latest/features.html#evaluation-code-snippets-in-comments)
[![asciicast](https://asciinema.org/a/TffryPrWpBkLnBK6dKXvOxd41.svg)](https://asciinema.org/a/TffryPrWpBkLnBK6dKXvOxd41?t=0:04)

##### [Make import lists fully explicit](https://haskell-language-server.readthedocs.io/en/latest/features.html#make-import-lists-fully-explicit-code-lens)
#### [Make import lists fully explicit](https://haskell-language-server.readthedocs.io/en/latest/features.html#make-import-lists-fully-explicit-code-lens)
[![asciicast](https://asciinema.org/a/l2ggVaN5eQbOj9iGkaethnS7P.svg)](https://asciinema.org/a/l2ggVaN5eQbOj9iGkaethnS7P?t=0:02)

##### [Fix module names that do not match the file path](https://haskell-language-server.readthedocs.io/en/latest/features.html#fix-module-names)
#### [Fix module names that do not match the file path](https://haskell-language-server.readthedocs.io/en/latest/features.html#fix-module-names)
[![asciicast](https://asciinema.org/a/n2qd2zswLOonl2ZEb8uL4MHsG.svg)](https://asciinema.org/a/n2qd2zswLOonl2ZEb8uL4MHsG?t=0:02)

### Beyond haskell-language-server

The below features are not implemented by haskell-language-server.

#### Hoogle-search for signature

* Search for the type signature under the cursor.
* Falls back to the word under the cursor if the type signature cannot be determined.
* Telescope keymaps:
- `<CR>` to copy the selected entry to the clipboard.
- `<C-b>` to open the selected entry's URL in a browser.

```lua
require('haskell-tools').hoogle.hoogle_signature()
```

[![asciicast](https://asciinema.org/a/4GSmXrCvpt7idBHnuZVQQkJ9R.svg)](https://asciinema.org/a/4GSmXrCvpt7idBHnuZVQQkJ9R)

### Planned

For planned features, refer to the [issues](https://github.com/MrcJkb/haskell-tools.nvim/issues?q=is%3Aopen+is%3Aissue+label%3Aenhancement).
Expand All @@ -90,18 +126,26 @@ For planned features, refer to the [issues](https://github.com/MrcJkb/haskell-to
To modify the language server configs, call

```lua
-- defaults
require('haskell-tools').setup {
tools = { -- haskell-tools options
codeLens = {
-- Whether to automatically display/refresh codeLenses
autoRefresh = false, -- defaults to true
autoRefresh = true,
},
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',
},
},
hls = { -- LSP client options
-- ...
haskell = { -- haskell-language-server options
formattingProvider = 'fourmolu', -- Defaults to 'ormolu'
checkProject = false, -- Defaults to true, which could have a performance impact on large monorepos.
formattingProvider = 'ormolu',
checkProject = true, -- Setting this to true could have a performance impact on large monorepos.
-- ...
}
}
Expand Down
9 changes: 9 additions & 0 deletions lua/haskell-tools/config.lua
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,15 @@ local defaults = {
-- Whether to automatically display/refresh codeLenses
autoRefresh = true,
},
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,
},
},
hls = {
on_attach = function(...) end,
Expand Down
29 changes: 25 additions & 4 deletions lua/haskell-tools/deps.lua
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ local M = {}
-- @return the return value of on_available or on_not_available
function M.if_available(modname, on_available, on_not_available)
local has_mod, mod = pcall(require, modname)
if has_mod then
if has_mod and type(on_available) == 'function' then
return on_available(mod)
elseif has_mod then
return on_available
end
if not on_not_available then
return nil
Expand All @@ -18,14 +20,33 @@ function M.if_available(modname, on_available, on_not_available)
return on_not_available
end

local function require_or_err(modname, err_msg)
--@return unknown
function M.require_or_err(modname, plugin_name)
return M.if_available(
modname,
function(mod) return mod end,
function() error('haskell-tools: ' .. err_msg) end
function() error('haskell-tools: This plugin requires the ' .. plugin_name .. ' plugin.') end
)
end

M.lspconfig = require_or_err('lspconfig', 'This plugin requires the neovim/nvim-lspconfig plugin')
--@return boolean
function M.has(modname)
return M.if_available(modname, true, false)
end

--@return boolean
function M.has_telescope()
return M.has('telescope')
end

--@return unknown
function M.require_telescope(modname)
return M.require_or_err(modname, 'nvim-telescope/telescope.nvim')
end

--@return unknown
function M.require_plenary(modname)
return M.require_or_err(modname, 'nvim-lua/plenary.nvim')
end

return M
65 changes: 65 additions & 0 deletions lua/haskell-tools/hoogle-local.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
local deps = require('haskell-tools.deps')
local util = require('haskell-tools.util')

local M = {}

function M.has_hoogle()
return vim.fn.executable('hoogle') == 1
end

local function mk_hoogle_args(search_term, opts)
local count = opts.count or 50
local cmd = vim.tbl_flatten { '--json', '--count=' .. count, search_term, }
return cmd
end

local function setup_telescope_search()

local pickers = deps.require_telescope('telescope.pickers')
local finders = deps.require_telescope('telescope.finders')
local previewers = deps.require_telescope('telescope.previewers')
local config = deps.require_telescope('telescope.config').values
local telescope_util = require('haskell-tools.telescope-util')
local Job = deps.require_plenary('plenary.job')

function M.telescope_search(search_term, opts)
opts = util.tbl_merge(opts or {}, {
layout_strategy = 'horizontal',
layout_config = { preview_width = 80 },
})
opts.entry_maker = opts.entry_maker or telescope_util.mk_hoogle_entry
Job:new({
command = 'hoogle',
args = mk_hoogle_args(search_term, opts),
on_exit = function(j, return_val)
vim.schedule(function()
if (return_val ~= 0) then
error('haskell-toos: hoogle search failed. Return value: ' .. return_val)
end
local output = j:result()[1]
if #output < 1 then
return
end
pickers.new(opts, {
prompt_title = 'Hoogle: ' .. search_term,
sorter = config.generic_sorter(opts),
finder = finders.new_table {
results = vim.json.decode(output),
entry_maker = telescope_util.mk_hoogle_entry
},
previewer = previewers.display_content.new(opts),
attach_mappings = telescope_util.attach_mappings,
}):find()
end)
end
}):start()
end
end

function M.setup()
if M.has_hoogle() then
setup_telescope_search()
end
end

return M
85 changes: 85 additions & 0 deletions lua/haskell-tools/hoogle-web.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
local deps = require('haskell-tools.deps')
local util = require('haskell-tools.util')

local M = {}

local char_to_hex = function(c)
return string.format("%%%02X", string.byte(c))
end

local function urlencode(url)
if url == nil then
return
end
url = url:gsub("\n", "\r\n")
url = url:gsub("([^%w ])", char_to_hex)
url = url:gsub(" ", "+")
return url
end

local function mk_hoogle_request(search_term, opts)
local hoogle_opts = opts.hoogle or {}
local scope_param = hoogle_opts.scope and '&scope=' .. hoogle_opts.scope or ''
return 'https://hoogle.haskell.org/?hoogle='
.. urlencode(search_term)
.. scope_param
.. (hoogle_opts.json and '&mode=json' or '')
end

local function setup_telescope_search()
local pickers = deps.require_telescope('telescope.pickers')
local finders = deps.require_telescope('telescope.finders')
local previewers = deps.require_telescope('telescope.previewers')
local config = deps.require_telescope('telescope.config').values
local telescope_util = require('haskell-tools.telescope-util')
local async = deps.require_plenary('plenary.async')

local curl = deps.require_plenary('plenary.curl')

function M.telescope_search(search_term, opts)
async.run(function()
if vim.fn.executable('curl') == 0 then
error("haskell-tools.hoogle-web: 'curl' executable not found! Aborting.")
return
end
opts = util.tbl_merge(opts or {}, {
layout_strategy = 'horizontal',
layout_config = { preview_width = 80 },
hoogle = { json = true },
})
local response = curl.get {
url = mk_hoogle_request(search_term, opts),
accept = 'application/json',
}
local results = vim.json.decode(response.body)
pickers.new(opts, {
prompt_title = 'Hoogle: ' .. search_term,
finder = finders.new_table {
results = results,
entry_maker = telescope_util.mk_hoogle_entry
},
sorter = config.generic_sorter(opts),
previewer = previewers.display_content.new(opts),
attach_mappings = telescope_util.attach_mappings,
}):find()
end)
end
end

local function setup_browser_search()
function M.browser_search(search_term, opts)
opts = util.tbl_merge(opts or {}, {
hoogle = { json = false },
})
util.open_browser(mk_hoogle_request(search_term, opts))
end
end

function M.setup()
if deps.has_telescope() then
setup_telescope_search()
end
setup_browser_search()
end

return M
Loading

0 comments on commit aaa2672

Please sign in to comment.