2

I'm new to vimscript and writing a function to toggle a terminal buffer. Here is what I tried:

let g:toggle_term = "<C-j>"

let g:term_buf_nr = -1
function! ToggleTerminal()
    if g:term_buf_nr == -1
        execute "bot term"
        let g:term_buf_nr = bufnr("$")
    else
        execute "bd! " .g:term_buf_nr
        let g:term_buf_nr = -1
    endif
endfunction

execute "nnoremap ".g:toggle_term ." :call ToggleTerminal()<CR>"

The function works fine and toggles the terminal buffer only in case the terminal buffer itself is not focused. If it is focused then pressing the g:toggle_term combination does nothing.

Is there a way make g:toggle_term to toggle terminal even if the terminal buffer is currently focused?

  • 2
    Don't you need to also create a tnoremap to have the mapping executed? :h :tnoremap – statox Nov 12 '19 at 17:19
  • @statox Not sure, because this will send a command to the terminal itself. – St.Antario Nov 12 '19 at 17:45
  • 1
    "Not sure" then read :h Terminal-Job. nnoremap only applies in Terminal-Normal mode. – Matt Nov 12 '19 at 19:13
  • @Matt There is no such manual entry. Did you mean Terminal-mode? – St.Antario Nov 13 '19 at 6:39
  • @Matt execute "tnoremap ".g:toggle_term ." exit<CR> :call ToggleTerminal<CR>" does not really call the function. – St.Antario Nov 13 '19 at 6:53
2

As @statox suggested in comments I should have used tnoremap to implement it. The following works fine:

execute "tnoremap ".g:toggle_term ." <C-w>:call ToggleTerminal()<CR>"

The mistake I initially made was the space right after the N

execute "tnoremap ".g:toggle_term ." <C-w>N :call ToggleTerminal()<CR>"

and since I rebound leader to <space> it did not work the way I wanted.

Also, the declaration

let g:term_buf_nr = -1

should be script private and replaced with

let s:term_buf_nr = -1
  • 1
    No need N, there is :h ctrl-w_: – dedowsdi Nov 13 '19 at 8:24
  • @dedowsdi Could you please expand? There is a manual entry specifying that C-w N is the combination to switch to a normal mode. :h terminal-use – St.Antario Nov 13 '19 at 8:29
  • 1
    :h ctrl-w_: is a valid command in vim, execute it, you should see everything. – dedowsdi Nov 13 '19 at 8:30
  • @dedowsdi Really, thanks. – St.Antario Nov 13 '19 at 8:40
1

I slightly refactored your function and made it more robust. After quitting the terminal, with for example exit command in Windows, you will get an error when trying to invoke your ToggleTerminal() function.

let s:term_buf_nr = -1
function! s:ToggleTerminal() abort
    if s:term_buf_nr == -1
        execute "botright terminal"
        let s:term_buf_nr = bufnr("$")
    else
        try
            execute "bdelete! " . s:term_buf_nr
        catch
            let s:term_buf_nr = -1
            call <SID>ToggleTerminal()
            return
        endtry
        let s:term_buf_nr = -1
    endif
endfunction

nnoremap <silent> <Leader>t :call <SID>ToggleTerminal()<CR>
tnoremap <silent> <Leader>t <C-w>N:call <SID>ToggleTerminal()<CR>

By enclosing the else part of the code in try-catch block, you can react to the aforementioned problem accordingly. I bet you can also let s:term_buf_nr on even of closing the terminal, but I don't know how to do it (I didn't bother, as this solution works for me just fine).

The only thing I don't like about it yet is the fact that it destroys the buffer instead of hiding it somehow, but that's different topic :)

0

I was looking for a robust way to toggle a terminal and ended up building something that meets my expectations.

  • Off: any visible terminal is hidden
  • On: terminal as a bottom panel (restore first hidden terminal or create new one)
  • Works with already open terminal
  • Arguments: size and side of the terminal, defaults to 6 lines at the bottom

It seems pretty robust, but any idea to improve it is welcome.

function! PutTermPanel(buf, side, size) abort
  " new term if no buffer
  if a:buf == 0
    term
  else
    execute "sp" bufname(a:buf)
  endif
  " default side if wrong argument
  if stridx("hjklHJKL", a:side) == -1
    execute "wincmd" "J"
  else
    execute "wincmd" a:side
  endif
  " horizontal split resize
  if stridx("jkJK", a:side) >= 0
    if ! a:size > 0
      resize 6
    else
      execute "resize" a:size
    endif
    return
  endif
  " vertical split resize
  if stridx("hlHL", a:side) >= 0
    if ! a:size > 0
      vertical resize 6
    else
      execute "vertical resize" a:size
    endif
  endif
endfunction

function! s:ToggleTerminal(side, size) abort
  let tpbl=[]
  let closed = 0
  let tpbl = tabpagebuflist()
  " hide visible terminals
  for buf in filter(range(1, bufnr('$')), 'bufexists(bufname(v:val)) && index(tpbl, v:val)>=0')
    if getbufvar(buf, '&buftype') ==? 'terminal'
      silent execute bufwinnr(buf) . "hide"
      let closed += 1
    endif
  endfor
  if closed > 0
    return
  endif
  " open first hidden terminal
  for buf in filter(range(1, bufnr('$')), 'bufexists(v:val) && index(tpbl, v:val)<0')
    if getbufvar(buf, '&buftype') ==? 'terminal'
      call PutTermPanel(buf, a:side, a:size)
      return
    endif
  endfor
  " open new terminal
  call PutTermPanel(0, a:side, a:size)
endfunction



nnoremap <silent> yot :call <SID>ToggleTerminal('J', 6)<CR>

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.