1
Fork 0
mirror of git://git.sv.gnu.org/emacs.git synced 2025-12-17 11:20:39 -08:00

New variable 'project-files-relative-names'

* lisp/progmodes/project.el (project-files-relative-names):
New variable (bug#69233).
(project--files-in-directory): Honor it.
(project--vc-list-files): Here too.
(project-find-regexp): Use it to improve performance.
(project-or-external-find-regexp): Add a TODO.
(project-find-file): Use it here too.
(project--read-file-cpd-relative, project--read-file-absolute):
Try to handle file lists with absolute and relative files names.
(project-find-file-in): Set default-directory, so relative names
are interpreted correctly.

* lisp/progmodes/xref.el (xref-matches-in-files):
Consider that the first in FILES can be a relative file name.

* test/lisp/progmodes/project-tests.el (project-find-regexp):
New test.

* etc/NEWS: Mention it.
This commit is contained in:
Dmitry Gutov 2024-05-05 06:27:39 +03:00
parent e0993f5169
commit 370b216f08
4 changed files with 78 additions and 17 deletions

View file

@ -696,6 +696,10 @@ you can add this to your init script:
(setopt project-switch-commands #'project-prefix-or-any-command) (setopt project-switch-commands #'project-prefix-or-any-command)
*** New variable 'project-files-relative-names'.
Project backends can support it to improve the performance of their
'project-files' implementation when this variable is non-nil.
** VC ** VC
--- ---

View file

@ -323,6 +323,12 @@ end it with `/'. DIR must be either `project-root' or one of
(cl-defmethod project-root ((project (head transient))) (cl-defmethod project-root ((project (head transient)))
(cdr project)) (cdr project))
(defvar project-files-relative-names nil
"When non-nil, `project-files' is allowed to return relative names.
The names will be relative to the project root. And this can only
happen when all returned files are in the same directory. Meaning, the
DIRS argument has to be nil or have only one element.")
(cl-defgeneric project-files (project &optional dirs) (cl-defgeneric project-files (project &optional dirs)
"Return a list of files in directories DIRS in PROJECT. "Return a list of files in directories DIRS in PROJECT.
DIRS is a list of absolute directories; it should be some DIRS is a list of absolute directories; it should be some
@ -345,7 +351,6 @@ to find the list of ignores for each directory."
;; expanded and not left for the shell command ;; expanded and not left for the shell command
;; to interpret. ;; to interpret.
(localdir (file-name-unquote (file-local-name (expand-file-name dir)))) (localdir (file-name-unquote (file-local-name (expand-file-name dir))))
(dfn (directory-file-name localdir))
(command (format "%s -H . %s -type f %s -print0" (command (format "%s -H . %s -type f %s -print0"
find-program find-program
(xref--find-ignores-arguments ignores "./") (xref--find-ignores-arguments ignores "./")
@ -376,12 +381,14 @@ to find the list of ignores for each directory."
(error "File listing failed: %s" (buffer-string)))) (error "File listing failed: %s" (buffer-string))))
(goto-char pt) (goto-char pt)
(while (search-forward "\0" nil t) (while (search-forward "\0" nil t)
(push (buffer-substring-no-properties (1+ pt) (1- (point))) (push (buffer-substring-no-properties (+ pt 2) (1- (point)))
res) res)
(setq pt (point))))) (setq pt (point)))))
(if project-files-relative-names
(sort res #'string<)
(project--remote-file-names (project--remote-file-names
(mapcar (lambda (s) (concat dfn s)) (mapcar (lambda (s) (concat localdir s))
(sort res #'string<))))) (sort res #'string<))))))
(defun project--remote-file-names (local-files) (defun project--remote-file-names (local-files)
"Return LOCAL-FILES as if they were on the system of `default-directory'. "Return LOCAL-FILES as if they were on the system of `default-directory'.
@ -689,7 +696,9 @@ See `project-vc-extra-root-markers' for the marker value format.")
(mapcar (mapcar
(lambda (file) (lambda (file)
(unless (member file submodules) (unless (member file submodules)
(concat default-directory file))) (if project-files-relative-names
file
(concat default-directory file))))
(split-string (split-string
(apply #'vc-git--run-command-string nil "ls-files" args) (apply #'vc-git--run-command-string nil "ls-files" args)
"\0" t)))) "\0" t))))
@ -716,7 +725,8 @@ See `project-vc-extra-root-markers' for the marker value format.")
dir)) dir))
(args (list (concat "-mcard" (and include-untracked "u")) (args (list (concat "-mcard" (and include-untracked "u"))
"--no-status" "--no-status"
"-0"))) "-0"))
files)
(when extra-ignores (when extra-ignores
(setq args (nconc args (setq args (nconc args
(mapcan (mapcan
@ -725,9 +735,12 @@ See `project-vc-extra-root-markers' for the marker value format.")
extra-ignores)))) extra-ignores))))
(with-temp-buffer (with-temp-buffer
(apply #'vc-hg-command t 0 "." "status" args) (apply #'vc-hg-command t 0 "." "status" args)
(mapcar (setq files (split-string (buffer-string) "\0" t))
(unless project-files-relative-names
(setq files (mapcar
(lambda (s) (concat default-directory s)) (lambda (s) (concat default-directory s))
(split-string (buffer-string) "\0" t))))))) files)))
files)))))
(defun project--vc-merge-submodules-p (dir) (defun project--vc-merge-submodules-p (dir)
(project--value-in-dir (project--value-in-dir
@ -970,6 +983,7 @@ requires quoting, e.g. `\\[quoted-insert]<space>'."
(let* ((caller-dir default-directory) (let* ((caller-dir default-directory)
(pr (project-current t)) (pr (project-current t))
(default-directory (project-root pr)) (default-directory (project-root pr))
(project-files-relative-names t)
(files (files
(if (not current-prefix-arg) (if (not current-prefix-arg)
(project-files pr) (project-files pr)
@ -1000,6 +1014,8 @@ requires quoting, e.g. `\\[quoted-insert]<space>'."
(require 'xref) (require 'xref)
(let* ((pr (project-current t)) (let* ((pr (project-current t))
(default-directory (project-root pr)) (default-directory (project-root pr))
;; TODO: Make use of `project-files-relative-names' by
;; searching each root separately (maybe in parallel, too).
(files (files
(project-files pr (cons (project-files pr (cons
(project-root pr) (project-root pr)
@ -1054,7 +1070,8 @@ for VCS directories listed in `vc-directory-exclusion-list'."
(interactive "P") (interactive "P")
(let* ((pr (project-current t)) (let* ((pr (project-current t))
(root (project-root pr)) (root (project-root pr))
(dirs (list root))) (dirs (list root))
(project-files-relative-names t))
(project-find-file-in (project-find-file-in
(or (thing-at-point 'filename) (or (thing-at-point 'filename)
(and buffer-file-name (project--find-default-from buffer-file-name pr))) (and buffer-file-name (project--find-default-from buffer-file-name pr)))
@ -1130,7 +1147,12 @@ by the user at will."
(if (> (length common-prefix) 0) (if (> (length common-prefix) 0)
(file-name-directory common-prefix)))) (file-name-directory common-prefix))))
(cpd-length (length common-parent-directory)) (cpd-length (length common-parent-directory))
(prompt (if (zerop cpd-length) (common-parent-directory (if (file-name-absolute-p (car all-files))
common-parent-directory
(concat default-directory common-parent-directory)))
(prompt (if (and (zerop cpd-length)
all-files
(file-name-absolute-p (car all-files)))
prompt prompt
(concat prompt (format " in %s" common-parent-directory)))) (concat prompt (format " in %s" common-parent-directory))))
(included-cpd (when (member common-parent-directory all-files) (included-cpd (when (member common-parent-directory all-files)
@ -1167,10 +1189,19 @@ by the user at will."
(defun project--read-file-absolute (prompt (defun project--read-file-absolute (prompt
all-files &optional predicate all-files &optional predicate
hist mb-default) hist mb-default)
(project--completing-read-strict prompt (let* ((new-prompt (if (file-name-absolute-p (car all-files))
(project--file-completion-table all-files) prompt
(concat prompt " in " default-directory)))
;; FIXME: Map relative names to absolute?
(ct (project--file-completion-table all-files))
(file
(project--completing-read-strict new-prompt
ct
predicate predicate
hist mb-default)) hist mb-default)))
(unless (file-name-absolute-p file)
(setq file (expand-file-name file)))
file))
(defun project--read-file-name ( project prompt (defun project--read-file-name ( project prompt
all-files &optional predicate all-files &optional predicate
@ -1215,6 +1246,7 @@ directories listed in `vc-directory-exclusion-list'."
dirs) dirs)
(project-files project dirs))) (project-files project dirs)))
(completion-ignore-case read-file-name-completion-ignore-case) (completion-ignore-case read-file-name-completion-ignore-case)
(default-directory (project-root project))
(file (project--read-file-name (file (project--read-file-name
project "Find file" project "Find file"
all-files nil 'file-name-history all-files nil 'file-name-history

View file

@ -1922,7 +1922,8 @@ to control which program to use when looking for matches."
(hits nil) (hits nil)
;; Support for remote files. The assumption is that, if the ;; Support for remote files. The assumption is that, if the
;; first file is remote, they all are, and on the same host. ;; first file is remote, they all are, and on the same host.
(dir (file-name-directory (car files))) (dir (or (file-name-directory (car files))
default-directory))
(remote-id (file-remote-p dir)) (remote-id (file-remote-p dir))
;; The 'auto' default would be fine too, but ripgrep can't handle ;; The 'auto' default would be fine too, but ripgrep can't handle
;; the options we pass in that case. ;; the options we pass in that case.

View file

@ -163,4 +163,28 @@ When `project-ignores' includes a name matching project dir."
(should-not (null project)) (should-not (null project))
(should (string-match-p "/test/lisp/progmodes/project-resources/\\'" (project-root project))))) (should (string-match-p "/test/lisp/progmodes/project-resources/\\'" (project-root project)))))
(ert-deftest project-find-regexp ()
"Check the happy path."
(skip-unless (executable-find find-program))
(skip-unless (executable-find "xargs"))
(skip-unless (executable-find "grep"))
(let* ((directory (ert-resource-directory))
(project-find-functions nil)
(project (cons 'transient directory)))
(add-hook 'project-find-functions (lambda (_dir) project))
(should (eq (project-current) project))
(let* ((matches nil)
(xref-search-program 'grep)
(xref-show-xrefs-function
(lambda (fetcher _display)
(setq matches (funcall fetcher)))))
(project-find-regexp "etc")
(should (equal (mapcar (lambda (item)
(file-name-base
(xref-location-group (xref-item-location item))))
matches)
'(".dir-locals" "etc")))
(should (equal (sort (mapcar #'xref-item-summary matches) #'string<)
'("((nil . ((project-vc-ignores . (\"etc\")))))" "etc"))))))
;;; project-tests.el ends here ;;; project-tests.el ends here