From b8889cfa060bd8b742a9e65643071ccfe03fb68a Mon Sep 17 00:00:00 2001 From: Daneel Date: Sun, 22 Feb 2026 23:11:35 +0100 Subject: [PATCH] fix: magnifier buffer-switch + cursor visibility (architect-critic-coder pipeline) --- config.el | 137 ++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 87 insertions(+), 50 deletions(-) diff --git a/config.el b/config.el index 76754b4..e967c21 100644 --- a/config.el +++ b/config.el @@ -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))