>From b35c21cdb2d3c051bd374bcd2f9e3fe16ffb9380 Mon Sep 17 00:00:00 2001 From: Earl Hyatt Date: Sat, 19 Jun 2021 08:30:31 -0400 Subject: [PATCH] Add commands 'kill-lines' and 'copy-lines' * doc/emacs/search.texi: Document these additions. * lisp/replace.el: Add the commands 'kill-lines' and 'copy-lines'. 'kill-lines' (alias 'kill-matching-lines') is like 'flush-lines', but adds the lines to the kill ring as a single item. 'copy-lines' (alias 'copy-matching-lines') is like 'kill-lines', but only copies those lines instead of killing them. --- doc/emacs/search.texi | 10 ++++ etc/NEWS | 4 ++ lisp/replace.el | 130 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 144 insertions(+) diff --git a/doc/emacs/search.texi b/doc/emacs/search.texi index e6b066e973..2ddb44c310 100644 --- a/doc/emacs/search.texi +++ b/doc/emacs/search.texi @@ -1971,6 +1971,16 @@ 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-lines +@item M-x kill-lines +Like @code{flush-lines}, but also add the matching lines to the kill +ring as a single item. + +@findex copy-lines +@item M-x copy-lines +Like @code{kill-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..42210315b6 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -461,6 +461,10 @@ 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-lines' and 'kill-lines'. +These commands are similar to the command 'flush-lines', +but add the matching lines to the kill ring as a single item. + * Changes in Specialized Modes and Packages in Emacs 28.1 diff --git a/lisp/replace.el b/lisp/replace.el index fe2cbc447a..edbd85f2b3 100644 --- a/lisp/replace.el +++ b/lisp/replace.el @@ -912,6 +912,8 @@ read-regexp (defalias 'delete-non-matching-lines 'keep-lines) (defalias 'delete-matching-lines 'flush-lines) +(defalias 'kill-matching-lines 'kill-lines) +(defalias 'copy-matching-lines 'copy-lines) (defalias 'count-matches 'how-many) (defun keep-lines-read-args (prompt) @@ -1054,6 +1056,134 @@ flush-lines count)) count)) +(defun kill-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 arg 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 it. + +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. When +calling this function from Lisp, you can pretend that it was +called interactively by passing a non-nil INTERACTIVE argument. + +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-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 arg 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 it. + +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. When +calling this function from Lisp, you can pretend that it was +called interactively by passing a non-nil INTERACTIVE argument. + +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 + (progn + (barf-if-buffer-read-only) + (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