From 891f360919244e966ad1278a2c364b038c8156c4 Mon Sep 17 00:00:00 2001 From: Earl Hyatt Date: Sat, 19 Jun 2021 08:30:31 -0400 Subject: [PATCH] Add commands 'kill-matching-lines' and 'copy-matching-lines' * doc/emacs/search.texi: Document these additions. * lisp/replace.el: Add the commands 'kill-matching-lines' and 'copy-matching-lines'. 'kill-matching-lines' is like 'flush-lines', but adds the lines to the kill ring as a single string, keeping line endings. 'copy-matching-lines' is like 'kill-matching-lines', but only copies those lines instead of killing them. --- doc/emacs/search.texi | 11 ++++ etc/NEWS | 5 ++ lisp/replace.el | 124 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 140 insertions(+) diff --git a/doc/emacs/search.texi b/doc/emacs/search.texi index e6b066e973..a1760ad66f 100644 --- a/doc/emacs/search.texi +++ b/doc/emacs/search.texi @@ -1971,6 +1971,17 @@ Other Repeating Search (a newline that ends a line counts as part of that line). If a match is split across lines, this command keeps all those lines. + +@findex kill-matching-lines +@item M-x kill-matching-lines +Like @code{flush-lines}, but also add the matching lines to the kill +ring. The command adds the matching lines to the kill ring as a +single string, including the newlines that separated the lines. + +@findex copy-matching-lines +@item M-x copy-matching-lines +Like @code{kill-matching-lines}, but the matching lines are not +removed from the buffer. @end table @node Search Customizations diff --git a/etc/NEWS b/etc/NEWS index 1693342f0a..eb862538da 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -461,6 +461,11 @@ highlighting on heading lines using standard outline faces. This works well only when there are no conflicts with faces used by the major mode. +** New commands 'copy-matching-lines' and 'kill-matching-lines'. +These commands are similar to the command 'flush-lines', +but add the matching lines to the kill ring as a single string, +including the newlines that separate the lines. + * Changes in Specialized Modes and Packages in Emacs 28.1 diff --git a/lisp/replace.el b/lisp/replace.el index fe2cbc447a..051112770e 100644 --- a/lisp/replace.el +++ b/lisp/replace.el @@ -1054,6 +1054,130 @@ flush-lines count)) count)) +(defun kill-matching-lines (regexp &optional rstart rend interactive) + "Kill lines containing matches for REGEXP. + +When called from Lisp (and usually when called interactively as +well, see below), applies to the part of the buffer after point. +The line point is in is killed if and only if it contains a match +for REGEXP starting after point. + +If REGEXP contains upper case characters (excluding those +preceded by `\\') and `search-upper-case' is non-nil, the +matching is case-sensitive. + +Second and third args RSTART and REND specify the region to +operate on. Lines partially contained in this region are killed +if and only if they contain a match entirely contained in the +region. + +Interactively, in Transient Mark mode when the mark is active, +operate on the contents of the region. Otherwise, operate from +point to the end of (the accessible portion of) the buffer. + +If a match is split across lines, all the lines it lies in are +killed. They are killed _before_ looking for the next match. +Hence, a match starting on the same line at which another match +ended is ignored. + +Return the number of killed matching lines. When called +interactively, also print the number." + (interactive + (progn + (barf-if-buffer-read-only) + (keep-lines-read-args "Kill lines containing match for regexp"))) + (if rstart + (progn + (goto-char (min rstart rend)) + (setq rend (copy-marker (max rstart rend)))) + (if (and interactive (use-region-p)) + (setq rstart (region-beginning) + rend (copy-marker (region-end))) + (setq rstart (point) + rend (point-max-marker))) + (goto-char rstart)) + (let ((count 0) + (case-fold-search + (if (and case-fold-search search-upper-case) + (isearch-no-upper-case-p regexp t) + case-fold-search))) + (save-excursion + (while (and (< (point) rend) + (re-search-forward regexp rend t)) + (unless (zerop count) + (setq last-command 'kill-region)) + (kill-region (save-excursion (goto-char (match-beginning 0)) + (forward-line 0) + (point)) + (progn (forward-line 1) (point))) + (setq count (1+ count)))) + (set-marker rend nil) + (when interactive (message (ngettext "Killed %d matching line" + "Killed %d matching lines" + count) + count)) + count)) + +(defun copy-matching-lines (regexp &optional rstart rend interactive) + "Copy lines containing matches for REGEXP to the kill ring. + +When called from Lisp (and usually when called interactively as +well, see below), applies to the part of the buffer after point. +The line point is in is copied if and only if it contains a match +for REGEXP starting after point. + +If REGEXP contains upper case characters (excluding those +preceded by `\\') and `search-upper-case' is non-nil, the +matching is case-sensitive. + +Second and third args RSTART and REND specify the region to +operate on. Lines partially contained in this region are copied +if and only if they contain a match entirely contained in the +region. + +Interactively, in Transient Mark mode when the mark is active, +operate on the contents of the region. Otherwise, operate from +point to the end of (the accessible portion of) the buffer. + +If a match is split across lines, all the lines it lies in are +copied. + +Return the number of copied matching lines. When called +interactively, also print the number." + (interactive + (keep-lines-read-args "Copy lines containing match for regexp")) + (if rstart + (progn + (goto-char (min rstart rend)) + (setq rend (copy-marker (max rstart rend)))) + (if (and interactive (use-region-p)) + (setq rstart (region-beginning) + rend (copy-marker (region-end))) + (setq rstart (point) + rend (point-max-marker))) + (goto-char rstart)) + (let ((count 0) + (case-fold-search + (if (and case-fold-search search-upper-case) + (isearch-no-upper-case-p regexp t) + case-fold-search))) + (save-excursion + (while (and (< (point) rend) + (re-search-forward regexp rend t)) + (unless (zerop count) + (setq last-command 'kill-region)) + (copy-region-as-kill (save-excursion (goto-char (match-beginning 0)) + (forward-line 0) + (point)) + (progn (forward-line 1) (point))) + (setq count (1+ count)))) + (set-marker rend nil) + (when interactive (message (ngettext "Copied %d matching line" + "Copied %d matching lines" + count) + count)) + count)) + (defun how-many (regexp &optional rstart rend interactive) "Print and return number of matches for REGEXP following point. When called from Lisp and INTERACTIVE is omitted or nil, just return -- 2.25.1