A Month of Haskell, Day 3 - vim configuration

Posted on May 3, 2017 by Chris Lumens in month-of-haskell, vim.

My favorite text editor is vim. While there are certainly better IDEs out there, vim is great at editing text and can be turned into a reasonable IDE with enough plugins and helper programs. It also has the advantage of being a text-based program that can easily be run over an ssh session.

This post is about how you can configure vim to be a better IDE for Haskell. There are a ton of other documents out there explaining this very thing, including an excellent one from Stephen Diehl. I’ve borrowed from him a bit, but made a lot of my own changes. There are also some projects up on github that look like you just clone them, install, and they make all the necessary changes. While that sounds fast, I’m not as much of a fan of that because it seems to take over your home directory.

This is going to be a long one.

Cabal

First, I’m going to assume you are starting with a clean ~/.cabal and ~/.ghc directory. If that is not the case, you might want to check if there’s anything useful in there and clear them out if not. I find they can easily get crudded up over time. We’re going to be installing various Haskell programs which will result in a lot of libraries being installed, and it’s nice to not have old versions of things laying around.

$ rm -r ~/.cabal ~/.ghc
$ cabal update
Config file path source is default config file.
Config file /home/clumens/.cabal/config not found.
Writing default configuration to /home/clumens/.cabal/config
Downloading the latest package list from hackage.haskell.org
$

I like to build my Haskell programs dynamically linked and my libraries as shared. This may or may not be the default, depending on your environment. You don’t have to do this - it’ll just result in everything being gigantic. Edit ~/.cabal/config.

Before:

-- shared:
-- executable-dynamic: False

After:

shared: True
executable-dynamic: True

You should also make sure your $PATH contains $HOME/.cabal/bin. This is very environment specific, too. On my systems, I have a /etc/profile.d/cabal-install.sh program that does it for me. This is a good way to not have to deal with the variety of shell config files in your home directory. I can never remember which one works in which case.

$ cat /etc/profile.d/cabal-install.sh
CABALBIN=${HOME}/.cabal/bin

if ! echo ${PATH} | /bin/grep -q ${CABALBIN} ; then
    if [ -d ${CABALBIN} ]; then
    PATH=${PATH}:${CABALBIN}
    fi
fi
unset CABALBIN

Installing Haskell programs

Now it’s time to install a bunch of Haskell support programs. These may already be installed on your system, but if so they are likely old versions. I prefer to have the latest everything. We’re going to be installing:

I’ll discuss what these can do for you as we go along. At the time I’m writing this, I can’t install ghcmod at the same time as everything else, but it’s easy enough to run it in two cabal commands:

$ cabal install happy hlint pointfree hoogle
Resolving dependencies...
<lots of downloading, configuring, and installing>
Installed hoogle-5.0.11
Installed hlint-2.0.5
$ cabal install ghc-mod
Resolving dependencies...
<lots of downloading, configuring, and installing>
Installed ghc-mod-5.7.0.0

hoogle

Hoogle, like its name suggests, is a search program for Haskell functions. It searches against a local database, which you have to generate. You should probably create a crontab entry to regenerate it periodically (daily? weekly? monthly?) to keep its results relevant:

$ hoogle generate
Starting generate
Reading Cabal... 6.93s
<lots of output follows>
Reodering items... 0.26s
Writing tags... 2.05s
Writing names... 0.97s
Writing types... 6.62s
Took 2m05s

With it installed, you can now search by function name:

$ hoogle mapM_
Prelude mapM_ :: (Foldable t, Monad m) => (a -> m b) -> t a -> m ()
Control.Monad mapM_ :: (Foldable t, Monad m) => (a -> m b) -> t a -> m ()
Data.Foldable mapM_ :: (Foldable t, Monad m) => (a -> m b) -> t a -> m ()
Data.Vector mapM_ :: Monad m => (a -> m b) -> Vector a -> m ()
Data.Vector.Fusion.Bundle mapM_ :: Monad m => (a -> m b) -> Bundle v a -> m ()
Data.Vector.Fusion.Bundle.Monadic mapM_ :: Monad m => (a -> m b) -> Bundle m v a -> m ()
Data.Vector.Fusion.Stream.Monadic mapM_ :: Monad m => (a -> m b) -> Stream m a -> m ()
Data.Vector.Generic mapM_ :: (Monad m, Vector v a) => (a -> m b) -> v a -> m ()
Data.Vector.Primitive mapM_ :: (Monad m, Prim a) => (a -> m b) -> Vector a -> m ()
Data.Vector.Storable mapM_ :: (Monad m, Storable a) => (a -> m b) -> Vector a -> m ()
-- plus more results not shown, pass --count=20 to see more

