Skip to content

Commit

Permalink
emacs: Add support for opam-switch-mode (#1654)
Browse files Browse the repository at this point in the history
from erikmd/opam-switch-mode
  • Loading branch information
voodoos authored Jul 26, 2023
2 parents 0065474 + 5bac17d commit fd1bac3
Show file tree
Hide file tree
Showing 4 changed files with 56 additions and 27 deletions.
2 changes: 2 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ unreleased
local values in the completion options. Alternatively, this
behavior can be enabled permanently by customizing
`merlin-construct-with-local-values` (#1644)
- emacs: add support for opam-switch-mode (#1654, fixes #1591).
See <https://github.com/ProofGeneral/opam-switch-mode>

merlin 4.9
==========
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ Just add the following to your *.emacs* file:
;; (require 'merlin-iedit) ; iedit.el editing of occurrences
;; (require 'merlin-company) ; company.el completion
;; (require 'merlin-ac) ; auto-complete.el completion
;; To easily change opam switches and pick the ocamlmerlin binary accordingly,
;; you can use the minor mode https://github.com/ProofGeneral/opam-switch-mode
```

More comprehensive documentation can be found on the [emacs-from-scratch wiki](https://github.com/ocaml/merlin/wiki/emacs-from-scratch).
Expand All @@ -127,6 +129,8 @@ Emacs startup file is sufficient:
;; (require 'merlin-iedit) ; iedit.el editing of occurrences
;; (require 'merlin-company) ; company.el completion
;; (require 'merlin-ac) ; auto-complete.el completion
;; To easily change opam switches and pick the ocamlmerlin binary accordingly,
;; you can use the minor mode https://github.com/ProofGeneral/opam-switch-mode
```

### Other Editors
Expand Down
70 changes: 45 additions & 25 deletions emacs/merlin.el
Original file line number Diff line number Diff line change
Expand Up @@ -258,8 +258,6 @@ The association list can contain the following optional keys:
;; Internal variables ;;
;;;;;;;;;;;;;;;;;;;;;;;;

(defvar merlin-opam-bin-path nil)

;; If user did not specify its merlin-favourite-caml-mode, try to guess it from
;; the buffer being edited
(defvar merlin-guessed-favorite-caml-mode nil)
Expand Down Expand Up @@ -512,7 +510,13 @@ argument (lookup appropriate binary, setup logging, pass global settings)"
;; Really start process
(let ((binary (merlin-command))
;; (flags (merlin-lookup 'flags merlin-buffer-configuration))
(process-environment (cl-copy-list process-environment))
(process-environment
;; for simplicity, we use a mere append here (leading to a
;; duplicate binding), it does work because only the first
;; occurrence is considered, one can check this by running
;; (call-process "printenv" nil t)
(append (merlin-lookup 'env merlin-buffer-configuration)
process-environment))
(dot-merlin (merlin-lookup 'dot-merlin merlin-buffer-configuration))
;; FIXME use logfile
;; (logfile (or (merlin-lookup 'logfile merlin-buffer-configuration)
Expand All @@ -522,16 +526,6 @@ argument (lookup appropriate binary, setup logging, pass global settings)"
(packages (merlin--map-flatten (lambda (x) (cons "-I" x))
merlin-buffer-packages-path))
(filename (buffer-file-name (buffer-base-buffer))))
;; Update environment
(dolist (binding (merlin-lookup 'env merlin-buffer-configuration))
(let* ((equal-pos (string-match-p "=" binding))
(prefix (if equal-pos
(substring binding 0 (1+ equal-pos))
binding))
(is-prefix (lambda (x) (string-prefix-p prefix x))))
(setq process-environment (cl-delete-if is-prefix process-environment))
(when equal-pos
(setq process-environment (cons binding process-environment)))))
;; Compute verbosity
(when (eq merlin-verbosity-context t)
(setq merlin-verbosity-context (cons command args)))
Expand Down Expand Up @@ -611,6 +605,35 @@ argument (lookup appropriate binary, setup logging, pass global settings)"
(kill-local-variable 'merlin-buffer-configuration)
(kill-local-variable 'merlin-erroneous-buffer)))))

(defcustom merlin-stop-server-on-opam-switch t
"If t, stops the Merlin server before the opam switch changes.
If the user changes the opam switch using `opam-switch-set-switch'
or an `\"OPSW\"' menu from `opam-switch-mode', this option asks to
stop the Merlin server process, so that the next Merlin command
starts a new server, typically with a different Merlin version
from a different opam switch.
See https://github.com/ProofGeneral/opam-switch-mode
Note: `opam-switch-mode' triggers automatic changes for `exec-path' and
`process-environment', which are useful to find the `\"ocamlmerlin\"'
binary (its filename can be overriden in `merlin-command') and the
binary of Merlin's subprocesses, in the ambient opam switch."
:type 'boolean)

(defun merlin--stop-server-on-opam-switch ()
"Stop the Merlin server before the opam switch changes.
This function is for the `opam-switch-mode' hook
`opam-switch-before-change-opam-switch-hook', which runs just
before the user changes the opam switch through `opam-switch-mode'."
(when (and merlin-mode merlin-stop-server-on-opam-switch)
(condition-case _sig
(merlin-stop-server)
(t (message "Info: (merlin-stop-server) failed in the previous opam switch")))))

(add-hook 'opam-switch-before-change-opam-switch-hook
#'merlin--stop-server-on-opam-switch t)

;;;;;;;;;;;;;;;;;;;;
;; FILE SWITCHING ;;
;;;;;;;;;;;;;;;;;;;;
Expand Down Expand Up @@ -1910,7 +1933,8 @@ Empty string defaults to jumping to all these."
(merlin-lookup 'do-not-cache-config merlin-buffer-configuration))
(setq merlin-buffer-configuration (merlin--configuration)))

(let ((command (merlin-lookup 'command merlin-buffer-configuration)))
(let ((command (merlin-lookup 'command merlin-buffer-configuration))
bin-path)
(unless command
(setq
command
Expand All @@ -1921,7 +1945,7 @@ Empty string defaults to jumping to all these."
(with-temp-buffer
(if (eq (call-process-shell-command
"opam var bin" nil (current-buffer) nil) 0)
(let ((bin-path
(let ((bin-dir
(replace-regexp-in-string "\n$" "" (buffer-string))))
;; the opam bin dir needs to be on the path, so if merlin
;; calls out to sub binaries (e.g. ocamlmerlin-reason), the
Expand All @@ -1930,21 +1954,17 @@ Empty string defaults to jumping to all these."

;; this was originally done via `opam exec' but that does not
;; work for opam 1, and added a performance hit
(setq merlin-opam-bin-path (list (concat "PATH=" bin-path)))
(concat bin-path "/ocamlmerlin"))

(setq bin-path (list (concat "PATH=" bin-dir))))
;; best effort if opam is not available, lookup for the binary in
;; the existing env
(progn
(message "merlin-command: opam var failed (%S)"
(buffer-string))
"ocamlmerlin"))))))

(message "merlin-command: opam var failed (%S)"
(buffer-string)))
"ocamlmerlin"))))
;; cache command in merlin-buffer configuration to avoid having to shell
;; out to `opam` each time.
(push (cons 'command command) merlin-buffer-configuration)
(when merlin-opam-bin-path
(push (cons 'env merlin-opam-bin-path) merlin-buffer-configuration)))
(when bin-path
(push (cons 'env bin-path) merlin-buffer-configuration)))

command))

Expand Down
7 changes: 5 additions & 2 deletions merlin.opam
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,11 @@ Add opam emacs directory to your load-path by appending this to your .emacs:
(add-hook 'tuareg-mode-hook 'merlin-mode t)
(add-hook 'caml-mode-hook 'merlin-mode t)
;; Use opam switch to lookup ocamlmerlin binary
(setq merlin-command 'opam)))

(setq merlin-command 'opam)
;; To easily change opam switches within a given Emacs session, you can
;; install the minor mode https://github.com/ProofGeneral/opam-switch-mode
;; and use one of its \"OPSW\" menus.
))
Take a look at https://github.com/ocaml/merlin for more information

Quick setup with opam-user-setup
Expand Down

0 comments on commit fd1bac3

Please sign in to comment.