diff options
| author | Augusto Stoffel | 2022-12-07 18:44:07 +0100 |
|---|---|---|
| committer | Juri Linkov | 2023-02-27 21:14:47 +0200 |
| commit | 93f557af0ef85ce301bb0780e26351eb8809e91c (patch) | |
| tree | ebf239aecd0e86cc4384ca40acc8c5bea2e910c7 | |
| parent | b699c380286151c97ffae65010d733a092d2db14 (diff) | |
| download | emacs-93f557af0ef85ce301bb0780e26351eb8809e91c.tar.gz emacs-93f557af0ef85ce301bb0780e26351eb8809e91c.zip | |
New user option 'grep-use-headings'
* lisp/progmodes/grep.el (grep-heading-regexp): New user option.
(grep-heading): New face (bug#59888).
(grep--heading-format, grep--heading-state, grep--heading-filter):
Filter function for grep processes and supporting variables.
(grep-use-headings): New user option.
(grep-mode): Use the above, if applicable.
| -rw-r--r-- | etc/NEWS | 9 | ||||
| -rw-r--r-- | lisp/progmodes/grep.el | 66 | ||||
| -rw-r--r-- | test/lisp/progmodes/grep-tests.el | 14 |
3 files changed, 89 insertions, 0 deletions
| @@ -95,6 +95,15 @@ If you want to get back the old behavior, set the user option to the value | |||
| 95 | (setopt gdb-locals-table-row-config | 95 | (setopt gdb-locals-table-row-config |
| 96 | `((type . 0) (name . 0) (value . ,gdb-locals-value-limit))) | 96 | `((type . 0) (name . 0) (value . ,gdb-locals-value-limit))) |
| 97 | 97 | ||
| 98 | ** Compile | ||
| 99 | |||
| 100 | *** New user option 'grep-use-headings'. | ||
| 101 | When non-nil, the output of Grep is split into sections, one for each | ||
| 102 | file, instead of having file names prefixed to each line. It is | ||
| 103 | equivalent to the --heading option of some tools such as 'git grep' | ||
| 104 | and 'rg'. The headings are displayed using the new 'grep-heading' | ||
| 105 | face. | ||
| 106 | |||
| 98 | ** VC | 107 | ** VC |
| 99 | 108 | ||
| 100 | --- | 109 | --- |
diff --git a/lisp/progmodes/grep.el b/lisp/progmodes/grep.el index 0da16b44dda..82e9c5d8edf 100644 --- a/lisp/progmodes/grep.el +++ b/lisp/progmodes/grep.el | |||
| @@ -457,6 +457,33 @@ buffer `default-directory'." | |||
| 457 | :type '(repeat (choice (const :tag "Default" nil) | 457 | :type '(repeat (choice (const :tag "Default" nil) |
| 458 | (string :tag "Directory")))) | 458 | (string :tag "Directory")))) |
| 459 | 459 | ||
| 460 | (defcustom grep-use-headings nil | ||
| 461 | "If non-nil, subdivide grep output into sections, one per file." | ||
| 462 | :type 'boolean | ||
| 463 | :version "30.1") | ||
| 464 | |||
| 465 | (defface grep-heading `((t :inherit ,grep-hit-face)) | ||
| 466 | "Face of headings when `grep-use-headings' is non-nil." | ||
| 467 | :version "30.1") | ||
| 468 | |||
| 469 | (defvar grep-heading-regexp | ||
| 470 | (rx bol | ||
| 471 | (or | ||
| 472 | (group-n 2 | ||
| 473 | (group-n 1 (+ (not (any 0 ?\n)))) | ||
| 474 | 0) | ||
| 475 | (group-n 2 | ||
| 476 | (group-n 1 (+? nonl)) | ||
| 477 | (any ?: ?- ?=))) | ||
| 478 | (+ digit) | ||
| 479 | (any ?: ?- ?=)) | ||
| 480 | "Regexp used to create headings from grep output lines. | ||
| 481 | It should be anchored at beginning of line. The first capture | ||
| 482 | group, if present, should match the heading associated to the | ||
| 483 | line. The buffer range of the second capture, if present, is | ||
| 484 | made invisible (presumably because displaying it would be | ||
| 485 | redundant).") | ||
| 486 | |||
| 460 | (defvar grep-find-abbreviate-properties | 487 | (defvar grep-find-abbreviate-properties |
| 461 | (let ((ellipsis (if (char-displayable-p ?…) "[…]" "[...]")) | 488 | (let ((ellipsis (if (char-displayable-p ?…) "[…]" "[...]")) |
| 462 | (map (make-sparse-keymap))) | 489 | (map (make-sparse-keymap))) |
| @@ -612,6 +639,40 @@ This function is called from `compilation-filter-hook'." | |||
| 612 | (while (re-search-forward "\033\\[[0-9;]*[mK]" end 1) | 639 | (while (re-search-forward "\033\\[[0-9;]*[mK]" end 1) |
| 613 | (replace-match "" t t)))))) | 640 | (replace-match "" t t)))))) |
| 614 | 641 | ||
| 642 | (defvar grep--heading-format | ||
| 643 | (eval-when-compile | ||
| 644 | (let ((title (propertize "%s" | ||
| 645 | 'font-lock-face 'grep-heading | ||
| 646 | 'outline-level 1))) | ||
| 647 | (propertize (concat title "\n") 'compilation-annotation t))) | ||
| 648 | "Format string of grep headings. | ||
| 649 | This is passed to `format' with one argument, the text of the | ||
| 650 | first capture group of `grep-heading-regexp'.") | ||
| 651 | |||
| 652 | (defvar-local grep--heading-state nil | ||
| 653 | "Variable to keep track of the `grep--heading-filter' state.") | ||
| 654 | |||
| 655 | (defun grep--heading-filter () | ||
| 656 | "Filter function to add headings to output of a grep process." | ||
| 657 | (unless grep--heading-state | ||
| 658 | (setq grep--heading-state (cons (point-min-marker) nil))) | ||
| 659 | (save-excursion | ||
| 660 | (let ((limit (car grep--heading-state))) | ||
| 661 | ;; Move point to the old limit and update limit marker. | ||
| 662 | (move-marker limit (prog1 (pos-bol) (goto-char limit))) | ||
| 663 | (while (re-search-forward grep-heading-regexp limit t) | ||
| 664 | (unless (get-text-property (point) 'compilation-annotation) | ||
| 665 | (let ((heading (match-string-no-properties 1)) | ||
| 666 | (start (match-beginning 2)) | ||
| 667 | (end (match-end 2))) | ||
| 668 | (when start | ||
| 669 | (put-text-property start end 'invisible t)) | ||
| 670 | (when (and heading (not (equal heading (cdr grep--heading-state)))) | ||
| 671 | (save-excursion | ||
| 672 | (goto-char (pos-bol)) | ||
| 673 | (insert-before-markers (format grep--heading-format heading))) | ||
| 674 | (setf (cdr grep--heading-state) heading)))))))) | ||
| 675 | |||
| 615 | (defun grep-probe (command args &optional func result) | 676 | (defun grep-probe (command args &optional func result) |
| 616 | (let (process-file-side-effects) | 677 | (let (process-file-side-effects) |
| 617 | (equal (condition-case nil | 678 | (equal (condition-case nil |
| @@ -906,6 +967,11 @@ The value depends on `grep-command', `grep-template', | |||
| 906 | (add-function :filter-return (local 'kill-transform-function) | 967 | (add-function :filter-return (local 'kill-transform-function) |
| 907 | (lambda (string) | 968 | (lambda (string) |
| 908 | (string-replace "\0" ":" string))) | 969 | (string-replace "\0" ":" string))) |
| 970 | (when grep-use-headings | ||
| 971 | (add-hook 'compilation-filter-hook #'grep--heading-filter 80 t) | ||
| 972 | (setq-local outline-search-function #'outline-search-level | ||
| 973 | outline-level (lambda () (get-text-property | ||
| 974 | (point) 'outline-level)))) | ||
| 909 | (add-hook 'compilation-filter-hook #'grep-filter nil t)) | 975 | (add-hook 'compilation-filter-hook #'grep-filter nil t)) |
| 910 | 976 | ||
| 911 | (defun grep--save-buffers () | 977 | (defun grep--save-buffers () |
diff --git a/test/lisp/progmodes/grep-tests.el b/test/lisp/progmodes/grep-tests.el index 39307999d6d..9b7f83086bf 100644 --- a/test/lisp/progmodes/grep-tests.el +++ b/test/lisp/progmodes/grep-tests.el | |||
| @@ -66,4 +66,18 @@ | |||
| 66 | (cl-letf (((symbol-function 'w32-shell-dos-semantics) #'ignore)) | 66 | (cl-letf (((symbol-function 'w32-shell-dos-semantics) #'ignore)) |
| 67 | (grep-tests--check-rgrep-abbreviation)))) | 67 | (grep-tests--check-rgrep-abbreviation)))) |
| 68 | 68 | ||
| 69 | (ert-deftest grep-tests--grep-heading-regexp-without-null () | ||
| 70 | (dolist (sep '(?: ?- ?=)) | ||
| 71 | (let ((string (format "filename%c123%ctext" sep sep))) | ||
| 72 | (should (string-match grep-heading-regexp string)) | ||
| 73 | (should (equal (match-string 1 string) "filename")) | ||
| 74 | (should (equal (match-string 2 string) (format "filename%c" sep)))))) | ||
| 75 | |||
| 76 | (ert-deftest grep-tests--grep-heading-regexp-with-null () | ||
| 77 | (dolist (sep '(?: ?- ?=)) | ||
| 78 | (let ((string (format "funny:0:filename%c123%ctext" 0 sep))) | ||
| 79 | (should (string-match grep-heading-regexp string)) | ||
| 80 | (should (equal (match-string 1 string) "funny:0:filename")) | ||
| 81 | (should (equal (match-string 2 string) "funny:0:filename\0"))))) | ||
| 82 | |||
| 69 | ;;; grep-tests.el ends here | 83 | ;;; grep-tests.el ends here |