Subject: bug#64198: Feature request: compile.el's "Find this in" prompt should be hookable.

From: Dave Abrahams <[email-masked]>
Date: Thu, 22 Jun 2023 14:49:56 -0700

> On Jun 22, 2023, at 10:58 AM, Daniel Martín <[email-masked]> wrote:
> 
> Dave Abrahams <[email-masked] <mailto:[email-masked]>> writes:
>> 
>> Swift is a programming language.  When its assertions (like C's
>> assert() macro) fail, a diagnostic is output with a file ID and
>> line. The file ID looks like a path, but it's really not. If it's a
>> path in the filesystem, that's coincidental but it's likely to point
>> to the right file.  I don't want to manually hunt down the file every
>> time an assertion fails; I'd rather emacs made an educated guess based
>> on the current project.  It's obviously not the right answer for
>> everyone, but I'd like to have the hooks needed to implement that
>> without too much hackery.
> 
> This is the Elisp code you posted on GitHub, based on customizing
> compilation-parse-errors-filename-function:
> 
> (defun dwa/path-tails-matching (path paths)
>  "Returns the elements of PATHS with PATH as their suffix, matching by path component."
>  (let ((tail (file-name-concat "/" path)))
>    (seq-filter (lambda (full-path) (string-suffix-p tail full-path)) paths)))
> 
> (defun dwa/compilation-parse-errors-filename-in-project (path-in-error)
>  "If PATH-IN-ERROR is found in the filesystem, returns it
> unchanged; otherwise tries to return a unique file in the current
> project likely to be identified by PATH-IN-ERROR.  Returns
> PATH-IN-ERROR unchanged if no such file can be found."
>  (if (file-exists-p path-in-error) path-in-error
>    ;; First look for relative path suffixes.
>    (let* ((candidates (project-files (project-current t)))
> 	   (full-matches (dwa/path-tails-matching path-in-error candidates)))
> 
>      ;; If not found, try just the filename component of
>      ;; path-in-error; Swift assertions are weird and include a
>      ;; module name that doesn't necessarily correspond to anything in the
>      ;; filesystem.
>      (cond ((null full-matches)
> 	     (let ((filename-matches
> 		   (dwa/path-tails-matching (file-name-nondirectory path-in-error) candidates)))
> 	       (cond ((null filename-matches) path-in-error)
> 		     ;; unique match; return it
> 		     ((null (cadr filename-matches)) (car filename-matches))
> 		     ;; multiple matches; consider prompting for the user to select one.
> 		     (t path-in-error))))
> 	    ;; unique match; return it
> 	    ((null (cadr matches)) (car matches))
> 	     ; multiple matches; consider prompting for the user to select one.
> 	    (t path-in-error)))))
> 
> (setq compilation-parse-errors-filename-function 'dwa/compilation-parse-errors-filename-in-project)
> 
> I've tried it and it seems to work well, although I agree that the code
> is a bit hacky and prone to error.  How would the new hooks you propose
> simplify this customization code?

It would not.  The problem with this code is that it runs too early.  That means two things:

1. if you get 1000 errors in different files, this code will try to resolve each one, accessing the filesystem during parsing, before the compilation buffer even font locks, which is really inefficient especially if you only care about the first of these errors.
2. There's no good way to prompt the user for a match when there are multiple matches.
 
I want a hook that will be run at the time compile.el currently prompts me to locate the file in the filesystem: the first time I visit an error at a given path and no file is found there by emacs.

> Similar use cases have been discussed in the mailing list in the past.
> Someone proposed to make compilation-find-file customizable, which may
> not be a bad idea.  See
> https://lists.gnu.org/archive/html/emacs-devel/2021-04/msg00910.html

Yes, I think I could probably get where I need to by advising compilation-find-file.