Skip to content

Commit

Permalink
Merge pull request #22 from tarides/fixes-and-improvement-before-release
Browse files Browse the repository at this point in the history
Fixes and improvement before release
  • Loading branch information
xvw authored Jan 17, 2025
2 parents 7e23b2a + b2102fc commit 43e6032
Show file tree
Hide file tree
Showing 5 changed files with 140 additions and 21 deletions.
25 changes: 25 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
ocaml-eglot 1.0.0
=================
Fri Jan 17 04:50:35 PM CET 2025

- First release of `ocaml-eglot`
([#1](https://github.com/tarides/ocaml-eglot/pull/1),
[#2](https://github.com/tarides/ocaml-eglot/pull/2),
[#3](https://github.com/tarides/ocaml-eglot/pull/3),
[#4](https://github.com/tarides/ocaml-eglot/pull/4),
[#5](https://github.com/tarides/ocaml-eglot/pull/5),
[#6](https://github.com/tarides/ocaml-eglot/pull/6),
[#7](https://github.com/tarides/ocaml-eglot/pull/7),
[#8](https://github.com/tarides/ocaml-eglot/pull/8),
[#9](https://github.com/tarides/ocaml-eglot/pull/9),
[#10](https://github.com/tarides/ocaml-eglot/pull/10),
[#11](https://github.com/tarides/ocaml-eglot/pull/11),
[#12](https://github.com/tarides/ocaml-eglot/pull/12),
[#13](https://github.com/tarides/ocaml-eglot/pull/13),
[#14](https://github.com/tarides/ocaml-eglot/pull/14),
[#16](https://github.com/tarides/ocaml-eglot/pull/16),
[#18](https://github.com/tarides/ocaml-eglot/pull/18),
[#19](https://github.com/tarides/ocaml-eglot/pull/19),
[#20](https://github.com/tarides/ocaml-eglot/pull/20),
[#21](https://github.com/tarides/ocaml-eglot/pull/21),
[#22](https://github.com/tarides/ocaml-eglot/pull/22))
85 changes: 80 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,3 @@
> [!WARNING]
> `ocaml-eglot` is **highly experimental** and at a very early stage
> of development. While we're very happy to collect user feedback,
> **don't overwhelm your OCaml development** environment just yet.
# ocaml-eglot

**`ocaml-eglot`** is a lightweight
Expand All @@ -15,13 +10,58 @@ client. This tool specifically caters to the OCaml ecosystem by
implementing canonical custom requests and commands exposed by the
[**`ocaml-lsp-server`**](https://github.com/ocaml/ocaml-lsp).

> [!WARNING]
> `ocaml-eglot` is **experimental** and at an early stage
> of development. While we're very happy to collect user feedback.
`ocaml-eglot` bridges the gap between generic LSP support and the
**specific needs of OCaml developers**. Its tight coupling with Eglot
ensures a lightweight experience without sacrificing the advanced
features made available by `ocaml-lsp-server`. Its aim is to offer a
user experience as close as possible to that offered by the Emacs mode
[Merlin](https://ocaml.github.io/merlin/editor/emacs/).

## Installation

`ocaml-eglot` is distributed as a [MELPA
package](https://melpa.org/#/ocaml-eglot). `ocaml-eglot` is only an
interface between `eglot` (available _out of the box_ since `emacs >=
29.1`) and Emacs, a major mode dedicated to OCaml editing must be
installed (e.g. [caml-mode](https://melpa.org/#/caml) or
[tuareg](https://melpa.org/#/tuareg)). Then, for example, you can use
[`use-package`](https://www.gnu.org/software/emacs/manual/html_node/use-package/Lisp-Configuration.html)
to install OCaml-eglot. You will also need
`https://ocaml.org/p/ocaml-lsp-server/latest` in the [current
switch](https://ocaml.org/docs/opam-switch-introduction).


Here's an example with Tuareg already installed:

```scheme
(use-package ocaml-eglot
:ensure t
:after tuareg
:hook
(tuareg-mode . ocaml-eglot)
(ocaml-eglot . eglot-ensure))
```

### Activating `format-on-save`

Eglot provides a hook to format the buffer on saving:

```diff
(use-package ocaml-eglot
:ensure t
:after tuareg
:hook
(tuareg-mode . ocaml-eglot)
- (ocaml-eglot . eglot-ensure))
+ (ocaml-eglot . eglot-ensure)
+ :config
+ (add-hook #'after-save-hook #'eglot-format))
```

## Features

### Browsing errors
Expand Down Expand Up @@ -122,6 +162,12 @@ project, it requires an index. This index can be created by running

![Occurences example](media/occurences.gif)

### Renaming

Use `ocaml-eglot-rename` to rename the symbol under the cursor. Starting with OCaml 5.3 it is possible to rename a symbol across multiple files after building an up-to-date index with `dune build @ocaml-index`.

![Rename example](media/rename.gif)

### Infer Interface

Used to infer the type of an interface file. If the buffer is not
Expand Down Expand Up @@ -218,3 +264,32 @@ option`:
(the search type is defined by the input query)

![Search Example](media/search.gif)

## Comparison of Merlin and OCaml-eglot commands

| `merlin` | `ocaml-eglot` | Note |
|-----------------------------|------------------------------------|--------------------------------------------------------------------------------------------------------------|
| `merlin-error-check` || The functionality is supported by `eglot` diagnostics (via LSP). |
| `merlin-error-next` | `ocaml-eglot-error-next` | |
| `merlin-error-prev` | `ocaml-eglot-error-prev` | |
| `merlin-type-enclosing` | `ocaml-eglot-type-enclosing` | |
| `merlin-type-expr` | `ocaml-eglot-type-expression` | |
| `merlin-locate` | `ocaml-eglot-find-declaration` | |
|| `ocaml-eglot-find-definition` | Available in Merlin by configuration |
|| `ocaml-eglot-find-type-definition` | |
| `merlin-locate-ident` || |
| `merlin-occurences` | `ocaml-eglot-occurences` | |
| `merlin-project-occurences` || Handle by `ocaml-eglot-occurences` (if `ocaml-version >= 5.2` and need an index, `dune build @ocaml-index`) |
| `merlin-iedit-occurrences` | `ocaml-eglot-rename` | |
| `merlin-document` | `ocaml-eglot-document` | also `ocaml-eglot-document-identifier` |
| `merlin-phrase-next` | `ocaml-eglot-phrase-next` | |
| `merlin-phrase-prev` | `ocaml-eglot-phrase-prev` | |
| `merlin-switch-to-ml` | `ocaml-eglot-alternate-file` | |
| `merlin-switch-to-mli` | `ocaml-eglot-alternate-file` | |
|| `ocaml-eglot-infer-interface` | It was supported by `Tuareg` (and a bit ad-hoc) |
| `merlin-jump` | `ocaml-eglot-jump` | |
| `merlin-destruct` | `ocaml-eglot-destruct` | |
| `merlin-construct` | `ocaml-eglot-construct` | |
| `merlin-next-hole` | `ocaml-eglot-hole-next` | |
| `merlin-previous-hole` | `ocaml-eglot-hole-prev` | |
| `merlin-toggle-view-errors` || An `eglot` configuration |
Binary file added media/rename.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
15 changes: 10 additions & 5 deletions ocaml-eglot-type-enclosing.el
Original file line number Diff line number Diff line change
Expand Up @@ -62,22 +62,27 @@
ocaml-eglot-type-enclosing-current-type)
(kill-new ocaml-eglot-type-enclosing-current-type)))

(defun ocaml-eglot-type-enclosing--with-fixed-offset ()
"Compute the type enclosing for a dedicated offset."
(defun ocaml-eglot-type-enclosing--with-fixed-offset (&optional prev-verb)
"Compute the type enclosing for a dedicated offset.
If PREV-VERB is given, the verbosity change ensure that the type is different."
(let* ((verbosity ocaml-eglot-type-enclosing-verbosity)
(index ocaml-eglot-type-enclosing-offset)
(at (ocaml-eglot-util--current-position-or-range))
(result (ocaml-eglot-req--type-enclosings at index verbosity))
(type (cl-getf result :type)))
(when (and prev-verb
(string= type ocaml-eglot-type-enclosing-current-type))
(setq ocaml-eglot-type-enclosing-verbosity prev-verb))
(setq ocaml-eglot-type-enclosing-current-type type)
(ocaml-eglot-type-enclosing--display type t)))

(defun ocaml-eglot-type-enclosing-increase-verbosity ()
"Increase the verbosity of the current request."
(interactive)
(setq ocaml-eglot-type-enclosing-verbosity
(1+ ocaml-eglot-type-enclosing-verbosity))
(ocaml-eglot-type-enclosing--with-fixed-offset))
(let ((prev-verbosity ocaml-eglot-type-enclosing-verbosity))
(setq ocaml-eglot-type-enclosing-verbosity
(1+ ocaml-eglot-type-enclosing-verbosity))
(ocaml-eglot-type-enclosing--with-fixed-offset prev-verbosity)))

(defun ocaml-eglot-type-enclosing-decrease-verbosity ()
"Decrease the verbosity of the current request."
Expand Down
36 changes: 25 additions & 11 deletions ocaml-eglot.el
Original file line number Diff line number Diff line change
Expand Up @@ -362,13 +362,14 @@ If there is no available holes, it returns the first one of HOLES."
(line (split-string doc "[\r\n]+")))
(car line)))

(defun ocaml-eglot--search-to-completion (entries)
"Transforms a list of ENTRIES into a search candidate (autocomplete)."
(defun ocaml-eglot--search-to-completion (entries key-completable)
"Transforms a list of ENTRIES into a search candidate (autocomplete).
KEY-COMPLETABLE define the current value to be selected."
(mapcar
(lambda (entry)
(let* ((value-name (cl-getf entry :name))
(value-type (cl-getf entry :typ))
(value-hole (cl-getf entry :constructible))
(value-hole (cl-getf entry key-completable))
(value-doc (ocaml-eglot--search-as-doc (cl-getf entry :doc)))
(key (ocaml-eglot--search-as-key value-name value-type value-doc)))
(cons key value-hole)))
Expand All @@ -386,26 +387,34 @@ If there is no available holes, it returns the first one of HOLES."
(cycle-sort-function . identity))
(complete-with-action action choices string pred))))

(defun ocaml-eglot-search (query &optional limit)
(defun ocaml-eglot--search (query limit key)
"Search a value using his type (or polarity) by a QUERY.
the universal prefix argument can be used to change the maximim number
of result (LIMIT)."
(interactive "sSearch query: \np")
the universal prefix argument can be used to change the maximum number
of result (LIMIT). KEY define the current value to be selected."
(eglot--server-capable-or-lose :experimental :ocamllsp :handleTypeSearch)
(let* ((start (eglot--pos-to-lsp-position))
(limit (or(if (> limit 1) limit nil)
(let* ((limit (or(if (> limit 1) limit nil)
ocaml-eglot-type-search-limit 25))
(with-doc (or ocaml-eglot-type-search-include-doc :json-false))
;; We use plaintext because the result of the documentation may
;; be truncated
(entries (ocaml-eglot-req--search query limit with-doc "plaintext"))
(choices (ocaml-eglot--search-to-completion entries))
(choices (ocaml-eglot--search-to-completion entries key))
(chosen (ocaml-eglot--search-completion
choices
(completing-read
"Candidates: "
(ocaml-eglot--search-complete-sort choices)
nil nil nil t)))
nil nil nil t))))
chosen))

(defun ocaml-eglot-search (query &optional limit)
"Search a value using his type (or polarity) by a QUERY.
the universal prefix argument can be used to change the maximim number
of result (LIMIT)."
(interactive "sSearch query: \np")
(eglot--server-capable-or-lose :experimental :ocamllsp :handleTypeSearch)
(let* ((start (eglot--pos-to-lsp-position))
(chosen (ocaml-eglot--search query limit :constructible))
(result (concat "(" chosen ")"))
(end (ocaml-eglot-util--position-increase-char start result)))
(when (region-active-p)
Expand Down Expand Up @@ -505,6 +514,11 @@ and print its type."
(interactive)
(call-interactively #'xref-find-references))

(defun ocaml-eglot-rename ()
"Rename the symbol at point."
(interactive)
(call-interactively #'eglot-rename))

;;; Mode

(defvar ocaml-eglot-map
Expand Down

0 comments on commit 43e6032

Please sign in to comment.