From 8a8ac9ec56c1999fa650ed094e87ba2816b6a4b5 Mon Sep 17 00:00:00 2001 From: Eric Dallo <ericdallo06@hotmail.com> Date: Sat, 1 Aug 2020 15:23:11 -0300 Subject: [PATCH 1/6] Add posframe integration to lsp-ui-doc --- Cask | 1 + lsp-ui-doc.el | 183 +++++++++++++++++++++++++------------------------- lsp-ui.el | 2 +- 3 files changed, 92 insertions(+), 94 deletions(-) diff --git a/Cask b/Cask index 22f56301..d7354324 100644 --- a/Cask +++ b/Cask @@ -7,6 +7,7 @@ (depends-on "lsp-mode") (depends-on "markdown-mode") (depends-on "rustic") +(depends-on "posframe") (package-file "lsp-ui.el") (files "*.el" "lsp-ui-doc.html") diff --git a/lsp-ui-doc.el b/lsp-ui-doc.el index 66a9a383..a9c70034 100644 --- a/lsp-ui-doc.el +++ b/lsp-ui-doc.el @@ -37,6 +37,7 @@ (require 'goto-addr) (require 'markdown-mode) (require 'cl-lib) +(require 'posframe) (when (featurep 'xwidget-internal) (require 'xwidget)) @@ -144,29 +145,14 @@ Only the `background' is used in this face." :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) + '((no-focus-on-map . t) (vertical-scroll-bars . nil) (horizontal-scroll-bars . nil) - (right-fringe . 0) - (menu-bar-lines . 0) - (tool-bar-lines . 0) - (line-spacing . 0) - (unsplittable . t) - (undecorated . t) - (top . -1) - (visibility . nil) (mouse-wheel-frame . nil) - (no-other-frame . t) + (no-accept-focus . nil) (inhibit-double-buffering . t) - (drag-internal-border . t) - (no-special-glyphs . t) - (desktop-dont-save . t)) + (cursor-type . box) + (drag-internal-border . t)) "Frame parameters used to create the frame.") (defvar lsp-ui-doc-render-function nil @@ -365,10 +351,7 @@ We don't extract the string that `lps-line' is already displaying." (when (overlayp lsp-ui-doc--inline-ov) (delete-overlay lsp-ui-doc--inline-ov)) (when (lsp-ui-doc--get-frame) - (unless lsp-ui-doc-use-webkit - (lsp-ui-doc--with-buffer - (erase-buffer))) - (make-frame-invisible (lsp-ui-doc--get-frame)))) + (posframe-hide (lsp-ui-doc--make-buffer-name)))) (defun lsp-ui-doc--buffer-width () "Calcul the max width of the buffer." @@ -403,19 +386,6 @@ We don't extract the string that `lps-line' is already displaying." (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--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 (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 (frame width height start-x start-y) "Move FRAME to be where the point is. WIDTH is the child frame width. @@ -459,12 +429,11 @@ FRAME just below the symbol at point." (if (eq lsp-ui-doc-position 'at-point) (lsp-ui-doc--mv-at-point frame width height left top) (set-frame-position frame - (max (- frame-right width 10 (frame-char-width)) 10) + (max (- frame-right width) 0) (pcase lsp-ui-doc-position - ('top (+ top 10)) + ('top top) ('bottom (- (lsp-ui-doc--line-height 'mode-line) - height - 10))))))) + height))))))) (defun lsp-ui-doc--visit-file (filename) "Visit FILENAME in the parent frame." @@ -495,26 +464,31 @@ FN is the function to call on click." (lsp-ui-doc--put-click (match-beginning 0) (match-end 0) 'browse-url-at-mouse))))) -(defun lsp-ui-doc--render-buffer (string symbol) - "Set the buffer with STRING." +(defvar lsp-ui-doc--render-string nil + "The string to render in the documentation popup.") +(defvar lsp-ui-doc--render-symbol nil + "The symbol to render documentation for.") + +(defun lsp-ui-doc--render-buffer () + "Set the buffer with `lsp-ui-doc--render-string'." (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--render-symbol + (url-hexify-string lsp-ui-doc--render-string)) 'lsp-ui-doc--webkit-resize-callback)) (erase-buffer) (let ((inline-p (lsp-ui-doc--inline-p))) (insert (concat (unless inline-p (propertize "\n" 'face '(:height 0.2))) - (s-trim string) + (s-trim lsp-ui-doc--render-string) (unless inline-p (propertize "\n\n" 'face '(:height 0.3)))))) (lsp-ui-doc--make-clickable-link)) (setq-local face-remapping-alist `((header-line lsp-ui-doc-header))) (setq-local window-min-height 1) - (setq header-line-format (when lsp-ui-doc-header (concat " " symbol)) + (setq header-line-format (when lsp-ui-doc-header (concat " " lsp-ui-doc--render-symbol)) mode-line-format nil cursor-type nil))) @@ -619,49 +593,64 @@ HEIGHT is the documentation number of lines." (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 (posframe-workable-p)) (not (fboundp 'display-buffer-in-child-frame)))) (defun lsp-ui-doc--display (symbol string) - "Display the documentation." + "Display the documentation for SYMBOL and STRING." + (setq lsp-ui-doc--render-symbol symbol + lsp-ui-doc--render-string string) (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--render-buffer string symbol) + (lsp-ui-doc--render-buffer) (if (lsp-ui-doc--inline-p) (lsp-ui-doc--inline) - (unless (lsp-ui-doc--get-frame) + (when (or (not lsp-ui-doc-use-webkit) + (not (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))) (unless (frame-visible-p (lsp-ui-doc--get-frame)) (make-frame-visible (lsp-ui-doc--get-frame)))))) +(defun lsp-ui-doc--posframe-poshandler-point-top-left-corner (info) + "Place the posframe at the top-left corner of the point without covering it. +The structure of INFO is defined in the documentation of `posframe-show'." + (let* ((frame (plist-get info :posframe)) + (height (frame-pixel-height frame))) + (posframe-poshandler-point-bottom-left-corner info (- height)))) + (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)) + (let* ((before-make-frame-hook nil) + (buffer-name (lsp-ui-doc--make-buffer-name)) + (buffer (get-buffer-create buffer-name)) (params (append lsp-ui-doc-frame-parameters `((name . "") (default-minibuffer-frame . ,(selected-frame)) - (minibuffer . ,(minibuffer-window)) - (left-fringe . ,(frame-char-width)) - (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))) + (minibuffer . ,(minibuffer-window))))) + (position (pcase (list lsp-ui-doc-position lsp-ui-doc-alignment) + ('(top frame) #'posframe-poshandler-frame-top-right-corner) + ('(top window) #'posframe-poshandler-window-top-right-corner) + ('(bottom frame) #'posframe-poshandler-frame-bottom-right-corner) + ('(bottom window) #'posframe-poshandler-window-bottom-right-corner) + ('(at-point frame) #'lsp-ui-doc--posframe-poshandler-point-top-left-corner) + ('(at-point window) #'lsp-ui-doc--posframe-poshandler-point-top-left-corner))) + (frame (posframe-show buffer + :width lsp-ui-doc-max-width + :height lsp-ui-doc-max-height + :poshandler position + :internal-border-width 1 + :internal-border-color lsp-ui-doc-border + :left-fringe t + :right-fringe t + :background-color (face-background 'lsp-ui-doc-background nil t) + :override-parameters params)) + (window (frame-root-window frame))) (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) + (lsp-ui-doc-frame-mode 1) + (visual-line-mode 1)) (set-face-background 'fringe nil frame) (run-hook-with-args 'lsp-ui-doc-frame-hook frame window) (when lsp-ui-doc-use-webkit @@ -670,6 +659,8 @@ HEIGHT is the documentation number of lines." (interactive) (let ((xwidget-event-type (nth 1 last-input-event))) + (when (eq xwidget-event-type 'load-changed) + (lsp-ui-doc--render-buffer)) ;; (when (eq xwidget-event-type 'load-changed) ;; (lsp-ui-doc--move-frame (lsp-ui-doc--get-frame))) @@ -727,7 +718,7 @@ BUFFER is the buffer where the request has been made." (defun lsp-ui-doc--delete-frame () "Delete the child frame if it exists." (-when-let (frame (lsp-ui-doc--get-frame)) - (delete-frame frame) + (posframe-delete-frame (lsp-ui-doc--make-buffer-name)) (lsp-ui-doc--set-frame nil))) (defun lsp-ui-doc--visible-p () @@ -766,6 +757,27 @@ before, or if the new window is the minibuffer." (and (buffer-live-p it) it) (kill-buffer it))) +(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))) + +(defun lsp-ui-doc-focus-frame () + "Focus into lsp-ui-doc-frame." + (interactive) + (when (lsp-ui-doc--frame-visible-p) + (lsp-ui-doc--with-buffer + (setq cursor-type t)) + (select-frame-set-input-focus (lsp-ui-doc--get-frame)))) + +(defun lsp-ui-doc-unfocus-frame () + "Unfocus from lsp-ui-doc-frame." + (interactive) + (when-let ((frame (frame-parent (lsp-ui-doc--get-frame)))) + (select-frame-set-input-focus frame))) + (define-minor-mode lsp-ui-doc-mode "Minor mode for showing hover information in child frame." :init-value nil @@ -783,11 +795,17 @@ before, or if the new window is the minibuffer." (cl-callf copy-tree frameset-filter-alist) (push '(lsp-ui-doc-frame . :never) frameset-filter-alist))) (add-hook 'post-command-hook 'lsp-ui-doc--make-request nil t) - (add-hook 'delete-frame-functions 'lsp-ui-doc--on-delete nil t)) + (add-hook 'delete-frame-functions 'lsp-ui-doc--on-delete nil t) + (advice-add #'posframe--redirect-posframe-focus + :before-until (lambda (&rest _) + lsp-ui-doc-frame-mode) + '((name . lsp-ui-doc--dont-redirect-posframe)))) (t (lsp-ui-doc-hide) (remove-hook 'post-command-hook 'lsp-ui-doc--make-request t) - (remove-hook 'delete-frame-functions 'lsp-ui-doc--on-delete t)))) + (remove-hook 'delete-frame-functions 'lsp-ui-doc--on-delete t) + (advice-remove #'posframe--redirect-posframe-focus + 'lsp-ui-doc--dont-redirect-posframe)))) (defun lsp-ui-doc-enable (enable) "Enable/disable ‘lsp-ui-doc-mode’. @@ -825,26 +843,5 @@ It is supposed to be called from `lsp-ui--toggle'" (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))) - -(defun lsp-ui-doc-focus-frame () - "Focus into lsp-ui-doc-frame." - (interactive) - (when (lsp-ui-doc--frame-visible-p) - (lsp-ui-doc--with-buffer - (setq cursor-type t)) - (select-frame-set-input-focus (lsp-ui-doc--get-frame)))) - -(defun lsp-ui-doc-unfocus-frame () - "Unfocus from lsp-ui-doc-frame." - (interactive) - (when-let ((frame (frame-parent (lsp-ui-doc--get-frame)))) - (select-frame-set-input-focus frame))) - (provide 'lsp-ui-doc) ;;; lsp-ui-doc.el ends here diff --git a/lsp-ui.el b/lsp-ui.el index b5488875..9a1e3a95 100644 --- a/lsp-ui.el +++ b/lsp-ui.el @@ -6,7 +6,7 @@ ;; Author: Sebastien Chapuis <sebastien@chapu.is>, Fangrui Song <i@maskray.me> ;; Keywords: languages, tools ;; URL: https://github.com/emacs-lsp/lsp-ui -;; Package-Requires: ((emacs "26.1") (dash "2.14") (dash-functional "1.2.0") (lsp-mode "6.0") (markdown-mode "2.3")) +;; Package-Requires: ((emacs "26.1") (dash "2.14") (dash-functional "1.2.0") (lsp-mode "6.0") (markdown-mode "2.3") (posframe "0.7.0")) ;; Version: 7.0 ;;; License From ab97add65c2587a27491f83e119da712eb02ea14 Mon Sep 17 00:00:00 2001 From: Eric Dallo <ericdallo06@hotmail.com> Date: Sat, 1 Aug 2020 17:28:36 -0300 Subject: [PATCH 2/6] Fix hide window when buffer didn't change --- lsp-ui-doc.el | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/lsp-ui-doc.el b/lsp-ui-doc.el index a9c70034..f5ff52d2 100644 --- a/lsp-ui-doc.el +++ b/lsp-ui-doc.el @@ -184,6 +184,8 @@ Because some variables are buffer local.") (defvar-local lsp-ui-doc--bounds nil) (defvar-local lsp-ui-doc--timer nil) +(defvar-local lsp-ui-doc--inline-width nil) + (defconst lsp-ui-doc--buffer-prefix " *lsp-ui-doc-") (defmacro lsp-ui-doc--with-buffer (&rest body) @@ -506,8 +508,6 @@ FN is the function to call on click." (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)) @@ -620,6 +620,8 @@ The structure of INFO is defined in the documentation of `posframe-show'." (height (frame-pixel-height frame))) (posframe-poshandler-point-bottom-left-corner info (- height)))) +(defvar-local lsp-ui-doc--original-buffer-with-frame nil) + (defun lsp-ui-doc--make-frame () "Create the child frame and return it." (lsp-ui-doc--delete-frame) @@ -648,6 +650,7 @@ The structure of INFO is defined in the documentation of `posframe-show'." :background-color (face-background 'lsp-ui-doc-background nil t) :override-parameters params)) (window (frame-root-window frame))) + (setq lsp-ui-doc--original-buffer-with-frame (current-buffer)) (with-current-buffer buffer (lsp-ui-doc-frame-mode 1) (visual-line-mode 1)) @@ -746,7 +749,14 @@ before, or if the new window is the minibuffer." (advice-add #'select-window :around #'lsp-ui-doc-hide-frame-on-window-change) (advice-add 'load-theme :before (lambda (&rest _) (lsp-ui-doc--delete-frame))) -(add-hook 'window-configuration-change-hook #'lsp-ui-doc--hide-frame) + +(defun lsp-ui-doc--hide-checking-buffer () + "Check if buffer has changed before hide hook." + (when (or (null lsp-ui-doc--original-buffer-with-frame) + (not (eq (current-buffer) lsp-ui-doc--original-buffer-with-frame))) + (lsp-ui-doc--hide-frame))) + +(add-hook 'window-configuration-change-hook #'lsp-ui-doc--hide-checking-buffer) (advice-add #'keyboard-quit :before #'lsp-ui-doc--hide-frame) From 9c5b01f995b72aeff43e75c376f17cd6211353e2 Mon Sep 17 00:00:00 2001 From: Eric Dallo <ericdallo06@hotmail.com> Date: Sat, 1 Aug 2020 17:50:29 -0300 Subject: [PATCH 3/6] Avoid mouse focus --- lsp-ui-doc.el | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lsp-ui-doc.el b/lsp-ui-doc.el index f5ff52d2..4d77394a 100644 --- a/lsp-ui-doc.el +++ b/lsp-ui-doc.el @@ -150,6 +150,8 @@ Only the `background' is used in this face." (horizontal-scroll-bars . nil) (mouse-wheel-frame . nil) (no-accept-focus . nil) + (focus-follows-mouse . nil) + (mouse-autoselect-window . nil) (inhibit-double-buffering . t) (cursor-type . box) (drag-internal-border . t)) From 0728d418610ff9e2c904f54665fe781e98fac046 Mon Sep 17 00:00:00 2001 From: Eric Dallo <ericdallo06@hotmail.com> Date: Sat, 1 Aug 2020 17:51:07 -0300 Subject: [PATCH 4/6] Fix performace following https://github.com/emacs-lsp/lsp-ui/pull/465/files --- lsp-ui-doc.el | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lsp-ui-doc.el b/lsp-ui-doc.el index 4d77394a..61c106c4 100644 --- a/lsp-ui-doc.el +++ b/lsp-ui-doc.el @@ -609,8 +609,7 @@ HEIGHT is the documentation number of lines." (lsp-ui-doc--render-buffer) (if (lsp-ui-doc--inline-p) (lsp-ui-doc--inline) - (when (or (not lsp-ui-doc-use-webkit) - (not (lsp-ui-doc--get-frame))) + (unless lsp-ui-doc-use-webkit (lsp-ui-doc--set-frame (lsp-ui-doc--make-frame))) (unless (frame-visible-p (lsp-ui-doc--get-frame)) (make-frame-visible (lsp-ui-doc--get-frame)))))) @@ -626,7 +625,6 @@ The structure of INFO is defined in the documentation of `posframe-show'." (defun lsp-ui-doc--make-frame () "Create the child frame and return it." - (lsp-ui-doc--delete-frame) (let* ((before-make-frame-hook nil) (buffer-name (lsp-ui-doc--make-buffer-name)) (buffer (get-buffer-create buffer-name)) From 96e79b3cbf1ae218224707e8b3710a2e8e7d63c7 Mon Sep 17 00:00:00 2001 From: Eric Dallo <ericdallo06@hotmail.com> Date: Sat, 1 Aug 2020 17:58:13 -0300 Subject: [PATCH 5/6] Fix tests --- test/test-helper.el | 1 + 1 file changed, 1 insertion(+) diff --git a/test/test-helper.el b/test/test-helper.el index 90a3d6fd..3e5ec44e 100644 --- a/test/test-helper.el +++ b/test/test-helper.el @@ -5,6 +5,7 @@ (file-name-as-directory (f-parent (f-parent (f-this-file))))) (require 'lsp-mode) +(require 'lsp-modeline) (require 'lsp-rust) (require 'lsp-ui) (require 'flycheck) From 106816385e9273b6c591b7e57291070ef1fdcc5f Mon Sep 17 00:00:00 2001 From: Eric Dallo <ericdallo06@hotmail.com> Date: Sat, 1 Aug 2020 22:43:42 -0300 Subject: [PATCH 6/6] Make variable global --- lsp-ui-doc.el | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lsp-ui-doc.el b/lsp-ui-doc.el index 61c106c4..3f182989 100644 --- a/lsp-ui-doc.el +++ b/lsp-ui-doc.el @@ -621,7 +621,7 @@ The structure of INFO is defined in the documentation of `posframe-show'." (height (frame-pixel-height frame))) (posframe-poshandler-point-bottom-left-corner info (- height)))) -(defvar-local lsp-ui-doc--original-buffer-with-frame nil) +(defvar lsp-ui-doc--original-buffer-with-frame nil) (defun lsp-ui-doc--make-frame () "Create the child frame and return it."