VIM and Tmux as a tiny quasi IDE

watch video

There are multiple approaches to development and everyone has a some style of how they like to approach this process. Every task requires could require totally different approach and different IDE. So if I do native Android or Flutter developemnt it is natural for me to Android Stuio, for Java code it is Intellij, for Qt it is more comforatable if Qt Creator is used.

Here I use pharase “quasi IDE” to separate from trandional understanding of an IDE. Usually IDE is something that can autocomplete, do debugging, version controll and billion other things. The only requirement for our “tiny IDE” is to efficiently write code, then run it, see results and do all this seamlessly. (Although you could easily add autocomplete with vim plugins). I use this setup without any plugins and you can spice it more if you need.

The idea is super simple but yet I have never seen this being used anywhere. What I see from presentations and videos is that people write code, exit vim, execute it find errors, reenter vim then rinse and repeat. I myself just open a new pane and send commands with vim to it. It works very good when you are working on tiny projects or single files.

Vim and tmux as fancy calculator

So image above you can see a session of python developement I did a few days ago. In this setup is like a fancy calculator. I was reading a book on networking where it claimed that 100Gb link needs five nanosecods of processing time for a 64 byte payload before there will be another bit knocking on the door. Me be being me of corse didn’t trust and didn’t fully understand the reasoning so I opened tmux/vim to do this calculation. You might argue that you can do the same calculation in python terminal but for me it is easier to jump around the the vim buffer and send the whole buffer to python for execution. Thih way I can quickly change variable and can see them all at the same time.

To do this I just use ‘autocmd’ inside vim to create a map that sends command to the other pane and execute whatever we want. This is how it works for python.

autocmd FileType python nnoremap <leader>m :!tmux send-keys -t .+ "python " % c-l Enter <enter> <c-l>

Here I create a mapping to m to execute tmux command that sends “text” to other pane. This text is “python " % and then press enter. <Control-l> is used to clear screen so that I see only latest output. Percent is expanded into the name of current file. So in the example above vim will send “python test” to other pane everytime I press <leader>m.

autocmd FileType python nnoremap <leader>m :w<enter>:!tmux send-keys -t .+ "python " % c-l Enter <enter> <c-l>
autocmd FileType dart   nnoremap <leader>m :w<enter>:!tmux send-keys -t .+ "dart " % c-l Enter <enter> <c-l>
autocmd FileType cpp    nnoremap <leader>m :w<enter>:!tmux send-keys -t .+ "./ " c-l Enter <enter> <c-l>
autocmd FileType sh     nnoremap <leader>m :w<enter>:!tmux send-keys -t .+ "./% " c-l Enter <enter> <c-l>
autocmd FileType groovy nnoremap <leader>m :w<enter>:!tmux send-keys -t .+ "groovy %"  c-l Enter <enter> <c-l>

For each filetype I create the same mapping <leader>m. It is very conviniet as I can press the same combo to execute pretty much any code, similar to F5 in real IDE. It does not have to be code execution. For example for the C++ code I use this method to send compiled shared library to remote android phone via a script, pretty handy.

One neat feature feature that you can add additionaly to that is to send whole buffer or just a clipboard to the other pane. I almost never use it as it all the programming languages that I used are good with sending files. But there is one way where it is useful in my work - it is sql. On the other pane I can open postgres/mysql/sqlite terminal and send commands directly there. It is quite useful. I know that there are more functionality in real IDE’s but this one closes 90% of usecases and pretty good for simple tasks.

But this one requires a bit more fiddling. First of all I add a couple functions to my vimrc. Here they are:

function! SendSelection()
    let l:buffer = GetVisualSelection()
    let l:buffer = substitute(l:buffer, "\n", "\r", "g")
    exec "silent !tmux send-keys -t .+ C-l \"" . l:buffer . ";\""
    exec "silent !tmux send-keys -t .+ Enter"

function! GetVisualSelection()
    let [line_start, column_start] = getpos("'<")[1:2]
    let [line_end, column_end] = getpos("'>")[1:2]
    let lines = getline(line_start, line_end)

    if len(lines) == 0
        " let [line_end, column_end] = getpos("$")[1:2]
        " let lines = getline(0, line_end)

        echo "--------------------------------"
        echo "-   Select lines to send       -"
        echo "--------------------------------"
        return ""
    return join(lines, "\n")

And then I just add one more autocmd like this:

autocmd FileType sql    noremap <leader>m :<c-u>call SendSelection()<CR>

This will send selected lines to the other pane and press enter to execute it. You can go to town with and send exactly selected text or do formatting before sendin and et. I used to send whole buffer if it didn’t have a selection but then I removed it since I often sent whole buffer by forgetting to select. Since I rarely send in full and when I need I can just ggVG I dicided to remove it.

Apart from this I use this method and it does quite a lot for me. As long as I don’t need a real debugger and can get by with printf debugging this is very useful.


I recorded a short video where I demo doing small tasks with dart, bash script, groovy, sql and python to show how it all work together and how unified workflow for different languages becomes.