Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ _Screenshot is showing the Snacks picker._

## Features

- Browse and search Go standard library packages and project packages.
- Search and browse docs for Go standard library packages and project packages.
- Go to definition capability.
- Syntax highlighting for Go documentation.
- Optionally leverage [`stdsym`](https://github.com/lotusirous/gostdsym) for
symbols searching.
Expand Down Expand Up @@ -89,6 +90,8 @@ provided:
- `:GoDoc` - Open picker and search packages.
- `:GoDoc <package>` - Directly open documentation for the specified package or
symbol.
- In Normal mode, press `gd` to go package definition (only supported by Snacks
and Telescope pickers). For fzf-lua, the keymap is `ctrl-s`.

> [!WARNING]
>
Expand Down Expand Up @@ -287,6 +290,7 @@ All adapters must implement the interface of `GoDocAdapter`:
--- @field get_items fun(): string[] Function that returns a list of available items
--- @field get_content fun(choice: string): string[] Function that returns the content
--- @field get_syntax_info fun(): GoDocSyntaxInfo Function that returns syntax info
--- @field goto_definition? fun(choice: string, picker_gotodef_fun: fun()?): nil Function that returns the definition location
--- @field health? fun(): GoDocHealthCheck[] Optional health check function
```

Expand All @@ -299,6 +303,7 @@ The `opts` which can be passed into an adapter (by the user) is implemented by
--- @field get_items? fun(): string[] Override the get_items function
--- @field get_content? fun(choice: string): string[] Override the get_content function
--- @field get_syntax_info? fun(): GoDocSyntaxInfo Override the get_syntax_info function
--- @field goto_definition? fun(choice: string, picker_gotodef_fun: fun()?): nil Override the get_definition function
--- @field health? fun(): GoDocHealthCheck[] Override the health check function
--- @field [string] any Other adapter-specific options
```
Expand Down
91 changes: 91 additions & 0 deletions lua/godoc/adapters/go.lua
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,94 @@ local function get_packages()
return all_packages
end

--- @param item string
--- @param picker_gotodef_fun fun()
local function goto_definition(item, picker_gotodef_fun)
-- write temp file
local content = {}
local cursor_pos = {}
-- check if the chosen item contains a symbol
local is_symbol = string.match(item, "%.%a[%w_]*$") ~= nil
if is_symbol then
-- an import with symbol is passed, e.g. archive/tar.FileInfoNames
--
-- extract the import path (archive/tar)
local import_path = item:match("^(.-)%.")
-- extract the package name, which comes after the last slash (e.g. tar)
local package_name = import_path:match("([^/]+)$")
-- extract the symbol name, which comes after the last dot (e.g. FileInfoNames)
local symbol = item:match("([^%.]+)$")
-- the contents of the go file, which contains imports to the package and the symbol as well as the fmt package
local line = ' fmt.Printf("%v", ' .. package_name .. "." .. symbol .. ")"
content = {
"package main",
"",
"import (",
' "' .. import_path .. '"',
' "fmt"',
")",
"",
"func main() {",
line,
"}",
}
-- put the position/cursor on the symbol, e.g. on the 'F' of FileInfoNames
cursor_pos = { 9, line:find(symbol) + 1 }
else
-- a package name is passed, e.g. "archive/tar"
content = {
"package main",
"",
'import "' .. item .. '"',
}
cursor_pos = { 3, 9 }
end
local now = os.time()
local filename = "godoc_" .. now .. ".go"
local tempfile = vim.fn.getcwd() .. "/" .. filename
local go_mod_filepath = vim.fn.findfile("go.mod", vim.fn.getcwd() .. ";")
local go_mod_dir = vim.fn.fnamemodify(go_mod_filepath, ":p:h")
if go_mod_dir == "" then
vim.notify("Failed to find go.mod file, can only gotodef on std lib", vim.log.levels.WARN)
else
tempfile = go_mod_dir .. "/" .. filename
end
vim.fn.writefile(content, tempfile)

-- Open the temp file in the current (godoc-managed) buffer
vim.cmd("silent! e " .. tempfile)
local window = vim.api.nvim_get_current_win()
local buf = vim.api.nvim_get_current_buf()

-- Set buffer options
vim.api.nvim_set_option_value("filetype", "go", { buf = buf })
vim.api.nvim_set_option_value("bufhidden", "wipe", { buf = buf })
vim.diagnostic.enable(false, { bufnr = buf })

-- Wait until LSP has attached, can be queried and returns a client_id
local client_id = nil
local maxretries = 50
while client_id == nil and maxretries >= 0 do
for _, client in ipairs(vim.lsp.get_clients({ name = "gopls", bufnr = buf })) do
client_id = client.id
break
end
vim.wait(50)
maxretries = maxretries - 1
end

-- Position cursor at the right spot
vim.api.nvim_win_set_cursor(window, cursor_pos)

-- Execute goto definition in the new window
vim.api.nvim_win_call(window, function()
picker_gotodef_fun()
end)

-- Delete the temp file on disk
vim.fn.delete(tempfile)
end

local function health()
--- @type GoDocHealthCheck[]
local checks = {}
Expand Down Expand Up @@ -217,6 +305,9 @@ function M.setup(opts)
language = "go",
}
end,
goto_definition = function(choice, picker_gotodef_fun)
return goto_definition(choice, picker_gotodef_fun)
end,
health = health,
}
end
Expand Down
2 changes: 2 additions & 0 deletions lua/godoc/adapters/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ function M.validate_adapter(adapter)
{ name = "get_items", type = "function" },
{ name = "get_content", type = "function" },
{ name = "get_syntax_info", type = "function" },
{ name = "goto_definition", type = "function" },
}

