Stop terminal buffer from auto-closing (window)

Background

In my NeoVim usage, I like to keep the window layout intact. Even when I close a buffer, I want the window that contained the buffer to remain open. I use bufdelete.nvim and it works perfectly for my requirements.

When I create a window and open a terminal in it, the terminal’s process may terminate. For example, because the command terminates or because I type exit<CR> in the terminal.
When that happens, there is a helpful message [Process exited 0] and any key closes the terminal buffer.

Question

When the terminal buffer closes, because the process in the terminal terminates, it also closes the window which contains the terminal buffer. Instead, I would like the window to stay open and not mess with my layout. Is there any way to achieve that?

I want to setup terminal buffers to behave like my other buffers.

Thank you!

Resources

There are some GitHub issues for NeoVim, but I am not skilled enough in NeoVim or Lua to make use of the information in there:

This may work:

vim.api.nvim_create_autocmd('TermClose',{callback=function()
  local buf=vim.api.nvim_get_current_buf()
  local newbuf=vim.api.nvim_create_buf(false,true)
  local windows=vim.fn.getwininfo()
  for _,i in ipairs(windows) do
    if i.bufnr==buf then
      vim.api.nvim_win_set_buf(i.winid,newbuf)
    end
  end
  vim.api.nvim_buf_delete(buf,{})
end})
2 Likes

oh wow this sparked an idea for me this is what i came up for my usage.

set_map("n", "<leader>x", function()
	-- don't close tab if has only one buffer in a tab
	local api = vim.api
	local tabs = api.nvim_list_tabpages()
	local is_terminal = api.nvim_buf_get_option(0, "buftype") == "terminal"
	if #tabs > 1 and not is_terminal then
		local cur_tab = api.nvim_win_get_tabpage(0)
		local cur_buf = api.nvim_get_current_buf()
		local tab_info = api.nvim_tabpage_list_wins(cur_tab)
		local valid_bufs = {}
		for _, win in ipairs(tab_info) do
			if api.nvim_buf_is_valid(win) and win ~= cur_buf then
				table.insert(valid_bufs, win)
			end
		end
		if #valid_bufs < 1 then
			local new_buf = api.nvim_create_buf(true, false)
			table.insert(valid_bufs, new_buf)
		end
		api.nvim_set_current_buf(valid_bufs[1])
		pcall(api.nvim_buf_delete, cur_buf, { force = is_terminal })
	else
		pcall(api.nvim_buf_delete, 0, { force = is_terminal })
	end
end)
1 Like

Thank you! That does work indeed!

However, it doesn’t keep the terminal buffer around, so I cannot read the output of the process that terminated. Instead, the auto-command immediately closes the terminal buffer.

But your proposal lead me to a solution that almost works:

vim.api.nvim_create_autocmd("TermClose", {
	callback = function()
		local buf = vim.api.nvim_get_current_buf()
		vim.api.nvim_buf_set_keymap(buf, "n", "<Esc>", ":Bdelete<CR>", { noremap = true })
		vim.api.nvim_buf_set_keymap(buf, "t", "<Esc>", ":Bdelete<CR>", { noremap = true })
	end,
})

The problem: it works in normal mode, but not in terminal mode. Pressing <Esc> in terminal mode still closes the window. What am I doing wrong here?

Thank you so much for you help!

I found a solution which works. I need to:

  1. Use <C-\><C-N> in the mapping to escape terminal mode first
  2. Escape terminal characters in order for the mapping to work with the lua API

With bufdelete.nvim:

-- Keep window around when terminal closes
vim.api.nvim_create_autocmd("TermClose", {
	callback = function()
		local buf = vim.api.nvim_get_current_buf()
		vim.api.nvim_buf_set_keymap(buf, "n", "<Esc>", ":Bdelete<CR>", { noremap = true, silent = true })
		vim.api.nvim_buf_set_keymap(
			buf,
			"t",
			"<Esc>",
			vim.api.nvim_replace_termcodes("<C-\\><C-N>:Bdelete<CR>", true, true, true),
			{ noremap = true, silent = true }
		)
	end,
})

Thank you all for your help! :heart: