394 lines
16 KiB
EmacsLisp
394 lines
16 KiB
EmacsLisp
|
|
;;; lsp-ocaml.el --- description -*- lexical-binding: t; -*-
|
|||
|
|
|
|||
|
|
;; Copyright (C) 2020-2026 emacs-lsp maintainers
|
|||
|
|
|
|||
|
|
;; Author: emacs-lsp maintainers
|
|||
|
|
;; Keywords: lsp, ocaml
|
|||
|
|
|
|||
|
|
;; This program is free software; you can redistribute it and/or modify
|
|||
|
|
;; it under the terms of the GNU General Public License as published by
|
|||
|
|
;; the Free Software Foundation, either version 3 of the License, or
|
|||
|
|
;; (at your option) any later version.
|
|||
|
|
|
|||
|
|
;; This program is distributed in the hope that it will be useful,
|
|||
|
|
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|||
|
|
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|||
|
|
;; GNU General Public License for more details.
|
|||
|
|
|
|||
|
|
;; You should have received a copy of the GNU General Public License
|
|||
|
|
;; along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||
|
|
|
|||
|
|
;;; Commentary:
|
|||
|
|
|
|||
|
|
;; LSP Clients for the Ocaml Programming Language.
|
|||
|
|
|
|||
|
|
;;; Code:
|
|||
|
|
|
|||
|
|
(require 'lsp-mode)
|
|||
|
|
(require 'find-file)
|
|||
|
|
|
|||
|
|
(defgroup lsp-ocaml nil
|
|||
|
|
"LSP support for OCaml, using ocaml-language-server."
|
|||
|
|
:group 'lsp-mode
|
|||
|
|
:link '(url-link "https://github.com/freebroccolo/ocaml-language-server"))
|
|||
|
|
|
|||
|
|
(define-obsolete-variable-alias
|
|||
|
|
'lsp-ocaml-ocaml-lang-server-command
|
|||
|
|
'lsp-ocaml-lang-server-command
|
|||
|
|
"lsp-mode 6.1")
|
|||
|
|
|
|||
|
|
(defcustom lsp-ocaml-lang-server-command
|
|||
|
|
'("ocaml-language-server" "--stdio")
|
|||
|
|
"Command to start ocaml-language-server."
|
|||
|
|
:group 'lsp-ocaml
|
|||
|
|
:type '(choice
|
|||
|
|
(string :tag "Single string value")
|
|||
|
|
(repeat :tag "List of string values"
|
|||
|
|
string)))
|
|||
|
|
|
|||
|
|
(lsp-register-client
|
|||
|
|
(make-lsp-client :new-connection (lsp-stdio-connection
|
|||
|
|
(lambda () lsp-ocaml-lang-server-command))
|
|||
|
|
:major-modes '(reason-mode caml-mode neocaml-mode neocaml-interface-mode tuareg-mode)
|
|||
|
|
:priority -1
|
|||
|
|
:server-id 'ocaml-ls))
|
|||
|
|
|
|||
|
|
(defgroup lsp-ocaml-lsp-server nil
|
|||
|
|
"LSP support for OCaml, using ocaml-lsp-server."
|
|||
|
|
:group 'lsp-mode
|
|||
|
|
:link '(url-link "https://github.com/ocaml/ocaml-lsp"))
|
|||
|
|
|
|||
|
|
(define-obsolete-variable-alias 'lsp-merlin 'lsp-ocaml-lsp-server "lsp-mode 6.1")
|
|||
|
|
(define-obsolete-variable-alias 'lsp-merlin-command 'lsp-ocaml-lsp-server-command "lsp-mode 6.1")
|
|||
|
|
|
|||
|
|
;;; -------------------
|
|||
|
|
;;; OCaml-lsp custom variables
|
|||
|
|
;;; -------------------
|
|||
|
|
|
|||
|
|
(defcustom lsp-ocaml-lsp-server-command
|
|||
|
|
'("opam" "exec" "--" "ocamllsp")
|
|||
|
|
"Command to start ocaml-lsp-server."
|
|||
|
|
:group 'lsp-ocaml-lsp-server
|
|||
|
|
:type '(choice
|
|||
|
|
(string :tag "Single string value")
|
|||
|
|
(repeat :tag "List of string values"
|
|||
|
|
string)))
|
|||
|
|
|
|||
|
|
(lsp-register-client
|
|||
|
|
(make-lsp-client
|
|||
|
|
:new-connection
|
|||
|
|
(lsp-stdio-connection (lambda () lsp-ocaml-lsp-server-command))
|
|||
|
|
:major-modes '(reason-mode caml-mode neocaml-mode neocaml-interface-mode tuareg-mode)
|
|||
|
|
:priority 0
|
|||
|
|
:server-id 'ocaml-lsp-server))
|
|||
|
|
|
|||
|
|
(defcustom lsp-cut-signature 'space
|
|||
|
|
"If non-nil, signatures returned on hover will not be split on newline."
|
|||
|
|
:group 'lsp-ocaml-lsp-server
|
|||
|
|
:type '(choice (symbol :tag "Default behaviour" 'cut)
|
|||
|
|
(symbol :tag "Display all the lines with spaces" 'space)))
|
|||
|
|
|
|||
|
|
(defcustom lsp-ocaml-markupkind 'markdown
|
|||
|
|
"Preferred markup format."
|
|||
|
|
:group 'lsp-ocaml-lsp-server
|
|||
|
|
:type '(choice (symbol :tag "Markdown" 'markdown)
|
|||
|
|
(symbol :tag "Plain text" 'plaintext)))
|
|||
|
|
|
|||
|
|
(defcustom lsp-ocaml-enclosing-type-verbosity 1
|
|||
|
|
"Number of expansions of aliases in answers."
|
|||
|
|
:group 'lsp-ocaml-lsp-server
|
|||
|
|
:type 'int)
|
|||
|
|
|
|||
|
|
(defcustom lsp-ocaml-enclosing-type-cycle nil
|
|||
|
|
"When growing up or down the enclosings of a type, cycle when reaching one bound."
|
|||
|
|
:group 'lsp-ocaml-server
|
|||
|
|
:type 'boolean)
|
|||
|
|
|
|||
|
|
;;; -------------------
|
|||
|
|
;;; OCaml-lsp faces
|
|||
|
|
;;; -------------------
|
|||
|
|
|
|||
|
|
(defface lsp-ocaml-highlight-region-face '((t (:inherit region)))
|
|||
|
|
"Face used to highlight a region.")
|
|||
|
|
|
|||
|
|
;;; -------------------
|
|||
|
|
;;; OCaml-lsp extensions interface
|
|||
|
|
;;; -------------------
|
|||
|
|
|
|||
|
|
;;; The following functions are used to create an interface between custom OCaml-lsp requests and lsp-mode
|
|||
|
|
|
|||
|
|
(defun lsp-ocaml--switch-impl-intf ()
|
|||
|
|
"Switch to the file(s) that the current file can switch to.
|
|||
|
|
|
|||
|
|
OCaml-lsp custom protocol documented here
|
|||
|
|
https://github.com/ocaml/ocaml-lsp/blob/master/ocaml-lsp-server/docs/ocamllsp/switchImplIntf-spec.md"
|
|||
|
|
(-if-let* ((params (make-vector 1 (lsp--buffer-uri)))
|
|||
|
|
(uris (lsp-request "ocamllsp/switchImplIntf" params)))
|
|||
|
|
uris
|
|||
|
|
(lsp--warn "Your version of ocaml-lsp doesn't support the switchImplIntf extension")))
|
|||
|
|
|
|||
|
|
(defun lsp-ocaml--type-enclosing (verbosity index)
|
|||
|
|
"Get the type of the identifier at point.
|
|||
|
|
|
|||
|
|
VERBOSITY and INDEX use is described in the OCaml-lsp protocol documented here
|
|||
|
|
https://github.com/ocaml/ocaml-lsp/blob/master/ocaml-lsp-server/docs/ocamllsp/typeEnclosing-spec.md"
|
|||
|
|
(-if-let* ((params (lsp-make-ocaml-lsp-type-enclosing-params
|
|||
|
|
:uri (lsp--buffer-uri)
|
|||
|
|
:at (lsp--cur-position)
|
|||
|
|
:index index
|
|||
|
|
:verbosity verbosity))
|
|||
|
|
(result (lsp-request "ocamllsp/typeEnclosing" params)))
|
|||
|
|
result
|
|||
|
|
(lsp--warn "Your version of ocaml-lsp doesn't support the typeEnclosing extension")))
|
|||
|
|
|
|||
|
|
(defun lsp-ocaml--get-documentation (identifier content-format)
|
|||
|
|
"Get the documentation of IDENTIFIER or the identifier at point if IDENTIFIER is nil.
|
|||
|
|
|
|||
|
|
CONTENT-FORMAT is `Markdown' or `Plaintext'.
|
|||
|
|
OCaml-lsp protocol documented here
|
|||
|
|
https://github.com/ocaml/ocaml-lsp/blob/master/ocaml-lsp-server/docs/ocamllsp/getDocumentation-spec.md"
|
|||
|
|
(-if-let* ((position (if identifier nil (lsp--cur-position)))
|
|||
|
|
((&TextDocumentPositionParams :text-document :position) (lsp--text-document-position-params identifier position))
|
|||
|
|
(params (lsp-make-ocaml-lsp-get-documentation-params
|
|||
|
|
:textDocument text-document
|
|||
|
|
:position position
|
|||
|
|
:contentFormat content-format)))
|
|||
|
|
;; Don't exit if the request returns nil, an identifier can have no documentation
|
|||
|
|
(lsp-request "ocamllsp/getDocumentation" params)
|
|||
|
|
(lsp--warn "Your version of ocaml-lsp doesn't support the getDocumentation extension")))
|
|||
|
|
|
|||
|
|
(defun lsp-ocaml--infer-intf ()
|
|||
|
|
"Infer the interface of the given URI.
|
|||
|
|
|
|||
|
|
The URI should correspond to an implementation file, not an interface one.
|
|||
|
|
|
|||
|
|
OCaml-lsp protocol is documented here:
|
|||
|
|
https://github.com/ocaml/ocaml-lsp/blob/master/ocaml-lsp-server/docs/ocamllsp/inferIntf-spec.md"
|
|||
|
|
(-if-let* ((params (make-vector 1 (lsp--buffer-uri)))
|
|||
|
|
(result (lsp-request "ocamllsp/inferIntf" params)))
|
|||
|
|
result
|
|||
|
|
(lsp--warn "Your version of ocaml-lsp doesn't support the inferIntf extension")))
|
|||
|
|
|
|||
|
|
;;; -------------------
|
|||
|
|
;;; OCaml-lsp general utilities
|
|||
|
|
;;; -------------------
|
|||
|
|
|
|||
|
|
(defun lsp-ocaml--has-one-element-p (lst)
|
|||
|
|
"Return t if LST is a singleton."
|
|||
|
|
(and lst (= (length lst) 1)))
|
|||
|
|
|
|||
|
|
;;; -------------------
|
|||
|
|
;;; OCaml-lsp URI utilities
|
|||
|
|
;;; -------------------
|
|||
|
|
|
|||
|
|
(defun lsp-ocaml--is-interface (uri)
|
|||
|
|
"Return non-nil if the given URI is an interface, nil otherwise."
|
|||
|
|
(let ((path (lsp--uri-to-path uri)))
|
|||
|
|
(string-match-p "\\.\\(mli\\|rei\\|eliomi\\)\\'" path)))
|
|||
|
|
|
|||
|
|
(defun lsp-ocaml--on-interface ()
|
|||
|
|
"Return non-nil if the current URI is an interface, nil otherwise."
|
|||
|
|
(lsp-ocaml--is-interface (lsp--buffer-uri)))
|
|||
|
|
|
|||
|
|
(defun lsp-ocaml--load-uri (uri &optional other-window)
|
|||
|
|
"Check if URI exists and open its buffer or create a new one.
|
|||
|
|
|
|||
|
|
If OTHER-WINDOW is not nil, open the buffer in an other window."
|
|||
|
|
(let ((path (lsp--uri-to-path uri)))
|
|||
|
|
(cond
|
|||
|
|
|
|||
|
|
;; A buffer already exists with PATH
|
|||
|
|
((bufferp (get-file-buffer path))
|
|||
|
|
(ff-switch-to-buffer (get-file-buffer path) other-window)
|
|||
|
|
path)
|
|||
|
|
|
|||
|
|
;; PATH is an existing file
|
|||
|
|
((file-exists-p path)
|
|||
|
|
(ff-find-file path other-window nil)
|
|||
|
|
path)
|
|||
|
|
|
|||
|
|
;; PATH is not an existing file
|
|||
|
|
(t
|
|||
|
|
nil))))
|
|||
|
|
|
|||
|
|
(defun lsp-ocaml--find-alternate-uri ()
|
|||
|
|
"Return the URI corresponding to the alternate file if there's only one or prompt for a choice."
|
|||
|
|
(let ((uris (lsp-ocaml--switch-impl-intf)))
|
|||
|
|
(if (lsp-ocaml--has-one-element-p uris)
|
|||
|
|
(car uris)
|
|||
|
|
(let* ((filenames (mapcar #'f-filename uris))
|
|||
|
|
(selected-file (completing-read "Choose an alternate file " filenames)))
|
|||
|
|
(nth (cl-position selected-file filenames :test #'string=) uris)))))
|
|||
|
|
|
|||
|
|
;;; -------------------
|
|||
|
|
;;; OCaml-lsp type enclosing utilities
|
|||
|
|
;;; ------------------
|
|||
|
|
|
|||
|
|
(defvar-local lsp-ocaml--type-enclosing-verbosity lsp-ocaml-enclosing-type-verbosity)
|
|||
|
|
(defvar-local lsp-ocaml--type-enclosing-index 0)
|
|||
|
|
(defvar-local lsp-ocaml--type-enclosing-saved-type nil)
|
|||
|
|
(defvar-local lsp-ocaml--type-enclosing-type-enclosings nil)
|
|||
|
|
|
|||
|
|
(defun lsp-ocaml--init-type-enclosing-config ()
|
|||
|
|
"Create a new config for the type enclosing requests."
|
|||
|
|
(setq lsp-ocaml--type-enclosing-verbosity lsp-ocaml-enclosing-type-verbosity)
|
|||
|
|
(setq lsp-ocaml--type-enclosing-index 0)
|
|||
|
|
(setq lsp-ocaml--type-enclosing-saved-type nil)
|
|||
|
|
(setq lsp-ocaml--type-enclosing-type-enclosings nil))
|
|||
|
|
|
|||
|
|
(defun lsp-ocaml--highlight-current-type (range)
|
|||
|
|
"Highlight RANGE.
|
|||
|
|
|
|||
|
|
RANGE is (:start (:character .. :line ..)) :end (:character .. :line ..)"
|
|||
|
|
(remove-overlays nil nil 'face 'lsp-ocaml-highlight-region-face)
|
|||
|
|
(let* ((point-min (lsp--position-to-point (cl-getf range :start)))
|
|||
|
|
(point-max (lsp--position-to-point (cl-getf range :end)))
|
|||
|
|
(overlay (make-overlay point-min point-max)))
|
|||
|
|
(overlay-put overlay 'face 'lsp-ocaml-highlight-region-face)
|
|||
|
|
(unwind-protect (sit-for 10) (delete-overlay overlay))))
|
|||
|
|
|
|||
|
|
(defun lsp-ocaml--display-type (markupkind type doc)
|
|||
|
|
"Display TYPE in MARKUPKIND with its DOC attached.
|
|||
|
|
|
|||
|
|
If TYPE is a single-line that represents a module type, reformat it."
|
|||
|
|
(let* (;; Regroup the type and documentation at point
|
|||
|
|
(single-linep (not (string-match-p "\n" type)))
|
|||
|
|
(new-type (if single-linep (string-replace " val " "\n val " type) type))
|
|||
|
|
(new-type (if single-linep (string-replace " end" "\nend" new-type) type))
|
|||
|
|
(contents `(:kind ,markupkind
|
|||
|
|
:value ,(mapconcat #'identity `("```ocaml" ,new-type "```" "***" ,doc) "\n"))))
|
|||
|
|
(lsp--display-contents contents)))
|
|||
|
|
|
|||
|
|
;;; -------------------
|
|||
|
|
;;; OCaml-lsp type enclosing transient map
|
|||
|
|
;;; -------------------
|
|||
|
|
|
|||
|
|
(defvar lsp-ocaml-type-enclosing-map
|
|||
|
|
(let ((keymap (make-sparse-keymap)))
|
|||
|
|
(define-key keymap (kbd "C-<up>") #'lsp-ocaml-type-enclosing-go-up)
|
|||
|
|
(define-key keymap (kbd "C-<down>") #'lsp-ocaml-type-enclosing-go-down)
|
|||
|
|
(define-key keymap (kbd "C-w") #'lsp-ocaml-type-enclosing-copy)
|
|||
|
|
(define-key keymap (kbd "C-t") #'lsp-ocaml-type-enclosing-increase-verbosity)
|
|||
|
|
(define-key keymap (kbd "C-<right>") #'lsp-ocaml-type-enclosing-increase-verbosity)
|
|||
|
|
(define-key keymap (kbd "C-<left>") #'lsp-ocaml-type-enclosing-decrease-verbosity)
|
|||
|
|
keymap)
|
|||
|
|
"Keymap for OCaml-lsp type enclosing transient mode.")
|
|||
|
|
|
|||
|
|
(defun lsp-ocaml-type-enclosing-go-up ()
|
|||
|
|
"Go up the type's enclosing."
|
|||
|
|
(interactive)
|
|||
|
|
(when lsp-ocaml--type-enclosing-type-enclosings
|
|||
|
|
(setq lsp-ocaml--type-enclosing-index
|
|||
|
|
(if lsp-ocaml-enclosing-type-cycle
|
|||
|
|
(mod (1+ lsp-ocaml--type-enclosing-index)
|
|||
|
|
(length lsp-ocaml--type-enclosing-type-enclosings))
|
|||
|
|
(min (1+ lsp-ocaml--type-enclosing-index)
|
|||
|
|
(1- (length lsp-ocaml--type-enclosing-type-enclosings))))))
|
|||
|
|
(lsp-ocaml--get-and-display-type-enclosing))
|
|||
|
|
|
|||
|
|
(defun lsp-ocaml-type-enclosing-go-down ()
|
|||
|
|
"Go down the type's enclosing."
|
|||
|
|
(interactive)
|
|||
|
|
(when lsp-ocaml--type-enclosing-type-enclosings
|
|||
|
|
(setq lsp-ocaml--type-enclosing-index
|
|||
|
|
(if lsp-ocaml-enclosing-type-cycle
|
|||
|
|
(mod (1- lsp-ocaml--type-enclosing-index)
|
|||
|
|
(length lsp-ocaml--type-enclosing-type-enclosings))
|
|||
|
|
(max (1- lsp-ocaml--type-enclosing-index) 0))))
|
|||
|
|
(lsp-ocaml--get-and-display-type-enclosing))
|
|||
|
|
|
|||
|
|
(defun lsp-ocaml-type-enclosing-decrease-verbosity ()
|
|||
|
|
"Decreases the number of expansions of aliases in answer."
|
|||
|
|
(interactive)
|
|||
|
|
(let ((verbosity (max 0 (1- lsp-ocaml--type-enclosing-verbosity))))
|
|||
|
|
(setq lsp-ocaml--type-enclosing-verbosity verbosity))
|
|||
|
|
(lsp-ocaml--get-and-display-type-enclosing))
|
|||
|
|
|
|||
|
|
(defun lsp-ocaml-type-enclosing-increase-verbosity ()
|
|||
|
|
"Increases the number of expansions of aliases in answer."
|
|||
|
|
(interactive)
|
|||
|
|
(let ((verbosity (1+ lsp-ocaml--type-enclosing-verbosity)))
|
|||
|
|
(setq lsp-ocaml--type-enclosing-verbosity verbosity))
|
|||
|
|
(lsp-ocaml--get-and-display-type-enclosing t))
|
|||
|
|
|
|||
|
|
(defun lsp-ocaml-type-enclosing-copy ()
|
|||
|
|
"Copy the type of the saved enclosing type to the `kill-ring'."
|
|||
|
|
(interactive)
|
|||
|
|
(when lsp-ocaml--type-enclosing-saved-type
|
|||
|
|
(message "Copied `%s' to kill-ring"
|
|||
|
|
lsp-ocaml--type-enclosing-saved-type)
|
|||
|
|
(kill-new lsp-ocaml--type-enclosing-saved-type)))
|
|||
|
|
|
|||
|
|
(defun lsp-ocaml--get-and-display-type-enclosing (&optional increased-verbosity)
|
|||
|
|
"Compute the type enclosing request.
|
|||
|
|
|
|||
|
|
If INCREASED-VERBOSITY is t, if the computed type is the same as the previous
|
|||
|
|
one, decrease the verbosity.
|
|||
|
|
This allows to make sure that we don't increase infinitely the verbosity."
|
|||
|
|
(-let* ((verbosity lsp-ocaml--type-enclosing-verbosity)
|
|||
|
|
(index lsp-ocaml--type-enclosing-index)
|
|||
|
|
(type_result (lsp-ocaml--type-enclosing verbosity index))
|
|||
|
|
((&ocaml-lsp:TypeEnclosingResult :index :type :enclosings) type_result)
|
|||
|
|
;; Get documentation information
|
|||
|
|
(markupkind (symbol-name lsp-ocaml-markupkind))
|
|||
|
|
(doc_result (lsp-ocaml--get-documentation nil markupkind))
|
|||
|
|
(doc (cl-getf (cl-getf doc_result :doc) :value)))
|
|||
|
|
(when (and increased-verbosity
|
|||
|
|
(string= type lsp-ocaml--type-enclosing-saved-type))
|
|||
|
|
(setq lsp-ocaml--type-enclosing-verbosity (1- verbosity)))
|
|||
|
|
(setq lsp-ocaml--type-enclosing-saved-type type)
|
|||
|
|
(setq lsp-ocaml--type-enclosing-type-enclosings enclosings)
|
|||
|
|
(lsp-ocaml--display-type markupkind type doc)
|
|||
|
|
(lsp-ocaml--highlight-current-type (aref enclosings index))
|
|||
|
|
type))
|
|||
|
|
|
|||
|
|
;;; -------------------
|
|||
|
|
;;; OCaml-lsp extensions
|
|||
|
|
;;; -------------------
|
|||
|
|
|
|||
|
|
;;; The following functions are interactive implementations of the OCaml-lsp requests
|
|||
|
|
|
|||
|
|
(defun lsp-ocaml-infer-interface ()
|
|||
|
|
"Infer the interface for the current file."
|
|||
|
|
(interactive)
|
|||
|
|
(let* ((current-uri (lsp--buffer-uri))
|
|||
|
|
(intf-uri (if (lsp-ocaml--is-interface current-uri)
|
|||
|
|
current-uri
|
|||
|
|
(lsp-ocaml--find-alternate-uri)))
|
|||
|
|
(impl-uri (if (lsp-ocaml--is-interface current-uri)
|
|||
|
|
(lsp-ocaml--find-alternate-uri)
|
|||
|
|
current-uri))
|
|||
|
|
(intf-path (lsp--uri-to-path intf-uri))
|
|||
|
|
(impl-path (lsp--uri-to-path impl-uri)))
|
|||
|
|
(if (lsp-ocaml--load-uri impl-uri) ; the impl file needs to be loaded
|
|||
|
|
(when (y-or-n-p
|
|||
|
|
(format "Try to generate an interface for %s? " impl-path))
|
|||
|
|
(let ((result (lsp-ocaml--infer-intf)))
|
|||
|
|
(with-current-buffer (get-buffer-create intf-path)
|
|||
|
|
(when (or (= (buffer-size) 0)
|
|||
|
|
(y-or-n-p "The buffer is not empty, overwrite it? "))
|
|||
|
|
(erase-buffer)
|
|||
|
|
(insert result)
|
|||
|
|
;; Create the file if it doesn’t exist
|
|||
|
|
(unless (file-exists-p intf-path)
|
|||
|
|
(write-file intf-path)))))))))
|
|||
|
|
|
|||
|
|
(defun lsp-ocaml-find-alternate-file ()
|
|||
|
|
"Return the URI corresponding to the alternate file if there's only one or prompt for a choice."
|
|||
|
|
(interactive)
|
|||
|
|
(let ((uri (lsp-ocaml--find-alternate-uri)))
|
|||
|
|
(unless (lsp-ocaml--load-uri uri nil)
|
|||
|
|
(message "No alternate file %s could be found for %s" (f-filename uri) (buffer-name)))))
|
|||
|
|
|
|||
|
|
(defun lsp-ocaml-type-enclosing ()
|
|||
|
|
"Returns the type of the indent at point."
|
|||
|
|
(interactive)
|
|||
|
|
(lsp-ocaml--init-type-enclosing-config)
|
|||
|
|
(when-let* ((type (lsp-ocaml--get-and-display-type-enclosing)))
|
|||
|
|
(set-transient-map lsp-ocaml-type-enclosing-map t)))
|
|||
|
|
|
|||
|
|
(lsp-consistency-check lsp-ocaml)
|
|||
|
|
|
|||
|
|
(provide 'lsp-ocaml)
|
|||
|
|
;;; lsp-ocaml.el ends here
|