for _, field in ipairs(required_fields) do
Expand All @@ -70,6 +71,7 @@ function M.override_adapter(default_adapter, user_opts)
get_items = user_opts.get_items or default_adapter.get_items,
get_content = user_opts.get_content or default_adapter.get_content,
get_syntax_info = user_opts.get_syntax_info or default_adapter.get_syntax_info,
goto_definition = user_opts.goto_definition or default_adapter.goto_definition,
health = user_opts.health or default_adapter.health,
}
end
Expand Down
46 changes: 34 additions & 12 deletions lua/godoc/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,16 @@ local function configure_adapters(config)
return configured_adapters
end

--- Open window based on split type
--- @param type 'split' | 'vsplit'
local function open_window(type)
if type == "split" or type == "vsplit" then
vim.cmd(type)
else
vim.notify("Invalid window type: " .. type, vim.log.levels.ERROR)
end
end

-- Set up the plugin with user config
--- @param opts? GoDocConfig
function M.setup(opts)
Expand All @@ -104,9 +114,15 @@ function M.setup(opts)
local picker = pickers.get_picker(M.config.picker.type)
if picker then
---@type GoDocPicker
picker.show(adapter, M.config, function(choice)
if choice then
M.show_documentation(adapter, choice)
picker.show(adapter, M.config, function(data)
if data.choice then
if data.type == "show_documentation" then
open_window(M.config.window.type)
M.show_documentation(adapter, data.choice)
elseif data.type == "goto_definition" then
open_window(M.config.window.type)
M.goto_definition(adapter, data.choice, picker.goto_definition)
end
end
end)
else
Expand All @@ -130,15 +146,6 @@ function M.show_documentation(adapter, item)
vim.api.nvim_set_option_value("modifiable", false, { buf = buf })
vim.api.nvim_set_option_value("filetype", adapter.get_syntax_info().filetype, { buf = buf })

-- Open window based on config
if M.config.window.type == "split" then
vim.cmd("split")
elseif M.config.window.type == "vsplit" then
vim.cmd("vsplit")
else
vim.notify("Invalid window type: " .. M.config.window.type, vim.log.levels.ERROR)
end

vim.api.nvim_set_current_buf(buf)

-- Set up keymaps for the documentation window
Expand All @@ -147,4 +154,19 @@ function M.show_documentation(adapter, item)
vim.keymap.set("n", "<Esc>", ":close<CR>", opts)
end

--- Go to definition on chosen item
--- @param adapter GoDocAdapter
--- @param choice string The chosen item (package, symbol)
--- @param picker_gotodef_fun fun()? The picker's goto_definition function
--- @return nil
function M.goto_definition(adapter, choice, picker_gotodef_fun)
if not picker_gotodef_fun then
vim.notify(
"Picker does not implement a function which can be used for showing definitions",
vim.log.levels.WARN
)
end
adapter.goto_definition(choice, picker_gotodef_fun)
end

return M
13 changes: 11 additions & 2 deletions lua/godoc/pickers/fzf_lua.lua
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ local M = {}

--- @param adapter GoDocAdapter
--- @param config GoDocConfig
--- @param callback fun(choice: string|nil)
--- @param callback fun(choice: GoDocCallbackData)
function M.show(adapter, config, callback)
local core = require("fzf-lua.core")
local opts = {
Expand All @@ -12,7 +12,11 @@ function M.show(adapter, config, callback)
debug = false,
actions = {
["default"] = function(selected, _)
callback(table.concat(selected, ""))
callback({ type = "show_documentation", choice = table.concat(selected, "") })
end,
-- TODO: fzf lua doesn't accept 'gd' as a keymap
["ctrl-s"] = function(selected, _)
callback({ type = "goto_definition", choice = table.concat(selected, "") })
end,
},
}
Expand All @@ -24,4 +28,9 @@ function M.show(adapter, config, callback)
core.fzf_exec(adapter.get_items(), opts)
end

--- @return nil
M.goto_definition = function()
require("fzf-lua").lsp_definitions()
end

return M
7 changes: 5 additions & 2 deletions lua/godoc/pickers/mini.lua
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ local M = {}

--- @param adapter GoDocAdapter
--- @param config GoDocConfig
--- @param callback fun(choice: string|nil)
--- @param callback fun(choice: GoDocCallbackData)
function M.show(adapter, config, callback)
local minipick = require("mini.pick")

Expand All @@ -20,7 +20,7 @@ function M.show(adapter, config, callback)
vim.api.nvim_set_option_value("filetype", syntax_info.filetype, { buf = buf_id })
end,
choose = function(item)
callback(item)
callback({ type = "show_documentation", choice = item })
end,
},
}
Expand All @@ -32,4 +32,7 @@ function M.show(adapter, config, callback)
minipick.start(opts)
end

