README.org

November 10, 2024 ยท View on GitHub

[[https://github.com/ManasJayanth/esy-mode/actions/workflows/ci.yaml][https://github.com/ManasJayanth/esy-mode/actions/workflows/ci.yaml/badge.svg]]

  • esy-mode

    =esy-mode= is a minor mode written for [[http://esy.sh][esy]] - the package manager for Reason/OCaml development. It is meant to serve as a auxiliary plugin that helps other plugins to find the correct path to the tools.

    For instance, it can help lsp-mode get the right path to ocaml language server. It, however, doesn't ship the tools themselves. =esy-mode= simply creates a buffer local environment for the tools - which enables it to efficiently inform the user of available/missing tools.

    If you are looking for a tool to have a great first working state, checkout [[https://github.com/esy/pesy][pesy]]. Or simply clone the [[https://github.com/esy-ocaml/hello-reason][hello-reason]] repo.

** Pre-requisites

This minor mode is meant to work with =esy= which is not shipped here. Quickest way to get =esy= installed on your machine is to install it globally using a nodejs package manager

#+BEGIN_SRC shell npm i -g esy # or yarn global add esy #+END_SRC

No, =esy= is not written in JS, but a prebuilt binary and is currently only containing ones that work on 64 bit Windows, Linux and MacOS. More details on the [[http://esy.sh][website]]

You'll need to tweak =reason-mode= a bit so that the project setup is completely =esy-mode's= hands.

Example: [[https://github.com/prometheansacrifice/reason-mode/commit/e98f88a24491578461be85b2adf0dc5e354937cb][e98f88a244]]

TODO: make the tweaks less intrusive. Ping me on [[https://discord.com/invite/reasonml][Discord]] if you need help with it.

** Installation

At the moment the package hasn't been published anywhere. Quickest way to get started is to clone the repo and load it

#+BEGIN_SRC shell git clone https://github.com/ManasJayanth/esy-mode #+END_SRC

#+BEGIN_SRC emacs-lisp (load-file "/path/to/esy-mode/esy-mode.el") #+END_SRC

If you use quelpa and use package, you can use the following

#+BEGIN_SRC emacs-lisp (use-package esy-mode :quelpa (esy-mode :path "/path/to/esy-mode.el" :fetcher file) :hook reason-mode :config (progn (lsp-register-client (make-lsp-client :new-connection (lsp-stdio-connection '("ocamllsp")) :major-modes '(reason-mode tuareg-mode) :server-id 'esy-ocamlmerlin-lsp)))) #+END_SRC

** Configuration

  1. =esy-command= to specify full path to esy command if necessary. (Falls back to a just "esy')
  2. =esy-mode-callback= to specify a callback that could run one all buffer local variables are initialised. For more info on the buffer local variables, checkout the next section =How it works=.

** How it works

All =npm= and =opam= managed Reason/OCaml projects are valid esy projects too. This means, the minor mode must correctly distinguish them from actual =esy= projects (which are basically Reason/OCaml source with build config files shipped with an =esy.json= or a =package.json=).

=esy= provides a =status= sub-command. Which can return an output like this in the case of an esy project

#+BEGIN_SRC js { "isProject": true, "isProjectSolved": true, "isProjectFetched": true, "isProjectReadyForDev": true, "rootBuildPath": "/path/to/g/foo/_esy/default/store/b/foo-03e8a06e", "rootInstallPath": "/path/to/g/foo/_esy/default/store/i/foo-03e8a06e", "rootPackageConfigPath": "/path/to/g/foo/esy.json" } #+END_SRC

For an opam project, it could look like

#+BEGIN_SRC js { "isProject": true, "isProjectSolved": true, "isProjectFetched": true, "isProjectReadyForDev": true, "rootBuildPath": "/path/to/ocaml/ocaml-lsp/_esy/default/store/b/ocaml_lsp-38a74123", "rootInstallPath": "/path/to/ocaml/ocaml-lsp/_esy/default/store/i/ocaml_lsp-38a74123", "rootPackageConfigPath": null } #+END_SRC

So,

  1. All non-json manifest file driven projects, by looking up =rootPackageConfigPath= property, are opam projects

  2. Among json file driven projects,

    1. Those with that are not package.json are esy projects
    2. =package.json= with =esy= property are =esy= projects - others were being managed by npm

Valid =esy= projects are straight forward to handle - the minor mode checks if the project is in buildable state and prompts if it isn't.

=esy= provides the [[https://esy.sh/docs/en/environment.html][command environment]] (the environment where tools like editors are supposed to run) as a json output. =esy-mode= loads all complete environment in a buffer local =process-environment=, giving each project workspace an identical development environment. Here on, running =(executable-find ...)= returns the dev tools from the =esy= sandbox, ensuring you and your co-worker always have the same exact version of tools while developing.

**** Why load the environment when one can simply esy exec-command?

 =esy exec-command <tool-command>= is a great approach. For good
 editor experience, one might have to resort to =esy exec-command
 command -v <tool-command>= repeatedly to check if the tool
 exists and inform the user. =(executable-find ...)= is better
 approach IMO. This is subject to how Emacs handles buffer local
 process environments of course.

*** NPM and Bucklescript build system managed projects

=npm= unfortunately doesn't provide prebuilts with reproducibility
guarantees, nor does it sandbox tools that need each other on the
path to work together. Mismatching compiler and [[https://github.com/ocaml/merlin][merlin]] prebuilts
cause a lot of confusion - using npm to install these tools
globally is not an easy experience for newcomers.

Similarly, inter-tool interaction is not reliable in
global environments. For instance, =ocamlmerlin= expects
=ocamlmerlin-reason= binary to be available in it's path - and
both of these must be built with the *same* version of the
compiler. In the global environment, it was incredibly hard to get
them to work - user's system wide configuration is a complete
blackbox. The only reliable way to ensure interacting tools work
together is to run them in sandboxed environments - and =esy=
provides just that!

This is why we recommend bucklescript users to allow editor
plugins to drop an =esy.json= - plugins look into the compiler
version and create this file themselves.

*** Opam managed projects

This is a work in progress - =esy= provides sandboxed environments
for opam projects too (without creating any =esy.json=). But opam
users dont ship development time dependencies in their package
manifests. For now, the plugin stays inactive. Ideas are welcome.

** Contributing guidelines

Currently beta quality. Looking forward to ideas and feedback. If you're raising a PR, please add a test. Not having types to catch your errors are hard - even if lisp somehow makes it bearable, let's ensure we still try to catch errors early!

** License

MIT licensed. Please see LICENSE for more details