mirror of
https://github.com/neovim/neovim.git
synced 2026-05-23 21:30:11 +00:00
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.
This commit is contained in:
@@ -510,6 +510,9 @@ update({names}, {opts}) *vim.pack.update()*
|
||||
• |]]| 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()|) -
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 = {}
|
||||
|
||||
@@ -1436,7 +1436,74 @@ describe('vim.pack', function()
|
||||
|
||||
eq(1, exec_lua('return #vim.lsp.get_clients({ bufnr = 0 })'))
|
||||
|
||||
-- textDocument/documentLink
|
||||
--- @param ref ([number, number, number, number, string])[]
|
||||
local assert_links = function(ref)
|
||||
--- @type table[]
|
||||
local out_links = exec_lua(function()
|
||||
local params = { textDocument = vim.lsp.util.make_text_document_params(0) }
|
||||
local response = vim.lsp.buf_request_sync(0, 'textDocument/documentLink', params)
|
||||
return response[1].result
|
||||
end)
|
||||
|
||||
--- @type table[]
|
||||
local ref_links = {}
|
||||
for i, r in ipairs(ref) do
|
||||
local start = { line = r[1], character = r[2] }
|
||||
local end_ = { line = r[3], character = r[4] }
|
||||
ref_links[i] = { range = { start = start, ['end'] = end_ }, target = r[5] }
|
||||
end
|
||||
|
||||
eq(ref_links, out_links)
|
||||
end
|
||||
|
||||
local fetch_src = repos_src.fetch
|
||||
local fetch_path = pack_get_plug_path('fetch')
|
||||
local fetch_path_uri = vim.uri_from_fname(fetch_path)
|
||||
local semver_src = repos_src.semver
|
||||
local semver_path = pack_get_plug_path('semver')
|
||||
local semver_path_uri = vim.uri_from_fname(semver_path)
|
||||
|
||||
-- With `file://` sources it can only return "Path:" and "Source:" links
|
||||
local ref_file_links = {
|
||||
{ 11, 17, 11, 17 + fetch_path:len() - 1, fetch_path_uri }, -- Path: ...
|
||||
{ 12, 17, 12, 17 + fetch_src:len() - 1, fetch_src }, -- Source: ...
|
||||
{ 24, 10, 24, 10 + semver_path:len() - 1, semver_path_uri }, -- Path: ...
|
||||
{ 25, 10, 25, 10 + semver_src:len() - 1, semver_src }, -- Source: ...
|
||||
}
|
||||
assert_links(ref_file_links)
|
||||
|
||||
-- Mock using GitHub sources which should provide all links
|
||||
local fetch_github = 'https://github.com/user/fetch'
|
||||
local semver_github = 'https://github.com/user/semver'
|
||||
local lines = api.nvim_buf_get_lines(0, 0, -1, false)
|
||||
lines[13] = lines[13]:gsub('^(Source: +)(.+)$', '%1' .. fetch_github)
|
||||
lines[26] = lines[26]:gsub('^(Source: +)(.+)$', '%1' .. semver_github)
|
||||
api.nvim_set_option_value('modifiable', true, { buf = 0 })
|
||||
api.nvim_buf_set_lines(0, 0, -1, false, lines)
|
||||
api.nvim_set_option_value('modifiable', false, { buf = 0 })
|
||||
|
||||
local ref_github_links = {
|
||||
{ 11, 17, 11, 17 + fetch_path:len() - 1, fetch_path_uri }, -- Path: ...
|
||||
{ 12, 17, 12, 45, fetch_github }, -- Source: ...
|
||||
{ 13, 17, 13, 56, fetch_github .. '/commit/' .. lines[14]:match('%S+$') }, -- Revision before: ...
|
||||
{ 14, 17, 14, 56, fetch_github .. '/commit/' .. lines[15]:match('(%S+) %b()$') }, -- Revision before: ...
|
||||
{ 17, 2, 17, 8, fetch_github .. '/commit/' .. lines[18]:match('^< (%S+)') },
|
||||
{ 18, 2, 18, 8, fetch_github .. '/commit/' .. lines[19]:match('^> (%S+)') },
|
||||
{ 19, 2, 19, 8, fetch_github .. '/commit/' .. lines[20]:match('^> (%S+)') },
|
||||
{ 24, 10, 24, 10 + semver_path:len() - 1, semver_path_uri }, -- Path: ...
|
||||
{ 25, 10, 25, 39, semver_github }, -- Source: ...
|
||||
{ 26, 10, 26, 49, semver_github .. '/commit/' .. lines[27]:match('(%S+) %b()$') }, -- Revision: ...
|
||||
-- Should use UTF index
|
||||
{ 29, 2, 29, 7, semver_github .. '/releases/tag/v1.0.0' },
|
||||
{ 30, 2, 30, 6, semver_github .. '/releases/tag/0.3.1' },
|
||||
}
|
||||
assert_links(ref_github_links)
|
||||
|
||||
n.exec('quit')
|
||||
|
||||
-- textDocument/documentSymbol
|
||||
exec_lua('vim.pack.update()')
|
||||
exec_lua('vim.lsp.buf.document_symbol()')
|
||||
local loclist = vim.tbl_map(function(x) --- @param x table
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user