Skip to content

Invalid window id when the context window closes mid-render #677

Description

@Chiarandini

Description

Render.open validates window_context.context_winid at render.lua:518, then runs set_lines(), nvim_buf_clear_namespace(), highlight_contexts(), copy_extmarks() and highlight_bottom() before handing that same handle to horizontal_scroll_contexts() at render.lua:532, which calls nvim_win_call on it.

The window can close in between. set_lines() writes vim.bo[bufnr].modifiable, which fires OptionSet; any handler that closes a window during that event can take the context float with it, since it is relative='win'. The check at 518 is real but too early to cover the use at 532.

The guard at treesitter-context.lua:104 does not help either: it validates winid, before context.get() and the whole render.

At the point of failure winid is still valid and context_winid is not, so the id in the message is the context float.

Neovim version

NVIM v0.12.4, LuaJIT 2.1.1774638290

Expected behavior

A context window closing mid-render aborts that render quietly, the way render.lua:518 already handles the same window being gone.

Actual behavior

vim.schedule callback: .../render.lua:382: Invalid window id: 1003
stack traceback:
	[C]: in function 'nvim_win_call'
	.../render.lua:382: in function 'horizontal_scroll_contexts'
	.../render.lua:532: in function 'open'
	.../treesitter-context.lua:120: in function 'f'
	.../treesitter-context.lua:60: in function <.../treesitter-context.lua:40>

Reproduced on master f3061339.

Minimal config

local plugins = {
  ts         = 'https://github.com/nvim-treesitter/nvim-treesitter',
  ts_context = 'https://github.com/nvim-treesitter/nvim-treesitter-context',
}

for name, url in pairs(plugins) do
  local install_path = '/tmp/nvim/site/'..name
  if vim.fn.isdirectory(install_path) == 0 then
    vim.fn.system { 'git', 'clone', '--depth=1', url, install_path }
  end
  vim.o.runtimepath = install_path..','..vim.o.runtimepath
end

require('treesitter-context').setup({})

-- A file deep enough to have a context, written here so the repro is one file.
local body = {}
for i = 1, 80 do
  body[#body + 1] = ('      local x%d = %d'):format(i, i)
end
vim.fn.writefile(
  vim.list_extend({
    'local function outer()',
    '  local function middle()',
    '    local function inner()',
  }, vim.list_extend(body, { '    end', '  end', 'end' })),
  '/tmp/nvim/sample.lua')

-- Stand-in for whatever closed the window in the wild: fires once, during the
-- render, from the 'modifiable' toggle in set_lines().
local fired = false
vim.api.nvim_create_autocmd('OptionSet', {
  pattern = 'modifiable',
  callback = function()
    if fired or #vim.api.nvim_list_wins() < 2 then return end
    fired = true
    pcall(vim.api.nvim_win_close, vim.api.nvim_get_current_win(), true)
  end,
})

Steps to reproduce

  1. nvim --clean -u minimal.lua /tmp/nvim/sample.lua
  2. :vsplit
  3. 60G
  4. :messages

The OptionSet autocmd is a stand-in that makes the timing deterministic. This started from an organic occurrence in a large config, which I could not isolate to a specific plugin; the autocmd reproduces the same traceback on demand.

Suggested fix

local function horizontal_scroll_contexts(winid, context_winid)
  if not api.nvim_win_is_valid(winid) or not api.nvim_win_is_valid(context_winid) then
    return
  end

Verified against f3061339: the repro is clean with this applied, and contexts still render normally. Guarding only winid is not enough, the error moves to the next line.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions