Responsive IDEs

Posted on May 29, 2020 by Zubin Duggal

This is the fourth installment in our weekly series of IDE related updates. I will discuss some of the latest developments with respect to the ghcide architecture and how we’ve been working to increase its responsiveness.

Slow response times in ghcide

A while ago, Matthew and others noticed that performance for requests like hovering was still far too slow, especially for big projects like ghc. Furthermore, other requests like completions were also pretty useless, since they took ages to show up, and only did so when you paused while typing.

One of the reasons for this turned out to be the way ghcide handled new requests. Only one Shake Action can run with access to the Shake database at a time, so when ever new requests came in, ghcide would cancel whatever requests were previously running and schedule the new one. This meant that if you started typing, your most recent modification to the file would cancel any already running typecheck from the previous modifications and run a new one. Then, when a completion request came in, it would even cancel this latest typecheck if it was still running, kick off a new typecheck and finally report results when this succeeded. If the typecheck failed, ghcide would still try to use the results of the previous typecheck to give you your results, but, crucially, it has to wait for the previous typecheck to fail before it can do this.

A new old solution

We already had a pretty good idea about how to fix this problem, especially since haskell-ide-engine had usable and fast completions. The key idea was to not make arbitrary requests like hover, goto definition and completion cancel running typechecks. Instead, we always want them to use the results of the last successful typecheck. This trades off some correctness for responsiveness, since if a typecheck is running, these requests will not wait for the typecheck to complete before reporting results, and just use the results of the previous typecheck.

In addition to this, we maintain a queue of requests to schedule with shake, and add Actions to this queue to refresh whatever information from the database was accessed by our requests, so that the database is always kept up to date.

This solution was implemented by Matthew, and you can use it by running his branch of ghcide. This is also the branch of ghcide used by haskell-language-server.

No more waiting for your IDE to catch up to you

As covered in earlier blog posts, I have been working on integrating hiedb with ghcide so that it can display project wide references. While doing this, I was reminded of the architecture of the clangd language server, and I realised that many other requests could be served using this model.

The idea is for ghcide to act as an indexing service for hiedb, generating .hi and .hie files which are indexed and saved in the database, available for all future queries, even across restarts. A local cache of .hie files/typechecked modules is maintained on top of this to answer queries for the files the user is currently editing. All information that is not in some sense “local” to a particular module is accessed through the database. On the other hand, information like the symbol under a point, the references and types of local variables etc. will be accessed through the local cache.

A goal we would like to work towards would be to have an instantly responsive IDE as soon as you open your editor. Ideally, we wouldn’t even want to wait for your code to typecheck before your IDE is usable. Indeed, on my branch of ghcide, many features are available instantly, provided a previous run had cached a .hie file for your module on disk.

Here you can see that we can use the hover and go to definition features as soon as we open our editor, even on a big project like GHC which takes quite a while to typecheck.

The hover, go to (type) definition, references, document highlight and workspace symbols requests have been (re)written to fit this model. We can even teach hiedb to index .hi files, so that module imports, identifiers exported from modules, their types and their documentation are also stored in the database. We can then use this info to serve completion requests.

You can follow along with my progess here.

Space leaks and shake responsiveness

The un-intuitive behaviour of the GHC and Shake schedulers was responsible for some of the delay while answering requests. It seems like neither’s behaviour is quite optimal for the kinds of workloads ghcide generates, which consist of many extremely short running actions.

You can read more about this and Neil’s investigation into it in his blog post

Other developments

The students accepted for IDE related GSOCs (Luke, Fendor and Michalis) have been gearing up for the official start of their program on the coming Monday.

  • Javier Niera has been looking into the haskell-language-server CI and testsuite, as well as porting over the old haskell-ide-engine README instructions, so that people can quickly get it set up on their own editors
  • Fendor has picked up an old GHC MR to reimplement ghci’s :set +c functionality using .hie files
  • Luke Lau has been working on adding the ability to attach haddocks to declarations generated by TH as a yak shave while working on improvements to the haskell-lsp library