fix: magnifier buffer-switch + cursor visibility (architect-critic-coder pipeline)

This commit is contained in:
2026-02-22 23:11:35 +01:00
parent d3747f71c4
commit b8889cfa06

137
config.el
View File

@@ -824,16 +824,15 @@ Keeps the status bar and tab bar fully visible at any zoom level.")
;; RIGHT pane (60%): zvětšený pohled — indirect buffer, sleduje cursor ;; RIGHT pane (60%): zvětšený pohled — indirect buffer, sleduje cursor
;; ;;
;; Funkce: ;; Funkce:
;; - Sleduje cursor v aktivním okně (post-command-hook) ;; - Sleduje cursor v aktivním okně (post-command-hook + buffer-list-update-hook)
;; - Přepíná automaticky při změně bufferu/okna (buffer switch) ;; - Přepíná automaticky při změně bufferu/okna
;; - SPC z + / SPC z = zoom in (jen magnifier, ne globálně) ;; - SPC z + / SPC z = zoom in (jen magnifier, ne globálně)
;; - SPC z - zoom out (jen magnifier) ;; - SPC z - zoom out (jen magnifier)
;; - SPC z 0 reset zoom magnifieru na výchozí ;; - SPC z 0 reset zoom magnifieru na výchozí
;; - SPC z m toggle on/off ;; - SPC z m toggle on/off
;; - Všechny zoom příkazy jsou no-op pokud magnifier vypnut ;; - Kurzor v magnifier pane viditelný (hollow cursor v non-selected windows)
;; - Kurzor v magnifier pane skrytý (cursor-type nil) ;; - Magnifier pane je dedicated — Emacs do něj neotevírá jiné buffery
;; - Magnifier pane je read-only zobrazení (žádné edit v indirect buf) ;; - Správné cleanup při window close, buffer kill, workspace switch
;; - Správné cleanup při window-configuration-change
(defvar my/mag--active nil "Non-nil when split magnifier is enabled.") (defvar my/mag--active nil "Non-nil when split magnifier is enabled.")
(defvar my/mag--window nil "The magnified (right) window.") (defvar my/mag--window nil "The magnified (right) window.")
@@ -848,51 +847,64 @@ Keeps the status bar and tab bar fully visible at any zoom level.")
(kill-buffer my/mag--buffer)) (kill-buffer my/mag--buffer))
(setq my/mag--buffer nil)) (setq my/mag--buffer nil))
(defun my/mag--setup-mag-window ()
"Configure magnifier window display settings.
Call after `set-window-buffer' on `my/mag--window'."
(when (and my/mag--window (window-live-p my/mag--window))
(set-window-dedicated-p my/mag--window t)
(set-window-parameter my/mag--window 'no-other-window t)
(with-selected-window my/mag--window
(text-scale-set my/mag--zoom-level)
(setq-local cursor-type 'box)
(setq-local cursor-in-non-selected-windows 'hollow)
(setq-local scroll-margin 0)
(when (bound-and-true-p display-line-numbers-mode)
(display-line-numbers-mode -1))
(when (bound-and-true-p hl-line-mode)
(hl-line-mode -1)))))
(defun my/mag--valid-source-p (buf)
"Return non-nil if BUF is a valid magnifier source buffer."
(and (buffer-live-p buf)
(not (minibufferp buf))
(not (string-prefix-p " " (buffer-name buf)))
(not (string-prefix-p "*mag:" (buffer-name buf)))))
(defun my/mag--switch-source () (defun my/mag--switch-source ()
"Switch magnifier to track the current buffer." "Switch magnifier to track the current buffer."
(cl-block my/mag--switch-source (let ((new-source (window-buffer (selected-window))))
(let* ((new-source (current-buffer)) (when (my/mag--valid-source-p new-source)
(mag-name (format "*mag:%s*" (buffer-name new-source))))
;; Don't switch to transient/minibuffer buffers
(when (or (minibufferp new-source)
(string-prefix-p " " (buffer-name new-source)))
(cl-return-from my/mag--switch-source))
(my/mag--kill-indirect) (my/mag--kill-indirect)
(setq my/mag--source new-source) (setq my/mag--source new-source)
(setq my/mag--buffer (make-indirect-buffer new-source mag-name t)) (let ((mag-name (format "*mag:%s*" (buffer-name new-source))))
;; Clean up stale buffer with same name
(when-let ((old (get-buffer mag-name)))
(kill-buffer old))
(setq my/mag--buffer (make-indirect-buffer new-source mag-name t)))
(when (and my/mag--window (window-live-p my/mag--window)) (when (and my/mag--window (window-live-p my/mag--window))
(set-window-dedicated-p my/mag--window nil) ; temporarily un-dedicate
(set-window-buffer my/mag--window my/mag--buffer) (set-window-buffer my/mag--window my/mag--buffer)
(with-selected-window my/mag--window (my/mag--setup-mag-window)))))
(text-scale-set my/mag--zoom-level)
(setq-local cursor-type 'box)
(setq-local scroll-margin 0)
(when (bound-and-true-p display-line-numbers-mode)
(display-line-numbers-mode -1))
(when (bound-and-true-p hl-line-mode)
(hl-line-mode -1)))))))
(defun my/mag--sync () (defun my/mag--sync ()
"Sync magnified pane to current cursor position." "Sync magnified pane to current cursor position."
(when my/mag--active (when (and my/mag--active
;; Skip when minibuffer is active (M-x, vertico, which-key, etc.) my/mag--window (window-live-p my/mag--window)
(when (or (active-minibuffer-window) (not (active-minibuffer-window))
(window-minibuffer-p)) (not (window-minibuffer-p))
(cl-return-from my/mag--sync)) (not (eq (selected-window) my/mag--window))
;; Ignore if we're in the magnifier pane itself (not (window-parameter (selected-window) 'window-side)))
(when (eq (selected-window) my/mag--window) (let ((cur-buf (window-buffer (selected-window))))
(cl-return-from my/mag--sync)) ;; Switch source if buffer changed (only for valid buffers)
;; Check magnifier window is still alive (when (and (not (eq cur-buf my/mag--source))
(unless (and my/mag--window (window-live-p my/mag--window)) (my/mag--valid-source-p cur-buf))
(cl-return-from my/mag--sync)) (my/mag--switch-source))
;; Switch source if buffer changed ;; Sync point + recenter
(unless (eq (current-buffer) my/mag--source) (when (and my/mag--buffer (buffer-live-p my/mag--buffer))
(my/mag--switch-source)) (let ((pt (window-point (selected-window))))
;; Sync point + recenter (with-selected-window my/mag--window
(when (and my/mag--buffer (buffer-live-p my/mag--buffer)) (goto-char pt)
(let ((pt (point))) (recenter)))))))
(with-selected-window my/mag--window
(goto-char pt)
(recenter))))))
(defun my/mag--on-window-change () (defun my/mag--on-window-change ()
"Clean up if magnifier window was closed by user." "Clean up if magnifier window was closed by user."
@@ -901,6 +913,23 @@ Keeps the status bar and tab bar fully visible at any zoom level.")
(not (and my/mag--window (window-live-p my/mag--window)))) (not (and my/mag--window (window-live-p my/mag--window))))
(my/mag--disable))) (my/mag--disable)))
(defun my/mag--on-source-killed ()
"Handle source buffer being killed."
(when (and my/mag--active
(eq (current-buffer) my/mag--source))
;; Try to switch to another visible buffer, or disable
(let ((alt (cl-find-if
(lambda (b)
(and (not (eq b (current-buffer)))
(my/mag--valid-source-p b)))
(buffer-list))))
(if alt
(progn
(set-window-buffer (selected-window) alt)
(setq my/mag--source nil) ; force switch
(my/mag--switch-source))
(my/mag--disable)))))
(defun my/mag--enable () (defun my/mag--enable ()
"Enable split-screen magnifier." "Enable split-screen magnifier."
(delete-other-windows) (delete-other-windows)
@@ -916,24 +945,32 @@ Keeps the status bar and tab bar fully visible at any zoom level.")
(floor (* 0.4 (window-total-width))) (floor (* 0.4 (window-total-width)))
'right)) 'right))
(set-window-buffer my/mag--window my/mag--buffer) (set-window-buffer my/mag--window my/mag--buffer)
(with-selected-window my/mag--window (my/mag--setup-mag-window)
(text-scale-set my/mag--zoom-level)
(setq-local cursor-type 'box)
(setq-local scroll-margin 0)
(when (bound-and-true-p display-line-numbers-mode)
(display-line-numbers-mode -1))
(when (bound-and-true-p hl-line-mode)
(hl-line-mode -1)))
(setq my/mag--active t) (setq my/mag--active t)
(add-hook 'post-command-hook #'my/mag--sync) (add-hook 'post-command-hook #'my/mag--sync)
(add-hook 'buffer-list-update-hook #'my/mag--sync)
(add-hook 'window-configuration-change-hook #'my/mag--on-window-change) (add-hook 'window-configuration-change-hook #'my/mag--on-window-change)
(add-hook 'kill-buffer-hook #'my/mag--on-source-killed)
(when (boundp 'persp-activated-functions)
(add-hook 'persp-activated-functions #'my/mag--on-persp-change))
(message "Split magnifier ON (zoom %+d)" my/mag--zoom-level)) (message "Split magnifier ON (zoom %+d)" my/mag--zoom-level))
(defun my/mag--on-persp-change (&rest _)
"Disable magnifier if window is dead after workspace switch."
(when (and my/mag--active
(not (window-live-p my/mag--window)))
(my/mag--disable)))
(defun my/mag--disable () (defun my/mag--disable ()
"Disable split-screen magnifier." "Disable split-screen magnifier."
(remove-hook 'post-command-hook #'my/mag--sync) (remove-hook 'post-command-hook #'my/mag--sync)
(remove-hook 'buffer-list-update-hook #'my/mag--sync)
(remove-hook 'window-configuration-change-hook #'my/mag--on-window-change) (remove-hook 'window-configuration-change-hook #'my/mag--on-window-change)
(remove-hook 'kill-buffer-hook #'my/mag--on-source-killed)
(when (boundp 'persp-activated-functions)
(remove-hook 'persp-activated-functions #'my/mag--on-persp-change))
(when (and my/mag--window (window-live-p my/mag--window)) (when (and my/mag--window (window-live-p my/mag--window))
(set-window-dedicated-p my/mag--window nil)
(delete-window my/mag--window)) (delete-window my/mag--window))
;; Kill all *mag:* buffers ;; Kill all *mag:* buffers
(dolist (buf (buffer-list)) (dolist (buf (buffer-list))