]> code.delx.au - gnu-emacs/blobdiff - lisp/align.el
Update docs for `customize-mode'
[gnu-emacs] / lisp / align.el
index a384b7c508049fedec75eae96ec0c5cb435be6c5..f09f57032d44a737ba74f683a18c0d7339af2dc6 100644 (file)
@@ -1,10 +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, 2000, 2001, 2002, 2003, 2004,
-;;   2005, 2006, 2007, 2008 Free Software Foundation, Inc.
+;; Copyright (C) 1999-2016 Free Software Foundation, Inc.
 
-;; Author: John Wiegley <johnw@gnu.org> 
-;; Maintainer: FSF
+;; Author: John Wiegley <johnw@gnu.org>
+;; Maintainer: emacs-devel@gnu.org
 ;; Keywords: convenience languages lisp
 
 ;; This file is part of GNU Emacs.
@@ -75,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
-;; 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.
 
 ;; 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
 ;;; User Variables:
 
 (defcustom align-load-hook nil
-  "*Hook that gets run after the aligner has been loaded."
+  "Hook that gets run after the aligner has been loaded."
   :type 'hook
   :group 'align)
 
 (defcustom align-indent-before-aligning nil
-  "*If non-nil, indent the marked region before aligning it."
+  "If non-nil, indent the marked region before aligning it."
   :type 'boolean
   :group 'align)
 
 (defcustom align-default-spacing 1
-  "*An integer that represents the default amount of padding to use.
+  "An integer that represents the default amount of padding to use.
 If `align-to-tab-stop' is non-nil, this will represent the number of
 tab stops to use for alignment, rather than the number of spaces.
