feat(ui): gx: use url extmark attribute and tree-sitter directive (#30192)

Use the "url" extmark attribute as well as the "url" tree-sitter
metadata key to determine if the cursor is over something Nvim considers
a URL.
This commit is contained in:
Gregory Anders 2024-08-31 19:56:20 -05:00 committed by GitHub
parent 808d73b5df
commit 9762c5e340
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 58 additions and 22 deletions

View File

@ -113,9 +113,11 @@ do
local gx_desc = local gx_desc =
'Opens filepath or URI under cursor with the system handler (file explorer, web browser, …)' 'Opens filepath or URI under cursor with the system handler (file explorer, web browser, …)'
vim.keymap.set({ 'n' }, 'gx', function() vim.keymap.set({ 'n' }, 'gx', function()
local err = do_open(require('vim.ui')._get_url()) for _, url in ipairs(require('vim.ui')._get_urls()) do
if err then local err = do_open(url)
vim.notify(err, vim.log.levels.ERROR) if err then
vim.notify(err, vim.log.levels.ERROR)
end
end end
end, { desc = gx_desc }) end, { desc = gx_desc })
vim.keymap.set({ 'x' }, 'gx', function() vim.keymap.set({ 'x' }, 'gx', function()

View File

@ -167,29 +167,63 @@ function M.open(path)
return vim.system(cmd, opts), nil return vim.system(cmd, opts), nil
end end
--- Gets the URL at cursor, if any. --- Returns all URLs at cursor, if any.
function M._get_url() --- @return string[]
if vim.bo.filetype == 'markdown' then function M._get_urls()
local range = vim.api.nvim_win_get_cursor(0) local urls = {}
vim.treesitter.get_parser():parse(range)
-- marking the node as `markdown_inline` is required. Setting it to `markdown` does not local bufnr = vim.api.nvim_get_current_buf()
-- work. local cursor = vim.api.nvim_win_get_cursor(0)
local current_node = vim.treesitter.get_node { lang = 'markdown_inline' } local row = cursor[1] - 1
while current_node do local col = cursor[2]
local type = current_node:type() local extmarks = vim.api.nvim_buf_get_extmarks(bufnr, -1, { row, col }, { row, col }, {
if type == 'inline_link' or type == 'image' then details = true,
local child = assert(current_node:named_child(1)) type = 'highlight',
return vim.treesitter.get_node_text(child, 0) overlap = true,
end })
current_node = current_node:parent() for _, v in ipairs(extmarks) do
local details = v[4]
if details.url then
urls[#urls + 1] = details.url
end end
end end
local url = vim._with({ go = { isfname = vim.o.isfname .. ',@-@' } }, function() local highlighter = vim.treesitter.highlighter.active[bufnr]
return vim.fn.expand('<cfile>') if highlighter then
end) local range = { row, col, row, col }
local ltree = highlighter.tree:language_for_range(range)
local lang = ltree:lang()
local query = vim.treesitter.query.get(lang, 'highlights')
if query then
local tree = ltree:tree_for_range(range)
for _, match, metadata in query:iter_matches(tree:root(), bufnr, row, row + 1, { all = true }) do
for id, nodes in pairs(match) do
for _, node in ipairs(nodes) do
if vim.treesitter.node_contains(node, range) then
local url = metadata[id] and metadata[id].url
if url and match[url] then
for _, n in ipairs(match[url]) do
urls[#urls + 1] = vim.treesitter.get_node_text(n, bufnr, metadata[url])
end
end
end
end
end
end
end
end
return url if #urls == 0 then
-- If all else fails, use the filename under the cursor
table.insert(
urls,
vim._with({ go = { isfname = vim.o.isfname .. ',@-@' } }, function()
return vim.fn.expand('<cfile>')
end)
)
end
return urls
end end
return M return M