How do I run Lua code in the context of a specific buffer without switching to it in Neovim?
Answer
vim.api.nvim_buf_call()
Explanation
vim.api.nvim_buf_call(bufnr, fn) temporarily sets the given buffer as the current buffer, runs your function, then restores the original buffer — all without any visible window switching. This is useful when you need buffer-local context (like % filename expansion, b: variables, or local directory state) for a buffer that is not currently displayed.
How it works
-- Get the full path of a buffer by number without switching to it
local path = vim.api.nvim_buf_call(bufnr, function()
return vim.fn.expand("%:p")
end)
-- Set a buffer-local option on a hidden buffer
vim.api.nvim_buf_call(bufnr, function()
vim.opt_local.tabstop = 4
end)
The return value of the function is returned by nvim_buf_call().
Example
Building a list of full file paths for all loaded buffers:
local paths = {}
for _, bufnr in ipairs(vim.api.nvim_list_bufs()) do
if vim.api.nvim_buf_is_loaded(bufnr) then
local p = vim.api.nvim_buf_call(bufnr, function()
return vim.fn.expand("%:p")
end)
table.insert(paths, p)
end
end
Tips
nvim_buf_call()does not change the window layout or triggerBufEnter/BufLeaveautocommands, making it safe to use in scripts and plugins- For window-local context (like
w:variables orwinnr()), use the analogousvim.api.nvim_win_call(winnr, fn) - This is the correct way to call
vim.fnfunctions that implicitly use the current buffer (likeexpand(),getline(), orsetline()) on a specific non-current buffer