blog/content/posts/vim-stt.md

3.1 KiB

title date
Vim Send To Terminal 2023-07-15T20:18:03-04:00

Semi automatic scripts with vim :terminal

For a long time, I used the below to send current line to vim's :terminal

:call term_list()[0]->term_sendkeys(getline('.') .. "\<CR>")

This is very useful if you have a text file with complex shell commands. To run again, @: and then again, @@. This works because, last command run is stored in register : and the last macro executed using @ is stored in register @. And then to run another day, do :call te<UP arrow> to recall from vim's command history.

Since this was very useful, I then wrapped it in a function and added a mapping

def SendToTerminal()
    if term_list()->empty()
        echomsg "No Terminal windows found"
        return
    endif
    terms[0]->term_sendkeys(getline(.) .. "\<CR>")
enddef

nnoremap <silent><leader>s call SendToTerminal()<CR>

Adding more features

So far good for shell commands. But when working with python, had to send a block of functions to the ipython shell. So added support for range of lines. But then when sending a range of lines to shell, sometimes there had to be a small delay (sleep) between commands so that the previous command can complete and not eat the rest of commands. Then finally added support for sending ctrl characters like ctrl c, ctrl l

vim9script
def SendRangeToTerminal(start_line: number, end_line: number, _ = 0)
    const terms = term_list()
    if terms->empty()
        echomsg "No Terminal windows found"
        return
    endif
    var line_num = start_line
    for line in getline(start_line, end_line)
        line_num += 1
        const spl_cmd = line->matchlist('\vVIMST (sleep|ctrl) ([0-9]+|[a-z])?')
        if !spl_cmd->empty()
            const [_, cmd, arg1; _] = spl_cmd
            if cmd == "sleep"
                timer_start(arg1->str2nr(), funcref('SendRangeToTerminal', [line_num, end_line]))
                return
            elseif cmd == "ctrl"
                terms[0]->term_sendkeys(nr2char(arg1->char2nr() - 96))
                continue
            endif
        endif
        terms[0]->term_sendkeys(line .. "\<CR>")
    endfor
enddef

command -range -bar SendToTerm :call <SID>SendRangeToTerminal(<line1>, <line2>)
vnoremap <silent><leader>s :SendToTerm<CR>
nnoremap <silent><leader>s :SendToTerm<CR>

Wouldn't it be nice to just double-click commands with mouse? Like a simple GUI! ;)

nnoremap <silent><2-LeftMouse> :SendToTerm<CR>

Or just Enter?

nnoremap <buffer> <CR> :SendToTerm \| norm j<CR>

Of course, mapping Enter for any file is a bad idea. So lets just map in our cheat file

autocmd BufNewFile,BufRead cheat.sh {
    nnoremap <buffer> <silent><2-LeftMouse> :SendToTerm<CR>
    nnoremap <buffer> <CR> :SendToTerm \| norm j<CR>
}

Demo!

What about neovim/tmux/screen?

I am not the only one who thought about this. See vim-slime since 2007. However it does not support adding a sleep or sending arbitrary ctrl characters without additional mappings