Introduction

If you write code in multiple languages you may have found it tiresome to maintain an IDE for each. After some experimentation I found the combination of Vim and Tmux to be the ideal solution for my case. The main benefits are:

✓ One environment with support for any language
✓ Efficient keyboard-only file editing
✓ Easy installation in any new environment

However, being a text editor and not an IDE, this solution lacks some important features out of the box. Fortunately, both Vim and Tmux are highly configurable. This article shows how to configure Vim and Tmux to support a list of critical features for Python development. Although Tmux plays an important part, most of the configuration concerns Vim.

Prerequisites:

This workflow is designed to run on a Unix system like Linux or MacOS. To start, create a .vimrc file in the home/ directory as well as an empty folder .vim/. Install Vim, Tmux and (optionally) IPython. Some familiarity with Vim and its configuration through the .vimrc file is assumed. If you don’t know how to install Vim plugins take a look at this article.

What features are really necessary?

Below is an opinionated list of features that make Python development in Vim more sensible:

  1. One environment for multiple languages
  2. Easy access to the terminal
  3. Smooth directory browsing
  4. Sending lines of code to a Python console
  5. Running a Python file in the terminal
  6. Interactive debugging

1. One environment for multiple languages

Vim can be configured for each file type independently. For example, Vim can be configured to use four spaces for a tab when it opens a Python file but two spaces when it opens an R file. To enable file-specific settings put the following line in the .vimrc file:

filetype plugin indent on

The indent part enables file specific indentation. Create the directory .vim/after/ftplugin. Files in ftplugin are named after the language (e.g. python, json, r, sh) and have the .vim extension. Below is an example for JSON, Python, R and Bash:

.vim
└── after
    └── ftplugin
	    ├── json.vim
	    ├── python.vim
	    ├── r.vim
	    └── sh.vim

The idea is to put any .vimrc configuration that only applies to a particular language into the corresponding file in ftplugin. In that sense, files in ftplugin can be viewed as “language-specific .vimrc” files. These files are sourced after .vimrc and overwrite any general settings.

For example, Python-specific indentation should be put in the python.vim file:

setlocal tabstop=4
setlocal shiftwidth=4
setlocal softtabstop=4
setlocal expandtab

Notice the use of setlocal so that these settings only overwrite the general settings from the .vimrc file for the current buffer only. The next chapters in this article follow the convention of putting language-specific settings in ftplugin and general Vim settings in .vimrc.

2. Easy access to the terminal

alt text

This feature only works if Vim runs inside the terminal. In that case Vim can be suspended by running the Ex command:

:stop<CR>

This moves Vim to the background and switches the screen back to the terminal from which Vim was called. Running fg in the terminal moves Vim back to the foreground. Switching between Vim and a full-screen terminal is so convenient that it’s worth creating a mapping in .vimrc:

nnoremap <leader>t :stop<CR>

3. Smooth directory browsing

alt text

IDEs generally provide a file explorer built as a project drawer. This approach doesn’t mix well with Vim’s window style workflow. After all, how does Vim know in which window you want to open a file you selected from the project drawer?

Vim already comes with a built-in plugin called netrw. To understand this plugin, imagine Vim windows as flipping cards: on one side you have your file and on the other side the netrw file explorer.

It’s useful to map two key bindings for file browsing in .vimrc. The first one opens netrw at the current file’s directory. The second key binding opens netrw at the current working directory.

nmap <leader>f :Explore<CR>
nmap <leader><s-f> :edit.<CR>

Netrw has some annoying defaults though. It’s recommended to put these two additional netrw settings in .vimrc. The first setting allows to open a file in a right split. The second setting suppresses netrw from saving .netrwhist files in the .vim folder.

let g:netrw_altv = 1
let g:netrw_dirhistmax = 0

Finally, a minimal plugin called vim-vinegar makes netrw more sensible and comes with several useful shortcuts:

  • Press - in any buffer to hop up to the directory listing and seek to the file you just came from.
  • Press . on a file to pre-populate it at the end of a : command line. There’s also !, which starts the line off with a bang.
  • Press y. to yank an absolute path for the file under the cursor.

4. Sending lines of code to a Python console

alt text

Tmux allows to split the screen horizontally into two terminal windows: the upper one for Vim (possible with multiple vertical split windows) and the bottom one containing a IPython console. If you dislike IPython the above procedure will work just as well with the regular Python console.

Next, the plugin vim-slime is used to send selected code from Vim to the IPython console. The following to configuration in the .vimrc file enables this behavior:

let g:slime_target = "tmux"
let g:slime_default_config = {"socket_name": get(split($TMUX, ","), 0), "target_pane": ":.1"}

Pressing C-c, C-c (holding Ctrl and double-tapping C) sends the paragraph beneath the cursor to the IPython console. (The first time vim-slime will prompt the target pane. Press Enter twice.) The above procedure is language agnostic: the same procedure allows to send lines of R code to an R console or lines from a Bash script to the terminal.

5. Running a Python file in the terminal

alt text

It’s convenient to have a shortcut to run the current script in the terminal. Generally, this is done by calling the interpreter followed by the file name:

python filename.py

The following mapping in defined in python.vim runs two consecutive Ex-commands:

nmap <buffer> <leader>r <Esc>:w<CR>:!clear;python %<CR>

Here’s what it does:

  1. The first Ex-command :w<CR> saves the file.
  2. The second Ex-command starts with :! indicating it’s meant meant for the terminal, clears the terminal with :clear and finally calls the interpreter python %<CR> on the current file whose path is given by %.

Notice that this mapping is language-specific. A similar mapping to run a bash script can be defined as follows:

nnoremap <buffer> <leader>r <Esc>:w<CR>:!clear;sh %<CR>

6. Interactive debugging

alt text

Debugging is an area where IDEs really shine. Nonetheless, by installing the Python ipdb a satisfactory debugging experience can be obtained in Vim.

The module ipdb is similar to pdb but designed for IPython. It doesn’t have to be installed as it comes with IPython. It allows to define a breakpoint with set_trace() which will pause the program at that point and drop start the debugger where the program’s state can be inspected using IPython.

The following Vim mapping in python.vim puts a breakpoint below the current line:

nmap <buffer> <leader>b oimport ipdb;ipdb.set_trace(context=5)<ESC>

The context argument specifies the number of lines shown by the debugger.

A nice feature if IPython is embed which launches a separate IPython session during debugging. Changes made in this session will not affect objects in the original debugging session.

ipdb> from IPython import embed
ipdb> embed()

A further review of the debugger commands is beyond the scope of the article. They are nicely documented in the documentation in the referenced below.

Conclusion

The configurations in this article will help you get started with Vim and Tmux for your Python development. Other useful features such as linting, code completion and jumping to definitions were not covered. Take a look at my personal Vim configuration for a more complete example of what’s possible.


References:

Keep your vimrc clean

Terminal Vim versus GUI

You don’t need NERDTREE or (maybe) netrw

Oil and vinegar — split windows and the project drawer

vim-vinegar

vim-slime

The Tao of Tmux

Running Python code in Vim

pdb documentation