diff --git a/README.md b/README.md index 2efeb891..0817d97e 100644 --- a/README.md +++ b/README.md @@ -211,7 +211,6 @@ Note: calling `setup()` is optional. ```lua require'treesitter-context'.setup{ enable = true, -- Enable this plugin (Can be enabled/disabled later via commands) - multiwindow = false, -- Enable multiwindow support. max_lines = 0, -- How many lines the window should span. Values <= 0 mean no limit. min_window_height = 0, -- Minimum editor window height to enable context. Values <= 0 mean no limit. line_numbers = true, diff --git a/doc/nvim-treesitter-context.txt b/doc/nvim-treesitter-context.txt index 9d194ffd..982a6b9f 100644 --- a/doc/nvim-treesitter-context.txt +++ b/doc/nvim-treesitter-context.txt @@ -23,9 +23,6 @@ not required. -- Enable this plugin (Can be enabled/disabled later via commands) enable = true, - -- Enable multiwindow support. - multiwindow = false, - -- How many lines the window should span. Values <= 0 mean no limit. max_lines = 0, diff --git a/lua/treesitter-context.lua b/lua/treesitter-context.lua index d6d06b0d..38edb2a7 100644 --- a/lua/treesitter-context.lua +++ b/lua/treesitter-context.lua @@ -39,25 +39,8 @@ end local attached = {} --- @type table -local function close(args) - local render = require('treesitter-context.render') - if args.event == "WinClosed" then - -- Closing current window instead of intended window may lead to context window flickering. - render.close(tonumber(args.match)) - else - render.close(api.nvim_get_current_win()) - end -end - -local function close_all() - local render = require('treesitter-context.render') - if config.multiwindow then - for _, winid in pairs(api.nvim_list_wins()) do - render.close(winid) - end - else - render.close(api.nvim_get_current_win()) - end +local function close() + require('treesitter-context.render').close() end ---@param bufnr integer @@ -67,6 +50,7 @@ local function cannot_open(bufnr, winid) or vim.bo[bufnr].filetype == '' or vim.bo[bufnr].buftype ~= '' or vim.wo[winid].previewwindow + or vim.fn.getcmdtype() ~= '' or api.nvim_win_get_height(winid) < config.min_window_height end @@ -74,14 +58,14 @@ end local update_single_context = throttle_by_id(function(winid) -- Since the update is performed asynchronously, the window may be closed at this moment. -- Therefore, we need to check if it is still valid. - if not api.nvim_win_is_valid(winid) or vim.fn.getcmdtype() ~= '' then + if not api.nvim_win_is_valid(winid) then return end local bufnr = api.nvim_win_get_buf(winid) - if cannot_open(bufnr, winid) or not config.multiwindow and winid ~= api.nvim_get_current_win() then - require('treesitter-context.render').close(winid) + if cannot_open(bufnr, winid) then + close() return end @@ -89,7 +73,7 @@ local update_single_context = throttle_by_id(function(winid) all_contexts[bufnr] = context_ranges if not context_ranges or #context_ranges == 0 then - require('treesitter-context.render').close(winid) + close() return end @@ -98,23 +82,8 @@ local update_single_context = throttle_by_id(function(winid) require('treesitter-context.render').open(bufnr, winid, context_ranges, context_lines) end) ----@param args table -local function update(args) - if args.event == "OptionSet" and args.match ~= 'number' and args.match ~= 'relativenumber' then - return - end - - local multiwindow_events = { "WinResized", "User" } - - if config.multiwindow and vim.tbl_contains(multiwindow_events, args.event) then - -- Resizing a single window may cause many resizes in different windows, - -- so it is necessary to iterate over all windows when a WinResized event is received. - for _, winid in pairs(api.nvim_list_wins()) do - update_single_context(winid) - end - else - update_single_context(api.nvim_get_current_win()) - end +local function update() + update_single_context(api.nvim_get_current_win()) end local M = { @@ -134,22 +103,7 @@ local function autocmd(event, callback, opts) end function M.enable() - local update_events = { - 'WinScrolled', - 'BufEnter', - 'WinEnter', - 'VimResized', - 'DiagnosticChanged', - 'CursorMoved', - 'OptionSet', - } - - if config.multiwindow then - table.insert(update_events, 'WinResized') - table.insert(update_events, 'WinLeave') - end - - autocmd(update_events, update) + autocmd({ 'WinScrolled', 'BufEnter', 'WinEnter', 'VimResized', 'DiagnosticChanged' }, update) autocmd('BufReadPost', function(args) attached[args.buf] = nil @@ -162,30 +116,27 @@ function M.enable() attached[args.buf] = nil end) - if config.multiwindow then - autocmd({ 'WinClosed' }, close) - else - autocmd({ 'BufLeave', 'WinLeave', 'WinClosed' }, close) - end + autocmd('CursorMoved', update) + + autocmd('OptionSet', function(args) + if args.match == 'number' or args.match == 'relativenumber' then + update() + end + end) + + autocmd({ 'BufLeave', 'WinLeave' }, close) autocmd('User', close, { pattern = 'SessionSavePre' }) autocmd('User', update, { pattern = 'SessionSavePost' }) - if config.multiwindow then - for _, winid in pairs(api.nvim_list_wins()) do - update_single_context(winid) - end - else - update_single_context(api.nvim_get_current_win()) - end - + update() enabled = true end function M.disable() augroup('treesitter_context_update', {}) attached = {} - close_all() + close() enabled = false end diff --git a/lua/treesitter-context/config.lua b/lua/treesitter-context/config.lua index 382492ed..60c89c66 100644 --- a/lua/treesitter-context/config.lua +++ b/lua/treesitter-context/config.lua @@ -1,7 +1,6 @@ --- @class (exact) TSContext.Config --- @field enable boolean ---- @field multiwindow boolean --- @field max_lines integer --- @field min_window_height integer --- @field line_numbers boolean @@ -17,9 +16,6 @@ --- Enable this plugin (Can be enabled/disabled later via commands) --- @field enable? boolean --- ---- Enable multiwindow support. ---- @field multiwindow? boolean ---- --- How many lines the window should span. Values <= 0 mean no limit. --- @field max_lines? integer --- @@ -49,7 +45,6 @@ --- @type TSContext.Config local default_config = { enable = true, - multiwindow = false, max_lines = 0, -- no limit min_window_height = 0, line_numbers = true, diff --git a/lua/treesitter-context/context.lua b/lua/treesitter-context/context.lua index 2d69fb5d..5a53d538 100644 --- a/lua/treesitter-context/context.lua +++ b/lua/treesitter-context/context.lua @@ -66,26 +66,24 @@ local function calc_max_lines(winid) end ---@param node TSNode ----@param bufnr integer ---@return string -local function hash_args(node, bufnr) +local function hash_node(node) return table.concat({ node:id(), node:symbol(), node:child_count(), node:type(), node:range(), - bufnr, }, ',') end --- Run the context query on a node and return the range if it is a valid --- context node. --- @param node TSNode ---- @param bufnr integer --- @param query vim.treesitter.Query --- @return Range4? -local context_range = cache.memoize(function(node, bufnr, query) +local context_range = cache.memoize(function(node, query) + local bufnr = api.nvim_get_current_buf() local range = { node:range() } --- @type Range4 range[3] = range[1] + 1 range[4] = 0 @@ -121,7 +119,7 @@ local context_range = cache.memoize(function(node, bufnr, query) return range end end -end, hash_args) +end, hash_node) ---@param lang string ---@return vim.treesitter.Query? @@ -167,9 +165,8 @@ local function trim_contexts(context_ranges, context_lines, trim, top) end --- @param range Range4 ---- @param bufnr integer --- @return Range4, string[] -local function get_text_for_range(range, bufnr) +local function get_text_for_range(range) local start_row, end_row, end_col = range[1], range[3], range[4] if end_col == 0 then @@ -177,7 +174,7 @@ local function get_text_for_range(range, bufnr) end_col = -1 end - local lines = api.nvim_buf_get_text(bufnr, start_row, 0, end_row, -1, {}) + local lines = api.nvim_buf_get_text(0, start_row, 0, end_row, -1, {}) -- Strip any empty lines from the node while #lines > 0 do @@ -337,9 +334,9 @@ function M.get(bufnr, winid) -- Only process the parent if it is not in view. if parent_start_row < contexts_end_row then - local range0 = context_range(parent, bufnr, query) + local range0 = context_range(parent, query) if range0 and range_is_valid(range0) then - local range, lines = get_text_for_range(range0, bufnr) + local range, lines = get_text_for_range(range0) if range_is_valid(range) then local last_context = context_ranges[#context_ranges] if last_context and parent_start_row == last_context[1] then diff --git a/lua/treesitter-context/render.lua b/lua/treesitter-context/render.lua index 25800946..0687aff3 100644 --- a/lua/treesitter-context/render.lua +++ b/lua/treesitter-context/render.lua @@ -6,22 +6,21 @@ local config = require('treesitter-context.config') local ns = api.nvim_create_namespace('nvim-treesitter-context') ---- List of buffers that are to be deleted. ----@type integer[] -local retired_buffers = {} +-- Don't access directly, use get_bufs() +local gutter_bufnr --- @type integer? +local context_bufnr --- @type integer? ---- @class WindowContext ---- @field context_winid integer? The context window ID. ---- @field gutter_winid integer? The gutter window ID. - ---- A table mapping window IDs to WindowContext objects. ---- This table contains mappings for windows where the context is displayed. ---- @type table -local window_contexts = {} +local gutter_winid --- @type integer? +local context_winid --- @type integer? +--- @param buf integer? --- @return integer buf -local function create_buf() - local buf = api.nvim_create_buf(false, true) +local function create_buf(buf) + if buf and api.nvim_buf_is_valid(buf) then + return buf + end + + buf = api.nvim_create_buf(false, true) vim.bo[buf].undolevels = -1 vim.bo[buf].bufhidden = 'wipe' @@ -29,19 +28,27 @@ local function create_buf() return buf end ---- @param winid integer ---- @param context_winid integer? +--- @return integer gutter_bufnr +--- @return integer context_bufnr +local function get_bufs() + context_bufnr = create_buf(context_bufnr) + gutter_bufnr = create_buf(gutter_bufnr) + + return gutter_bufnr, context_bufnr +end + +--- @param bufnr integer +--- @param winid integer? --- @param width integer --- @param height integer --- @param col integer --- @param ty string --- @param hl string ---- @return integer Window ID of context window -local function display_window(winid, context_winid, width, height, col, ty, hl) - if not context_winid then +--- @return integer +local function display_window(bufnr, winid, width, height, col, ty, hl) + if not winid or not api.nvim_win_is_valid(winid) then local sep = config.separator and { config.separator, 'TreesitterContextSeparator' } or nil - context_winid = api.nvim_open_win(create_buf(), false, { - win = winid, + winid = api.nvim_open_win(bufnr, false, { relative = 'win', width = width, height = height, @@ -53,13 +60,13 @@ local function display_window(winid, context_winid, width, height, col, ty, hl) zindex = config.zindex, border = sep and { '', '', '', '', sep, sep, sep, '' } or nil, }) - vim.w[context_winid][ty] = true - vim.wo[context_winid].wrap = false - vim.wo[context_winid].foldenable = false - vim.wo[context_winid].winhl = 'NormalFloat:' .. hl - elseif api.nvim_win_is_valid(context_winid) then - api.nvim_win_set_config(context_winid, { - win = winid, + vim.w[winid][ty] = true + vim.wo[winid].wrap = false + vim.wo[winid].foldenable = false + vim.wo[winid].winhl = 'NormalFloat:' .. hl + else + api.nvim_win_set_config(winid, { + win = api.nvim_get_current_win(), relative = 'win', width = width, height = height, @@ -67,7 +74,7 @@ local function display_window(winid, context_winid, width, height, col, ty, hl) col = col, }) end - return context_winid + return winid end --- @param winid integer @@ -297,40 +304,20 @@ local function render_lno(win, bufnr, contexts, gutter_width) highlight_bottom(bufnr, #lno_text - 1, 'TreesitterContextLineNumberBottom') end ----@param context_winid? integer -local function close(context_winid) +---@param winid? integer +local function win_close(winid) vim.schedule(function() - if context_winid == nil or not api.nvim_win_is_valid(context_winid) then - return - end - - local bufnr = api.nvim_win_get_buf(context_winid) - if bufnr ~= nil then - table.insert(retired_buffers, bufnr) - end - if api.nvim_win_is_valid(context_winid) then - api.nvim_win_close(context_winid, true) - end - - if fn.getcmdwintype() ~= '' then - -- Can't delete buffers when the command-line window is open. - return + if winid ~= nil and api.nvim_win_is_valid(winid) then + api.nvim_win_close(winid, true) end - - -- Delete retired buffers. - for _, retired_bufnr in ipairs(retired_buffers) do - if api.nvim_buf_is_valid(retired_bufnr) then - api.nvim_buf_delete(retired_bufnr, { force = true }) - end - end - retired_buffers = {} end) end ---- @param winid integer ---- @param context_winid integer -local function horizontal_scroll_contexts(winid, context_winid) - local active_win_view = api.nvim_win_call(winid, fn.winsaveview) +local function horizontal_scroll_contexts() + if context_winid == nil then + return + end + local active_win_view = fn.winsaveview() local context_win_view = api.nvim_win_call(context_winid, fn.winsaveview) if active_win_view.leftcol ~= context_win_view.leftcol then context_win_view.leftcol = active_win_view.leftcol @@ -396,31 +383,26 @@ function M.open(bufnr, winid, ctx_ranges, ctx_lines) local win_height = math.max(1, #ctx_lines) - window_contexts[winid] = window_contexts[winid] or {} - local window_context = window_contexts[winid] + local gbufnr, ctx_bufnr = get_bufs() if config.line_numbers and (vim.wo[winid].number or vim.wo[winid].relativenumber) then - window_context.gutter_winid = display_window( - winid, - window_context.gutter_winid, + gutter_winid = display_window( + gbufnr, + gutter_winid, gutter_width, win_height, 0, 'treesitter_context_line_number', 'TreesitterContextLineNumber' ) - - if api.nvim_win_is_valid(window_context.gutter_winid) then - render_lno(winid, api.nvim_win_get_buf(window_context.gutter_winid), ctx_ranges, gutter_width) - end + render_lno(winid, gbufnr, ctx_ranges, gutter_width) else - close(window_context.gutter_winid) - window_context.gutter_winid = nil + win_close(gutter_winid) end - window_context.context_winid = display_window( - winid, - window_context.context_winid, + context_winid = display_window( + ctx_bufnr, + context_winid, win_width, win_height, gutter_width, @@ -428,12 +410,6 @@ function M.open(bufnr, winid, ctx_ranges, ctx_lines) 'TreesitterContext' ) - if not api.nvim_win_is_valid(window_context.context_winid) then - return - end - - local ctx_bufnr = api.nvim_win_get_buf(window_context.context_winid) - if not set_lines(ctx_bufnr, ctx_lines) then -- Context didn't change, can return here return @@ -443,28 +419,20 @@ function M.open(bufnr, winid, ctx_ranges, ctx_lines) highlight_contexts(bufnr, ctx_bufnr, ctx_ranges) copy_extmarks(bufnr, ctx_bufnr, ctx_ranges) highlight_bottom(ctx_bufnr, win_height - 1, 'TreesitterContextBottom') - horizontal_scroll_contexts(winid, window_context.context_winid) + horizontal_scroll_contexts() end ---- @param winid? integer -function M.close(winid) +function M.close() -- Can't close other windows when the command-line window is open if fn.getcmdwintype() ~= '' then return end - if winid == nil then - return - end - - local window_context = window_contexts[winid] - if window_context == nil then - return - end - close(window_context.context_winid) - close(window_context.gutter_winid) + win_close(context_winid) + context_winid = nil - window_contexts[winid] = nil + win_close(gutter_winid) + gutter_winid = nil end return M