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:
@@ -89,7 +89,7 @@ M.defaults = {
|
||||
up_and_jump = '<C-k>',
|
||||
},
|
||||
providers = {
|
||||
priority = { 'lsp', 'coc', 'markdown' },
|
||||
priority = { 'lsp', 'coc', 'markdown', 'norg' },
|
||||
lsp = {
|
||||
blacklist_clients = {},
|
||||
},
|
||||
@@ -211,8 +211,8 @@ end
|
||||
---@return boolean include
|
||||
function M.should_include_symbol(kind, bufnr)
|
||||
local ft = vim.api.nvim_buf_get_option(bufnr, 'ft')
|
||||
-- There can only be one kind in markdown as of now
|
||||
if ft == 'markdown' or kind == nil then
|
||||
-- There can only be one kind in markdown and norg as of now
|
||||
if ft == 'markdown' or ft == 'norg' or kind == nil then
|
||||
return true
|
||||
end
|
||||
|
||||
|
||||
135
lua/outline/providers/norg.lua
Normal file
135
lua/outline/providers/norg.lua
Normal 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
|
||||
Reference in New Issue
Block a user