Move lisp/emacs-lisp/authors.el to admin/
[bpt/emacs.git] / lisp / align.el
index 83e27da..0e6b84d 100644 (file)
@@ -1,9 +1,9 @@
-;;; align.el --- align text to a specific column, by regexp
+;;; align.el --- align text to a specific column, by regexp -*- lexical-binding:t -*-
 
 
-;; Copyright (C) 1999-201 Free Software Foundation, Inc.
+;; Copyright (C) 1999-2014 Free Software Foundation, Inc.
 
 ;; Author: John Wiegley <johnw@gnu.org>
 
 ;; Author: John Wiegley <johnw@gnu.org>
-;; Maintainer: FSF
+;; Maintainer: emacs-devel@gnu.org
 ;; Keywords: convenience languages lisp
 
 ;; This file is part of GNU Emacs.
 ;; Keywords: convenience languages lisp
 
 ;; This file is part of GNU Emacs.
@@ -74,7 +74,7 @@
 ;; align-?-modes variables (for example, `align-dq-string-modes'), use
 ;; `add-to-list', or some similar function which checks first to see
 ;; if the value is already there.  Since the user may customize that
 ;; align-?-modes variables (for example, `align-dq-string-modes'), use
 ;; `add-to-list', or some similar function which checks first to see
 ;; if the value is already there.  Since the user may customize that
-;; mode list, and then write your mode name into their .emacs file,
+;; mode list, and then write your mode name into their init file,
 ;; causing the symbol already to be present the next time they load
 ;; your package.
 
 ;; causing the symbol already to be present the next time they load
 ;; your package.
 
 ;; simple algorithm that understand only basic regular expressions.
 ;; Parts of the code were broken up and included in vhdl-mode.el
 ;; around this time.  After several comments from users, and a need to
 ;; simple algorithm that understand only basic regular expressions.
 ;; Parts of the code were broken up and included in vhdl-mode.el
 ;; around this time.  After several comments from users, and a need to
-;; find a more robust, performant algorithm, 2.0 was born in late
+;; find a more robust, higher performing algorithm, 2.0 was born in late
 ;; 1998.  Many different approaches were taken (mostly due to the
 ;; complexity of TeX tables), but finally a scheme was discovered
 ;; which worked fairly well for most common usage cases.  Development
 ;; 1998.  Many different approaches were taken (mostly due to the
 ;; complexity of TeX tables), but finally a scheme was discovered
 ;; which worked fairly well for most common usage cases.  Development
@@ -906,15 +906,8 @@ on the format of these lists."
 ;;;###autoload
 (defun align-regexp (beg end regexp &optional group spacing repeat)
   "Align the current region using an ad-hoc rule read from the minibuffer.
 ;;;###autoload
 (defun align-regexp (beg end regexp &optional group spacing repeat)
   "Align the current region using an ad-hoc rule read from the minibuffer.
-BEG and END mark the limits of the region.  This function will prompt
-for the REGEXP to align with.  If no prefix arg was specified, you
-only need to supply the characters to be lined up and any preceding
-whitespace is replaced.  If a prefix arg was specified, the full
-regexp with parenthesized whitespace should be supplied; it will also
-prompt for which parenthesis GROUP within REGEXP to modify, the amount
-of SPACING to use, and whether or not to REPEAT the rule throughout
-the line.  See `align-rules-list' for more information about these
-options.
+BEG and END mark the limits of the region.  Interactively, this function
+prompts for the regular expression REGEXP to align with.
 
 For example, let's say you had a list of phone numbers, and wanted to
 align them so that the opening parentheses would line up:
 
 For example, let's say you had a list of phone numbers, and wanted to
 align them so that the opening parentheses would line up:
@@ -925,8 +918,29 @@ align them so that the opening parentheses would line up:
     Joe (123) 456-7890
 
 There is no predefined rule to handle this, but you could easily do it
     Joe (123) 456-7890
 
 There is no predefined rule to handle this, but you could easily do it
-using a REGEXP like \"(\".  All you would have to do is to mark the
-region, call `align-regexp' and type in that regular expression."
+using a REGEXP like \"(\".  Interactively, all you would have to do is
+to mark the region, call `align-regexp' and enter that regular expression.
+
+REGEXP must contain at least one parenthesized subexpression, typically
+whitespace of the form \"\\\\(\\\\s-*\\\\)\".  In normal interactive use,
+this is automatically added to the start of your regular expression after
+you enter it.  You only need to supply the characters to be lined up, and
+any preceding whitespace is replaced.
+
+If you specify a prefix argument (or use this function non-interactively),
+you must enter the full regular expression, including the subexpression.
+The function also then prompts for which subexpression parenthesis GROUP
+\(default 1) within REGEXP to modify, the amount of SPACING (default
+`align-default-spacing') to use, and whether or not to REPEAT the rule
+throughout the line.
+
+See `align-rules-list' for more information about these options.
+
+The non-interactive form of the previous example would look something like:
+  (align-regexp (point-min) (point-max) \"\\\\(\\\\s-*\\\\)(\")
+
+This function is a nothing more than a small wrapper that helps you
+construct a rule to pass to `align-region', which does the real work."
   (interactive
    (append
     (list (region-beginning) (region-end))
   (interactive
    (append
     (list (region-beginning) (region-end))
@@ -1106,7 +1120,7 @@ documentation for `align-region-separate' for more details."
             (setq seps (cdr seps))))
           yes))))
 
             (setq seps (cdr seps))))
           yes))))
 
-(defun align-adjust-col-for-rule (column rule spacing tab-stop)
+(defun align-adjust-col-for-rule (column _rule spacing tab-stop)
   "Adjust COLUMN according to the given RULE.
 SPACING specifies how much spacing to use.
 TAB-STOP specifies whether SPACING refers to tab-stop boundaries."
   "Adjust COLUMN according to the given RULE.
 SPACING specifies how much spacing to use.
 TAB-STOP specifies whether SPACING refers to tab-stop boundaries."
@@ -1116,13 +1130,8 @@ TAB-STOP specifies whether SPACING refers to tab-stop boundaries."
       column
     (if (not tab-stop)
        (+ column spacing)
       column
     (if (not tab-stop)
        (+ column spacing)
-      (let ((stops tab-stop-list))
-       (while stops
-         (if (and (> (car stops) column)
-                  (= (setq spacing (1- spacing)) 0))
-             (setq column (car stops)
-                   stops nil)
-           (setq stops (cdr stops)))))
+      (dotimes (_ spacing)
+       (setq column (indent-next-tab-stop column)))
       column)))
 
 (defsubst align-column (pos)
       column)))
 
 (defsubst align-column (pos)
@@ -1161,7 +1170,7 @@ have been aligned.  No changes will be made to the buffer."
         (justify (cdr (assq 'justify rule)))
         (col (or fixed 0))
         (width 0)
         (justify (cdr (assq 'justify rule)))
         (col (or fixed 0))
         (width 0)
-        ecol change look)
+        ecol change)
 
     ;; Determine the alignment column.
     (let ((a areas))
 
     ;; Determine the alignment column.
     (let ((a areas))
@@ -1201,7 +1210,10 @@ have been aligned.  No changes will be made to the buffer."
              (gocol col) cur)
          (when area
            (if func
              (gocol col) cur)
          (when area
            (if func
-               (funcall func (car area) (cdr area) change)
+               (funcall func
+                        (marker-position (car area))
+                        (marker-position (cdr area))
+                        change)
              (if (not (and justify
                            (consp (cdr area))))
                  (goto-char (cdr area))
              (if (not (and justify
                            (consp (cdr area))))
                  (goto-char (cdr area))
@@ -1246,6 +1258,13 @@ have been aligned.  No changes will be made to the buffer."
                                         (car props) (cdr props)))))))))))
        (setq areas (cdr areas))))))
 
                                         (car props) (cdr props)))))))))))
        (setq areas (cdr areas))))))
 
+(defmacro align--set-marker (marker-var pos &optional type)
+  "If MARKER-VAR is a marker, move it to position POS.
+Otherwise, create a new marker at position POS, with type TYPE."
+  `(if (markerp ,marker-var)
+       (move-marker ,marker-var ,pos)
+     (setq ,marker-var (copy-marker ,pos ,type))))
+
 (defun align-region (beg end separate rules exclude-rules
                         &optional func)
   "Align a region based on a given set of alignment rules.
 (defun align-region (beg end separate rules exclude-rules
                         &optional func)
   "Align a region based on a given set of alignment rules.
@@ -1285,11 +1304,11 @@ purpose where you might want to know where the regions that the
 aligner would have dealt with are."
   (let ((end-mark (and end (copy-marker end t)))
        (real-beg beg)
 aligner would have dealt with are."
   (let ((end-mark (and end (copy-marker end t)))
        (real-beg beg)
-       (real-end end)
        (report (and (not func) align-large-region beg end
                     (>= (- end beg) align-large-region)))
        (rule-index 1)
        (report (and (not func) align-large-region beg end
                     (>= (- end beg) align-large-region)))
        (rule-index 1)
-       (rule-count (length rules)))
+       (rule-count (length rules))
+       markers)
     (if (and align-indent-before-aligning real-beg end-mark)
        (indent-region real-beg end-mark nil))
     (while rules
     (if (and align-indent-before-aligning real-beg end-mark)
        (indent-region real-beg end-mark nil))
     (while rules
@@ -1301,7 +1320,7 @@ aligner would have dealt with are."
        (unless (or (and modes (not (memq major-mode
                                          (eval (cdr modes)))))
                    (and run-if (not (funcall (cdr run-if)))))
        (unless (or (and modes (not (memq major-mode
                                          (eval (cdr modes)))))
                    (and run-if (not (funcall (cdr run-if)))))
-         (let* ((current-case-fold case-fold-search)
+         (let* ((case-fold-search case-fold-search)
                 (case-fold (assq 'case-fold rule))
                 (regexp  (cdr (assq 'regexp rule)))
                 (regfunc (and (functionp regexp) regexp))
                 (case-fold (assq 'case-fold rule))
                 (regexp  (cdr (assq 'regexp rule)))
                 (regfunc (and (functionp regexp) regexp))
@@ -1309,14 +1328,14 @@ aligner would have dealt with are."
                 (thissep (if rulesep (cdr rulesep) separate))
                 same (eol 0)
                 search-start
                 (thissep (if rulesep (cdr rulesep) separate))
                 same (eol 0)
                 search-start
-                group group-c
+                groups group-c
                 spacing spacing-c
                 tab-stop tab-stop-c
                 repeat repeat-c
                 valid valid-c
                 spacing spacing-c
                 tab-stop tab-stop-c
                 repeat repeat-c
                 valid valid-c
-                pos-list first
+                first
                 regions index
                 regions index
-                last-point b e
+                last-point
                 save-match-data
                 exclude-p
                 align-props)
                 save-match-data
                 exclude-p
                 align-props)
@@ -1371,229 +1390,217 @@ aligner would have dealt with are."
                  (if (not here)
                      (goto-char end))
                  (forward-line)
                  (if (not here)
                      (goto-char end))
                  (forward-line)
-                 (setq end (point)
-                       end-mark (copy-marker end t))
+                 (setq end (point))
+                  (align--set-marker end-mark end t)
                  (goto-char beg)))
 
              ;; If we have a region to align, and `func' is set and
              ;; reports back that the region is ok, then align it.
              (when (or (not func)
                        (funcall func beg end rule))
                  (goto-char beg)))
 
              ;; If we have a region to align, and `func' is set and
              ;; reports back that the region is ok, then align it.
              (when (or (not func)
                        (funcall func beg end rule))
-               (unwind-protect
-                   (let (exclude-areas)
-                     ;; determine first of all where the exclusions
-                     ;; lie in this region
-                     (when exclude-rules
-                       ;; guard against a problem with recursion and
-                       ;; dynamic binding vs. lexical binding, since
-                       ;; the call to `align-region' below will
-                       ;; re-enter this function, and rebind
-                       ;; `exclude-areas'
-                       (set (setq exclude-areas
-                                  (make-symbol "align-exclude-areas"))
-                            nil)
-                       (align-region
-                        beg end 'entire
-                        exclude-rules nil
-                        `(lambda (b e mode)
-                           (or (and mode (listp mode))
-                               (set (quote ,exclude-areas)
-                                    (cons (cons b e)
-                                          ,exclude-areas)))))
-                       (setq exclude-areas
-                             (sort (symbol-value exclude-areas)
-                                   (function
-                                    (lambda (l r)
-                                      (>= (car l) (car r)))))))
-
-                     ;; set `case-fold-search' according to the
-                     ;; (optional) `case-fold' property
-                     (and case-fold
-                          (setq case-fold-search (cdr case-fold)))
-
-                     ;; while we can find the rule in the alignment
-                     ;; region..
-                     (while (and (< (point) end-mark)
-                                 (setq search-start (point))
-                                 (if regfunc
-                                     (funcall regfunc end-mark nil)
-                                   (re-search-forward regexp
-                                                      end-mark t)))
-
-                       ;; give the user some indication of where we
-                       ;; are, if it's a very large region being
-                       ;; aligned
-                       (if report
-                           (let ((symbol (car rule)))
-                             (if (and symbol (symbolp symbol))
-                                 (message
-                                  "Aligning `%s' (rule %d of %d) %d%%..."
-                                  (symbol-name symbol) rule-index rule-count
-                                  (/ (* (- (point) real-beg) 100)
-                                     (- end-mark real-beg)))
-                               (message
-                                "Aligning %d%%..."
-                                (/ (* (- (point) real-beg) 100)
-                                   (- end-mark real-beg))))))
-
-                       ;; if the search ended us on the beginning of
-                       ;; the next line, move back to the end of the
-                       ;; previous line.
-                       (if (and (bolp) (> (point) search-start))
-                           (forward-char -1))
-
-                       ;; lookup the `group' attribute the first time
-                       ;; that we need it
-                       (unless group-c
-                         (setq group (or (cdr (assq 'group rule)) 1))
-                         (if (listp group)
-                             (setq first (car group))
-                           (setq first group group (list group)))
-                         (setq group-c t))
-
-                       (unless spacing-c
-                         (setq spacing (cdr (assq 'spacing rule))
-                               spacing-c t))
-
-                       (unless tab-stop-c
-                         (setq tab-stop
-                               (let ((rule-ts (assq 'tab-stop rule)))
-                                 (if rule-ts
-                                     (cdr rule-ts)
-                                   (if (symbolp align-to-tab-stop)
-                                       (symbol-value align-to-tab-stop)
-                                     align-to-tab-stop)))
-                               tab-stop-c t))
-
-                       ;; test whether we have found a match on the same
-                       ;; line as a previous match
-                       (if (> (point) eol)
-                           (setq same nil
-                                 eol (save-excursion
-                                       (end-of-line)
-                                       (point-marker))))
-
-                       ;; lookup the `repeat' attribute the first time
-                       (or repeat-c
-                           (setq repeat (cdr (assq 'repeat rule))
-                                 repeat-c t))
-
-                       ;; lookup the `valid' attribute the first time
-                       (or valid-c
-                           (setq valid (assq 'valid rule)
-                                 valid-c t))
-
-                       ;; 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 b (match-beginning first)
-                             save-match-data (match-data))
-
-                       ;; unless the `valid' attribute is set, and tells
-                       ;; us that the rule is not valid at this point in
-                       ;; the code..
-                       (unless (and valid (not (funcall (cdr valid))))
-
-                         ;; look to see if this match begins a new
-                         ;; section.  If so, we should align what we've
-                         ;; collected so far, and then begin collecting
-                         ;; anew for the next alignment section
-                         (if (and last-point
-                                  (align-new-section-p last-point b
-                                                       thissep))
-                             (progn
-                               (align-regions regions align-props
-                                              rule func)
-                               (setq last-point (copy-marker b t)
-                                     regions nil
-                                     align-props nil))
-                           (setq last-point (copy-marker b t)))
-
-                         ;; restore the match data
-                         (set-match-data save-match-data)
-
-                         ;; check whether the region to be aligned
-                         ;; straddles an exclusion area
-                         (let ((excls exclude-areas))
-                           (setq exclude-p nil)
-                           (while excls
-                             (if (and (< (match-beginning (car group))
-                                         (cdar excls))
-                                      (> (match-end (car (last group)))
-                                         (caar excls)))
-                                 (setq exclude-p t
-                                       excls nil)
-                               (setq excls (cdr excls)))))
-
-                         ;; go through the list of parenthesis groups
-                         ;; matching whitespace text to be
-                         ;; contracted/expanded (or possibly
-                         ;; justified, if the `justify' attribute was
-                         ;; set)
-                         (unless exclude-p
-                           (let ((g group))
-                             (while g
-
-                               ;; we have to use markers, since
-                               ;; `align-areas' may modify the buffer
-                               (setq b (copy-marker
-                                        (match-beginning (car g)) t)
-                                     e (copy-marker (match-end (car g)) t))
-
-                               ;; record this text region for alignment
-                               (setq index (if same (1+ index) 0))
-                               (let ((region (cons b e))
-                                     (props (cons
-                                             (if (listp spacing)
-                                                 (car spacing)
-                                               spacing)
-                                             (if (listp tab-stop)
-                                                 (car tab-stop)
-                                               tab-stop))))
-                                 (if (nth index regions)
-                                     (setcar (nthcdr index regions)
-                                             (cons region
-                                                   (nth index regions)))
-                                   (if regions
-                                       (progn
-                                         (nconc regions
-                                                (list (list region)))
-                                         (nconc align-props (list props)))
-                                     (setq regions
-                                           (list (list region)))
-                                     (setq align-props (list props)))))
-
-                               ;; if any further rule matches are
-                               ;; found before `eol', then they are
-                               ;; on the same line as this one; this
-                               ;; can only happen if the `repeat'
-                               ;; attribute is non-nil
-                               (if (listp spacing)
-                                   (setq spacing (cdr spacing)))
-                               (if (listp tab-stop)
-                                   (setq tab-stop (cdr tab-stop)))
-                               (setq same t g (cdr g))))
-
-                           ;; if `repeat' has not been set, move to
-                           ;; the next line; don't bother searching
-                           ;; anymore on this one
-                           (if (and (not repeat) (not (bolp)))
-                               (forward-line))
-
-                           ;; if the search did not change point,
-                           ;; move forward to avoid an infinite loop
-                           (if (= (point) search-start)
-                               (forward-char)))))
-
-                     ;; when they are no more matches for this rule,
-                     ;; align whatever was left over
-                     (if regions
-                         (align-regions regions align-props rule func)))
-
-                 (setq case-fold-search current-case-fold)))))))
+                (let (rule-beg exclude-areas)
+                  ;; determine first of all where the exclusions
+                  ;; lie in this region
+                  (when exclude-rules
+                    (align-region
+                     beg end 'entire
+                     exclude-rules nil
+                     (lambda (b e mode)
+                       (or (and mode (listp mode))
+                           (setq exclude-areas
+                                 (cons (cons b e)
+                                       exclude-areas)))))
+                    (setq exclude-areas
+                          (nreverse
+                           (sort exclude-areas #'car-less-than-car))))
+
+                  ;; set `case-fold-search' according to the
+                  ;; (optional) `case-fold' property
+                  (and case-fold
+                       (setq case-fold-search (cdr case-fold)))
+
+                  ;; while we can find the rule in the alignment
+                  ;; region..
+                  (while (and (< (point) end-mark)
+                              (setq search-start (point))
+                              (if regfunc
+                                  (funcall regfunc end-mark nil)
+                                (re-search-forward regexp
+                                                   end-mark t)))
+
+                    ;; give the user some indication of where we
+                    ;; are, if it's a very large region being
+                    ;; aligned
+                    (if report
+                        (let ((symbol (car rule)))
+                          (if (and symbol (symbolp symbol))
+                              (message
+                               "Aligning `%s' (rule %d of %d) %d%%..."
+                               (symbol-name symbol) rule-index rule-count
+                               (/ (* (- (point) real-beg) 100)
+                                  (- end-mark real-beg)))
+                            (message
+                             "Aligning %d%%..."
+                             (/ (* (- (point) real-beg) 100)
+                                (- end-mark real-beg))))))
+
+                    ;; if the search ended us on the beginning of
+                    ;; the next line, move back to the end of the
+                    ;; previous line.
+                    (if (and (bolp) (> (point) search-start))
+                        (forward-char -1))
+
+                    ;; lookup the `group' attribute the first time
+                    ;; that we need it
+                    (unless group-c
+                      (setq groups (or (cdr (assq 'group rule)) 1))
+                      (unless (listp groups)
+                        (setq groups (list groups)))
+                      (setq first (car groups)))
+
+                    (unless spacing-c
+                      (setq spacing (cdr (assq 'spacing rule))
+                            spacing-c t))
+
+                    (unless tab-stop-c
+                      (setq tab-stop
+                            (let ((rule-ts (assq 'tab-stop rule)))
+                              (cond (rule-ts
+                                     (cdr rule-ts))
+                                    ((symbolp align-to-tab-stop)
+                                     (symbol-value align-to-tab-stop))
+                                    (t
+                                     align-to-tab-stop)))
+                            tab-stop-c t))
+
+                    ;; test whether we have found a match on the same
+                    ;; line as a previous match
+                    (when (> (point) eol)
+                      (setq same nil)
+                      (align--set-marker eol (line-end-position)))
+
+                    ;; lookup the `repeat' attribute the first time
+                    (or repeat-c
+                        (setq repeat (cdr (assq 'repeat rule))
+                              repeat-c t))
+
+                    ;; lookup the `valid' attribute the first time
+                    (or valid-c
+                        (setq valid (assq 'valid rule)
+                              valid-c t))
+
+                    ;; 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))
+
+                    (or rule-beg
+                        (error "No match for subexpression %s" first))
+
+                    ;; unless the `valid' attribute is set, and tells
+                    ;; us that the rule is not valid at this point in
+                    ;; the code..
+                    (unless (and valid (not (funcall (cdr valid))))
+
+                      ;; look to see if this match begins a new
+                      ;; section.  If so, we should align what we've
+                      ;; collected so far, and then begin collecting
+                      ;; anew for the next alignment section
+                      (when (and last-point
+                                 (align-new-section-p last-point rule-beg
+                                                      thissep))
+                        (align-regions regions align-props rule func)
+                        (setq regions nil)
+                        (setq align-props nil))
+                      (align--set-marker last-point rule-beg t)
+
+                      ;; restore the match data
+                      (set-match-data save-match-data)
+
+                      ;; check whether the region to be aligned
+                      ;; straddles an exclusion area
+                      (let ((excls exclude-areas))
+                        (setq exclude-p nil)
+                        (while excls
+                          (if (and (< (match-beginning (car groups))
+                                      (cdar excls))
+                                   (> (match-end (car (last groups)))
+                                      (caar excls)))
+                              (setq exclude-p t
+                                    excls nil)
+                            (setq excls (cdr excls)))))
+
+                      ;; go through the parenthesis groups
+                      ;; matching whitespace to be contracted or
+                      ;; expanded (or possibly justified, if the
+                      ;; `justify' attribute was set)
+                      (unless exclude-p
+                        (dolist (g groups)
+                          ;; We must use markers, since
+                          ;; `align-areas' may modify the buffer.
+                          ;; Avoid polluting the markers.
+                          (let* ((group-beg (copy-marker
+                                             (match-beginning g) t))
+                                 (group-end (copy-marker
+                                             (match-end g) t))
+                                 (region (cons group-beg group-end))
+                                 (props (cons (if (listp spacing)
+                                                  (car spacing)
+                                                spacing)
+                                              (if (listp tab-stop)
+                                                  (car tab-stop)
+                                                tab-stop))))
+                            (push group-beg markers)
+                            (push group-end markers)
+                            (setq index (if same (1+ index) 0))
+                            (cond
+                             ((nth index regions)
+                              (setcar (nthcdr index regions)
+                                      (cons region
+                                            (nth index regions))))
+                             (regions
+                              (nconc regions
+                                     (list (list region)))
+                              (nconc align-props (list props)))
+                             (t
+                              (setq regions
+                                    (list (list region)))
+                              (setq align-props (list props)))))
+                          ;; If any further rule matches are found
+                          ;; before `eol', they are on the same
+                          ;; line as this one; this can only
+                          ;; happen if the `repeat' attribute is
+                          ;; non-nil.
+                          (if (listp spacing)
+                              (setq spacing (cdr spacing)))
+                          (if (listp tab-stop)
+                              (setq tab-stop (cdr tab-stop)))
+                          (setq same t))
+
+                        ;; if `repeat' has not been set, move to
+                        ;; the next line; don't bother searching
+                        ;; anymore on this one
+                        (if (and (not repeat) (not (bolp)))
+                            (forward-line))
+
+                        ;; if the search did not change point,
+                        ;; move forward to avoid an infinite loop
+                        (if (= (point) search-start)
+                            (forward-char)))))
+
+                  ;; when they are no more matches for this rule,
+                  ;; align whatever was left over
+                  (if regions
+                      (align-regions regions align-props rule func))))))))
       (setq rules (cdr rules)
            rule-index (1+ rule-index)))
       (setq rules (cdr rules)
            rule-index (1+ rule-index)))
+    ;; This function can use a lot of temporary markers, so instead of
+    ;; waiting for the next GC we delete them immediately (Bug#10047).
+    (when end-mark (set-marker end-mark nil))
+    (dolist (m markers)
+      (set-marker m nil))
 
     (if report
        (message "Aligning...done"))))
 
     (if report
        (message "Aligning...done"))))