Vim as a Simple IDE without Plugins
1 Introduction
There are a vast number of options for text editors, but one compelling reason to stick with the classic Vim is that it comes packaged with many Unix-based operating systems such as Linux. It is free, open source, and available almost everywhere. Despite its humble appearance, Vim is a powerful tool with extensive features and can be heavily customized.
This post demonstrates some configuration changes that will let us use Vim as a simple integrated development environment (IDE), without the need for any additional plugins or a graphical environment. Here, one Vim window can be used to edit and run code and a second Vim window can be running a console. While editing the code, we can use a few keystrokes to run snippets in the console. We also make it easy to launch the console when we are editing a file. A small demonstration of the result is given in Figure 1; here we launch an R console using one of our keybindings, issue some R commands from the script using several other bindings, and then close Vim.
A file with the all of the functions and keybindings is given here. The easiest way to use it is to paste the contents into your own vimrc file; however, it is also possible to use a more modular configuration with multiple files.
This material was developed using version 9.1.1882 of Vim. The embedded terminal that we will need was introduced in version 8.1.
If you would prefer more sophisticated IDE capabilities than DIYing, see vim-slime, R.nvim for R within Neovim, julia-repl-vim for Julia. There are probably also some good options for tmux if you’d like persistant terminal sessions or are not primarily focused on Vim.
The code shown in this post is written in the Vimscript scripting language. I am not very familiar with Vimscript, but was able to get things working with some help from AI and various forum posts. There are certainly opportunities for improvement in this code.
Notice, for example in Figure 1, some of the content appears out of order as it is sent to the console. This is only a visual artifact: the code actually runs correctly, but we would prefer the display to be correct as well.
2 Embedded Terminal
We first note that Vim has its own internal terminal. We will make use of this to run an intrepreter for our language(s) of choice. To start a terminal from Vim, enter :term from normal mode. This starts the terminal in a horizontal split. See the documentation on windows on how to change focus between windows, change their positions, resize them, etc. Exiting the terminal closes the split. We can also start a terminal with a vertical split using :vert term from normal mode.
Suppose we have a vertical split with an R script on the left and a terminal running the R console on the right. We would like to be able to send text from the editor to be execute in the R console without too many keystrokes. We would also like the ability to quickly spawn that R console when we are working on an R script.
We will now define two functions.
3 Send Text to the Terminal
Our first function takes a string content and sends it to the terminal. There may be multiple terminals; in this simple implementation - and because I have never used more than one terminal in the same session - we will always send our content to the first terminal in the list.
The function term_list() returns an array of terminal buffer numbers. If there are no terminal buffers, a notification to the user is printed with echom. Otherwise, the argument content is sent to the selected terminal using term_sendkeys.
4 Spawn a Terminal with a Console Program
The second function spawns a terminal with a suitable program to handle the type of file we are currently editing. If we don’t have a specific program in mind, we will spawn the terminal and just leave it at the command line.
The function takes a boolean argument vert; the terminal will be started with a vertical split if vert is true, otherwise a horizontal split will be used. After the terminal is spawned, we exchange positions with the previous window. In vertical mode, this puts the script on the left and the terminal on the right. In horizontal mode, the script will be on top and the terminal will be underneath. This will also shift focus back to the script.
function! FileTypeToTerm(vert)
if &filetype == 'r'
let cmd = "R\n"
elseif &filetype == 'julia'
let cmd = "julia\n"
elseif &filetype == 'python'
let cmd = "python\n"
else
let cmd = ""
endif
if a:vert
execute "vertical terminal! " . cmd
else
execute "terminal! " . cmd
endif
execute "normal! \<c-w>x"
endfunctionI have defined programs for three specific languages. Of course, you can modify this for your own cases.
The strings 'r', 'julia', and 'python' represent designated file types in Vim. To display the file type for the currently open buffer, use the command :echo(&filetype) in normal mode.
When cmd is a non-empty command, the terminal will halt when the command exits. However, the Vim window will remain open in the halted state until we exit (e.g., via :q). This allows us to note any error messages from our code, in case the console crashed or the exit was otherwise not intentional.
5 Keybindings
We will set several keybindings to interact with our functions. First, let us define the leader key, assuming it has not been defined already. A space is commonly used for this purpose.
The following to send content from a script to the console.
The nnoremap binding is used in normal mode. It sends the current line under the cursor to the console and moves cursor to the next non-whitespace character after the line. The move is accomplished by pattern-matching for \S, with option W to avoid wrapping back to the beginning when we have reached the last line of non-whitespace. The command :nohl is used to suppress highlighting during pattern searching.
The xnoremap binding handles two distinct cases. The first case handles visual line mode, where entire lines are selected at a time. Here we send the entire selection - which already includes a newline at the end - to the console, then move the cursor at the end of the selection using <backtick> then >.
The second case handles regular visual mode which is character-by-character. Here the selection ends with a non-newline character, so we explicitly send a newline at the end.
The normal mode binding allows us to type <space><space> repeatedly to step through the script and run commands line-by-line. The visual mode bindings allow us to highlight text in visual mode and type <space><space> to send it to the console.
The next set of keybindings initializes our console: <space>th opens a console with a horizontal split and <space>tv opens a console with a vertical split.