Skip to content

Commit 87f62b1

Browse files
authored
Merge pull request #14 from tarides/type-enclosing-final
Type Enclosing Command
2 parents 904707e + de76812 commit 87f62b1

File tree

6 files changed

+250
-4
lines changed

6 files changed

+250
-4
lines changed

README.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,30 @@ navigating through errors:
3838

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

41+
### Type Enclosings
42+
43+
In `ocaml-eglot` one can display the type of the expression below the cursor and
44+
navigate the enclosing nodes while increasing or decreasing verbosity:
45+
46+
- `ocaml-eglot-type-enclosing` (<kbd>C-c</kbd> <kbd>C-t</kbd>)
47+
Display the type of the selection and start a "type enclosing" session.
48+
49+
During a "type enclosing" session the following commands are available:
50+
51+
- `ocaml-eglot-type-enclosing-increase-verbosity` (<kbd>C-c</kbd>
52+
<kbd>C-t</kbd> or <kbd>C-→</kbd>): to increase the verbosity of the
53+
type observed
54+
- `ocaml-eglot-type-enclosing-decrease-verbosity` (<kbd>C-←</kbd>): to
55+
decrease verbosity of the type observed
56+
- `ocaml-eglot-type-enclosing-grow` (<kbd>C-↑</kbd>): to grow the
57+
expression
58+
- `ocaml-eglot-type-enclosing-shrink` (<kbd>C-↓</kbd>): to shrink the
59+
expression
60+
- `ocaml-eglot-type-enclosing-copy` (<kbd>C-w</kbd>): to copy the
61+
type expression to the _kill-ring_ (clipboard)
62+
63+
![Type Enclosings example](media/type-enclosing.gif)
64+
4165
### Jump to definition/declaration
4266

4367
OCaml-eglot provides a shortcut to quickly jump to the definition or

media/type-enclosing.gif

228 KB
Loading

ocaml-eglot-req.el

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
;;; ocaml-eglot-req.el --- LSP custom request -*- coding: utf-8; lexical-binding: t -*-
22

3-
;; Copyright (C) 2024 Xavier Van de Woestyne
3+
;; Copyright (C) 2024-2025 Xavier Van de Woestyne
44
;; Licensed under the MIT license.
55

