From 0fa229e15269e89619ac7cd64340b9df65994bfe Mon Sep 17 00:00:00 2001 From: Julia Mertz Date: Tue, 25 Feb 2025 22:02:38 +0100 Subject: [PATCH 01/20] feat: goto definition --- lua/godoc/adapters/go.lua | 16 +++++++++++ lua/godoc/init.lua | 51 ++++++++++++++++++++++++++------- lua/godoc/pickers/fzf_lua.lua | 8 ++++-- lua/godoc/pickers/mini.lua | 4 +-- lua/godoc/pickers/native.lua | 6 ++-- lua/godoc/pickers/snacks.lua | 20 +++++++++++-- lua/godoc/pickers/telescope.lua | 14 +++++++-- lua/godoc/types.lua | 12 +++++++- 8 files changed, 109 insertions(+), 22 deletions(-) diff --git a/lua/godoc/adapters/go.lua b/lua/godoc/adapters/go.lua index d598f21..a47844e 100644 --- a/lua/godoc/adapters/go.lua +++ b/lua/godoc/adapters/go.lua @@ -84,6 +84,19 @@ local function get_packages() return all_packages end +--- @param package string +--- @return GoDocDefinition? +local function get_package_definition(package) + -- TODO: get package entry point + local stdout = vim.fn.system("go list -f '{{.Dir}}' " .. package) + if vim.v.shell_error == 0 then + return { + filepath = vim.trim(stdout), + position = nil, + } + end +end + local function health() --- @type GoDocHealthCheck[] local checks = {} @@ -211,6 +224,9 @@ function M.setup(opts) get_content = function(choice) return vim.fn.systemlist("go doc -all " .. choice) end, + get_definition = function(choice) + return get_package_definition(choice) + end, get_syntax_info = function() return { filetype = "godoc", diff --git a/lua/godoc/init.lua b/lua/godoc/init.lua index 2d96dfa..2c8084f 100644 --- a/lua/godoc/init.lua +++ b/lua/godoc/init.lua @@ -104,9 +104,13 @@ 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 + M.show_documentation(adapter, data.choice) + elseif data.type == "goto_definition" then + M.goto_definition(adapter, data.choice) + end end end) else @@ -116,6 +120,16 @@ function M.setup(opts) end 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 + -- Show documentation in new buffer --- @param adapter GoDocAdapter --- @param item string @@ -130,14 +144,7 @@ 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 + open_window(M.config.window.type) vim.api.nvim_set_current_buf(buf) @@ -147,4 +154,26 @@ function M.show_documentation(adapter, item) vim.keymap.set("n", "", ":close", opts) end +--- Open source code in new buffer +--- @param adapter GoDocAdapter +--- @param item string +function M.goto_definition(adapter, item) + if not adapter.get_definition then + return + end + + local definition = adapter.get_definition(item) + if not definition then + vim.notify("No definition found for: " .. item, vim.log.levels.WARN) + return + end + + open_window(M.config.window.type) + + vim.cmd("silent! e " .. vim.fn.fnameescape(definition.filepath)) + if definition.position then + vim.api.nvim_win_set_cursor(0, definition.position) + end +end + return M diff --git a/lua/godoc/pickers/fzf_lua.lua b/lua/godoc/pickers/fzf_lua.lua index 9dc74c8..d3321f4 100644 --- a/lua/godoc/pickers/fzf_lua.lua +++ b/lua/godoc/pickers/fzf_lua.lua @@ -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 = { @@ -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, }, } diff --git a/lua/godoc/pickers/mini.lua b/lua/godoc/pickers/mini.lua index 1423295..1e65513 100644 --- a/lua/godoc/pickers/mini.lua +++ b/lua/godoc/pickers/mini.lua @@ -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") @@ -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, }, } diff --git a/lua/godoc/pickers/native.lua b/lua/godoc/pickers/native.lua index cfb0f9a..f0b9470 100644 --- a/lua/godoc/pickers/native.lua +++ b/lua/godoc/pickers/native.lua @@ -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 = { @@ -16,7 +16,9 @@ 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 return M diff --git a/lua/godoc/pickers/snacks.lua b/lua/godoc/pickers/snacks.lua index 54e0105..7ee7bce 100644 --- a/lua/godoc/pickers/snacks.lua +++ b/lua/godoc/pickers/snacks.lua @@ -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") @@ -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, }, diff --git a/lua/godoc/pickers/telescope.lua b/lua/godoc/pickers/telescope.lua index 6bc38f1..f0ef0cd 100644 --- a/lua/godoc/pickers/telescope.lua +++ b/lua/godoc/pickers/telescope.lua @@ -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 action_state = require("telescope.actions.state") local finders = require("telescope.finders") @@ -28,7 +28,14 @@ function M.show(adapter, config, callback) local function on_package_select(prompt_bufnr) local selection = action_state.get_selected_entry() if selection then - callback(selection.value) + callback({ type = "show_documentation", choice = selection.value }) + end + end + + local function on_goto_definition(prompt_bufnr) + local selection = action_state.get_selected_entry() + if selection then + callback({ type = "goto_definition", choice = selection.value }) end end @@ -65,6 +72,9 @@ function M.show(adapter, config, callback) map("n", "", function(prompt_bufnr) on_package_select(prompt_bufnr) end) + map("n", "gd", function(prompt_bufnr) + on_goto_definition(prompt_bufnr) + end) return true end, } diff --git a/lua/godoc/types.lua b/lua/godoc/types.lua index f5ae66f..1cc4859 100644 --- a/lua/godoc/types.lua +++ b/lua/godoc/types.lua @@ -8,6 +8,7 @@ --- @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 get_definition? fun(choice: string): GoDocDefinition Function that returns the definition location --- @field health? fun(): GoDocHealthCheck[] Optional health check function --- @class GoDocAdapterOpts @@ -15,6 +16,7 @@ --- @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 get_definition? fun(choice: string): GoDocDefinition Override the get_definition function --- @field health? fun(): GoDocHealthCheck[] Override the health check function --- @field [string] any Other adapter-specific options @@ -40,11 +42,19 @@ --- @field filetype string The filetype to set for the documentation buffer --- @field language string The treesitter language to use for syntax highlighting +--- @class GoDocDefinition +--- @field filepath string Path on filesystem to the source code +--- @field position [number, number]? Position of definition in source (row, col) + --- @class GoDocWindowConfig --- @field type "split"|"vsplit" The type of window to open +--- @class GoDocCallbackData +--- @field type "show_documentation" | "goto_definition" +--- @field choice string? + --- @class GoDocPicker ---- @field show fun(adapter: GoDocAdapter, user_config: GoDocConfig, callback: fun(choice: string|nil)) Shows the picker UI with items from adapter +--- @field show fun(adapter: GoDocAdapter, user_config: GoDocConfig, callback: fun(data:GoDocCallbackData)) Shows the picker UI with items from adapter --- @class GoDocPickerConfig --- @field type "native"|"telescope"|"snacks"|"mini"|"fzf_lua" The type of picker to use From 7f59da05beeaade4885f84dcae2ee53de3f21e17 Mon Sep 17 00:00:00 2001 From: Julia Mertz Date: Fri, 28 Feb 2025 13:12:23 +0100 Subject: [PATCH 02/20] fix: add get_definition in validate and override --- lua/godoc/adapters/init.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lua/godoc/adapters/init.lua b/lua/godoc/adapters/init.lua index a79ebb2..813cd05 100644 --- a/lua/godoc/adapters/init.lua +++ b/lua/godoc/adapters/init.lua @@ -43,6 +43,7 @@ function M.validate_adapter(adapter) { name = "command", type = "string" }, { name = "get_items", type = "function" }, { name = "get_content", type = "function" }, + { name = "get_definition", type = "function" }, { name = "get_syntax_info", type = "function" }, } @@ -69,6 +70,7 @@ function M.override_adapter(default_adapter, user_opts) command = user_opts.command or default_adapter.command, get_items = user_opts.get_items or default_adapter.get_items, get_content = user_opts.get_content or default_adapter.get_content, + get_definition = user_opts.get_definition or default_adapter.get_definition, get_syntax_info = user_opts.get_syntax_info or default_adapter.get_syntax_info, health = user_opts.health or default_adapter.health, } From 2e44f0f626c43ba593f3c0ff35dbef92a2c97e72 Mon Sep 17 00:00:00 2001 From: Julia Mertz Date: Tue, 4 Mar 2025 22:36:01 +0100 Subject: [PATCH 03/20] wip: go adapter get_definition with picker --- lua/godoc/adapters/go.lua | 64 +++++++++++++++++++++++++++------ lua/godoc/adapters/init.lua | 2 +- lua/godoc/init.lua | 7 ++-- lua/godoc/pickers/fzf_lua.lua | 2 ++ lua/godoc/pickers/snacks.lua | 2 ++ lua/godoc/pickers/telescope.lua | 2 ++ lua/godoc/types.lua | 5 +-- 7 files changed, 68 insertions(+), 16 deletions(-) diff --git a/lua/godoc/adapters/go.lua b/lua/godoc/adapters/go.lua index a47844e..67cc423 100644 --- a/lua/godoc/adapters/go.lua +++ b/lua/godoc/adapters/go.lua @@ -85,18 +85,62 @@ local function get_packages() end --- @param package string +--- @param picker GoDocPicker --- @return GoDocDefinition? -local function get_package_definition(package) - -- TODO: get package entry point - local stdout = vim.fn.system("go list -f '{{.Dir}}' " .. package) - if vim.v.shell_error == 0 then - return { - filepath = vim.trim(stdout), - position = nil, - } +local function get_package_definition(package, picker) + if not picker.lsp_definitions then + vim.notify("Picker does not implement 'lsp_definitions'", vim.log.levels.WARN) + return end + + local content = { + "package main", + 'import "' .. package .. '"', + } + + -- creating a new buffer and reading the content into it results in a gopls error: 'no package metadata for file file:///' + -- using a tempfile as a workaround fixes this + local tempfile = vim.fn.stdpath("cache") .. "/godoc-tmp.go" + vim.fn.writefile(content, tempfile) + + -- create hidden window to run picker `get_definition` in + local window = vim.api.nvim_open_win(0, false, { + hide = true, + relative = "win", + row = 0, + col = 0, + width = 1, + height = 1, + }) + + vim.fn.win_execute(window, "silent! e " .. tempfile, true) + + -- lsp client won't attach in time so it has to be done manually + local buf = vim.api.nvim_win_get_buf(window) + for _, client in ipairs(vim.lsp.get_clients()) do + if client.name == "gopls" then + vim.lsp.buf_attach_client(buf, client.id) + break + end + end + + -- move cursor to the beginning of import string + vim.api.nvim_win_set_cursor(window, { 2, 8 }) + + vim.api.nvim_win_call(window, function() + picker.lsp_definitions() + end) + + -- picker fails to open if this call isn't deferred + vim.defer_fn(function() + vim.api.nvim_win_close(window, true) + vim.cmd("!rm " .. tempfile) + end, 50) end +-- running with ':so %' works on the second try, getting errors when ran trough GoDoc usercommand +get_package_definition("bytes", { lsp_definitions = require("telescope.builtin").lsp_definitions }) + local function health() --- @type GoDocHealthCheck[] local checks = {} @@ -224,8 +268,8 @@ function M.setup(opts) get_content = function(choice) return vim.fn.systemlist("go doc -all " .. choice) end, - get_definition = function(choice) - return get_package_definition(choice) + get_definition = function(choice, picker) + return get_package_definition(choice, picker) end, get_syntax_info = function() return { diff --git a/lua/godoc/adapters/init.lua b/lua/godoc/adapters/init.lua index 813cd05..741bfa4 100644 --- a/lua/godoc/adapters/init.lua +++ b/lua/godoc/adapters/init.lua @@ -43,7 +43,7 @@ function M.validate_adapter(adapter) { name = "command", type = "string" }, { name = "get_items", type = "function" }, { name = "get_content", type = "function" }, - { name = "get_definition", type = "function" }, + { name = "get_definition", type = "function" }, { name = "get_syntax_info", type = "function" }, } diff --git a/lua/godoc/init.lua b/lua/godoc/init.lua index 2c8084f..708dee7 100644 --- a/lua/godoc/init.lua +++ b/lua/godoc/init.lua @@ -109,7 +109,7 @@ function M.setup(opts) if data.type == "show_documentation" then M.show_documentation(adapter, data.choice) elseif data.type == "goto_definition" then - M.goto_definition(adapter, data.choice) + M.goto_definition(adapter, picker, data.choice) end end end) @@ -156,13 +156,14 @@ end --- Open source code in new buffer --- @param adapter GoDocAdapter +--- @param picker GoDocPicker --- @param item string -function M.goto_definition(adapter, item) +function M.goto_definition(adapter, picker, item) if not adapter.get_definition then return end - local definition = adapter.get_definition(item) + local definition = adapter.get_definition(item, picker) if not definition then vim.notify("No definition found for: " .. item, vim.log.levels.WARN) return diff --git a/lua/godoc/pickers/fzf_lua.lua b/lua/godoc/pickers/fzf_lua.lua index d3321f4..cf5a1a4 100644 --- a/lua/godoc/pickers/fzf_lua.lua +++ b/lua/godoc/pickers/fzf_lua.lua @@ -1,6 +1,8 @@ --- @class Fzflua: GoDocPicker local M = {} +M.lsp_definitions = require("fzf-lua").lsp_definitions + --- @param adapter GoDocAdapter --- @param config GoDocConfig --- @param callback fun(choice: GoDocCallbackData) diff --git a/lua/godoc/pickers/snacks.lua b/lua/godoc/pickers/snacks.lua index 7ee7bce..cb87f30 100644 --- a/lua/godoc/pickers/snacks.lua +++ b/lua/godoc/pickers/snacks.lua @@ -1,6 +1,8 @@ --- @class SnacksPicker: GoDocPicker local M = {} +M.lsp_definitions = require("snacks").picker.lsp_definitions + --- @param adapter GoDocAdapter --- @param config GoDocConfig --- @param callback fun(data: GoDocCallbackData) diff --git a/lua/godoc/pickers/telescope.lua b/lua/godoc/pickers/telescope.lua index f0ef0cd..b5ff886 100644 --- a/lua/godoc/pickers/telescope.lua +++ b/lua/godoc/pickers/telescope.lua @@ -1,6 +1,8 @@ --- @class TelescopePicker: GoDocPicker local M = {} +M.lsp_definitions = require("telescope.builtin").lsp_definitions + --- @param adapter GoDocAdapter --- @param config GoDocConfig --- @param callback fun(data: GoDocCallbackData) diff --git a/lua/godoc/types.lua b/lua/godoc/types.lua index 1cc4859..d496056 100644 --- a/lua/godoc/types.lua +++ b/lua/godoc/types.lua @@ -8,7 +8,7 @@ --- @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 get_definition? fun(choice: string): GoDocDefinition Function that returns the definition location +--- @field get_definition? fun(choice: string, picker: GoDocPicker): GoDocDefinition Function that returns the definition location --- @field health? fun(): GoDocHealthCheck[] Optional health check function --- @class GoDocAdapterOpts @@ -16,7 +16,7 @@ --- @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 get_definition? fun(choice: string): GoDocDefinition Override the get_definition function +--- @field get_definition? fun(choice: string, picker: GoDocPicker): GoDocDefinition Override the get_definition function --- @field health? fun(): GoDocHealthCheck[] Override the health check function --- @field [string] any Other adapter-specific options @@ -55,6 +55,7 @@ --- @class GoDocPicker --- @field show fun(adapter: GoDocAdapter, user_config: GoDocConfig, callback: fun(data:GoDocCallbackData)) Shows the picker UI with items from adapter +--- @field lsp_definitions fun()? Picker specific implementation of lsp_definitions --- @class GoDocPickerConfig --- @field type "native"|"telescope"|"snacks"|"mini"|"fzf_lua" The type of picker to use From 95acd4afb46cfecd07ad10b2a67e69d88a0139ca Mon Sep 17 00:00:00 2001 From: Fredrik Averpil Date: Sat, 8 Mar 2025 13:14:07 +0100 Subject: [PATCH 04/20] refactor: delegate responsibilites to adapter, rename symbols, update interfaces --- lua/godoc/adapters/go.lua | 78 +++++++++++++-------------------- lua/godoc/adapters/init.lua | 4 +- lua/godoc/init.lua | 19 ++------ lua/godoc/pickers/fzf_lua.lua | 5 ++- lua/godoc/pickers/mini.lua | 2 + lua/godoc/pickers/native.lua | 2 + lua/godoc/pickers/snacks.lua | 6 ++- lua/godoc/pickers/telescope.lua | 6 ++- lua/godoc/types.lua | 12 ++--- 9 files changed, 57 insertions(+), 77 deletions(-) diff --git a/lua/godoc/adapters/go.lua b/lua/godoc/adapters/go.lua index 67cc423..4c3d302 100644 --- a/lua/godoc/adapters/go.lua +++ b/lua/godoc/adapters/go.lua @@ -85,61 +85,45 @@ local function get_packages() end --- @param package string ---- @param picker GoDocPicker ---- @return GoDocDefinition? -local function get_package_definition(package, picker) - if not picker.lsp_definitions then +--- @param picker_gotodef_fun fun() | nil +--- @return boolean +local function goto_package_definition(package, picker_gotodef_fun) + if not picker_gotodef_fun then vim.notify("Picker does not implement 'lsp_definitions'", vim.log.levels.WARN) - return + return false end - local content = { - "package main", - 'import "' .. package .. '"', - } + -- Create temp file instead of just in-memory buffer + local temp_dir = vim.fn.tempname() + vim.fn.mkdir(temp_dir, "p") + local temp_file = temp_dir .. "/temp.go" - -- creating a new buffer and reading the content into it results in a gopls error: 'no package metadata for file file:///' - -- using a tempfile as a workaround fixes this - local tempfile = vim.fn.stdpath("cache") .. "/godoc-tmp.go" - vim.fn.writefile(content, tempfile) + -- Write content to the actual file + local file = io.open(temp_file, "w") + file:write(string.format( + [[ +package main - -- create hidden window to run picker `get_definition` in - local window = vim.api.nvim_open_win(0, false, { - hide = true, - relative = "win", - row = 0, - col = 0, - width = 1, - height = 1, - }) +import "%s" - vim.fn.win_execute(window, "silent! e " .. tempfile, true) +]], + package + )) + file:close() - -- lsp client won't attach in time so it has to be done manually - local buf = vim.api.nvim_win_get_buf(window) - for _, client in ipairs(vim.lsp.get_clients()) do - if client.name == "gopls" then - vim.lsp.buf_attach_client(buf, client.id) - break - end - end + -- Open the actual file and set cursor immediately + vim.cmd("edit " .. temp_file) + local buf = vim.api.nvim_get_current_buf() - -- move cursor to the beginning of import string - vim.api.nvim_win_set_cursor(window, { 2, 8 }) + -- Position cursor immediately + vim.api.nvim_win_set_cursor(0, { 3, 8 }) - vim.api.nvim_win_call(window, function() - picker.lsp_definitions() - end) + vim.wait(100) - -- picker fails to open if this call isn't deferred - vim.defer_fn(function() - vim.api.nvim_win_close(window, true) - vim.cmd("!rm " .. tempfile) - end, 50) -end + picker_gotodef_fun() --- running with ':so %' works on the second try, getting errors when ran trough GoDoc usercommand -get_package_definition("bytes", { lsp_definitions = require("telescope.builtin").lsp_definitions }) + return true +end local function health() --- @type GoDocHealthCheck[] @@ -268,15 +252,15 @@ function M.setup(opts) get_content = function(choice) return vim.fn.systemlist("go doc -all " .. choice) end, - get_definition = function(choice, picker) - return get_package_definition(choice, picker) - end, get_syntax_info = function() return { filetype = "godoc", language = "go", } end, + goto_definition = function(choice, lsp_definition_fun) + return goto_package_definition(choice, lsp_definition_fun) + end, health = health, } end diff --git a/lua/godoc/adapters/init.lua b/lua/godoc/adapters/init.lua index 741bfa4..90be618 100644 --- a/lua/godoc/adapters/init.lua +++ b/lua/godoc/adapters/init.lua @@ -43,8 +43,8 @@ function M.validate_adapter(adapter) { name = "command", type = "string" }, { name = "get_items", type = "function" }, { name = "get_content", type = "function" }, - { name = "get_definition", type = "function" }, { name = "get_syntax_info", type = "function" }, + { name = "goto_definition", type = "function" }, } for _, field in ipairs(required_fields) do @@ -70,8 +70,8 @@ function M.override_adapter(default_adapter, user_opts) command = user_opts.command or default_adapter.command, get_items = user_opts.get_items or default_adapter.get_items, get_content = user_opts.get_content or default_adapter.get_content, - get_definition = user_opts.get_definition or default_adapter.get_definition, 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 diff --git a/lua/godoc/init.lua b/lua/godoc/init.lua index 708dee7..db56d7f 100644 --- a/lua/godoc/init.lua +++ b/lua/godoc/init.lua @@ -109,7 +109,7 @@ function M.setup(opts) if data.type == "show_documentation" then M.show_documentation(adapter, data.choice) elseif data.type == "goto_definition" then - M.goto_definition(adapter, picker, data.choice) + M.goto_definition(adapter, data.choice, picker.goto_definition) end end end) @@ -156,25 +156,14 @@ end --- Open source code in new buffer --- @param adapter GoDocAdapter ---- @param picker GoDocPicker --- @param item string -function M.goto_definition(adapter, picker, item) - if not adapter.get_definition then - return - end - - local definition = adapter.get_definition(item, picker) +--- @param picker_gotodef_fun fun()? +function M.goto_definition(adapter, item, picker_gotodef_fun) + local definition = adapter.goto_definition(item, picker_gotodef_fun) if not definition then vim.notify("No definition found for: " .. item, vim.log.levels.WARN) return end - - open_window(M.config.window.type) - - vim.cmd("silent! e " .. vim.fn.fnameescape(definition.filepath)) - if definition.position then - vim.api.nvim_win_set_cursor(0, definition.position) - end end return M diff --git a/lua/godoc/pickers/fzf_lua.lua b/lua/godoc/pickers/fzf_lua.lua index cf5a1a4..4c7fba8 100644 --- a/lua/godoc/pickers/fzf_lua.lua +++ b/lua/godoc/pickers/fzf_lua.lua @@ -1,13 +1,14 @@ --- @class Fzflua: GoDocPicker local M = {} -M.lsp_definitions = require("fzf-lua").lsp_definitions +M.lsp_definitions = nil --- @param adapter GoDocAdapter --- @param config GoDocConfig --- @param callback fun(choice: GoDocCallbackData) function M.show(adapter, config, callback) local core = require("fzf-lua.core") + M.lsp_definitions = require("fzf-lua").lsp_definitions local opts = { prompt = "Select item", fn_transform = function() end, @@ -30,4 +31,6 @@ function M.show(adapter, config, callback) core.fzf_exec(adapter.get_items(), opts) end +M.goto_definition = nil + return M diff --git a/lua/godoc/pickers/mini.lua b/lua/godoc/pickers/mini.lua index 1e65513..8357d67 100644 --- a/lua/godoc/pickers/mini.lua +++ b/lua/godoc/pickers/mini.lua @@ -32,4 +32,6 @@ function M.show(adapter, config, callback) minipick.start(opts) end +M.goto_definition = nil + return M diff --git a/lua/godoc/pickers/native.lua b/lua/godoc/pickers/native.lua index f0b9470..35b8080 100644 --- a/lua/godoc/pickers/native.lua +++ b/lua/godoc/pickers/native.lua @@ -21,4 +21,6 @@ function M.show(adapter, config, callback) end) end +M.goto_definition = nil + return M diff --git a/lua/godoc/pickers/snacks.lua b/lua/godoc/pickers/snacks.lua index cb87f30..244df3d 100644 --- a/lua/godoc/pickers/snacks.lua +++ b/lua/godoc/pickers/snacks.lua @@ -1,8 +1,6 @@ --- @class SnacksPicker: GoDocPicker local M = {} -M.lsp_definitions = require("snacks").picker.lsp_definitions - --- @param adapter GoDocAdapter --- @param config GoDocConfig --- @param callback fun(data: GoDocCallbackData) @@ -71,4 +69,8 @@ function M.show(adapter, config, callback) snacks.picker.pick(opts) end +function M.goto_definition() + require("snacks").picker.lsp_definitions() +end + return M diff --git a/lua/godoc/pickers/telescope.lua b/lua/godoc/pickers/telescope.lua index b5ff886..7ad8908 100644 --- a/lua/godoc/pickers/telescope.lua +++ b/lua/godoc/pickers/telescope.lua @@ -1,8 +1,6 @@ --- @class TelescopePicker: GoDocPicker local M = {} -M.lsp_definitions = require("telescope.builtin").lsp_definitions - --- @param adapter GoDocAdapter --- @param config GoDocConfig --- @param callback fun(data: GoDocCallbackData) @@ -89,4 +87,8 @@ function M.show(adapter, config, callback) pickers.new(opts, {}):find() end +function M.goto_definition() + return require("telescope.builtin").lsp_definitions +end + return M diff --git a/lua/godoc/types.lua b/lua/godoc/types.lua index d496056..5346073 100644 --- a/lua/godoc/types.lua +++ b/lua/godoc/types.lua @@ -8,7 +8,7 @@ --- @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 get_definition? fun(choice: string, picker: GoDocPicker): GoDocDefinition Function that returns the definition location +--- @field goto_definition? fun(choice: string, picker_gotodef_fun: fun()?): boolean Function that returns the definition location --- @field health? fun(): GoDocHealthCheck[] Optional health check function --- @class GoDocAdapterOpts @@ -16,7 +16,7 @@ --- @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 get_definition? fun(choice: string, picker: GoDocPicker): GoDocDefinition Override the get_definition function +--- @field goto_definition? fun(choice: string, picker_gotodef_fun: fun()?): boolean Override the get_definition function --- @field health? fun(): GoDocHealthCheck[] Override the health check function --- @field [string] any Other adapter-specific options @@ -42,10 +42,6 @@ --- @field filetype string The filetype to set for the documentation buffer --- @field language string The treesitter language to use for syntax highlighting ---- @class GoDocDefinition ---- @field filepath string Path on filesystem to the source code ---- @field position [number, number]? Position of definition in source (row, col) - --- @class GoDocWindowConfig --- @field type "split"|"vsplit" The type of window to open @@ -55,8 +51,8 @@ --- @class GoDocPicker --- @field show fun(adapter: GoDocAdapter, user_config: GoDocConfig, callback: fun(data:GoDocCallbackData)) Shows the picker UI with items from adapter ---- @field lsp_definitions fun()? Picker specific implementation of lsp_definitions - +--- @field goto_definition fun()? Picker-specific implementation of "go to definition" +--- --- @class GoDocPickerConfig --- @field type "native"|"telescope"|"snacks"|"mini"|"fzf_lua" The type of picker to use --- @field native? table Options for native picker From c6c42492f508b4258e72cbe4b4ecbff073e3d618 Mon Sep 17 00:00:00 2001 From: Fredrik Averpil Date: Sat, 8 Mar 2025 13:26:18 +0100 Subject: [PATCH 05/20] fix: naming/redundant --- lua/godoc/adapters/go.lua | 9 ++++++--- lua/godoc/pickers/fzf_lua.lua | 3 --- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lua/godoc/adapters/go.lua b/lua/godoc/adapters/go.lua index 4c3d302..dc6c4ac 100644 --- a/lua/godoc/adapters/go.lua +++ b/lua/godoc/adapters/go.lua @@ -89,7 +89,10 @@ end --- @return boolean local function goto_package_definition(package, picker_gotodef_fun) if not picker_gotodef_fun then - vim.notify("Picker does not implement 'lsp_definitions'", vim.log.levels.WARN) + vim.notify( + "Picker does not implement a function which can be used for showing definitions", + vim.log.levels.WARN + ) return false end @@ -258,8 +261,8 @@ function M.setup(opts) language = "go", } end, - goto_definition = function(choice, lsp_definition_fun) - return goto_package_definition(choice, lsp_definition_fun) + goto_definition = function(choice, picker_gotodef_fun) + return goto_package_definition(choice, picker_gotodef_fun) end, health = health, } diff --git a/lua/godoc/pickers/fzf_lua.lua b/lua/godoc/pickers/fzf_lua.lua index 4c7fba8..17242c5 100644 --- a/lua/godoc/pickers/fzf_lua.lua +++ b/lua/godoc/pickers/fzf_lua.lua @@ -1,14 +1,11 @@ --- @class Fzflua: GoDocPicker local M = {} -M.lsp_definitions = nil - --- @param adapter GoDocAdapter --- @param config GoDocConfig --- @param callback fun(choice: GoDocCallbackData) function M.show(adapter, config, callback) local core = require("fzf-lua.core") - M.lsp_definitions = require("fzf-lua").lsp_definitions local opts = { prompt = "Select item", fn_transform = function() end, From 71caafdc48c32658a3427be68dbd814fc2818ca2 Mon Sep 17 00:00:00 2001 From: Fredrik Averpil Date: Sat, 8 Mar 2025 13:33:52 +0100 Subject: [PATCH 06/20] fix: remove unused return type --- lua/godoc/adapters/go.lua | 5 +---- lua/godoc/init.lua | 8 ++------ lua/godoc/types.lua | 4 ++-- 3 files changed, 5 insertions(+), 12 deletions(-) diff --git a/lua/godoc/adapters/go.lua b/lua/godoc/adapters/go.lua index dc6c4ac..73a6f08 100644 --- a/lua/godoc/adapters/go.lua +++ b/lua/godoc/adapters/go.lua @@ -86,14 +86,13 @@ end --- @param package string --- @param picker_gotodef_fun fun() | nil ---- @return boolean local function goto_package_definition(package, 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 ) - return false + return end -- Create temp file instead of just in-memory buffer @@ -124,8 +123,6 @@ import "%s" vim.wait(100) picker_gotodef_fun() - - return true end local function health() diff --git a/lua/godoc/init.lua b/lua/godoc/init.lua index db56d7f..5cc5ec1 100644 --- a/lua/godoc/init.lua +++ b/lua/godoc/init.lua @@ -154,16 +154,12 @@ function M.show_documentation(adapter, item) vim.keymap.set("n", "", ":close", opts) end ---- Open source code in new buffer +--- Go to definition on chosen item --- @param adapter GoDocAdapter --- @param item string --- @param picker_gotodef_fun fun()? function M.goto_definition(adapter, item, picker_gotodef_fun) - local definition = adapter.goto_definition(item, picker_gotodef_fun) - if not definition then - vim.notify("No definition found for: " .. item, vim.log.levels.WARN) - return - end + adapter.goto_definition(item, picker_gotodef_fun) end return M diff --git a/lua/godoc/types.lua b/lua/godoc/types.lua index 5346073..f140b2a 100644 --- a/lua/godoc/types.lua +++ b/lua/godoc/types.lua @@ -8,7 +8,7 @@ --- @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()?): boolean Function that returns the definition location +--- @field goto_definition? fun(choice: string, picker_gotodef_fun: fun()?) Function that returns the definition location --- @field health? fun(): GoDocHealthCheck[] Optional health check function --- @class GoDocAdapterOpts @@ -16,7 +16,7 @@ --- @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()?): boolean Override the get_definition function +--- @field goto_definition? fun(choice: string, picker_gotodef_fun: fun()?) Override the get_definition function --- @field health? fun(): GoDocHealthCheck[] Override the health check function --- @field [string] any Other adapter-specific options From a70684242bd469b0a582632d957fe5a1eef123ed Mon Sep 17 00:00:00 2001 From: Fredrik Averpil Date: Sat, 8 Mar 2025 14:55:05 +0100 Subject: [PATCH 07/20] fix: add nilcheck --- lua/godoc/adapters/go.lua | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lua/godoc/adapters/go.lua b/lua/godoc/adapters/go.lua index 73a6f08..3a30304 100644 --- a/lua/godoc/adapters/go.lua +++ b/lua/godoc/adapters/go.lua @@ -102,6 +102,10 @@ local function goto_package_definition(package, picker_gotodef_fun) -- Write content to the actual file local file = io.open(temp_file, "w") + if not file then + vim.notify("Failed to create temporary file", vim.log.levels.ERROR) + return +end file:write(string.format( [[ package main From deb5710617aa78ab02b4470d7d3cf31d437f239f Mon Sep 17 00:00:00 2001 From: Fredrik Averpil Date: Sat, 8 Mar 2025 15:35:44 +0100 Subject: [PATCH 08/20] fix: type --- lua/godoc/adapters/go.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/godoc/adapters/go.lua b/lua/godoc/adapters/go.lua index 3a30304..701cbbf 100644 --- a/lua/godoc/adapters/go.lua +++ b/lua/godoc/adapters/go.lua @@ -85,7 +85,7 @@ local function get_packages() end --- @param package string ---- @param picker_gotodef_fun fun() | nil +--- @param picker_gotodef_fun fun()? local function goto_package_definition(package, picker_gotodef_fun) if not picker_gotodef_fun then vim.notify( From 967a893757f5f7f5534a1081a2050cbc2412d33b Mon Sep 17 00:00:00 2001 From: Fredrik Averpil Date: Sat, 8 Mar 2025 17:48:24 +0100 Subject: [PATCH 09/20] fix: add stability --- lua/godoc/adapters/go.lua | 70 ++++++++++++++++++++++++--------------- 1 file changed, 44 insertions(+), 26 deletions(-) diff --git a/lua/godoc/adapters/go.lua b/lua/godoc/adapters/go.lua index 701cbbf..f5a6127 100644 --- a/lua/godoc/adapters/go.lua +++ b/lua/godoc/adapters/go.lua @@ -95,38 +95,56 @@ local function goto_package_definition(package, picker_gotodef_fun) return end - -- Create temp file instead of just in-memory buffer - local temp_dir = vim.fn.tempname() - vim.fn.mkdir(temp_dir, "p") - local temp_file = temp_dir .. "/temp.go" - - -- Write content to the actual file - local file = io.open(temp_file, "w") - if not file then - vim.notify("Failed to create temporary file", vim.log.levels.ERROR) - return -end - file:write(string.format( - [[ -package main + -- write temp file + local content = { + "package main", + "", + 'import "' .. package .. '"', + } + local tempdir = vim.fn.tempname() + vim.fn.mkdir(tempdir, "p") + local tempfile = tempdir .. "/temp.go" + vim.fn.writefile(content, tempfile) -import "%s" + -- create hidden window to run picker in + local window = vim.api.nvim_open_win(0, false, { + hide = true, + relative = "win", + row = 0, + col = 0, + width = 1, + height = 1, + }) -]], - package - )) - file:close() + -- open temp file in window and automatically create a buffer for it + vim.fn.win_execute(window, "silent! e " .. tempfile, true) - -- Open the actual file and set cursor immediately - vim.cmd("edit " .. temp_file) - local buf = vim.api.nvim_get_current_buf() + -- get ahold of the created buffer + local buf = vim.api.nvim_win_get_buf(window) - -- Position cursor immediately - vim.api.nvim_win_set_cursor(0, { 3, 8 }) + -- 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, window = window })) do + client_id = client.id + break + end + vim.wait(100) + maxretries = maxretries - 1 + end - vim.wait(100) + -- execute in window + vim.api.nvim_win_call(window, function() + vim.api.nvim_win_set_cursor(0, { 3, 8 }) -- position cursor over package name + picker_gotodef_fun() -- run picker's goto definition function + end) - picker_gotodef_fun() + -- close hidden window and delete temp file, but wait so that picker has time to finish + vim.defer_fn(function() + vim.api.nvim_win_close(window, true) + vim.fn.delete(tempfile) + end, 500) end local function health() From 7f509ef4dc1ae9341cbf00fc96e3fd40707c2d3b Mon Sep 17 00:00:00 2001 From: Fredrik Averpil Date: Sat, 8 Mar 2025 19:47:14 +0100 Subject: [PATCH 10/20] fix: handle import paths as well as symbols --- lua/godoc/adapters/go.lua | 67 ++++++++++++++++++++++++++++++++------- 1 file changed, 56 insertions(+), 11 deletions(-) diff --git a/lua/godoc/adapters/go.lua b/lua/godoc/adapters/go.lua index f5a6127..f89bf7c 100644 --- a/lua/godoc/adapters/go.lua +++ b/lua/godoc/adapters/go.lua @@ -96,14 +96,55 @@ local function goto_package_definition(package, picker_gotodef_fun) end -- write temp file - local content = { - "package main", - "", - 'import "' .. package .. '"', - } - local tempdir = vim.fn.tempname() - vim.fn.mkdir(tempdir, "p") - local tempfile = tempdir .. "/temp.go" + local content = {} + local cursor_pos = {} + local is_symbol = package:find("%.") ~= 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 = package: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 = package: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" + local line = 'import "' .. package .. '"' + content = { + "package main", + "", + line, + } + cursor_pos = { 3, line:find(package) + 1 } + end + vim.notify(vim.inspect(cursor_pos)) + local now = os.time() + local filename = "godoc_" .. now .. ".go" + local tempfile = vim.fn.getcwd() .. "/" .. filename + local go_mod_filepath = vim.fn.findfile("go.mod", ".;") + 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) -- create hidden window to run picker in @@ -122,6 +163,9 @@ local function goto_package_definition(package, picker_gotodef_fun) -- get ahold of the created buffer local buf = vim.api.nvim_win_get_buf(window) + -- disable diagnostics for the buffer + 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 @@ -136,15 +180,16 @@ local function goto_package_definition(package, picker_gotodef_fun) -- execute in window vim.api.nvim_win_call(window, function() - vim.api.nvim_win_set_cursor(0, { 3, 8 }) -- position cursor over package name + vim.api.nvim_win_set_cursor(0, cursor_pos) -- position cursor over package name picker_gotodef_fun() -- run picker's goto definition function end) - -- close hidden window and delete temp file, but wait so that picker has time to finish + -- close hidden window and delete temp file, but wait so that picker has time to finish + -- TODO: would be nice to do this more reliably, and not based on a timeout vim.defer_fn(function() vim.api.nvim_win_close(window, true) vim.fn.delete(tempfile) - end, 500) + end, 2000) end local function health() From 66aa5bd340e2eeba5da2f429369b7353315ad6ea Mon Sep 17 00:00:00 2001 From: Fredrik Averpil Date: Sat, 8 Mar 2025 19:48:09 +0100 Subject: [PATCH 11/20] fix: remove debug print --- lua/godoc/adapters/go.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/lua/godoc/adapters/go.lua b/lua/godoc/adapters/go.lua index f89bf7c..fa50fa0 100644 --- a/lua/godoc/adapters/go.lua +++ b/lua/godoc/adapters/go.lua @@ -134,7 +134,6 @@ local function goto_package_definition(package, picker_gotodef_fun) } cursor_pos = { 3, line:find(package) + 1 } end - vim.notify(vim.inspect(cursor_pos)) local now = os.time() local filename = "godoc_" .. now .. ".go" local tempfile = vim.fn.getcwd() .. "/" .. filename From 200e1554ec60a809cd5dc48128ab5c28f791fb6b Mon Sep 17 00:00:00 2001 From: Fredrik Averpil Date: Sat, 8 Mar 2025 20:00:02 +0100 Subject: [PATCH 12/20] fix: properly distinguish between symbols and package URLs --- lua/godoc/adapters/go.lua | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/lua/godoc/adapters/go.lua b/lua/godoc/adapters/go.lua index fa50fa0..938aa76 100644 --- a/lua/godoc/adapters/go.lua +++ b/lua/godoc/adapters/go.lua @@ -84,9 +84,9 @@ local function get_packages() return all_packages end ---- @param package string +--- @param item string --- @param picker_gotodef_fun fun()? -local function goto_package_definition(package, picker_gotodef_fun) +local function goto_definition(item, picker_gotodef_fun) if not picker_gotodef_fun then vim.notify( "Picker does not implement a function which can be used for showing definitions", @@ -98,16 +98,17 @@ local function goto_package_definition(package, picker_gotodef_fun) -- write temp file local content = {} local cursor_pos = {} - local is_symbol = package:find("%.") ~= nil + -- check if the package 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 = package:match("^(.-)%.") + 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 = package:match("([^%.]+)$") + 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 = { @@ -126,13 +127,13 @@ local function goto_package_definition(package, picker_gotodef_fun) cursor_pos = { 9, line:find(symbol) + 1 } else -- a package name is passed, e.g. "archive/tar" - local line = 'import "' .. package .. '"' + local line = 'import "' .. item .. '"' content = { "package main", "", line, } - cursor_pos = { 3, line:find(package) + 1 } + cursor_pos = { 3, line:find(item) + 1 } end local now = os.time() local filename = "godoc_" .. now .. ".go" @@ -325,7 +326,7 @@ function M.setup(opts) } end, goto_definition = function(choice, picker_gotodef_fun) - return goto_package_definition(choice, picker_gotodef_fun) + return goto_definition(choice, picker_gotodef_fun) end, health = health, } From a85793141abd955b9e73feb7c7a23becc3472c4d Mon Sep 17 00:00:00 2001 From: Fredrik Averpil Date: Sat, 8 Mar 2025 20:27:47 +0100 Subject: [PATCH 13/20] fix: search upwards from cwd --- lua/godoc/adapters/go.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/godoc/adapters/go.lua b/lua/godoc/adapters/go.lua index 938aa76..6850908 100644 --- a/lua/godoc/adapters/go.lua +++ b/lua/godoc/adapters/go.lua @@ -138,7 +138,7 @@ local function goto_definition(item, picker_gotodef_fun) local now = os.time() local filename = "godoc_" .. now .. ".go" local tempfile = vim.fn.getcwd() .. "/" .. filename - local go_mod_filepath = vim.fn.findfile("go.mod", ".;") + 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) From 0ac3aea4b082c36fec8b6972a644638dbfb08918 Mon Sep 17 00:00:00 2001 From: Fredrik Averpil Date: Sat, 8 Mar 2025 20:27:59 +0100 Subject: [PATCH 14/20] fix: delete buffer when not needed anymore --- lua/godoc/adapters/go.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/lua/godoc/adapters/go.lua b/lua/godoc/adapters/go.lua index 6850908..221c250 100644 --- a/lua/godoc/adapters/go.lua +++ b/lua/godoc/adapters/go.lua @@ -188,6 +188,7 @@ local function goto_definition(item, picker_gotodef_fun) -- TODO: would be nice to do this more reliably, and not based on a timeout vim.defer_fn(function() vim.api.nvim_win_close(window, true) + vim.api.nvim_buf_delete(buf, { force = true }) vim.fn.delete(tempfile) end, 2000) end From 68d5274435beaf8c1f85447cf4c19a11407ef5a7 Mon Sep 17 00:00:00 2001 From: Fredrik Averpil Date: Sun, 9 Mar 2025 09:32:09 +0100 Subject: [PATCH 15/20] fix: touchups --- lua/godoc/adapters/go.lua | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lua/godoc/adapters/go.lua b/lua/godoc/adapters/go.lua index 221c250..b268a28 100644 --- a/lua/godoc/adapters/go.lua +++ b/lua/godoc/adapters/go.lua @@ -98,7 +98,7 @@ local function goto_definition(item, picker_gotodef_fun) -- write temp file local content = {} local cursor_pos = {} - -- check if the package contains a symbol + -- 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 @@ -127,13 +127,12 @@ local function goto_definition(item, picker_gotodef_fun) cursor_pos = { 9, line:find(symbol) + 1 } else -- a package name is passed, e.g. "archive/tar" - local line = 'import "' .. item .. '"' content = { "package main", "", - line, + line = 'import "' .. item .. '"', } - cursor_pos = { 3, line:find(item) + 1 } + cursor_pos = { 3, 9 } end local now = os.time() local filename = "godoc_" .. now .. ".go" From 185d5d73586d57f3fd905dfd8e607cf95e5507c6 Mon Sep 17 00:00:00 2001 From: Fredrik Averpil Date: Sun, 9 Mar 2025 09:32:26 +0100 Subject: [PATCH 16/20] feat(fzf-lua): add call to lsp_definitons() --- lua/godoc/pickers/fzf_lua.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lua/godoc/pickers/fzf_lua.lua b/lua/godoc/pickers/fzf_lua.lua index 17242c5..cd1fd4d 100644 --- a/lua/godoc/pickers/fzf_lua.lua +++ b/lua/godoc/pickers/fzf_lua.lua @@ -28,6 +28,8 @@ function M.show(adapter, config, callback) core.fzf_exec(adapter.get_items(), opts) end -M.goto_definition = nil +M.goto_definition = function() + return require("fzf-lua").lsp_definitions() +end return M From 4a73682f993bae008c702a96801a3c48bdd3c8bb Mon Sep 17 00:00:00 2001 From: Fredrik Averpil Date: Sun, 9 Mar 2025 09:58:48 +0100 Subject: [PATCH 17/20] fix(bug): typo --- lua/godoc/adapters/go.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/godoc/adapters/go.lua b/lua/godoc/adapters/go.lua index b268a28..0511623 100644 --- a/lua/godoc/adapters/go.lua +++ b/lua/godoc/adapters/go.lua @@ -130,7 +130,7 @@ local function goto_definition(item, picker_gotodef_fun) content = { "package main", "", - line = 'import "' .. item .. '"', + 'import "' .. item .. '"', } cursor_pos = { 3, 9 } end From 540516fb5591481ae140aaea694128fda103689e Mon Sep 17 00:00:00 2001 From: Fredrik Averpil Date: Sun, 9 Mar 2025 09:59:10 +0100 Subject: [PATCH 18/20] fix: goto_definition returns nil --- lua/godoc/init.lua | 1 + lua/godoc/pickers/fzf_lua.lua | 3 ++- lua/godoc/pickers/mini.lua | 1 + lua/godoc/pickers/native.lua | 1 + lua/godoc/pickers/snacks.lua | 1 + lua/godoc/pickers/telescope.lua | 3 ++- lua/godoc/types.lua | 4 ++-- 7 files changed, 10 insertions(+), 4 deletions(-) diff --git a/lua/godoc/init.lua b/lua/godoc/init.lua index 5cc5ec1..a9ade2f 100644 --- a/lua/godoc/init.lua +++ b/lua/godoc/init.lua @@ -158,6 +158,7 @@ end --- @param adapter GoDocAdapter --- @param item string --- @param picker_gotodef_fun fun()? +--- @return nil function M.goto_definition(adapter, item, picker_gotodef_fun) adapter.goto_definition(item, picker_gotodef_fun) end diff --git a/lua/godoc/pickers/fzf_lua.lua b/lua/godoc/pickers/fzf_lua.lua index cd1fd4d..4ffa447 100644 --- a/lua/godoc/pickers/fzf_lua.lua +++ b/lua/godoc/pickers/fzf_lua.lua @@ -28,8 +28,9 @@ function M.show(adapter, config, callback) core.fzf_exec(adapter.get_items(), opts) end +--- @return nil M.goto_definition = function() - return require("fzf-lua").lsp_definitions() + require("fzf-lua").lsp_definitions() end return M diff --git a/lua/godoc/pickers/mini.lua b/lua/godoc/pickers/mini.lua index 8357d67..0c858c5 100644 --- a/lua/godoc/pickers/mini.lua +++ b/lua/godoc/pickers/mini.lua @@ -32,6 +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 diff --git a/lua/godoc/pickers/native.lua b/lua/godoc/pickers/native.lua index 35b8080..b3d4613 100644 --- a/lua/godoc/pickers/native.lua +++ b/lua/godoc/pickers/native.lua @@ -21,6 +21,7 @@ function M.show(adapter, config, callback) end) end +-- NOTE: goto definition is not supported in native picker M.goto_definition = nil return M diff --git a/lua/godoc/pickers/snacks.lua b/lua/godoc/pickers/snacks.lua index 244df3d..9580312 100644 --- a/lua/godoc/pickers/snacks.lua +++ b/lua/godoc/pickers/snacks.lua @@ -69,6 +69,7 @@ function M.show(adapter, config, callback) snacks.picker.pick(opts) end +--- @return nil function M.goto_definition() require("snacks").picker.lsp_definitions() end diff --git a/lua/godoc/pickers/telescope.lua b/lua/godoc/pickers/telescope.lua index 7ad8908..937badf 100644 --- a/lua/godoc/pickers/telescope.lua +++ b/lua/godoc/pickers/telescope.lua @@ -87,8 +87,9 @@ function M.show(adapter, config, callback) pickers.new(opts, {}):find() end +--- @return nil function M.goto_definition() - return require("telescope.builtin").lsp_definitions + require("telescope.builtin").lsp_definitions() end return M diff --git a/lua/godoc/types.lua b/lua/godoc/types.lua index f140b2a..64cfcf5 100644 --- a/lua/godoc/types.lua +++ b/lua/godoc/types.lua @@ -8,7 +8,7 @@ --- @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()?) Function that returns the definition location +--- @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 --- @class GoDocAdapterOpts @@ -16,7 +16,7 @@ --- @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()?) Override the get_definition 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 From 72669e3364c5ee7b0975bb270481e047efcf9d11 Mon Sep 17 00:00:00 2001 From: Fredrik Averpil Date: Sun, 9 Mar 2025 17:39:25 +0100 Subject: [PATCH 19/20] fix: use split|vsplit option for gotodef --- lua/godoc/adapters/go.lua | 56 +++++++++++++-------------------------- lua/godoc/init.lua | 38 +++++++++++++++----------- 2 files changed, 40 insertions(+), 54 deletions(-) diff --git a/lua/godoc/adapters/go.lua b/lua/godoc/adapters/go.lua index 0511623..1cda6fb 100644 --- a/lua/godoc/adapters/go.lua +++ b/lua/godoc/adapters/go.lua @@ -85,16 +85,8 @@ local function get_packages() end --- @param item string ---- @param picker_gotodef_fun fun()? +--- @param picker_gotodef_fun fun() local function goto_definition(item, 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 - ) - return - end - -- write temp file local content = {} local cursor_pos = {} @@ -146,50 +138,38 @@ local function goto_definition(item, picker_gotodef_fun) end vim.fn.writefile(content, tempfile) - -- create hidden window to run picker in - local window = vim.api.nvim_open_win(0, false, { - hide = true, - relative = "win", - row = 0, - col = 0, - width = 1, - height = 1, - }) - - -- open temp file in window and automatically create a buffer for it - vim.fn.win_execute(window, "silent! e " .. tempfile, true) + -- 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() - -- get ahold of the created buffer - local buf = vim.api.nvim_win_get_buf(window) - - -- disable diagnostics for the buffer + -- 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 + -- 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, window = window })) do + for _, client in ipairs(vim.lsp.get_clients({ name = "gopls", bufnr = buf })) do client_id = client.id break end - vim.wait(100) + vim.wait(50) maxretries = maxretries - 1 end - -- execute in window + -- 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() - vim.api.nvim_win_set_cursor(0, cursor_pos) -- position cursor over package name - picker_gotodef_fun() -- run picker's goto definition function + picker_gotodef_fun() end) - -- close hidden window and delete temp file, but wait so that picker has time to finish - -- TODO: would be nice to do this more reliably, and not based on a timeout - vim.defer_fn(function() - vim.api.nvim_win_close(window, true) - vim.api.nvim_buf_delete(buf, { force = true }) - vim.fn.delete(tempfile) - end, 2000) + -- Delete the temp file on disk + vim.fn.delete(tempfile) end local function health() diff --git a/lua/godoc/init.lua b/lua/godoc/init.lua index a9ade2f..96e7ef8 100644 --- a/lua/godoc/init.lua +++ b/lua/godoc/init.lua @@ -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) @@ -107,8 +117,10 @@ function M.setup(opts) 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 @@ -120,16 +132,6 @@ function M.setup(opts) end 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 - -- Show documentation in new buffer --- @param adapter GoDocAdapter --- @param item string @@ -144,8 +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(M.config.window.type) - vim.api.nvim_set_current_buf(buf) -- Set up keymaps for the documentation window @@ -156,11 +156,17 @@ end --- Go to definition on chosen item --- @param adapter GoDocAdapter ---- @param item string ---- @param picker_gotodef_fun fun()? +--- @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, item, picker_gotodef_fun) - adapter.goto_definition(item, picker_gotodef_fun) +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 From 572e685d927d99ab19c00e47d8125b82057142be Mon Sep 17 00:00:00 2001 From: Fredrik Averpil Date: Sat, 15 Mar 2025 08:56:10 +0100 Subject: [PATCH 20/20] docs: mention gotodef --- README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 38e9d7d..f43c1fc 100644 --- a/README.md +++ b/README.md @@ -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. @@ -89,6 +90,8 @@ provided: - `:GoDoc` - Open picker and search packages. - `:GoDoc ` - 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] > @@ -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 ``` @@ -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 ```