;;; helm-find.el --- helm interface for find command. -*- lexical-binding: t -*- ;; Copyright (C) 2012 ~ 2021 Thierry Volpiatto ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Code: (require 'helm-files) (require 'helm-external) (defcustom helm-findutils-skip-boring-files t "Ignore boring files in find command results." :group 'helm-files :type 'boolean) (defcustom helm-findutils-search-full-path nil "Search in full path with shell command find when non-nil. I.e. use the -path/ipath arguments of find instead of -name/iname." :group 'helm-files :type 'boolean) (defcustom helm-find-noerrors nil "Prevent showing error messages in helm buffer when non nil." :group 'helm-files :type 'boolean) (defvar helm-find-map (let ((map (make-sparse-keymap))) (set-keymap-parent map helm-generic-files-map) (define-key map (kbd "DEL") 'helm-delete-backward-no-update) map)) (defvar helm-source-findutils (helm-build-async-source "Find" :header-name (lambda (name) (concat name " in [" (helm-default-directory) "]")) :candidates-process 'helm-find-shell-command-fn :filtered-candidate-transformer 'helm-findutils-transformer :action-transformer 'helm-transform-file-load-el :persistent-action 'helm-ff-kill-or-find-buffer-fname :action 'helm-type-file-actions :help-message 'helm-generic-file-help-message :keymap helm-find-map :candidate-number-limit 9999 :requires-pattern 3)) (defun helm-findutils-transformer (candidates _source) (let (non-essential (default-directory (helm-default-directory))) (cl-loop for i in candidates for abs = (expand-file-name (helm-aif (file-remote-p default-directory) (concat it i) i)) for type = (car (file-attributes abs)) for disp = (if (and helm-ff-transformer-show-only-basename (not (string-match "[.]\\{1,2\\}$" i))) (helm-basename abs) abs) collect (cond ((eq t type) (cons (propertize disp 'face 'helm-ff-directory) abs)) ((stringp type) (cons (propertize disp 'face 'helm-ff-symlink) abs)) (t (cons (propertize disp 'face 'helm-ff-file) abs)))))) (defun helm-find--build-cmd-line () (require 'find-cmd) (let* ((default-directory (or (file-remote-p default-directory 'localname) default-directory)) (patterns+options (split-string helm-pattern "\\(\\`\\| +\\)\\* +")) (fold-case (helm-set-case-fold-search (car patterns+options))) (patterns (split-string (car patterns+options))) (additional-options (and (cdr patterns+options) (list (concat (cadr patterns+options) " ")))) (ignored-dirs ()) (ignored-files (when helm-findutils-skip-boring-files (cl-loop for f in completion-ignored-extensions if (string-match "/$" f) do (push (replace-match "" nil t f) ignored-dirs) else collect (concat "*" f)))) (path-or-name (if helm-findutils-search-full-path '(ipath path) '(iname name))) (name-or-iname (if fold-case (car path-or-name) (cadr path-or-name)))) (find-cmd (and ignored-dirs `(prune (name ,@ignored-dirs))) (and ignored-files `(not (name ,@ignored-files))) `(and ,@(mapcar (lambda (pattern) `(,name-or-iname ,(concat "*" pattern "*"))) patterns) ,@additional-options)))) (defun helm-find-shell-command-fn () "Asynchronously fetch candidates for `helm-find'. Additional find options can be specified after a \"*\" separator." (let* (process-connection-type non-essential (cmd (concat (helm-find--build-cmd-line) (if helm-find-noerrors "2> /dev/null" ""))) (proc (start-file-process-shell-command "hfind" helm-buffer cmd))) (helm-log "Find command:\n%s" cmd) (prog1 proc (set-process-sentinel proc (lambda (process event) (helm-process-deferred-sentinel-hook process event (helm-default-directory)) (if (string= event "finished\n") (helm-locate-update-mode-line "Find") (helm-log "Error: Find %s" (replace-regexp-in-string "\n" "" event)))))))) (defun helm-find-1 (dir) (let ((default-directory (file-name-as-directory dir))) (helm :sources 'helm-source-findutils :buffer "*helm find*" :ff-transformer-show-only-basename nil :case-fold-search helm-file-name-case-fold-search))) ;;; Preconfigured commands ;; ;; ;;;###autoload (defun helm-find (arg) "Preconfigured `helm' for the find shell command. Recursively find files whose names are matched by all specified globbing PATTERNs under the current directory using the external program specified in `find-program' (usually \"find\"). Every input PATTERN is silently wrapped into two stars: *PATTERN*. With prefix argument, prompt for a directory to search. When user option `helm-findutils-search-full-path' is non-nil, match against complete paths, otherwise, against file names without directory part. The (possibly empty) list of globbing PATTERNs can be followed by the separator \"*\" plus any number of additional arguments that are passed to \"find\" literally." (interactive "P") (let ((directory (if arg (file-name-as-directory (read-directory-name "DefaultDirectory: ")) default-directory))) (helm-find-1 directory))) (provide 'helm-find) ;;; helm-find.el ends here