refactor: Reorganize modules
Primarily: - Utils - Sidebar (removed the need of writer.lua) - Resolve keymaps shortcut in config eraly - Put highlight functions into highlight.lua - Put functions that do stuff on outline window into view.lua
This commit is contained in:
@@ -326,6 +326,12 @@ function M.resolve_config()
|
|||||||
if type(au.only) ~= 'number' then
|
if type(au.only) ~= 'number' then
|
||||||
au.only = (au.only and 1) or 0
|
au.only = (au.only and 1) or 0
|
||||||
end
|
end
|
||||||
|
----- KEYMAPS -----
|
||||||
|
for action, keys in pairs(M.o.keymaps) do
|
||||||
|
if type(keys) == 'string' then
|
||||||
|
M.o.keymaps[action] = { keys }
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
---Ensure l is either table, false, or nil. If not, print warning using given
|
---Ensure l is either table, false, or nil. If not, print warning using given
|
||||||
|
|||||||
@@ -1,38 +1,102 @@
|
|||||||
local M = {}
|
local M = {
|
||||||
|
ns = {
|
||||||
|
hover = vim.api.nvim_create_namespace('outline-current'),
|
||||||
|
items = vim.api.nvim_create_namespace('outline-items-highlight'),
|
||||||
|
vt = vim.api.nvim_create_namespace('outline-virt-text'),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
M.hovered_hl_ns = vim.api.nvim_create_namespace('hovered_item')
|
---Clear all highlights in buffer
|
||||||
|
---@param bufnr integer
|
||||||
function M.clear_hover_highlight(bufnr)
|
function M.clear_all_ns(bufnr)
|
||||||
vim.api.nvim_buf_clear_namespace(bufnr, M.hovered_hl_ns, 0, -1)
|
vim.api.nvim_buf_clear_namespace(bufnr, -1, 0, -1)
|
||||||
end
|
end
|
||||||
|
|
||||||
function M.add_hover_highlight(bufnr, line, col_start)
|
---Clear hover highlights in buffer
|
||||||
vim.api.nvim_buf_add_highlight(bufnr, M.hovered_hl_ns, 'OutlineCurrent', line, col_start, -1)
|
---@param bufnr integer
|
||||||
|
function M.clear_hovers(bufnr)
|
||||||
|
vim.api.nvim_buf_clear_namespace(bufnr, M.ns.hover, 0, -1)
|
||||||
end
|
end
|
||||||
|
|
||||||
local get_hl_by_name
|
---Add single hover highlights
|
||||||
|
---@param bufnr integer
|
||||||
if vim.fn.has('nvim-0.9') == 1 then
|
---@param nodes outline.FlatSymbolNode[]
|
||||||
get_hl_by_name = function(name)
|
function M.hovers(bufnr, nodes)
|
||||||
local hl = vim.api.nvim_get_hl(0, { name = name, link = false })
|
for line, node in ipairs(nodes) do
|
||||||
return { fg = hl.fg, bg = hl.bg, ctermfg = hl.ctermfg, ctermbg = hl.ctermbg }
|
if node.hovered then
|
||||||
end
|
vim.api.nvim_buf_add_highlight(bufnr, M.ns.hover, 'OutlineCurrent', line - 1, node.prefix_length, -1)
|
||||||
else
|
end
|
||||||
get_hl_by_name = function(name)
|
|
||||||
---@diagnostic disable-next-line undefined-field
|
|
||||||
local hlrgb = vim.api.nvim_get_hl_by_name(name, true)
|
|
||||||
---@diagnostic disable-next-line undefined-field
|
|
||||||
local hl = vim.api.nvim_get_hl_by_name(name, false)
|
|
||||||
return {
|
|
||||||
fg = hlrgb.foreground,
|
|
||||||
bg = hlrgb.background,
|
|
||||||
ctermfg = hl.foreground,
|
|
||||||
ctermbg = hl.background,
|
|
||||||
}
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function M.setup_highlights()
|
---Add list of highlights `hl` for outline items
|
||||||
|
---@param bufnr integer
|
||||||
|
---@param hl_list outline.HL[]
|
||||||
|
function M.items(bufnr, hl_list)
|
||||||
|
for _, h in ipairs(hl_list) do
|
||||||
|
-- stylua: ignore start
|
||||||
|
vim.api.nvim_buf_add_highlight(
|
||||||
|
bufnr, M.ns.items,
|
||||||
|
h.name, h.line - 1, h.from, h.to
|
||||||
|
)
|
||||||
|
-- stylua: ignore end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---Add details virtual text
|
||||||
|
---@param bufnr integer Outline buffer
|
||||||
|
---@param details string[] Virtual text to add
|
||||||
|
function M.details(bufnr, details)
|
||||||
|
for index, detail in ipairs(details) do
|
||||||
|
vim.api.nvim_buf_set_extmark(bufnr, M.ns.vt, index - 1, -1, {
|
||||||
|
virt_text = { { detail, 'OutlineDetails' } },
|
||||||
|
virt_text_pos = 'eol',
|
||||||
|
hl_mode = 'combine',
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---Add linenos virtual text
|
||||||
|
---@param bufnr integer Outline buffer
|
||||||
|
---@param linenos string[] Must already be padded
|
||||||
|
---@param hl_mode string Valid value for `buf_set_extmark` option `hl_mode`
|
||||||
|
function M.linenos(bufnr, linenos, hl_mode)
|
||||||
|
-- TODO: Fix lineno not appearing if text in line is truncated on the right
|
||||||
|
-- due to narrow window, after nvim fixes virt_text_hide.
|
||||||
|
for index, lineno in ipairs(linenos) do
|
||||||
|
vim.api.nvim_buf_set_extmark(bufnr, M.ns.vt, index - 1, -1, {
|
||||||
|
virt_text = { { lineno, 'OutlineLineno' } },
|
||||||
|
virt_text_pos = 'overlay',
|
||||||
|
virt_text_win_col = 0,
|
||||||
|
hl_mode = hl_mode,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---Create Outline highlights with default values if they don't already exist
|
||||||
|
function M.setup()
|
||||||
|
local get_hl_by_name
|
||||||
|
|
||||||
|
if _G._outline_nvim_has[9] then
|
||||||
|
get_hl_by_name = function(name)
|
||||||
|
local hl = vim.api.nvim_get_hl(0, { name = name, link = false })
|
||||||
|
return { fg = hl.fg, bg = hl.bg, ctermfg = hl.ctermfg, ctermbg = hl.ctermbg }
|
||||||
|
end
|
||||||
|
else
|
||||||
|
get_hl_by_name = function(name)
|
||||||
|
---@diagnostic disable-next-line undefined-field
|
||||||
|
local hlrgb = vim.api.nvim_get_hl_by_name(name, true)
|
||||||
|
---@diagnostic disable-next-line undefined-field
|
||||||
|
local hl = vim.api.nvim_get_hl_by_name(name, false)
|
||||||
|
return {
|
||||||
|
fg = hlrgb.foreground,
|
||||||
|
bg = hlrgb.background,
|
||||||
|
ctermfg = hl.foreground,
|
||||||
|
ctermbg = hl.background,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
-- Setup the OutlineCurrent highlight group if it hasn't been done already by
|
-- Setup the OutlineCurrent highlight group if it hasn't been done already by
|
||||||
-- a theme or manually set
|
-- a theme or manually set
|
||||||
if vim.fn.hlexists('OutlineCurrent') == 0 then
|
if vim.fn.hlexists('OutlineCurrent') == 0 then
|
||||||
|
|||||||
@@ -337,7 +337,7 @@ function M.setup(opts)
|
|||||||
}
|
}
|
||||||
|
|
||||||
cfg.setup(opts)
|
cfg.setup(opts)
|
||||||
highlight.setup_highlights()
|
highlight.setup()
|
||||||
|
|
||||||
setup_global_autocmd()
|
setup_global_autocmd()
|
||||||
setup_commands()
|
setup_commands()
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
local cfg = require('outline.config')
|
local cfg = require('outline.config')
|
||||||
local folding = require('outline.folding')
|
local folding = require('outline.folding')
|
||||||
local lsp_utils = require('outline.utils.lsp_utils')
|
local lsp_utils = require('outline.utils.lsp')
|
||||||
local symbols = require('outline.symbols')
|
local symbols = require('outline.symbols')
|
||||||
local t_utils = require('outline.utils.table')
|
local utils = require('outline.utils.init')
|
||||||
|
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
@@ -56,7 +56,7 @@ local function parse_result(result, depth, hierarchy, parent, bufnr)
|
|||||||
local children = nil
|
local children = nil
|
||||||
if value.children ~= nil then
|
if value.children ~= nil then
|
||||||
-- copy by value because we dont want it messing with the hir table
|
-- copy by value because we dont want it messing with the hir table
|
||||||
local child_hir = t_utils.array_copy(hir)
|
local child_hir = utils.array_copy(hir)
|
||||||
table.insert(child_hir, isLast)
|
table.insert(child_hir, isLast)
|
||||||
children = parse_result(value.children, level + 1, child_hir, node, bufnr)
|
children = parse_result(value.children, level + 1, child_hir, node, bufnr)
|
||||||
else
|
else
|
||||||
@@ -123,4 +123,66 @@ function M.preorder_iter(items, children_check)
|
|||||||
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
|
return M
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
local config = require('outline.config')
|
local config = require('outline.config')
|
||||||
local jsx = require('outline.providers.jsx')
|
local jsx = require('outline.providers.jsx')
|
||||||
local lsp_utils = require('outline.utils.lsp_utils')
|
local lsp_utils = require('outline.utils.lsp')
|
||||||
|
|
||||||
local M = {
|
local M = {
|
||||||
name = 'lsp',
|
name = 'lsp',
|
||||||
|
|||||||
@@ -4,9 +4,7 @@ local folding = require('outline.folding')
|
|||||||
local parser = require('outline.parser')
|
local parser = require('outline.parser')
|
||||||
local providers = require('outline.providers.init')
|
local providers = require('outline.providers.init')
|
||||||
local utils = require('outline.utils.init')
|
local utils = require('outline.utils.init')
|
||||||
local writer = require('outline.writer')
|
|
||||||
local symbols = require('outline.symbols')
|
local symbols = require('outline.symbols')
|
||||||
local t_utils = require('outline.utils.table')
|
|
||||||
|
|
||||||
local strlen = vim.fn.strlen
|
local strlen = vim.fn.strlen
|
||||||
|
|
||||||
@@ -105,25 +103,30 @@ function Sidebar:initial_handler(response, opts)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- stylua: ignore start
|
||||||
---Convenience function for setup_keymaps
|
---Convenience function for setup_keymaps
|
||||||
---@param cfg_name string Field in cfg.o.keymaps
|
---@param cfg_name string Field in cfg.o.keymaps
|
||||||
---@param method string|function If string, field in Sidebar
|
---@param method string|function If string, field in Sidebar
|
||||||
---@param args table Passed to method
|
---@param args table Passed to method
|
||||||
function Sidebar:nmap(cfg_name, method, args)
|
function Sidebar:nmap(cfg_name, method, args)
|
||||||
|
local keys = cfg.o.keymaps[cfg_name]
|
||||||
|
local fn
|
||||||
|
|
||||||
if type(method) == 'string' then
|
if type(method) == 'string' then
|
||||||
utils.nmap(self.view.bufnr, cfg.o.keymaps[cfg_name], function()
|
fn = function() Sidebar[method](self, unpack(args)) end
|
||||||
Sidebar[method](self, unpack(args))
|
|
||||||
end)
|
|
||||||
else
|
else
|
||||||
utils.nmap(self.view.bufnr, cfg.o.keymaps[cfg_name], function()
|
fn = function() method(unpack(args)) end
|
||||||
method(unpack(args))
|
end
|
||||||
end)
|
|
||||||
|
for _, key in ipairs(keys) do
|
||||||
|
vim.keymap.set( 'n', key, fn,
|
||||||
|
{ silent = true, noremap = true, buffer = self.view.bufnr }
|
||||||
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function Sidebar:setup_keymaps()
|
function Sidebar:setup_keymaps()
|
||||||
for name, meth in pairs({
|
for name, meth in pairs({
|
||||||
-- stylua: ignore start
|
|
||||||
goto_location = { '_goto_location', { true } },
|
goto_location = { '_goto_location', { true } },
|
||||||
peek_location = { '_goto_location', { false } },
|
peek_location = { '_goto_location', { false } },
|
||||||
restore_location = { '_map_follow_cursor', {} },
|
restore_location = { '_map_follow_cursor', {} },
|
||||||
@@ -143,12 +146,12 @@ function Sidebar:setup_keymaps()
|
|||||||
fold_all = { '_set_all_folded', { true } },
|
fold_all = { '_set_all_folded', { true } },
|
||||||
unfold_all = { '_set_all_folded', { false } },
|
unfold_all = { '_set_all_folded', { false } },
|
||||||
fold_reset = { '_set_all_folded', {} },
|
fold_reset = { '_set_all_folded', {} },
|
||||||
-- stylua: ignore end
|
|
||||||
}) do
|
}) do
|
||||||
---@diagnostic disable-next-line param-type-mismatch
|
---@diagnostic disable-next-line param-type-mismatch
|
||||||
self:nmap(name, meth[1], meth[2])
|
self:nmap(name, meth[1], meth[2])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
-- stylua: ignore end
|
||||||
|
|
||||||
---Autocmds for the (current) outline buffer
|
---Autocmds for the (current) outline buffer
|
||||||
function Sidebar:setup_buffer_autocmd()
|
function Sidebar:setup_buffer_autocmd()
|
||||||
@@ -257,12 +260,12 @@ function Sidebar:update_cursor_pos(current)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
---Calls writer.make_outline and then calls M.update_cursor_pos if
|
---Calls build_outline and then calls update_cursor_pos if update_cursor is
|
||||||
-- update_cursor is not false
|
--not false
|
||||||
---@param update_cursor boolean?
|
---@param update_cursor boolean?
|
||||||
---@param set_cursor_to_node outline.SymbolNode|outline.FlatSymbolNode?
|
---@param set_cursor_to_node outline.SymbolNode|outline.FlatSymbolNode?
|
||||||
function Sidebar:_update_lines(update_cursor, set_cursor_to_node)
|
function Sidebar:_update_lines(update_cursor, set_cursor_to_node)
|
||||||
local current = self:write_outline(set_cursor_to_node)
|
local current = self:build_outline(set_cursor_to_node)
|
||||||
if update_cursor ~= false then
|
if update_cursor ~= false then
|
||||||
self:update_cursor_pos(current)
|
self:update_cursor_pos(current)
|
||||||
end
|
end
|
||||||
@@ -299,7 +302,7 @@ end
|
|||||||
|
|
||||||
---@param items outline.SymbolNode[]
|
---@param items outline.SymbolNode[]
|
||||||
function Sidebar:_merge_items(items)
|
function Sidebar:_merge_items(items)
|
||||||
utils.merge_items_rec({ children = items }, { children = self.items })
|
parser.merge_items_rec({ children = items }, { children = self.items })
|
||||||
end
|
end
|
||||||
|
|
||||||
---Re-request symbols from provider
|
---Re-request symbols from provider
|
||||||
@@ -600,60 +603,57 @@ function Sidebar:_highlight_current_item(winnr, update_cursor)
|
|||||||
end
|
end
|
||||||
|
|
||||||
---The quintessential function of this entire plugin. Clears virtual text,
|
---The quintessential function of this entire plugin. Clears virtual text,
|
||||||
-- parses each node and replaces old lines with new lines to be written for the
|
---parses each node and replaces old lines with new lines to be written for the
|
||||||
-- outline buffer.
|
---outline buffer.
|
||||||
--
|
---
|
||||||
-- Handles highlights, virtual text, and of course lines of outline to write.
|
---Handles highlights, virtual text, and of course lines of outline to write.
|
||||||
---@note Ensure new outlines are already set to `self.items` before calling this function. `self.flats` will be overwritten and current line is obtained from `win_get_cursor` using `self.code.win`.
|
---@note Ensure new outlines are already set to `self.items` before calling
|
||||||
|
---this function. `self.flats` will be overwritten and current line is obtained
|
||||||
|
---from `win_get_cursor` using `self.code.win`.
|
||||||
---@param find_node outline.FlatSymbolNode|outline.SymbolNode? Find a given node rather than node matching cursor position in codewin
|
---@param find_node outline.FlatSymbolNode|outline.SymbolNode? Find a given node rather than node matching cursor position in codewin
|
||||||
---@return outline.FlatSymbolNode? set_cursor_to_this_node
|
---@return outline.FlatSymbolNode? set_cursor_to_this_node
|
||||||
function Sidebar:write_outline(find_node)
|
function Sidebar:build_outline(find_node)
|
||||||
-- 0-indexed
|
---@type integer 0-indexed
|
||||||
local hovered_line = vim.api.nvim_win_get_cursor(self.code.win)[1] - 1
|
local hovered_line = vim.api.nvim_win_get_cursor(self.code.win)[1] - 1
|
||||||
-- Deepest matching node to put cursor on based on hovered line
|
---@type outline.FlatSymbolNode Deepest visible matching node to set cursor
|
||||||
local put_cursor ---@type outline.FlatSymbolNode
|
local put_cursor
|
||||||
|
|
||||||
self.flats = {}
|
self.flats = {}
|
||||||
local line_count = 0
|
local line_count = 0
|
||||||
|
|
||||||
local lines = {} ---@type string[]
|
local lines = {} ---@type string[]
|
||||||
local details = {} ---@type string[]
|
local details = {} ---@type string[]
|
||||||
local linenos = {} ---@type string[]
|
local linenos = {} ---@type string[]
|
||||||
local hl = {}
|
local hl = {} ---@type outline.HL[]
|
||||||
|
|
||||||
-- Find the prefix for each line needed for the lineno space
|
-- Find the prefix for each line needed for the lineno space.
|
||||||
|
-- Use [max width of [max_line-1]] + 1 space padding.
|
||||||
|
-- -1 because if max_width is a power of ten, don't shift the entire lineno
|
||||||
|
-- column by the right just because the last line number requires an extra
|
||||||
|
-- digit. i.e.: If max_width is 1000, the lineno column will take up 3
|
||||||
|
-- columns to fill the digits, and 1 padding on the right. The 1000 can fit
|
||||||
|
-- perfectly there.
|
||||||
local lineno_offset = 0
|
local lineno_offset = 0
|
||||||
local lineno_prefix = ''
|
local lineno_prefix = ''
|
||||||
local lineno_max_width = #tostring(vim.api.nvim_buf_line_count(self.code.buf) - 1)
|
local lineno_max_width = #tostring(vim.api.nvim_buf_line_count(self.code.buf) - 1)
|
||||||
if cfg.o.outline_items.show_symbol_lineno then
|
if cfg.o.outline_items.show_symbol_lineno then
|
||||||
-- Use max width-1 plus 1 space padding.
|
|
||||||
-- -1 because if max_width is a power of ten, don't shift the entire lineno
|
|
||||||
-- column by the right just because the last line number requires an extra
|
|
||||||
-- digit. If max_width is 1000, the lineno column will take up 3 columns to
|
|
||||||
-- fill the digits, and 1 padding on the right. The 1000 can fit perfectly
|
|
||||||
-- there.
|
|
||||||
lineno_offset = math.max(2, lineno_max_width) + 1
|
lineno_offset = math.max(2, lineno_max_width) + 1
|
||||||
lineno_prefix = string.rep(' ', lineno_offset)
|
lineno_prefix = string.rep(' ', lineno_offset)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Closures for convenience
|
-- Closures for convenience
|
||||||
local function add_guide_hl(from, to)
|
-- stylua: ignore start
|
||||||
|
local function save_guide_hl(from, to)
|
||||||
table.insert(hl, {
|
table.insert(hl, {
|
||||||
line_count,
|
line = line_count, name = 'OutlineGuides',
|
||||||
from,
|
from = from, to = to,
|
||||||
to,
|
|
||||||
'OutlineGuides',
|
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
local function save_fold_hl(from, to)
|
||||||
local function add_fold_hl(from, to)
|
|
||||||
table.insert(hl, {
|
table.insert(hl, {
|
||||||
line_count,
|
line = line_count, name = 'OutlineFoldMarker',
|
||||||
from,
|
from = from, to = to,
|
||||||
to,
|
|
||||||
'OutlineFoldMarker',
|
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
-- stylua: ignore end
|
||||||
|
|
||||||
local guide_markers = cfg.o.guides.markers
|
local guide_markers = cfg.o.guides.markers
|
||||||
local fold_markers = cfg.o.symbol_folding.markers
|
local fold_markers = cfg.o.symbol_folding.markers
|
||||||
@@ -687,7 +687,7 @@ function Sidebar:write_outline(find_node)
|
|||||||
table.insert(linenos, leftpad .. lineno)
|
table.insert(linenos, leftpad .. lineno)
|
||||||
|
|
||||||
-- Make the guides for the line prefix
|
-- Make the guides for the line prefix
|
||||||
local pref = t_utils.str_to_table(string.rep(' ', node.depth))
|
local pref = utils.str_to_table(string.rep(' ', node.depth))
|
||||||
local fold_marker_width = 0
|
local fold_marker_width = 0
|
||||||
|
|
||||||
if folding.is_foldable(node) then
|
if folding.is_foldable(node) then
|
||||||
@@ -722,16 +722,15 @@ function Sidebar:write_outline(find_node)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Finished with guide prefix
|
-- Finished with guide prefix. Now join all prefix chars by a space
|
||||||
-- Join all prefix chars by a space
|
|
||||||
local pref_str = table.concat(pref, ' ')
|
local pref_str = table.concat(pref, ' ')
|
||||||
local total_pref_len = lineno_offset + #pref_str
|
local total_pref_len = lineno_offset + #pref_str
|
||||||
|
|
||||||
-- Guide hl goes from start of prefix till before the fold marker, if any.
|
-- Guide hl goes from start of prefix till before the fold marker, if any.
|
||||||
-- Fold hl goes from start of fold marker until before the icon.
|
-- Fold hl goes from start of fold marker until before the icon.
|
||||||
add_guide_hl(lineno_offset, total_pref_len - fold_marker_width)
|
save_guide_hl(lineno_offset, total_pref_len - fold_marker_width)
|
||||||
if fold_marker_width > 0 then
|
if fold_marker_width > 0 then
|
||||||
add_fold_hl(total_pref_len - fold_marker_width, total_pref_len + 1)
|
save_fold_hl(total_pref_len - fold_marker_width, total_pref_len + 1)
|
||||||
end
|
end
|
||||||
|
|
||||||
local line = lineno_prefix .. pref_str
|
local line = lineno_prefix .. pref_str
|
||||||
@@ -742,42 +741,35 @@ function Sidebar:write_outline(find_node)
|
|||||||
end
|
end
|
||||||
line = line .. ' ' .. node.name
|
line = line .. ' ' .. node.name
|
||||||
|
|
||||||
-- Highlight for the icon ✨
|
-- Start from left of icon col
|
||||||
-- Start from icon col
|
|
||||||
local hl_start = #pref_str + #lineno_prefix + icon_pref
|
local hl_start = #pref_str + #lineno_prefix + icon_pref
|
||||||
local hl_end = hl_start + #node.icon -- until after icon
|
local hl_end = hl_start + #node.icon -- until after icon
|
||||||
local hl_type = cfg.o.symbols.icons[symbols.kinds[node.kind]].hl
|
local hl_type = cfg.o.symbols.icons[symbols.kinds[node.kind]].hl
|
||||||
table.insert(hl, { line_count, hl_start, hl_end, hl_type })
|
-- stylua: ignore start
|
||||||
|
table.insert(hl, {
|
||||||
|
line = line_count, name = hl_type,
|
||||||
|
from = hl_start, to = hl_end,
|
||||||
|
})
|
||||||
|
-- stylua: ignore end
|
||||||
-- Prefix length is from start until the beginning of the node.name, used
|
-- Prefix length is from start until the beginning of the node.name, used
|
||||||
-- for hover highlights.
|
-- for hover highlights.
|
||||||
node.prefix_length = hl_end + 1
|
node.prefix_length = hl_end + 1
|
||||||
|
|
||||||
-- lines passed to nvim_buf_set_lines cannot contain newlines in each line
|
-- Each line passed to nvim_buf_set_lines cannot contain newlines
|
||||||
line = line:gsub('\n', ' ')
|
line = line:gsub('\n', ' ')
|
||||||
table.insert(lines, line)
|
table.insert(lines, line)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Render
|
-- PERF:
|
||||||
|
-- * Is setting individual lines is not as good as rewriting entire buffer?
|
||||||
writer.clear_virt_text(self.view.bufnr)
|
-- That way we can set all highlights and virtual text together without
|
||||||
writer.clear_icon_hl(self.view.bufnr)
|
-- requiring extra O(n) iterations.
|
||||||
|
-- * Is there a significant difference if new lines are set first, on top
|
||||||
vim.api.nvim_buf_set_option(self.view.bufnr, 'modifiable', true)
|
-- of old highlights, before resetting the highlights? (Rather than doing
|
||||||
vim.api.nvim_buf_set_lines(self.view.bufnr, 0, -1, false, lines)
|
-- like below)
|
||||||
vim.api.nvim_buf_set_option(self.view.bufnr, 'modifiable', false)
|
self.view:clear_all_ns()
|
||||||
|
self.view:rewrite_lines(lines)
|
||||||
-- Unfortunately highlights and extmarks cannot be added to lines that do not
|
self.view:add_hl_and_ns(hl, self.flats, details, linenos)
|
||||||
-- yet exist. Hence these require another O(n) of iteration.
|
|
||||||
writer.add_highlights(self.view.bufnr, hl, self.flats)
|
|
||||||
|
|
||||||
if cfg.o.outline_items.show_symbol_details then
|
|
||||||
writer.add_details(self.view.bufnr, details)
|
|
||||||
end
|
|
||||||
|
|
||||||
if cfg.o.outline_items.show_symbol_lineno then
|
|
||||||
writer.add_linenos(self.view.bufnr, linenos)
|
|
||||||
end
|
|
||||||
|
|
||||||
return put_cursor
|
return put_cursor
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -74,7 +74,7 @@
|
|||||||
-- HELP
|
-- HELP
|
||||||
|
|
||||||
---@class outline.HL
|
---@class outline.HL
|
||||||
---@field line integer
|
---@field line integer Line number 1-indexed
|
||||||
---@field from integer
|
---@field from integer
|
||||||
---@field to integer
|
---@field to integer
|
||||||
---@field name string
|
---@field name string
|
||||||
|
|||||||
@@ -1,13 +1,9 @@
|
|||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
---maps the table|string of keys to the action
|
---maps the table|string of keys to the action
|
||||||
---@param keys table|string
|
---@param keys table
|
||||||
---@param action function|string
|
---@param action function|string
|
||||||
function M.nmap(bufnr, keys, action)
|
function M.nmap(bufnr, keys, action)
|
||||||
if type(keys) == 'string' then
|
|
||||||
keys = { keys }
|
|
||||||
end
|
|
||||||
|
|
||||||
for _, lhs in ipairs(keys) do
|
for _, lhs in ipairs(keys) do
|
||||||
vim.keymap.set('n', lhs, action, { silent = true, noremap = true, buffer = bufnr })
|
vim.keymap.set('n', lhs, action, { silent = true, noremap = true, buffer = bufnr })
|
||||||
end
|
end
|
||||||
@@ -33,78 +29,6 @@ function M.debounce(f, delay)
|
|||||||
end
|
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)
|
function M.flash_highlight(winnr, lnum, durationMs, hl_group)
|
||||||
if durationMs == false then
|
if durationMs == false then
|
||||||
return
|
return
|
||||||
@@ -147,4 +71,49 @@ function M.str_or_nonempty_table(t)
|
|||||||
return type(t) == 'string' or M.table_has_content(t)
|
return type(t) == 'string' or M.table_has_content(t)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function M.table_to_str(t)
|
||||||
|
local ret = ''
|
||||||
|
for _, value in ipairs(t) do
|
||||||
|
ret = ret .. tostring(value)
|
||||||
|
end
|
||||||
|
return ret
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.str_to_table(str)
|
||||||
|
local t = {}
|
||||||
|
for i = 1, #str do
|
||||||
|
t[i] = str:sub(i, i)
|
||||||
|
end
|
||||||
|
return t
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Copies an array and returns it because lua usually does references
|
||||||
|
---@generic T
|
||||||
|
---@param t T[]
|
||||||
|
---@return T[]
|
||||||
|
function M.array_copy(t)
|
||||||
|
local ret = {}
|
||||||
|
for _, value in ipairs(t) do
|
||||||
|
table.insert(ret, value)
|
||||||
|
end
|
||||||
|
return ret
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Deep copy a table, deeply excluding certain keys
|
||||||
|
function M.deepcopy_excluding(t, keys)
|
||||||
|
local res = {}
|
||||||
|
|
||||||
|
for key, value in pairs(t) do
|
||||||
|
if not vim.tbl_contains(keys, key) then
|
||||||
|
if type(value) == 'table' then
|
||||||
|
res[key] = M.deepcopy_excluding(value, keys)
|
||||||
|
else
|
||||||
|
res[key] = value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return res
|
||||||
|
end
|
||||||
|
|
||||||
return M
|
return M
|
||||||
|
|||||||
@@ -1,48 +0,0 @@
|
|||||||
local M = {}
|
|
||||||
|
|
||||||
function M.table_to_str(t)
|
|
||||||
local ret = ''
|
|
||||||
for _, value in ipairs(t) do
|
|
||||||
ret = ret .. tostring(value)
|
|
||||||
end
|
|
||||||
return ret
|
|
||||||
end
|
|
||||||
|
|
||||||
function M.str_to_table(str)
|
|
||||||
local t = {}
|
|
||||||
for i = 1, #str do
|
|
||||||
t[i] = str:sub(i, i)
|
|
||||||
end
|
|
||||||
return t
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Copies an array and returns it because lua usually does references
|
|
||||||
---@generic T
|
|
||||||
---@param t T[]
|
|
||||||
---@return T[]
|
|
||||||
function M.array_copy(t)
|
|
||||||
local ret = {}
|
|
||||||
for _, value in ipairs(t) do
|
|
||||||
table.insert(ret, value)
|
|
||||||
end
|
|
||||||
return ret
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Deep copy a table, deeply excluding certain keys
|
|
||||||
function M.deepcopy_excluding(t, keys)
|
|
||||||
local res = {}
|
|
||||||
|
|
||||||
for key, value in pairs(t) do
|
|
||||||
if not vim.tbl_contains(keys, key) then
|
|
||||||
if type(value) == 'table' then
|
|
||||||
res[key] = M.deepcopy_excluding(value, keys)
|
|
||||||
else
|
|
||||||
res[key] = value
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return res
|
|
||||||
end
|
|
||||||
|
|
||||||
return M
|
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
local cfg = require('outline.config')
|
local cfg = require('outline.config')
|
||||||
|
local highlight = require('outline.highlight')
|
||||||
|
|
||||||
---@class outline.View
|
---@class outline.View
|
||||||
local View = {}
|
local View = {}
|
||||||
@@ -66,6 +67,7 @@ function View:setup_view(split_command)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---Close view window and remove winnr/bufnr fields
|
||||||
function View:close()
|
function View:close()
|
||||||
if self.winnr then
|
if self.winnr then
|
||||||
vim.api.nvim_win_close(self.winnr, true)
|
vim.api.nvim_win_close(self.winnr, true)
|
||||||
@@ -74,6 +76,7 @@ function View:close()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---Return whether view has valid buf and win numbers
|
||||||
function View:is_open()
|
function View:is_open()
|
||||||
return self.winnr
|
return self.winnr
|
||||||
and self.bufnr
|
and self.bufnr
|
||||||
@@ -81,4 +84,45 @@ function View:is_open()
|
|||||||
and vim.api.nvim_win_is_valid(self.winnr)
|
and vim.api.nvim_win_is_valid(self.winnr)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---Replace all lines in buffer with given new `lines`
|
||||||
|
---@param lines string[]
|
||||||
|
function View:rewrite_lines(lines)
|
||||||
|
vim.api.nvim_buf_set_option(self.bufnr, 'modifiable', true)
|
||||||
|
vim.api.nvim_buf_set_lines(self.bufnr, 0, -1, false, lines)
|
||||||
|
vim.api.nvim_buf_set_option(self.bufnr, 'modifiable', false)
|
||||||
|
end
|
||||||
|
|
||||||
|
function View:clear_all_ns()
|
||||||
|
highlight.clear_all_ns(self.bufnr)
|
||||||
|
end
|
||||||
|
|
||||||
|
---Ensure all existing highlights are already cleared before calling!
|
||||||
|
---@param hl outline.HL[]
|
||||||
|
---@param nodes outline.FlatSymbolNode[]
|
||||||
|
---@param details string[]
|
||||||
|
---@param linenos string[]
|
||||||
|
function View:add_hl_and_ns(hl, nodes, details, linenos)
|
||||||
|
highlight.items(self.bufnr, hl)
|
||||||
|
if cfg.o.outline_items.highlight_hovered_item then
|
||||||
|
highlight.hovers(self.bufnr, nodes)
|
||||||
|
end
|
||||||
|
if cfg.o.outline_items.show_symbol_details then
|
||||||
|
highlight.details(self.bufnr, details)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Note on hl_mode:
|
||||||
|
-- When hide_cursor + cursorline enabled, we want the lineno to also take on
|
||||||
|
-- the cursorline background so wherever the cursor is, it appears blended.
|
||||||
|
-- We want 'replace' even for `hide_cursor=false cursorline=true` because
|
||||||
|
-- vim's native line numbers do not get highlighted by cursorline.
|
||||||
|
if cfg.o.outline_items.show_symbol_lineno then
|
||||||
|
-- stylua: ignore start
|
||||||
|
highlight.linenos(
|
||||||
|
self.bufnr, linenos,
|
||||||
|
(cfg.o.outline_window.hide_cursor and 'combine') or 'replace'
|
||||||
|
)
|
||||||
|
-- stylua: ignore end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
return View
|
return View
|
||||||
|
|||||||
@@ -1,89 +0,0 @@
|
|||||||
local cfg = require('outline.config')
|
|
||||||
local highlight = require('outline.highlight')
|
|
||||||
|
|
||||||
local M = {}
|
|
||||||
|
|
||||||
local hlns = vim.api.nvim_create_namespace('outline-icon-highlight')
|
|
||||||
local vtns = vim.api.nvim_create_namespace('outline-virt-text')
|
|
||||||
|
|
||||||
---@param bufnr integer
|
|
||||||
---@return boolean
|
|
||||||
function M.is_buffer_outline(bufnr)
|
|
||||||
if not vim.api.nvim_buf_is_valid(bufnr) then
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
local name = vim.api.nvim_buf_get_name(bufnr)
|
|
||||||
local ft = vim.api.nvim_buf_get_option(bufnr, 'filetype')
|
|
||||||
return string.match(name, 'OUTLINE') ~= nil and ft == 'Outline'
|
|
||||||
end
|
|
||||||
|
|
||||||
---Apply highlights and hover highlights to bufnr
|
|
||||||
---@param bufnr integer
|
|
||||||
---@param nodes outline.FlatSymbolNode[] flattened nodes
|
|
||||||
function M.add_highlights(bufnr, hl_info, nodes)
|
|
||||||
for _, line_hl in ipairs(hl_info) do
|
|
||||||
local line, hl_start, hl_end, hl_type = unpack(line_hl)
|
|
||||||
vim.api.nvim_buf_add_highlight(bufnr, hlns, hl_type, line - 1, hl_start, hl_end)
|
|
||||||
end
|
|
||||||
M.add_hover_highlights(bufnr, nodes)
|
|
||||||
end
|
|
||||||
|
|
||||||
---@param bufnr integer Outline buffer
|
|
||||||
function M.clear_icon_hl(bufnr)
|
|
||||||
vim.api.nvim_buf_clear_namespace(bufnr, hlns, 0, -1)
|
|
||||||
end
|
|
||||||
|
|
||||||
---@param bufnr integer Outline buffer
|
|
||||||
function M.clear_virt_text(bufnr)
|
|
||||||
vim.api.nvim_buf_clear_namespace(bufnr, vtns, 0, -1)
|
|
||||||
end
|
|
||||||
|
|
||||||
---@param bufnr integer Outline buffer
|
|
||||||
---@param nodes outline.FlatSymbolNode[]
|
|
||||||
function M.add_hover_highlights(bufnr, nodes)
|
|
||||||
if not cfg.o.outline_items.highlight_hovered_item then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
-- clear old highlight
|
|
||||||
highlight.clear_hover_highlight(bufnr)
|
|
||||||
for _, node in ipairs(nodes) do
|
|
||||||
if node.hovered then
|
|
||||||
highlight.add_hover_highlight(bufnr, node.line_in_outline - 1, node.prefix_length)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
---@param bufnr integer Outline buffer
|
|
||||||
---@param details string[]
|
|
||||||
function M.add_details(bufnr, details)
|
|
||||||
for index, value in ipairs(details) do
|
|
||||||
vim.api.nvim_buf_set_extmark(bufnr, vtns, index - 1, -1, {
|
|
||||||
virt_text = { { value, 'OutlineDetails' } },
|
|
||||||
virt_text_pos = 'eol',
|
|
||||||
hl_mode = 'combine',
|
|
||||||
})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
---@param bufnr integer Outline buffer
|
|
||||||
---@param linenos string[] Must already be padded
|
|
||||||
function M.add_linenos(bufnr, linenos)
|
|
||||||
-- TODO: Fix lineno not appearing if text in line is truncated on the right
|
|
||||||
-- due to narrow window, after nvim fixes virt_text_hide.
|
|
||||||
for index, value in ipairs(linenos) do
|
|
||||||
vim.api.nvim_buf_set_extmark(bufnr, vtns, index - 1, -1, {
|
|
||||||
virt_text = { { value, 'OutlineLineno' } },
|
|
||||||
virt_text_pos = 'overlay',
|
|
||||||
virt_text_win_col = 0,
|
|
||||||
-- When hide_cursor + cursorline enabled, we want the lineno to also
|
|
||||||
-- take on the cursorline background so wherever the cursor is, it
|
|
||||||
-- appears blended. We want 'replace' even for `hide_cursor=false
|
|
||||||
-- cursorline=true` because vim's native line numbers do not get
|
|
||||||
-- highlighted by cursorline.
|
|
||||||
hl_mode = (cfg.o.outline_window.hide_cursor and 'combine') or 'replace',
|
|
||||||
})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return M
|
|
||||||
Reference in New Issue
Block a user