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:

12 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

Another tip for large projects: switching off the scenario service can save GBs of memory, as discussed here:

Just add --scenarios=no to the command, before --RTS.

1 Like

Thanks for the tips, it’s great to get back in vim (no amount of VSCode vim plugins will get you to the real thing).

I’m not sure if this is the case for vim-lsp, but to get this to work with coc.nvim I also needed to add au BufRead,BufNewFile *.daml set filetype=daml to my .vimrc.

2 Likes

Hi @alex.graham, as discussed offline, it’s also possible to enable custom syntax highlighting, as in the screenshot. It was a while ago I did this. I created a syntax file ~/.vim/syntax/daml.vim that references the Haskell syntax as nearly all of it is inherited:

so $VIMRUNTIME/syntax/haskell.vim

syn match damlStructure		"\<\(template\|with\|scenario\)\>"
hi def link damlStructure Structure

Then you can enable highlighting with :set syntax=daml. You can also set up an automate this on opening of files, see :help new-filetype, along with LSP, as you mentioned above.

2 Likes

Hi @drsk I followed your instructions after choosing Neovim.

Worked great, except for an error which required CocList diagnostics everytime I opened a .daml file. The cause was a missing `}’ at the end of the file.

My version which works:

{
  "languageserver": {

    "daml": {
      "command": "daml",
      "args": [
        "ide"
      ],
      "rootPatterns": [
        "daml.yaml"
      ],
      "filetypes": [
        "daml"
      ],
      "initializationOptions": {}
    }
  }
}  <= this closing curly bracket was missing 8-)  

Any chance anyone has a Daml code highlighting plugin or hack?

TODO: Modify that horrendous Pink colour in the Daml-LSP output.

1 Like

Yes - see the post above yours, if you’re using vim :point_up:

2 Likes

mkdir -p ~/.vim/pack/lsp/start has a further sub-dir/

1 Like

System info

uname -a >> Linux un150 5.8.0-45-generic #51~20.04.1-Ubuntu
vim --version >> VIM - Vi IMproved 8.2 (2019 Dec 12, compiled Mar 23 2021 15:25:21)
daml version >> 1.11.1  (default SDK version for new projects)

Fixed!

$ cd ~/.vim
$ rm -rf *
# Follow the instructions at https://github.com/junegunn/vim-plug
# Ensure you use ~/.vim/autoload as the $PLUGIN directory
# Follow the instructions at https://github.com/neoclide/coc.nvim
" Use release branch (recommend)
Plug 'neoclide/coc.nvim', {'branch': 'release'}
# Open a *.daml file using Vim
$ vim ~/Projects/daml/trash_app/daml/Main.daml
# Use Vim to install the 'neoclide' plugin
# Install the required extensions
:CocInstall coc-json coc-tsserver
# Configure language server in coc-settings.json by
# Use Vim to edit the 'coc-settings.json' file
:CocConfig

# Use the following:
{
  "languageserver": {

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

# The actual file 'coc-settings.json' is located in ~/.vim
$ quid ~/.vim $ ls coc-settings.json 
coc-settings.json
# Edit ~/.vimrc to include
au BufRead,BufNewFile *.daml set filetype=daml
# There is no need for ':set syntax=daml'
# Use Vim to open the previous *.daml file
# Enjoy the power and the awful Pink

TODO:

Summary

Read GitHub - microsoft/language-server-protocol: Defines a common protocol for language servers.</titl :ballot_box_with_check:
Read GitHub - mattn/vim-lsp-settings: Auto configurations for Language Server for vim-lsp :ballot_box_with_check:
Add in additional languages, ie Erlang & Haskell Native

1 Like

:laughing:

A post was split to a new topic: Getting whitespace to be interpreted correctly with vanilla vim on LSP?

If you recently switched to neovim 5.0 and its builtin language server client, just add this to your init.lua:

local util = require 'lspconfig/util'

configs.daml = {
  default_config = {
    cmd = { 'daml', 'ide' },
    filetypes = { 'daml' },
    root_dir = util.root_pattern('daml.yaml'),
  },

  docs = {
    description = [[ https://daml.com ]],
    default_config = {
      root_dir = [[root_pattern("daml.yaml")]],
    },
  },
}

This requires that the neovim/nvim-lspconfig plugin is installed.

I opened a PR to include this in the neovim/nvim-lspconfig plugin, such that in a future version you will simply be able to add

require'lspconfig'.daml.setup{}
2 Likes

That’s a terse and solid config, thank you for the update :+1:t2:

Now that I am using VS Code almost exclusively,
my ~/.vim is stock standard.

Hi @alex.graham and @Luciano I have been meaning to ask you Vimmers, how do you do testing using Vim? Are you importing a test framework?

VS Code has the inbuilt Script testing if invoked in the Daml app, but I cannot see any type of testing reference in any of the code references in this thread.

The LSP server will run the scenario service, unless you pass the --scenarios=no argument as mentioned in the thread :point_up:, but as stated, it’s a memory hog. Also, the error messages tend to be too long to read in vim.

Instead, I just run daml test when I need to. I’m not sure if you can get the nifty ‘disclosure’ diagram as you do in VSCode though. There’s an option --junit FILENAME but not sure what that does …

1 Like

In theory you can write a vim integration that shows you the scenario results including the transaction view and the table view. However, it relies on non-standard (and not documented) LSP extensions so it won’t work with any LSP client out of the box.

1 Like