From 4d0d46d5be9c2f3ce8850d1a5d31b66a25ca8f13 Mon Sep 17 00:00:00 2001 From: Kinneyzhang Date: Fri, 11 Sep 2026 04:26:44 +0800 Subject: [PATCH] fix(display): keep top borders within the mounted line grid --- ebox-state-contract.el | 3 +++ ebox.el | 20 ++++++++++++++++++-- tests/ebox-display-tests.el | 31 +++++++++++++++++++++++++++++++ 3 files changed, 52 insertions(+), 2 deletions(-) diff --git a/ebox-state-contract.el b/ebox-state-contract.el index a646bf1..2f3cf34 100644 --- a/ebox-state-contract.el +++ b/ebox-state-contract.el @@ -403,6 +403,9 @@ rather than relying on an implicit default.") (:symbol ebox-native-status--diagnosis :reason buffer-local-native-status-diagnostic :evidence status-buffer-presentation-only) + (:symbol ebox--saved-overline-margin + :reason buffer-local-native-display-preference + :evidence mode-lifetime-preference-restoration-not-render-generation-state) (:symbol ebox-surface--observation-context :reason dynamically-bound-publication-observation :evidence let-bound-for-one-public-ebox-call) diff --git a/ebox.el b/ebox.el index e7bb883..98635d5 100644 --- a/ebox.el +++ b/ebox.el @@ -4787,14 +4787,30 @@ Examples: (memq (car entry) ebox--scroll-overridden-minor-modes)) minor-mode-overriding-map-alist))) +(defvar-local ebox--saved-overline-margin nil + "Original local-binding flag and value of `overline-margin' while mounted.") + ;;;###autoload (define-minor-mode ebox-buffer-mode "Minor mode for interactive ebox buffers." :lighter " Ebox" :keymap ebox-scroll-map (if ebox-buffer-mode - (ebox--install-scroll-map-overrides) - (ebox--clear-scroll-map-overrides))) + (progn + (unless ebox--saved-overline-margin + (setq ebox--saved-overline-margin + (cons (local-variable-p 'overline-margin) overline-margin))) + ;; Native overlines otherwise add ascent to whichever row currently + ;; contains a top border. Paint inside the measured line grid so + ;; moving a positioned border cannot move the surrounding rows. + (setq-local overline-margin 0) + (ebox--install-scroll-map-overrides)) + (ebox--clear-scroll-map-overrides) + (when ebox--saved-overline-margin + (if (car ebox--saved-overline-margin) + (setq-local overline-margin (cdr ebox--saved-overline-margin)) + (kill-local-variable 'overline-margin)) + (setq ebox--saved-overline-margin nil)))) (defun ebox--render-observer-option (options) "Validate OPTIONS and return `(PRESENT . OBSERVER)'." diff --git a/tests/ebox-display-tests.el b/tests/ebox-display-tests.el index 9324158..d254fc7 100644 --- a/tests/ebox-display-tests.el +++ b/tests/ebox-display-tests.el @@ -5,6 +5,37 @@ (require 'ert) (require 'ebox) +(ert-deftest ebox-display-border-metrics-restore-on-unmount () + "Mounted borders keep the line grid without leaking redisplay settings." + (let ((default-margin (default-value 'overline-margin))) + (dolist (local-margin '(nil 7)) + (with-temp-buffer + (when local-margin (setq-local overline-margin local-margin)) + (let ((input (ebox-build '(box :border "red" "Border")))) + (ebox-render-to-buffer (current-buffer) input) + (should (local-variable-p 'overline-margin)) + (should (zerop overline-margin)) + ;; Remounting must not replace the original setting with zero. + (ebox-render-to-buffer (current-buffer) input) + (should (zerop overline-margin)) + (ebox-unmount-buffer (current-buffer)) + (should (eq (local-variable-p 'overline-margin) (and local-margin t))) + (should (= overline-margin (or local-margin default-margin)))))) + (should (= (default-value 'overline-margin) default-margin)))) + +(ert-deftest ebox-display-failed-mount-preserves-border-metrics () + "An unsuccessful initial publication must leave native metrics alone." + (with-temp-buffer + (setq-local overline-margin 7) + (let ((tp--surface-publication-step-function + (lambda (step _surface) + (when (eq step 'client-state) (error "Reject initial mount"))))) + (should-error + (ebox-render-to-buffer (current-buffer) + (ebox-build '(box :border "red" "Border"))))) + (should (= overline-margin 7)) + (should-not ebox-buffer-mode))) + (ert-deftest ebox-display-invalid-input-preserves-windows () "Invalid input must fail before changing the caller's window layout." (save-window-excursion