2018-12-11 14:15:00 +00:00
|
|
|
;;; eldoc-box.el --- Display documentation in childframe -*- lexical-binding: t; -*-
|
2018-12-11 01:26:08 +00:00
|
|
|
|
2018-12-12 14:24:09 +00:00
|
|
|
;; Copyright (C) 2017-2018 Sebastien Chapuis, 2018 Yuan Fu
|
2018-12-11 01:26:08 +00:00
|
|
|
|
2019-02-20 19:36:09 +00:00
|
|
|
;; Version: 1.8
|
2018-12-11 21:40:48 +00:00
|
|
|
|
|
|
|
;; Author: Sebastien Chapuis <sebastien@chapu.is>
|
2018-12-12 14:24:09 +00:00
|
|
|
;; Maintainer: Yuan Fu <casouri@gmail.com>
|
2018-12-20 18:53:24 +00:00
|
|
|
;; Contributors:
|
|
|
|
;; João Távora <joaotavora@gmail.com>
|
2018-12-11 17:24:52 +00:00
|
|
|
;; URL: https://github.com/casouri/eldoc-box
|
|
|
|
;; Package-Requires: ((emacs "26.1"))
|
2018-12-11 01:26:08 +00:00
|
|
|
|
2018-12-11 21:40:48 +00:00
|
|
|
;;; License
|
|
|
|
;;
|
|
|
|
;; 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, 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; see the file COPYING. If not, write to
|
|
|
|
;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth
|
|
|
|
;; Floor, Boston, MA 02110-1301, USA.
|
2018-12-11 21:42:08 +00:00
|
|
|
|
2018-12-11 01:26:08 +00:00
|
|
|
;;; This file is NOT part of GNU Emacs
|
|
|
|
|
|
|
|
;;; Commentary:
|
|
|
|
;;
|
2019-07-11 12:18:04 +00:00
|
|
|
;; See documentation in README.org or visit homepage
|
2018-12-11 01:26:08 +00:00
|
|
|
|
|
|
|
;;; Code:
|
|
|
|
;;
|
|
|
|
|
2018-12-11 16:27:54 +00:00
|
|
|
(require 'cl-lib)
|
2021-03-25 15:24:50 +00:00
|
|
|
(require 'seq)
|
2018-12-11 16:27:54 +00:00
|
|
|
|
2018-12-11 01:26:08 +00:00
|
|
|
;;;; Userland
|
|
|
|
;;;;; Variable
|
2018-12-12 14:21:43 +00:00
|
|
|
(defgroup eldoc-box nil
|
|
|
|
"Display Eldoc docs in a pretty child frame."
|
|
|
|
:prefix "eldoc-box-"
|
|
|
|
:group 'eldoc)
|
2018-12-11 01:26:08 +00:00
|
|
|
|
2018-12-11 14:15:00 +00:00
|
|
|
(defface eldoc-box-border '((((background dark)) . (:background "white"))
|
2018-12-11 21:34:37 +00:00
|
|
|
(((background light)) . (:background "black")))
|
2018-12-11 02:02:24 +00:00
|
|
|
"The border color used in childframe.")
|
|
|
|
|
2018-12-11 14:15:00 +00:00
|
|
|
(defface eldoc-box-body '((t . (:background nil)))
|
2019-08-13 05:42:44 +00:00
|
|
|
"Body face used in eglot doc childframe.")
|
2018-12-11 05:20:21 +00:00
|
|
|
|
2018-12-11 16:16:02 +00:00
|
|
|
(defvar eldoc-box-only-multi-line nil
|
|
|
|
"If non-nil, only use childframe when there are more than one line.")
|
|
|
|
|
2018-12-18 17:34:48 +00:00
|
|
|
(defvar eldoc-box-cleanup-interval 1
|
|
|
|
"After this amount of seconds will eldoc-box attempt to cleanup the childframe.
|
|
|
|
E.g. if it is set to 1, the childframe is cleared 1 second after
|
2018-12-21 03:19:58 +00:00
|
|
|
you moved the point to somewhere else (that doesn't have a doc to show).
|
2018-12-29 05:49:43 +00:00
|
|
|
This doesn't apply to `eldoc-box-hover-at-point-mode',
|
2018-12-21 03:19:58 +00:00
|
|
|
in that mode the childframe is cleared as soon as point moves.")
|
2018-12-18 17:34:48 +00:00
|
|
|
|
2018-12-20 15:26:03 +00:00
|
|
|
(defvar eldoc-box-clear-with-C-g nil
|
2018-12-29 05:49:43 +00:00
|
|
|
"If set to non-nil, eldoc-box clears childframe when you hit \C-g.")
|
2018-12-20 15:26:03 +00:00
|
|
|
|
2018-12-11 14:15:00 +00:00
|
|
|
(defvar eldoc-box-frame-parameters
|
2019-02-09 20:52:50 +00:00
|
|
|
'(;; make the childframe unseen when first created
|
|
|
|
(left . -1)
|
|
|
|
(top . -1)
|
|
|
|
(width . 0)
|
|
|
|
(height . 0)
|
|
|
|
|
2018-12-11 01:26:08 +00:00
|
|
|
(no-accept-focus . t)
|
|
|
|
(no-focus-on-map . t)
|
|
|
|
(min-width . 0)
|
|
|
|
(min-height . 0)
|
|
|
|
(internal-border-width . 1)
|
|
|
|
(vertical-scroll-bars . nil)
|
|
|
|
(horizontal-scroll-bars . nil)
|
2019-10-03 05:06:55 +00:00
|
|
|
(right-fringe . 3)
|
|
|
|
(left-fringe . 3)
|
2018-12-11 01:26:08 +00:00
|
|
|
(menu-bar-lines . 0)
|
|
|
|
(tool-bar-lines . 0)
|
|
|
|
(line-spacing . 0)
|
|
|
|
(unsplittable . t)
|
|
|
|
(undecorated . t)
|
2018-12-11 20:20:11 +00:00
|
|
|
(visibility . nil)
|
2018-12-11 01:26:08 +00:00
|
|
|
(mouse-wheel-frame . nil)
|
|
|
|
(no-other-frame . t)
|
|
|
|
(cursor-type . nil)
|
|
|
|
(inhibit-double-buffering . t)
|
|
|
|
(drag-internal-border . t)
|
|
|
|
(no-special-glyphs . t)
|
|
|
|
(desktop-dont-save . t))
|
|
|
|
"Frame parameters used to create the frame.")
|
|
|
|
|
2018-12-11 14:15:00 +00:00
|
|
|
(defvar eldoc-box-max-pixel-width 800
|
2018-12-29 05:49:43 +00:00
|
|
|
"Maximum width of doc childframe in pixel.
|
2018-12-20 14:27:01 +00:00
|
|
|
Consider your machine's screen's resolution when setting this variable.
|
|
|
|
Set it to a function with no argument
|
|
|
|
if you want to dynamically change the maximum width.")
|
2018-12-13 02:58:54 +00:00
|
|
|
|
|
|
|
(defvar eldoc-box-max-pixel-height 700
|
2018-12-29 05:49:43 +00:00
|
|
|
"Maximum height of doc childframe in pixel.
|
2018-12-20 14:27:01 +00:00
|
|
|
Consider your machine's screen's resolution when setting this variable.
|
|
|
|
Set it to a function with no argument
|
|
|
|
if you want to dynamically change the maximum height.")
|
2018-12-11 01:26:08 +00:00
|
|
|
|
2018-12-20 14:57:30 +00:00
|
|
|
(defvar eldoc-box-position-function #'eldoc-box--default-upper-corner-position-function
|
|
|
|
"Eldoc-box uses this function to set childframe's position.
|
|
|
|
This should be a function that returns a (X . Y) cons cell.
|
|
|
|
It will be passes with two arguments: WIDTH and HEIGHT of the childframe.")
|
|
|
|
|
2019-03-05 16:47:15 +00:00
|
|
|
(defvar eldoc-box-fringe-use-same-bg t
|
|
|
|
"T means fringe's background color is set to as same as that of default.")
|
|
|
|
|
2019-08-13 05:44:28 +00:00
|
|
|
(defvar eldoc-box-buffer-hook nil
|
|
|
|
"Hook run after buffer for doc is setup.
|
|
|
|
Run inside the new buffer.")
|
|
|
|
|
|
|
|
(defvar eldoc-box-frame-hook nil
|
|
|
|
"Hook run after doc frame is setup but just before it is made visible.
|
|
|
|
Each function runs inside the new frame and receives the main frame as argument.")
|
|
|
|
|
2019-10-03 04:38:32 +00:00
|
|
|
(defvar eldoc-box-self-insert-command-list '(self-insert-command outshine-self-insert-command)
|
|
|
|
"Commands in this list are considered self-insert-command by eldoc-box.
|
|
|
|
See `eldoc-box-inhibit-display-when-moving'.")
|
|
|
|
|
2018-12-11 01:26:08 +00:00
|
|
|
;;;;; Function
|
2019-10-03 04:38:32 +00:00
|
|
|
(defvar eldoc-box--inhibit-childframe nil
|
|
|
|
"If non-nil, inhibit display of childframe.")
|
|
|
|
|
2018-12-12 14:21:43 +00:00
|
|
|
(defvar eldoc-box--frame nil ;; A backstage variable
|
|
|
|
"The frame to display doc.")
|
|
|
|
|
2018-12-11 14:15:00 +00:00
|
|
|
(defun eldoc-box-quit-frame ()
|
2018-12-11 01:26:08 +00:00
|
|
|
"Hide childframe used by eglot doc."
|
|
|
|
(interactive)
|
2018-12-11 14:15:00 +00:00
|
|
|
(when eldoc-box--frame
|
2018-12-11 20:45:13 +00:00
|
|
|
(make-frame-invisible eldoc-box--frame t)))
|
2018-12-11 01:26:08 +00:00
|
|
|
|
2019-10-03 03:28:35 +00:00
|
|
|
(defun eldoc-box--enable ()
|
|
|
|
"Enable eldoc-box hover.
|
|
|
|
Intended for internal use."
|
2021-03-12 05:48:14 +00:00
|
|
|
(add-function :before-while (local 'eldoc-message-function)
|
2019-10-03 03:28:35 +00:00
|
|
|
#'eldoc-box--eldoc-message-function)
|
|
|
|
(when eldoc-box-clear-with-C-g
|
|
|
|
(advice-add #'keyboard-quit :before #'eldoc-box-quit-frame)))
|
|
|
|
|
|
|
|
(defun eldoc-box--disable ()
|
|
|
|
"Disable eldoc-box hover.
|
|
|
|
Intended for internal use."
|
|
|
|
(remove-function (local 'eldoc-message-function) #'eldoc-box--eldoc-message-function)
|
|
|
|
(advice-remove #'keyboard-quit #'eldoc-box-quit-frame)
|
|
|
|
;; if minor mode is turned off when childframe is visible
|
|
|
|
;; hide it
|
|
|
|
(when eldoc-box--frame
|
|
|
|
(delete-frame eldoc-box--frame)
|
|
|
|
(setq eldoc-box--frame nil)))
|
|
|
|
|
2018-12-11 01:26:08 +00:00
|
|
|
;;;; Backstage
|
|
|
|
;;;;; Variable
|
2018-12-13 02:54:18 +00:00
|
|
|
(defvar eldoc-box--buffer " *eldoc-box*"
|
2018-12-11 01:26:08 +00:00
|
|
|
"The buffer used to display eglot doc.")
|
|
|
|
|
|
|
|
;;;;; Function
|
|
|
|
|
2018-12-11 14:15:00 +00:00
|
|
|
(defun eldoc-box--display (str)
|
2018-12-11 01:26:08 +00:00
|
|
|
"Display STR in childframe."
|
2018-12-29 02:45:38 +00:00
|
|
|
(unless (equal str "") ; WORKAROUND lsp returns empty string from time to time
|
|
|
|
(let ((doc-buffer (get-buffer-create eldoc-box--buffer)))
|
|
|
|
(with-current-buffer doc-buffer
|
|
|
|
(setq mode-line-format nil)
|
2019-10-08 14:26:46 +00:00
|
|
|
(when (bound-and-true-p global-tab-line-mode)
|
|
|
|
(setq tab-line-format nil))
|
2018-12-29 02:45:38 +00:00
|
|
|
;; without this, clicking childframe will make doc buffer the current buffer
|
|
|
|
;; and `eldoc-box--maybe-cleanup' in `eldoc-box--cleanup-timer' will clear the childframe
|
2019-08-13 05:42:44 +00:00
|
|
|
(buffer-face-set 'eldoc-box-body)
|
2018-12-29 02:45:38 +00:00
|
|
|
(setq eldoc-box-hover-mode t)
|
|
|
|
(erase-buffer)
|
2019-01-05 00:06:43 +00:00
|
|
|
(insert str)
|
2019-08-13 05:44:28 +00:00
|
|
|
(goto-char (point-min))
|
|
|
|
(run-hook-with-args 'eldoc-box-buffer-hook))
|
2018-12-29 02:45:38 +00:00
|
|
|
(eldoc-box--get-frame doc-buffer))))
|
2018-12-11 01:26:08 +00:00
|
|
|
|
|
|
|
|
2018-12-11 14:15:00 +00:00
|
|
|
(defun eldoc-box--window-side ()
|
2018-12-11 01:26:08 +00:00
|
|
|
"Return 'left if the selected window is on the left,
|
|
|
|
'right if on the right. Return 'left if there is only one window."
|
|
|
|
(let ((left-window(window-at 0 0)))
|
|
|
|
(if (eq left-window (selected-window))
|
|
|
|
'left
|
|
|
|
'right)))
|
|
|
|
|
2018-12-20 14:57:30 +00:00
|
|
|
(defun eldoc-box--default-upper-corner-position-function (width _)
|
|
|
|
"The default function to set childframe position.
|
|
|
|
Used by `eldoc-box-position-function'.
|
2018-12-29 05:49:43 +00:00
|
|
|
Position is calculated base on WIDTH and HEIGHT of childframe text window"
|
2018-12-20 14:57:30 +00:00
|
|
|
(cons (pcase (eldoc-box--window-side) ; x position + a little padding (16)
|
|
|
|
;; display doc on right
|
|
|
|
('left (- (frame-outer-width (selected-frame)) width 16))
|
|
|
|
;; display doc on left
|
|
|
|
('right 16))
|
|
|
|
;; y position + a little padding (16)
|
|
|
|
16))
|
|
|
|
|
2019-11-02 04:10:11 +00:00
|
|
|
(defun eldoc-box--point-position-relative-to-native-frame (&optional point window)
|
|
|
|
"Return (X . Y) as the coordinate of POINT in WINDOW.
|
2019-02-10 17:53:55 +00:00
|
|
|
The coordinate is relative to the native frame.
|
|
|
|
|
|
|
|
WINDOW nil means use selected window."
|
2019-11-02 14:33:49 +00:00
|
|
|
(let* ((pos (pos-visible-in-window-p point window t))
|
|
|
|
(x (car pos))
|
2020-03-16 19:56:50 +00:00
|
|
|
(en (frame-char-width))
|
2019-11-02 14:33:49 +00:00
|
|
|
(y (cadr pos))
|
2020-03-16 16:19:29 +00:00
|
|
|
(edges (window-edges window nil nil t)))
|
2020-03-16 19:56:50 +00:00
|
|
|
;; HACK: for unknown reasons we need to add en to x position
|
|
|
|
(cons (+ x (car edges) en)
|
2019-11-02 14:33:49 +00:00
|
|
|
(+ y (cadr edges)))))
|
2019-02-10 17:53:55 +00:00
|
|
|
|
2019-06-07 12:31:35 +00:00
|
|
|
(defun eldoc-box--default-at-point-position-function-1 (width height)
|
|
|
|
"See `eldoc-box--default-at-point-position-function'."
|
2019-02-10 17:53:55 +00:00
|
|
|
(let* ((point-pos (eldoc-box--point-position-relative-to-native-frame))
|
|
|
|
;; calculate point coordinate relative to native frame
|
|
|
|
;; because childframe coordinate is relative to native frame
|
|
|
|
(x (car point-pos))
|
|
|
|
(y (cdr point-pos))
|
2019-09-29 01:41:12 +00:00
|
|
|
(em (frame-char-height)))
|
2018-12-21 02:56:36 +00:00
|
|
|
(cons (if (< (- (frame-inner-width) width) x)
|
|
|
|
;; space on the right of the pos is not enough
|
|
|
|
;; put to left
|
2018-12-21 07:16:02 +00:00
|
|
|
(max 0 (- x width))
|
2019-02-10 17:53:55 +00:00
|
|
|
;; normal, just return x
|
2018-12-29 02:41:16 +00:00
|
|
|
x)
|
2018-12-21 02:56:36 +00:00
|
|
|
(if (< (- (frame-inner-height) height) y)
|
|
|
|
;; space under the pos is not enough
|
|
|
|
;; put above
|
2018-12-21 07:16:02 +00:00
|
|
|
(max 0 (- y height))
|
2019-02-10 17:53:55 +00:00
|
|
|
;; normal, just return y + em
|
|
|
|
(+ y em)))))
|
2018-12-20 14:57:30 +00:00
|
|
|
|
2019-06-08 14:53:54 +00:00
|
|
|
(defun eldoc-box--default-at-point-position-function (width height)
|
|
|
|
"Set `eldoc-box-position-function' to this function to have childframe appear under point.
|
|
|
|
Position is calculated base on WIDTH and HEIGHT of childframe text window."
|
|
|
|
(let* ((pos (eldoc-box--default-at-point-position-function-1 width height))
|
|
|
|
(x (car pos))
|
|
|
|
(y (cdr pos)))
|
|
|
|
(cons (or (eldoc-box--at-point-x-by-company) x)
|
|
|
|
y)))
|
2019-06-08 04:07:18 +00:00
|
|
|
(defun eldoc-box--update-childframe-geometry (frame window)
|
|
|
|
"Update the size and the position of childframe.
|
|
|
|
FRAME is the childframe, WINDOW is the primary window."
|
|
|
|
(let* ((size
|
|
|
|
(window-text-pixel-size
|
|
|
|
window nil nil
|
|
|
|
(if (functionp eldoc-box-max-pixel-width) (funcall eldoc-box-max-pixel-width) eldoc-box-max-pixel-width)
|
|
|
|
(if (functionp eldoc-box-max-pixel-height) (funcall eldoc-box-max-pixel-height) eldoc-box-max-pixel-height)
|
|
|
|
t))
|
|
|
|
(width (car size))
|
|
|
|
(height (cdr size))
|
|
|
|
(width (+ width (frame-char-width frame))) ; add margin
|
|
|
|
(frame-resize-pixelwise t)
|
|
|
|
(pos (funcall eldoc-box-position-function width height)))
|
|
|
|
(set-frame-size frame width height t)
|
|
|
|
;; move position
|
|
|
|
(set-frame-position frame (car pos) (cdr pos))))
|
|
|
|
|
2019-10-03 04:38:32 +00:00
|
|
|
(defvar eldoc-box--inhibit-childframe-timer nil
|
|
|
|
"When this timer is on, inhibit childframe display.
|
|
|
|
Intended for follow-cursor to disable display when moving cursor.")
|
|
|
|
|
|
|
|
(defun eldoc-box--inhibit-childframe-for (sec)
|
|
|
|
"Inhibit display of childframe for SEC seconds."
|
|
|
|
(when eldoc-box--inhibit-childframe-timer
|
|
|
|
(cancel-timer eldoc-box--inhibit-childframe-timer))
|
|
|
|
(eldoc-box-quit-frame)
|
|
|
|
(setq eldoc-box--inhibit-childframe t
|
|
|
|
eldoc-box--inhibit-childframe-timer
|
|
|
|
(run-with-timer sec nil
|
|
|
|
(lambda ()
|
|
|
|
(setq eldoc-box--inhibit-childframe nil)))))
|
|
|
|
|
2019-06-08 04:07:18 +00:00
|
|
|
(defun eldoc-box--follow-cursor ()
|
|
|
|
"Make childframe follow cursor in at-point mode."
|
2019-10-03 04:38:32 +00:00
|
|
|
(if (member this-command eldoc-box-self-insert-command-list)
|
|
|
|
(progn (when (frame-live-p eldoc-box--frame)
|
|
|
|
(eldoc-box--update-childframe-geometry
|
|
|
|
eldoc-box--frame (frame-selected-window eldoc-box--frame))))
|
|
|
|
;; if not typing, inhibit display
|
|
|
|
(eldoc-box--inhibit-childframe-for 0.2)))
|
2019-06-07 12:31:35 +00:00
|
|
|
|
2018-12-11 14:15:00 +00:00
|
|
|
(defun eldoc-box--get-frame (buffer)
|
2018-12-11 01:26:08 +00:00
|
|
|
"Return a childframe displaying BUFFER.
|
|
|
|
Checkout `lsp-ui-doc--make-frame', `lsp-ui-doc--move-frame'."
|
2019-10-03 04:38:32 +00:00
|
|
|
(if eldoc-box--inhibit-childframe
|
|
|
|
;; if inhibit display, do nothing
|
|
|
|
eldoc-box--frame
|
|
|
|
(let* ((after-make-frame-functions nil)
|
|
|
|
(before-make-frame-hook nil)
|
|
|
|
(parameter (append eldoc-box-frame-parameters
|
|
|
|
`((default-minibuffer-frame . ,(selected-frame))
|
|
|
|
(minibuffer . ,(minibuffer-window))
|
|
|
|
(left-fringe . ,(frame-char-width)))))
|
|
|
|
window frame
|
|
|
|
(main-frame (selected-frame)))
|
|
|
|
(if (and eldoc-box--frame (frame-live-p eldoc-box--frame))
|
|
|
|
(progn (setq frame eldoc-box--frame)
|
|
|
|
(setq window (frame-selected-window frame))
|
|
|
|
;; in case the main frame changed
|
|
|
|
(set-frame-parameter frame 'parent-frame main-frame))
|
|
|
|
(setq window (display-buffer-in-child-frame
|
|
|
|
buffer
|
|
|
|
`((child-frame-parameters . ,parameter))))
|
|
|
|
(setq frame (window-frame window)))
|
2019-10-03 05:06:55 +00:00
|
|
|
;; workaround
|
|
|
|
;; (set-frame-parameter frame 'left-fringe (alist-get 'left-fringe eldoc-box-frame-parameters))
|
|
|
|
;; (set-frame-parameter frame 'right-fringe (alist-get 'right-fringe eldoc-box-frame-parameters))
|
|
|
|
|
2019-10-03 04:38:32 +00:00
|
|
|
(set-face-attribute 'fringe frame :background nil :inherit 'eldoc-box-body)
|
|
|
|
(set-window-dedicated-p window t)
|
|
|
|
(redirect-frame-focus frame (frame-parent frame))
|
|
|
|
(set-face-attribute 'internal-border frame :inherit 'eldoc-box-border)
|
|
|
|
;; set size
|
|
|
|
(eldoc-box--update-childframe-geometry frame window)
|
|
|
|
(setq eldoc-box--frame frame)
|
|
|
|
(run-hook-with-args 'eldoc-box-frame-hook main-frame)
|
|
|
|
(make-frame-visible frame))))
|
2018-12-11 01:26:08 +00:00
|
|
|
|
2018-12-20 14:57:30 +00:00
|
|
|
|
2018-12-11 01:26:08 +00:00
|
|
|
;;;;; ElDoc
|
2018-12-12 14:21:43 +00:00
|
|
|
|
2018-12-12 05:03:38 +00:00
|
|
|
(defvar eldoc-box--cleanup-timer nil
|
|
|
|
"The timer used to cleanup childframe after ElDoc.")
|
|
|
|
|
2018-12-13 02:49:20 +00:00
|
|
|
(defvar eldoc-box--last-point 0
|
|
|
|
;; used in `eldoc-box--maybe-cleanup'
|
|
|
|
"Last point when eldoc-box showed childframe.")
|
|
|
|
|
2018-12-12 05:03:38 +00:00
|
|
|
(defun eldoc-box--maybe-cleanup ()
|
2018-12-12 18:28:40 +00:00
|
|
|
"Clean up after ElDoc."
|
2018-12-12 05:03:38 +00:00
|
|
|
;; timer is global, so this function will be called outside
|
|
|
|
;; the buffer with `eldoc-box-hover-mode' enabled
|
2018-12-13 02:49:20 +00:00
|
|
|
(if (and (frame-parameter eldoc-box--frame 'visibility)
|
|
|
|
(or (and (not eldoc-last-message) ; 1
|
|
|
|
(not (eq (point) eldoc-box--last-point)) ; 2
|
|
|
|
(not (eq (current-buffer) (get-buffer eldoc-box--buffer)))) ; 3
|
2019-10-03 03:28:35 +00:00
|
|
|
(not (or eldoc-box-hover-mode eldoc-box-hover-at-point-mode)))) ; 4
|
2018-12-13 02:49:20 +00:00
|
|
|
;; 1. Obviously, last-message nil means we are not on a valid symbol anymore.
|
|
|
|
;; 2. Or are we? If you scroll the childframe with mouse wheel
|
|
|
|
;; `eldoc-pre-command-refresh-echo-area' will set `eldoc-last-message' to nil.
|
|
|
|
;; Without the point test, this function, called by `eldoc-box--cleanup-timer'
|
|
|
|
;; will clear the doc frame, not good
|
|
|
|
;; 3. If scrolling can't satisfy you and you clicked the childframe
|
|
|
|
;; both 1. and 2. are satisfied. 3. is the last hope to prevent this function
|
|
|
|
;; from clearing your precious childframe. There is another safety pin in
|
|
|
|
;; `eldoc-box--display' that works with 3.
|
|
|
|
;; 4. Sometimes you switched buffer when childframe is on.
|
|
|
|
;; it wouldn't go away unless you goes back and let eldoc shut it off.
|
|
|
|
;; So if we are not in `eldoc-box-hover-mode', clear childframe
|
|
|
|
(eldoc-box-quit-frame)
|
|
|
|
;; so you didn't clear the doc frame this time, and the last timer has ran out
|
|
|
|
;; setup another one to make sure the doc frame is cleared
|
|
|
|
;; once the condition above it met
|
|
|
|
(setq eldoc-box--cleanup-timer
|
2018-12-18 17:34:48 +00:00
|
|
|
(run-with-timer eldoc-box-cleanup-interval nil #'eldoc-box--maybe-cleanup))))
|
2018-12-12 06:58:56 +00:00
|
|
|
|
2018-12-11 14:15:00 +00:00
|
|
|
(defun eldoc-box--eldoc-message-function (str &rest args)
|
2018-12-12 18:28:40 +00:00
|
|
|
"Front-end for eldoc. Display STR in childframe and ARGS works like `message'."
|
2019-05-29 08:31:01 +00:00
|
|
|
(when (and (stringp str) (not (equal str "")))
|
2021-03-12 05:48:14 +00:00
|
|
|
(let* ((doc (apply #'format str args))
|
|
|
|
(multi-line-p (and eldoc-box-only-multi-line
|
|
|
|
(eq (cl-count ?\n doc) 0))))
|
|
|
|
(unless multi-line-p
|
2018-12-17 15:31:50 +00:00
|
|
|
(eldoc-box--display doc)
|
|
|
|
(setq eldoc-box--last-point (point))
|
|
|
|
;; Why a timer? ElDoc is mainly used in minibuffer,
|
|
|
|
;; where the text is constantly being flushed by other commands
|
|
|
|
;; so ElDoc doesn't try very hard to cleanup
|
|
|
|
(when eldoc-box--cleanup-timer (cancel-timer eldoc-box--cleanup-timer))
|
|
|
|
;; this function is also called by `eldoc-pre-command-refresh-echo-area'
|
|
|
|
;; in `pre-command-hook', which means the timer is reset before every
|
|
|
|
;; command if `eldoc-box-hover-mode' is on and `eldoc-last-message' is not nil.
|
|
|
|
(setq eldoc-box--cleanup-timer
|
2021-03-12 05:48:14 +00:00
|
|
|
(run-with-timer eldoc-box-cleanup-interval nil #'eldoc-box--maybe-cleanup)))
|
|
|
|
multi-line-p)))
|
2018-12-11 01:26:08 +00:00
|
|
|
|
2021-03-25 15:24:50 +00:00
|
|
|
;;;###autoload
|
|
|
|
(define-minor-mode eldoc-box-hover-mode
|
|
|
|
"Displays hover documentations in a childframe.
|
|
|
|
The default position of childframe is upper corner."
|
|
|
|
:lighter " ELDOC-BOX"
|
|
|
|
(if eldoc-box-hover-mode
|
|
|
|
(progn (when eldoc-box-hover-at-point-mode
|
|
|
|
(eldoc-box-hover-at-point-mode -1))
|
|
|
|
(eldoc-box--enable))
|
|
|
|
(eldoc-box--disable)))
|
|
|
|
|
|
|
|
;;;###autoload
|
|
|
|
(define-minor-mode eldoc-box-hover-at-point-mode
|
|
|
|
"A convenient minor mode to display doc at point.
|
|
|
|
You can use C-g to hide the doc."
|
|
|
|
:lighter " ELDOC-BOX"
|
|
|
|
(if eldoc-box-hover-at-point-mode
|
|
|
|
(progn (when eldoc-box-hover-mode
|
|
|
|
(eldoc-box-hover-mode -1))
|
|
|
|
(setq-local eldoc-box-position-function
|
|
|
|
#'eldoc-box--default-at-point-position-function)
|
|
|
|
(setq-local eldoc-box-clear-with-C-g t)
|
|
|
|
(remove-hook 'pre-command-hook #'eldoc-pre-command-refresh-echo-area t)
|
|
|
|
(add-hook 'post-command-hook #'eldoc-box--follow-cursor t t)
|
|
|
|
(eldoc-box--enable))
|
|
|
|
(eldoc-box--disable)
|
|
|
|
(add-hook 'pre-command-hook #'eldoc-pre-command-refresh-echo-area t)
|
|
|
|
(remove-hook 'post-command-hook #'eldoc-box--follow-cursor t)
|
|
|
|
(kill-local-variable 'eldoc-box-position-function)
|
|
|
|
(kill-local-variable 'eldoc-box-clear-with-C-g)))
|
|
|
|
|
2019-01-05 00:06:34 +00:00
|
|
|
;;;; Eglot helper
|
|
|
|
|
|
|
|
(defvar eldoc-box-eglot-help-at-point-last-point 0
|
|
|
|
"This point cache is used by clean up function.
|
|
|
|
If (point) != last point, cleanup frame.")
|
|
|
|
|
|
|
|
(defun eldoc-box--eglot-help-at-point-cleanup ()
|
|
|
|
"Try to clean up the childframe made by eldoc-box hack."
|
2019-01-06 00:45:50 +00:00
|
|
|
(if (or (eq (point) eldoc-box-eglot-help-at-point-last-point)
|
|
|
|
;; don't clean up when the user clicks childframe
|
|
|
|
(eq (selected-frame) eldoc-box--frame))
|
2019-01-05 00:06:34 +00:00
|
|
|
(run-with-timer 0.1 nil #'eldoc-box--eglot-help-at-point-cleanup)
|
|
|
|
(eldoc-box-quit-frame)))
|
|
|
|
|
|
|
|
(defvar eglot--managed-mode)
|
|
|
|
(declare-function eglot--dbind "eglot.el")
|
2021-03-25 15:24:50 +00:00
|
|
|
(declare-function eglot--hover-info "eglot.el")
|
|
|
|
(declare-function eglot--current-server-or-lose "eglot.el")
|
|
|
|
(declare-function eglot--TextDocumentPositionParams "eglot.el")
|
|
|
|
(declare-function eglot--error "eglot.el")
|
|
|
|
(declare-function jsonrpc-request "jsonrpc")
|
2019-01-05 00:06:34 +00:00
|
|
|
|
|
|
|
|
|
|
|
(defun eldoc-box-eglot-help-at-point ()
|
|
|
|
"Display documentation of the symbol at point."
|
|
|
|
(interactive)
|
|
|
|
(when eglot--managed-mode
|
|
|
|
(let ((eldoc-box-position-function #'eldoc-box--default-at-point-position-function))
|
|
|
|
(eldoc-box--display
|
|
|
|
(eglot--dbind ((Hover) contents range)
|
2019-06-07 12:31:35 +00:00
|
|
|
(jsonrpc-request (eglot--current-server-or-lose) :textDocument/hover
|
|
|
|
(eglot--TextDocumentPositionParams))
|
|
|
|
(when (seq-empty-p contents) (eglot--error "No hover info here"))
|
|
|
|
(eglot--hover-info contents range))))
|
2019-01-05 00:06:34 +00:00
|
|
|
(setq eldoc-box-eglot-help-at-point-last-point (point))
|
|
|
|
(run-with-timer 0.1 nil #'eldoc-box--eglot-help-at-point-cleanup)))
|
|
|
|
|
2019-06-06 13:17:37 +00:00
|
|
|
;;;; Comany compatibility
|
|
|
|
;;
|
|
|
|
|
2019-06-07 12:31:35 +00:00
|
|
|
;; see also `eldoc-box--default-at-point-position-function'
|
2019-06-06 13:17:37 +00:00
|
|
|
|
2019-06-07 12:31:35 +00:00
|
|
|
;; please compiler
|
|
|
|
(defvar company-pseudo-tooltip-overlay)
|
2019-06-06 13:17:37 +00:00
|
|
|
|
2019-06-07 12:31:35 +00:00
|
|
|
(defun eldoc-box--at-point-x-by-company ()
|
|
|
|
"Return the x position that accommodates company's popup."
|
|
|
|
(if (and (featurep 'company) company-pseudo-tooltip-overlay)
|
2019-11-02 04:09:10 +00:00
|
|
|
(+ (* (frame-char-width)
|
|
|
|
(+ (overlay-get company-pseudo-tooltip-overlay 'company-width)
|
|
|
|
(overlay-get company-pseudo-tooltip-overlay 'company-column)))
|
|
|
|
(or (line-number-display-width t) 0))
|
2019-06-07 12:31:35 +00:00
|
|
|
nil))
|
2019-06-06 13:17:37 +00:00
|
|
|
|
2018-12-11 14:15:00 +00:00
|
|
|
(provide 'eldoc-box)
|
2018-12-11 01:26:08 +00:00
|
|
|
|
2018-12-11 14:15:00 +00:00
|
|
|
;;; eldoc-box.el ends here
|