Subject: [PATCH] Re: bug#70193: Acknowledgement (eglot: RFE: recenter buffer upon showDocument request)

From: Felician Nemeth <[email-masked]>
Date: Wed, 17 Apr 2024 11:15:25 +0200

Hi João,

I've attached a patch fixing this issue.  It includes Martin's new
recenter-region.

This is the original report:

> In eglot 1.17, the LSP showDocument downcall opens the designated
> file, moves the cursor to the designated position, and raises the
> frame. One other thing it could do to make it easier to see where the
> cursor is would be to recenter the buffer.
> 
> The patch below is a minimal fix; the discussion at
> https://github.com/joaotavora/eglot/discussions/1382 suggests a couple
> of possible refinements.

Thanks.

>From 9496ee48c77f60c17387b4331911ba7ad5642c0e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Felici=C3=A1n=20N=C3=A9meth?= <[email-masked]>
Date: Wed, 17 Apr 2024 10:47:03 +0200
Subject: [PATCH] Eglot: Recenter buffer upon showDocument request (bug#70193)

* lisp/progmodes/eglot.el (eglot--window-recenter-region): New
defun.
(eglot-handle-request window/showDocument): Try to show the whole
selection centered; in prog modes, try to show preceding comments
as well.

Co-authored-by: Martin Rudalics <[email-masked]>
---
 lisp/progmodes/eglot.el | 91 +++++++++++++++++++++++++++++++++++++++--
 1 file changed, 87 insertions(+), 4 deletions(-)

diff --git a/lisp/progmodes/eglot.el b/lisp/progmodes/eglot.el
index 5e4f7bba679..cbadd495b28 100644
--- a/lisp/progmodes/eglot.el
+++ b/lisp/progmodes/eglot.el
@@ -2488,6 +2488,76 @@ eglot-handle-request
   "Handle server request workspace/workspaceFolders."
   (eglot-workspace-folders server))
 
+(defun eglot--window-recenter-region (&optional from to window buffer)
+  "Center region and return window start position for recentered region.
+WINDOW specifies the window whose start position to return and defaults
+to the selected window.  BUFFER specifies the buffer supposed to contain
+the region and defaults to WINDOW's buffer. FROM and TO specify the
+beginning and end of the region in BUFFER and default to the active
+region or the entire text of BUFFER."
+  (let* ((window (or window (selected-window)))
+	 (buffer (or buffer (window-buffer window)))
+	 (from (or from
+		   (and (eq buffer (current-buffer))
+			(region-active-p)
+			(region-beginning))
+		   (with-current-buffer buffer
+		     (point-min))))
+	 (to (or to
+		 (and (eq buffer (current-buffer))
+		      (region-active-p)
+		      (region-end))
+		 (with-current-buffer buffer
+		   (point-max))))
+	 (body-width (window-body-width window t))
+	 (body-height (window-body-height window t))
+	 old-buffer old-point
+	 remainder height start prev)
+    ;; If WINDOW doesn't show BUFFER, save its buffer, start and point
+    ;; positions.
+    (unless (eq (window-buffer window) buffer)
+      (setq old-buffer (window-buffer window))
+      (setq old-point (window-point window))
+      (set-window-buffer window buffer))
+
+    ;; The amount of pixels by which the region can be scrolled down in
+    ;; the window.  Initially, half of the difference of the height of
+    ;; the window's body and that of the region.
+    (setq remainder
+	  (round
+	   (/ (- body-height
+		 (cdr (window-text-pixel-size
+		       window from to body-width body-height)))
+	      2.0)))
+
+    ;; Now move back and subtract the height of one line preceding the
+    ;; region until the remainder has been used.
+    (save-excursion
+      (goto-char from)
+      (setq start (pos-bol))
+      (while (and (not (bobp))
+		  (>= remainder 0)
+		  (setq prev (pos-bol 0))
+		  (setq height (cdr (window-text-pixel-size
+				   window prev start
+				   body-width body-height)))
+		  ;; At least half of the line should fit.
+		  (>= remainder (/ height 2.0)))
+	(setq remainder (- remainder height))
+	(setq start prev)
+	(goto-char start)))
+
+    ;; Restore WINDOW's old buffer, start and point.
+    (when old-buffer
+      (set-window-buffer window old-buffer)
+      (set-window-point window old-point)
+      (set-window-start window old-point))
+
+    (set-window-start window start)
+
+    ;; Return the new start position.
+    start))
+
 (cl-defmethod eglot-handle-request
   (_server (_method (eql window/showDocument)) &key
            uri external takeFocus selection)
@@ -2510,10 +2580,23 @@ eglot-handle-request
                  ((display-buffer (current-buffer))))
            (when selection
              (pcase-let ((`(,beg . ,end) (eglot-range-region selection)))
-               ;; FIXME: it is very naughty to use someone else's `--'
-               ;; function, but `xref--goto-char' happens to have
-               ;; exactly the semantics we want vis-a-vis widening.
-               (xref--goto-char beg)
+               (with-selected-window (get-buffer-window (current-buffer))
+                 ;; FIXME: it is very naughty to use someone else's `--'
+                 ;; function, but `xref--goto-char' happens to have
+                 ;; exactly the semantics we want vis-a-vis widening.
+                 (xref--goto-char end)
+                 (xref--goto-char beg)
+                 (eglot--window-recenter-region beg end)
+
+                 (when (derived-mode-p 'prog-mode)
+                   (let ((recentered-visible (pos-visible-in-window-p end))
+                         (recentered-start (window-start)))
+                     (reposition-window) ; Try to show preceding comments.
+                     (when (and recentered-visible
+                                (not (pos-visible-in-window-p end)))
+                       ;; reposition-window ruined visibility of selection.
+                       (set-window-start nil recentered-start)))))
+
                (pulse-momentary-highlight-region beg end 'highlight)))))))
      (t (setq success :json-false)))
     `(:success ,success)))
-- 
2.39.2