This commit introduces a basic framework for symbol filtering in outline.nvim, where users can set per-filetype kinds to filter - include or exclude for each filetype. As a side effect the checking of symbol inclusion function has been improved to O(1) time-complexity (previously O(n)). You can see this from types/outline.lua and config.lua: a lookup table is used to check if a kind is filtered, rather than looping through a list each time. Former takes O(1) for lookup whereas the old implementation would be O(n) for *each* node! The old symbols.blacklist option *still works as expected*. The schema for the new confit is detailed in #23 and types/outline.lua. By the way, this commit also closes #23. These should equivalent: symbols.blacklist = { 'Function', 'Method' } symbols.filter = { 'Function', 'Method', exclude=true } symbols.filter = { ['*'] = { 'Function', 'Method', exclude=true } } And these should be equivalent: symbols.blacklist = {} symbols.filter = false symbols.filter = nil symbols.filter = { ['*'] = false } symbols.filter = { ['*'] = { exclude = true } } symbols.filter = { exclude = true } The last two of which could be considered unidiomatic. When multiple filetypes are specified, filetype specific filters are NOT merged with the default ('*') filter, they are independent. If a filetype is used, the default filter is not considered. The default filter is only considered if a filetype filter for the given buffer is not provided. LIMITATIONS: This is carried over from the implementation from symbols-outline: filters can only be applied to parents at the moment. I.e.: If some node has a kind that is excluded, all its children will NOT be considered. Filters are only applied to children if its parent was not excluded during filtering. Also extracted all types into types module, and updated conversion script to use the new symbols.filter opt. NOTE: On outline open it appears that parsing functions are called twice? I should definitely add tests soon.
127 lines
4.3 KiB
Lua
127 lines
4.3 KiB
Lua
local symbols = require 'outline.symbols'
|
|
local ui = require 'outline.ui'
|
|
local cfg = require 'outline.config'
|
|
local t_utils = require 'outline.utils.table'
|
|
local lsp_utils = require 'outline.utils.lsp_utils'
|
|
local folding = require 'outline.folding'
|
|
|
|
local M = {}
|
|
|
|
---Parses result from LSP into a reorganized tree of symbols (not flattened,
|
|
-- simply reoganized by merging each property table from the arguments into a
|
|
-- table for each symbol)
|
|
---@param result table The result from a language server.
|
|
---@param depth number? The current depth of the symbol in the hierarchy.
|
|
---@param hierarchy table? A table of booleans which tells if a symbols parent was the last in its group.
|
|
---@param parent table? A reference to the current symbol's parent in the function's recursion
|
|
---@param bufnr integer The buffer number which the result was from
|
|
---@return outline.SymbolNode[]
|
|
local function parse_result(result, depth, hierarchy, parent, bufnr)
|
|
local ret = {}
|
|
|
|
for index, value in pairs(result) do
|
|
-- FIXME: If a parent was excluded, all children will not be considered
|
|
if cfg.should_include_symbol(symbols.kinds[value.kind], bufnr) then
|
|
-- the hierarchy is basically a table of booleans which
|
|
-- tells whether the parent was the last in its group or
|
|
-- not
|
|
local hir = hierarchy or {}
|
|
-- how many parents this node has, 1 is the lowest value because its
|
|
-- easier to work it
|
|
local level = depth or 1
|
|
-- whether this node is the last (~born~) in its siblings
|
|
local isLast = index == #result
|
|
|
|
local selectionRange = lsp_utils.get_selection_range(value)
|
|
local range = lsp_utils.get_range(value)
|
|
|
|
local node = {
|
|
deprecated = value.deprecated,
|
|
kind = value.kind,
|
|
icon = symbols.icon_from_kind(value.kind),
|
|
name = value.name or value.text,
|
|
detail = value.detail,
|
|
line = selectionRange.start.line,
|
|
character = selectionRange.start.character,
|
|
range_start = range.start.line,
|
|
range_end = range['end'].line,
|
|
depth = level,
|
|
isLast = isLast,
|
|
hierarchy = hir,
|
|
parent = parent,
|
|
traversal_child = 1,
|
|
}
|
|
|
|
table.insert(ret, node)
|
|
|
|
local children = nil
|
|
if value.children ~= nil then
|
|
-- copy by value because we dont want it messing with the hir table
|
|
local child_hir = t_utils.array_copy(hir)
|
|
table.insert(child_hir, isLast)
|
|
children = parse_result(value.children, level + 1, child_hir, node, bufnr)
|
|
else
|
|
value.children = {}
|
|
end
|
|
|
|
node.children = children
|
|
end
|
|
end
|
|
return ret
|
|
end
|
|
|
|
---Sorts and reorganizes the response from lsp request
|
|
--'textDocument/documentSymbol', buf_request_all.
|
|
---Used when refreshing and setting up new symbols
|
|
---@param response table The result from buf_request_all
|
|
---@param bufnr integer
|
|
---@return outline.SymbolNode[]
|
|
function M.parse(response, bufnr)
|
|
local sorted = lsp_utils.sort_symbols(response)
|
|
|
|
return parse_result(sorted, nil, nil, nil, bufnr)
|
|
end
|
|
|
|
---Iterator that traverses the tree parent first before children, returning each node.
|
|
-- Essentailly 'flatten' items, but returns an iterator.
|
|
---@param items outline.SymbolNode[] Tree of symbols parsed by parse_result
|
|
---@param children_check function? Takes a node and return whether the children should be explored.
|
|
---Note that the root node (param items) is always explored regardless of children_check.
|
|
function M.preorder_iter(items, children_check)
|
|
local node = { children = items, traversal_child = 1, depth = 1, is_root = true }
|
|
local prev
|
|
local visited = {}
|
|
|
|
if children_check == nil then
|
|
children_check = function(n)
|
|
return not folding.is_folded(n)
|
|
end
|
|
end
|
|
|
|
return function()
|
|
while node do
|
|
if node.name and not visited[node] then
|
|
visited[node] = true
|
|
return node
|
|
end
|
|
|
|
if
|
|
node.children and node.traversal_child <= #node.children
|
|
and (node.is_root or children_check(node))
|
|
then
|
|
prev = node
|
|
if node.children[node.traversal_child] then
|
|
node.children[node.traversal_child].parent_node = node
|
|
node = node.children[node.traversal_child]
|
|
end
|
|
prev.traversal_child = prev.traversal_child + 1
|
|
else
|
|
node.traversal_child = 1
|
|
node = node.parent_node
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
return M
|