branch: elpa/eldoc-mouse
commit 60ded7eadb03dcfc916aa2f886d1e87c003585ba
Merge: d0193a64680 17bc2aea4cb
Author: huangfeiyu <[email protected]>
Commit: GitHub <[email protected]>
Merge pull request #16 from huangfeiyu/dev
Dev
---
README.md | 52 +++++++++++---
eldoc-mouse.el | 222 ++++++++++++++++++++++++++-------------------------------
2 files changed, 147 insertions(+), 127 deletions(-)
diff --git a/README.md b/README.md
index a7c38611137..094255734eb 100644
--- a/README.md
+++ b/README.md
@@ -2,20 +2,20 @@
# eldoc-mouse
-`eldoc-mouse` is an Emacs package that enhances the `eldoc` functionality by
displaying documentation in a popup at the mouse point using
[posframe](https://github.com/tumashu/posframe) when the mouse hovers over a
symbol in an `eglot` managed buffer. It integrates with `posframe` to provide
popping up documentation and features a debounced hover mechanism to prevent
excessive requests to the LSP server.
+`eldoc-mouse` is an Emacs package that enhances the `eldoc` functionality by
displaying documentation in a popup at the mouse point using
[posframe](https://github.com/tumashu/posframe) when the mouse hovers over a
symbol. It integrates with `posframe` to provide popping up documentation.
[Eldoc-mouse Demo at Youtube](https://youtu.be/XFAc4WyiJjI)
<video
src="https://github.com/user-attachments/assets/5622cfd2-de0c-46e8-9276-d67615671932"
controls></video>
## Features
-- Displays documentation in a popup when hovering over symbols in `eglot`
managed buffers.
+- Displays documentation in a popup when hovering over symbols.
- Integrates with `posframe` for popup documentation.
- Support moving mouse to the popup by move mouse on it and click.
- Automatically hide the popup when mouse moved away from the symbol, also
supoort pressing `C-g` to hide the popup.
-- Avoids spamming the LSP server by debouncing hover events.
-- Works in eglot managed buffers to show documentation for the symbol under
the mouse point.
-- Removed the unnecessary signatures from the document to make doucment more
clear.
-- Still keep highlighting the symbol under the cursor.
+- The following enhancements are made for eglot managed buffers.
+ - Removed the unnecessary signatures from the document to make doucment
more clear.
+ - Still keep highlighting the symbol under the cursor.
- An interactive command to pop up document at cursor
`eldoc-mouse-pop-doc-at-cursor`, this is helpful when you don't bother to move
mouse, but want to check document. I bind it to key sequence `F1 F1`, press
Ctrl-G or moving cursor away from the current symbol to close the popup.
+- More modes can be easily supported by extends `eldoc-mouse`.
- So far, `eldoc-mouse` works only for GUI Emacs.
## Installation
@@ -31,12 +31,48 @@ You can install `eldoc-mouse` with the following command.
Add the following in your Emacs configuration:
```
;; The following two lines are both optional, but you would like to add at
least one of them to your Emacs configuration.
-(use-package eldoc-mouse :hook (eglot-managed-mode)) ;; enable mouse hover for
eglot managed buffers.
+(use-package eldoc-mouse :hook (eglot-managed-mode emacs-lisp-mode)) ;; enable
mouse hover for eglot managed buffers, and emacs lisp buffers.
(global-set-key (kbd "<f1> <f1>") 'eldoc-mouse-pop-doc-at-cursor) ;; replace
<f1> <f1> to a key you like. Displaying document on a popup when you press a
key.
```
+### Supported modes
+* eglot-managed-mode
+* emacs-list-mode
## Customization
You can customize the behavior of eldoc-mouse by adjusting the variables. For
instance, you can adjust the delay time between mouse hover and displaying the
documentation by changing the eldoc-mouse-mouse-timer settings.
+
+## Extend (Add new mode)
+`eldoc-mouse` can be easily extended to support more major/minor modes, finish
the following 3 steps to extend it to support a new mode.
+
+1. write an impementation of `eldoc-documentation-functions`. see
https://www.gnu.org/software/emacs/manual/html_node/emacs/Programming-Language-Doc.html#index-eldoc_002ddocumentation_002dfunctions.
Here's an example implementation for `emacs-lisp-mode`
+ ```elisp
+ (defun eldoc-mouse--elisp-eldoc-documentation-function (_cb)
+ "The `eldoc-documentation-functions' implementation for elisp."
+ (if (eq major-mode 'emacs-lisp-mode)
+ (let ((sym (symbol-at-point)))
+ (cond
+ ;; If the symbol is a function
+ ((and sym (fboundp sym))
+ (documentation sym))
+ ;; If the symbol is a variable
+ ((and sym (boundp sym))
+ (let ((doc (documentation-property sym 'variable-documentation)))
+ (if doc
+ doc
+ nil)))
+ ;; If no symbol or not a function/variable
+ (t nil)))
+ nil)) ;; if the expected mode is not available, nil should be returned.
+ ```
+2. add the function name to the `eldoc-mouse` variable
`eldoc-mouse--eldoc-documentation-functions`. for example:
+ ```elisp
+ (defvar eldoc-mouse--eldoc-documentation-functions
+ '(eldoc-mouse--eglot-eldoc-documentation-function
+ eldoc-mouse--elisp-eldoc-documentation-function)
+ "The `eldoc-documentation-functions' for `eldoc-mouse-mode'.")
+ ```
+3. submit a pull request. I'd love to merge it.
+
## Requirements
Emacs 30.1 or higher
@@ -52,7 +88,7 @@ Contributing
Feel free to open issues and pull requests for improvements. If you encounter
any bugs or have feature requests, please create an issue on the GitHub Issues
page.
## TODO
* make moving mouse to the posframe easier, currently, it requires moving
mouse quickly and with a click.
-* make showing document for mouse hover more generic, not only for eglot
managed buffers, but also for buffers that it makes sense to show something on
a posframe for mouse hover. (truly lives up to its name)
+* *(done)* make showing document for mouse hover more generic, not only for
eglot managed buffers, but also for buffers that it makes sense to show
something on a posframe for mouse hover. (truly lives up to its name)
* *(done)* an interactive command to popup document on a posframe for the
symbol of the cursor.
## Acknowledgments
diff --git a/eldoc-mouse.el b/eldoc-mouse.el
index 44a2f11b521..970511a4777 100644
--- a/eldoc-mouse.el
+++ b/eldoc-mouse.el
@@ -28,10 +28,9 @@
;;; Commentary:
;; This package enhances eldoc' by displaying documentation in a child frame
-;; when the mouse hovers over a symbol in eglot'-managed buffers. It
integrates
-;; with posframe' for popup documentation and provides a debounced mouse hover
-;; mechanism to avoid spamming the LSP server. Enable it in prog-mode' buffers
-;; to show documentation for the symbol under the mouse cursor.
+;; when the mouse hovers over a symbol. It integrates with posframe' for
popup
+;; documentation. Enable it in buffers that you want to show documentation
using
+;; eldoc for the symbol under the mouse cursor.
;; To use, ensure posframe is installed, then add:
;; (require 'eldoc-mouse)
@@ -87,13 +86,20 @@ no limit, the popup may affect writing."
(defvar-local eldoc-mouse-last-symbol-bounds nil
"Bounds of the last symbol processed for eldoc.")
-(defvar-local eldoc-mouse-unsupress-posframe nil
- "Temporarily un-suppress the posframe.
-By default, posframe will not used by eldoc.")
-
(defvar-local eldoc-mouse--original-display-functions nil
"Store the original `eldoc-display-functions'.")
+(defvar-local eldoc-mouse--doc-identifier "*^eldoc-mouse*^"
+ "The identifier used for distinguish the doc triggered by eldoc-mouse.")
+
+(defvar eldoc-mouse--eldoc-documentation-functions
+ '(eldoc-mouse--eglot-eldoc-documentation-function
+ eldoc-mouse--elisp-eldoc-documentation-function)
+ "The `eldoc-documentation-functions' for `eldoc-mouse-mode'.")
+
+(defvar-local eldoc-mouse--original-documentation-functions nil
+ "The original eldoc-documentation-fuctions.")
+
;;;###autoload
(define-minor-mode eldoc-mouse-mode
"Toggle the `eldoc-mouse-mode'."
@@ -109,56 +115,38 @@ By default, posframe will not used by eldoc.")
(interactive)
(eldoc-mouse--hide-posframe)
(when-let* ((symbol-bounds (bounds-of-thing-at-point 'symbol)))
- (cond
- (eldoc-mouse-mode
- (add-hook
- 'eldoc-documentation-functions #'eldoc-mouse-hover-eldoc-function
- nil t)
- (setq-local eldoc-mouse-last-symbol-bounds symbol-bounds)
- (setq-local eldoc-mouse-unsupress-posframe t)
-
- ;; Make sure eldoc always send the request to get doc.
- (setq eldoc--last-request-state nil)
-
- (eldoc-print-current-symbol-info)
- (remove-hook
- 'eldoc-documentation-functions #'eldoc-mouse-hover-eldoc-function
- t))
- (t
- (when (eglot-managed-p)
- (remove-hook
- 'eldoc-documentation-functions #'eglot-signature-eldoc-function
- t))
-
- (setq-local eldoc-mouse-last-symbol-bounds symbol-bounds)
+ (setq-local eldoc-mouse--original-documentation-functions
eldoc-documentation-functions)
+ (setq-local eldoc-documentation-functions nil)
+ (dolist (fun-sym eldoc-mouse--eldoc-documentation-functions)
+ (advice-add fun-sym :around #'eldoc-mouse--hover-edloc-function-advise)
+ (add-hook 'eldoc-documentation-functions (symbol-function fun-sym) nil
t))
+ (setq-local eldoc-mouse-last-symbol-bounds symbol-bounds)
+ (when (not eldoc-mouse-mode)
(unless eldoc-mouse--original-display-functions
- (setq-local eldoc-mouse--original-display-functions
- eldoc-display-functions))
+ (setq-local eldoc-mouse--original-display-functions
eldoc-display-functions))
(setq-local eldoc-display-functions
(append
- eldoc-display-functions '(eldoc-mouse-display-in-posframe)))
- (setq-local eldoc-mouse-unsupress-posframe t)
-
- ;; Make sure eldoc always send the request to get doc.
- (setq eldoc--last-request-state nil)
+ eldoc-display-functions
'(eldoc-mouse-display-in-posframe))))
+ (setq eldoc--last-request-state nil)
+ (eldoc-print-current-symbol-info)
+ (dolist (fun-sym eldoc-mouse--eldoc-documentation-functions)
+ (advice-remove fun-sym #'eldoc-mouse--hover-edloc-function-advise))
+ (setq-local eldoc-documentation-functions
eldoc-mouse--original-documentation-functions)))
- (eldoc-print-current-symbol-info)
- (when (eglot-managed-p)
- (add-hook 'eldoc-documentation-functions
#'eglot-signature-eldoc-function
- nil
- t))))))
(defun eldoc-mouse-enable ()
- "Enable eldoc-mouse in all `eglot-managed-p' buffers."
+ "Enable eldoc-mouse in buffers."
+ ;; Enable mouse tracking.
+ (setq track-mouse t)
+ (setq-local eldoc-mouse--original-display-functions eldoc-display-functions)
+ (setq-local eldoc-display-functions
+ (append
+ eldoc-display-functions '(eldoc-mouse-display-in-posframe)))
+ (local-set-key [mouse-movement] #'eldoc-mouse-doc-on-mouse)
+
+ ;; Optimization for eglot managed buffers.
(when (eglot-managed-p)
- ;; Enable mouse tracking.
- (setq track-mouse t)
- (setq-local eldoc-mouse--original-display-functions
eldoc-display-functions)
- (setq-local eldoc-display-functions
- (append
- eldoc-display-functions '(eldoc-mouse-display-in-posframe)))
;; Avoid unnecessary document of signatures that clutters the document.
- (remove-hook 'eldoc-documentation-functions
#'eglot-signature-eldoc-function
- t)
+ (remove-hook 'eldoc-documentation-functions
#'eglot-signature-eldoc-function t)
;; Avoid show document for the cursor.
(remove-hook 'eldoc-documentation-functions #'eglot-hover-eldoc-function t)
;; Enable highlight symbol under the cursor.
@@ -169,34 +157,30 @@ By default, posframe will not used by eldoc.")
;; See details:
;;
https://cgit.git.savannah.gnu.org/cgit/emacs.git/commit/?id=60166a419f601b413db86ddce186cc387e8ec269
(when (fboundp 'eglot--highlight-piggyback)
- (add-hook 'eldoc-documentation-functions #'eglot--highlight-piggyback
- nil
- t))
- (local-set-key [mouse-movement] #'eldoc-mouse-doc-on-mouse)))
+ (add-hook 'eldoc-documentation-functions #'eglot--highlight-piggyback
nil t))))
(defun eldoc-mouse-disable ()
- "Disable eldoc-mouse in all `eglot-managed-p' buffers."
+ "Disable eldoc-mouse in buffers."
(when eldoc-mouse--original-display-functions
(setq-local eldoc-display-functions
eldoc-mouse--original-display-functions))
- (when (fboundp 'eglot--highlight-piggyback)
- (remove-hook 'eldoc-documentation-functions #'eglot--highlight-piggyback
t))
-
- (unless (memq #'eglot-signature-eldoc-function eldoc-documentation-functions)
- (add-hook 'eldoc-documentation-functions #'eglot-signature-eldoc-function
- nil
- t))
- (unless (memq #'eglot-hover-eldoc-function eldoc-documentation-functions)
- (add-hook 'eldoc-documentation-functions #'eglot-hover-eldoc-function
- nil
- t))
+
+ ;; Optimization for eglot managed buffers.
+ (when (eglot-managed-p)
+ (when (fboundp 'eglot--highlight-piggyback)
+ (remove-hook 'eldoc-documentation-functions #'eglot--highlight-piggyback
t))
+
+ (unless (memq #'eglot-signature-eldoc-function
eldoc-documentation-functions)
+ (add-hook 'eldoc-documentation-functions
#'eglot-signature-eldoc-function nil t))
+ (unless (memq #'eglot-hover-eldoc-function eldoc-documentation-functions)
+ (add-hook 'eldoc-documentation-functions #'eglot-hover-eldoc-function
nil t)))
(when eldoc-mouse-mouse-timer
(cancel-timer eldoc-mouse-mouse-timer)
(setq eldoc-mouse-mouse-timer nil))
(eldoc-mouse--hide-posframe)
(local-unset-key [mouse-movement])
- (when (y-or-n-p "Disable mouse-tracking (may impact other modes)?")
+ (when (y-or-n-p "eldoc-mouse-mode has been turned off. Also disable
mouse-tracking (may impact other modes)?")
(setq track-mouse nil)))
(defun eldoc-mouse--post-command-hook ()
@@ -225,15 +209,14 @@ POS is the buffer position under the mouse cursor."
(< pos (car eldoc-mouse-last-symbol-bounds))
(> pos (cdr eldoc-mouse-last-symbol-bounds))))
(eldoc-mouse--hide-posframe)
- (when (fboundp 'eglot--highlight-piggyback)
- (remove-hook 'eldoc-documentation-functions #'eglot--highlight-piggyback
- t))
(when eldoc-mouse-mouse-overlay
(delete-overlay eldoc-mouse-mouse-overlay))
(save-excursion
- (add-hook
- 'eldoc-documentation-functions #'eldoc-mouse-hover-eldoc-function
- nil t)
+ (setq-local eldoc-mouse--original-documentation-functions
eldoc-documentation-functions)
+ (setq-local eldoc-documentation-functions nil)
+ (dolist (fun-sym eldoc-mouse--eldoc-documentation-functions)
+ (advice-add fun-sym :around #'eldoc-mouse--hover-edloc-function-advise)
+ (add-hook 'eldoc-documentation-functions (symbol-function fun-sym) nil
t))
(goto-char pos)
(setq-local eldoc-mouse-last-symbol-bounds
(bounds-of-thing-at-point 'symbol))
@@ -243,20 +226,15 @@ POS is the buffer position under the mouse cursor."
(when (and eldoc-mouse-last-symbol-bounds
(not (eolp))
(not (nth 4 (syntax-ppss))))
- (setq-local eldoc-mouse-unsupress-posframe t)
(eldoc-print-current-symbol-info)
(setq-local eldoc-mouse-mouse-overlay
(make-overlay
(car eldoc-mouse-last-symbol-bounds)
(cdr eldoc-mouse-last-symbol-bounds)))
- (overlay-put eldoc-mouse-mouse-overlay 'face 'highlight))
- (remove-hook
- 'eldoc-documentation-functions #'eldoc-mouse-hover-eldoc-function
- t)
- (when (fboundp 'eglot--highlight-piggyback)
- (add-hook 'eldoc-documentation-functions #'eglot--highlight-piggyback
- nil
- t)))))
+ (overlay-put eldoc-mouse-mouse-overlay 'face 'secondary-selection))
+ (dolist (fun-sym eldoc-mouse--eldoc-documentation-functions)
+ (advice-remove fun-sym #'eldoc-mouse--hover-edloc-function-advise))
+ (setq-local eldoc-documentation-functions
eldoc-mouse--original-documentation-functions))))
(defun eldoc-mouse--hide-posframe ()
"Hide the posframe."
@@ -268,48 +246,56 @@ POS is the buffer position under the mouse cursor."
(defun eldoc-mouse-doc-on-mouse (event)
"Show eldoc documentation when mouse hovers over EVENT."
(interactive "e")
- (let ((pos (posn-point (event-start event))))
- (when (and pos (number-or-marker-p pos) (eglot-managed-p))
+ (when eldoc-mouse-mode
+ (let ((pos (posn-point (event-start event))))
+ (when (and pos (number-or-marker-p pos))
;; Debounce to avoid spamming eglot.
(when eldoc-mouse-mouse-timer
(cancel-timer eldoc-mouse-mouse-timer))
(setq eldoc-mouse-mouse-timer
(run-with-idle-timer
eldoc-mouse-idle-time nil #'eldoc-mouse-show-doc-at
- pos)))))
+ pos))))))
-(defun eldoc-mouse-hover-eldoc-function (cb)
+(defun eldoc-mouse--eglot-eldoc-documentation-function (cb)
"Modify the `eglot-hover-eldoc-function'.
So it won't call `eglot--highlight-piggyback` with `CB`."
- (if (fboundp 'eglot--highlight-piggyback)
- (cl-letf (((symbol-function 'eglot--highlight-piggyback)
- (lambda (&rest _args) (message ""))))
+ (if (eglot-managed-p)
+ (if (fboundp 'eglot--highlight-piggyback)
+ (cl-letf (((symbol-function 'eglot--highlight-piggyback)
+ (lambda (&rest _args) (message ""))))
+ (eglot-hover-eldoc-function cb))
(eglot-hover-eldoc-function cb))
- (eglot-hover-eldoc-function cb)))
-
-(defun eldoc-mouse-handle-eglot-hooks ()
- "Handle the eldoc eglot hooks.
-Remove all eglot hooks and keep highlighting on cursor,
-add eldoc-mouse's `eldoc-display-functions'."
- (setq-local eldoc-display-functions
- (append
- eldoc-display-functions '(eldoc-mouse-display-in-posframe)))
- ;; Avoid unnecessary document of signatures that clutters the document.
- (remove-hook 'eldoc-documentation-functions #'eglot-signature-eldoc-function
- t)
- ;; Avoid show document for the cursor.
- (remove-hook 'eldoc-documentation-functions #'eglot-hover-eldoc-function t)
- ;; Enable highlight symbol under the cursor.
- ;; In the future the following line is no longer necessary,
- ;; as emacs use a specific function eglot-highlight-eldoc-function
- ;; for highlighting.
- ;; And here, we want to keep the highlight at cursor.
- ;; See details:
- ;;
https://cgit.git.savannah.gnu.org/cgit/emacs.git/commit/?id=60166a419f601b413db86ddce186cc387e8ec269
- (when (fboundp 'eglot--highlight-piggyback)
- (add-hook 'eldoc-documentation-functions #'eglot--highlight-piggyback
- nil
- t)))
+ nil))
+
+(defun eldoc-mouse--elisp-eldoc-documentation-function (_cb)
+ "The `eldoc-documentation-functions' implementation for elisp."
+ (if (eq major-mode 'emacs-lisp-mode)
+ (let ((sym (symbol-at-point)))
+ (cond
+ ;; If the symbol is a function
+ ((and sym (fboundp sym))
+ (documentation sym))
+ ;; If the symbol is a variable
+ ((and sym (boundp sym))
+ (let ((doc (documentation-property sym 'variable-documentation)))
+ (if doc
+ doc
+ nil)))
+ ;; If no symbol or not a function/variable
+ (t nil)))
+ nil))
+
+(defun eldoc-mouse--hover-edloc-function-advise (orig-fn fn)
+ "Wrap FN argument of ORIG-FN so that it append indentifier'."
+ (let ((result (funcall orig-fn
+ (lambda (s &rest r)
+ (funcall fn (if (and s (not (string-empty-p (string-trim s))))
+ (concat s eldoc-mouse--doc-identifier)
+ s) r)))))
+ (if (stringp result)
+ (concat result eldoc-mouse--doc-identifier)
+ result)))
(defun eldoc-mouse-is-mouse-hovering-posframe? (posframe-name)
"Check if the mouse is hovering over the given posframe `POSFRAME-NAME'."
@@ -323,10 +309,8 @@ add eldoc-mouse's `eldoc-display-functions'."
(defun eldoc-mouse-display-in-posframe (docs _interactive)
"Display `DOCS` STRING in a posframe at the current mouse position."
- (when (and docs eldoc-mouse-unsupress-posframe)
- (setq-local eldoc-mouse-unsupress-posframe nil)
+ (when (and docs (string-match-p (regexp-quote eldoc-mouse--doc-identifier)
(car (car docs))))
;; Output the document for *eldoc* buffer.
- ;; (eldoc--format-doc-buffer docs)
(let* ((eldoc-buffer
(get-buffer
(car
@@ -340,7 +324,7 @@ add eldoc-mouse's `eldoc-display-functions'."
(buffer-string)))
(border-color (face-foreground 'default)))
(when text
- (eldoc-mouse--pop-doc text border-color))))
+ (eldoc-mouse--pop-doc (replace-regexp-in-string (regexp-quote
eldoc-mouse--doc-identifier) "" text) border-color))))
;; non-nil => suppress other display functions.
t)))