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.
This commit is contained in:
John Wiegley 2026-02-02 16:04:04 -08:00 committed by Eli Zaretskii
parent 3ea1010a6b
commit fd6d8faa62
2 changed files with 77 additions and 8 deletions

View file

@ -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))

View file

@ -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")