diff --git a/lua/outline/config.lua b/lua/outline/config.lua index 0d8988c..d1f3f6b 100644 --- a/lua/outline/config.lua +++ b/lua/outline/config.lua @@ -326,6 +326,12 @@ function M.resolve_config() if type(au.only) ~= 'number' then au.only = (au.only and 1) or 0 end + ----- KEYMAPS ----- + for action, keys in pairs(M.o.keymaps) do + if type(keys) == 'string' then + M.o.keymaps[action] = { keys } + end + end end ---Ensure l is either table, false, or nil. If not, print warning using given diff --git a/lua/outline/highlight.lua b/lua/outline/highlight.lua index c9f2401..d95f310 100644 --- a/lua/outline/highlight.lua +++ b/lua/outline/highlight.lua @@ -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_all_ns(bufnr) + vim.api.nvim_buf_clear_namespace(bufnr, -1, 0, -1) +end -function M.clear_hover_highlight(bufnr) - vim.api.nvim_buf_clear_namespace(bufnr, M.hovered_hl_ns, 0, -1) +---Clear hover highlights in buffer +---@param bufnr integer +function M.clear_hovers(bufnr) + vim.api.nvim_buf_clear_namespace(bufnr, M.ns.hover, 0, -1) end -function M.add_hover_highlight(bufnr, line, col_start) - vim.api.nvim_buf_add_highlight(bufnr, M.hovered_hl_ns, 'OutlineCurrent', line, col_start, -1) +---Add single hover highlights +---@param bufnr integer +---@param nodes outline.FlatSymbolNode[] +function M.hovers(bufnr, nodes) + for line, node in ipairs(nodes) do + if node.hovered then + vim.api.nvim_buf_add_highlight(bufnr, M.ns.hover, 'OutlineCurrent', line - 1, node.prefix_length, -1) + end + end end -local get_hl_by_name +---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 -if vim.fn.has('nvim-0.9') == 1 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 } +---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 -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 + +---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 -function M.setup_highlights() +---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 -- a theme or manually set if vim.fn.hlexists('OutlineCurrent') == 0 then diff --git a/lua/outline/init.lua b/lua/outline/init.lua index 43b3832..bd1f4f6 100644 --- a/lua/outline/init.lua +++ b/lua/outline/init.lua @@ -337,7 +337,7 @@ function M.setup(opts) } cfg.setup(opts) - highlight.setup_highlights() + highlight.setup() setup_global_autocmd() setup_commands() diff --git a/lua/outline/parser.lua b/lua/outline/parser.lua index 466e77d..b24b9de 100644 --- a/lua/outline/parser.lua +++ b/lua/outline/parser.lua @@ -1,8 +1,8 @@ local cfg = require('outline.config') 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 t_utils = require('outline.utils.table') +local utils = require('outline.utils.init') local M = {} @@ -56,7 +56,7 @@ local function parse_result(result, depth, hierarchy, parent, bufnr) local children = nil if value.children ~= nil then -- 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) children = parse_result(value.children, level + 1, child_hir, node, bufnr) else @@ -123,4 +123,66 @@ function M.preorder_iter(items, children_check) 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 diff --git a/lua/outline/providers/nvim-lsp.lua b/lua/outline/providers/nvim-lsp.lua index bb33559..dec9f5a 100644 --- a/lua/outline/providers/nvim-lsp.lua +++ b/lua/outline/providers/nvim-lsp.lua @@ -1,6 +1,6 @@ local config = require('outline.config') local jsx = require('outline.providers.jsx') -local lsp_utils = require('outline.utils.lsp_utils') +local lsp_utils = require('outline.utils.lsp') local M = { name = 'lsp', diff --git a/lua/outline/sidebar.lua b/lua/outline/sidebar.lua index 576ff10..7c49fd4 100644 --- a/lua/outline/sidebar.lua +++ b/lua/outline/sidebar.lua @@ -4,9 +4,7 @@ local folding = require('outline.folding') local parser = require('outline.parser') local providers = require('outline.providers.init') local utils = require('outline.utils.init') -local writer = require('outline.writer') local symbols = require('outline.symbols') -local t_utils = require('outline.utils.table') local strlen = vim.fn.strlen @@ -105,25 +103,30 @@ function Sidebar:initial_handler(response, opts) end end +-- stylua: ignore start ---Convenience function for setup_keymaps ---@param cfg_name string Field in cfg.o.keymaps ---@param method string|function If string, field in Sidebar ---@param args table Passed to method function Sidebar:nmap(cfg_name, method, args) + local keys = cfg.o.keymaps[cfg_name] + local fn + if type(method) == 'string' then - utils.nmap(self.view.bufnr, cfg.o.keymaps[cfg_name], function() - Sidebar[method](self, unpack(args)) - end) + fn = function() Sidebar[method](self, unpack(args)) end else - utils.nmap(self.view.bufnr, cfg.o.keymaps[cfg_name], function() - method(unpack(args)) - end) + fn = function() 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 function Sidebar:setup_keymaps() for name, meth in pairs({ - -- stylua: ignore start goto_location = { '_goto_location', { true } }, peek_location = { '_goto_location', { false } }, restore_location = { '_map_follow_cursor', {} }, @@ -143,12 +146,12 @@ function Sidebar:setup_keymaps() fold_all = { '_set_all_folded', { true } }, unfold_all = { '_set_all_folded', { false } }, fold_reset = { '_set_all_folded', {} }, - -- stylua: ignore end }) do ---@diagnostic disable-next-line param-type-mismatch self:nmap(name, meth[1], meth[2]) end end +-- stylua: ignore end ---Autocmds for the (current) outline buffer function Sidebar:setup_buffer_autocmd() @@ -257,12 +260,12 @@ function Sidebar:update_cursor_pos(current) end end ----Calls writer.make_outline and then calls M.update_cursor_pos if --- update_cursor is not false +---Calls build_outline and then calls update_cursor_pos if update_cursor is +--not false ---@param update_cursor boolean? ---@param set_cursor_to_node outline.SymbolNode|outline.FlatSymbolNode? 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 self:update_cursor_pos(current) end @@ -299,7 +302,7 @@ end ---@param items outline.SymbolNode[] function Sidebar:_merge_items(items) - utils.merge_items_rec({ children = items }, { children = self.items }) + parser.merge_items_rec({ children = items }, { children = self.items }) end ---Re-request symbols from provider @@ -600,60 +603,57 @@ function Sidebar:_highlight_current_item(winnr, update_cursor) end ---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 --- outline buffer. --- --- 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`. +---parses each node and replaces old lines with new lines to be written for the +---outline buffer. +--- +---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`. ---@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 -function Sidebar:write_outline(find_node) - -- 0-indexed +function Sidebar:build_outline(find_node) + ---@type integer 0-indexed 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 - local put_cursor ---@type outline.FlatSymbolNode - + ---@type outline.FlatSymbolNode Deepest visible matching node to set cursor + local put_cursor self.flats = {} local line_count = 0 - local lines = {} ---@type string[] local details = {} ---@type string[] local linenos = {} ---@type string[] - local hl = {} - - -- Find the prefix for each line needed for the lineno space + local hl = {} ---@type outline.HL[] + + -- 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_prefix = '' local lineno_max_width = #tostring(vim.api.nvim_buf_line_count(self.code.buf) - 1) 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_prefix = string.rep(' ', lineno_offset) end -- Closures for convenience - local function add_guide_hl(from, to) + -- stylua: ignore start + local function save_guide_hl(from, to) table.insert(hl, { - line_count, - from, - to, - 'OutlineGuides', + line = line_count, name = 'OutlineGuides', + from = from, to = to, }) end - - local function add_fold_hl(from, to) + local function save_fold_hl(from, to) table.insert(hl, { - line_count, - from, - to, - 'OutlineFoldMarker', + line = line_count, name = 'OutlineFoldMarker', + from = from, to = to, }) end + -- stylua: ignore end local guide_markers = cfg.o.guides.markers local fold_markers = cfg.o.symbol_folding.markers @@ -687,7 +687,7 @@ function Sidebar:write_outline(find_node) table.insert(linenos, leftpad .. lineno) -- 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 if folding.is_foldable(node) then @@ -722,16 +722,15 @@ function Sidebar:write_outline(find_node) end end - -- Finished with guide prefix - -- Join all prefix chars by a space + -- Finished with guide prefix. Now join all prefix chars by a space local pref_str = table.concat(pref, ' ') local total_pref_len = lineno_offset + #pref_str -- 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. - 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 - 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 local line = lineno_prefix .. pref_str @@ -742,42 +741,35 @@ function Sidebar:write_outline(find_node) end line = line .. ' ' .. node.name - -- Highlight for the icon ✨ - -- Start from icon col + -- Start from left of icon col local hl_start = #pref_str + #lineno_prefix + icon_pref local hl_end = hl_start + #node.icon -- until after icon 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 -- for hover highlights. 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', ' ') table.insert(lines, line) end - -- Render - - writer.clear_virt_text(self.view.bufnr) - writer.clear_icon_hl(self.view.bufnr) - - vim.api.nvim_buf_set_option(self.view.bufnr, 'modifiable', true) - vim.api.nvim_buf_set_lines(self.view.bufnr, 0, -1, false, lines) - vim.api.nvim_buf_set_option(self.view.bufnr, 'modifiable', false) - - -- Unfortunately highlights and extmarks cannot be added to lines that do not - -- 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 + -- PERF: + -- * Is setting individual lines is not as good as rewriting entire buffer? + -- That way we can set all highlights and virtual text together without + -- requiring extra O(n) iterations. + -- * Is there a significant difference if new lines are set first, on top + -- of old highlights, before resetting the highlights? (Rather than doing + -- like below) + self.view:clear_all_ns() + self.view:rewrite_lines(lines) + self.view:add_hl_and_ns(hl, self.flats, details, linenos) return put_cursor end diff --git a/lua/outline/types/outline.lua b/lua/outline/types/outline.lua index 9243ca1..484c8a9 100644 --- a/lua/outline/types/outline.lua +++ b/lua/outline/types/outline.lua @@ -74,7 +74,7 @@ -- HELP ---@class outline.HL ----@field line integer +---@field line integer Line number 1-indexed ---@field from integer ---@field to integer ---@field name string diff --git a/lua/outline/utils/init.lua b/lua/outline/utils/init.lua index f58666a..05ce12f 100644 --- a/lua/outline/utils/init.lua +++ b/lua/outline/utils/init.lua @@ -1,13 +1,9 @@ local M = {} ---maps the table|string of keys to the action ----@param keys table|string +---@param keys table ---@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 @@ -33,78 +29,6 @@ function M.debounce(f, delay) 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 @@ -147,4 +71,49 @@ function M.str_or_nonempty_table(t) return type(t) == 'string' or M.table_has_content(t) 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 diff --git a/lua/outline/utils/lsp_utils.lua b/lua/outline/utils/lsp.lua similarity index 100% rename from lua/outline/utils/lsp_utils.lua rename to lua/outline/utils/lsp.lua diff --git a/lua/outline/utils/table.lua b/lua/outline/utils/table.lua deleted file mode 100644 index c692fc3..0000000 --- a/lua/outline/utils/table.lua +++ /dev/null @@ -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 diff --git a/lua/outline/view.lua b/lua/outline/view.lua index b170d2b..6ab9aaf 100644 --- a/lua/outline/view.lua +++ b/lua/outline/view.lua @@ -1,4 +1,5 @@ local cfg = require('outline.config') +local highlight = require('outline.highlight') ---@class outline.View local View = {} @@ -66,6 +67,7 @@ function View:setup_view(split_command) end end +---Close view window and remove winnr/bufnr fields function View:close() if self.winnr then vim.api.nvim_win_close(self.winnr, true) @@ -74,6 +76,7 @@ function View:close() end end +---Return whether view has valid buf and win numbers function View:is_open() return self.winnr and self.bufnr @@ -81,4 +84,45 @@ function View:is_open() and vim.api.nvim_win_is_valid(self.winnr) 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 diff --git a/lua/outline/writer.lua b/lua/outline/writer.lua deleted file mode 100644 index e814fac..0000000 --- a/lua/outline/writer.lua +++ /dev/null @@ -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