aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDmitry Gutov2019-01-18 06:38:12 +0300
committerDmitry Gutov2019-01-18 06:46:36 +0300
commitfbe87d0f8f8878b30b1dfe74f7eb369b569bab6b (patch)
tree56c7e1e3277e1e738f7d2c552ad7e026eec5df1d
parentafc8a41f4889b0b207bbd1c30fa9f310437b439e (diff)
downloademacs-fbe87d0f8f8878b30b1dfe74f7eb369b569bab6b.tar.gz
emacs-fbe87d0f8f8878b30b1dfe74f7eb369b569bab6b.zip
Rebase project-find-regexp on top of project-files
* lisp/progmodes/project.el (project--files-in-directory): New function. (project-files, project-find-regexp): Use it. (project--dir-ignores): New function. (project--find-regexp-in): Remove. (project--process-file-region): New function. (project--find-regexp-in-files): New function. (project-find-regexp, project-or-external-find-regexp): Use it, and project-files as well.
-rw-r--r--lisp/progmodes/project.el139
1 files changed, 107 insertions, 32 deletions
diff --git a/lisp/progmodes/project.el b/lisp/progmodes/project.el
index c16b2578ebf..f795c36fa06 100644
--- a/lisp/progmodes/project.el
+++ b/lisp/progmodes/project.el
@@ -184,17 +184,30 @@ to find the list of ignores for each directory."
184 (require 'xref) 184 (require 'xref)
185 (cl-mapcan 185 (cl-mapcan
186 (lambda (dir) 186 (lambda (dir)
187 (let ((command 187 (project--files-in-directory dir (project-ignores project dir)))
188 (format "%s %s %s -type f -print0"
189 find-program
190 (shell-quote-argument
191 (expand-file-name dir))
192 (xref--find-ignores-arguments
193 (project-ignores project dir)
194 (expand-file-name dir)))))
195 (split-string (shell-command-to-string command) "\0" t)))
196 (or dirs (project-roots project)))) 188 (or dirs (project-roots project))))
197 189
190(defun project--files-in-directory (dir ignores &optional files)
191 (require 'find-dired)
192 (defvar find-name-arg)
193 (let ((command (format "%s %s %s -type f %s -print0"
194 find-program
195 dir
196 (xref--find-ignores-arguments
197 ignores
198 (expand-file-name dir))
199 (if files
200 (concat (shell-quote-argument "(")
201 " " find-name-arg " "
202 (mapconcat
203 #'shell-quote-argument
204 (split-string files)
205 (concat " -o " find-name-arg " "))
206 " "
207 (shell-quote-argument ")"))"")
208 )))
209 (split-string (shell-command-to-string command) "\0" t)))
210
198(defgroup project-vc nil 211(defgroup project-vc nil
199 "Project implementation using the VC package." 212 "Project implementation using the VC package."
200 :version "25.1" 213 :version "25.1"
@@ -320,11 +333,26 @@ triggers completion when entering a pattern, including it
320requires quoting, e.g. `\\[quoted-insert]<space>'." 333requires quoting, e.g. `\\[quoted-insert]<space>'."
321 (interactive (list (project--read-regexp))) 334 (interactive (list (project--read-regexp)))
322 (let* ((pr (project-current t)) 335 (let* ((pr (project-current t))
323 (dirs (if current-prefix-arg 336 (files
324 (list (read-directory-name "Base directory: " 337 (if (not current-prefix-arg)
325 nil default-directory t)) 338 (project-files pr (project-roots pr))
326 (project-roots pr)))) 339 (let ((dir (read-directory-name "Base directory: "
327 (project--find-regexp-in dirs regexp pr))) 340 nil default-directory t)))
341 (project--files-in-directory dir
342 (project--dir-ignores pr dir)
343 (grep-read-files regexp))))))
344 (project--find-regexp-in-files regexp files)))
345
346(defun project--dir-ignores (project dir)
347 (let* ((roots (project-roots project))
348 (root (cl-find dir roots :test #'file-in-directory-p)))
349 (when root
350 (let ((ignores (project-ignores project root)))
351 (if (file-equal-p root dir)
352 ignores
353 ;; FIXME: Update the "rooted" ignores to relate to DIR instead.
354 (cl-delete-if (lambda (str) (string-prefix-p "./" str))
355 ignores))))))
328 356
329;;;###autoload 357;;;###autoload
330(defun project-or-external-find-regexp (regexp) 358(defun project-or-external-find-regexp (regexp)
@@ -333,29 +361,76 @@ With \\[universal-argument] prefix, you can specify the file name
333pattern to search for." 361pattern to search for."
334 (interactive (list (project--read-regexp))) 362 (interactive (list (project--read-regexp)))
335 (let* ((pr (project-current t)) 363 (let* ((pr (project-current t))
336 (dirs (append 364 (files
337 (project-roots pr) 365 (project-files pr (append
338 (project-external-roots pr)))) 366 (project-roots pr)
339 (project--find-regexp-in dirs regexp pr))) 367 (project-external-roots pr)))))
368 (project--find-regexp-in-files regexp files)))
369
370(defun project--find-regexp-in-files (regexp files)
371 (pcase-let*
372 ((output (get-buffer-create " *project grep output*"))
373 (`(,grep-re ,file-group ,line-group . ,_) (car grep-regexp-alist))
374 (status nil)
375 (hits nil)
376 (xrefs nil)
377 (command (format "xargs -0 grep %s -nHe %s"
378 (if (and case-fold-search
379 (isearch-no-upper-case-p regexp t))
380 "-i"
381 "")
382 (shell-quote-argument (xref--regexp-to-extended regexp)))))
383 (with-current-buffer output
384 (erase-buffer)
385 (with-temp-buffer
386 (insert (mapconcat #'identity files "\0"))
387 (setq status
388 (project--process-file-region (point-min)
389 (point-max)
390 shell-file-name
391 output
392 nil
393 shell-command-switch
394 command)))
395 (goto-char (point-min))
396 (when (and (/= (point-min) (point-max))
397 (not (looking-at grep-re))
398 ;; TODO: Show these matches as well somehow?
399 (not (looking-at "Binary file .* matches")))
400 (user-error "Search failed with status %d: %s" status
401 (buffer-substring (point-min) (line-end-position))))
402 (while (re-search-forward grep-re nil t)
403 (push (list (string-to-number (match-string line-group))
404 (match-string file-group)
405 (buffer-substring-no-properties (point) (line-end-position)))
406 hits)))
407 (setq xrefs (xref--convert-hits (nreverse hits) regexp))
408 (unless xrefs
409 (user-error "No matches for: %s" regexp))
410 (xref--show-xrefs xrefs nil)))
411
412(defun project--process-file-region (start end program
413 &optional buffer display
414 &rest args)
415 ;; FIXME: This branching shouldn't be necessary, but
416 ;; call-process-region *is* measurably faster, even for a program
417 ;; doing some actual work (for a period of time). Even though
418 ;; call-process-region also creates a temp file internally
419 ;; (http://lists.gnu.org/archive/html/emacs-devel/2019-01/msg00211.html).
420 (if (not (file-remote-p default-directory))
421 (apply #'call-process-region
422 start end program nil buffer display args)
423 (let ((infile (make-temp-file "ppfr")))
424 (unwind-protect
425 (progn
426 (write-region start end infile nil 'silent)
427 (apply #'process-file program infile buffer display args))
428 (delete-file infile)))))
340 429
341(defun project--read-regexp () 430(defun project--read-regexp ()
342 (let ((id (xref-backend-identifier-at-point (xref-find-backend)))) 431 (let ((id (xref-backend-identifier-at-point (xref-find-backend))))
343 (read-regexp "Find regexp" (and id (regexp-quote id))))) 432 (read-regexp "Find regexp" (and id (regexp-quote id)))))
344 433
345(defun project--find-regexp-in (dirs regexp project)
346 (require 'grep)
347 (let* ((files (if current-prefix-arg
348 (grep-read-files regexp)
349 "*"))
350 (xrefs (cl-mapcan
351 (lambda (dir)
352 (xref-collect-matches regexp files dir
353 (project-ignores project dir)))
354 dirs)))
355 (unless xrefs
356 (user-error "No matches for: %s" regexp))
357 (xref--show-xrefs xrefs nil)))
358
359;;;###autoload 434;;;###autoload
360(defun project-find-file () 435(defun project-find-file ()
361 "Visit a file (with completion) in the current project's roots. 436 "Visit a file (with completion) in the current project's roots.