Files
outline.nvim/lua/outline/utils/init.lua
hedy d35ee70f95 feat: Better highlight-hover/follow-cursor procedures
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 :)
2023-11-18 09:34:16 +08:00

151 lines
3.5 KiB
Lua

local M = {}
---maps the table|string of keys to the action
---@param keys table|string
---@param action function|string
function M.nmap(bufnr, keys, action)
if type(keys) == 'string' then
keys = { keys }
end
for _, lhs in ipairs(keys) do
vim.keymap.set('n', lhs, action, { silent = true, noremap = true, buffer = bufnr })
end
end
--- @param f function
--- @param delay number
--- @return function
function M.debounce(f, delay)
local timer = vim.loop.new_timer()
return function(...)
local args = { ... }
timer:start(
delay,
0,
vim.schedule_wrap(function()
timer:stop()
f(unpack(args))
end)
)
end
end
function M.items_dfs(callback, children)
for _, val in ipairs(children) do
callback(val)
if val.children then
M.items_dfs(callback, val.children)
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
function M.flash_highlight(winnr, lnum, durationMs, hl_group)
if durationMs == false then
return
end
hl_group = hl_group or 'Visual'
if durationMs == true or durationMs == 1 then
durationMs = 400
end
local bufnr = vim.api.nvim_win_get_buf(winnr)
local ns = vim.api.nvim_buf_add_highlight(bufnr, 0, hl_group, lnum - 1, 0, -1)
local remove_highlight = function()
pcall(vim.api.nvim_buf_clear_namespace, bufnr, ns, 0, -1)
end
vim.defer_fn(remove_highlight, durationMs)
end
---@param module string Used as message if second param omitted
---@param message string?
function M.echo(module, message)
if not message then
message = module
module = ''
end
local prefix = 'outline'
if module ~= '' then
prefix = prefix .. '.' .. module
end
local prefix_chunk = { '(' .. prefix .. ') ', 'WarningMsg' }
-- For now we don't echo much, so add all to history
vim.api.nvim_echo({ prefix_chunk, { message } }, true, {})
end
---@param t table
function M.table_has_content(t)
return t and next(t) ~= nil
end
---@param t table|string
function M.str_or_nonempty_table(t)
return type(t) == 'string' or M.table_has_content(t)
end
return M