diff --git a/README.md b/README.md index 5f2c76b..4a7b23e 100644 --- a/README.md +++ b/README.md @@ -790,6 +790,25 @@ require'outline' This is automatically called on events `outline_items.auto_update_events.refresh` from config. +- **get_breadcrumb(opts)** + + Return a string concatenated from hovered symbols hierarchy representing code + location. + + Optional opts table fields: + - depth (nil): Maximum depth of the last symbol included. First item has depth 1. + Set to 0 or nil to include all + - sep (` > `): String for separator + +- **get_symbol(opts)** + + Return the symbol name of the deepest hovered symbol representing code + location. + + Optional opts table fields: + - depth (nil): Maximum depth of the returned symbol + - kind (nil): Symbol kind to search for (string). + ## Tips diff --git a/lua/outline/init.lua b/lua/outline/init.lua index e072cad..4b7a81d 100644 --- a/lua/outline/init.lua +++ b/lua/outline/init.lua @@ -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 }) diff --git a/lua/outline/providers/coc.lua b/lua/outline/providers/coc.lua index fa4ad4b..08538ce 100644 --- a/lua/outline/providers/coc.lua +++ b/lua/outline/providers/coc.lua @@ -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 = {} diff --git a/lua/outline/sidebar.lua b/lua/outline/sidebar.lua index dd87466..e6296b6 100644 --- a/lua/outline/sidebar.lua +++ b/lua/outline/sidebar.lua @@ -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) diff --git a/lua/outline/symbols.lua b/lua/outline/symbols.lua index 03d43f9..9547d5d 100644 --- a/lua/outline/symbols.lua +++ b/lua/outline/symbols.lua @@ -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 diff --git a/lua/outline/types/outline.lua b/lua/outline/types/outline.lua index 158fb03..9243ca1 100644 --- a/lua/outline/types/outline.lua +++ b/lua/outline/types/outline.lua @@ -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? diff --git a/lua/outline/writer.lua b/lua/outline/writer.lua index 3d80763..8a9bf85 100644 --- a/lua/outline/writer.lua +++ b/lua/outline/writer.lua @@ -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