Files
neovim/runtime/lua/vim/treesitter/_headings.lua
Justin M. Keyes 74fcc9452c fix(runtime): gO always says "Help TOC" #32971
Problem:
gO always says "Help TOC".

Solution:
Use a generic title instead.
2025-03-19 04:17:00 -07:00

145 lines
3.9 KiB
Lua
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

local ts = vim.treesitter
local api = vim.api
--- Treesitter-based navigation functions for headings
local M = {}
-- TODO(clason): use runtimepath queries (for other languages)
local heading_queries = {
vimdoc = [[
(h1 (heading) @h1)
(h2 (heading) @h2)
(h3 (heading) @h3)
(column_heading (heading) @h4)
]],
markdown = [[
(setext_heading
heading_content: (_) @h1
(setext_h1_underline))
(setext_heading
heading_content: (_) @h2
(setext_h2_underline))
(atx_heading
(atx_h1_marker)
heading_content: (_) @h1)
(atx_heading
(atx_h2_marker)
heading_content: (_) @h2)
(atx_heading
(atx_h3_marker)
heading_content: (_) @h3)
(atx_heading
(atx_h4_marker)
heading_content: (_) @h4)
(atx_heading
(atx_h5_marker)
heading_content: (_) @h5)
(atx_heading
(atx_h6_marker)
heading_content: (_) @h6)
]],
}
local function hash_tick(bufnr)
return tostring(vim.b[bufnr].changedtick)
end
---@class TS.Heading
---@field bufnr integer
---@field lnum integer
---@field text string
---@field level integer
--- Extract headings from buffer
--- @param bufnr integer buffer to extract headings from
--- @return TS.Heading[]
local get_headings = vim.func._memoize(hash_tick, function(bufnr)
local lang = ts.language.get_lang(vim.bo[bufnr].filetype)
if not lang then
return {}
end
local parser = assert(ts.get_parser(bufnr, lang, { error = false }))
local query = ts.query.parse(lang, heading_queries[lang])
local root = parser:parse()[1]:root()
local headings = {}
for id, node, _, _ in query:iter_captures(root, bufnr) do
local text = ts.get_node_text(node, bufnr)
local row, col = node:start()
--- why can't you just be normal?!
local skip ---@type boolean|integer
if lang == 'vimdoc' then
-- only column_headings at col 1 are headings, otherwise it's code examples
skip = (id == 4 and col > 0)
-- ignore tabular material
or (id == 4 and (text:find('\t') or text:find(' ')))
-- ignore tag-only headings
or (node:child_count() == 1 and node:child(0):type() == 'tag')
end
if not skip then
table.insert(headings, {
bufnr = bufnr,
lnum = row + 1,
text = text,
level = id,
})
end
end
return headings
end)
--- Shows an Outline (table of contents) of the current buffer, in the loclist.
function M.show_toc()
local bufnr = api.nvim_get_current_buf()
local headings = get_headings(bufnr)
if #headings == 0 then
return
end
-- add indentation for nicer list formatting
for _, heading in pairs(headings) do
if heading.level > 2 then
heading.text = '  ' .. heading.text
end
if heading.level > 4 then
heading.text = '  ' .. heading.text
end
end
vim.fn.setloclist(0, headings, ' ')
vim.fn.setloclist(0, {}, 'a', { title = 'Table of contents' })
vim.cmd.lopen()
end
--- Jump to section
--- @param opts table jump options
--- - count integer direction to jump (>0 forward, <0 backward)
--- - level integer only consider headings up to level
--- todo(clason): support count
function M.jump(opts)
local bufnr = api.nvim_get_current_buf()
local headings = get_headings(bufnr)
if #headings == 0 then
return
end
local winid = api.nvim_get_current_win()
local curpos = vim.fn.getcurpos(winid)[2] --[[@as integer]]
local maxlevel = opts.level or 6
if opts.count > 0 then
for _, heading in ipairs(headings) do
if heading.lnum > curpos and heading.level <= maxlevel then
api.nvim_win_set_cursor(winid, { heading.lnum, 0 })
return
end
end
elseif opts.count < 0 then
for i = #headings, 1, -1 do
if headings[i].lnum < curpos and headings[i].level <= maxlevel then
api.nvim_win_set_cursor(winid, { headings[i].lnum, 0 })
return
end
end
end
end
return M