Previously on each outline open, the `writer.make_outline` function might be called at least 4 times(!), after this refactor it will only be called once. And on update cursor autocmds, also called once (previously at least twice). behaviour: - Now the outline window focus and highlight can update on each cursor move (previously CursorHold, dependent on updatetime). This is now configurable as well. - During fold-all/unfold-all operations, now the cursor will remain on the same node (rather than same line in outline buffer). - The performance improvement is not significantly observable since even the old implementation can appear instant. One may even argue I am fixing a problem that did not exist, but implementation-wise it's just so much better now. config: - outline_window.auto_update_events, list of events to be passed to create_user_autocmd for updating cursor focus in outline, and updating outline items (refetching symbols), using keys cursor and items respectively. - outline_window.show_cursorline now supports 2 other string values: 'focus_in_outline'/'focus_in_code' which controls when to enable cursorline. Setting to true retains the default behaviour of always showing the cursorline. This was added because now that the cursor focus on the outline could change on each CursorMoved, the cursorline may pose to be qute attention-seeking during the outline cursor updates. Hence `focus_in_outline` is added so that when focus is in code, the cursorline for outline window is not shown. 'focus_in_code' is added so that a user who disabled highlight_hovered_item can keep track of position in outline when focus is in code, disabling cursorline when focus is in outline. At any given time, if hide cursor is enabled and show_cursorline is a string value, hiding of cursor will not be done if cursorline is not shown in the the given situation. implementation: - The reason for the improvement in performance as described in the first paragraph is due to merging of finding hover item and finding the deepest matched node to put cursor, into writer.make_outline. This done, when previously done in separate function, because after the separate function (namely _highlight_hovered_item) finishes, writer.make_outline is called *again* anyway. - Autocmds to update cursor position in outline is now done per buffer rather than global. Somehow the auto unfold and unfold depth options still work perfectly, for this we should thank simrat or which ever contributor that modularized the folding module and made it adaptable :)
700 lines
20 KiB
Lua
700 lines
20 KiB
Lua
local View = require('outline.view')
|
|
local cfg = require('outline.config')
|
|
local folding = require('outline.folding')
|
|
local parser = require('outline.parser')
|
|
local providers = require('outline.providers.init')
|
|
local ui = require('outline.ui')
|
|
local utils = require('outline.utils.init')
|
|
local writer = require('outline.writer')
|
|
|
|
local M = {}
|
|
|
|
local function setup_global_autocmd()
|
|
if utils.table_has_content(cfg.o.outline_window.auto_update_events.items) then
|
|
vim.api.nvim_create_autocmd(cfg.o.outline_window.auto_update_events.items, {
|
|
pattern = '*',
|
|
callback = M._refresh,
|
|
})
|
|
end
|
|
vim.api.nvim_create_autocmd('WinEnter', {
|
|
pattern = '*',
|
|
callback = require('outline.preview').close,
|
|
})
|
|
end
|
|
|
|
-------------------------
|
|
-- STATE
|
|
-------------------------
|
|
M.state = {
|
|
opened_first_outline = false,
|
|
---@type outline.SymbolNode[]
|
|
outline_items = {},
|
|
---@type outline.FlatSymbolNode[]
|
|
flattened_outline_items = {},
|
|
code_win = 0,
|
|
autocmds = {},
|
|
-- In case unhide_cursor was called before hide_cursor for _some_ reason,
|
|
-- this can still be used as a fallback
|
|
original_cursor = vim.o.guicursor,
|
|
}
|
|
|
|
local function wipe_state()
|
|
M.state = {
|
|
outline_items = {},
|
|
flattened_outline_items = {},
|
|
code_win = 0,
|
|
autocmds = {},
|
|
opts = {},
|
|
}
|
|
end
|
|
|
|
local function _update_lines(update_cursor, set_cursor_to_node)
|
|
local current
|
|
M.state.flattened_outline_items, current =
|
|
writer.make_outline(M.view.bufnr, M.state.outline_items, M.state.code_win, set_cursor_to_node)
|
|
if update_cursor ~= false then
|
|
M.update_cursor_pos(current)
|
|
end
|
|
end
|
|
|
|
---@param items outline.SymbolNode[]
|
|
local function _merge_items(items)
|
|
utils.merge_items_rec({ children = items }, { children = M.state.outline_items })
|
|
end
|
|
|
|
local function __refresh()
|
|
local current_buffer_is_outline = M.view.bufnr == vim.api.nvim_get_current_buf()
|
|
if M.view:is_open() and not current_buffer_is_outline then
|
|
local function refresh_handler(response)
|
|
if response == nil or type(response) ~= 'table' then
|
|
return
|
|
end
|
|
|
|
local curwin = vim.api.nvim_get_current_win()
|
|
|
|
if M.state.codewin ~= curwin then
|
|
if M.state.autocmds[M.state.codewin] then
|
|
vim.api.nvim_del_autocmd(M.state.autocmds[M.state.codewin])
|
|
end
|
|
M.state.codewin = curwin
|
|
end
|
|
|
|
if cfg.o.outline_items.highlight_hovered_item or cfg.o.symbol_folding.auto_unfold_hover then
|
|
if M.state.autocmds[M.state.code_win] then
|
|
vim.api.nvim_del_autocmd(M.state.autocmds[M.state.code_win])
|
|
end
|
|
if utils.str_or_nonempty_table(cfg.o.outline_window.auto_update_events.cursor) then
|
|
M.state.autocmds[M.state.code_win] = vim.api.nvim_create_autocmd(
|
|
cfg.o.outline_window.auto_update_events.cursor,
|
|
{
|
|
buffer = vim.api.nvim_win_get_buf(M.state.code_win),
|
|
callback = function() M._highlight_current_item(nil) end
|
|
}
|
|
)
|
|
end
|
|
end
|
|
|
|
local items = parser.parse(response, vim.api.nvim_get_current_buf())
|
|
_merge_items(items)
|
|
|
|
_update_lines()
|
|
end
|
|
|
|
providers.request_symbols(refresh_handler)
|
|
end
|
|
end
|
|
|
|
M._refresh = utils.debounce(__refresh, 100)
|
|
|
|
---@return outline.FlatSymbolNode
|
|
function M._current_node()
|
|
local current_line = vim.api.nvim_win_get_cursor(M.view.winnr)[1]
|
|
return M.state.flattened_outline_items[current_line]
|
|
end
|
|
|
|
---@param change_focus boolean
|
|
function M.__goto_location(change_focus)
|
|
local node = M._current_node()
|
|
vim.api.nvim_win_set_cursor(M.state.code_win, { node.line + 1, node.character })
|
|
if cfg.o.outline_window.center_on_jump then
|
|
vim.fn.win_execute(M.state.code_win, "normal! zz")
|
|
end
|
|
|
|
if vim.fn.hlexists('OutlineJumpHighlight') == 0 then
|
|
vim.api.nvim_set_hl(0, 'OutlineJumpHighlight', { link = 'Visual' })
|
|
end
|
|
utils.flash_highlight(
|
|
M.state.code_win,
|
|
node.line + 1,
|
|
cfg.o.outline_window.jump_highlight_duration,
|
|
'OutlineJumpHighlight'
|
|
)
|
|
|
|
if change_focus then
|
|
vim.fn.win_gotoid(M.state.code_win)
|
|
end
|
|
end
|
|
|
|
---Wraps __goto_location and handles auto_close.
|
|
---@see __goto_location
|
|
---@param change_focus boolean
|
|
function M._goto_location(change_focus)
|
|
M.__goto_location(change_focus)
|
|
if change_focus and cfg.o.outline_window.auto_close then
|
|
M.close_outline()
|
|
end
|
|
end
|
|
|
|
function M._goto_and_close()
|
|
M.__goto_location(true)
|
|
M.close_outline()
|
|
end
|
|
|
|
---@param direction "up"|"down"
|
|
function M._move_and_jump(direction)
|
|
local move = direction == 'down' and 1 or -1
|
|
local cur = vim.api.nvim_win_get_cursor(0)
|
|
cur[1] = cur[1] + move
|
|
pcall(vim.api.nvim_win_set_cursor, 0, cur)
|
|
M.__goto_location(false)
|
|
end
|
|
|
|
---@param move_cursor boolean
|
|
---@param node_index integer Index for M.state.flattened_outline_items
|
|
function M._toggle_fold(move_cursor, node_index)
|
|
local node = M.state.flattened_outline_items[node_index] or M._current_node()
|
|
local is_folded = folding.is_folded(node)
|
|
|
|
if folding.is_foldable(node) then
|
|
M._set_folded(not is_folded, move_cursor, node_index)
|
|
end
|
|
end
|
|
|
|
local function update_cursor_style()
|
|
local cl = cfg.o.outline_window.show_cursorline
|
|
-- XXX: Still 'hide' cursor if show_cursorline set to false, because we've
|
|
-- already warned the user during setup.
|
|
local hide_cursor = type(cl) ~= 'string'
|
|
|
|
if cl == 'focus_in_outline' or cl == 'focus_in_code' then
|
|
vim.api.nvim_win_set_option(0, "cursorline", cl == 'focus_in_outline')
|
|
hide_cursor = cl == 'focus_in_outline'
|
|
end
|
|
|
|
-- Set cursor color to CursorLine in normal mode
|
|
if hide_cursor then
|
|
M.state.original_cursor = vim.o.guicursor
|
|
local cur = vim.o.guicursor:match('n.-:(.-)[-,]')
|
|
vim.opt.guicursor:append('n:' .. cur .. '-Cursorline')
|
|
end
|
|
end
|
|
|
|
local function reset_cursor_style()
|
|
local cl = cfg.o.outline_window.show_cursorline
|
|
|
|
if cl == 'focus_in_outline' or cl == 'focus_in_code' then
|
|
vim.api.nvim_win_set_option(0, "cursorline", cl ~= 'focus_in_outline')
|
|
end
|
|
-- vim.opt doesn't seem to provide a way to remove last item, like a pop()
|
|
-- vim.o.guicursor = vim.o.guicursor:gsub(",n.-:.-$", "")
|
|
vim.o.guicursor = M.state.original_cursor
|
|
end
|
|
|
|
---Autocmds for the (current) outline buffer
|
|
local function setup_buffer_autocmd()
|
|
if cfg.o.preview_window.auto_preview then
|
|
vim.api.nvim_create_autocmd('CursorMoved', {
|
|
buffer = 0,
|
|
callback = require('outline.preview').show,
|
|
})
|
|
else
|
|
vim.api.nvim_create_autocmd('CursorMoved', {
|
|
buffer = 0,
|
|
callback = require('outline.preview').close,
|
|
})
|
|
end
|
|
if cfg.o.outline_window.auto_jump then
|
|
vim.api.nvim_create_autocmd('CursorMoved', {
|
|
buffer = 0,
|
|
callback = function()
|
|
-- Don't use _goto_location because we don't want to auto-close
|
|
M.__goto_location(false)
|
|
end,
|
|
})
|
|
end
|
|
if cfg.o.outline_window.hide_cursor or type(cfg.o.outline_window.show_cursorline) == 'string' then
|
|
-- Unfortunately guicursor is a global option, so we have to make sure to
|
|
-- set and unset when cursor leaves the outline window.
|
|
update_cursor_style()
|
|
vim.api.nvim_create_autocmd('BufEnter', {
|
|
buffer = 0,
|
|
callback = update_cursor_style,
|
|
})
|
|
vim.api.nvim_create_autocmd('BufLeave', {
|
|
buffer = 0,
|
|
callback = reset_cursor_style,
|
|
})
|
|
end
|
|
end
|
|
|
|
---@param folded boolean
|
|
---@param move_cursor? boolean
|
|
---@param node_index? integer
|
|
function M._set_folded(folded, move_cursor, node_index)
|
|
local node = M.state.flattened_outline_items[node_index] or M._current_node()
|
|
local changed = (folded ~= folding.is_folded(node))
|
|
|
|
if folding.is_foldable(node) and changed then
|
|
node.folded = folded
|
|
|
|
if move_cursor then
|
|
vim.api.nvim_win_set_cursor(M.view.winnr, { node_index, 0 })
|
|
end
|
|
|
|
_update_lines(false)
|
|
elseif node.parent then
|
|
local parent_node = M.state.flattened_outline_items[node.parent.line_in_outline]
|
|
|
|
if parent_node then
|
|
M._set_folded(folded, not parent_node.folded and folded, parent_node.line_in_outline)
|
|
end
|
|
end
|
|
end
|
|
|
|
---@param nodes outline.SymbolNode[]
|
|
function M._toggle_all_fold(nodes)
|
|
nodes = nodes or M.state.outline_items
|
|
local folded = true
|
|
|
|
for _, node in ipairs(nodes) do
|
|
if folding.is_foldable(node) and not folding.is_folded(node) then
|
|
folded = false
|
|
break
|
|
end
|
|
end
|
|
|
|
M._set_all_folded(not folded, nodes)
|
|
end
|
|
|
|
---@param folded boolean|nil
|
|
---@param nodes? outline.SymbolNode[]
|
|
function M._set_all_folded(folded, nodes)
|
|
local stack = { nodes or M.state.outline_items }
|
|
local current = M._current_node()
|
|
|
|
while #stack > 0 do
|
|
local current_nodes = table.remove(stack, #stack)
|
|
for _, node in ipairs(current_nodes) do
|
|
node.folded = folded
|
|
if node.children then
|
|
stack[#stack + 1] = node.children
|
|
end
|
|
end
|
|
end
|
|
|
|
_update_lines(true, current)
|
|
end
|
|
|
|
---@param winnr? integer Window number of code window
|
|
function M._highlight_current_item(winnr)
|
|
local has_provider = M.has_provider()
|
|
local has_outline_open = M.view:is_open()
|
|
local current_buffer_is_outline = M.view.bufnr == vim.api.nvim_get_current_buf()
|
|
|
|
if not has_provider then
|
|
return
|
|
end
|
|
|
|
if current_buffer_is_outline and not winnr then
|
|
-- Don't update cursor pos and content if they are navigating the outline.
|
|
-- Winnr may be given when user explicitly wants to restore location
|
|
-- (follow_cursor), or through the open handler.
|
|
return
|
|
end
|
|
|
|
if not has_outline_open and not winnr then
|
|
-- Outline not open and no code window given
|
|
return
|
|
end
|
|
|
|
-- TODO: Find an efficient way to:
|
|
-- 1) Set highlight for all nodes in range (regardless of visibility)
|
|
-- 2) Find the line number of the deepest node in range, that is visible (no
|
|
-- parents folded)
|
|
-- In one go
|
|
|
|
-- XXX: Could current win ~= M.state.codewin here?
|
|
|
|
_update_lines(true)
|
|
end
|
|
|
|
local function setup_keymaps(bufnr)
|
|
local map = function(...)
|
|
utils.nmap(bufnr, ...)
|
|
end
|
|
-- goto_location of symbol and focus that window
|
|
map(cfg.o.keymaps.goto_location, function()
|
|
M._goto_location(true)
|
|
end)
|
|
-- goto_location of symbol but stay in outline
|
|
map(cfg.o.keymaps.peek_location, function()
|
|
M._goto_location(false)
|
|
end)
|
|
-- Navigate to corresponding outline location for current code location
|
|
map(cfg.o.keymaps.restore_location, M._map_follow_cursor)
|
|
-- Navigate to corresponding outline location for current code location
|
|
map(cfg.o.keymaps.goto_and_close, M._goto_and_close)
|
|
-- Move down/up in outline and peek that location in code
|
|
map(cfg.o.keymaps.down_and_jump, function()
|
|
M._move_and_jump('down')
|
|
end)
|
|
-- Move down/up in outline and peek that location in code
|
|
map(cfg.o.keymaps.up_and_jump, function()
|
|
M._move_and_jump('up')
|
|
end)
|
|
-- hover symbol
|
|
map(cfg.o.keymaps.hover_symbol, require('outline.hover').show_hover)
|
|
-- preview symbol
|
|
map(cfg.o.keymaps.toggle_preview, require('outline.preview').toggle)
|
|
-- rename symbol
|
|
map(cfg.o.keymaps.rename_symbol, require('outline.rename').rename)
|
|
-- code actions
|
|
map(cfg.o.keymaps.code_actions, require('outline.code_action').show_code_actions)
|
|
-- show help
|
|
map(cfg.o.keymaps.show_help, require('outline.config').show_help)
|
|
-- close outline
|
|
map(cfg.o.keymaps.close, function()
|
|
M.view:close()
|
|
end)
|
|
-- toggle fold selection
|
|
map(cfg.o.keymaps.fold_toggle, M._toggle_fold)
|
|
-- fold selection
|
|
map(cfg.o.keymaps.fold, function()
|
|
M._set_folded(true)
|
|
end)
|
|
-- unfold selection
|
|
map(cfg.o.keymaps.unfold, function()
|
|
M._set_folded(false)
|
|
end)
|
|
-- toggle fold all
|
|
map(cfg.o.keymaps.fold_toggle_all, M._toggle_all_fold)
|
|
-- fold all
|
|
map(cfg.o.keymaps.fold_all, function()
|
|
M._set_all_folded(true)
|
|
end)
|
|
-- unfold all
|
|
map(cfg.o.keymaps.unfold_all, function()
|
|
M._set_all_folded(false)
|
|
end)
|
|
-- fold reset
|
|
map(cfg.o.keymaps.fold_reset, function()
|
|
M._set_all_folded(nil)
|
|
end)
|
|
end
|
|
|
|
---@param current outline.FlatSymbolNode?
|
|
function M.update_cursor_pos(current)
|
|
local col = 0
|
|
local buf = vim.api.nvim_win_get_buf(M.state.code_win)
|
|
if cfg.o.outline_items.show_symbol_lineno then
|
|
-- Padding area between lineno column and start of guides
|
|
col = #tostring(vim.api.nvim_buf_line_count(buf) - 1)
|
|
end
|
|
if current then -- Don't attempt to set cursor if the matching node is not found
|
|
vim.api.nvim_win_set_cursor(M.view.winnr, { current.line_in_outline, col })
|
|
end
|
|
end
|
|
|
|
---@param response table?
|
|
---@param opts outline.OutlineOpts?
|
|
local function handler(response, opts)
|
|
if response == nil or type(response) ~= 'table' or M.view:is_open() then
|
|
return
|
|
end
|
|
|
|
M.state.code_win = vim.api.nvim_get_current_win()
|
|
M.state.opened_first_outline = true
|
|
|
|
if opts and opts.on_symbols then
|
|
opts.on_symbols()
|
|
end
|
|
|
|
M.view:setup_view()
|
|
|
|
if opts and opts.on_outline_setup then
|
|
opts.on_outline_setup()
|
|
end
|
|
|
|
if cfg.o.outline_items.highlight_hovered_item or cfg.o.symbol_folding.auto_unfold_hover then
|
|
if M.state.autocmds[M.state.code_win] then
|
|
vim.api.nvim_del_autocmd(M.state.autocmds[M.state.code_win])
|
|
end
|
|
if utils.str_or_nonempty_table(cfg.o.outline_window.auto_update_events.cursor) then
|
|
M.state.autocmds[M.state.code_win] = vim.api.nvim_create_autocmd(
|
|
cfg.o.outline_window.auto_update_events.cursor,
|
|
{
|
|
buffer = vim.api.nvim_win_get_buf(M.state.code_win),
|
|
callback = function() M._highlight_current_item(nil) end
|
|
}
|
|
)
|
|
end
|
|
end
|
|
|
|
-- clear state when buffer is closed
|
|
vim.api.nvim_buf_attach(M.view.bufnr, false, {
|
|
on_detach = function(_, _)
|
|
wipe_state()
|
|
end,
|
|
})
|
|
|
|
setup_keymaps(M.view.bufnr)
|
|
setup_buffer_autocmd()
|
|
|
|
local items = parser.parse(response, vim.api.nvim_win_get_buf(M.state.code_win))
|
|
|
|
M.state.outline_items = items
|
|
local current
|
|
M.state.flattened_outline_items, current = writer.make_outline(M.view.bufnr, items, M.state.code_win)
|
|
|
|
M.update_cursor_pos(current)
|
|
|
|
if not cfg.o.outline_window.focus_on_open or (opts and not opts.focus_outline) then
|
|
vim.fn.win_gotoid(M.state.code_win)
|
|
end
|
|
end
|
|
|
|
---Set position of outline window to match cursor position in code, return
|
|
---whether the window is just newly opened (previously not open).
|
|
---@param opts outline.OutlineOpts? Field `focus_outline` = `false` or `nil` means don't focus on outline window after following cursor. If opts is not provided, focus will be on outline window after following cursor.
|
|
---@return boolean ok Whether it was successful. If ok=false, either the outline window is not open or the code window cannot be found.
|
|
function M.follow_cursor(opts)
|
|
if not M.view:is_open() then
|
|
return false
|
|
end
|
|
|
|
if require('outline.preview').has_code_win() then
|
|
M._highlight_current_item(M.state.code_win)
|
|
else
|
|
return false
|
|
end
|
|
|
|
if not opts then
|
|
opts = { focus_outline = true }
|
|
end
|
|
if opts.focus_outline then
|
|
M.focus_outline()
|
|
end
|
|
|
|
return true
|
|
end
|
|
|
|
local function _cmd_follow_cursor(opts)
|
|
local fnopts = { focus_outline = true }
|
|
if opts.bang then
|
|
fnopts.focus_outline = false
|
|
end
|
|
M.follow_cursor(fnopts)
|
|
end
|
|
|
|
function M._map_follow_cursor()
|
|
if not M.follow_cursor({ focus_outline = true }) then
|
|
utils.echo('Code window no longer active. Try closing and reopening the outline.')
|
|
end
|
|
end
|
|
|
|
---Toggle the outline window, and return whether the outline window is open
|
|
---after this operation.
|
|
---@see open_outline
|
|
---@param opts outline.OutlineOpts? Table of options
|
|
---@return boolean is_open Whether outline window is open
|
|
function M.toggle_outline(opts)
|
|
if M.view:is_open() then
|
|
M.close_outline()
|
|
return false
|
|
else
|
|
M.open_outline(opts)
|
|
return true
|
|
end
|
|
end
|
|
|
|
local function _cmd_open_with_mods(fn)
|
|
return function(opts)
|
|
local old_sc, use_old_sc
|
|
local split = opts.smods.split
|
|
if split ~= '' then
|
|
old_sc = cfg.o.outline_window.split_command
|
|
use_old_sc = true
|
|
cfg.o.outline_window.split_command = split .. ' vsplit'
|
|
end
|
|
|
|
local function on_outline_setup()
|
|
if use_old_sc then
|
|
cfg.o.outline_window.split_command = old_sc
|
|
-- the old option should already have been resolved during set up
|
|
end
|
|
end
|
|
|
|
if opts.bang then
|
|
fn({ focus_outline = false, on_outline_setup = on_outline_setup })
|
|
else
|
|
fn({ focus_outline = true, on_outline_setup = on_outline_setup })
|
|
end
|
|
end
|
|
end
|
|
|
|
---Open the outline window.
|
|
---@param opts outline.OutlineOpts? Field focus_outline=false means don't focus on outline window after opening. If opts is not provided, focus will be on outline window after opening.
|
|
function M.open_outline(opts)
|
|
if not opts then
|
|
opts = { focus_outline = true }
|
|
end
|
|
if not M.view:is_open() then
|
|
local found = providers.request_symbols(handler, opts)
|
|
if not found then
|
|
utils.echo('No providers found for current buffer')
|
|
end
|
|
end
|
|
end
|
|
|
|
---Close the outline window.
|
|
function M.close_outline()
|
|
M.view:close()
|
|
end
|
|
|
|
---Set cursor to focus on the outline window, return whether the window is currently open..
|
|
---@return boolean is_open Whether the window is open
|
|
function M.focus_outline()
|
|
if M.view:is_open() then
|
|
vim.fn.win_gotoid(M.view.winnr)
|
|
return true
|
|
end
|
|
return false
|
|
end
|
|
|
|
---Set cursor to focus on the code window, return whether this operation was successful.
|
|
---@return boolean ok Whether it was successful. If unsuccessful, it might mean that the attached code window has been closed or is no longer valid.
|
|
function M.focus_code()
|
|
if require('outline.preview').has_code_win() then
|
|
vim.fn.win_gotoid(M.state.code_win)
|
|
return true
|
|
end
|
|
return false
|
|
end
|
|
|
|
---Toggle focus between outline and code window, returns whether it was successful.
|
|
---@return boolean ok Whether it was successful. If `ok=false`, either the outline window is not open or the code window is no longer valid.
|
|
function M.focus_toggle()
|
|
if M.view:is_open() and require('outline.preview').has_code_win() then
|
|
local winid = vim.fn.win_getid()
|
|
if winid == M.state.code_win then
|
|
vim.fn.win_gotoid(M.view.winnr)
|
|
else
|
|
vim.fn.win_gotoid(M.state.code_win)
|
|
end
|
|
return true
|
|
end
|
|
return false
|
|
end
|
|
|
|
---Whether the outline window is currently open.
|
|
---@return boolean is_open
|
|
function M.is_open()
|
|
return M.view:is_open()
|
|
end
|
|
|
|
---Display outline window status in the message area.
|
|
function M.show_status()
|
|
-- TODO: Use a floating window instead
|
|
local p = _G._outline_current_provider
|
|
if not M.is_active() then
|
|
p = providers.find_provider()
|
|
end
|
|
|
|
if p ~= nil then
|
|
print('Current provider: ' .. p.name)
|
|
if p.get_status then
|
|
print(p.get_status())
|
|
print()
|
|
end
|
|
|
|
if M.view:is_open() then
|
|
print('Outline window is open.')
|
|
else
|
|
print('Outline window is not open.')
|
|
end
|
|
|
|
if require('outline.preview').has_code_win() then
|
|
print('Code window is active.')
|
|
else
|
|
print('Code window is either closed or invalid. Please close and reopen the outline window.')
|
|
end
|
|
else
|
|
print('No providers')
|
|
end
|
|
end
|
|
|
|
function M.is_active()
|
|
local winid = vim.fn.win_getid()
|
|
if M.view:is_open() and winid == M.view.winnr then
|
|
return true
|
|
end
|
|
return false
|
|
end
|
|
|
|
---Whether there is currently an available provider.
|
|
---@return boolean has_provider
|
|
function M.has_provider()
|
|
if M.is_active() then
|
|
return _G._outline_current_provider ~= nil
|
|
end
|
|
return providers.has_provider()
|
|
end
|
|
|
|
local function setup_commands()
|
|
local cmd = function(n, c, o)
|
|
vim.api.nvim_create_user_command('Outline' .. n, c, o)
|
|
end
|
|
|
|
cmd('', _cmd_open_with_mods(M.toggle_outline), {
|
|
desc = 'Toggle the outline window. \
|
|
With bang, keep focus on initial window after opening.',
|
|
nargs = 0,
|
|
bang = true,
|
|
})
|
|
cmd('Open', _cmd_open_with_mods(M.open_outline), {
|
|
desc = 'With bang, keep focus on initial window after opening.',
|
|
nargs = 0,
|
|
bang = true,
|
|
})
|
|
cmd('Close', M.close_outline, { nargs = 0 })
|
|
cmd('FocusOutline', M.focus_outline, { nargs = 0 })
|
|
cmd('FocusCode', M.focus_code, { nargs = 0 })
|
|
cmd('Focus', M.focus_toggle, { nargs = 0 })
|
|
cmd('Status', M.show_status, {
|
|
desc = 'Show a message about the current status of the outline window.',
|
|
nargs = 0,
|
|
})
|
|
cmd('Follow', _cmd_follow_cursor, {
|
|
desc = "Update position of outline with position of cursor. \
|
|
With bang, don't switch cursor focus to outline window.",
|
|
nargs = 0,
|
|
bang = true,
|
|
})
|
|
cmd('Refresh', __refresh, {
|
|
desc = "Trigger manual outline refresh of items.",
|
|
nargs = 0,
|
|
})
|
|
end
|
|
|
|
---Set up configuration options for outline.
|
|
function M.setup(opts)
|
|
cfg.setup(opts)
|
|
ui.setup_highlights()
|
|
|
|
M.view = View:new()
|
|
setup_global_autocmd()
|
|
setup_commands()
|
|
end
|
|
|
|
return M
|