You can also search by type signature:

$ hoogle 'm a -> m b'
Control.Concurrent.Lifted runInBoundThread :: MonadBaseControl IO m => m a -> m a
Control.Concurrent.Lifted runInUnboundThread :: MonadBaseControl IO m => m a -> m a
Control.Exception.Lifted mask_ :: MonadBaseControl IO m => m a -> m a
Control.Exception.Lifted uninterruptibleMask_ :: MonadBaseControl IO m => m a -> m a
CorePrelude mask_ :: MonadBaseControl IO m => m a -> m a
CorePrelude uninterruptibleMask_ :: MonadBaseControl IO m => m a -> m a
Control.Reference.Predefined mask_ :: (MorphControl IO m) => m a -> m a
Agda.TypeChecking.Monad.Env performedSimplification :: MonadReader TCEnv m => m a -> m a
Agda.TypeChecking.Monad.Signature inAbstractMode :: MonadReader TCEnv m => m a -> m a
Agda.TypeChecking.Monad.Signature inConcreteMode :: MonadReader TCEnv m => m a -> m a
-- plus more results not shown, pass --count=20 to see more

You can also get documentation on the first result:

$ hoogle --info sequence
sequence :: (Traversable t, Monad m) => t (m a) -> m (t a)
base Prelude
Evaluate each monadic action in the structure from left to right, and
collect the results. For a version that ignores the results see
sequence_.

The manual has more information including how to restrict results. One more nice thing you can do with hoogle is integrate it with ghci (the Haskell REPL) to get information when you are experimenting. Add the following to ~/.ghc/ghci.conf:

:def doc \x -> return $ ":!hoogle --info \"" ++ x ++ "\""
:def hoogle \s -> return $ ":!hoogle --count=15 \"" ++ s ++ "\""

And then from ghci you can use the new :doc and :hoogle commands you’ve defined:

$ ghci
GHCi, version 8.0.2: http://www.haskell.org/ghc/  :? for help
Loaded GHCi configuration from /home/clumens/.ghc/ghci.conf
Prelude> :hoogle <$>
Prelude (<$>) :: Functor f => (a -> b) -> f a -> f b
Control.Applicative (<$>) :: Functor f => (a -> b) -> f a -> f b
Data.Functor (<$>) :: Functor f => (a -> b) -> f a -> f b
-- plus more results not shown, pass --count=25 to see more

Prelude> :doc *>
(*>) :: Applicative f => f a -> f b -> f b
base Prelude
Sequence actions, discarding the value of the first argument.

Prelude>

lushtags

Next up is lushtags, which is like any of the other tag generating programs out there, but is specifically written with Haskell and vim in mind. Features include showing type signatures, grouping tags by type, and generating tags on-the-fly so you don’t have to do anything. It requires a fair bit of configuration on the vim side, but we’ll get to that. For better integration with vim plugins we’ll be installing, you need a special forked version of lush tags.

$ mkdir src
$ cd src
src$ git clone https://github.com/mkasa/lushtags.git
src$ cd lushtags
src/lushtags$ cabal install
Resolving dependencies...
<more downloading, configuring, and installing>
Installed lushtags-0.0.2.1

vimproc

vimproc is a compiled vim module for asynchronous execution. It is required for ghc-mod to work correctly. You otherwise don’t use it for anything. It’s kind of a pain to install.