66
;; Author: Xavier Van de Woestyne <[email protected]>
@@ -95,6 +95,17 @@ A potential IDENTIFIER can be given and MARKUP-KIND can be parametrized."
9595
(if identifier (append params `(:identifier, identifier))
9696
params)))
9797

98+
(defun ocaml-eglot-req--TypeEnclosingParams (at index verbosity)
99+
"Compute the `TypeEnclosingParams'.
100+
AT is the range or the position.
101+
INDEX is the index of the enclosing.
102+
VERBOSITY is a potential verbosity index."
103+
(append (list :textDocument (ocaml-eglot-req--TextDocumentIdentifier))
104+
(ocaml-eglot-req--TextDocumentIdentifier)
105+
`(:at, at)
106+
`(:index, index)
107+
`(:verbosity, verbosity)))
108+
98109
;;; Concrete requests
99110

100111
(defun ocaml-eglot-req--jump ()
@@ -156,5 +167,13 @@ under the cursor. The MARKUP-KIND can also be configured."
156167
(let ((params (ocaml-eglot-req--TextDocumentPositionParams)))
157168
(ocaml-eglot-req--send :textDocument/declaration params)))
158169

170+
(defun ocaml-eglot-req--type-enclosings (at index verbosity)
171+
"Execute the `ocamllsp/typeEnclosing' request for the current point.
172+
AT is the range or the position.
173+
INDEX is the index of the enclosing.
174+
VERBOSITY is a potential verbosity index."
175+
(let ((params (ocaml-eglot-req--TypeEnclosingParams at index verbosity)))
176+
(ocaml-eglot-req--send :ocamllsp/typeEnclosing params)))
177+
159178
(provide 'ocaml-eglot-req)
160179
;;; ocaml-eglot-req.el ends here

ocaml-eglot-type-enclosing.el

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
;;; ocaml-eglot-type-enclosing.el --- Type Enclosing feature -*- coding: utf-8; lexical-binding: t -*-
2+
3+
;; Copyright (C) 2024-2025 Xavier Van de Woestyne
4+
;; Licensed under the MIT license.
5+
6+
;; Author: Xavier Van de Woestyne <[email protected]>
7+
;; Created: 10 January 2025
8+
;; SPDX-License-Identifier: MIT
9+
10+
;;; Commentary:
11+
12+
;; Plumbing needed to implement the primitives related to type
13+
;; enclosing commands.
14+
15+
;;; Code:
16+
17+
(require 'cl-lib)
18+
(require 'ocaml-eglot-util)
19+
(require 'ocaml-eglot-req)
20+
21+
;;; Customizable variables
22+
23+
(defcustom ocaml-eglot-type-buffer-name "*ocaml-eglot-types*"
24+
"The name of the buffer storing types."
25+
:group 'ocaml-eglot
26+
:type 'string)
27+
28+
;;; Internal variables
29+
30+
(defvar-local ocaml-eglot-type-enclosing-types nil
31+
"Current list of enclosings related to types.")
32+
33+
(defvar-local ocaml-eglot-type-enclosing-current-type nil
34+
"Current type for the current enclosing.")
35+
36+
(defvar-local ocaml-eglot-type-enclosing-offset 0
37+
"The offset of the requested enclosings.")
38+
39+
(defvar-local ocaml-eglot-type-enclosing-verbosity 0
40+
"The verbosity of the current enclosing request.")
41+
42+
;;; Key mapping for type enclosing
43+
44+
(defvar ocaml-eglot-type-enclosing-map
45+
(let ((keymap (make-sparse-keymap)))
46+
(define-key keymap (kbd "C-<up>") #'ocaml-eglot-type-enclosing-grow)
47+
(define-key keymap (kbd "C-<down>") #'ocaml-eglot-type-enclosing-shrink)
48+
(define-key keymap (kbd "C-w") #'ocaml-eglot-type-enclosing-copy)
49+
(define-key keymap (kbd "C-c C-t") #'ocaml-eglot-type-enclosing-increase-verbosity)
50+
(define-key keymap (kbd "C-<right>") #'ocaml-eglot-type-enclosing-increase-verbosity)
51+
(define-key keymap (kbd "C-<left>") #'ocaml-eglot-type-enclosing-decrease-verbosity)
52+
keymap)
53+
"Keymap for OCaml-eglot's type enclosing transient mode.")
54+
55+
;;; Internal functions
56+
57+
(defun ocaml-eglot-type-enclosing-copy ()
58+
"Copy the type of the current enclosing to the Kill-ring."
59+
(interactive)
60+
(when ocaml-eglot-type-enclosing-current-type
61+
(eglot--message "Copied `%s' to kill-ring"
62+
ocaml-eglot-type-enclosing-current-type)
63+
(kill-new ocaml-eglot-type-enclosing-current-type)))
64+
65+
(defun ocaml-eglot-type-enclosing--with-fixed-offset ()
66+
"Compute the type enclosing for a dedicated offset."
67+
(let* ((verbosity ocaml-eglot-type-enclosing-verbosity)
68+
(index ocaml-eglot-type-enclosing-offset)
69+
(at (ocaml-eglot-util--current-position-or-range))
70+
(result (ocaml-eglot-req--type-enclosings at index verbosity))
71+
(type (cl-getf result :type)))
72+
(setq ocaml-eglot-type-enclosing-current-type type)
73+
(ocaml-eglot-type-enclosing--display type t)))
74+
75+
(defun ocaml-eglot-type-enclosing-increase-verbosity ()
76+
"Increase the verbosity of the current request."
77+
(interactive)
78+
(setq ocaml-eglot-type-enclosing-verbosity
79+
(1+ ocaml-eglot-type-enclosing-verbosity))
80+
(ocaml-eglot-type-enclosing--with-fixed-offset))
81+
82+
(defun ocaml-eglot-type-enclosing-decrease-verbosity ()
83+
"Decrease the verbosity of the current request."
84+
(interactive)
85+
(when (> ocaml-eglot-type-enclosing-verbosity 0)
86+
(setq ocaml-eglot-type-enclosing-verbosity
87+
(1- ocaml-eglot-type-enclosing-verbosity)))
88+
(ocaml-eglot-type-enclosing--with-fixed-offset))
89+
90+
(defun ocaml-eglot-type-enclosing-grow ()
91+
"Growing of the type enclosing."
92+
(interactive)
93+
(when ocaml-eglot-type-enclosing-types
94+
(setq ocaml-eglot-type-enclosing-offset
95+
(mod (1+ ocaml-eglot-type-enclosing-offset)
96+
(length ocaml-eglot-type-enclosing-types)))
97+
(ocaml-eglot-type-enclosing--with-fixed-offset)))
98+
99+
(defun ocaml-eglot-type-enclosing-shrink ()
100+
"Display the type enclosing of a smaller enclosing if possible."
101+
(interactive)
102+
(when ocaml-eglot-type-enclosing-types
103+
(setq ocaml-eglot-type-enclosing-offset
104+
(mod (1- ocaml-eglot-type-enclosing-offset)
105+
(length ocaml-eglot-type-enclosing-types)))
106+
(ocaml-eglot-type-enclosing--with-fixed-offset)))
107+
108+
(defun ocaml-eglot-type-enclosing--type-buffer (type-expr)
109+
"Create buffer with content TYPE-EXPR of the enclosing type buffer."
110+
; We store the current major mode to be used in the type buffer for
111+
; syntax highlighting.
112+
(let ((curr-dir default-directory)
113+
(current-major-mode major-mode))
114+
(with-current-buffer (get-buffer-create ocaml-eglot-type-buffer-name)
115+
(funcall current-major-mode)
116+
(read-only-mode 0)
117+
(erase-buffer)
118+
(insert type-expr)
119+
(goto-char (point-min))
120+
(read-only-mode 1)
121+
(setq default-directory curr-dir))))
122+
123+
(defun ocaml-eglot-type-enclosing--display (type-expr &optional current)
124+
"Display the type-enclosing for TYPE-EXPR in a dedicated buffer.
125+
If CURRENT is set, the range of the enclosing will be highlighted."
126+
(ocaml-eglot-type-enclosing--type-buffer type-expr)
127+
(if (ocaml-eglot-util--text-less-than type-expr 8)
128+
(message "%s" (with-current-buffer ocaml-eglot-type-buffer-name
129+
(font-lock-fontify-region (point-min) (point-max))
130+
(buffer-string)))
131+
(display-buffer ocaml-eglot-type-buffer-name))
132+
(when (and current (> (length ocaml-eglot-type-enclosing-types) 0))
133+
(let ((current (aref ocaml-eglot-type-enclosing-types
134+
ocaml-eglot-type-enclosing-offset)))
135+
(ocaml-eglot-util--highlight-range current
136+
'ocaml-eglot-highlight-region-face))))
137+
138+
(defun ocaml-eglot-type-enclosing--reset ()
139+
"Reset local variables defined by the enclosing query."
140+
(setq ocaml-eglot-type-enclosing-current-type nil)
141+
(setq ocaml-eglot-type-enclosing-verbosity 0)
142+
(setq ocaml-eglot-type-enclosing-types nil)
143+
(setq ocaml-eglot-type-enclosing-offset 0))
144+
145+
(defun ocaml-eglot-type-enclosing--call ()
146+
"Print the type of the expression under point."
147+
(ocaml-eglot-type-enclosing--reset)
148+
(let* ((verbosity ocaml-eglot-type-enclosing-verbosity)
149+
(index ocaml-eglot-type-enclosing-offset)
150+
(at (ocaml-eglot-util--current-position-or-range))
151+
(result (ocaml-eglot-req--type-enclosings at index verbosity))
152+
(type (cl-getf result :type))
153+
(enclosings (cl-getf result :enclosings)))
154+
(setq ocaml-eglot-type-enclosing-types enclosings)
155+
(setq ocaml-eglot-type-enclosing-current-type type)
156+
(ocaml-eglot-type-enclosing--display type t)
157+
(set-transient-map ocaml-eglot-type-enclosing-map t
158+
'ocaml-eglot-type-enclosing--reset)))
159+
160+
(provide 'ocaml-eglot-type-enclosing)
161+
;;; ocaml-eglot-type-enclosing.el ends here

ocaml-eglot-util.el

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
;;; ocaml-eglot-util.el --- Auxiliary tools -*- coding: utf-8; lexical-binding: t -*-
22

3-
;; Copyright (C) 2024 Xavier Van de Woestyne
3+
;; Copyright (C) 2024-2025 Xavier Van de Woestyne
44
;; Licensed under the MIT license.
55

66
;; Author: Xavier Van de Woestyne <[email protected]>
@@ -21,6 +21,17 @@
2121

2222
;; Generic util
2323

24+
(defun ocaml-eglot-util--text-less-than (text limit)
25+
"Return non-nil if TEXT is less than LIMIT."
26+
(let ((count 0)
27+
(pos 0))
28+
(save-match-data
29+
(while (and (<= count limit)
30+
(string-match "\n" text pos))
31+
(setq pos (match-end 0))
32+
(setq count (1+ count))))
33+
(<= count limit)))
34+
2435
(defun ocaml-eglot-util--vec-first-or-nil (vec)
2536
"Return the first element of VEC or nil."
2637
(when (> (length vec) 0)
@@ -110,6 +121,14 @@
110121
(list :start start
111122
:end (ocaml-eglot-util--position-increase-char start "_")))))
112123

124+
(defun ocaml-eglot-util--current-position-or-range ()
125+
"Return the current position or a range if the region is active."
126+
(if (region-active-p)
127+
(let ((beg (eglot--pos-to-lsp-position (region-beginning)))
128+
(end (eglot--pos-to-lsp-position (region-end))))
129+
`(:start ,beg :end ,end))
130+
(eglot--pos-to-lsp-position)))
131+
113132
(defun ocaml-eglot-util--visit-file (strategy current-file new-file range)
114133
"Visits a referenced document, NEW-FILE at position start of RANGE.
115134
The STRATEGY can be `'new' `'current' or `'smart'. The later opens a
@@ -122,5 +141,15 @@ current window otherwise."
122141
(t (find-file-other-window new-file)))
123142
(ocaml-eglot-util--jump-to-range range))
124143

144+
(defun ocaml-eglot-util--highlight-range (range face)
145+
"Highlight a given RANGE using a given FACE."
146+
(remove-overlays nil nil 'ocaml-eglot-highlight 'highlight)
147+
(let* ((beg (eglot--lsp-position-to-point (cl-getf range :start)))
148+
(end (eglot--lsp-position-to-point (cl-getf range :end)))
149+
(overlay (make-overlay beg end)))
150+
(overlay-put overlay 'face face)
151+
(overlay-put overlay 'ocaml-eglot-highlight 'highlight)
152+
(unwind-protect (sit-for 60) (delete-overlay overlay))))
153+
125154
(provide 'ocaml-eglot-util)
126155
;;; ocaml-eglot-util.el ends here

ocaml-eglot.el

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
;;; ocaml-eglot.el --- An OCaml companion for Eglot -*- coding: utf-8; lexical-binding: t -*-
22

3-
;; Copyright (C) 2024 The OCaml-eglot Project Contributors
3+
;; Copyright (C) 2024-2025 The OCaml-eglot Project Contributors
44
;; Licensed under the MIT license.
55

66
;; Author: Xavier Van de Woestyne <[email protected]>
@@ -33,10 +33,10 @@
3333
;;; Code:
3434

3535
(require 'flymake)
36-
(require 'xref)
3736
(require 'cl-lib)
3837
(require 'ocaml-eglot-util)
3938
(require 'ocaml-eglot-req)
39+
(require 'ocaml-eglot-type-enclosing)
4040
(require 'eglot)
4141

4242
(defgroup ocaml-eglot nil
@@ -93,6 +93,10 @@ Otherwise, `merlin-construct' only includes constructors."
9393
"Face describing the doc of values (used for search for example)."
9494
:group 'ocaml-eglot)
9595

96+
(defface ocaml-eglot-highlight-region-face
97+
'((t (:inherit highlight)))
98+
"Face used when highlighting a region.")
99+
96100
;;; Features
97101

98102
;; Jump to errors
@@ -446,6 +450,14 @@ It use the ARG to use local values or not."
446450
(interactive "sIdentifier: ")
447451
(ocaml-eglot--document-aux identifier))
448452

453+
;; Type Enclosings
454+
455+
(defun ocaml-eglot-type-enclosing ()
456+
"Print the type of the expression under point (or of the region, if it exists).
457+
If called repeatedly, increase the verbosity of the type shown."
458+
(interactive)
459+
(ocaml-eglot-type-enclosing--call))
460+
449461
;;; Mode
450462

451463
(defvar ocaml-eglot-map
@@ -456,6 +468,7 @@ It use the ARG to use local values or not."
456468
(define-key ocaml-eglot-keymap (kbd "C-c C-i") #'ocaml-eglot-find-declaration)
457469
(define-key ocaml-eglot-keymap (kbd "C-c C-a") #'ocaml-eglot-alternate-file)
458470
(define-key ocaml-eglot-keymap (kbd "C-c C-d") #'ocaml-eglot-document)
471+
(define-key ocaml-eglot-keymap (kbd "C-c C-t") #'ocaml-eglot-type-enclosing)
459472
ocaml-eglot-keymap)
460473
"Keymap for OCaml-eglot minor mode.")
461474

0 commit comments

Comments
 (0)