Files
outline.nvim/lua/outline/parser.lua
2025-08-19 06:11:44 +10:00

267 lines
7.7 KiB
Lua

local cfg = require('outline.config')
local folding = require('outline.folding')
local lsp_utils = require('outline.utils.lsp')
local symbols = require('outline.symbols')
local utils = require('outline.utils.init')
local M = {}
local function norm_kind(kind)
if type(kind) == 'number' then
return kind
else
-- string
return symbols.str_to_kind[kind] or 21 -- fallback to Null
end
end
---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 outline.ProviderSymbol 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.Symbol[]
local function parse_result(result, depth, hierarchy, parent, bufnr)
local ret = {}
-- LEIGH MOORE
-- Begin filter and Sort Symbols
local lspKind = {
[5] = 0, -- 'Class'
[7] = 1, -- 'Property'
[9] = 2, -- 'Constructor'
[6] = 3, -- 'Method'
[1] = 999, -- 'File'
[2] = 999, -- 'Module'
[3] = 999, -- 'Namespace'
[4] = 999, -- 'Package'
[8] = 999, -- 'Field'
[10] = 999, -- 'Enum'
[11] = 999, -- 'Interface'
[12] = 999, -- 'Function'
[13] = 999, -- 'Variable'
[14] = 999, -- 'Constant'
[15] = 999, -- 'String'
[16] = 999, -- 'Number'
[17] = 999, -- 'Boolean'
[18] = 999, -- 'Array'
[19] = 999, -- 'Object'
[20] = 999, -- 'Key'
[21] = 999, -- 'Null'
[22] = 999, -- 'EnumMember'
[23] = 999, -- 'Struct'
[24] = 999, -- 'Event'
[25] = 999, -- 'Operator'
[26] = 999, -- 'TypeParameter'
[27] = 999, -- 'Component'
[28] = 999, -- 'Fragment'
-- ccls
[252] = 999, -- 'TypeAlias'
[253] = 999, -- 'Parameter'
[254] = 999, -- 'StaticMethod'
[255] = 999, -- 'Macro'
}
local function filter_array(arr, predicate)
local result = {}
for _, value in ipairs(arr) do
-- vim.print(vim.inspect(value))
if predicate(value) then
table.insert(result, value)
end
end
return result
end
result = filter_array(result, function(value)
return value.kind ~= 12 and value.name:match(' callback') == nil
end)
table.sort(result, function(a, b)
a.kind = norm_kind(a.kind)
b.kind = norm_kind(b.kind)
if lspKind[a.kind] < lspKind[b.kind] then
return true
elseif lspKind[a.kind] > lspKind[b.kind] then
return false
else
return a.name < b.name
end
end)
-- End filter and Sort Symbols
local kinds = {}
for index, value in pairs(result) do
-- FIXME: If a parent was excluded, all children will not be considered
value.kind = norm_kind(value.kind)
kinds[value.kind] = true;
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, bufnr, value),
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,
_i = 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 = 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
vim.print("KINDS")
vim.print(vim.inspect(kinds))
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.Symbol[]
function M.parse(response, bufnr)
local sorted = lsp_utils.sort_symbols(response)
return parse_result(sorted, nil, nil, { is_root = true, child_count = #sorted }, bufnr)
end
---Iterator that traverses the tree parent first before children, returning each node.
-- Essentailly 'flatten' items, but returns an iterator.
---@param items outline.Symbol[] 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, _i = 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._i <= #node.children and (node.is_root or children_check(node)) then
prev = node
if node.children[node._i] then
node.children[node._i].parent_node = node
node = node.children[node._i]
end
prev._i = prev._i + 1
else
node._i = 1
node = node.parent_node
end
end
end
end
---Merges a symbol tree recursively, only replacing nodes
---which have changed. This will maintain the folding
---status of any unchanged nodes.
---@param new_node table New node
---@param old_node table Old node
---@param index? number Index of old_item in parent
---@param parent? table Parent of old_item
function M.merge_items_rec(new_node, old_node, index, parent)
local failed = false
if not new_node or not old_node then
failed = true
else
for key, _ in pairs(new_node) do
if
vim.tbl_contains({
'parent',
'children',
'folded',
'hovered',
'line_in_outline',
'hierarchy',
}, key)
then
goto continue
end
if key == 'name' then
-- in the case of a rename, just rename the existing node
old_node['name'] = new_node['name']
else
if not vim.deep_equal(new_node[key], old_node[key]) then
failed = true
break
end
end
::continue::
end
end
if failed then
if parent and index then
parent[index] = new_node
end
else
local next_new_item = new_node.children or {}
-- in case new children are created on a node which
-- previously had no children
if #next_new_item > 0 and not old_node.children then
old_node.children = {}
end
local next_old_item = old_node.children or {}
for i = 1, math.max(#next_new_item, #next_old_item) do
M.merge_items_rec(next_new_item[i], next_old_item[i], i, next_old_item)
end
end
end
return M