Rewrite merlin-completion-at-point integration to be faster and better #1759
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Let me know how you'd like me to approach this PR. This is what we use internally at Jane Street, and it's been written to be relatively upstreamable. At the same time, the old version of merlin-cap was in need of a lot of improvement and modernization to how the Emacs completion API is supposed to be used, and essentially none of the original code survived. So it's kind of just a giant code drop, which I know is pretty hard to review.
The merlin-completion-at-point integration is completely rewritten. The version before was simple and slow; the new version is much faster and much more featureful:
Completions are requested for an entire module at once and filtered locally in Emacs rather than doing the filtering on the Merlin side.
Completions are cached based on the OCaml atom being completed, so they are re-used instead of re-requested when a new character is typed.
Those completions are cached for a given position inside an OCaml atom, so that if a user types Li TAB ma TAB to complete "List.map" and then decides they actually want the module "Labels", when they delete the "ist.map" part and hit TAB, they'll use the previously-requested completions.
We avoid updating Merlin with the new buffer contents as completion proceeds, so that Merlin doesn't need to re-parse and re-type-check, substantially improving performance in expensive
files. (merlin-cap--omit-bounds)
Completion requests are handled asynchronously and reused, so that if completion is interrupted and then resumed, we're able to use the results of the previous completion request. This makes completion UIs which use while-no-input (like corfu-mode) much more performant.
Completion is wrapped in while-no-input when non-essential is set; this makes completion UIs which don't use while-no-input (like company-mode) much more responsive.
Completions are sorted more intelligently: if they're a constructor or variant or label, they're likely to be more relevant to the user, so they're sorted first.
Module names in completions are suffixed with a ., matching Emacs behavior for file name completion (where directories are suffixed with a /); this makes completion of module paths much more fluent, since there's no need to hit . after every module name.
We use completion boundaries, so the built-in Emacs partial-completion feature now works: if the user types "Li.ma TAB", it will complete to "List.map".
Likewise, partial-completion will expand * as a glob, so if the user types "Deferred.*.map TAB" they will be presented with every module in "Deferred." which contains the method "map"
There are also several tests now, testing the new functionality.