From fd6d8faa62c56064c41a9709ab1010eecb8797f2 Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Mon, 2 Feb 2026 16:04:04 -0800 Subject: [PATCH] Fix aligning buffer regions containing multiple alignment sections * lisp/align.el (align-region): Use markers to ensure the regions stay accurate after overlapping aligning modifications. (Bug#80316) * test/lisp/align-tests.el (align-c-multi-section): New test. --- lisp/align.el | 38 +++++++++++++++++++++++++------- test/lisp/align-tests.el | 47 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+), 8 deletions(-) diff --git a/lisp/align.el b/lisp/align.el index 1f1c8f58009..c2132da17ea 100644 --- a/lisp/align.el +++ b/lisp/align.el @@ -1407,11 +1407,18 @@ aligner would have dealt with are." (align-region beg end 'entire exclude-rules nil + ;; Use markers for exclusion area bounds so + ;; they remain accurate after subsequent + ;; alignment sections modify the buffer. (lambda (b e mode) (or (and mode (listp mode)) + (let ((bm (copy-marker b)) + (em (copy-marker e t))) + (push bm markers) + (push em markers) (setq exclude-areas - (cons (cons b e) - exclude-areas))))) + (cons (cons bm em) + exclude-areas)))))) (setq exclude-areas (nreverse (sort exclude-areas #'car-less-than-car)))) @@ -1458,12 +1465,15 @@ aligner would have dealt with are." (setq same nil) (align--set-marker eol (line-end-position))) - ;; remember the beginning position of this rule - ;; match, and save the match-data, since either - ;; the `valid' form, or the code that searches for - ;; section separation, might alter it - (setq rule-beg (match-beginning first) - save-match-data (match-data)) + ;; Remember the beginning position of this rule + ;; match as a marker so it remains accurate after + ;; `align-regions' modifies the buffer for a + ;; previous alignment section. Also save the + ;; match-data, since either the `valid' form, or + ;; the code that searches for section separation, + ;; might alter it. + (align--set-marker rule-beg (match-beginning first) t) + (setq save-match-data (match-data)) (or rule-beg (error "No match for subexpression %s" first)) @@ -1480,6 +1490,18 @@ aligner would have dealt with are." (when (and last-point (align-new-section-p last-point rule-beg thissep)) + ;; Convert saved match-data positions to + ;; markers before `align-regions' modifies + ;; the buffer, so the restored match-data + ;; reflects the updated buffer state. + (setq save-match-data + (mapcar (lambda (pos) + (if (integerp pos) + (let ((m (copy-marker pos))) + (push m markers) + m) + pos)) + save-match-data)) (align-regions regions align-props rule func) (setq regions nil) (setq align-props nil)) diff --git a/test/lisp/align-tests.el b/test/lisp/align-tests.el index 92605a7f4aa..43660f8de87 100644 --- a/test/lisp/align-tests.el +++ b/test/lisp/align-tests.el @@ -36,6 +36,53 @@ (ert-test-erts-file (ert-resource-file "c-mode.erts") (test-align-transform-fun #'c-mode))) +(ert-deftest align-c-multi-section () + "Test alignment of multiple sections in C code. +Regression test for bug where positions become stale after earlier +sections are aligned, causing incorrect alignment in later sections." + (let ((input "int main(void) +{ + long signed int foo = 5; + int bar = 7; + { + int a1 = 4; + int b1 = 2; + long signed int junk1 = 2; + } + { + int a2 = 4; /* comment */ + int b2 = 2; + long signed int junk2 = 2; /* another comment */ + } + + return 0; +} +") + (expected "int main(void) +{ + long signed int foo = 5; + int bar = 7; + { + int a1 = 4; + int b1 = 2; + long signed int junk1 = 2; + } + { + int a2 = 4; /* comment */ + int b2 = 2; + long signed int junk2 = 2; /* another comment */ + } + + return 0; +} +")) + (with-temp-buffer + (c-mode) + (setq indent-tabs-mode nil) + (insert input) + (align (point-min) (point-max)) + (should (equal (buffer-string) expected))))) + (ert-deftest align-css () (let ((indent-tabs-mode nil)) (ert-test-erts-file (ert-resource-file "css-mode.erts")