X-Git-Url: https://code.delx.au/gnu-emacs/blobdiff_plain/2b0c7330457b8ca42375c92ada7dc7cefb0fa9fb..4a2f33d1a11e0608d521520afcb14ec13dd1a722:/lisp/align.el diff --git a/lisp/align.el b/lisp/align.el index 83e27daece..f09f57032d 100644 --- a/lisp/align.el +++ b/lisp/align.el @@ -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-2011 Free Software Foundation, Inc. +;; Copyright (C) 1999-2016 Free Software Foundation, Inc. ;; Author: John Wiegley -;; Maintainer: FSF +;; Maintainer: emacs-devel@gnu.org ;; 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 -;; 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. @@ -109,7 +109,7 @@ ;; 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 @@ -888,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) @@ -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. -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: @@ -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 -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)) @@ -1037,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)) @@ -1106,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." @@ -1116,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) @@ -1161,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)) @@ -1201,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)) @@ -1246,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. @@ -1285,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 @@ -1301,7 +1322,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))))) - (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)) @@ -1309,14 +1330,14 @@ aligner would have dealt with are." (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 - pos-list first + first regions index - last-point b e + last-point save-match-data exclude-p align-props) @@ -1329,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)) @@ -1371,229 +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) - (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 + (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"))))