$ cd src
src$ git clone https://github.com/Shougo/vimproc.vim.git
src$ cd vimproc.vim
src/vimproc.vim$ make
make -f make_unix.mak
make[1]: Entering directory '/home/clumens/src/vimproc.vim'
cc -W -O2 -Wall -Wno-unused -Wno-unused-parameter -std=gnu99 -pedantic -shared -fPIC  -o lib/vimproc_linux64.so src/proc.c -lutil
make[1]: Leaving directory '/home/clumens/src/vimproc.vim'
src/vimproc.vim$ mkdir -p ~/.vim/autoload ~/.vim/lib ~/.vim/plugin
src/vimproc.vim$ cp -R autoload/* ~/.vim/autoload
src/vimproc.vim$ cp lib/vimproc_linux64.so ~/.vim/lib
src/vimproc.vim$ cp plugin/vimproc.vim ~/.vim/plugin

vundle

There’s just one more step and we can start configuring vim. There’s a lot of vim plugins out there now, so plugin managers have become popular. There’s several big ones. I started using vundle and haven’t found any need to switch to anything else. The basic idea is you install it, configure your .vimrc to list the plugins you want, and then use the :PluginInstall command from within vim to install them all. There’s also a :PluginUpdate to update things and a :PluginClean to remove plugins you no longer care about. Plugins themselves just live in git checkouts in ~/.vim/bundle.

First step, check out the source:

$ git clone https://github.com/VundleVim/Vundle.vim.git ~/.vim/bundle/Vundle.vim
Cloning into '/home/clumens/.vim/bundle/Vundle.vim'...
remote: Counting objects: 3117, done.
remote: Total 3117 (delta 0), reused 0 (delta 0), pack-reused 3117
Receiving objects: 100% (3117/3117), 927.92 KiB | 764.00 KiB/s, done.
Resolving deltas: 100% (1096/1096), done.

Add this to the very top of your .vimrc:

set nocompatible
filetype off

" set vundle location and initialize
set rtp+=~/.vim/bundle/Vundle.vim
call vundle#begin()

" let vundle manage vundle
Plugin 'gmarik/Vundle.vim'

" other plugins go here
Plugin 'bling/vim-airline'
Plugin 'eagletmt/ghcmod-vim.git'
Plugin 'ervandew/supertab'
Plugin 'majutsushi/tagbar'
Plugin 'mkasa/neco-ghc-lushtags'
Plugin 'neovimhaskell/haskell-vim.git'
Plugin 'scrooloose/syntastic'
Plugin 'tpope/vim-fugitive'
Plugin 'tpope/vim-commentary'
Plugin 'Twinside/vim-hoogle'

call vundle#end()
filetype plugin indent on

Now start vim and run :PluginInstall:

All the listed plugins are now installed. So what exactly do they all do? I’ll cover them in detail as we configure them, but here’s a brief description:

With all these plugins installed, you can also copy ghc-mod-cache into place to drastically speed up using ghc-mod from within vim. It may still be slow the first time you edit a source file, but later edits should be much faster.

$ cp .vim/bundle/neco-ghc-lushtags/bin/ghc-mod-cache ~/.cabal/bin
$ chmod +x ~/.cabal/bin/ghc-mod-cache 

vim configuration

Finally, we’re to the point where we can configure vim! I’m going to step through this one section at a time, showing what’s in my .vimrc and why it’s there. Along the way, I’ll explain how to use the various plugins and have some pictures of each. First, there’s some global settings that I’m not going to explain. You can easily read about each with the :help command in vim. But here they are:

" Wipe out any previous commands.
autocmd!

"""
""" GLOBAL SETTINGS
"""

set backspace=0
set completeopt=menuone,menu,longest
set expandtab
set laststatus=2
set nohlsearch
set pastetoggle=<F8>
set shiftwidth=4
set softtabstop=4
set tabstop=4
set wildignore+=*.o,.sw*.git,.cabal-sandbox
set wildmenu
set wildmode=longest,list
set wm=6

"""
""" COLORS
"""

set background=dark
colorscheme industry

Status line

Next come the settings dealing with the fancy status bar. airline is extensively configurable and integrates with tons of vim plugins. I recommend digging through :help airline to set it up exactly as you want. My configuration is pretty simple:

"""
""" STATUS LINE SETTINGS
"""

let g:airline_symbols_ascii = 1

" I don't care about the file type, encoding, or format.
let g:airline_section_x = "%{airline#util#prepend(airline#extensions#tagbar#currenttag(),0)}"
let g:airline_section_y = ""

airline will automatically find and enable extensions based on what other modules you have installed, so there’s nothing to do unless you want to force something to be disabled. We have added support for the branch (display which git branch we’re on), syntastic (show where in the file syntax errors occur), and tagbar (display which identifier the cursor is currently in) extensions. I also disable the fancy unicode symbols that can get screwed up when ssh is involved.

The result (in a file with some syntax errors) looks like this:

There are two airlines in this picture, one for the buffer with the source file and one for the buffer with messages from syntastic. In the first airline, it is telling us the following things:

The second airline is much less interesting, and you should be able to figure out what it’s telling you.

ghc-mod

Next up is ghc-mod. This program runs in the background and provides information to the editor about your source code. It knows about the type of functions and values, can run hlint and other checkers, knows how to generate boilerplate code, and much more. It knows about stack and cabal sandboxes. Most of its configuration comes down to specifying extra arguments to pass to ghc and other programs.

"""
""" GHC-MOD
"""

au FileType haskell
    \ map <silent> <buffer> tc :GhcModTypeClear<CR> |
    \ map <silent> <buffer> ti :GhcModTypeInsert<CR> |
    \ map <silent> <buffer> tt :GhcModType<CR>

Here, I am binding keys to just three of its various functions. tt will display the type of whatever’s under the cursor:

Pressing it multiple times will show the type of the surrounding expression:

Pressing tc will clear it. Pressing ti on an identifier will paste its type signature on the line above. That’s not easy to take a picture of, so you’ll just have to trust me or try it out yourself.

I am purposefully leaving out GhcModSplitFunCase and GhcModSigCodegen because they don’t seem very helpful to me, and GhcModInfo and friends because we can use hoogle for that. If you try these and can make them do something interesting, let me know.

Tab completion

Tab completion is a pretty big thing in IDEs. Many of the vim plugins we installed earlier are needed to support this. Luckily, the vim configuration side of things is pretty simple:

"""
""" TAB COMPLETION
"""

function! EnableSuperTab() 
    let g:SuperTabDefaultCompletionType = '<c-x><c-o>'
    let g:haskellmode_completion_ghc = 0
    let g:necoghc_enable_detailed_browse = 1
    setlocal omnifunc=necoghc#omnifunc

    if has("gui_running")
        imap <c-space> <c-r>=SuperTabAlternateCompletion("\<lt>c-x>\<lt>c-o>")<cr>
    else
        if has("unix")
            inoremap <Nul> <c-r>=SuperTabAlternateCompletion("\<lt>c-x>\<lt>c-o>")<cr>
        endif
    endif
endfunction

autocmd FileType haskell :call EnableSuperTab()

I surround it in a function and use autocmd to make sure this only happens when Haskell source files are opened, to prevent errors from popping up. I’ve also enabled detailed browsing so type signatures are displayed.

As usual, :help supertab will show you more configuration options than you could ever care about. For now, I think the defaults are just fine. Using it is a simple matter of starting to type and then pressing tab. The neco-ghc-lushtags site goes into some good detail about what can be tab completed, complete with pictures. I’ll just show a few to get you excited about it.

You can tab complete imports:

You can tab complete data constructors from types you create:

You can tab complete language extension pragmas:

And of course you can also tab complete function names. With ghc-mod-cache, tab completion should be fast, at least after you’ve run it once per source file (or is it once per project?). This is one of the best features of this setup.

Tag browsing

On the right side of the screen is the tagbar, which shows all the identifiers in the source file. Enabling this requires a big annoying block in your .vimrc to define exactly which things you want to see:

"""
""" TAG BROWSING
"""

" Open tagbar for source code.
autocmd FileType c,cpp,haskell,python nested :TagbarOpen

To avoid pasting a giant block of crud into your .vimrc, you should instead do the following:

$ cp src/lushtags/plugin/tagbar-haskell.vim ~/.vim/plugin/

You’ll need to modify it, though. Change the ctagsbin line to point at the lushtags installed earlier:

\ 'ctagsbin' : '~/.cabal/bin/lushtags',

With that done, you get something like this (results will obviously vary depending on the source file contains):

This provides you a quick view of all the functions, data type constructors, imports, and exports. Clicking (or navigating to the tagbar and pressing plus/minus) on the headings will cause them to open and close. Clicking on an individual name will take you to the identifier’s definition in the source file. Pressing F1 in the tagbar window will show you various other ways to navigate.

Syntax checking

Adding the following gets you syntax checking for all the source files syntastic understands, not just Haskell:

"""
""" SYNTAX CHECKING
"""

let g:syntastic_always_populate_loc_list = 1
let g:syntastic_aggregate_errors = 1
let g:syntastic_auto_loc_list = 1
let g:syntastic_check_on_open = 1
let g:syntastic_check_on_wq = 0

set statusline+=%#warningmsg#
set statusline+=%{SyntasticStatuslineFlag()}
set statusline+=%*

It knows how to find installed checkers for many different languages, and you can use its extensive configuration options to define how to run others. :help syntastic goes into lots of detail. I find the options I’ve listed about reasonable. In particular, you may wish to tweak check_on_open and check_on_wq. Besides that, I haven’t felt the need to move beyond the defaults. Enabling it on a file with a syntax error looks like this:

It shows the error in several different ways: by putting a red arrow next to the line, by showing the error message in a special syntastic window at the bottom of the screen, on the status line, and with a pop up window if you hover over the line with your mouse. Obviously, the pop up won’t work if you aren’t using gvim. If you have multiple errors, clicking on them in the syntastic window will take you to each one.

It updates as you save. By removing that syntax error and saving, I now see a couple suggestions from hlint:

Hoogle

Earlier, I skipped binding the ghc-mod command that tells us the info on some identifier, because hoogle can also do that. Add the following to your .vimrc:

"""
""" HOOGLE
"""

let g:hoogle_search_bin = '~/.cabal/bin/hoogle'
au FileType haskell
    \ map <buffer> <F2> :Hoogle<CR> |
    \ map <buffer> <F3> :HoogleInfo<CR> |
    \ map <buffer> <F4> :HoogleClose<CR>

Now, pressing F2 under an identifier will open a special new window at the top of the screen with the hoogle search results, just like the ghci commands we added:

Pressing F3 will look up the documentation for an identifier:

And of course, F4 will get rid of the hoogle results window. There’s not much more to the hoogle integration than that.

Pointfree

pointfree is a program that turns code that looks like this:

fn xs = map (\x -> succ (succ (succ x))) xs

Into code that looks like this:

fn = map (succ . succ . succ)

Sometimes that helps readability, sometimes it really hurts it. I’m a fan of having the option but not a fan of making it too easy. So, my .vimrc adds a function that calls pointfree but doesn’t bind it to a key:

"""
""" POINTFREE
"""

function! Pointfree()
    call setline('.', split(system('~/.cabal/bin/pointfree '.shellescape(join(getline(a:firstline, a:lastline), "\n"))), "\n"))
endfunction

You simply highlight the code you want to change, use the vim command :call Pointfree(), and it will do the substitution for you if it can. If you find this very useful, you may wish to add a key binding for it. There’s plenty of other examples elsewhere in this post about how to do that.

And the rest

We’re at the bottom now. In general I don’t like tabs, but Makefiles require them. So I make sure they show up there. And then I enable both syntax highlighting and some more advanced syntax highlighting rules:

"""
""" MISC SOURCE CODE RELATED SETTINGS
"""

" Turn off tab expansion on makefiles, since make cares about whitespace.
autocmd FileType make set noexpandtab

"""
""" SYNTAX HIGHLIGHTING
"""

" Now that we may have added to the syntax possibilities, turn it on.
syntax on
filetype plugin indent on
let g:haskell_enable_quantification = 1
let g:haskell_enable_pattern_synonyms = 1

vim-commentary

This plugin requires no configuration. It’s an extremely simple, extremely useful plugin though. Simply use some movement keys to select a block of source code (I find using the visual block mode most useful) and hit gc. It will insert comment characters before each line appropriate to the language. Repeating the action uncomments.

Useful newly bound keys

All these modules bind lots of commands to lots of keys. It can be a little bit overwhelming. Here are the most useful ones now bound in your fantastic vim haskell environment. I am likely missing a couple.

Key Purpose
F1 tagbar help, when pressed in the tagbar window
F2 Look up an identifier with hoogle
F3 Look up documentation with hoogle
F4 Close the hoogle window
gc Comment and uncomment source code
tc Clear the type highlight
ti Insert a type signature
tt Look up the type of an expression