termsay

the cow says moo the pig says oink the terminal says whatever you tell it to


When I'm on windows I use gvim instead of using vim in a terminal as I would normally do on Linux or MacOS. I also don't use tmux since it isn't available on windows. (Other than via WSL.)

The main thing I use tmux for is not for multiplexing but rather the ability to easily send commands to an arbitrary tmux window.

A similar thing is possible with vim when it is compiled with +clientserver, and luckily, gvim has that feature.

So I have this function in my vimrc:

if has('win32')
    fun! Termsay(msg)
        let l:filename = expand("%:p:t")
        let l:parentdirname = expand("%:p:h:t")
        let l:servername = toupper(l:parentdirname . "_" . l:filename)
        let l:startcmdfmt = "start /B gvim.exe --servername %s"
        let l:startcmd = printf(l:startcmdfmt,l:servername)
        if match(serverlist(),l:servername) == -1
            call system(l:startcmd)
            sleep 333m
            call remote_send(l:servername,":term ++curwin ++kill=kill<cr>")
        endif
        call remote_send(l:servername,a:msg."<cr>")
    endfun
endif
            

Along with this command:

if has('win32')
    com! -nargs=1 Termsay call Termsay(<q-args>)
endif
            

Coupled with an autocommand:

aug DeskPy
    au!
    au BufNew,BufReadPost ~/Desktop/*.py call DeskPySetup(expand("<afile>"))
aug END
            

Which executes (in this example) when editing python files on my Desktop or in any folder therein. (Remember: the glob character * in an autocommand is recursive!)

The autocommand runs this function which sets things up for python and defines a mapping:

fun! DeskPySetup(afile)
    if has('win32')
        let l:prog = expand("~/anaconda3/python.exe")
        exe printf("nmap <buffer><F12> :Termsay %s %s <cr>",l:prog,a:afile)
    endif
endfun
            

The end result is that I can press F12 to run whatever I'm editing in a terminal.

It doesn't block my editing window waiting for me to press ENTER. It doesn't steal my focus by switching to the other terminal either.

How it works: Vim can talk to another vim instance with +clientserver, using remote_send()

The main vim instance starts the secondary vim instance using

start /B gvim.exe --servername FOO_BAR_NAME
            

Then you can send keystrokes to that vim instance like this:

call remote_send(FOO_BAR_NAME,"QUOTED STRING OF KEYSTROKES")
            

(In order to tell it to press return and execute the keystrokes as a command, you send the string "<cr>".)

Since vim can run a terminal inside itself, we tell the new vim instance to make one of those like this:

call remote_send(FOO_BAR_NAME,":term ++curwin ++kill=kill<cr>")
            

:term is the command. ++curwin makes the new terminal occupy the full screen instead of being a split, and ++kill=kill makes it not complain when you close vim with a running process.

So from then on we can send that vim instance commands and they will be run inside the terminal. In this case, I want to tell python to run the file I am editing.

let l:filename = expand("%:p:t")
            

The name of the file currently being edited -- you get that with expand("%:p:t")

% is the current buffer. The modification :p gets the path to the file in the buffer. The modification :t gets the tail of the path, aka the name.

The secondary vim instance needs a server name so that we can direct messages to it, so I name the server based on the directory of the file I am editing plus the name of the file.

let l:parentdirname = expand("%:p:h:t")
let l:servername = toupper(l:parentdirname . "_" . l:filename)
            

expand("%:p:h:t") gives that value. The :h modifier give the head of the path -- everything that's not the tail, that is. So the tail of the head of the path of the buffer is the name of the directory.

They say a picture is word a thousand words. I don't know how many words an animation is worth but here is this setup in action: