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:
@@ -73,6 +73,8 @@
|
|||||||
code)
|
code)
|
||||||
- Highlights will also take into account `ctermfg/bg` when setting default values.
|
- Highlights will also take into account `ctermfg/bg` when setting default values.
|
||||||
This ensures outline.nvim highlights work if `termguicolors` is not enabled
|
This ensures outline.nvim highlights work if `termguicolors` is not enabled
|
||||||
|
- A built-in provider for `norg` files that displays headings in the outline is now
|
||||||
|
provided. This requires `norg` parser to be installed for treesitter
|
||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
|
|
||||||
|
|||||||
63
README.md
63
README.md
@@ -41,6 +41,7 @@ Table of contents
|
|||||||
* [Installation](#installation)
|
* [Installation](#installation)
|
||||||
* [Setup](#setup)
|
* [Setup](#setup)
|
||||||
* [Configuration](#configuration)
|
* [Configuration](#configuration)
|
||||||
|
* [Providers](#providers)
|
||||||
* [Commands](#commands)
|
* [Commands](#commands)
|
||||||
* [Default keymaps](#default-keymaps)
|
* [Default keymaps](#default-keymaps)
|
||||||
* [Highlights](#highlights)
|
* [Highlights](#highlights)
|
||||||
@@ -59,7 +60,7 @@ Table of contents
|
|||||||
- Neovim 0.7+
|
- Neovim 0.7+
|
||||||
- To use modifiers on [commands](#commands), Neovim 0.8 is required.
|
- To use modifiers on [commands](#commands), Neovim 0.8 is required.
|
||||||
Everything else works with Neovim 0.7.
|
Everything else works with Neovim 0.7.
|
||||||
- Properly configured Neovim LSP client (otherwise only markdown is supported)
|
- To use outline.nvim with LSP, a properly configured LSP client is required.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
@@ -333,7 +334,7 @@ Pass a table to the setup call with your configuration options.
|
|||||||
},
|
},
|
||||||
|
|
||||||
providers = {
|
providers = {
|
||||||
priority = { 'lsp', 'coc', 'markdown' },
|
priority = { 'lsp', 'coc', 'markdown', 'norg' },
|
||||||
lsp = {
|
lsp = {
|
||||||
-- Lsp client names to ignore
|
-- Lsp client names to ignore
|
||||||
blacklist_clients = {},
|
blacklist_clients = {},
|
||||||
@@ -448,6 +449,64 @@ The order in which the sources for icons are checked is:
|
|||||||
|
|
||||||
A fallback is always used if the previous candidate returned a falsey value.
|
A fallback is always used if the previous candidate returned a falsey value.
|
||||||
|
|
||||||
|
## Providers
|
||||||
|
|
||||||
|
The current list of tested providers are:
|
||||||
|
1. LSP (requires a suitable LSP server to be configured for the requested buffer)
|
||||||
|
- For JSX support, `javascript` parser for treesitter is required
|
||||||
|
1. Markdown (no external requirements)
|
||||||
|
1. Norg (requires `norg` parser for treesitter)
|
||||||
|
|
||||||
|
### External providers
|
||||||
|
|
||||||
|
External providers can be appended to the `providers.priority` list. Each
|
||||||
|
item in the list is appended to `"outline.providers.<item>"` to form an import
|
||||||
|
path, for use as a provider.
|
||||||
|
|
||||||
|
External providers from plugins should define the provider module at
|
||||||
|
`lua/outline/providers/<name>.lua` with these functions:
|
||||||
|
|
||||||
|
- `supports_buffer(bufnr: integer) -> boolean`
|
||||||
|
|
||||||
|
This function could check buffer filetype, existence of required modules, etc.
|
||||||
|
|
||||||
|
- `get_status() -> string[]` (optional)
|
||||||
|
|
||||||
|
Return a list of lines to be included in `:OutlineStatus` as supplementary
|
||||||
|
information when this provider is active.
|
||||||
|
|
||||||
|
See an example of this function in the
|
||||||
|
[LSP](./lua/outline/providers/nvim-lsp.lua) provider.
|
||||||
|
|
||||||
|
- `request_symbols(callback: function, opts: table)`
|
||||||
|
|
||||||
|
- param `callback` is a function that receives a list of symbols and the
|
||||||
|
`opts` table.
|
||||||
|
- param `opts` can be passed to `callback` without processing
|
||||||
|
|
||||||
|
Each symbol table in the list of symbols should these fields:
|
||||||
|
- name: string
|
||||||
|
- kind: integer
|
||||||
|
- selectionRange: table with fields `start` and `end`, each have fields
|
||||||
|
`line` and `character`, each integers
|
||||||
|
- range: table with fields `start` and `end`, each have fields `line` and
|
||||||
|
`character`, each integers
|
||||||
|
- children: list of table of symbols
|
||||||
|
- detail: (optional) string, shown as `outline_items.show_symbol_details`
|
||||||
|
|
||||||
|
The built-in [markdown](./lua/outline/providers/markdown.lua) provider is a
|
||||||
|
good example of a very simple outline-provider module which parses raw buffer
|
||||||
|
lines and uses regex; the built-in [norg](./lua/outline/providers/norg.lua)
|
||||||
|
provider is an example which uses treesitter.
|
||||||
|
|
||||||
|
All providers should support at least nvim 0.7. You can make use of
|
||||||
|
`_G._outline_nvim_has` with fields `[8]` and `[9]` equivalent to
|
||||||
|
`vim.fn.has('nvim-0.8) == 1` and `vim.fn.has('nvim-0.9) == 1` respectively.
|
||||||
|
|
||||||
|
If a higher nvim version is required, it is recommended to check for this
|
||||||
|
requirement in the `supports_buffer` function.
|
||||||
|
|
||||||
|
|
||||||
## Commands
|
## Commands
|
||||||
|
|
||||||
- **:Outline[!]** (✓ bang ✓ mods)
|
- **:Outline[!]** (✓ bang ✓ mods)
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ M.defaults = {
|
|||||||
up_and_jump = '<C-k>',
|
up_and_jump = '<C-k>',
|
||||||
},
|
},
|
||||||
providers = {
|
providers = {
|
||||||
priority = { 'lsp', 'coc', 'markdown' },
|
priority = { 'lsp', 'coc', 'markdown', 'norg' },
|
||||||
lsp = {
|
lsp = {
|
||||||
blacklist_clients = {},
|
blacklist_clients = {},
|
||||||
},
|
},
|
||||||
@@ -211,8 +211,8 @@ end
|
|||||||
---@return boolean include
|
---@return boolean include
|
||||||
function M.should_include_symbol(kind, bufnr)
|
function M.should_include_symbol(kind, bufnr)
|
||||||
local ft = vim.api.nvim_buf_get_option(bufnr, 'ft')
|
local ft = vim.api.nvim_buf_get_option(bufnr, 'ft')
|
||||||
-- There can only be one kind in markdown as of now
|
-- There can only be one kind in markdown and norg as of now
|
||||||
if ft == 'markdown' or kind == nil then
|
if ft == 'markdown' or ft == 'norg' or kind == nil then
|
||||||
return true
|
return true
|
||||||
end
|
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