DAML-on-vim

Introduction

Just under a year ago our very own Neil Mitchell blogged about his vision of a GHC IDE. Today, this project forms the core of DAML’s IDE. It’s maintained by @cocreature and team; also, @shaynefletcher has added support for hlint, as was intended in the original post. All this ships as a plugin to VSCode - but what if, like me, you’re obstinate and don’t want to switch to a new editor, and want to stick to vim? Read on …

In this post I will show you how to integrate Microsoft’s Langauge Server Protocol into vim. LSP is what allows VSCode to provide real-time error highlighting, code snippets, auto-complete etc., while remaining language agnostic. In fact, this will work not only for DAML but for a large number of other languages as well; we’ll only cover DAML here though.

Setup

LSP is a client-server architecture. The client sits in your editor (vim in our case) and displays the information which it gets from the language server, such as errors, on your screen.

1. Client

There are a number of LSP clients available for vim; I’ve chosen vim-lsp as it’s fully written in vims native scripting language, and it’s also non-blocking, making it light-weight.

We’re going to install it using Vim 8’s native packaging system. Again, you have a wide number of package managers you can choose from, but I’ve selected the built-in one as it doesn’t require installing even more stuff on my system. Simply:

mkdir -p ~/.vim/pack/async/start
cd ~/.vim/pack/async/start
git clone https://github.com/prabirshrestha/async.vim.git
mkdir -p ~/.vim/pack/lsp/start
cd ~/.vim/pack/lsp/start
git clone https://github.com/prabirshrestha/vim-lsp.git

(n.b. yes, you need to clone everything under start/ - it’s a bit weird).

2. Server

So where are we going to find a LSP DAML language server? Googling doesn’t seem to turn up anything. I’ll let you in on a little secret: the daml SDK itself contains a fork of hgcide, which I told you about in the introduction! You can run it with the daml assistant like so: daml damlc ide. If you try this it won’t appear to do anything - it just sits there and listens for commands from the client.
To make the language server start this automatically with vim, append the following to your .vimrc:

au User lsp_setup call lsp#register_server({
    \ 'name': 'damlc',
    \ 'cmd': {server_info->['daml', 'damlc', 'ide', '--RTS', '+RTS', '-M6G', '-N']},
    \ 'whitelist': ['daml'],
    \ })

Restart vim, and that’s it! You should now have working code integration. You can check this by opening a *.daml file and then typing :LspStatus in vim. You should see:

damlc: running

Troubleshooting

When I went through above process myself, I ran into a couple of problems:

  • First of all, you should check that you have a working SDK. Create a skeleton project using daml new and then compile it with daml build. If you can’t get that to work, refer to the official documentation to install the DAML SDK.

  • If error highlighting isn’t working, ensure you are on a recent version of Vim, preferably 8.2. There are known issues with earlier versions. If like me, you’re using Ubuntu LTS 18.04, you will have to upgrade to 20.04 or use a custom PPA.

  • When we (manually) installed the LSP client, we pulled down the latest development version - if you’re experiencing problems, you may want to switch to a stable release, by going to .vim/lsp/start, and git checking out a stable git tag.

What next?

Head on over to github vim-lsp to learn more about how to use this extension. For instance, try :LspDocumentDiagnostic to populate the quick fix buffer with a list of errors (:help :ll). Sadly this doesn’t update in real-time, so you may want to create an auto-command (:help :au) to execute this every time you save the file.

Why do I need LSP at all? Why vim?

Vim does provide a :make command (:help :make) that also allows you to get error highlighting. Previously I was using this with a custom configuration (remind me to post it here later). This works fine for smaller projects, but when I started working on one of our larger client projects - by large I mean tens of thousands of lines of code - I found that even with the daml build --incremental=yes flag, build times would run into the minutes.

The language server, on the other hand, is able to provide instantaneous feedback. It seems that this is at the cost of only processing open buffers. So for instance it doesn’t seem know how to find definitions outside the current file. I’m not certain whether this is a limitation of the vim plugin, or an issue with the LSP.

As for vim: well, I’ve been using it for 20 years now. I was forced into it at my first job, where we were configuring a bank’s trading systems that ran on Solaris servers. We would need to telnet to the servers, and I found that vi was ubiquitous at the time (and probably still is) - you would always find it pre-installed on Unix systems. Since then, I’ve grown accustomed to it. It’s lightweight, extremely configurable, and fast. And well, better than emacs of course … :fire: :laughing:

9 Likes

If you choose to use coc.nvim for vim8 or neovim instead of vim-lsp, here’s the relevant section of the coc-settings.json, which you can edit with :CocConfig within vim:

{
  "languageserver": {

    "daml": {
      "command": "daml",
      "args": [
        "ide"
      ],
      "rootPatterns": [
        "daml.yaml"
      ],
      "filetypes": [
        "daml"
      ],
      "initializationOptions": {}
    }
  }

Thanks Robin for sharing this! That reminds me, that if you are interested in getting the language server working with other editors/plugins, a good resource is the ghcide readme. It has a number of sample configurations. As damlc is based on that, so the setup will be similar.

Over the past few weeks I found that the LSP server was using a huge amount of memory - 18 GB - so I had to restart it daily.

Courtesy of our colleagues in Australia (sorry - thread on slack has been archived, can’t find the OP), I was told I can use the following arguments in my .vimrc to limit the amount of memory used:

    \ 'cmd': {server_info->['daml', 'damlc', 'ide', '--RTS', '+RTS', '-M6G', '-N']},

Will update the OP.

2 Likes