feat: Norg provider and support of external providers

- Closes #3
- Ref: simrat39/symbols-outline.nvim#190

Norg contains indents and different types of verbatim tags, I was rather
lazy to read the spec properly and parse norg using regex line-by-line
like markdown, so used treesitter instead. The only requirement is the
`norg` parser for treesitter to be installed. Tested on nvim 0.7.2.

This should lead the way for supporting vimdoc files in a similar
manner.

Documentation for how external providers could look like as of now has
been added.

In the future we could let the provider determine what to do for each
keymap, such as `goto_location` and `toggle_preview`. This would allow
the zk extension[1] to work properly without having to override existing
functions (bad practice).

[1]: https://github.com/mickael-menu/zk-nvim/discussions/134
This commit is contained in:
hedy
2023-11-22 22:16:19 +08:00
parent 8c5c69feb2
commit d35187ef37
4 changed files with 201 additions and 5 deletions

View File

@@ -0,0 +1,135 @@
local M = {
name = 'norg',
query = [[
[
(heading1 (heading1_prefix)
title: (paragraph_segment) @name)
(heading2 (heading2_prefix)
title: (paragraph_segment) @name)
(heading3 (heading3_prefix)
title: (paragraph_segment) @name)
(heading4 (heading4_prefix)
title: (paragraph_segment) @name)
(heading5 (heading5_prefix)
title: (paragraph_segment) @name)
(heading6 (heading6_prefix)
title: (paragraph_segment) @name)
]
]],
}
function M.supports_buffer(bufnr)
if vim.api.nvim_buf_get_option(bufnr, 'ft') ~= 'norg' then
return false
end
local status, parser = pcall(vim.treesitter.get_parser, bufnr, 'norg')
if not status or not parser then
return false
end
M.parser = parser
return true
end
local is_ancestor = vim.treesitter.is_ancestor
if not _G._outline_nvim_has[8] then
is_ancestor = function(dest, source)
if not (dest and source) then
return false
end
local current = source
while current ~= nil do
if current == dest then
return true
end
current = current:parent()
end
return false
end
end
local function rec_remove_field(node, field)
node[field] = nil
if node.children then
for _, child in ipairs(node.children) do
rec_remove_field(child, field)
end
end
end
function M.request_symbols(callback, opts)
if not M.parser then
local status, parser = pcall(vim.treesitter.get_parser, 0, 'norg')
if not status or not parser then
callback(nil, opts)
return
end
M.parser = parser
end
local root = M.parser:parse()[1]:root()
if not root then
callback(nil, opts)
return
end
local r = { children = {}, tsnode = root, name = 'root' }
local stack = { r }
local query
if _G._outline_nvim_has[9] then
query = vim.treesitter.query.parse('norg', M.query)
else
---@diagnostic disable-next-line: deprecated
query = vim.treesitter.query.parse_query('norg', M.query)
end
---@diagnostic disable-next-line: missing-parameter
for _, captured_node, _ in query:iter_captures(root, 0) do
local row1, col1, row2, col2 = captured_node:range()
local title = vim.api.nvim_buf_get_text(0, row1, col1, row2, col2, {})[1]
local heading_node = captured_node:parent()
row1, col1, row2, col2 = heading_node:range()
title = title:gsub('^%s+', '')
local current = {
kind = 15,
name = title,
selectionRange = {
start = { character = col1, line = row1 },
['end'] = { character = col2, line = row2 - 1 },
},
range = {
start = { character = col1, line = row1 },
['end'] = { character = col2, line = row2 - 1 },
},
children = {},
tsnode = heading_node,
}
while #stack > 0 do
local top = stack[#stack]
if is_ancestor(top.tsnode, heading_node) then
current.parent = top
table.insert(top.children, current)
break
end
table.remove(stack, #stack)
end
table.insert(stack, current)
end
rec_remove_field(r, 'tsnode')
callback(r.children, opts)
end
return M