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
;;
;; Funkce:
;; - Sleduje cursor v aktivním okně (post-command-hook)
;; - Přepíná automaticky při změně bufferu/okna (buffer switch)
;; - Sleduje cursor v aktivním okně (post-command-hook + buffer-list-update-hook)
;; - Přepíná automaticky při změně bufferu/okna
;; - SPC z + / SPC z = zoom in (jen magnifier, ne globálně)
;; - SPC z - zoom out (jen magnifier)
;; - SPC z 0 reset zoom magnifieru na výchozí
;; - SPC z m toggle on/off
;; - Všechny zoom příkazy jsou no-op pokud magnifier vypnut
;; - Kurzor v magnifier pane skrytý (cursor-type nil)
;; - Magnifier pane je read-only zobrazení (žádné edit v indirect buf)
;; - Správné cleanup při window-configuration-change
;; - Kurzor v magnifier pane viditelný (hollow cursor v non-selected windows)
;; - Magnifier pane je dedicated — Emacs do něj neotevírá jiné buffery
;; - Správné cleanup při window close, buffer kill, workspace switch
(defvar my/mag--active nil "Non-nil when split magnifier is enabled.")
(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))
(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 ()
"Switch magnifier to track the current buffer."
(cl-block my/mag--switch-source
(let* ((new-source (current-buffer))
(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))
(let ((new-source (window-buffer (selected-window))))
(when (my/mag--valid-source-p new-source)
(my/mag--kill-indirect)
(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))
(set-window-dedicated-p my/mag--window nil) ; temporarily un-dedicate
(set-window-buffer my/mag--window my/mag--buffer)
(with-selected-window my/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)))))))
(my/mag--setup-mag-window)))))
(defun my/mag--sync ()
"Sync magnified pane to current cursor position."
(when my/mag--active
;; Skip when minibuffer is active (M-x, vertico, which-key, etc.)
(when (or (active-minibuffer-window)
(window-minibuffer-p))
(cl-return-from my/mag--sync))
;; Ignore if we're in the magnifier pane itself
(when (eq (selected-window) my/mag--window)
(cl-return-from my/mag--sync))
;; Check magnifier window is still alive
(unless (and my/mag--window (window-live-p my/mag--window))
(cl-return-from my/mag--sync))
;; Switch source if buffer changed
(unless (eq (current-buffer) my/mag--source)
(my/mag--switch-source))
;; Sync point + recenter
(when (and my/mag--buffer (buffer-live-p my/mag--buffer))
(let ((pt (point)))
(with-selected-window my/mag--window
(goto-char pt)
(recenter))))))
(when (and my/mag--active
my/mag--window (window-live-p my/mag--window)
(not (active-minibuffer-window))
(not (window-minibuffer-p))
(not (eq (selected-window) my/mag--window))
(not (window-parameter (selected-window) 'window-side)))
(let ((cur-buf (window-buffer (selected-window))))
;; Switch source if buffer changed (only for valid buffers)
(when (and (not (eq cur-buf my/mag--source))
(my/mag--valid-source-p cur-buf))
(my/mag--switch-source))
;; Sync point + recenter
(when (and my/mag--buffer (buffer-live-p my/mag--buffer))
(let ((pt (window-point (selected-window))))
(with-selected-window my/mag--window
(goto-char pt)
(recenter)))))))
(defun my/mag--on-window-change ()
"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))))
(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 ()
"Enable split-screen magnifier."
(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)))
'right))
(set-window-buffer my/mag--window my/mag--buffer)
(with-selected-window my/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)))
(my/mag--setup-mag-window)
(setq my/mag--active t)
(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 '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))
(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 ()
"Disable split-screen magnifier."
(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 '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))
(set-window-dedicated-p my/mag--window nil)
(delete-window my/mag--window))
;; Kill all *mag:* buffers
(dolist (buf (buffer-list))