Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Question :: (Re)-focus on open terminal window using it's count number #42

Open
lcrockett opened this issue May 17, 2021 · 6 comments
Open
Labels
bug Something isn't working enhancement New feature or request

Comments

@lcrockett
Copy link
Contributor

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 :toggle to open the terminal window (again). However, if I focus on a non-terminal window and use :toggle to refer to the specific terminal window to focus on, it closes the already open terminal window and errors out with Failed 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
@akinsho
Copy link
Owner

akinsho commented May 18, 2021

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 👍🏾

@akinsho akinsho added bug Something isn't working enhancement New feature or request labels May 18, 2021
@lcrockett
Copy link
Contributor Author

Cheers ! Let me know if you need more information or a test of the fix once available.

@akinsho
Copy link
Owner

akinsho commented Jun 18, 2021

@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.

@lcrockett
Copy link
Contributor Author

@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.

@lcrockett
Copy link
Contributor Author

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().

@fhill2
Copy link

fhill2 commented Oct 16, 2021

@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:https://*toggleterm#* instead
vim.cmd('autocmd! TermOpen term:https://* 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

3 participants