diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index c5bc2199ca..34d600bb6c 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -194,6 +194,8 @@ EDITOR and |:vimgrep| commands. • For security, 'exrc' no longer shows an "(a)llow" choice. Instead you must "(v)iew" then run `:trust`. +• |gx| in help buffers opens the online documentation for the tag under the + cursor. EVENTS diff --git a/runtime/ftplugin/help.lua b/runtime/ftplugin/help.lua index 14255fa0fc..34a477ad62 100644 --- a/runtime/ftplugin/help.lua +++ b/runtime/ftplugin/help.lua @@ -66,52 +66,86 @@ vim.keymap.set('n', '[[', function() require('vim.treesitter._headings').jump({ count = -1 }) end, { buffer = 0, silent = false, desc = 'Jump to previous section' }) --- Add "runnables" for Lua/Vimscript code examples. ----@type table -local code_blocks = {} local parser = assert(vim.treesitter.get_parser(0, 'vimdoc', { error = false })) -local query = vim.treesitter.query.parse( - 'vimdoc', - [[ - (codeblock - (language) @_lang - . - (code) @code - (#any-of? @_lang "lua" "vim") - (#set! @code lang @_lang)) -]] -) local root = parser:parse()[1]:root() -for _, match, metadata in query:iter_matches(root, 0, 0, -1) do - for id, nodes in pairs(match) do - local name = query.captures[id] - local node = nodes[1] - local start, _, end_ = node:parent():range() +-- Add "runnables" for Lua/Vimscript code examples. +do + ---@type table + local code_blocks = {} + local query = vim.treesitter.query.parse( + 'vimdoc', + [[ + (codeblock + (language) @_lang + . + (code) @code + (#any-of? @_lang "lua" "vim") + (#set! @code lang @_lang)) + ]] + ) - if name == 'code' then - local code = vim.treesitter.get_node_text(node, 0) - local lang_node = match[metadata[id].lang][1] --[[@as TSNode]] - local lang = vim.treesitter.get_node_text(lang_node, 0) - for i = start + 1, end_ do - code_blocks[i] = { lang = lang, code = code } + for _, match, metadata in query:iter_matches(root, 0, 0, -1) do + for id, nodes in pairs(match) do + local name = query.captures[id] + local node = nodes[1] + local start, _, end_ = node:parent():range() + + if name == 'code' then + local code = vim.treesitter.get_node_text(node, 0) + local lang_node = match[metadata[id].lang][1] --[[@as TSNode]] + local lang = vim.treesitter.get_node_text(lang_node, 0) + for i = start + 1, end_ do + code_blocks[i] = { lang = lang, code = code } + end + end + end + end + + vim.keymap.set('n', 'g==', function() + local pos = vim.api.nvim_win_get_cursor(0)[1] + local code_block = code_blocks[pos] + if not code_block then + vim.print('No code block found') + elseif code_block.lang == 'lua' then + vim.cmd.lua(code_block.code) + elseif code_block.lang == 'vim' then + vim.cmd(code_block.code) + end + end, { buffer = true }) +end + +do + local ns = vim.api.nvim_create_namespace('nvim.help.urls') + local base = 'https://neovim.io/doc/user/helptag.html?tag=' + local query = vim.treesitter.query.parse( + 'vimdoc', + [[ + ((optionlink) @helplink) + (taglink + text: (_) @helplink) + (tag + text: (_) @helplink) + ]] + ) + + for _, match, _ in query:iter_matches(root, 0, 0, -1) do + for id, nodes in pairs(match) do + if query.captures[id] == 'helplink' then + for _, node in ipairs(nodes) do + local start_line, start_col, end_line, end_col = node:range() + local tag = vim.treesitter.get_node_text(node, 0) + vim.api.nvim_buf_set_extmark(0, ns, start_line, start_col, { + end_line = end_line, + end_col = end_col, + url = base .. vim.uri_encode(tag), + }) + end end end end end -vim.keymap.set('n', 'g==', function() - local pos = vim.api.nvim_win_get_cursor(0)[1] - local code_block = code_blocks[pos] - if not code_block then - vim.print('No code block found') - elseif code_block.lang == 'lua' then - vim.cmd.lua(code_block.code) - elseif code_block.lang == 'vim' then - vim.cmd(code_block.code) - end -end, { buffer = true }) - vim.b.undo_ftplugin = (vim.b.undo_ftplugin or '') .. '\n sil! exe "nunmap gO" | sil! exe "nunmap g=="' .. '\n sil! exe "nunmap ]]" | sil! exe "nunmap [["' diff --git a/test/functional/lua/ui_spec.lua b/test/functional/lua/ui_spec.lua index 5c727b3347..572df3ee26 100644 --- a/test/functional/lua/ui_spec.lua +++ b/test/functional/lua/ui_spec.lua @@ -13,7 +13,7 @@ local poke_eventloop = n.poke_eventloop describe('vim.ui', function() before_each(function() - clear() + clear({ args_rm = { '-u' }, args = { '--clean' } }) end) describe('select()', function() @@ -180,5 +180,28 @@ describe('vim.ui', function() end, { n.testprg('printargs-test'), 'arg1' }) ) end) + + it('gx on a help tag opens URL', function() + n.command('helptags $VIMRUNTIME/doc') + n.command('help nvim.txt') + + local link_ns = n.api.nvim_create_namespace('nvim.help.urls') + local tag = n.api.nvim_buf_get_extmarks(0, link_ns, 0, -1, { + limit = 1, + details = true, + })[1] + + local url = tag[4].url + assert(url) + + --- points to the neovim.io site + eq(true, vim.startswith(url, 'https://neovim.io/doc')) + + -- tag is URI encoded + local param = url:match('%?tag=(.*)') + local tagname = + n.api.nvim_buf_get_text(0, tag[2], tag[3], tag[4].end_row, tag[4].end_col, {})[1] + eq(vim.uri_encode(tagname), param) + end) end) end)