blog/content/posts/vim-stt.md

3.5 KiB

title date asciinema tags categories
Vim Send To Terminal 2023-07-26T15:23:22-04:00 true
vim
development

Semi automatic scripts with vim :terminal

Sometimes, fully automating a task is not worth the effort. So I end up running a set of commands usually from a cheat sheet text file slightly modifying the arguments each time. I used the below in vim to send line under cursor to vim's :terminal open in a split

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

To send another line @: and then for every other line @@. This works because, last command run is stored in register : and the last macro executed using @ is stored in register @.

To do the same another day, :call te<UP arrow> to recall from vim's command history. Or better, add a function and 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>

More cool features

For most use-cases that was enough. However added a few more nice features which is very helpful when you need it

  1. Support sending a range of lines (visual selection) at a time. E.g. Send a function block to a python shell
  2. Add a delay between lines in milliseconds. This is useful when the previous command reads from standard input and takes some time to complete
  3. Support sending ctrl characters like ctrl d, ctrl c etc,
def g:SendRangeToTerminal(start_line: number, end_line: number)
    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(str2nr(arg1), (_) => g:SendRangeToTerminal(line_num, end_line))
                return
            elseif cmd == "ctrl"
                terms[0]->term_sendkeys(nr2char(char2nr(arg1) - 96))
                continue
            endif
        endif
        terms[0]->term_sendkeys(line .. "\<CR>")
    endfor
enddef

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

More cool mapping

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

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

Or just hit Enter?

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

Automatically add those mappings for cheat.sh

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

Demo

{{< asciinema key="vimstt" >}}

Using

Copying above snippets to your vimrc should work in a recent vim. Checked in ubuntu 22.04 and archlinux. Git: repo

neovim/tmux/screen

Since neovim uses a different terminal API, above snippets don't work in neovim. vim-slime plugin (available since 2007!) supports different types of terminal. However it does not support adding delay and ctrl characters in text.