diff --git a/lisp/minibuffer.el b/lisp/minibuffer.el index 1251a4e..f6bcbd9 100644 --- a/lisp/minibuffer.el +++ b/lisp/minibuffer.el @@ -831,7 +831,8 @@ completion-styles (defvar completion-category-defaults '((buffer (styles . (basic substring))) - (unicode-name (styles . (basic substring)))) + (unicode-name (styles . (basic substring))) + (project-file (styles . (basic substring partial-completion)))) "Default settings for specific completion categories. Each entry has the shape (CATEGORY . ALIST) where ALIST is an association list that can specify properties such as: diff --git a/lisp/progmodes/project.el b/lisp/progmodes/project.el index d771587..2e3222f 100644 --- a/lisp/progmodes/project.el +++ b/lisp/progmodes/project.el @@ -45,10 +45,12 @@ ;;; TODO: -;; * Commands `project-find-file' and `project-or-external-find-file'. -;; Currently blocked on adding a new completion style that would let -;; the user enter just the base file name (or a part of it), and get -;; it expanded to the absolute file name. +;; * Reliably cache the list of files in the project, probably using +;; filenotify.el (if supported) to invalidate. And avoiding caching +;; if it's not available (manual cache invalidation is not nice). +;; +;; * Allow the backend to override the file-listing logic? Maybe also +;; to delegate file name completion to an external tool. ;; ;; * Build tool related functionality. Start with a `project-build' ;; command, which should provide completions on tasks to run, and @@ -258,6 +260,7 @@ project--value-in-dir (declare-function xref-collect-matches "xref") (declare-function xref--show-xrefs "xref") (declare-function xref-backend-identifier-at-point "xref") +(declare-function xref--find-ignores-arguments "xref") ;;;###autoload (defun project-find-regexp (regexp) @@ -302,5 +305,42 @@ project--find-regexp-in (user-error "No matches for: %s" regexp)) (xref--show-xrefs xrefs nil))) +(defun project-find-file () + (interactive) + (let* ((pr (project-current t)) + (dirs (project-roots pr))) + (project--find-file-in dirs pr))) + +(defun project-or-external-find-file () + (interactive) + (let* ((pr (project-current t)) + (dirs (append + (project-roots pr) + (project-external-roots pr)))) + (project--find-file-in dirs pr))) + +;; FIXME: Uniquely abbreviate the roots? +(defun project--find-file-in (dirs project) + (let* ((all-files + (cl-mapcan + (lambda (dir) + (let ((command + (format "%s %s %s -type f -print0" + find-program + dir + (xref--find-ignores-arguments + (project-ignores project dir) + (expand-file-name dir))))) + (split-string (shell-command-to-string command) "\0" t))) + dirs)) + (table (lambda (string pred action) + (cond + ((eq action 'metadata) + '(metadata . ((category . project-file)))) + (t + (complete-with-action action all-files string pred)))))) + (find-file + (completing-read "Find file: " table nil t)))) + (provide 'project) ;;; project.el ends here diff --git a/lisp/progmodes/xref.el b/lisp/progmodes/xref.el index ae5ec61..5970011 100644 --- a/lisp/progmodes/xref.el +++ b/lisp/progmodes/xref.el @@ -876,7 +876,9 @@ xref-collect-matches grep-find-template t t)) (grep-highlight-matches nil) (command (xref--rgrep-command (xref--regexp-to-extended regexp) - files dir ignores)) + files + (expand-file-name dir) + ignores)) (orig-buffers (buffer-list)) (buf (get-buffer-create " *xref-grep*")) (grep-re (caar grep-regexp-alist)) @@ -912,23 +914,28 @@ xref--rgrep-command " " (shell-quote-argument ")")) dir - (concat - (shell-quote-argument "(") - " -path " - (mapconcat - (lambda (ignore) - (when (string-match-p "/\\'" ignore) - (setq ignore (concat ignore "*"))) - (if (string-match "\\`\\./" ignore) - (setq ignore (replace-match dir t t ignore)) - (unless (string-prefix-p "*" ignore) - (setq ignore (concat "*/" ignore)))) - (shell-quote-argument ignore)) - ignores - " -o -path ") - " " - (shell-quote-argument ")") - " -prune -o "))) + (xref--find-ignores-arguments ignores dir))) + +(defun xref--find-ignores-arguments (ignores dir) + ;; `shell-quote-argument' quotes the tilde as well. + (cl-assert (not (string-match-p "\\`~" dir))) + (concat + (shell-quote-argument "(") + " -path " + (mapconcat + (lambda (ignore) + (when (string-match-p "/\\'" ignore) + (setq ignore (concat ignore "*"))) + (if (string-match "\\`\\./" ignore) + (setq ignore (replace-match dir t t ignore)) + (unless (string-prefix-p "*" ignore) + (setq ignore (concat "*/" ignore)))) + (shell-quote-argument ignore)) + ignores + " -o -path ") + " " + (shell-quote-argument ")") + " -prune -o ")) (defun xref--regexp-to-extended (str) (replace-regexp-in-string