toggleterm.nvim
toggleterm.nvim copied to clipboard
Question :: (Re)-focus on open terminal window using it's count number
First of all, excellent plugin and thanks a million for putting effort in this (and other) plugins. I'm using nvim-toggleterm daily in my workflow with a few custom functions for my specific preferences.
One thing I haven't been able to figure out yet is how to navigate to a terminal window based on it's count number, regardless if the terminal window is already open (but not focused) or not opened (anymore).
- If the terminal window hasn't been opened yet or if I have previously closed the terminal window (but left it running in the background), i'm fine using
:toggleto open the terminal window (again). However, if I focus on a non-terminal window and use:toggleto refer to the specific terminal window to focus on, it closes the already open terminal window and errors out withFailed to close window: win id - [0-9]{1,4} does not exist(mind the in-place regex).
I can of course move to the terminal window using, for instance, 2wincmd w, or whatever number the window got assigned. It however requires me to look at the statusline to find out the window number to determine which one i'll have to move to, which obviously is less optimal than it could be ;-).
Is there a way to refocus on an already open terminal window by referring to it's number (set using count = [0-9]+) making use of an nvim-toggleterm function or similar ? This way I can hook a keymapping to it, preferably the same keymapping I already use to toggle it so I can keep using one keymapping per function.
For convenience, the nvim-toggleterm snippet i'm using with NVIM v0.5.0-dev+61aefaf29 on MacOS here below:
nvim-toggleterm Lua
require('toggleterm').setup {
close_on_exit = true,
float_opts = {
border = 'curved'
},
persist_size = false
}
local terminal_default_float = require('toggleterm.terminal').Terminal:new({
count = 50,
direction = 'float',
on_open = function(term)
vim.api.nvim_buf_set_keymap(term.bufnr, 'n', 'q', '<cmd>close<cr>', { noremap = true, silent = true })
vim.api.nvim_buf_set_keymap(term.bufnr, 't', '<c-k>', '<c-\\><c-n><cmd>close<cr><c-w><c-p>', { noremap = true })
vim.wo.cursorcolumn = false
vim.wo.cursorline = false
vim.cmd('VimadeBufDisable')
vim.cmd("startinsert!")
end,
on_close = function(term)
vim.cmd("quit!")
end
})
local terminal_horizontal_one = require('toggleterm.terminal').Terminal:new({
count = 11,
direction = 'horizontal',
on_open = function(term)
vim.api.nvim_buf_set_keymap(term.bufnr, 'n', 'q', '<cmd>close<cr>', { noremap = true, silent = true })
vim.api.nvim_buf_set_keymap(term.bufnr, 't', '<c-b>', '<c-\\><c-n><cmd>close<cr><c-w><c-p>', { noremap = true, silent = true })
vim.api.nvim_buf_set_keymap(term.bufnr, 't', '<c-k>', '<c-\\><c-n><c-w><c-p>', { noremap = true, silent = true })
vim.wo.cursorcolumn = false
vim.wo.cursorline = false
vim.cmd('VimadeBufDisable')
vim.cmd("startinsert!")
end,
on_close = function(term)
vim.cmd("quit!")
end
})
local terminal_horizontal_two = require('toggleterm.terminal').Terminal:new({
count = 12,
direction = 'horizontal',
on_open = function(term)
vim.api.nvim_buf_set_keymap(term.bufnr, 'n', 'q', '<cmd>close<cr>', { noremap = true, silent = true })
vim.api.nvim_buf_set_keymap(term.bufnr, 't', '<c-b>', '<c-\\><c-n><cmd>close<cr><c-w><c-p>', { noremap = true, silent = true })
vim.api.nvim_buf_set_keymap(term.bufnr, 't', '<c-k>', '<c-\\><c-n><c-w><c-p>', { noremap = true, silent = true })
vim.wo.cursorcolumn = false
vim.wo.cursorline = false
vim.cmd('VimadeBufDisable')
vim.cmd("startinsert!")
end,
on_close = function(term)
vim.cmd("quit!")
end
})
local terminal_horizontal_three = require('toggleterm.terminal').Terminal:new({
count = 13,
direction = 'horizontal',
on_open = function(term)
vim.api.nvim_buf_set_keymap(term.bufnr, 'n', 'q', '<cmd>close<cr>', { noremap = true, silent = true })
vim.api.nvim_buf_set_keymap(term.bufnr, 't', '<c-b>', '<c-\\><c-n><cmd>close<cr><c-w><c-p>', { noremap = true, silent = true })
vim.api.nvim_buf_set_keymap(term.bufnr, 't', '<c-k>', '<c-\\><c-n><c-w><c-p>', { noremap = true, silent = true })
vim.wo.cursorcolumn = false
vim.wo.cursorline = false
vim.cmd('VimadeBufDisable')
vim.cmd("startinsert!")
end,
on_close = function(term)
vim.cmd("quit!")
end
})
function _terminal_default_float_toggle()
terminal_default_float:toggle()
end
function _terminal_horizontal_one_toggle()
terminal_horizontal_one:toggle()
end
function _terminal_horizontal_two_toggle()
terminal_horizontal_two:toggle()
end
function _terminal_horizontal_three_toggle()
terminal_horizontal_three:toggle()
end
local terminal_tig = require('toggleterm.terminal').Terminal:new({
cmd = 'tig',
count = 60,
direction = 'float',
on_open = function(term)
vim.api.nvim_buf_set_keymap(term.bufnr, 'n', 'q', '<cmd>close<cr>', { noremap = true, silent = true })
vim.api.nvim_buf_set_keymap(term.bufnr, 't', '<c-k>', '<c-\\><c-n><cmd>close<cr><c-w><c-p>', { noremap = true })
vim.wo.cursorcolumn = false
vim.wo.cursorline = false
vim.cmd('VimadeBufDisable')
vim.cmd("startinsert!")
end,
on_close = function(term)
vim.cmd("quit!")
end
})
local terminal_lazygit = require('toggleterm.terminal').Terminal:new({
cmd = 'lazygit',
count = 61,
direction = 'float',
on_open = function(term)
vim.api.nvim_buf_set_keymap(term.bufnr, 'n', 'q', '<cmd>close<cr>', { noremap = true, silent = true })
vim.api.nvim_buf_set_keymap(term.bufnr, 't', '<c-k>', '<c-\\><c-n><cmd>close<cr><c-w><c-p>', { noremap = true })
vim.wo.cursorcolumn = false
vim.wo.cursorline = false
vim.cmd('VimadeBufDisable')
vim.cmd("startinsert!")
end,
on_close = function(term)
vim.cmd("quit!")
end
})
local terminal_k9s = require('toggleterm.terminal').Terminal:new({
cmd = 'k9s',
count = 62,
direction = 'float',
on_open = function(term)
vim.api.nvim_buf_set_keymap(term.bufnr, 'n', 'q', '<cmd>close<cr>', { noremap = true, silent = true })
vim.api.nvim_buf_set_keymap(term.bufnr, 't', '<c-k>', '<c-\\><c-n><cmd>close<cr><c-w><c-p>', { noremap = true })
vim.wo.cursorcolumn = false
vim.wo.cursorline = false
vim.cmd('VimadeBufDisable')
vim.cmd("startinsert!")
end,
on_close = function(term)
vim.cmd("quit!")
end
})
function _terminal_tig_toggle()
terminal_tig:toggle()
end
function _terminal_lazygit_toggle()
terminal_lazygit:toggle()
end
function _terminal_k9s_toggle()
terminal_k9s:toggle()
end
Used keymapping references
lua _terminal_horizontal_one_toggle()
lua _terminal_horizontal_two_toggle()
lua _terminal_horizontal_three_toggle()
lua _terminal_default_float_toggle()
lua _terminal_k9s_toggle()
lua _terminal_lazygit_toggle()
lua _terminal_tig_toggle
Hi @lcrockett 👋🏾 , so you've actually stumbled upon a bug that I haven't really been tracking anywhere. In the initial implementation of this plugin it was designed so this would work i.e. 4ToggleTerm should open terminal 4 or close terminal 4. Somewhere along the way though this functionality got broken/wasn't preserved during a refactor.
It's on my list though of things to fix 👍🏾
Cheers ! Let me know if you need more information or a test of the fix once available.
@lcrockett I actually just came back to this after a while and realised I'd read the exact request incorrectly the first time. I thought the issue was that specifying a count wasn't opening the correct terminal. Which was an issue i.e. 4ToggleTerm was not always toggling 4 but now it should be.
The specific issue here though seems to be about going to an unfocused but open toggle term, which tbh is an understandable use case but I think in hindsight one I personally will outsource to someone else. This isn't really something I need/would use so not particularly keen on spending time on it, not that it's a bad idea or anything. Happy to help anyone who wants to take it on re. reviewing a PR or coming up with a solution.
@akinsho Cheers on the understandable feedback. Trying to focus back on an unfocused but previously opened and currently open toggle term is indeed what i'm after.
I realized this can be achieved with the on_open and on_close directives and some boilerplating surrounding it. For future reference, see below for configuration details.
vim.g.self_plugin_toggleterm_horizontal_one_window_number = 0
vim.g.self_plugin_toggleterm_horizontal_two_window_number = 0
vim.g.self_plugin_toggleterm_horizontal_three_window_number = 0
local terminal_horizontal_one = require('toggleterm.terminal').Terminal:new({
count = 11,
direction = 'horizontal',
on_open = function(term)
vim.g.self_plugin_toggleterm_horizontal_one_window_number = vim.api.nvim_get_current_win()
vim.api.nvim_buf_set_keymap(term.bufnr, 'n', 'q', '<cmd>close<cr>', { noremap = true, silent = true })
vim.api.nvim_buf_set_keymap(term.bufnr, 't', '<c-b>', '<c-\\><c-n><cmd>close<cr><c-w><c-p>', { noremap = true, silent = true })
vim.api.nvim_buf_set_keymap(term.bufnr, 't', '<c-k>', '<c-\\><c-n><c-w><c-p>', { noremap = true, silent = true })
vim.wo.cursorcolumn = false
vim.wo.cursorline = false
vim.cmd('VimadeBufDisable')
vim.cmd("startinsert!")
end,
---@diagnostic disable-next-line
on_close = function(term)
vim.g.self_plugin_toggleterm_horizontal_one_window_number = 0
vim.cmd("quit!")
end
})
local terminal_horizontal_two = require('toggleterm.terminal').Terminal:new({
count = 12,
direction = 'horizontal',
on_open = function(term)
vim.g.self_plugin_toggleterm_horizontal_two_window_number = vim.api.nvim_get_current_win()
vim.api.nvim_buf_set_keymap(term.bufnr, 'n', 'q', '<cmd>close<cr>', { noremap = true, silent = true })
vim.api.nvim_buf_set_keymap(term.bufnr, 't', '<c-b>', '<c-\\><c-n><cmd>close<cr><c-w><c-p>', { noremap = true, silent = true })
vim.api.nvim_buf_set_keymap(term.bufnr, 't', '<c-k>', '<c-\\><c-n><c-w><c-p>', { noremap = true, silent = true })
vim.wo.cursorcolumn = false
vim.wo.cursorline = false
vim.cmd('VimadeBufDisable')
vim.cmd("startinsert!")
end,
---@diagnostic disable-next-line
on_close = function(term)
vim.g.self_plugin_toggleterm_horizontal_two_window_number = 0
vim.cmd("quit!")
end
})
local terminal_horizontal_three = require('toggleterm.terminal').Terminal:new({
count = 13,
direction = 'horizontal',
on_open = function(term)
vim.g.self_plugin_toggleterm_horizontal_three_window_number = vim.api.nvim_get_current_win()
vim.api.nvim_buf_set_keymap(term.bufnr, 'n', 'q', '<cmd>close<cr>', { noremap = true, silent = true })
vim.api.nvim_buf_set_keymap(term.bufnr, 't', '<c-b>', '<c-\\><c-n><cmd>close<cr><c-w><c-p>', { noremap = true, silent = true })
vim.api.nvim_buf_set_keymap(term.bufnr, 't', '<c-k>', '<c-\\><c-n><c-w><c-p>', { noremap = true, silent = true })
vim.wo.cursorcolumn = false
vim.wo.cursorline = false
vim.cmd('VimadeBufDisable')
vim.cmd("startinsert!")
end,
---@diagnostic disable-next-line
on_close = function(term)
vim.g.self_plugin_toggleterm_horizontal_three_window_number = 0
vim.cmd("quit!")
end
})
function _G.terminal_horizontal_one_toggle()
if vim.g.self_plugin_toggleterm_horizontal_one_window_number > 0 then
if vim.tbl_contains(vim.api.nvim_list_wins(), vim.g.self_plugin_toggleterm_horizontal_one_window_number) then
vim.api.nvim_set_current_win(vim.g.self_plugin_toggleterm_horizontal_one_window_number)
else
vim.g.self_plugin_toggleterm_horizontal_one_window_number = 0
terminal_horizontal_one:toggle()
end
else
terminal_horizontal_one:toggle()
end
end
function _G.terminal_horizontal_two_toggle()
if vim.g.self_plugin_toggleterm_horizontal_two_window_number > 0 then
if vim.tbl_contains(vim.api.nvim_list_wins(), vim.g.self_plugin_toggleterm_horizontal_two_window_number) then
vim.api.nvim_set_current_win(vim.g.self_plugin_toggleterm_horizontal_two_window_number)
else
vim.g.self_plugin_toggleterm_horizontal_two_window_number = 0
terminal_horizontal_two:toggle()
end
else
terminal_horizontal_two:toggle()
end
end
function _G.terminal_horizontal_three_toggle()
if vim.g.self_plugin_toggleterm_horizontal_three_window_number > 0 then
if vim.tbl_contains(vim.api.nvim_list_wins(), vim.g.self_plugin_toggleterm_horizontal_three_window_number) then
vim.api.nvim_set_current_win(vim.g.self_plugin_toggleterm_horizontal_three_window_number)
else
vim.g.self_plugin_toggleterm_horizontal_three_window_number = 0
terminal_horizontal_three:toggle()
end
else
terminal_horizontal_three:toggle()
end
end
One can use a Lua call to one of the defined functions to pop open a terminal window, for instance lua _G.terminal_horizontal_one_toggle().
@lcrockett Currently using this to toggle between all open terminal windows. It could be modified to support bringing terminals that have been sent to background into focus. I haven't tested for this scenario though, as I don't really use Toggleterm in this way. dotfiles link
_G.focus_toggleterm = function(count)
local terms = require("toggleterm.terminal"):get_all()
if vim.tbl_isempty(terms) then return end
local pwin = _G.toggleterm_last_editor_winnr
if count == 0 or not count then count = 1 end
local term_focused = vim.tbl_contains((function(terms)
return vim.tbl_map(function(term)
return term.window
end, terms)
end)(terms), vim.api.nvim_get_current_win())
if term_focused then
vim.api.nvim_set_current_win(pwin)
else
_G.toggleterm_last_editor_winnr = vim.api.nvim_get_current_win()
local term = terms[count]
if term:is_open() then
local start_in_insert = require"toggleterm.config".get("start_in_insert")
vim.api.nvim_set_current_win(term.window)
if start_in_insert then vim.cmd("startinsert") end
end
end
end
_G.toggleterm_wrap = function(arg, count)
_G.toggleterm_last_editor_winnr = vim.api.nvim_get_current_win()
require"toggleterm".toggle_command(arg, count)
end
vim.cmd("command! -count FocusTerm lua focus_toggleterm(<count>)")
vim.cmd("command! -count -nargs=* ToggleTerm lua toggleterm_wrap(<q-args>, <count>)")
local opts = {noremap = true}
function _G.set_terminal_keymaps()
vim.api.nvim_buf_set_keymap(0, 't', '<A-1>', [[<cmd>lua focus_toggleterm(1)<cr>]], opts)
vim.api.nvim_buf_set_keymap(0, 't', '<A-2>', [[<cmd>lua focus_toggleterm(2)<cr>]], opts)
vim.api.nvim_buf_set_keymap(0, 't', '<A-3>', [[<cmd>lua focus_toggleterm(3)<cr>]], opts)
end
vim.api.nvim_set_keymap('n', '<A-1>', [[<cmd>lua focus_toggleterm(1)<cr>]], opts)
vim.api.nvim_set_keymap('n', '<A-2>', [[<cmd>lua focus_toggleterm(2)<cr>]], opts)
vim.api.nvim_set_keymap('n', '<A-3>', [[<cmd>lua focus_toggleterm(3)<cr>]], opts)
-- if you only want these mappings for toggle term use term://*toggleterm#* instead
vim.cmd('autocmd! TermOpen term://* lua set_terminal_keymaps()')
Also, I had to put this line command! -count -nargs=* ToggleTerm lua toggleterm_wrap(<q-args>, <count>) into after/file.vim as I wasn't overriding the default ToggleTerm command. Not sure if this would be the same for you. Might be something to do with my config that I haven't looked into yet.
I couldn't find a way to achieve this without modifying the ToggleTerm command as I wanted to be able to save the original winnr so I could jump back to the editor window on first terminal creation, otherwise I would have to go back to the editor before using FocusTerm for the toggle to work correctly.