Skip to content

Commit

Permalink
Merge pull request #20 from tarides/phrase-prev-and-next-using-tunneling
Browse files Browse the repository at this point in the history
Add `ocaml-eglot-phrase-prev/next` (using merlinCall)
  • Loading branch information
xvw authored Jan 16, 2025
2 parents 51eb8cb + e97666f commit d002bce
Show file tree
Hide file tree
Showing 4 changed files with 91 additions and 3 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,10 @@ provided:

- `ocaml-eglot-find-type-definition-in-new-window`
- `ocaml-eglot-find-type-definition-in-current-window`
- `ocaml-eglot-phrase-prev` (<kbd>C-c</kbd> <kbd>C-p</kbd>): jump to
the beginning of the previous phrase
- `ocaml-eglot-phrase-next` (<kbd>C-c</kbd> <kbd>C-n</kbd>): jump to
the beginning of the next phrase

### Find occurences

Expand Down
17 changes: 17 additions & 0 deletions ocaml-eglot-req.el
Original file line number Diff line number Diff line change
Expand Up @@ -184,5 +184,22 @@ VERBOSITY is a potential verbosity index."
(let ((action-kind "destruct (enumerate cases)"))
(ocaml-eglot-req--call-code-action action-kind)))

(defun ocaml-eglot-req--merlin-call (command argv)
"Use tunneling `ocamllsp/merlinCallCompatible'.
COMMAND is the command of the Merlin Protocol.
ARGV is the list of arguments."
(let ((params (append (ocaml-eglot-req--TextDocumentIdentifier)
`(:command, command)
`(:resultAsSexp, :json-false)
`(:args, argv))))
(ocaml-eglot-req--send :ocamllsp/merlinCallCompatible params)))

(defun ocaml-eglot-req--phrase (target)
"Compute the beginning of the phrase referenced by TARGET."
; TODO: use a dedicated custom request instead of tunneling
(let ((argv (vector "-position" (ocaml-eglot-util-point-as-arg (point))
"-target" target)))
(ocaml-eglot-req--merlin-call "phrase" argv)))

(provide 'ocaml-eglot-req)
;;; ocaml-eglot-req.el ends here
50 changes: 47 additions & 3 deletions ocaml-eglot-util.el
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,19 @@

;;; Code:

(require 'json)
(require 'eglot)
(require 'cl-lib)

;; Generic util

(defun ocaml-eglot-util--goto-char (target)
"Goto the point TARGET."
(when (or (< target (point-min))
(> target (point-max)))
(widen))
(goto-char target))

(defun ocaml-eglot-util--text-less-than (text limit)
"Return non-nil if TEXT is less than LIMIT."
(let ((count 0)
Expand Down Expand Up @@ -48,21 +56,44 @@
(switch-to-buffer buf)
t)))))

(defun ocaml-eglot-util-point-as-arg (point)
"Compute POINT as a valid Merlin position."
(save-excursion
(save-restriction
(widen)
(goto-char point)
(let ((line (line-number-at-pos))
(column (- (position-bytes (point))
(position-bytes (line-beginning-position)))))
(format "%d:%d" line column)))))

(defun ocaml-eglot-util--point-by-pos (line col)
"Compute LINE and COL as a point."
(save-excursion
(save-restriction
(widen)
(goto-char (point-min))
(forward-line (1- line))
(let* ((offset-l (position-bytes (point)))
(offset-c (max 0 col))
(target (+ offset-l offset-c)))
(byte-to-position target)))))

(defun ocaml-eglot-util--replace-region (range content)
"Replace a LSP region (RANGE) by a given CONTENT."
(pcase-let ((`(,beg . ,end) (eglot--range-region range)))
(delete-region beg end)
(goto-char beg)
(ocaml-eglot-util--goto-char beg)
(insert content)))

(defun ocaml-eglot-util--jump-to (position)
"Move the cursor to a POSITION calculated by LSP."
(goto-char (eglot--lsp-position-to-point position)))
(ocaml-eglot-util--goto-char (eglot--lsp-position-to-point position)))

(defun ocaml-eglot-util--jump-to-range (range)
"Move the cursor to the start of a RANGE calculated by LSP."
(let ((start (cl-getf range :start)))
(goto-char (eglot--lsp-position-to-point start))))
(ocaml-eglot-util--goto-char (eglot--lsp-position-to-point start))))

(defun ocaml-eglot-util--compare-position (a b)
"Comparison between two LSP positions, A and B."
Expand Down Expand Up @@ -151,5 +182,18 @@ current window otherwise."
(overlay-put overlay 'ocaml-eglot-highlight 'highlight)
(unwind-protect (sit-for 60) (delete-overlay overlay))))

(defun ocaml-eglot-util--as-json (str)
"Parse a string STR as a Json object."
(json-parse-string str :object-type 'plist))

(defun ocaml-eglot-util--merlin-call-result (result)
"Extract the RESULT of a Merlin Call Compatible request."
(let* ((result (cl-getf result :result))
(json-result (ocaml-eglot-util--as-json result))
(result-class (cl-getf json-result :class)))
(if (string= result-class "return")
(cl-getf json-result :value)
(eglot--error "Invalid result class %s" result-class))))

(provide 'ocaml-eglot-util)
;;; ocaml-eglot-util.el ends here
23 changes: 23 additions & 0 deletions ocaml-eglot.el
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,27 @@ If there is no available holes, it returns the first one of HOLES."
(ocaml-eglot-util--jump-to position)
(eglot--error "Target not found")))))

(defun ocaml-eglot--phrase (direction)
"Move to the next or previous phrase using DIRECTION."
(let* ((result (ocaml-eglot-req--phrase direction))
(json-result (ocaml-eglot-util--merlin-call-result result))
(pos (cl-getf json-result :pos)))
(when pos
(let* ((line (cl-getf pos :line))
(col (cl-getf pos :col))
(target (ocaml-eglot-util--point-by-pos line col)))
(ocaml-eglot-util--goto-char target)))))

(defun ocaml-eglot-phrase-next ()
"Go to the beginning of the next phrase."
(interactive)
(ocaml-eglot--phrase "next"))

(defun ocaml-eglot-phrase-prev ()
"Go to the beginning of the previous phrase."
(interactive)
(ocaml-eglot--phrase "prev"))

;; Search by type or polarity

(defun ocaml-eglot--search-as-key (value-name value-type value-doc)
Expand Down Expand Up @@ -485,6 +506,8 @@ If called repeatedly, increase the verbosity of the type shown."
(define-key ocaml-eglot-keymap (kbd "C-c C-t") #'ocaml-eglot-type-enclosing)
(define-key ocaml-eglot-keymap (kbd "C-c |") #'ocaml-eglot-destruct)
(define-key ocaml-eglot-keymap (kbd "C-c \\") #'ocaml-eglot-construct)
(define-key ocaml-eglot-keymap (kbd "C-c C-p") #'ocaml-eglot-phrase-prev)
(define-key ocaml-eglot-keymap (kbd "C-c C-n") #'ocaml-eglot-phrase-next)
ocaml-eglot-keymap)
"Keymap for OCaml-eglot minor mode.")

Expand Down

0 comments on commit d002bce

Please sign in to comment.