mirror of
https://github.com/neovim/neovim.git
synced 2026-05-23 21:30:11 +00:00
fix(util): add and use forge link computation
Problem: There are many Git forges each with a different way of constructing permanent links to like commits and tags. Solution: Add a private utility function that computes these special links on the best effort basis.
This commit is contained in:
@@ -511,8 +511,8 @@ update({names}, {opts}) *vim.pack.update()*
|
||||
|
||||
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.
|
||||
commits, and tags. Makes a best effort educated guess about a link
|
||||
structure. Use |gx| to open a link to an object at cursor.
|
||||
• '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()|) -
|
||||
|
||||
@@ -129,4 +129,41 @@ function M.term_exitcode()
|
||||
return ''
|
||||
end
|
||||
|
||||
--- Compute a link to a target on a forge host
|
||||
--- @param repo string URL of repo, usually "https://<domain>/<user>/<name>"
|
||||
--- @param target string Identifier of a target, like commit hash or tag name
|
||||
--- @param target_type "commit"|"tag"
|
||||
--- @return string? # Example: <repo>/releases/tag/<target>
|
||||
function M.get_forge_url(repo, target, target_type)
|
||||
-- The structure <host>/<middle>/<target> works for most forges. Like:
|
||||
-- - https://github.com/neovim/nvim-lspconfig/commit/e146efa
|
||||
-- - https://github.com/neovim/nvim-lspconfig/releases/tag/v2.8.0
|
||||
local ref_middles = {
|
||||
{ pattern = '^https://github%.com/', commit = 'commit', tag = 'releases/tag' },
|
||||
{ pattern = '^https://gitlab%.com/', commit = '-/commit', tag = '-/tags' },
|
||||
{ pattern = '^https://git%.sr%.ht/', commit = 'commit', tag = 'refs' },
|
||||
{ pattern = '^https://tangled%.org/', commit = 'commit', tag = 'tags' },
|
||||
{ pattern = '^https://bitbucket%.org/', commit = 'commits', tag = 'src' },
|
||||
|
||||
-- Fall back to Forgejo style since there is no fixed host
|
||||
{ pattern = '^https://', commit = 'commit', tag = 'src/tag' },
|
||||
}
|
||||
|
||||
local middle = ''
|
||||
for _, mid in ipairs(ref_middles) do
|
||||
if repo:match(mid.pattern) then
|
||||
middle = mid[target_type]
|
||||
if middle ~= '' then
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if middle == '' then
|
||||
return nil
|
||||
end
|
||||
repo = repo:gsub('/+$', '')
|
||||
return ('%s/%s/%s'):format(repo, middle, target)
|
||||
end
|
||||
|
||||
return M
|
||||
|
||||
@@ -1248,8 +1248,8 @@ end
|
||||
---
|
||||
--- 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.
|
||||
--- commits, and tags. Makes a best effort educated guess about a link structure.
|
||||
--- Use |gx| to open a link to an object at cursor.
|
||||
--- - '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
|
||||
|
||||
@@ -83,42 +83,32 @@ end
|
||||
--- @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
|
||||
return nil
|
||||
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
|
||||
--- @type string?
|
||||
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)
|
||||
---@diagnostic disable-next-line: param-type-mismatch
|
||||
target = require('vim._core.util').get_forge_url(src, match, link_type)
|
||||
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,
|
||||
}
|
||||
if target == nil then
|
||||
return nil
|
||||
end
|
||||
|
||||
local start = { line = lnum - 1, character = from }
|
||||
local end_ = { line = lnum - 1, character = to }
|
||||
return { range = { start = start, ['end'] = end_ }, target = target }
|
||||
end
|
||||
|
||||
--- @param params { textDocument: { uri: string } }
|
||||
|
||||
@@ -1473,12 +1473,12 @@ describe('vim.pack', function()
|
||||
}
|
||||
assert_links(ref_file_links)
|
||||
|
||||
-- Mock using GitHub sources which should provide all links
|
||||
-- Mock using common sources which should provide all links
|
||||
local fetch_github = 'https://github.com/user/fetch'
|
||||
local semver_github = 'https://github.com/user/semver'
|
||||
local semver_codeberg = 'https://codeberg.org/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)
|
||||
lines[26] = lines[26]:gsub('^(Source: +)(.+)$', '%1' .. semver_codeberg)
|
||||
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 })
|
||||
@@ -1492,11 +1492,11 @@ describe('vim.pack', function()
|
||||
{ 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: ...
|
||||
{ 25, 10, 25, 41, semver_codeberg }, -- Source: ...
|
||||
{ 26, 10, 26, 49, semver_codeberg .. '/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' },
|
||||
{ 29, 2, 29, 7, semver_codeberg .. '/src/tag/v1.0.0' },
|
||||
{ 30, 2, 30, 6, semver_codeberg .. '/src/tag/0.3.1' },
|
||||
}
|
||||
assert_links(ref_github_links)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user