Disclaimer: this answer is more vim-centric than it is neovim-centric because I use the one and not the other. However, techniques shown are broadly applicable to both editors, as they share the same features.
TL;DR
Simply making use of built-in features and some scripting/maps, we can accomplish a lot.
But I also turned my approach into a plugin called simpl.
Feature Number 1: Terminals in the Editor
(Neo)Vim provides a builtin terminal emulator via :terminal
. There are other techniques to access the shell (including job control, !
, and tmux), but this is the closest to a REPL in the editor.
Learn to use it (both in Normal and Terminal modes!).
Feature Number 2: Terminal by Filetype
I recently realized I was using :term python
frequently when working on python code, and :term scala
for scala code, etc. Noticing a repeatable pattern, I generalized and abstracted.
I created a function to control terminal behavior based on a buffer setting:
" ~/.vim/autoload/terminal.vim
" Functions for dealing with the terminal
" Global function for calling terminal with the appropriate interpreter
" b:interpreter controls the program run
function! terminal#run(...) abort
let l:interpreter = get(b:, 'interpreter', &shell)
let l:command = 'term'
if a:0
for l:opt in a:000
let l:command .= printf(' %s', opt)
endfor
endif
let l:command .= printf(' %s', l:interpreter)
execute l:command
endfunction
Then, in my (many) after/ftplugin
files (available in the same repo), I set b:interpreter
as needed—the default is to launch a shell, but I get python, irb, scala, even jdb as necessary.
Feature Number 3: Mappings
Finally, I set up mappings to launch said terminal in my vimrc.
" Terms and interpreters
" set b:interpreter for filetypes to affect the term
nnoremap <silent> <Leader>t :call terminal#run('++close')<CR>
nnoremap <silent> <Leader>T :call terminal#run()<CR
Feature Number 4: Scriptability and Custom Operators
Vim’s scriptability is what makes it king, so we script some more. Custom operators mean we can operate over any motion of text to do whatever we want (in this case, send it to a terminal).
The plugin habamax/vim-sendtoterm accomplishes the last piece of the puzzle in a single, short script. It even supports neovim, and the Readme has instructions to use control-enter and go to next line.
Once you have this infrastructure in place, it should be fairly easy to map a key sequence of your choosing to
- Start REPL if not running (may have to set a
b:repl_running
to the window number or buffer number when opening the specified terminal)
- Get the text of the current line and send it to said REPL
- Move the cursor down one line.
Copy/paste work well enough that I’ve never needed the send-line functionality.
I ended up creating vim-simpl to meet both the original terminal#run
needs and the "load this file in the REPL" needs. It still requires the old b:interpreter
setup, and you get to make your own mappings/commands. It also only supports a few languages out-of-box (mostly because I haven't implemented any others), but it's easy to register new ones.