Skip to content

Instantly share code, notes, and snippets.

@Khady
Last active February 27, 2023 12:25
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save Khady/d37b7d88c81c4178dcccc6579fd0b526 to your computer and use it in GitHub Desktop.
Save Khady/d37b7d88c81c4178dcccc6579fd0b526 to your computer and use it in GitHub Desktop.
OCaml and Reasonml emacs configuration
(use-package company
:ensure t
:custom
(company-quickhelp-delay 0)
(company-tooltip-align-annotations t)
:hook
((prog-mode utop-mode) . company-mode)
:config
(company-quickhelp-mode 1)
:bind
("M-o" . company-complete)
)
(use-package company-quickhelp
:ensure t
:bind (:map company-active-map
("M-h" . company-quickhelp-manual-begin))
)
(defun shell-cmd (cmd)
"Returns the stdout output of a shell command or nil if the command returned
an error"
(car (ignore-errors (apply 'process-lines (split-string cmd)))))
(setq opam-p (shell-cmd "which opam"))
(setq reason-p (shell-cmd "which refmt"))
(if opam-p
(let ((opam-share (ignore-errors (car (process-lines "opam" "config" "var" "share")))))
(when (and opam-share (file-directory-p opam-share))
(add-to-list 'load-path (expand-file-name "emacs/site-lisp" opam-share)))))
(use-package caml
:ensure t)
(use-package tuareg
:if opam-p
:mode ("\\.ml[ily]?$" . tuareg-mode))
(use-package reason-mode
:if reason-p
:ensure t
:config
(let* ((refmt-bin (or (shell-cmd "refmt ----where")
(shell-cmd "which refmt")))
(merlin-bin (or (shell-cmd "ocamlmerlin ----where")
(shell-cmd "which ocamlmerlin")))
(merlin-base-dir (when merlin-bin
(replace-regexp-in-string "bin/ocamlmerlin$" "" merlin-bin))))
;; Add npm merlin.el to the emacs load path and tell emacs where to find ocamlmerlin
(when merlin-bin
(add-to-list 'load-path (concat merlin-base-dir "share/emacs/site-lisp/"))
(setq merlin-command merlin-bin))
(when refmt-bin
(setq refmt-command refmt-bin)))
)
(use-package ocp-indent :if opam-p)
(use-package ocp-index :if opam-p)
(use-package merlin
:custom
(merlin-completion-with-doc t)
:bind (:map merlin-mode-map
("M-." . merlin-locate)
("M-," . merlin-pop-stack)
("M-?" . merlin-occurrences)
("C-c C-j" . merlin-jump)
("C-c i" . merlin-locate-ident)
("C-c C-e" . merlin-iedit-occurrences)
)
:hook
;; Start merlin on ml files
((reason-mode tuareg-mode caml-mode) . merlin-mode)
)
(use-package utop
:custom
(utop-edit-command nil)
:hook
(tuareg-mode . (lambda ()
(setq utop-command "utop -emacs")
(utop-minor-mode)))
(reason-mode . (lambda ()
(setq utop-command "rtop -emacs")
(setq utop-prompt
(lambda ()
(let ((prompt (format "rtop[%d]> " utop-command-number)))
(add-text-properties 0 (length prompt) '(face utop-prompt) prompt)
prompt)))
(utop-minor-mode)))
)

Completion

Company

I use company mode as a completion backend

(use-package company
  :ensure t
  :custom
  (company-quickhelp-delay 0)
  (company-tooltip-align-annotations t)
  :hook
  ((prog-mode utop-mode) . company-mode)
  :config
  (company-quickhelp-mode 1)
  :bind
  ("M-o" . company-complete)
  )

Popup for documentation or help

(use-package company-quickhelp
  :ensure t
  :bind (:map company-active-map
              ("M-h" . company-quickhelp-manual-begin))
  )

Languages

Ocaml/Reason

Util function to select where to load merlin from.

(defun shell-cmd (cmd)
  "Returns the stdout output of a shell command or nil if the command returned
     an error"
  (car (ignore-errors (apply 'process-lines (split-string cmd)))))

(setq opam-p (shell-cmd "which opam"))
(setq reason-p (shell-cmd "which refmt"))

Load opam

Setup environment variables using opam.

(if opam-p
    (dolist (var (car (read-from-string (shell-command-to-string "opam config env --sexp"))))
      (setenv (car var) (cadr var))))

Add opam libs.

(if opam-p
    (let ((opam-share (ignore-errors (car (process-lines "opam" "config" "var" "share")))))
      (when (and opam-share (file-directory-p opam-share))
        (add-to-list 'load-path (expand-file-name "emacs/site-lisp" opam-share)))))

caml, reasonml and tuareg modes

caml is required because caml-types-expr-face is used by merlin.

(use-package caml
  :ensure t)

We don’t need the tuareg package from the emacs repositories, it comes from opam.

(use-package tuareg
  :if opam-p
  :mode ("\\.ml[ily]?$" . tuareg-mode))

When using reason-mode, we want to load merlin from node_modules rather than opam.

(use-package reason-mode
  :if reason-p
  :ensure t
  :config
  (let* ((refmt-bin (or (shell-cmd "refmt ----where")
                        (shell-cmd "which refmt")))
         (merlin-bin (or (shell-cmd "ocamlmerlin ----where")
                         (shell-cmd "which ocamlmerlin")))
         (merlin-base-dir (when merlin-bin
                            (replace-regexp-in-string "bin/ocamlmerlin$" "" merlin-bin))))
    ;; Add npm merlin.el to the emacs load path and tell emacs where to find ocamlmerlin
    (when merlin-bin
      (add-to-list 'load-path (concat merlin-base-dir "share/emacs/site-lisp/"))
      (setq merlin-command merlin-bin))
    (when refmt-bin
      (setq refmt-command refmt-bin)))
  )

ocp tools

Require ocp stuff first because of conflicts between shortcuts. It is installed from opam, ensure is not required.

(use-package ocp-indent :if opam-p)
(use-package ocp-index :if opam-p)

merlin

Configure merlin. Magical autocompletion and IDE features.

(use-package merlin
  :custom
  (merlin-completion-with-doc t)
  :bind (:map merlin-mode-map
              ("M-." . merlin-locate)
              ("M-," . merlin-pop-stack)
              ("M-?" . merlin-occurrences)
              ("C-c C-j" . merlin-jump)
              ("C-c i" . merlin-locate-ident)
              ("C-c C-e" . merlin-iedit-occurrences)
              )
  :hook
  ;; Start merlin on ml files
  ((reason-mode tuareg-mode caml-mode) . merlin-mode)
  )

utop

(use-package utop
  :custom
  (utop-edit-command nil)
  :hook
  (tuareg-mode . (lambda ()
                   (setq utop-command "utop -emacs")
                   (utop-minor-mode)))
  (reason-mode . (lambda ()
                   (setq utop-command "rtop -emacs")
                   (setq utop-prompt
                         (lambda ()
                           (let ((prompt (format "rtop[%d]> " utop-command-number)))
                             (add-text-properties 0 (length prompt) '(face utop-prompt) prompt)
                             prompt)))
                   (utop-minor-mode)))
  )
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment