aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAugusto Stoffel2022-12-07 18:44:07 +0100
committerJuri Linkov2023-02-27 21:14:47 +0200
commit93f557af0ef85ce301bb0780e26351eb8809e91c (patch)
treeebf239aecd0e86cc4384ca40acc8c5bea2e910c7
parentb699c380286151c97ffae65010d733a092d2db14 (diff)
downloademacs-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/NEWS9
-rw-r--r--lisp/progmodes/grep.el66
-rw-r--r--test/lisp/progmodes/grep-tests.el14
3 files changed, 89 insertions, 0 deletions
diff --git a/etc/NEWS b/etc/NEWS
index 9241598f185..31fb22fc1e2 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -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'.
101When non-nil, the output of Grep is split into sections, one for each
102file, instead of having file names prefixed to each line. It is
103equivalent to the --heading option of some tools such as 'git grep'
104and 'rg'. The headings are displayed using the new 'grep-heading'
105face.
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.
481It should be anchored at beginning of line. The first capture
482group, if present, should match the heading associated to the
483line. The buffer range of the second capture, if present, is
484made invisible (presumably because displaying it would be
485redundant).")
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.
649This is passed to `format' with one argument, the text of the
650first 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