Skip to content

Commit

Permalink
Merge pull request #13 from tarides/locate-using-lsp-queries
Browse files Browse the repository at this point in the history
Locate using lsp queries
  • Loading branch information
xvw authored Jan 6, 2025
2 parents 7bb5b4e + 441b617 commit 1578210
Show file tree
Hide file tree
Showing 7 changed files with 155 additions and 19 deletions.
43 changes: 35 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,17 +38,44 @@ navigating through errors:

![Error navigation example](media/error-navigation.gif)

### Jump to definition
### Jump to definition/declaration

Eglot relies on
[Xref](https://www.gnu.org/software/emacs/manual/html_node/emacs/Xref.html)
to index cross-references. OCaml-eglot provides a shortcut to quickly
jump to the definition:
OCaml-eglot provides a shortcut to quickly jump to the definition or
declaration of an identifier:

- `ocaml-eglot-find-definition` (<kbd>C-c</kbd> <kbd>C-l</kbd>): jump to
definition (the implementation)

- `ocaml-eglot-find-declaration` (<kbd>C-c</kbd> <kbd>C-i</kbd>): jump to
declaration (the signature)

![Jump to definition example](media/find-def-decl.gif)

The default calculation for the window containing the jump result is
_smart_: if the target is on the same file, the command uses the same
window; if the target is on another file, the command opens a new
window. Auxiliary functions for controlling the placement of a result
are provided:

- `ocaml-eglot-find-definition-in-new-window`
- `ocaml-eglot-find-declaration-in-new-window`
- `ocaml-eglot-find-definition-in-current-window`
- `ocaml-eglot-find-declaration-in-current-window`

The default behavior can also be configured using the
`ocaml-eglot-open-window-strategy` variable.

### Jump to type definition of an expression

You can also jump to the type definition of the expression at point.

![Jump to type definition example](media/find-type-decl.gif)

- `ocaml-eglot-locate` (<kbd>C-c</kbd> <kbd>C-l</kbd>): jump to
definition
Auxiliary functions for controlling the placement of a result are
provided:

![Jump to definition example](media/locate.gif)
- `ocaml-eglot-find-type-definition-in-new-window`
- `ocaml-eglot-find-type-definition-in-current-window`

### Infer Interface

Expand Down
Binary file added media/find-def-decl.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added media/find-type-decl.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed media/locate.gif
Binary file not shown.
15 changes: 15 additions & 0 deletions ocaml-eglot-req.el
Original file line number Diff line number Diff line change
Expand Up @@ -141,5 +141,20 @@ under the cursor. The MARKUP-KIND can also be configured."
markup-kind)))
(ocaml-eglot-req--send :ocamllsp/getDocumentation params)))

(defun ocaml-eglot-req--definition ()
"Execute the `textDocument/definition' request for the current point."
(let ((params (ocaml-eglot-req--TextDocumentPositionParams)))
(ocaml-eglot-req--send :textDocument/definition params)))

(defun ocaml-eglot-req--type-definition ()
"Execute the `textDocument/typeDefinition' request for the current point."
(let ((params (ocaml-eglot-req--TextDocumentPositionParams)))
(ocaml-eglot-req--send :textDocument/typeDefinition params)))

(defun ocaml-eglot-req--declaration ()
"Execute the `textDocument/declaration' request for the current point."
(let ((params (ocaml-eglot-req--TextDocumentPositionParams)))
(ocaml-eglot-req--send :textDocument/declaration params)))

(provide 'ocaml-eglot-req)
;;; ocaml-eglot-req.el ends here
12 changes: 12 additions & 0 deletions ocaml-eglot-util.el
Original file line number Diff line number Diff line change
Expand Up @@ -99,5 +99,17 @@
(list :start start
:end (ocaml-eglot-util--position-increase-char start "_")))))

(defun ocaml-eglot-util--visit-file (strategy current-file new-file range)
"Visits a referenced document, NEW-FILE at position start of RANGE.
The STRATEGY can be `'new' `'current' or `'smart'. The later opens a
new window if the destination is not in the CURRENT-FILE, ans uses the
current window otherwise."
(push-mark)
(cond ((eq strategy 'new) (find-file-other-window new-file))
((eq strategy 'current) (find-file new-file))
((string= current-file new-file) (find-file new-file))
(t (find-file-other-window new-file)))
(ocaml-eglot-util--jump-to-range range))

(provide 'ocaml-eglot-util)
;;; ocaml-eglot-util.el ends here
104 changes: 93 additions & 11 deletions ocaml-eglot.el
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,13 @@ Otherwise, `merlin-construct' only includes constructors."
:group 'ocaml-eglot
:type 'string)

(defcustom ocaml-eglot-open-window-strategy 'smart
"Defines window opening strategy."
:group 'ocaml-eglot
:type '(choice (const :tag "Open a new window only if the target differs" smart)
(const :tag "Always open in a new window" new)
(const :tag "Always open in the current window" current)))

;;; Faces

