Subject: Re: bug#49731: 28.0.50; Filter xref results by filename

From: Daniel Martín <[email-masked]>
Date: Tue, 27 Jul 2021 19:08:00 +0200

Dmitry Gutov <[email-masked]> writes:

>
> We've discussed this sort of functionality before. Here are some
> approaches (not mutually exclusive):
>
> 1. Add the possibility to add filtering by file names, types, etc,
> before the search is done. This should fit 'project-find-regexp'
> well. I can point you to a previous discussion with some ideas. The
> main upside is you can speed up the search. And store such settings as
> a history.

I think that kind of search scoping in advance can be specially useful
when you are doing a grep-like search in the codebase, using either
grep, rgrep, project-find-regexp, or xref-find-apropos.

I see it less useful for example when you place the point in an
identifier and press M-?.  You'll want to see all the references first,
and then filter afterwards if they are too many.  But I think it's a
matter of personal preferences and different workflows.

>
> 2. Filter in the resulting Xref buffer. The best part is it can work
> with the output from any command that uses Xref. The "filtering" is 
> temporary. I'm assuming this is the direction you want to work in.
>

Yes, that's the direction that interests me the most, if it's actually a
worthy feature for Emacs users.

>
> I've never exactly considered the option 2., but I'd be happy to talk
> the details. WRT UI, maybe something along the lines of 
> package-menu-filter-* commands, bound inside a '/' prefix. One command
> could add "inclusion filter", another - "exclusion filter", and the 
> third one - reset all filters. '/ /' be bound to the last one.
>

I didn't have in mind implementing cumulative filters.  I don't know if
people would need such advanced filtering of results.  FTR, I've
researched how other tools and IDEs implement this feature, which is
less common than what I initially thought:

- Xcode: In the references panel there is a filter box on the bottom so
  that you type and filter the results to keep those that match the
  pattern.

- IntelliJ IDEA: I haven't seen a similar functionality.  There is a
  button to remove references from automatically generated code, but
  that's all.

- Sourcegraph: It doesn't seem to offer a similar functionality.

- Chromium Code Search: It offers a box to filter by file path.  It also
  offers an option to exclude tests and generated files.

>
> Another thing to keep an eye out for - is how the filtering will
> affect n/p navigation and the xref-query-replace-in-results command. I
> think they should respect the filtering as well.

Here's a first quick and dirty prototype based on Juri's code snippet:

diff --git a/lisp/progmodes/xref.el b/lisp/progmodes/xref.el
index e2cd904a6c..27ff08f7ce 100644
--- a/lisp/progmodes/xref.el
+++ b/lisp/progmodes/xref.el
@@ -665,6 +665,36 @@ xref-goto-xref
       ;; Emacs < 27
       (setq next-error-last-buffer buffer))))
 
+(declare-function outline-show-entry "outline" ())
+(declare-function outline-hide-body "outline" ())
+
+(defun xref-filter-results-by-file (pattern)
+  "Filter xref results to only include those in files that match PATTERN."
+  (interactive (list (read-string
+                      "Filter results in files that match pattern (regexp): "
+                      nil nil)))
+  (require 'outline)
+  (setq-local outline-regexp
+              (if (eq xref-file-name-display 'abs) "/" "[^ 0-9]"))
+  (outline-minor-mode +1)
+  (outline-hide-body)
+  (save-excursion
+    (goto-char (point-min))
+    (let ((count 0))
+      (while (progn
+             (when (re-search-forward pattern (line-end-position) t)
+               (outline-show-entry)
+               (setq count (1+ count)))
+             (xref--search-property 'xref-group)))
+      (when (zerop count)
+        (message "No match")
+        (xref-exit-results-filter)))))
+
+(defun xref-exit-results-filter ()
+  "Remove any xref filter and show the full list of results."
+  (interactive)
+  (outline-minor-mode -1))
+
 (defun xref-quit-and-goto-xref ()
   "Quit *xref* buffer, then jump to xref on current line."
   (interactive)
@@ -824,6 +854,8 @@ xref--xref-buffer-mode-map
     (define-key map (kbd ",") #'xref-prev-line)
     (define-key map (kbd "g") #'xref-revert-buffer)
     (define-key map (kbd "M-,") #'xref-quit-and-pop-marker-stack)
+    (define-key map (kbd "f") #'xref-filter-results-by-file)
+    (define-key map (kbd "q") #'xref-exit-results-filter)
     map))
 
 (define-derived-mode xref--xref-buffer-mode special-mode "XREF"

I've bound the new command to "f".  For simplicity, each time you press
"f" you'll filter the entire list (filters are not cumulative).  As you
said, pressing "p" and "n" navigate results that are folded, which is
confusing.  Perhaps a new minor mode in xref could do the outline
folding and also make sure that "p" and "n" skip results that are
folded.