diff options
Diffstat (limited to 'elpa/lsp-ui-20220425.1046/lsp-ui-doc.el')
-rw-r--r-- | elpa/lsp-ui-20220425.1046/lsp-ui-doc.el | 1220 |
1 files changed, 1220 insertions, 0 deletions
diff --git a/elpa/lsp-ui-20220425.1046/lsp-ui-doc.el b/elpa/lsp-ui-20220425.1046/lsp-ui-doc.el new file mode 100644 index 0000000..1a1a666 --- /dev/null +++ b/elpa/lsp-ui-20220425.1046/lsp-ui-doc.el @@ -0,0 +1,1220 @@ +;;; lsp-ui-doc.el --- Lsp-Ui-Doc -*- lexical-binding: t -*- + +;; Copyright (C) 2017 Sebastien Chapuis + +;; Author: Sebastien Chapuis <sebastien@chapu.is> +;; URL: https://github.com/emacs-lsp/lsp-ui +;; Keywords: languagues, tools +;; Version: 6.2 + +;;; 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. + +;;; Commentary: +;; +;; Show documentation of the symbol at point in a child frame + +;;; Code: + +(require 'lsp-ui-util) + +(require 'lsp-protocol) +(require 'lsp-mode) +(require 'dash) +(require 'goto-addr) +(require 'markdown-mode) + +(require 'cl-lib) +(require 'face-remap) +(require 'subr-x) + +(when (featurep 'xwidget-internal) + (require 'xwidget)) + +(declare-function make-xwidget "ext:xwidget" (type title width height arguments &optional buffer)) +(declare-function set-xwidget-query-on-exit-flag "ext:xwidget") +(declare-function xwidget-webkit-mode "ext:xwidget") +(declare-function xwidget-webkit-goto-uri "ext:xwidget" (xwidget uri)) +(declare-function xwidget-at "ext:xwidget" (pos)) +(declare-function xwidget-webkit-execute-script "ext:xwidget" (xwidget script &optional callback)) +(declare-function xwidget-webkit-execute-script-rv "ext:xwidget" (xwidget script &optional default)) +(declare-function xwidget-resize "ext:xwidget" (xwidget new-width new-height)) + +(defgroup lsp-ui-doc nil + "Display informations of the current line." + :group 'tools + :group 'convenience + :group 'lsp-ui + :link '(custom-manual "(lsp-ui-doc) Top") + :link '(info-link "(lsp-ui-doc) Customizing")) + +(defcustom lsp-ui-doc-enable t + "Whether or not to enable lsp-ui-doc. +Displays documentation of the symbol at point on hover. This only takes effect when a buffer is started." + :type 'boolean + :group 'lsp-ui) + +(defcustom lsp-ui-doc-show-with-mouse t + "Move the mouse pointer over a symbol to show its documentation." + :type 'boolean + :group 'lsp-ui-doc) + +(defcustom lsp-ui-doc-show-with-cursor nil + "Move the cursor over a symbol to show its documentation." + :type 'boolean + :group 'lsp-ui-doc) + +(defcustom lsp-ui-doc-header nil + "Whether or not to enable the header which display the symbol string." + :type 'boolean + :group 'lsp-ui-doc) + +(defcustom lsp-ui-doc-include-signature nil + "Whether or not to include the object signature/type in the frame." + :type 'boolean + :group 'lsp-ui-doc) + +(defcustom lsp-ui-doc-position 'top + "Where to display the doc when moving the point cursor. +This affect the position of the documentation when `lsp-ui-doc-show-with-cursor' +is non-nil." + :type '(choice (const :tag "Top" top) + (const :tag "Bottom" bottom) + (const :tag "At point" at-point)) + :group 'lsp-ui-doc) + +(defcustom lsp-ui-doc-alignment 'frame + "How to align the doc. +This only takes effect when `lsp-ui-doc-position' is 'top or 'bottom." + :type '(choice (const :tag "Frame" frame) + (const :tag "Window" window)) + :group 'lsp-ui-doc) + +(defcustom lsp-ui-doc-border "white" + "Border color of the frame." + :type 'color + :group 'lsp-ui-doc) + +(defcustom lsp-ui-doc-max-width 150 + "Maximum number of columns of the frame." + :type 'integer + :group 'lsp-ui-doc) + +(defcustom lsp-ui-doc-webkit-max-width-px 600 + "Maximum width in pixels for the webkit frame." + :type 'integer + :group 'lsp-ui-doc) + +(defcustom lsp-ui-doc-max-height 13 + "Maximum number of lines in the frame." + :type 'integer + :group 'lsp-ui-doc) + +(defcustom lsp-ui-doc-use-childframe t + "Whether to display documentation in a child-frame or the current frame. +Child frames requires GNU/Emacs version >= 26 and graphical frames." + :type 'boolean + :group 'lsp-ui-doc) + +(defcustom lsp-ui-doc-use-webkit nil + "Whether to display documentation in a WebKit widget in a child-frame. +This requires GNU/Emacs version >= 26 and built with the `--with-xwidgets` +option." + :type 'boolean + :group 'lsp-ui-doc) + +(defcustom lsp-ui-doc-delay 0.2 + "Number of seconds before showing the doc." + :type 'number + :group 'lsp-ui-doc) + +(defcustom lsp-ui-doc-winum-ignore t + "Whether to ignore lsp-ui-doc buffers in winum." + :type 'boolean + :group 'lsp-ui-doc) + +(defcustom lsp-ui-doc-enhanced-markdown t + "Lsp-ui-doc will attempt to better format the markdown documentation." + :type 'boolean + :group 'lsp-ui-doc) + +(defcustom lsp-ui-doc-text-scale-level 0 + "Text scale amount for doc buffer." + :type 'integer + :group 'lsp-ui-doc) + +(defface lsp-ui-doc-background + '((((background light)) :background "#b3b3b3") + (t :background "#272A36")) + "Background color of the documentation. +Only the `background' is used in this face." + :group 'lsp-ui-doc) + +(defface lsp-ui-doc-header + '((t :foreground "black" + :background "deep sky blue")) + "Face used on the header." + :group 'lsp-ui-doc) + +(defface lsp-ui-doc-highlight-hover + '((t :inherit region)) + "Face used to highlight the hover symbol/region when using mouse." + :group 'lsp-ui-doc) + +(defface lsp-ui-doc-url + '((t :inherit link)) + "Face used on links." + :group 'lsp-ui-doc) + +(defvar lsp-ui-doc-frame-parameters + '((left . -1) + (no-focus-on-map . t) + (min-width . 0) + (width . 0) + (min-height . 0) + (height . 0) + (internal-border-width . 1) + (vertical-scroll-bars . nil) + (horizontal-scroll-bars . nil) + (right-fringe . 0) + (menu-bar-lines . 0) + (tool-bar-lines . 0) + (tab-bar-lines . 0) + (tab-bar-lines-keep-state . 0) + (line-spacing . 0) + (unsplittable . t) + (undecorated . t) + (top . -1) + (visibility . nil) + (mouse-wheel-frame . nil) + (no-other-frame . t) + (inhibit-double-buffering . t) + (drag-internal-border . t) + (no-special-glyphs . t) + (desktop-dont-save . t)) + "Frame parameters used to create the frame.") + +(defvar lsp-ui-doc-render-function nil + "Function called to format the documentation. +The function takes a string as parameter and should return a string. +If this variable is nil (the default), the documentation will be rendered +as markdown.") + +(defvar lsp-ui-doc-frame-hook nil + "Hooks run on child-frame creation. +The functions receive 2 parameters: the frame and its window.") + +(defvar lsp-ui-doc-webkit-client-path + (concat "file://" + (file-name-directory (or load-file-name buffer-file-name)) + "lsp-ui-doc.html") + "Path to the page loaded when a WebKit widget is created.") + +;; Avoid warning with emacs < 26 +(declare-function display-buffer-in-child-frame "window.el") + +(defvar-local lsp-ui-doc--parent-vars nil + "Variables from the parents frame that we want to access in the child. +Because some variables are buffer local.") + +(defvar-local lsp-ui-doc--inline-ov nil + "Overlay used to display the documentation in the buffer.") + +(defvar-local lsp-ui-doc--highlight-ov nil + "Overlay used to highlight the hover symbol.") + +(defvar-local lsp-ui-doc--bounds nil) +(defvar-local lsp-ui-doc--timer nil) +(defvar-local lsp-ui-doc--from-mouse nil + "Non nil when the doc was triggered by a mouse event.") +(defvar-local lsp-ui-doc--from-mouse-current nil + "Non nil when the current call is triggered by a mouse event") + +(defconst lsp-ui-doc--buffer-prefix " *lsp-ui-doc-") + +(defmacro lsp-ui-doc--with-buffer (&rest body) + "Execute BODY in the lsp-ui-doc buffer." + (declare (indent 0) (debug t)) + `(let ((parent-vars (list :buffer (current-buffer) + :window (get-buffer-window))) + (buffer-list-update-hook nil)) + (with-current-buffer (get-buffer-create (lsp-ui-doc--make-buffer-name)) + (setq lsp-ui-doc--parent-vars parent-vars) + (prog1 (let ((buffer-read-only nil) + (inhibit-modification-hooks t) + (inhibit-point-motion-hooks t) + (inhibit-redisplay t)) + ,@body) + (setq buffer-read-only t) + (let ((text-scale-mode-step 1.1)) + (text-scale-set lsp-ui-doc-text-scale-level)))))) + +(defmacro lsp-ui-doc--get-parent (var) + "Return VAR in `lsp-ui-doc--parent-vars'." + `(plist-get lsp-ui-doc--parent-vars ,var)) + +(defmacro lsp-ui-doc--set-frame (frame) + "Set the frame parameter ‘lsp-ui-doc-frame’ to FRAME." + `(set-frame-parameter nil 'lsp-ui-doc-frame ,frame)) + +(defun lsp-ui-doc--get-frame (&optional _) + "Return the child frame." + (let ((frame (frame-parameter nil 'lsp-ui-doc-frame))) + (and (frame-live-p frame) frame))) + +(defsubst lsp-ui-doc--frame-visible-p () + "Return child frame visibility." + (let ((frame (lsp-ui-doc--get-frame))) + (and frame (frame-visible-p frame)))) + +(defun lsp-ui-doc--make-buffer-name () + "Construct the buffer name, it should be unique for each frame." + (concat lsp-ui-doc--buffer-prefix + (or (frame-parameter nil 'window-id) + (frame-parameter nil 'name)) + "*")) + +;; ‘markdown-fontify-code-block-default-mode’ isn’t yet available in +;; Markdown 2.3. +(defvar markdown-fontify-code-block-default-mode) + +(defun lsp-ui-doc--inline-wrapped-line (string) + "Wraps a line of text (STRING) for inline display." + (cond ((string-empty-p string) "") + (t string))) + +(defun lsp-ui-doc--inline-formatted-string (string) + "Formats STRING for inline rendering." + (mapconcat (lambda (line) + (lsp-ui-doc--inline-wrapped-line (string-trim-right line))) + (split-string string "[\n\v\f\r]") + "\n")) + +(defun lsp-ui-doc--extract-marked-string (marked-string &optional language) + "Render the MARKED-STRING with LANGUAGE." + (string-trim-right + (let* ((string (if (stringp marked-string) + marked-string + (lsp:markup-content-value marked-string))) + (with-lang (lsp-marked-string? marked-string)) + (language (or (and with-lang + (or (lsp:marked-string-language marked-string) + (lsp:markup-content-kind marked-string))) + language)) + (markdown-hr-display-char nil)) + (cond + (lsp-ui-doc-use-webkit + (if (and language + (not (string= "text" language)) + (not (string= lsp/markup-kind-markdown language))) + (format "```%s\n%s\n```" language string) + string)) + ;; For other programming languages + (language (lsp--render-string (lsp-ui-doc--inline-formatted-string string) language)) + ;; For default element content + (t (lsp--render-element (lsp-ui-doc--inline-formatted-string string))))))) + +(defun lsp-ui-doc--filter-marked-string (list-marked-string) + "Filter the LIST-MARKED-STRING." + (let ((groups (--separate (and (lsp-marked-string? it) + (lsp-get-renderer (lsp:marked-string-language it))) + (append list-marked-string nil)))) + (if lsp-ui-doc-include-signature + list-marked-string + (cadr groups)))) + +(defun lsp-ui-doc--extract (contents) + "Extract the documentation from CONTENTS. +CONTENTS can be differents type of values: +MarkedString | MarkedString[] | MarkupContent (as defined in the LSP). +We don't extract the string that `lps-line' is already displaying." + (cond + ((vectorp contents) ;; MarkedString[] + (mapconcat 'lsp-ui-doc--extract-marked-string + (lsp-ui-doc--filter-marked-string (seq-filter #'identity contents)) + "\n\n" + ;;(propertize "\n\n" 'face '(:height 0.4)) + )) + ;; when we get markdown contents, render using emacs gfm-view-mode / markdown-mode + ((and (lsp-marked-string? contents) + (lsp:marked-string-language contents)) + (lsp-ui-doc--extract-marked-string (lsp:marked-string-value contents) + (lsp:marked-string-language contents))) + ;; The specification for MarkedString also includes raw strings of + ;; markdown, which is not reflected by `lsp-marked-string?' + ((stringp contents) + (lsp-ui-doc--extract-marked-string contents lsp/markup-kind-markdown)) + ((lsp-marked-string? contents) (lsp-ui-doc--extract-marked-string contents)) + ((and (lsp-markup-content? contents) + (string= (lsp:markup-content-kind contents) lsp/markup-kind-markdown)) + (lsp-ui-doc--extract-marked-string (lsp:markup-content-value contents) lsp/markup-kind-markdown)) + ((and (lsp-markup-content? contents) + (string= (lsp:markup-content-kind contents) lsp/markup-kind-plain-text)) + (lsp:markup-content-value contents)))) + +(defun lsp-ui-doc--webkit-run-xwidget () + "Launch embedded WebKit instance." + (lsp-ui-doc--with-buffer + (let ((inhibit-read-only t)) + (insert " ") + (goto-char 1) + (let ((id (make-xwidget 'webkit nil 1 1 nil (buffer-name)))) + (set-xwidget-query-on-exit-flag id nil) + (put-text-property (point) (+ 1 (point)) + 'display (list 'xwidget ':xwidget id)) + (xwidget-webkit-mode) + (xwidget-webkit-goto-uri (xwidget-at 1) + lsp-ui-doc-webkit-client-path) + (lsp-ui-doc--webkit-set-width) + (lsp-ui-doc--webkit-set-background) + (lsp-ui-doc--webkit-set-foreground))))) + +(defun lsp-ui-doc--webkit-set-width () + "Set webkit document max-width CSS property." + (lsp-ui-doc--webkit-execute-script + (format "document.documentElement.style.setProperty('--webkit-max-width-px', %d + 'px');" + lsp-ui-doc-webkit-max-width-px))) + +(defun lsp-ui-doc--webkit-set-background () + "Set background color of the WebKit widget." + (lsp-ui-doc--webkit-execute-script + (format "document.body.style.background = '%s';" + "#fdfdfd" + ;; (face-attribute 'lsp-ui-doc-background :background) + ))) + +(defun lsp-ui-doc--webkit-set-foreground () + "Set foreground color of the WebKit widget." + (lsp-ui-doc--webkit-execute-script + (format "document.body.style.color = '%s';" + (face-attribute 'default :foreground)))) + +(defun lsp-ui-doc--webkit-get-xwidget () + "Return Xwidget instance." + (lsp-ui-doc--with-buffer + (xwidget-at 1))) + +(defun lsp-ui-doc--webkit-execute-script (script &optional fn) + "Execute SCRIPT in embedded Xwidget and run optional callback FN." + (-when-let* ((xw (lsp-ui-doc--webkit-get-xwidget))) + (xwidget-webkit-execute-script xw script fn))) + +(defun lsp-ui-doc--webkit-execute-script-rv (script) + "Execute SCRIPT in embedded Xwidget synchronously." + (-when-let* ((xw (lsp-ui-doc--webkit-get-xwidget))) + (xwidget-webkit-execute-script-rv xw script))) + +(defun lsp-ui-doc--hide-frame (&optional _win) + "Hide the frame." + (setq lsp-ui-doc--bounds nil + lsp-ui-doc--from-mouse nil) + (lsp-ui-util-safe-delete-overlay lsp-ui-doc--inline-ov) + (lsp-ui-util-safe-delete-overlay lsp-ui-doc--highlight-ov) + (when-let ((frame (lsp-ui-doc--get-frame))) + (when (frame-visible-p frame) + (make-frame-invisible frame)))) + +(defun lsp-ui-doc--buffer-width () + "Calcul the max width of the buffer." + (lsp-ui-doc--with-buffer + (save-excursion + (let ((max 0)) + (goto-char (point-min)) + (while (not (eobp)) + (let* ((len (- (line-end-position) (line-beginning-position)))) + (when (> len max) + (setq max len))) + (forward-line 1)) + max)))) + +(defun lsp-ui-doc--line-height (&optional line) + "Return the pos-y of the LINE on screen, in pixel." + (or + (nth 2 (or (window-line-height line) + (and (redisplay t) + (window-line-height line)))) + 0)) + +(defun lsp-ui-doc--sideline-pos-y () + "Mark as unused function." + (-> (when (bound-and-true-p lsp-ui-sideline--occupied-lines) + (-min lsp-ui-sideline--occupied-lines)) + (line-number-at-pos) + (lsp-ui-doc--line-height))) + +(defun lsp-ui-doc--webkit-resize-callback (size) + "Callback when resizing using webkit depends on the SIZE." + (let ((offset-width (round (aref size 0))) + (offset-height (round (aref size 1)))) + (xwidget-resize (lsp-ui-doc--webkit-get-xwidget) offset-width offset-height)) + (lsp-ui-doc--move-frame (lsp-ui-doc--get-frame))) + +(defun lsp-ui-doc--scale-column-width (width) + "Return WIDTH adjusted relative to the text scale." + (floor (/ width (expt 1.1 lsp-ui-doc-text-scale-level)))) + +(defun lsp-ui-doc--resize-buffer () + "If the buffer's width is larger than the current frame, resize it." + (if lsp-ui-doc-use-webkit + (lsp-ui-doc--webkit-execute-script + "[document.querySelector('#lsp-ui-webkit').offsetWidth, document.querySelector('#lsp-ui-webkit').offsetHeight];" + 'lsp-ui-doc--webkit-resize-callback) + + (let* ((frame-width (frame-width)) + (fill-column (lsp-ui-doc--scale-column-width (min lsp-ui-doc-max-width (- frame-width 5))))) + (when (> (lsp-ui-doc--buffer-width) (min lsp-ui-doc-max-width frame-width)) + (lsp-ui-doc--with-buffer + (fill-region (point-min) (point-max))))))) + +(defun lsp-ui-doc--mv-at-point (width height start-x start-y) + "Return position of FRAME to be where the point is. +WIDTH is the child frame width. +HEIGHT is the child frame height. +START-X is the position x of the current window. +START-Y is the position y of the current window. +The algorithm prefers to position FRAME just above the +symbol at point, to not obstruct the view of the code that follows. +If there's no space above in the current window, it places +FRAME just below the symbol at point." + (-let* (((x . y) (--> (or lsp-ui-doc--bounds (bounds-of-thing-at-point 'symbol)) + (or (posn-x-y (posn-at-point (car it))) + (if (< (car it) (window-start)) + (cons 0 0) + (posn-x-y (posn-at-point (1- (window-end)))))))) + (frame-relative-symbol-x (+ start-x x (* (frame-char-width) 2))) + (frame-relative-symbol-y (+ start-y y)) + (char-height (frame-char-height)) + ;; Make sure the frame is positioned horizontally such that + ;; it does not go beyond the frame boundaries. + (frame-x (or (and (<= (frame-outer-width) (+ frame-relative-symbol-x width)) + (- x (- (+ frame-relative-symbol-x width) + (frame-outer-width)))) + x)) + (frame-y (+ (or (and (<= height frame-relative-symbol-y) + (- y height)) + (+ y char-height)) + (if (fboundp 'window-tab-line-height) (window-tab-line-height) 0)))) + (cons (+ start-x frame-x) (+ start-y frame-y)))) + +(defun lsp-ui-doc--size-and-pos-changed (frame left top width height) + (-let (((prev-left . prev-top) (frame-position frame))) + (not (and (= left prev-left) + (= top prev-top) + (= height (frame-text-height frame)) + (= width (frame-text-width frame)))))) + +(defun lsp-ui-doc--move-frame (frame) + "Place our FRAME on screen." + (-let* (((left top right _bottom) (window-edges nil t nil t)) + (window (frame-root-window frame)) + (char-h (frame-char-height frame)) + (char-w (frame-char-width frame)) + ((width . height) (window-text-pixel-size window nil nil 10000 10000 t)) + (width (+ width (* char-w 1))) ;; margins + (height (min (- (* lsp-ui-doc-max-height char-h) (/ char-h 2)) height)) + (width (min width (* lsp-ui-doc-max-width char-w))) + (frame-right (pcase lsp-ui-doc-alignment + ('frame (frame-pixel-width)) + ('window right))) + ((left . top) (if (eq lsp-ui-doc-position 'at-point) + (lsp-ui-doc--mv-at-point width height left top) + (cons (max (- frame-right width char-w) 10) + (pcase lsp-ui-doc-position + ('top (+ top char-w)) + ('bottom (- (lsp-ui-doc--line-height 'mode-line) + height + 10)))))) + (frame-resize-pixelwise t) + (move-frame-functions nil) + (window-size-change-functions nil) + (window-state-change-hook nil) + (window-state-change-functions nil) + (window-configuration-change-hook nil) + (inhibit-redisplay t)) + ;; Dirty way to fix unused variable in emacs 26 + (and window-state-change-functions + window-state-change-hook) + ;; Make frame invisible before moving/resizing it to avoid flickering: + ;; We set the position and size in 1 call, modify-frame-parameters, but + ;; internally emacs makes 2 different calls, which can be visible + ;; to the user + (and (frame-visible-p frame) + (lsp-ui-doc--size-and-pos-changed frame left top width height) + (make-frame-invisible frame)) + (modify-frame-parameters + frame + `((width . (text-pixels . ,width)) + (height . (text-pixels . ,height)) + (user-size . t) + (left . (+ ,left)) + (top . (+ ,top)) + (user-position . t) + (lsp-ui-doc--window-origin . ,(selected-window)) + (lsp-ui-doc--buffer-origin . ,(current-buffer)) + (lsp-ui-doc--no-focus . t) + (right-fringe . 0) + (left-fringe . 0))) + ;; Insert hr lines after width is computed + (lsp-ui-doc--fix-hr-props) + (unless (frame-visible-p frame) + (make-frame-visible frame)))) + +(defun lsp-ui-doc--visit-file (filename) + "Visit FILENAME in the parent frame." + (-some->> (find-file-noselect filename) + (set-window-buffer (lsp-ui-doc--get-parent :window)))) + +(defun lsp-ui-doc--put-click (start end fn) + "Add text properties on text to make it clickable. +The text delimiters bound from START to END. +FN is the function to call on click." + (let ((map (make-sparse-keymap))) + (define-key map [down-mouse-1] fn) + (put-text-property start end 'keymap map) + (put-text-property start end 'mouse-face + (list :inherit 'lsp-ui-doc-url + :box (list :line-width -1 + :color (face-foreground 'lsp-ui-doc-url)))) + (add-face-text-property start end 'lsp-ui-doc-url))) + +(defun lsp-ui-doc--open-markdown-link (&rest _) + (interactive "P") + (let ((buffer-list-update-hook nil)) + (-let [(buffer point) (if-let* ((valid (and (listp last-input-event) + (eq (car last-input-event) 'mouse-2))) + (event (cadr last-input-event)) + (win (posn-window event)) + (buffer (window-buffer win))) + `(,buffer ,(posn-point event)) + `(,(current-buffer) ,(point)))] + (with-current-buffer buffer + ;; Markdown-mode puts the url in 'help-echo + (-some--> (get-text-property point 'help-echo) + (and (string-match-p goto-address-url-regexp it) + (browse-url it))))))) + +(defun lsp-ui-doc--make-clickable-link () + "Find paths and urls in the buffer and make them clickable." + (goto-char (point-min)) + (save-excursion + (goto-char (point-min)) + (let (case-fold-search) + (while (re-search-forward goto-address-url-regexp nil t) + (goto-char (1+ (match-end 0))) + (lsp-ui-doc--put-click (match-beginning 0) (match-end 0) + 'browse-url-at-mouse))))) + +(defun lsp-ui-doc--buffer-pre-command (&rest _) + (and (not (eq this-command 'mwheel-scroll)) + (frame-parameter nil 'lsp-ui-doc--no-focus) + (select-frame (frame-parent) t))) + +(defun lsp-ui-doc--fill-document () + "Better wrap the document so it fits the doc window." + (let ((fill-column (lsp-ui-doc--scale-column-width (- lsp-ui-doc-max-width 5))) + start ; record start for `fill-region' + first-line) ; first line in paragraph + (save-excursion + (goto-char (point-min)) + (setq start (point) + first-line (thing-at-point 'line)) + (while (re-search-forward "^[ \t]*\n" nil t) + (setq first-line (thing-at-point 'line)) + (when (< fill-column (length first-line)) + (fill-region start (point))) + (setq start (point))) + ;; Fill the last paragraph + (when (< fill-column (length first-line)) + (fill-region start (point-max)))))) + +(defun lsp-ui-doc--make-smaller-empty-lines nil + "Make empty lines half normal lines." + (progn ; Customize line before header + (goto-char 1) + (insert (propertize "\n" 'face '(:height 0.3)))) + (progn ; Customize line after header + (forward-line 1) + (insert (propertize " " 'face '(:height 0.1)))) + (while (not (eobp)) + (when (and (eolp) (not (bobp))) + (save-excursion + (delete-region (point) (progn (forward-visible-line 1) (point)))) + (when (or (and (not (get-text-property (point) 'markdown-heading)) + (not (get-text-property (max (- (point) 2) 1) 'markdown-heading))) + (get-text-property (point) 'markdown-hr)) + (insert (propertize " " 'face `(:height 0.2)) + (propertize "\n" 'face '(:height 0.4))))) + (forward-line)) + (insert (propertize "\n\n" 'face '(:height 0.3)))) + +(defun lsp-ui-doc--fix-hr-props nil + ;; We insert the right display prop after window-text-pixel-size + (lsp-ui-doc--with-buffer + (let (next) + (while (setq next (next-single-property-change (or next 1) 'lsp-ui-doc--replace-hr)) + (when (get-text-property next 'lsp-ui-doc--replace-hr) + (put-text-property next (1+ next) 'display + '(space :align-to (- right-fringe 1) :height (1))) + (put-text-property (1+ next) (+ next 2) 'display + '(space :align-to right-fringe :height (1)))))))) + +(defun lsp-ui-doc--handle-hr-lines nil + (let (bolp next before after) + (goto-char 1) + (while (setq next (next-single-property-change (or next 1) 'markdown-hr)) + (when (get-text-property next 'markdown-hr) + (goto-char next) + (setq bolp (bolp) + before (char-before)) + (delete-region (point) (save-excursion (forward-visible-line 1) (point))) + (setq after (char-after (1+ (point)))) + (insert + (concat + (and bolp (not (equal before ?\n)) (propertize "\n" 'face '(:height 0.5))) + (propertize " " + ;; :align-to is added with lsp-ui-doc--fix-hr-props + 'display '(space :height (1)) + 'lsp-ui-doc--replace-hr t + 'face '(:background "dark grey")) + ;; :align-to is added here too + (propertize " " 'display '(space :height (1))) + (and (not (equal after ?\n)) (propertize " \n" 'face '(:height 0.2))))))))) + +(defun lsp-ui-doc--render-buffer (string symbol) + "Set the buffer with STRING and SYMBOL." + (lsp-ui-doc--with-buffer + (if lsp-ui-doc-use-webkit + (progn + (lsp-ui-doc--webkit-execute-script + (format "renderMarkdown('%s', '%s');" + symbol + (url-hexify-string string)) + 'lsp-ui-doc--webkit-resize-callback)) + (erase-buffer) + (insert (s-trim string)) + (unless (or (lsp-ui-doc--inline-p) (not lsp-ui-doc-enhanced-markdown)) + (lsp-ui-doc--fill-document) + (lsp-ui-doc--make-smaller-empty-lines) + (lsp-ui-doc--handle-hr-lines)) + (add-text-properties 1 (point) '(line-height 1)) + (lsp-ui-doc--make-clickable-link) + (add-text-properties 1 (point-max) '(pointer arrow))) + (lsp-ui-doc-frame-mode 1) + (setq wrap-prefix '(space :height (1) :width 1) + line-prefix '(space :height (1) :width 1)) + (setq-local face-remapping-alist `((header-line lsp-ui-doc-header))) + (setq-local window-min-height 1) + (setq-local show-trailing-whitespace nil) + (setq-local window-configuration-change-hook nil) + (add-hook 'pre-command-hook 'lsp-ui-doc--buffer-pre-command nil t) + (when (boundp 'window-state-change-functions) + (setq-local window-state-change-functions nil)) + (when (boundp 'window-state-change-hook) + (setq-local window-state-change-hook nil)) + (setq-local window-size-change-functions nil) + (setq header-line-format (when lsp-ui-doc-header (concat " " symbol)) + mode-line-format nil + cursor-type nil))) + +(defun lsp-ui-doc--inline-height () + (lsp-ui-doc--with-buffer + (length (split-string (buffer-string) "\n")))) + +(defun lsp-ui-doc--remove-invisibles (string) + "Remove invisible characters in STRING." + (let* ((start (text-property-not-all 0 (length string) 'invisible nil string))) + (while start + (setq string (concat (substring string 0 start) + (-some->> (next-single-property-change start 'invisible string) + (substring string)))) + (setq start (text-property-not-all 0 (length string) 'invisible nil string))) + string)) + +(defvar-local lsp-ui-doc--inline-width nil) + +(defun lsp-ui-doc--inline-window-width nil + (- (min (window-text-width) (window-body-width)) + (if (bound-and-true-p display-line-numbers-mode) + (+ 2 (line-number-display-width)) + 0) + 1)) + +(defun lsp-ui-doc--inline-zip (s1 s2) + (let* ((width (lsp-ui-doc--inline-window-width)) + (max-s1 (- width lsp-ui-doc--inline-width 2))) + (truncate-string-to-width + (concat (truncate-string-to-width s1 max-s1 nil ?\s) s2) + width nil ?\s))) + +(defun lsp-ui-doc--inline-padding (string len) + (let ((string (concat " " string (make-string (- len (string-width string)) ?\s) " "))) + (add-face-text-property 0 (length string) (list :background (face-background 'lsp-ui-doc-background nil t)) t string) + string)) + +(defun lsp-ui-doc--inline-faking-frame (doc-strings) + (let* ((len-max (-max-by '> (-map 'string-width doc-strings)))) + (setq lsp-ui-doc--inline-width len-max) + (--map (lsp-ui-doc--inline-padding it len-max) doc-strings))) + +(defun lsp-ui-doc--inline-untab (string) + (replace-regexp-in-string "\t" (make-string tab-width ?\s) string nil t)) + +(defun lsp-ui-doc--inline-merge (strings) + (let* ((buffer-strings (-> (lsp-ui-doc--inline-untab strings) + (lsp-ui-doc--remove-invisibles) + (split-string "\n"))) + (doc-strings (-> (lsp-ui-doc--with-buffer (buffer-string)) + (lsp-ui-doc--inline-untab) + (lsp-ui-doc--remove-invisibles) + (split-string "\n"))) + (merged (--> (lsp-ui-doc--inline-faking-frame doc-strings) + (-zip-with 'lsp-ui-doc--inline-zip buffer-strings it) + (string-join it "\n") + (concat it "\n")))) + merged)) + +(defun lsp-ui-doc--inline-pos-at (start lines) + "Calcul the position at START + forward n LINES." + (save-excursion (goto-char start) (forward-line lines) (point))) + +(defun lsp-ui-doc--inline-pos (height) + "Return a cons of positions where to place the doc. +HEIGHT is the documentation number of lines." + (let* ((w-start (window-start)) + (w-end (lsp-ui-doc--inline-pos-at w-start (window-body-height))) + (ov-end (lsp-ui-doc--inline-pos-at w-start height))) + (cond + ;; Display on top ? + ((< (lsp-ui-doc--inline-pos-at ov-end 1) (point)) + (cons w-start ov-end)) + ;; Display at the bottom ? + ((>= (lsp-ui-doc--inline-pos-at w-end (- height)) + (lsp-ui-doc--inline-pos-at (point) 2)) + (cons (lsp-ui-doc--inline-pos-at w-end (- height)) + w-end)) + ;; The doc is too long to display it fixed to the bottom ? + ;; Then display 2 lines after `point' + ;; The end of the documentation won't be visible in the window + (t (cons (lsp-ui-doc--inline-pos-at (point) 2) + (lsp-ui-doc--inline-pos-at (point) (+ height 2))))))) + +(defun lsp-ui-doc--inline () + "Display the doc in the buffer." + (-let* ((height (lsp-ui-doc--inline-height)) + ((start . end) (lsp-ui-doc--inline-pos height)) + (buffer-string (buffer-substring start end)) + (ov (if (overlayp lsp-ui-doc--inline-ov) lsp-ui-doc--inline-ov + (setq lsp-ui-doc--inline-ov (make-overlay start end))))) + (move-overlay ov start end) + (overlay-put ov 'face 'default) + (overlay-put ov 'display (lsp-ui-doc--inline-merge buffer-string)) + (overlay-put ov 'lsp-ui-doc-inline t) + (overlay-put ov 'window (selected-window)))) + +(defun lsp-ui-doc--inline-p () + "Return non-nil when the documentation should be display without a child frame." + (or (not lsp-ui-doc-use-childframe) + (not (display-graphic-p)) + (not (fboundp 'display-buffer-in-child-frame)))) + +(defun lsp-ui-doc--highlight-hover nil + (when lsp-ui-doc--from-mouse-current + (-let* (((start . end) lsp-ui-doc--bounds) + (ov (if (overlayp lsp-ui-doc--highlight-ov) lsp-ui-doc--highlight-ov + (setq lsp-ui-doc--highlight-ov (make-overlay start end))))) + (move-overlay ov start end) + (overlay-put ov 'face 'lsp-ui-doc-highlight-hover) + (overlay-put ov 'window (selected-window))))) + +(defun lsp-ui-doc--display (symbol string) + "Display the documentation." + (when (and lsp-ui-doc-use-webkit (not (featurep 'xwidget-internal))) + (setq lsp-ui-doc-use-webkit nil)) + (if (or (null string) (string-empty-p string)) + (lsp-ui-doc--hide-frame) + (lsp-ui-doc--highlight-hover) + (lsp-ui-doc--render-buffer string symbol) + (if (lsp-ui-doc--inline-p) + (lsp-ui-doc--inline) + (unless (lsp-ui-doc--get-frame) + (lsp-ui-doc--set-frame (lsp-ui-doc--make-frame))) + (unless lsp-ui-doc-use-webkit + (lsp-ui-doc--resize-buffer) + (lsp-ui-doc--move-frame (lsp-ui-doc--get-frame)))) + (setq lsp-ui-doc--from-mouse lsp-ui-doc--from-mouse-current))) + +(defun lsp-ui-doc--make-frame () + "Create the child frame and return it." + (lsp-ui-doc--delete-frame) + (let* ((after-make-frame-functions nil) + (before-make-frame-hook nil) + (name-buffer (lsp-ui-doc--make-buffer-name)) + (buffer (get-buffer name-buffer)) + (params (append lsp-ui-doc-frame-parameters + `((name . "") + (default-minibuffer-frame . ,(selected-frame)) + (minibuffer . ,(minibuffer-window)) + (left-fringe . 0) + (right-fringe . 0) + (cursor-type . nil) + (lsp-ui-doc--no-focus . t) + (background-color . ,(face-background 'lsp-ui-doc-background nil t))))) + (window (display-buffer-in-child-frame + buffer + `((child-frame-parameters . ,params)))) + (frame (window-frame window))) + (with-current-buffer buffer + (lsp-ui-doc-frame-mode 1)) + (set-frame-parameter nil 'lsp-ui-doc-buffer buffer) + (set-window-dedicated-p window t) + ;;(redirect-frame-focus frame (frame-parent frame)) + (set-face-background 'internal-border lsp-ui-doc-border frame) + (when (facep 'child-frame-border) + (set-face-background 'child-frame-border lsp-ui-doc-border frame)) + (set-face-background 'fringe nil frame) + (run-hook-with-args 'lsp-ui-doc-frame-hook frame window) + (when lsp-ui-doc-use-webkit + (define-key (current-global-map) [xwidget-event] + (lambda () + (interactive) + (let ((xwidget-event-type (nth 1 last-input-event))) + ;; (when (eq xwidget-event-type 'load-changed) + ;; (lsp-ui-doc--move-frame (lsp-ui-doc--get-frame))) + (when (eq xwidget-event-type 'javascript-callback) + (let ((proc (nth 3 last-input-event)) + (arg (nth 4 last-input-event))) + (funcall proc arg)))))) + (lsp-ui-doc--webkit-run-xwidget)) + frame)) + +(defconst lsp-ui-doc--ignore-commands + '(lsp-ui-doc-hide + lsp-ui-doc--handle-mouse-movement + keyboard-quit + ignore + handle-switch-frame + mwheel-scroll)) + +(defun lsp-ui-doc--make-request nil + "Request the documentation to the LS." + (and (not track-mouse) lsp-ui-doc-show-with-mouse (setq-local track-mouse t)) + (when (and lsp-ui-doc-show-with-cursor + (not (memq this-command lsp-ui-doc--ignore-commands)) + (not (bound-and-true-p lsp-ui-peek-mode)) + (lsp--capability "hoverProvider")) + (-if-let (bounds (or (and (symbol-at-point) (bounds-of-thing-at-point 'symbol)) + (and (looking-at "[[:graph:]]") (cons (point) (1+ (point)))))) + (unless (equal lsp-ui-doc--bounds bounds) + (lsp-ui-doc--hide-frame) + (lsp-ui-util-safe-kill-timer lsp-ui-doc--timer) + (setq lsp-ui-doc--timer + (run-with-idle-timer + lsp-ui-doc-delay nil + (let ((buf (current-buffer))) + (lambda nil + (when (equal buf (current-buffer)) + (lsp-request-async + "textDocument/hover" + (lsp--text-document-position-params) + (lambda (hover) + (when (equal buf (current-buffer)) + (lsp-ui-doc--callback hover bounds (current-buffer)))) + :mode 'tick + :cancel-token :lsp-ui-doc-hover))))))) + (lsp-ui-doc--hide-frame)))) + +(defun lsp-ui-doc--extract-bounds (hover) + (-when-let* ((hover hover) + (data (lsp-get hover :range)) + (start (-some-> (lsp:range-start data) lsp--position-to-point)) + (end (-some-> (lsp:range-end data) lsp--position-to-point))) + (cons start end))) + +(lsp-defun lsp-ui-doc--callback ((hover &as &Hover? :contents) bounds buffer) + "Process the received documentation. +HOVER is the doc returned by the LS. +BOUNDS are points of the symbol that have been requested. +BUFFER is the buffer where the request has been made." + (let ((bounds (or (lsp-ui-doc--extract-bounds hover) bounds))) + (if (and hover + (>= (point) (car bounds)) + (<= (point) (cdr bounds)) + (eq buffer (current-buffer))) + (progn + (setq lsp-ui-doc--bounds bounds) + (lsp-ui-doc--display + (thing-at-point 'symbol t) + (-some->> contents + lsp-ui-doc--extract + (replace-regexp-in-string "\r" "") + (replace-regexp-in-string " " " ")))) + (lsp-ui-doc--hide-frame)))) + +(defun lsp-ui-doc--delete-frame () + "Delete the child frame if it exists." + (-when-let (frame (lsp-ui-doc--get-frame)) + (delete-frame frame) + (lsp-ui-doc--set-frame nil))) + +(defun lsp-ui-doc--visible-p () + "Return whether the LSP UI doc is visible" + (or (overlayp lsp-ui-doc--inline-ov) + (and (lsp-ui-doc--get-frame) + (frame-visible-p (lsp-ui-doc--get-frame))))) + +(defun lsp-ui-doc-hide-frame-on-window-change (fun window &optional no-record) + "Delete the child frame if currently selected window changes. +Does nothing if the newly-selected window is the same window as +before, or if the new window is the minibuffer." + (let ((initial-window (selected-window))) + (prog1 (funcall fun window no-record) + (unless no-record + (when (lsp-ui-doc--visible-p) + (let* ((current-window (selected-window)) + (doc-buffer (get-buffer (lsp-ui-doc--make-buffer-name)))) + (unless (or (window-minibuffer-p current-window) + (equal current-window initial-window) + (and doc-buffer + (equal (window-buffer initial-window) doc-buffer))) + (lsp-ui-doc--hide-frame)))))))) + +(unless (boundp 'window-state-change-functions) + (advice-add #'select-window :around #'lsp-ui-doc-hide-frame-on-window-change) + (add-hook 'window-configuration-change-hook #'lsp-ui-doc--hide-frame)) + +(defvar-local lsp-ui-doc--timer-on-changes nil) + +(defun lsp-ui-doc--on-state-changed (_frame &optional on-idle) + (-when-let* ((frame (lsp-ui-doc--get-frame))) + (and (frame-live-p frame) + (frame-visible-p frame) + (not (minibufferp (window-buffer))) + (or (not (eq (selected-window) (frame-parameter frame 'lsp-ui-doc--window-origin))) + (not (eq (window-buffer) (frame-parameter frame 'lsp-ui-doc--buffer-origin)))) + (if on-idle (lsp-ui-doc--hide-frame) + (and (timerp lsp-ui-doc--timer-on-changes) + (cancel-timer lsp-ui-doc--timer-on-changes)) + (setq lsp-ui-doc--timer-on-changes + (run-with-idle-timer 0 nil (lambda nil (lsp-ui-doc--on-state-changed frame t)))))))) + +(advice-add 'load-theme :before (lambda (&rest _) (lsp-ui-doc--delete-frame))) + +(advice-add #'keyboard-quit :before #'lsp-ui-doc--hide-frame) + +(defun lsp-ui-doc--on-delete (frame) + "Function called when a FRAME is deleted." + (-some--> (frame-parameter frame 'lsp-ui-doc-buffer) + (get-buffer it) + (and (buffer-live-p it) it) + (kill-buffer it))) + +(defun lsp-ui-doc--handle-scroll (win _new-start) + "Handle scrolling to the document frame. + +This function is apply to hook `window-scroll-functions'. + +Argument WIN is current applying window." + (let ((frame (lsp-ui-doc--get-frame))) + (if (minibufferp (window-buffer)) + (lsp-ui-doc--hide-frame) + (when (and frame + (eq lsp-ui-doc-position 'at-point) + (frame-visible-p frame) + (eq win (selected-window))) ; This resolved #524 + (if (and lsp-ui-doc--bounds + (eq (window-buffer) (frame-parameter frame 'lsp-ui-doc--buffer-origin)) + (>= (point) (car lsp-ui-doc--bounds)) + (<= (point) (cdr lsp-ui-doc--bounds))) + (lsp-ui-doc--move-frame frame) + ;; The point might have changed if the window was scrolled + ;; too far + (lsp-ui-doc--hide-frame)))))) + +(defvar-local lsp-ui-doc--timer-mouse-movement nil) +(defvar-local lsp-ui-doc--last-event nil) + +(defun lsp-ui-doc--mouse-display nil + (when (and lsp-ui-doc--last-event + (lsp-feature? "textDocument/hover")) + (save-excursion + (goto-char lsp-ui-doc--last-event) + (-when-let* ((valid (not (eolp))) + (bounds (or (and (symbol-at-point) (bounds-of-thing-at-point 'symbol)) + (and (looking-at "[[:graph:]]") (cons (point) (1+ (point))))))) + (unless (equal bounds lsp-ui-doc--bounds) + (lsp-request-async + "textDocument/hover" + (lsp--text-document-position-params) + (lambda (hover) + (save-excursion + (goto-char lsp-ui-doc--last-event) + (let ((lsp-ui-doc-position 'at-point) + (lsp-ui-doc--from-mouse-current t)) + (lsp-ui-doc--callback hover bounds (current-buffer))))) + :mode 'tick + :cancel-token :lsp-ui-doc-hover)))))) + +(defun lsp-ui-doc--handle-mouse-movement (event) + "Show the documentation corresponding to the text under EVENT." + (interactive "e") + (when lsp-ui-doc-show-with-mouse + (and (timerp lsp-ui-doc--timer-mouse-movement) + (cancel-timer lsp-ui-doc--timer-mouse-movement)) + (let* ((e (cadr event)) + (point (posn-point e)) + (same-win (eq (selected-window) (posn-window e)))) + (and lsp-ui-doc--from-mouse + lsp-ui-doc--bounds + point + (or (< point (car lsp-ui-doc--bounds)) + (> point (cdr lsp-ui-doc--bounds)) + (not same-win) + (equal (char-after point) ?\n)) + (lsp-ui-doc--hide-frame)) + (when same-win + (setq lsp-ui-doc--last-event point + lsp-ui-doc--timer-mouse-movement + (run-with-idle-timer lsp-ui-doc-delay nil 'lsp-ui-doc--mouse-display)))))) + +(defun lsp-ui-doc--disable-mouse-on-prefix nil + (and (bound-and-true-p lsp-ui-doc-mode) + (bound-and-true-p lsp-ui-doc--mouse-tracked-by-us) + track-mouse + (> (length (this-single-command-keys)) 0) + (setq-local track-mouse nil))) + +(defvar lsp-ui-doc--timer-mouse-idle nil) + +(defvar-local lsp-ui-doc--mouse-tracked-by-us nil + "Nil if `track-mouse' was set by another package. +If nil, do not prevent mouse on prefix keys.") + +(defun lsp-ui-doc--setup-mouse nil + (when lsp-ui-doc-show-with-mouse + (setq lsp-ui-doc--mouse-tracked-by-us (not track-mouse)) + (setq-local track-mouse t) + (unless lsp-ui-doc--timer-mouse-idle + ;; Set only 1 timer for all buffers + (setq lsp-ui-doc--timer-mouse-idle + (run-with-idle-timer 0 t 'lsp-ui-doc--disable-mouse-on-prefix))))) + +(defun lsp-ui-doc--prevent-focus-doc (e) + (not (frame-parameter (cadr e) 'lsp-ui-doc--no-focus))) + +(define-minor-mode lsp-ui-doc-mode + "Minor mode for showing hover information in child frame." + :init-value nil + :keymap `((,(kbd "<mouse-movement>") . lsp-ui-doc--handle-mouse-movement)) + :group lsp-ui-doc + (cond + (lsp-ui-doc-mode + (with-eval-after-load 'frameset + ;; The documentation frame can’t be properly restored. Especially + ;; ‘desktop-save’ will misbehave and save a bogus string "Unprintable + ;; entity" in the desktop file. Therefore we have to prevent + ;; ‘frameset-save’ from saving the parameter. + (unless (assq 'lsp-ui-doc-frame frameset-filter-alist) + ;; Copy the variable first. See the documentation of + ;; ‘frameset-filter-alist’ for explanation. + (cl-callf copy-tree frameset-filter-alist) + (push '(lsp-ui-doc-frame . :never) frameset-filter-alist))) + (when (boundp 'window-state-change-functions) + (add-hook 'window-state-change-functions 'lsp-ui-doc--on-state-changed)) + (lsp-ui-doc--setup-mouse) + (advice-add 'handle-switch-frame :before-while 'lsp-ui-doc--prevent-focus-doc) + (add-hook 'post-command-hook 'lsp-ui-doc--make-request nil t) + (add-hook 'window-scroll-functions 'lsp-ui-doc--handle-scroll nil t) + (add-hook 'delete-frame-functions 'lsp-ui-doc--on-delete nil t)) + (t + (lsp-ui-doc-hide) + (when (boundp 'window-state-change-functions) + (remove-hook 'window-state-change-functions 'lsp-ui-doc--on-state-changed)) + (remove-hook 'window-scroll-functions 'lsp-ui-doc--handle-scroll t) + (remove-hook 'post-command-hook 'lsp-ui-doc--make-request t) + (remove-hook 'delete-frame-functions 'lsp-ui-doc--on-delete t)))) + +(defun lsp-ui-doc-enable (enable) + "Enable/disable ‘lsp-ui-doc-mode’. +It is supposed to be called from `lsp-ui--toggle'" + (lsp-ui-doc-mode (if enable 1 -1))) + +(defun lsp-ui-doc-show () + "Trigger display hover information popup." + (interactive) + (lsp-ui-doc--callback (lsp-request "textDocument/hover" (lsp--text-document-position-params)) + (or (bounds-of-thing-at-point 'symbol) (cons (point) (1+ (point)))) + (current-buffer))) + +(defun lsp-ui-doc-hide () + "Hide hover information popup." + (interactive) + (lsp-ui-doc--hide-frame)) + +(defvar-local lsp-ui-doc--unfocus-frame-timer nil) +(defun lsp-ui-doc--glance-hide-frame () + "Hook to hide hover information popup for `lsp-ui-doc-glance'." + (when (or (overlayp lsp-ui-doc--inline-ov) + (lsp-ui-doc--frame-visible-p)) + (lsp-ui-doc--hide-frame) + (remove-hook 'post-command-hook 'lsp-ui-doc--glance-hide-frame) + ;; make sure child frame is unfocused + (setq lsp-ui-doc--unfocus-frame-timer + (run-at-time 1 nil #'lsp-ui-doc-unfocus-frame)))) + +(defun lsp-ui-doc-glance () + "Trigger display hover information popup and hide it on next typing." + (interactive) + (let ((lsp-ui-doc-show-with-cursor t)) + (lsp-ui-doc--make-request)) + (when lsp-ui-doc--unfocus-frame-timer + (cancel-timer lsp-ui-doc--unfocus-frame-timer)) + (add-hook 'post-command-hook 'lsp-ui-doc--glance-hide-frame)) + +(define-minor-mode lsp-ui-doc-frame-mode + "Marker mode to add additional key bind for lsp-ui-doc-frame." + :init-value nil + :lighter "" + :group lsp-ui-doc + :keymap `(([?q] . lsp-ui-doc-unfocus-frame) + ([remap markdown-follow-thing-at-point] . lsp-ui-doc--open-markdown-link) + ([remap mouse-drag-region] . ignore))) + +(defun lsp-ui-doc-focus-frame () + "Focus into lsp-ui-doc-frame." + (interactive) + (when-let* ((frame (lsp-ui-doc--get-frame)) + (visible (lsp-ui-doc--frame-visible-p))) + (set-frame-parameter frame 'lsp-ui-doc--no-focus nil) + (set-frame-parameter frame 'cursor-type t) + (lsp-ui-doc--with-buffer + (setq cursor-type 'box)) + (select-frame-set-input-focus frame))) + +(defun lsp-ui-doc-unfocus-frame () + "Unfocus from lsp-ui-doc-frame." + (interactive) + (-some-> (frame-parent) select-frame-set-input-focus) + (when-let* ((frame (lsp-ui-doc--get-frame))) + (set-frame-parameter frame 'lsp-ui-doc--no-focus t) + (set-frame-parameter frame 'cursor-type nil) + (lsp-ui-doc--with-buffer + (setq cursor-type nil)) + (when lsp-ui-doc--from-mouse + (make-frame-invisible frame)))) + +(provide 'lsp-ui-doc) +;;; lsp-ui-doc.el ends here |