X-Git-Url: https://code.delx.au/gnu-emacs/blobdiff_plain/dab7711b3848fa6a22b23a5c33597ce9c8ad5d08..f95bbe5a67e03fe6d05cbfb4d0c9151a754d6ccd:/lisp/align.el diff --git a/lisp/align.el b/lisp/align.el index 4c82d7bea8..c3389dc986 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-2012 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. @@ -802,6 +802,9 @@ See the variable `align-exclude-rules-list' for more details.") (defvar align-highlight-overlays nil "The current overlays highlighting the text matched by a rule.") +(defvar align-regexp-history nil + "Input history for the full user-entered regex in `align-regexp'") + ;; Sample extension rule set, for vhdl-mode. This should properly be ;; in vhdl-mode.el itself. @@ -888,15 +891,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 +909,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,14 +921,35 @@ 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)) (if current-prefix-arg (list (read-string "Complex align using regexp: " - "\\(\\s-*\\)") + "\\(\\s-*\\)" 'align-regexp-history) (string-to-number (read-string "Parenthesis group to modify (justify if negative): " "1")) @@ -1116,13 +1133,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) @@ -1201,7 +1213,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)) @@ -1308,7 +1323,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)) @@ -1336,7 +1351,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)) @@ -1386,217 +1401,207 @@ aligner would have dealt with are." ;; reports back that the region is ok, then align it. (when (or (not func) (funcall func beg end rule)) - (unwind-protect - (let (rule-beg 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 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)) - - ;; 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 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). - (set-marker end-mark nil) + (when end-mark (set-marker end-mark nil)) (dolist (m markers) (set-marker m nil))