From 9762c5e3406cab8152d8dd161c0178965d841676 Mon Sep 17 00:00:00 2001 From: Gregory Anders <8965202+gpanders@users.noreply.github.com> Date: Sat, 31 Aug 2024 19:56:20 -0500 Subject: [PATCH] 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. --- runtime/lua/vim/_defaults.lua | 8 ++-- runtime/lua/vim/ui.lua | 72 ++++++++++++++++++++++++++--------- 2 files changed, 58 insertions(+), 22 deletions(-) diff --git a/runtime/lua/vim/_defaults.lua b/runtime/lua/vim/_defaults.lua index 630f2219c7..38cfdbdd32 100644 --- a/runtime/lua/vim/_defaults.lua +++ b/runtime/lua/vim/_defaults.lua @@ -113,9 +113,11 @@ do local gx_desc = 'Opens filepath or URI under cursor with the system handler (file explorer, web browser, …)' vim.keymap.set({ 'n' }, 'gx', function() - local err = do_open(require('vim.ui')._get_url()) - if err then - vim.notify(err, vim.log.levels.ERROR) + for _, url in ipairs(require('vim.ui')._get_urls()) do + local err = do_open(url) + if err then + vim.notify(err, vim.log.levels.ERROR) + end end end, { desc = gx_desc }) vim.keymap.set({ 'x' }, 'gx', function() diff --git a/runtime/lua/vim/ui.lua b/runtime/lua/vim/ui.lua index dccdbf32cc..b831a4f23e 100644 --- a/runtime/lua/vim/ui.lua +++ b/runtime/lua/vim/ui.lua @@ -167,29 +167,63 @@ function M.open(path) return vim.system(cmd, opts), nil end ---- Gets the URL at cursor, if any. -function M._get_url() - if vim.bo.filetype == 'markdown' then - local range = vim.api.nvim_win_get_cursor(0) - vim.treesitter.get_parser():parse(range) - -- marking the node as `markdown_inline` is required. Setting it to `markdown` does not - -- work. - local current_node = vim.treesitter.get_node { lang = 'markdown_inline' } - while current_node do - local type = current_node:type() - if type == 'inline_link' or type == 'image' then - local child = assert(current_node:named_child(1)) - return vim.treesitter.get_node_text(child, 0) - end - current_node = current_node:parent() +--- Returns all URLs at cursor, if any. +--- @return string[] +function M._get_urls() + local urls = {} + + local bufnr = vim.api.nvim_get_current_buf() + local cursor = vim.api.nvim_win_get_cursor(0) + local row = cursor[1] - 1 + local col = cursor[2] + local extmarks = vim.api.nvim_buf_get_extmarks(bufnr, -1, { row, col }, { row, col }, { + details = true, + type = 'highlight', + overlap = true, + }) + for _, v in ipairs(extmarks) do + local details = v[4] + if details.url then + urls[#urls + 1] = details.url end end - local url = vim._with({ go = { isfname = vim.o.isfname .. ',@-@' } }, function() - return vim.fn.expand('') - end) + local highlighter = vim.treesitter.highlighter.active[bufnr] + if highlighter then + 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('') + end) + ) + end + + return urls end return M