current commit
261010.1 · fix(ts-context): align upstream changes
local api = vim.api
local utils = require("fzf-lua.utils")

---@class fzf-lua.TSContext
---@field zindex? integer
---@field private _winids table<integer, integer> win to buf
---@field private _setup_opts TSContext.UserConfig
local M = {}

---@param opts TSContext.UserConfig
---@return boolean
function M.setup(opts)
  if M._setup then return true end
  if not package.loaded["treesitter-context"] then
    return false
  end
  -- Our temp nvim-treesitter-context config
  M._setup_opts = {}
  for k, v in pairs(opts) do
    M._setup_opts[k] = { v }
  end
  ---@diagnostic disable-next-line: unresolved-require
  local config = require("treesitter-context.config")
  M._config = utils.tbl_deep_clone(config)
  for k, v in pairs(M._setup_opts) do
    v[2] = config[k]
    config[k] = v[1]
  end
  M._winids = {}
  M._setup = true
  return true
end

function M.deregister()
  if not M._setup then return end
  for winid, _ in pairs(M._winids) do
    M.close(winid)
  end
  ---@diagnostic disable-next-line: unresolved-require
  local config = require("treesitter-context.config")
  for k, v in pairs(M._setup_opts) do
    config[k] = v[2]
  end
  M._config = nil
  M._winids = nil
  M._setup = nil
end

---@param winid integer
---@return boolean?
function M.is_attached(winid)
  if not M._setup then return false end
  return M._winids[winid] and true or false
end

---@param winid integer
function M.close(winid)
  if not M._setup then return end
  ---@diagnostic disable-next-line: unresolved-require
  require("treesitter-context.render").close(winid)
  M._winids[winid] = nil
end

---@param winid integer
---@param bufnr integer
function M.toggle(winid, bufnr)
  if not M._setup then return end
  if M.is_attached(winid) then
    M.close(winid)
  else
    M.update(winid, bufnr)
  end
end

function M.inc_dec_maxlines(num, winid, bufnr)
  if not M._setup then return end
  local n = tonumber(num)
  if not n then return end
  ---@diagnostic disable-next-line: unresolved-require
  local config = require("treesitter-context.config")
  local max_lines = config.max_lines or 0
  config.max_lines = math.max(0, max_lines + n)
  utils.info("treesitter-context `max_lines` set to %d.", config.max_lines)
  if M.is_attached(winid) then
    for _, t in ipairs({ 0, 20 }) do
      vim.defer_fn(function() M.update(winid, bufnr) end, t)
    end
  end
end

-- ts-context support global zindex config
-- but we must override zindex to ensure zindex order:
-- normal win's context < fzf win < (fzf) preview's context
---@param win integer
---@param zindex integer
local set_zindex = function(win, zindex)
  if win and api.nvim_win_is_valid(win) then
    utils.win_set_config(win, { zindex = zindex })
    -- noautocmd don't ignore WinResized/WinScrolled
    utils.wo[win].eventignorewin = "WinResized"
  end
end

---@alias TSContext.UserConfig table
---@param winid integer
---@param bufnr integer
---@param opts? TSContext.UserConfig
function M.update(winid, bufnr, opts)
  opts = opts or {}
  if not M.setup(opts) then return end
  assert(not api.nvim_win_is_valid(winid) or bufnr == api.nvim_win_get_buf(winid))
  ---@diagnostic disable-next-line: unresolved-require
  local render = require("treesitter-context.render")
  ---@diagnostic disable-next-line: unresolved-require
  local context_ranges, context_lines = require("treesitter-context.context").get(winid)
  if not context_ranges or #context_ranges == 0 then
    M.close(winid)
  else
    assert(context_lines)
    local function open()
      if api.nvim_buf_is_valid(bufnr) and api.nvim_win_is_valid(winid) then
        api.nvim_win_call(winid, function()
          render.open(winid, context_ranges, context_lines)
          M.window_contexts = M.window_contexts or utils.upvfind(render.open, "window_contexts")
          -- https://github.com/nvim-treesitter/nvim-treesitter-context/pull/680
            or utils.upvfind(utils.upvfind(render.open, "open") or print, "window_contexts")
          if not M.window_contexts then return end
          local window_context = M.window_contexts[winid]
          if not window_context or not M.zindex then return end
          set_zindex(window_context.context_winid, M.zindex)
          set_zindex(window_context.gutter_winid, M.zindex)
        end)
        M._winids[winid] = bufnr
      end
    end
    -- NOTE: no longer required since adding `eventignore` to `FzfWin:set_winopts`
    -- if TSContext.is_attached(winid) == bufnr then
    open()
    -- else
    --   -- HACK: but the entire nvim-treesitter-context is essentially a hack
    --   -- https://github.com/ibhagwan/fzf-lua/issues/1552#issuecomment-2525456813
    --   for _, t in ipairs({ 0, 20 }) do
    --     vim.defer_fn(function() open() end, t)
    --   end
    -- end
  end
end

---@param winid integer
---@param bufnr integer
---@param zindex integer
---@param cancel fun(): boolean
---@param opts TSContext.UserConfig
function M.schedule_update(winid, bufnr, zindex, cancel, opts)
  local ft = vim.b[bufnr] and vim.b[bufnr]._ft
  if not ft then
    M.close(winid)
    return
  end
  -- HACK: since TS async parsing commit we cannot guarantee the TSContext ranges as these will
  -- return empty unless parsing is complete and we have no access to the `on_parse` event
  -- https://github.com/neovim/neovim/commit/45e606b1fddbfeee8fe28385b5371ca6f2fba71b
  -- For more info see #1922
  local lang = vim.treesitter.language.get_lang(ft)
  if not lang or not utils.has_ts_parser(lang, "context") then
    M.close(winid)
    return
  end
  local parser, err = vim.treesitter.get_parser(bufnr, lang, { error = false })
  -- TODO: fix `has_ts_parser` regression from a953548 (#2366)
  -- should never fail since `utils.has_ts_parser` returned true
  -- assert(parser, "'vim.treesitter.get_parser' err: " .. tostring(err))
  if not parser or err then
    M.close(winid)
    return
  end
  local context_updated
  M.zindex = zindex
  -- `multiwindow` must be set regardless of user options
  opts = vim.tbl_extend("force", opts or {}, { multiwindow = true })
  for _, t in ipairs({ 0, 20, 50, 100 }) do
    vim.defer_fn(function()
      if context_updated or cancel() or not parser:is_valid(true) then return end
      context_updated = true
      M.update(winid, bufnr, opts)
    end, t)
  end
end

return M