-- NOTE: goto definition is not supported in mini picker
M.goto_definition = nil

return M
9 changes: 7 additions & 2 deletions lua/godoc/pickers/native.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ local M = {}

--- @param adapter GoDocAdapter
--- @param config GoDocConfig
--- @param callback fun(choice: string|nil)
--- @param callback fun(choice: GoDocCallbackData)
function M.show(adapter, config, callback)
-- Create picker configuration
local opts = {
Expand All @@ -16,7 +16,12 @@ function M.show(adapter, config, callback)
opts = vim.tbl_deep_extend("force", opts, config.picker.native)
end

vim.ui.select(adapter.get_items(), opts, callback)
vim.ui.select(adapter.get_items(), opts, function(choice)
callback({ type = "show_documentation", choice = choice })
end)
end

-- NOTE: goto definition is not supported in native picker
M.goto_definition = nil

return M
25 changes: 23 additions & 2 deletions lua/godoc/pickers/snacks.lua
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ local M = {}

--- @param adapter GoDocAdapter
--- @param config GoDocConfig
--- @param callback fun(choice: string|nil)
--- @param callback fun(data: GoDocCallbackData)
function M.show(adapter, config, callback)
local snacks = require("snacks")

Expand Down Expand Up @@ -35,11 +35,27 @@ function M.show(adapter, config, callback)
ctx.preview:reset()
end
end or nil,
win = {
input = {
keys = {
["gd"] = {
"goto_definition",
mode = { "n" },
},
},
},
},
actions = {
confirm = function(picker, item)
if item then
snacks.picker.actions.close(picker)
callback(item.item_name)
callback({ type = "show_documentation", choice = item.item_name })
end
end,
goto_definition = function(picker, item)
if item then
snacks.picker.actions.close(picker)
callback({ type = "goto_definition", choice = item.item_name })
end
end,
},
Expand All @@ -53,4 +69,9 @@ function M.show(adapter, config, callback)
snacks.picker.pick(opts)
end

--- @return nil
function M.goto_definition()
require("snacks").picker.lsp_definitions()
end

return M
Loading