-Each alignment rule can optionally override both this variable.  See
-`align-mode-alist'."
+Each alignment rule can optionally override both this variable and
+`align-to-tab-stop'.  See `align-rules-list'."
   :type 'integer
   :group 'align)
 
 (defcustom align-to-tab-stop 'indent-tabs-mode
-  "*If non-nil, alignments will always fall on a tab boundary.
+  "If non-nil, alignments will always fall on a tab boundary.
 It may also be a symbol, whose value will be taken."
   :type '(choice (const nil) symbol)
   :group 'align)
 
 (defcustom align-region-heuristic 500
-  "*If non-nil, used as a heuristic by `align-current'.
+  "If non-nil, used as a heuristic by `align-current'.
 Since each alignment rule can possibly have its own set of alignment
 sections (whenever `align-region-separate' is non-nil, and not a
 string), this heuristic is used to determine how far before and after
 point we should search in looking for a region separator.  Larger
-values can mean slower perform in large files, although smaller values
-may cause unexpected behavior at times."
+values can mean slower performance in large files, although smaller
+values may cause unexpected behavior at times."
   :type 'integer
   :group 'align)
 
 (defcustom align-highlight-change-face 'highlight
-  "*The face to highlight with if changes are necessary."
+  "The face to highlight with if changes are necessary."
   :type 'face
   :group 'align)
 
 (defcustom align-highlight-nochange-face 'secondary-selection
-  "*The face to highlight with if no changes are necessary."
+  "The face to highlight with if no changes are necessary."
   :type 'face
   :group 'align)
 
 (defcustom align-large-region 10000
-  "*If an integer, defines what constitutes a \"large\" region.
-If nil,then no messages will ever be printed to the minibuffer."
+  "If an integer, defines what constitutes a \"large\" region.
+If nil, then no messages will ever be printed to the minibuffer."
   :type 'integer
   :group 'align)
 
 (defcustom align-c++-modes '(c++-mode c-mode java-mode)
-  "*A list of modes whose syntax resembles C/C++."
+  "A list of modes whose syntax resembles C/C++."
   :type '(repeat symbol)
   :group 'align)
 
 (defcustom align-perl-modes '(perl-mode cperl-mode)
-  "*A list of modes where perl syntax is to be seen."
+  "A list of modes where Perl syntax is to be seen."
   :type '(repeat symbol)
   :group 'align)
 
 (defcustom align-lisp-modes
   '(emacs-lisp-mode lisp-interaction-mode lisp-mode scheme-mode)
-  "*A list of modes whose syntax resembles Lisp."
+  "A list of modes whose syntax resembles Lisp."
   :type '(repeat symbol)
   :group 'align)
 
 (defcustom align-tex-modes
   '(tex-mode plain-tex-mode latex-mode slitex-mode)
-  "*A list of modes whose syntax resembles TeX (and family)."
+  "A list of modes whose syntax resembles TeX (and family)."
   :type '(repeat symbol)
   :group 'align)
 
 (defcustom align-text-modes '(text-mode outline-mode)
-  "*A list of modes whose content is plain text."
+  "A list of modes whose content is plain text."
   :type '(repeat symbol)
   :group 'align)
 
 (defcustom align-dq-string-modes
   (append align-lisp-modes align-c++-modes align-perl-modes
          '(python-mode))
-  "*A list of modes where double quoted strings should be excluded."
+  "A list of modes where double quoted strings should be excluded."
   :type '(repeat symbol)
   :group 'align)
 
 (defcustom align-sq-string-modes
   (append align-perl-modes '(python-mode))
-  "*A list of modes where single quoted strings should be excluded."
+  "A list of modes where single quoted strings should be excluded."
   :type '(repeat symbol)
   :group 'align)
 
 (defcustom align-open-comment-modes
   (append align-lisp-modes align-c++-modes align-perl-modes
          '(python-mode makefile-mode))
-  "*A list of modes with a single-line comment syntax.
-These are comments as in Lisp, which have a beginning but, end with
+  "A list of modes with a single-line comment syntax.
+These are comments as in Lisp, which have a beginning, but end with
 the line (i.e., `comment-end' is an empty string)."
   :type '(repeat symbol)
   :group 'align)
 
 (defcustom align-region-separate "^\\s-*[{}]?\\s-*$"
-  "*Select the method by which alignment sections will be separated.
+  "Select the method by which alignment sections will be separated.
 If this is a symbol, that symbol's value will be used.
 
 For the sake of clarification, consider the following example, which
@@ -259,8 +258,8 @@ The possible settings for `align-region-separate' are:
 
  `group'   Each contiguous set of lines where a specific alignment
           occurs is considered a section for that alignment rule.
-          Note that each rule will may have any entirely different
-          set of section divisions than another.
+          Note that each rule may have any entirely different set
+           of section divisions than another.
 
             int    alpha = 1; /* one */
             double beta  = 2.0;
@@ -292,7 +291,7 @@ The possible settings for `align-region-separate' are:
           between sections, the behavior will be very similar to
           `largest', and faster.  But if the mode does not use clear
           separators (for example, if you collapse your braces onto
-          the preceding statement in C or perl), `largest' is
+          the preceding statement in C or Perl), `largest' is
           probably the better alternative.
 
  function  A function that will be passed the beginning and ending
@@ -301,8 +300,8 @@ The possible settings for `align-region-separate' are:
           both of these parameters will be nil, in which case the
           function should return non-nil if it wants each rule to
           define its own section, or nil if it wants the largest
-          section found to be used as the common section for all rules
-          that occur there.
+          section found to be used as the common section for all
+          rules that occur there.
 
  list      A list of markers within the buffer that represent where
           the section dividers lie.  Be certain to use markers!  For
@@ -581,7 +580,7 @@ The possible settings for `align-region-separate' are:
      (regexp . "^\\s-*\\w+:\\(\\s-*\\).*;")
      (group . (1))
      (modes . '(css-mode html-mode))))
-  "*A list describing all of the available alignment rules.
+  "A list describing all of the available alignment rules.
 The format is:
 
    ((TITLE
@@ -623,8 +622,8 @@ The following attributes are meaningful:
            the purposes of alignment.  The \"alignment character\" is
            always the first character immediately following this
            parenthesis group.  This attribute may also be a list of
-           integer, in which case multiple alignment characters will
-           be aligned, with the list of integer identifying the
+           integers, in which case multiple alignment characters will
+           be aligned, with the list of integers identifying the
            whitespace groups which precede them.  The default for
            this attribute is 1.
 
@@ -636,7 +635,7 @@ The following attributes are meaningful:
 `case-fold' If `regexp' is an ordinary regular expression string
            containing alphabetic character, sometimes you may want
            the search to proceed case-insensitively (for languages
-           that ignore case, such as pascal for example).  In that
+           that ignore case, such as Pascal for example).  In that
            case, set `case-fold' to a non-nil value, and the regular
            expression search will ignore case.  If `regexp' is set to
            a function, that function must handle the job of ignoring
@@ -779,7 +778,7 @@ The following attributes are meaningful:
      (regexp . "^\\s-*#\\s-*\\(if\\w*\\|endif\\)\\(.*\\)$")
      (group  . 2)
      (modes  . align-c++-modes)))
-  "*A list describing text that should be excluded from alignment.
+  "A list describing text that should be excluded from alignment.
 See the documentation for `align-rules-list' for more info."
   :type align-exclude-rules-list-type
   :group 'align)
@@ -837,7 +836,7 @@ See the variable `align-exclude-rules-list' for more details.")
 
     (use-entity
      (regexp   . "\\(\\s-+\\)use\\s-+entity")))
-  "*Alignment rules for `vhdl-mode'.  See `align-rules-list' for more info."
+  "Alignment rules for `vhdl-mode'.  See `align-rules-list' for more info."
   :type align-rules-list-type
   :group 'align)
 
@@ -889,15 +888,15 @@ on the format of these lists."
       (let ((sec-first end)
            (sec-last beg))
        (align-region beg end
-                     (or exclude-rules
-                         align-mode-exclude-rules-list
-                         align-exclude-rules-list) nil
                      separator
-                     (function
-                      (lambda (b e mode)
-                        (when (and mode (listp mode))
-                          (setq sec-first (min sec-first b)
-                                sec-last  (max sec-last e))))))
+                     nil ; rules
+                      (or exclude-rules
+                         align-mode-exclude-rules-list
+                         align-exclude-rules-list)
+                      (lambda (b e mode)
+                        (when (consp mode)
+                          (setq sec-first (min sec-first b)
+                                sec-last  (max sec-last e)))))
        (if (< sec-first sec-last)
            (align-region sec-first sec-last 'entire
                          (or rules align-mode-rules-list align-rules-list)
@@ -907,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.
-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:
@@ -926,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
-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))
@@ -944,6 +957,8 @@ region, call `align-regexp' and type in that regular expression."
       (list (concat "\\(\\s-*\\)"
                    (read-string "Align regexp: "))
            1 align-default-spacing nil))))
+  (or group (setq group 1))
+  (or spacing (setq spacing align-default-spacing))
   (let ((rule
         (list (list nil (cons 'regexp regexp)
                     (cons 'group (abs group))
@@ -1036,7 +1051,9 @@ to be colored."
 
 ;;;###autoload
 (defun align-newline-and-indent ()
-  "A replacement function for `newline-and-indent', aligning as it goes."
+  "A replacement function for `newline-and-indent', aligning as it goes.
+The alignment is done by calling `align' on the region that was
+indented."
   (interactive)
   (let ((separate (or (if (and (symbolp align-region-separate)
                               (boundp align-region-separate))
@@ -1105,7 +1122,7 @@ documentation for `align-region-separate' for more details."
             (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."
@@ -1115,13 +1132,8 @@ TAB-STOP specifies whether SPACING refers to tab-stop boundaries."
       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)
@@ -1160,7 +1172,7 @@ have been aligned.  No changes will be made to the buffer."
         (justify (cdr (assq 'justify rule)))
         (col (or fixed 0))
         (width 0)
-        ecol change look)
+        ecol change)
 
     ;; Determine the alignment column.
     (let ((a areas))
@@ -1200,7 +1212,10 @@ have been aligned.  No changes will be made to the buffer."
              (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))
@@ -1245,6 +1260,13 @@ have been aligned.  No changes will be made to the buffer."
                                         (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.
@@ -1284,11 +1306,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)
-       (real-end end)
        (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
@@ -1300,21 +1322,22 @@ 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)))))
-         (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))
                 (rulesep (assq 'separate rule))
                 (thissep (if rulesep (cdr rulesep) separate))
                 same (eol 0)
-                group group-c
+                search-start
+                groups group-c
                 spacing spacing-c
                 tab-stop tab-stop-c
                 repeat repeat-c
                 valid valid-c
-                pos-list first
+                first
                 regions index
-                last-point b e
+                last-point
                 save-match-data
                 exclude-p
                 align-props)
@@ -1327,7 +1350,7 @@ aligner would have dealt with are."
              (if real-beg
                  (goto-char beg)
                (if (or (not thissep) (eq thissep 'entire))
-                   (error "Cannot determine alignment region for '%s'"
+                   (error "Cannot determine alignment region for `%s'"
                           (symbol-name (cdr (assq 'title rule)))))
                (beginning-of-line)
                (while (and (not (eobp))
@@ -1369,223 +1392,217 @@ aligner would have dealt with are."
                  (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))
-               (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)
-                                 (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 (bolp)
-                           (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)))))
-
-                     ;; 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
+                               (floor (* (- (point) real-beg) 100.0)
+                                      (- end-mark real-beg)))
+                            (message
+                             "Aligning %d%%..."
+                             (floor (* (- (point) real-beg) 100.0)
+                                    (- 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)))
+    ;; 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"))))
@@ -1596,5 +1613,4 @@ aligner would have dealt with are."
 
 (run-hooks 'align-load-hook)
 
-;; arch-tag: ef79cccf-1db8-4888-a8a1-d7ce2d1532f7
 ;;; align.el ends here