aboutsummaryrefslogtreecommitdiff
path: root/runtime/lua/vim
diff options
context:
space:
mode:
authorEvgeni Chasnovski <evgeni.chasnovski@gmail.com>2026-04-23 19:50:02 +0300
committerEvgeni Chasnovski <evgeni.chasnovski@gmail.com>2026-04-25 20:28:43 +0300
commitd01dc690e1e86911f65fcbe0b733aca613a4dcd0 (patch)
treedb9ba1f4da8082bc581566ba6a400b2921573e0a /runtime/lua/vim
parent4ed2e66d2ec20c2bf0325283b0e785ca3abb15c0 (diff)
downloadrneovim-d01dc690e1e86911f65fcbe0b733aca613a4dcd0.tar.gz
rneovim-d01dc690e1e86911f65fcbe0b733aca613a4dcd0.tar.bz2
rneovim-d01dc690e1e86911f65fcbe0b733aca613a4dcd0.zip
feat(pack): support `textDocument/documentLink` in confirmation buffer
Problem: In `vim.pack.update()` confirmation buffer it might be useful to be able to use `gx` (open link at cursor) when cursor is on something like commit or tag. Solution: Add `textDocument/documentLink` method support for the in-process LSP. This may be used by LSP clients and makes `gx` automatically work. The shortcoming is that this requires tracking how to construct a URL from source and commit/tag. Currently only GitHub hosted repositories are supported.
Diffstat (limited to 'runtime/lua/vim')
-rw-r--r--runtime/lua/vim/pack.lua3
-rw-r--r--runtime/lua/vim/pack/_lsp.lua89
2 files changed, 86 insertions, 6 deletions
diff --git a/runtime/lua/vim/pack.lua b/runtime/lua/vim/pack.lua
index ae965c68df..c6dec53d32 100644
--- a/runtime/lua/vim/pack.lua
+++ b/runtime/lua/vim/pack.lua
@@ -1247,6 +1247,9 @@ end
--- - |]]| and |[[| to navigate through plugin sections.
---
--- Some features are provided via LSP:
+--- - 'textDocument/documentLink' - compute links for plugin paths, sources,
+--- commits, and tags. Use |gx| to open a link to an object at cursor.
+--- Only supports GitHub hosted repositories for commit and tag links.
--- - 'textDocument/documentSymbol' (`gO` via |lsp-defaults| or |vim.lsp.buf.document_symbol()|) -
--- show structure of the buffer.
--- - 'textDocument/hover' (`K` via |lsp-defaults| or |vim.lsp.buf.hover()|) - show more
diff --git a/runtime/lua/vim/pack/_lsp.lua b/runtime/lua/vim/pack/_lsp.lua
index a3342e9a30..869b0cf73f 100644
--- a/runtime/lua/vim/pack/_lsp.lua
+++ b/runtime/lua/vim/pack/_lsp.lua
@@ -1,7 +1,16 @@
local M = {}
+local git_cmd = function(cmd, cwd, on_exit)
+ cmd = vim.list_extend({ 'git', '-c', 'gc.auto=0' }, cmd)
+ local env = vim.fn.environ() --- @type table<string,string>
+ env.GIT_DIR, env.GIT_WORK_TREE = nil, nil
+ local sys_opts = { cwd = cwd, text = true, env = env, clear_env = true }
+ vim.system(cmd, sys_opts, vim.schedule_wrap(on_exit))
+end
+
local capabilities = {
codeActionProvider = true,
+ documentLinkProvider = { resolveProvider = false },
documentSymbolProvider = true,
executeCommandProvider = { commands = { 'delete_plugin', 'update_plugin', 'skip_update_plugin' } },
hoverProvider = true,
@@ -64,6 +73,79 @@ end
--- @alias vim.pack.lsp.Position { line: integer, character: integer }
--- @alias vim.pack.lsp.Range { start: vim.pack.lsp.Position, end: vim.pack.lsp.Position }
+--- @alias vim.pack.lsp.DocumentLink { range: vim.pack.lsp.Range, target: string }
+
+--- Finds a line range to be linked and computes the LSP style link
+--- @param line string Buffer line to find a link in
+--- @param pattern string Pattern matching link location and contents, like `'^Path: +()(.+)()$'`
+--- @param link_type "commit"|"path"|"src"|"tag"
+--- @param lnum number Line number in a buffer
+--- @param src string Plugin source
+--- @return vim.pack.lsp.DocumentLink? # A link structure according to the LSP specification
+local function match_link(line, pattern, link_type, lnum, src)
+ -- Only support GitHub for now. Maybe other forges in the future.
+ local is_github = vim.startswith(src, 'https://github.com/')
+ if (link_type == 'commit' or link_type == 'tag') and not is_github then
+ return nil
+ end
+
+ --- @type number?, string?, number?
+ local from, match, to = line:match(pattern)
+ if not (from and match and to) then
+ return
+ end
+
+ -- Convert to UTF index used in LSP positions
+ from = vim.str_utfindex(line, 'utf-16', from - 1, false)
+ to = vim.str_utfindex(line, 'utf-16', to - 2, false)
+
+ -- Reference:
+ -- - https://github.com/neovim/nvim-lspconfig/commit/e146efa
+ -- - https://github.com/neovim/nvim-lspconfig/commit/e146efacbafed3789ac568abcc5a981c5decaa58
+ -- - https://github.com/neovim/nvim-lspconfig/releases/tag/v2.8.0
+ local target = match
+ if link_type == 'commit' or link_type == 'tag' then
+ local src_noslash = src:gsub('/+$', '')
+ local middle = link_type == 'commit' and 'commit' or 'releases/tag'
+ target = ('%s/%s/%s'):format(src_noslash, middle, match)
+ elseif link_type == 'path' then
+ target = vim.uri_from_fname(match)
+ end
+
+ return {
+ range = {
+ start = { line = lnum - 1, character = from },
+ ['end'] = { line = lnum - 1, character = to },
+ },
+ target = target,
+ }
+end
+
+--- @param params { textDocument: { uri: string } }
+--- @param callback function
+methods['textDocument/documentLink'] = function(params, callback)
+ local bufnr = get_confirm_bufnr(params.textDocument.uri)
+ if bufnr == nil then
+ return callback(nil, {})
+ end
+
+ --- @type vim.pack.lsp.DocumentLink[]
+ local links = {}
+ local cur_src = ''
+ local lines = vim.api.nvim_buf_get_lines(0, 0, -1, false)
+ for i, l in ipairs(lines) do
+ cur_src = l:match('^Source: +(.+)$') or cur_src
+
+ links[#links + 1] = match_link(l, '^Path: +()(.+)()$', 'path', i, cur_src)
+ links[#links + 1] = match_link(l, '^Source: +()(.+)()$', 'src', i, cur_src)
+ links[#links + 1] = match_link(l, '^Revision[^:]*: +()(%S+)()', 'commit', i, cur_src)
+ -- NOTE: Assume that short revision works in the link
+ links[#links + 1] = match_link(l, '^[><] ()(%S+)()', 'commit', i, cur_src)
+ links[#links + 1] = match_link(l, '^• ()(.+)()$', 'tag', i, cur_src)
+ end
+
+ return callback(nil, links)
+end
--- @param params { textDocument: { uri: string } }
--- @param callback function
@@ -211,18 +293,13 @@ methods['textDocument/hover'] = function(params, callback)
return
end
- local cmd = { 'git', 'show', '--no-color', commit or tag }
--- @param sys_out vim.SystemCompleted
local on_exit = function(sys_out)
local markdown = '```diff\n' .. sys_out.stdout .. '\n```'
local res = { contents = { kind = vim.lsp.protocol.MarkupKind.Markdown, value = markdown } }
callback(nil, res)
end
-
- -- temporarily clear GIT env vars
- local env = vim.fn.environ() --- @type table<string,string>
- env.GIT_DIR, env.GIT_WORK_TREE = nil, nil
- vim.system(cmd, { cwd = path, env = env, clear_env = true }, vim.schedule_wrap(on_exit))
+ git_cmd({ 'show', '--no-color', commit or tag }, path, on_exit)
end
local dispatchers = {}