Emacs major mode for ReScript, part of experimental Emacs support for the ReScript Language.
New ReScript users are advised to use one of the editors with official support from the ReScript team.
Apart from a working ReScript install and this code, for a full setup you need:
- Strongly recommended: Emacs 27.0 or newer built with native JSON support, for LSP performance
- The ReScript language server, for type information, compiler errors, completion, and jump to definition/find references
- Currently, a way to format ReScript code (LSP also provides this, but there is an lsp-mode bug I need to report/fix before this works)
TODO: bundle this or provide a way of auto-installing it
Install the language server globally with:
npm i -g @rescript/language-server
lsp-rescript
provides configuration code for lsp-mode
and depends on rescript-mode
.
Install the following packages (e.g. using M-x package-install
-- you can use
things like use-package if you like of course):
- lsp-rescript
- lsp-ui
Add the following to your Emacs configuration code (for example to ~/.emacs
):
;; Tell `rescript-mode` how to run the language server
(customize-set-variable
'lsp-rescript-server-command
'("rescript-language-server" "--stdio"))
(with-eval-after-load 'rescript-mode
;; Tell `lsp-mode` about the language server
(require 'lsp-rescript)
;; Enable `lsp-mode` in rescript-mode buffers
(add-hook 'rescript-mode-hook 'lsp-deferred)
;; Enable display of type information in rescript-mode buffers
(require 'lsp-ui)
(add-hook 'rescript-mode-hook 'lsp-ui-doc-mode))
For now: ensure that reason-mode
is not installed, e.g. using package-delete
(I guess reason-mode
needs to change so that .re
is considered Reason code
but not .res
).
Restart Emacs and open a ReScript .res
file and you should have all the
features working.
Note that vanilla Emacs handles the LSP prompt for "Start a build for this project to get the freshest data?"
on opening a ReScript file in a rather
unfriendly way: you have to hit TAB
to see the single possible Start a Build
response, then hit return.
Install the following packages (e.g. using M-x package-install
:
- rescript-mode
- eglot
This configuration uses use-package
, which you also need to install, but it
shouldn't be too hard to rewrite this to not use use-package
.
(use-package rescript-mode
:hook ((rescript-mode . (lambda () (electric-indent-local-mode -1))))
:config
(add-to-list 'eglot-server-programs
'(rescript-mode . ("rescript-language-server" "--stdio"))))
Ensure lsp
is enabled in init.el
.
In packages.el
add:
(package! rescript-mode)
(package! lsp-rescript)
Then in config.el
add:
(after! rescript-mode
(setq lsp-rescript-server-command
'("rescript-language-server" "--stdio"))
;; Tell `lsp-mode` about the `rescript-vscode` LSP server
(require 'lsp-rescript)
;; Enable `lsp-mode` in rescript-mode buffers
(add-hook 'rescript-mode-hook 'lsp-deferred)
;; Enable display of type information in rescript-mode buffers
(require 'lsp-ui)
(add-hook 'rescript-mode-hook 'lsp-ui-doc-mode))
In init.el
, ensure you have the +eglot
option for lsp
:
(lsp +eglot)
In packages.el
add:
(package! rescript-mode)
Then in config.el
add:
(after! eglot
(add-to-list 'eglot-server-programs
'(rescript-mode . ("rescript-language-server" "--stdio")))
)
(add-hook 'rescript-mode-hook (lambda () (eglot-ensure)))
TODO: make a configuration layer
Add lsp
to the dotspacemacs-configuration-layers
section of your spacemacs
configuration file (SPC f e d
to find that file) -- it should look something
like this:
dotspacemacs-configuration-layers
'(
lsp
)
Add rescript-mode
and lsp-rescript
to the dotspacemacs-additional-packages
section of your spacemacs configuration file -- it should look something like
this:
dotspacemacs-additional-packages
'(
lsp-rescript
rescript-mode
)
Add this to the dotspacemacs/user-config
section of your spacemacs
configuration file:
;; Tell `rescript-mode` how to run the language server
(customize-set-variable
'lsp-rescript-server-command
'("rescript-language-server" "--stdio"))
(with-eval-after-load 'rescript-mode
;; Tell `lsp-mode` about the lamguage server
(require 'lsp-rescript)
;; All I remember is something weird happened if this wasn't there :-)
(spacemacs|define-jump-handlers rescript-mode)
;; Enable `lsp-mode` in rescript-mode buffers
(add-hook 'rescript-mode-hook 'lsp-deferred)
;; Enable display of type information in rescript-mode buffers
(require 'lsp-ui)
(add-hook 'rescript-mode-hook 'lsp-ui-doc-mode))
For now: ensure that reasonml
layer is not listed in
dotspacemacs-configuration-layers
and reason-mode
is not listed in
dotspacemacs-additional-packages
(I guess reason-mode
needs to change so
that .re
is considered Reason code but not .res
).
Restart spacemacs (SPC q r
) and open a ReScript .res
file and you should
have all the features working.
In case the distinction is unclear:
Formatting: This means that you run an Emacs command, and your whole buffer (or
some section of it that you specify maybe) is magically formatted correctly --
that is, in the way that rescript format
formats it.
Indentation: This means that hitting the tab key or the return key (depending
how you have things configured I guess) gives you an approximation of the
“official” formatting of a tool like rescript format
. It’s never identical to
proper formatting, but stops you having to pay attention to formatting when
writing code.
You can use a package like
format-all
or
reformatter
to get your code
formatted correctly (i.e. as rescript format
, soon to be renamed rescript format
, formats it -- this is like gofmt
for ReScript). See this
thread
(I've not tried either of these).
lsp-mode
will make this part unnecessary when I get around to submitting a fix
for an lsp-mode
bug that currently causes lsp-format-buffer
not to work with
rescript-vscode.
This is a terrible hack: it's lifted straight from js-mode
(js.el
) with
little effort to adapt it to ReScript, and without any JSX support.
Nevertheless, aside from JSX it seems to work OK.
For more predictability, you may prefer to use something like indent-relative
or indent-relative-first-indent-point
, by adding something like this in your
(with-eval-after-load 'rescript mode ...
:
(define my/rescript-mode-hook ()
(setq-local indent-line-function #'indent-relative))
(add-hook 'rescript-mode-hook #'my/rescript-mode-hook)
Aside from the usual font-lock and indentation provided by any language major mode, this is what is provided by LSP:
You should see any errors show up via flycheck -- for me they look like this:
These errors only show up when you save.
If you don't see that, rescript build *
may not be running on your project.
To provide these UI for these errors, LSP mode falls back to flymake
if
flycheck
is not installed, so it's recommended to install the latter.
When you open a .res
file in your project, you should see a prompt in Emacs in
the minibuffer "Start a build for this project to get the freshest data?"
.
You can either hit return on Start Build
to say yes to that and the LSP server
will start a build for you, or C-g
out of that and run rescript build *
yourself however
you usually do that in your rescript project (in my project I run npm start
).
You may find the UI here (how the Start Build
option is presented) is a bit
different from how I describe it depending if you're using vanilla emacs or some
configuration that uses a package like ivy
or helm
that overrides the
behaviour of completing-read
.
If you never want to see this prompt you can put this in your configuration:
(custom-set-variables '(lsp-rescript-prompt-for-build nil))
If you don't see the "Start a build for this project to get the freshest data?"
prompt, that may be because a build is already running somehow, or you
may have a stale .bsb.lock
lock file in your project.
The configuration above enables lsp-ui-doc-mode
. Hovering with the mouse or
moving point to some code should give a popup like this:
Here are some other ways to see type information if you don't like it popping up automatically:
- You can leave
lsp-ui-doc-mode
off and just uselsp-ui-doc-glance
every time you want to see it. - You can use
lsp-describe-thing-at-point
to see the type in a window instead of in a popup overlay.
lsp-mode
's completion UI is provided by company-mode
, so take a look at the
docs for the latter for more about that.
You can use functions like lsp-find-definition
and lsp-find-references
.
I believe functions like xref-find-definitions
and xref-find-references
also
end up using LSP and seem equivalent to the LSP functions for ReScript purposes.
lsp-ui-peek-find-definitions
also seems equivalent to lsp-find-definition
.
lsp-ui-peek-find-references
is a fancier more GUI-fied version of
lsp-find-references
which seems to only sometimes work for me (when it does,
you get a complicated overlay -- it looks like a collection of windows but is
not -- in which you can preview references and navigate through them; other
times it seems to jump me, sometimes inaccurately, to the definition).
lsp-find-references
itself seems to not find references in other files for me,
I haven't yet tested to see what the behaviour is in VS code.
In spacemacs , g g
(spacemacs/jump-to-definition
) ends up rather indirectly
using lsp-ui-peek-find-definitions
(which is also bound directly to , G d
).
, g r
is xref-find-references
. , G r
is lsp-ui-peek-find-references
(when it shows the overlay, j
and k
or n
and p
navigate the list of
references, I think h
and l
would navigate the file list if I ever saw one,
and q
quits).
If you don't see type information and errors: the ReScript compiler should be
running, and in order to run it, you need the JavaScript dependencies of the
project you're editing. Often those are fetched by running npm install
. When
you've done that, you can revert-buffer
(or SPC b R
in spacemacs) to reload
the .res
file you're looking at, which should prompt you to ask if you'd like
to start a build (i.e. run the ReScript compiler).
If you run into problems with display of compilation errors
(flycheck
/flymake
errors), try this to get rid of any stale ReScript build:
- Kill any
rescript
orbsb
processes - Remove any .bsb.lock file in your project
M-x revert-buffer
on the .res file you're trying to edit
If you run into problems with other things, you can try killing the language server,
and then if LSP doesn't automatically prompt you to restart the server, M-x lsp
.
I've barely used this yet, so probably a lot of things are very broken!
See the github issues, but notably:
Emacs support
- Indentation is a terrible hack and should very likely be replaced with tree-sitter-rescript and elisp-tree-sitter and tree-sitter-indent -- probably very easy? Perhaps this will also improve font-lock etc!
- Font lock and indentation are broken for things like
let \"try" = true
. - Formatting with
lsp-format-buffer
is broken because it does not correctly handle the response from rescript-vscode because it uses a range like"end":{"line":1.7976931348623157e+308,"character":1.7976931348623157e+308}
-- this should be easy to fix and you can use other means to do this.
Packaging issues:
- Teach lsp-mode how to install rescript-vscode, or bundle it
- Add spacemacs layer
To run the tests, install Cask and run this in the project root directory:
cask exec ert-runner
Please do not report issues related to editor support with Emacs upstream to the ReScript or rescript-vscode projects (neither on the forum nor the github issues). For now please use github issues or github discussions on this project as a place to discuss Emacs ReScript support.
Emacs is NOT SUPPORTED by the ReScript core team, nor rescript-vscode. The core
ReScript team’s focus for editor support is currently on supporting VS Code and
Sublime Text well. So, if you want something that you can be confident is going
to work smoothly and will not go away, use one of the editors listed as
supported by the core ReScript team (currently VS Code and Sublime Text). In
particular, the Emacs support here depends on the LSP server from
rescript-vscode and its --stdio
switch, neither of which are officially
supported and could be removed in a later version.
So if you have problems with Emacs and ReScript, please report your issues here, not upstream with ReScript or rescript-vscode and please don’t complain if you used ReScript with Emacs and had a bad time – if you did that, you’re going it alone and you really didn’t try the official ReScript experience – that’s unfair and a good way to annoy everybody.