feat(api): Get symbol and breadcrumb at location

Currently the implementation is very limited.

Ref: #24

- Outline must be open and have been loaded for it to work (requires
  lazy loading or headless loading of Sidebar)
- Empty string returned if cursor is not in any symbol ('closest' symbol
  not yet supported)
- Line column not used
- Returning concatenated symbol names rather than a list of tables with
  node info (requires a refactor of outline.SymbolNode type)
- Subject to config.symbols.filter and folds (requires finding hover
  list somewhere outside of writer.make_outline)
This commit is contained in:
hedy
2023-11-25 14:48:38 +08:00
parent f586147ee2
commit 6cea72f2b5
7 changed files with 124 additions and 12 deletions

View File

@@ -120,13 +120,14 @@ end
function M.open_outline(opts)
local tab = vim.api.nvim_get_current_tabpage()
local sidebar = M.sidebars[tab]
M.current = sidebar
if not sidebar then
sidebar = Sidebar:new()
M.sidebars[tab] = sidebar
end
M.current = sidebar
return sidebar:open(opts)
end
@@ -135,6 +136,85 @@ function M.is_focus_in_outline()
return M._sidebar_do('has_focus')
end
M.has_focus = M.is_focus_in_outline
---@return boolean
function M.is_open()
local sidebar = M._get_sidebar()
if sidebar then
return sidebar.view:is_open()
end
return false
end
---@param sidebar outline.Sidebar
---@param opts outline.BreadcrumbOpts
---@return string
local function _get_breadcrumb(sidebar, opts)
local list = sidebar.hovered
-- TODO: Use cursor column in writer.make_outline to prevent
-- siblings being included
local names = {}
for _, node in ipairs(list) do
if node.depth > opts.depth then
break
end
names[node.depth] = node.name
end
return table.concat(names, opts.sep)
end
---@param opts outline.BreadcrumbOpts?
---@return string? breadcrumb nil if outline not open
function M.get_breadcrumb(opts)
local sidebar = M._get_sidebar()
if not sidebar then
return
end
opts = opts or {}
opts.sep = opts.sep or ' > '
if not opts.depth or opts.depth < 1 then
opts.depth = math.huge
end
return _get_breadcrumb(sidebar, opts)
end
---@param opts outline.SymbolOpts
---@return string?
function M.get_symbol(opts)
local sidebar = M._get_sidebar()
if not sidebar then
return
end
opts = opts or {}
local depth = opts.depth
if not opts.depth or opts.depth < 1 then
depth = math.huge
end
if not utils.table_has_content(sidebar.hovered) then
return ""
end
local kind
if opts.kind then
kind = require('outline.symbols').str_to_kind[opts.kind]
end
for i = #sidebar.hovered, 1, -1 do
local node = sidebar.hovered[i]
if node.depth <= depth and (not kind or node.kind == kind) then
return node.name
end
end
return ""
end
---Handle follow cursor command with bang
local function _cmd_follow_cursor(opts)
M.follow_cursor({ focus_outline = not opts.bang })

View File

@@ -28,12 +28,7 @@ end
---@param result table
local function convert_symbols(result)
local s = {}
local kinds_index = {}
-- create a inverse indexing of symbols.kind
local symbols = require('outline.symbols')
for k, v in pairs(symbols.kinds) do
kinds_index[v] = k
end
local kinds_index = require('outline.symbol').str_to_kind
-- rebuild coc.nvim symbol list hierarchy according to the 'level' key
for _, value in pairs(result) do
value.children = {}

View File

@@ -17,6 +17,7 @@ local Sidebar = {}
---@field view outline.View
---@field items outline.SymbolNode[]
---@field flats outline.FlatSymbolNode[]
---@field hovered outline.FlatSymbolNode[]
---@field original_cursor string
---@field code outline.SidebarCodeState
---@field autocmds { [integer]: integer } winnr to autocmd id
@@ -27,6 +28,7 @@ function Sidebar:new()
code = { buf = 0, win = 0 },
items = {},
flats = {},
hovered = {},
autocmds = {},
original_cursor = vim.o.guicursor,
}, { __index = Sidebar })
@@ -94,7 +96,7 @@ function Sidebar:initial_handler(response, opts)
self.items = items
local current
self.flats, current = writer.make_outline(self.view.bufnr, items, self.code.win)
self.flats, current, self.hovered = writer.make_outline(self.view.bufnr, items, self.code.win)
self:update_cursor_pos(current)
@@ -261,7 +263,7 @@ end
---@param set_cursor_to_node outline.SymbolNode|outline.FlatSymbolNode?
function Sidebar:_update_lines(update_cursor, set_cursor_to_node)
local current
self.flats, current =
self.flats, current, self.hovered =
writer.make_outline(self.view.bufnr, self.items, self.code.win, set_cursor_to_node)
if update_cursor ~= false then
self:update_cursor_pos(current)

View File

@@ -39,6 +39,12 @@ M.kinds = {
[255] = 'Macro',
}
-- inverse indexing of symbols.kind
M.str_to_kind = {}
for k, v in pairs(M.kinds) do
M.str_to_kind[v] = k
end
---@param kind string|integer
function M.icon_from_kind(kind)
local kindstr = kind

View File

@@ -93,3 +93,11 @@
---@class outline.OutlineOpts
---@field focus_outline boolean? Whether to focus on outline of after some operation. If nil, defaults to true
---@field split_command string?
---@class outline.BreadcrumbOpts
---@field depth integer?
---@field sep string?
---@class outline.SymbolOpts
---@field depth integer?
---@field kind string?

View File

@@ -68,16 +68,17 @@ end
---@param items outline.SymbolNode[] Tree of symbols after being parsed by parser.parse_result
---@param codewin integer code window
---@param find_node outline.FlatSymbolNode|outline.SymbolNode? Find a given node rather than node matching cursor position in codewin
---@return outline.FlatSymbolNode[],outline.FlatSymbolNode? flattened_items Empty table returned if bufnr is invalid
---@return outline.FlatSymbolNode[],outline.FlatSymbolNode?,outline.FlatSymbolNode[]? flattened_items Empty table returned if bufnr is invalid
function M.make_outline(bufnr, items, codewin, find_node)
if not M.is_buffer_outline(bufnr) then
return {}, nil
return {}, nil, {}
end
local codebuf = vim.api.nvim_win_get_buf(codewin)
-- 0-indexed
local hovered_line = vim.api.nvim_win_get_cursor(codewin)[1] - 1
-- Deepest matching node to put cursor on based on hovered line
local put_cursor
local hover_list = {}
clear_virt_text(bufnr)
@@ -144,6 +145,7 @@ function M.make_outline(bufnr, items, codewin, find_node)
-- XXX: not setting for children, but it works because when unfold is called
-- this function is called again anyway.
node.hovered = true
table.insert(hover_list, node)
if not find_node then
put_cursor = node
end
@@ -270,7 +272,7 @@ function M.make_outline(bufnr, items, codewin, find_node)
end
end
return flattened, put_cursor
return flattened, put_cursor, hover_list
end
-- XXX: Is the performance tradeoff of calling `nvim_buf_set_lines` on each
-- iteration worth it in order to put setting of highlights, details, and