Skip to content

Commit

Permalink
fix(hover): wrong type definition location + de-clutter hover menu (#127
Browse files Browse the repository at this point in the history
)

- [x] Tested, as applicable:
  - [x] Manually
  - [ ] Added plenary specs
- [x] Updated
[CHANGELOG.md](https://github.com/MrcJkb/haskell-tools.nvim/blob/master/CHANGELOG.md)
(if applicable).
- [x] Fits
[CONTRIBUTING.md](https://github.com/MrcJkb/haskell-tools.nvim/blob/master/CONTRIBUTING.md)
  • Loading branch information
mrcjkb authored Jan 31, 2023
1 parent 74f5010 commit 2e63d63
Show file tree
Hide file tree
Showing 2 changed files with 67 additions and 33 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Changed
- Set up LSP client without `nvim-lspconfig` (removes the dependency).
- Hover actions: Show package/project relative locations.
- Hover actions: Shorten locations relative to file, package or project.
- Only show definition/typeDefinition hover actions if they are in different locations.
### Added
- `HlsStart`, `HlsStop` and `HlsRestart` commands.
- Dynamically load `haskell-language-server` settings JSON from project root, if available.
Expand Down
97 changes: 65 additions & 32 deletions lua/haskell-tools/lsp/hover.lua
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,18 @@ local function run_command()
end

---@param location string The location provided by LSP hover
---@param current_file string The current file path
---@return string formatted_location
local function format_location(location)
local function format_location(location, current_file)
local formatted_location = ('%s'):format(location):gsub('%*', ''):gsub('', '`'):gsub('', '`')
local file_location = formatted_location:match('(.*).hs:')
if not file_location then
return formatted_location
end
local is_current_buf = formatted_location:find(current_file, 1, true) == 1
if is_current_buf then
return formatted_location:sub(#current_file + 2)
end
local path = file_location .. '.hs'
local package_path = project_util.match_package_root(path)
if package_path then
Expand All @@ -67,6 +72,27 @@ local function format_location(location)
return formatted_location
end

---@param result table LSP result
---@return string location string
local function mk_location(result)
local range_start = result.range and result.range.start or {}
local line = range_start.line
local character = range_start.character
local uri = result.uri and result.uri:gsub('file://', '')
return line and character and uri and uri .. ':' .. tostring(line + 1) .. ':' .. tostring(character + 1) or ''
end

---Is the result's start location the same as the params location?
---@param params table LSP location params
---@param result table LSP result
---@return boolean
local function is_same_position(params, result)
local range_start = result.range and result.range.start or {}
return params.textDocument.uri == result.uri
and params.position.line == range_start.line
and params.position.character == range_start.character
end

---LSP handler for textDocument/hover
---@param result table
---@param ctx table
Expand All @@ -92,29 +118,30 @@ function hover.on_hover(_, result, ctx, config)
_state.commands = {}
local signature = ht_util.try_get_signature_from_markdown(result.contents.value)
if signature and signature ~= '' then
table.insert(actions, 1, string.format('%d. Hoogle search: `%s`.', #actions + 1, signature))
table.insert(actions, 1, string.format('%d. Hoogle search: `%s`', #actions + 1, signature))
table.insert(_state.commands, function()
ht.log.debug { 'Hover: Hoogle search for signature', signature }
ht.hoogle.hoogle_signature { search_term = signature }
end)
end
local cword = vim.fn.expand('<cword>')
if cword ~= signature then
table.insert(actions, 1, string.format('%d. Hoogle search: `%s`.', #actions + 1, cword))
table.insert(actions, 1, string.format('%d. Hoogle search: `%s`', #actions + 1, cword))
table.insert(_state.commands, function()
ht.log.debug { 'Hover: Hoogle search for cword', cword }
ht.hoogle.hoogle_signature { search_term = cword }
end)
end
local params = lsp_util.make_position_params()
local found_location = false
local found_type_definition = false
local found_documentation = false
local found_source = false
for i, value in ipairs(markdown_lines) do
if vim.startswith(value, '[Documentation]') and not found_documentation then
found_documentation = true
table.insert(to_remove, 1, i)
table.insert(actions, 1, string.format('%d. Open documentation in browser.', #actions + 1))
table.insert(actions, 1, string.format('%d. Open documentation in browser', #actions + 1))
local uri = string.match(value, '%[Documentation%]%((.+)%)')
table.insert(_state.commands, function()
ht.log.debug { 'Hover: Open documentation in browser', uri }
Expand All @@ -123,54 +150,67 @@ function hover.on_hover(_, result, ctx, config)
elseif vim.startswith(value, '[Source]') and not found_source then
found_source = true
table.insert(to_remove, 1, i)
table.insert(actions, 1, string.format('%d. View source in browser.', #actions + 1))
table.insert(actions, 1, string.format('%d. View source in browser', #actions + 1))
local uri = string.match(value, '%[Source%]%((.+)%)')
table.insert(_state.commands, function()
ht.log.debug { 'Hover: View source in browser', uri }
ht_util.open_browser(uri)
end)
end
local location = string.match(value, '*Defined [ia][nt] (.+)')
local current_file = params.textDocument.uri:gsub('file://', '')
if location and not found_location then
found_location = true
table.insert(to_remove, 1, i)
local location_suffix = (' in %s.'):format(format_location(location))
local location_suffix = ('%s'):format(format_location(location, current_file))
local results, err = vim.lsp.buf_request_sync(0, 'textDocument/definition', params, 1000)
local can_go_to_definition = false
if not err and results and #results > 0 then -- Can go to definition
local definition_results = results[1] and results[1].result or {}
if #definition_results > 0 then
can_go_to_definition = true
local definition_result = definition_results[1]
table.insert(actions, 1, string.format('%d. Go to definition' .. location_suffix, #actions + 1))
table.insert(_state.commands, function()
-- We don't call vim.lsp.buf.definition() because the location params may have changed
local definition_ctx = {
method = 'textDocument/definition',
client_id = ctx.client_id,
}
ht.log.debug { 'Hover: Go to definition', definition_result }
vim.lsp.handlers['textDocument/definition'](_, definition_result, definition_ctx)
end)
if not is_same_position(params, definition_result) then
table.insert(actions, 1, string.format('%d. Go to definition at ' .. location_suffix, #actions + 1))
table.insert(_state.commands, function()
-- We don't call vim.lsp.buf.definition() because the location params may have changed
local definition_ctx = {
method = 'textDocument/definition',
client_id = ctx.client_id,
}
ht.log.debug { 'Hover: Go to definition', definition_result }
vim.lsp.handlers['textDocument/definition'](_, definition_result, definition_ctx)
end)
end
end
end
if not can_go_to_definition then -- Display Hoogle search instead
local package = location:match('‘(.+)’')
local search_term = package and package .. '.' .. cword or cword
table.insert(actions, 1, string.format('%d. Hoogle search: `%s`.', #actions + 1, search_term))
table.insert(actions, 1, string.format('%d. Hoogle search: `%s`', #actions + 1, search_term))
table.insert(_state.commands, function()
ht.log.debug { 'Hover: Hoogle search for definition', search_term }
ht.hoogle.hoogle_signature { search_term = search_term }
end)
end
-- XXX: Reduce nesting
if location:match('.hs') then
results, err = vim.lsp.buf_request_sync(0, 'textDocument/typeDefinition', params, 1000)
if not err and results and #results > 0 then -- Can go to definition
local type_definition_results = results[1] and results[1].result or {}
if #type_definition_results > 0 then
local type_definition_result = type_definition_results[1]
table.insert(actions, 1, string.format('%d. Go to type definition' .. location_suffix, #actions + 1))
local reference_params = vim.tbl_deep_extend('force', params, { context = { includeDeclaration = true } })
table.insert(actions, 1, string.format('%d. Find references', #actions + 1))
table.insert(_state.commands, function()
ht.log.debug { 'Hover: Find references', reference_params }
-- We don't call vim.lsp.buf.references() because the location params may have changed
vim.lsp.buf_request(0, 'textDocument/references', reference_params)
end)
end
if not found_type_definition then
local results, err = vim.lsp.buf_request_sync(0, 'textDocument/typeDefinition', params, 1000)
if not err and results and #results > 0 then -- Can go to type definition
found_type_definition = true
local type_definition_results = results[1] and results[1].result or {}
if #type_definition_results > 0 then
local type_definition_result = type_definition_results[1]
local type_def_suffix = format_location(mk_location(type_definition_result), current_file)
if not is_same_position(params, result) then
table.insert(actions, 1, string.format('%d. Go to type definition at ' .. type_def_suffix, #actions + 1))
table.insert(_state.commands, function()
-- We don't call vim.lsp.buf.typeDefinition() because the location params may have changed
local type_definition_ctx = {
Expand All @@ -183,13 +223,6 @@ function hover.on_hover(_, result, ctx, config)
end
end
end
local reference_params = vim.tbl_deep_extend('force', params, { context = { includeDeclaration = true } })
table.insert(actions, 1, string.format('%d. Find references.', #actions + 1))
table.insert(_state.commands, function()
ht.log.debug { 'Hover: Find references', reference_params }
-- We don't call vim.lsp.buf.references() because the location params may have changed
vim.lsp.buf_request(0, 'textDocument/references', reference_params)
end)
end
end
for _, pos in ipairs(to_remove) do
Expand Down

0 comments on commit 2e63d63

Please sign in to comment.