Coding Python in Neovim with IPython as a REPL
Most of the time at work I am currently doing machine learning / data science using the Python ecosystem. My editor of choice for working in Python has become Neovim, which really works well for autocompletion and linting based on Neomake, UltiSnips, deoplete and deoplete-jedi. However, one thing I have been missing was a tight integration with the IPython / Jupyter Console REPL in order to quickly experiment with new code fragments in a fashion like SLIME for Emacs: simply select a few lines of code and send them to IPython using a command / binding. Finally, I have found something that works well, which I will explain here.
First try: vim-slime
The first Vim plugin that I have been using in order to replicate a SLIME-like behavior was vim-slime, which uses tools like screen or tmux to get the text into the target REPL application. That means you have to start IPython inside tmux and then configure vim-slime to connect to a specific session. While this works reasonably, it adds a tmux or screen layer around your IPython experience with all the clunky keyboard shortcuts that you have to remember then. It gets even worse if you want to use the Neovim terminal, which adds another layer of escape sequences around this. Also, it feels quite weird to start tmux inside the Neovim terminal just for this purpose. Finally, with some changes to IPython over the time, suddenly some heuristics for sending multi-line text stopped working correctly and I assume this might happen over and over again.
Principled approach: nvim-ipy
Therefore, my second attempt was to try something more principled. Since Jupyter started to appear in the Python ecosystem, where IPython is just a Kernel and several clients can attach to a running kernel, it would be nice if there was a way for Neovim to directly connect a running IPython kernel and send input there. nvim-ipy implements this for Neovim as a Python plugin. However, there are several drawbacks of this particular implementation that lead me to stop using it:
- The implementation quality of this particular plugin is quite low and a lot of corner-cases (e.g. reconnecting to a new kernel without restarting Vim) are not handled, which reduced the user experience.
- Getting the correct kernel in combination with virtualenvs and Miniconda is quite complex with kernel registries etc.
- Most important, this plugin open a buffer for the output that is produced when sending code to the IPython kernel you are connected to. This buffer is not a usual IPython REPL that can be used easily. So, what you have to do is to start Jupyter Console somewhere and connect the Vim plugin to this instance. Consequently, you have two window representing the same kernel, but the inputs you send to this kernel via Vim and the respective outputs are only visible in the created buffer and cannot be reused in the normal console session. That means you always have two observe two different windows and are constantly missing execute code for interactive changes in the REPL.
With the aforementioned drawbacks, I finally moved to iron.nvim. This doesn't use the kernel API of Jupyter but instead starts IPython inside a Neovim terminal and multiple other REPLs and languages are supported, too. This means that the additional buffer of nvim-ipy is gone and executed code fragments are directly available in he history of the REPL and als tmux isn't needed anymore. iron.nvim also correctly picks up IPython from Miniconda via a correctly set