diff --git a/lua/neorg/modules/core/qol/toc/module.lua b/lua/neorg/modules/core/qol/toc/module.lua index 94361e8ed..b5bc42121 100644 --- a/lua/neorg/modules/core/qol/toc/module.lua +++ b/lua/neorg/modules/core/qol/toc/module.lua @@ -55,8 +55,15 @@ module.config.public = { -- A function that takes node type as argument and returns true if this -- type of node should appear in the Table of Contents toc_filter = function(node_type) return node_type:match("^heading") end, + + -- If `true`, `cursurline` will be enabled (highlighted) in the ToC window, + -- and the cursor position between ToC and content window will be synchronized. + sync_cursorline = true, } +local start_lines_of_toc_buf = {} +local last_row_of_norg_win = {} + module.public = { parse_toc_macro = function(buffer) local toc, toc_name = false, nil @@ -153,6 +160,8 @@ module.public = { local prefix, title local extmarks = {} + local start_lines = { offset = offset } + start_lines_of_toc_buf[ui_buffer] = start_lines local toc_filter = module.config.public.toc_filter local success = module.required["core.integrations.treesitter"].execute_query( @@ -181,11 +190,15 @@ module.public = { if prefix and title then local _, column = title:start() + table.insert( extmarks, vim.api.nvim_buf_set_extmark(original_buffer, namespace, (prefix:start()), column, {}) ) + local row_0b, _, _, _ = node:range() + table.insert(start_lines, row_0b) + local prefix_text = module.required["core.integrations.treesitter"].get_node_text(prefix, original_buffer) local title_text = @@ -246,6 +259,24 @@ local function get_max_virtcol() return result end +local function upper_bound(array, v) + -- assume array is sorted + -- find index of first element in array that is > v + local l = 1 + local r = #array + + while l <= r do + local m = math.floor((l+r)/2) + if v >= array[m] then + l = m + 1 + else + r = m - 1 + end + end + + return l +end + module.on_event = function(event) if event.split_type[2] ~= module.name then return @@ -276,6 +307,10 @@ module.on_event = function(event) vim.api.nvim_win_set_option(window, "scrolloff", 999) vim.api.nvim_win_set_option(window, "conceallevel", 0) + if module.config.public.sync_cursorline then + vim.api.nvim_win_set_option(window, "cursorline", true) + end + module.public.update_toc(namespace, toc_title, event.buffer, event.window, buffer, window) if module.config.public.fit_width then @@ -336,6 +371,34 @@ module.on_event = function(event) module.public.update_toc(namespace, toc_title, buf, previous_window, buffer, window) end, }) + + if module.config.public.sync_cursorline then + vim.api.nvim_create_autocmd({"CursorMoved", "CursorMovedI"}, { + -- pattern = "*.norg", + buffer = previous_buffer, + callback = function(ev) + assert(ev.buf == previous_buffer) + local content_window = vim.fn.bufwinid(ev.buf) + + if not vim.api.nvim_buf_is_valid(buffer) or not vim.api.nvim_buf_is_loaded(buffer) then + return true + end + + local current_row_1b = vim.fn.line('.', content_window) + if last_row_of_norg_win[content_window] == current_row_1b then + return + end + last_row_of_norg_win[content_window] = current_row_1b + + local start_lines = start_lines_of_toc_buf[buffer] + assert(start_lines) + + local current_toc_item_idx = upper_bound(start_lines, current_row_1b-1) - 1 + local current_toc_row = current_toc_item_idx==0 and 1 or current_toc_item_idx + start_lines.offset + vim.api.nvim_win_set_cursor(window, { current_toc_row, 0 }) + end, + }) + end end end