(defface ocaml-eglot-value-name-face
Expand Down Expand Up @@ -102,18 +109,90 @@ Otherwise, `merlin-construct' only includes constructors."

;; Jump to definition

;; TODO: At the moment, the locate is essentially based on
;; `xref-find-definitions`, which isn't very smart:
;;
;; - We don't want to open a new window if the destination is the same
;; as the current document.
;; - We want to control whether we want to jump to ML or MLI
;; - We'd also like to be able to jump to the definition of a type
(defun ocaml-eglot--find-definition (strategy)
"Find the definition at point and jump to it using STRATEGY."
(let* ((query-result (ocaml-eglot-req--definition))
(result (ocaml-eglot-util--vec-first-or-nil query-result)))
(if result
(let* ((uri (cl-getf result :uri))
(range (cl-getf result :range))
(file (eglot--uri-to-path uri)))
(ocaml-eglot-util--visit-file strategy (buffer-file-name) file range))
(eglot--error "Not in environment"))))

(defun ocaml-eglot-find-definition ()
"Find the definition identifier at point."
(interactive)
(ocaml-eglot--find-definition ocaml-eglot-open-window-strategy))

(defun ocaml-eglot-locate ()
"Locate the identifier at point."
(defun ocaml-eglot-find-definition-in-new-window ()
"Find the definition of the identifier at point and show it in a new window."
(interactive)
(call-interactively #'xref-find-definitions))
(ocaml-eglot--find-definition 'new))

(defun ocaml-eglot-find-definition-in-current-window ()
"Find the definition of the identifier and show it in the current window."
(interactive)
(ocaml-eglot--find-definition 'current))

;; Jump to declaration

(defun ocaml-eglot--find-declaration (strategy)
"Find the declaration of the identifier at point and jump to it using STRATEGY."
(let* ((query-result (ocaml-eglot-req--declaration))
(result (ocaml-eglot-util--vec-first-or-nil query-result)))
(if result
(let* ((uri (cl-getf result :uri))
(range (cl-getf result :range))
(file (eglot--uri-to-path uri)))
(ocaml-eglot-util--visit-file strategy (buffer-file-name) file range))
(eglot--error "Not in environment"))))

(defun ocaml-eglot-find-declaration ()
"Find the declaration of the identifier at point."
(interactive)
(ocaml-eglot--find-declaration ocaml-eglot-open-window-strategy))

(defun ocaml-eglot-find-declaration-in-new-window ()
"Find the declaration of the identifier at point and show it in a new window."
(interactive)
(ocaml-eglot--find-declaration 'new))

(defun ocaml-eglot-find-declaration-in-current-window ()
"Find the declaration of the identifier at point.
Show it the current window."
(interactive)
(ocaml-eglot--find-declaration 'current))

;; Jump type declaration of expression

(defun ocaml-eglot--find-type-definition (strategy)
"Find the definition of the type of the expression at point using STRATEGY."
(let* ((query-result (ocaml-eglot-req--type-definition))
(result (ocaml-eglot-util--vec-first-or-nil query-result)))
(if result
(let* ((uri (cl-getf result :uri))
(range (cl-getf result :range))
(file (eglot--uri-to-path uri)))
(ocaml-eglot-util--visit-file strategy (buffer-file-name) file range))
(eglot--error "Not in environment"))))

(defun ocaml-eglot-find-type-definition ()
"Find the definition of the type of the expression at point."
(interactive)
(ocaml-eglot--find-type-definition ocaml-eglot-open-window-strategy))

(defun ocaml-eglot-find-type-definition-in-new-window ()
"Find the definition of the type of the expression at point.
Show it in a new window."
(interactive)
(ocaml-eglot--find-type-definition 'new))

(defun ocaml-eglot-find-type-definition-in-current-window ()
"Find the definition of the type of the expression at point.
Show it in the current window."
(interactive)
(ocaml-eglot--find-type-definition 'current))

;; Infer interface

Expand Down Expand Up @@ -369,12 +448,14 @@ It use the ARG to use local values or not."
(let ((ocaml-eglot-keymap (make-sparse-keymap)))
(define-key ocaml-eglot-keymap (kbd "C-c C-x") #'ocaml-eglot-error-next)
(define-key ocaml-eglot-keymap (kbd "C-c C-c") #'ocaml-eglot-error-prev)
(define-key ocaml-eglot-keymap (kbd "C-c C-l") #'ocaml-eglot-locate)
(define-key ocaml-eglot-keymap (kbd "C-c C-l") #'ocaml-eglot-find-definition)
(define-key ocaml-eglot-keymap (kbd "C-c C-i") #'ocaml-eglot-find-declaration)
(define-key ocaml-eglot-keymap (kbd "C-c C-a") #'ocaml-eglot-alternate-file)
(define-key ocaml-eglot-keymap (kbd "C-c C-d") #'ocaml-eglot-document)
ocaml-eglot-keymap)
"Keymap for OCaml-eglot minor mode.")


;;;###autoload
(define-minor-mode ocaml-eglot
"Minor mode for interacting with `ocaml-lsp-server' using `eglot' as a client.
Expand All @@ -387,3 +468,4 @@ It use the ARG to use local values or not."

(provide 'ocaml-eglot)
;;; ocaml-eglot.el ends here

0 comments on commit 1578210

Please sign in to comment.