emacs-lsp / lsp-mode

Emacs client/library for the Language Server Protocol
https://emacs-lsp.github.io/lsp-mode
GNU General Public License v3.0
4.81k stars 893 forks source link

Allow filtering of xref results #2103

Open muirdm opened 4 years ago

muirdm commented 4 years ago

I want to be able to filter out xref results within test files. For example, if I'm finding references to function foo which is called 50 times in tests but only 2 times in code, I want to be able to see a list with just the 2 code references. I think this is useful in general so I wanted to hear what others think.

It seems like there are a few approaches:

  1. Add an lsp-mode hook users can implement to filter the xref items before they are displayed. This is relatively flexible if we make it an argument passed in to lsp-find-references since then the user can define their own my-find-references that handles e.g. c-u and then delegates to lsp-find-references with or without the filter. My example below could use this.
  2. Add higher level support where, for example, lsp clients can define a test-file-p predicate, and then we add an optional exclude-tests parameter to lsp-find-references similar to include-declaration.
  3. Tweak xref mode so the user can filter results after generating them (e.g. xref-hide-matching-files). Maybe this is already possible somehow? I can kind of do it using ivy-xref by filtering in ivy then using ivy-occur to expand to an occur buffer with filtered results, but I'm not sure I want to use ivy for xref display.

Below is what I am using to test the functionality:

(defun muir-find-references (arg)
  (interactive "P")
  (when (not arg)
    (advice-add 'lsp--locations-to-xref-items :around #'muir-filter-tests))
  (lsp-find-references)
  (advice-remove 'lsp--locations-to-xref-items #'muir-filter-tests))

(define-key go-mode-map (kbd "M-?") 'muir-find-references)

(defun muir-go-non-test (loc)
  (setq loc (lsp--location-uri loc))
  (and
   (string-suffix-p ".go" loc)
   (not (string-suffix-p "_test.go" loc))))

(defun muir-filter-tests (orig-fun &rest locs)
  (setq locs (car locs))

  (when-let ((non-test (seq-filter #'muir-go-non-test locs)))
    (setq locs non-test))

  (funcall orig-fun locs))
yyoncho commented 4 years ago

helm/ivy support that, no? I mean you can type in the results window.

muirdm commented 4 years ago

helm/ivy support that, no? You can do it with ivy using a separate package ivy-xref and then ivy-occur to pop it out into a full buffer you can iterate over. I don't use helm, but I assume it works too.

I very rarely want to see references in tests so I wanted a separate command that would exclude tests by default. If no one else is interested I can just stick to my own devices.

yyoncho commented 4 years ago

If no one else is interested I can just stick to my own devices.

As a minimum, we should create an extension point to avoid having to use private functions.

What we have to figure out here is how to use the filter. I mean you have static filter you have to use code to turn it off/on. Helm has the functionality C-c a which stands for toggle the filter but xref does not have that functionality.

An alternative approach is introducing the ability to create new commands that show the set that you want easily.

yyoncho commented 4 years ago

what we can do is introduce a configurable filter function and then C-u M-x lsp-find-references will apply the filter without the prefix it wont.

muirdm commented 4 years ago

what we can do is introduce a configurable filter function and then C-u M-x lsp-find-references will apply the filter without the prefix it wont.

That sounds useful.

An alternative approach is introducing the ability to create new commands that show the set that you want easily.

I think we should consider this also since lsp-find-references already has C-u behavior, and users may want different kinds of filtering (no tests, tests only, only within a sub-directory of the project, etc).

rossabaker commented 4 years ago

Another use case: I'm trying out lsp-pyright, which is finding both the original Python source and *.pyi stubs as definitions. The stubs are helpful for documenting types, but don't add any value as definitions. I ended up hacking this together:

  (defun ross/xref-python-stub-p (item)
    "Return t if `item' is from a *.pyi stub."
    (string-suffix-p ".pyi" (oref (xref-item-location item) file)))

  (defun ross/filter-not-python-stub-definitions (items)
    "Remove Python stubs from a list of xref-items."
    (cl-remove-if #'ross/xref-python-stub-p items))

  (advice-add 'lsp--locations-to-xref-items
              :filter-return #'ross/filter-not-python-stub-definitions)

A proper extension point for definitions, as well as references, would be useful here.

vjohansen commented 10 months ago

oref .. file does not work for me.

I use

(defun ross/xref-python-stub-p (item)
  (string-suffix-p ".pyi" (xref-location-group  (xref-item-location item))))