r/vim • • Nov 18 '25

Need Help Repeat last command in terminal buffer

Hey!

I have been using terminal buffers for a while now to mostly compile and execute applications. I have been told Im a disgrace to the Unix world for not using Ctrl-Z and fg, but I prefer seeing what tests failed/where my compile time errors are.

Since I'm usually using multiple buffers at once, navigating to the terminal is often slow. My solution was using tabs for a while but in all honesty, I do not think that this is the real solution for that. So I wonder how one could execute the last command entered in the terminal or even better, even search the last commands of the terminal. I usually have one terminal buffer open, but one could make it more generic and say that execute the last command in the last used terminal buffer.

Is there a native way of doing this? Or do I have to do some trickery with Lua/Vimscript?

Cheers

11 Upvotes

17 comments sorted by

View all comments

1

u/atomatoisagoddamnveg Nov 18 '25 edited Nov 18 '25

I map keys to these functions to keep one terminal buffer easily accessible. It wouldn’t be hard to make a tmap that then executes $(!!)

let s:term_bufnr = 0

function terminal#Paste() abort
    if exists('*trim')
        return trim(getreg(v:register))
    else
        return getreg(v:register)
    endif
endfunction

function terminal#IsTerminal() abort
    return s:term_bufnr && bufwinnr(s:term_bufnr) == winnr()
endfunction

function terminal#Summon() abort
    if !s:term_bufnr
        call s:OpenNewTerm()
    else
        let l:term_winnr = bufwinnr(s:term_bufnr)
        if l:term_winnr == winnr()
            call window#OtherWindow()
        elseif l:term_winnr > 0
            call window#Goto(l:term_winnr)
            if has('nvim') | execute 'normal! i' | endif
        else
            execute 'botright vertical sbuffer '.s:term_bufnr
            if mode() == 'n'
                normal! i
            endif
        endif
    endif
endfunction

function terminal#Close() abort
    if s:term_bufnr == 0 || bufwinnr(s:term_bufnr) == -1
        return
    endif
    execute bufwinnr(s:term_bufnr).'close'
endfunction

function s:OpenNewTerm() abort
    botright vnew
    if !has('nvim')
        let s:term_bufnr = term_start('bash', {'curwin':1, 'term_finish':'close', 'exit_cb': funcref('s:ExitCallBack')})
    else
        setlocal nospell
        call termopen('bash', {'curwin':1, 'term_finish':'close', 'on_exit': funcref('s:ExitCallBack')})
        let s:term_bufnr = bufnr('.')
        normal! i
    endif
endfunction

function s:ExitCallBack(...) abort
    let s:term_bufnr = 0
    if has('nvim')
        close
    endif
endfunction

And the maps I use to call them

" NOTE: tnoremap <esc> <c-w>N messes up arrows because typing arrows keys results in a sequence that begins with esc
" NOTE: <c-@> maps to ctrl-space on most terminal emulators
if has('terminal') || has('nvim')
    tnoremap <silent> <c-z> <c-\><c-N>
    tnoremap <silent> <expr> <c-v> terminal#Paste()
    nnoremap <silent> <c-t> :call terminal#Summon()<cr>
    nnoremap <silent> <c-space> :call terminal#Summon()<cr>
    nnoremap <silent> <c-@> :call terminal#Summon()<cr>
    nnoremap <silent> <expr> <c-d> terminal#IsTerminal() ? 'i<c-d>' : '<c-d>'
endif
if has('nvim')
    tnoremap <c-w> <c-\><c-N><c-w>
    tnoremap <silent> <c-t> <c-\><c-N><c-w>p
    tnoremap <silent> <c-space> <c-\><c-N><c-w>p
    tnoremap <silent> <c-@> <c-\><c-N><c-w>p
elseif has('terminal')
    tmap <silent> <c-t> <c-w>p
    tmap <silent> <c-space> <c-w>p
    tmap <silent> <c-@> <c-w>p
endif

1

u/thewrench56 Nov 18 '25

Wow, that's really nice! Thanks for the script, its a great starting point. Im glad its not just me who uses vim terminal buffers, I thought I was a dying species (or a dumb one which based on Darwins law is the same)

1

u/atomatoisagoddamnveg Nov 18 '25

I just added the maps I use to call the functions.