mirror of https://github.com/Chizi123/.emacs.d.git

Chizi123
2018-11-18 76bbd07de7add0f9d13c6914f158d19630fe2f62
commit | author | age
76bbd0 1 ;;; helm-files.el --- helm file browser and related. -*- lexical-binding: t -*-
C 2
3 ;; Copyright (C) 2012 ~ 2018 Thierry Volpiatto <thierry.volpiatto@gmail.com>
4
5 ;; This program is free software; you can redistribute it and/or modify
6 ;; it under the terms of the GNU General Public License as published by
7 ;; the Free Software Foundation, either version 3 of the License, or
8 ;; (at your option) any later version.
9
10 ;; This program is distributed in the hope that it will be useful,
11 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
12 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 ;; GNU General Public License for more details.
14
15 ;; You should have received a copy of the GNU General Public License
16 ;; along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
18 ;;; Code:
19
20 (require 'cl-lib)
21 (require 'helm)
22 (require 'helm-types)
23 (require 'helm-utils)
24 (require 'helm-grep)
25 (require 'helm-help)
26 (require 'helm-locate)
27 (require 'helm-tags)
28 (require 'helm-buffers)
29 (eval-when-compile
30   (require 'thingatpt)
31   (require 'ffap)
32   (require 'dired-aux)
33   (require 'dired-x)
34   (require 'tramp)
35   (require 'image-dired))
36
37 (declare-function find-library-name "find-func.el" (library))
38 (declare-function w32-shell-execute "ext:w32fns.c" (operation document &optional parameters show-flag))
39 (declare-function gnus-dired-attach "ext:gnus-dired.el" (files-to-attach))
40 (declare-function image-dired-display-image "image-dired.el" (file &optional original-size))
41 (declare-function image-dired-update-property "image-dired.el" (prop value))
42 (declare-function eshell-read-aliases-list "em-alias")
43 (declare-function eshell-send-input "esh-mode" (&optional use-region queue-p no-newline))
44 (declare-function eshell-kill-input "esh-mode")
45 (declare-function eshell-bol "esh-mode")
46 (declare-function eshell-reset "esh-mode.el")
47 (declare-function eshell/cd "em-dirs.el")
48 (declare-function eshell-next-prompt "em-prompt.el")
49 (declare-function eshell-quote-argument "esh-arg.el")
50 (declare-function helm-ls-git-ls "ext:helm-ls-git")
51 (declare-function helm-hg-find-files-in-project "ext:helm-ls-hg")
52 (declare-function helm-gid "helm-id-utils.el")
53 (declare-function helm-find-1 "helm-find")
54 (declare-function helm-get-default-program-for-file "helm-external")
55 (declare-function helm-open-file-externally "helm-external")
56
57 (defvar recentf-list)
58 (defvar helm-mm-matching-method)
59 (defvar dired-async-mode)
60 (defvar org-directory)
61
62 (defgroup helm-files nil
63   "Files applications and libraries for Helm."
64   :group 'helm)
65
66 (defcustom helm-tramp-verbose 0
67   "Just like `tramp-verbose' but specific to helm.
68 When set to 0 don't show tramp messages in helm.
69 If you want to have the default tramp messages set it to 3."
70   :type 'integer
71   :group 'helm-files)
72
73 (defcustom helm-ff-auto-update-initial-value nil
74   "Auto update when only one candidate directory is matched.
75 Default value when starting `helm-find-files' is nil because
76 it prevent using <backspace> to delete char backward and by the way
77 confuse beginners.
78 For a better experience with `helm-find-files' set this to non--nil
79 and use C-<backspace> to toggle it."
80   :group 'helm-files
81   :type  'boolean)
82
83 (defcustom helm-ff-lynx-style-map t
84   "Use arrow keys to navigate with `helm-find-files'.
85 You will have to restart Emacs or reeval `helm-find-files-map'
86 and `helm-read-file-map' for this take effect."
87   :group 'helm-files
88   :type 'boolean)
89
90 (defcustom helm-ff-history-max-length 100
91   "Number of elements shown in `helm-find-files' history."
92   :group 'helm-files
93   :type 'integer)
94
95 (defcustom helm-ff-fuzzy-matching t
96   "Enable fuzzy matching for `helm-find-files' when non--nil.
97 See `helm-ff--transform-pattern-for-completion' for more info."
98   :group 'helm-files
99   :type 'boolean)
100
101 (defcustom helm-ff-exif-data-program "exiftran"
102   "Program used to extract exif data of an image file."
103   :group 'helm-files
104   :type 'string)
105
106 (defcustom helm-ff-exif-data-program-args "-d"
107   "Arguments used for `helm-ff-exif-data-program'."
108   :group 'helm-files
109   :type 'string)
110
111 (defcustom helm-ff-newfile-prompt-p t
112   "Whether Prompt or not when creating new file.
113 This set `ffap-newfile-prompt'."
114   :type  'boolean
115   :group 'helm-files)
116
117 (defcustom helm-ff-avfs-directory "~/.avfs"
118   "The default avfs directory, usually '~/.avfs'.
119 When this is set you will be able to expand archive filenames with `C-j'
120 inside an avfs directory mounted with mountavfs.
121 See <http://sourceforge.net/projects/avf/>."
122   :type  'string
123   :group 'helm-files)
124
125 (defcustom helm-ff-file-compressed-list '("gz" "bz2" "zip" "7z")
126   "Minimal list of compressed files extension."
127   :type  '(repeat (choice string))
128   :group 'helm-files)
129
130 (defcustom helm-ff-printer-list nil
131   "A list of available printers on your system.
132 When non--nil let you choose a printer to print file.
133 Otherwise when nil the variable `printer-name' will be used.
134 On Unix based systems (lpstat command needed) you don't need to set this,
135 `helm-ff-find-printers' will find a list of available printers for you."
136   :type '(repeat (choice string))
137   :group 'helm-files)
138
139 (defcustom helm-ff-transformer-show-only-basename t
140   "Show only basename of candidates in `helm-find-files'.
141 This can be toggled at anytime from `helm-find-files' with \
142 \\<helm-find-files-map>\\[helm-ff-run-toggle-basename]."
143   :type 'boolean
144   :group 'helm-files)
145
146 (defcustom helm-ff-signal-error-on-dot-files t
147   "Signal error when file is `.' or `..' on file deletion when non--nil.
148 Default is non--nil.
149 WARNING: Setting this to nil is unsafe and can cause deletion of a whole tree."
150   :group 'helm-files
151   :type  'boolean)
152
153 (defcustom helm-ff-search-library-in-sexp nil
154   "Search for library in `require' and `declare-function' sexp."
155   :group 'helm-files
156   :type  'boolean)
157
158 (defcustom helm-tooltip-hide-delay 25
159   "Hide tooltips automatically after this many seconds."
160   :group 'helm-files
161   :type 'integer)
162
163 (defcustom helm-ff-file-name-history-use-recentf nil
164   "Use `recentf-list' instead of `file-name-history' in `helm-find-files'."
165   :group 'helm-files
166   :type 'boolean)
167
168 (defcustom helm-ff-skip-boring-files nil
169   "Non--nil to skip files matching regexps in
170 `helm-boring-file-regexp-list'.
171
172 This take effect in `helm-find-files' and file completion used by `helm-mode'
173 i.e `helm-read-file-name'.
174 Note that when non-nil this will slow down slightly `helm-find-files'."
175   :group 'helm-files
176   :type  'boolean)
177
178 (defcustom helm-ff-candidate-number-limit 5000
179   "The `helm-candidate-number-limit' for `helm-find-files' and friends.
180 Note that when going one level up with `\\<helm-find-files-map>\\[helm-find-files-up-one-level]'
181 The length of directory will be used instead if it is higher than this
182 value, this to avoid failing to preselect the previous directory/file if
183 this one is situated lower than `helm-ff-candidate-number-limit' num
184 candidate."
185   :group 'helm-files
186   :type 'integer)
187
188 (defcustom helm-ff-up-one-level-preselect t
189   "Always preselect previous directory when going one level up.
190
191 When non nil `candidate-number-limit' source value is modified
192 dynamically when going one level up if the position of previous
193 candidate in its directory is > to `helm-ff-candidate-number-limit'.
194
195 This can be helpful to disable this and reduce
196 `helm-ff-candidate-number-limit' if you often navigate across very
197 large directories."
198   :group 'helm-files
199   :type 'boolean)
200
201 (defcustom helm-files-save-history-extra-sources
202   '("Find" "Locate" "Recentf"
203     "Files from Current Directory" "File Cache")
204   "Extras source that save candidate to `file-name-history'."
205   :group 'helm-files
206   :type '(repeat (choice string)))
207
208 (defcustom helm-find-files-before-init-hook nil
209   "Hook that run before initialization of `helm-find-files'."
210   :group 'helm-files
211   :type 'hook)
212
213 (defcustom helm-find-files-after-init-hook nil
214   "Hook that run after initialization of `helm-find-files'."
215   :group 'helm-files
216   :type 'hook)
217
218 (defcustom helm-find-files-bookmark-prefix "Helm-find-files: "
219   "bookmark name prefix of `helm-find-files' sessions."
220   :group 'helm-files
221   :type 'string)
222
223 (defcustom helm-ff-guess-ffap-filenames nil
224   "Use ffap to guess local filenames at point in `helm-find-files'.
225 This doesn't disable url or mail at point, see
226 `helm-ff-guess-ffap-urls' for this."
227   :group 'helm-files
228   :type 'boolean)
229
230 (defcustom helm-ff-guess-ffap-urls t
231   "Use ffap to guess local urls at point in `helm-find-files'.
232 This doesn't disable guessing filenames at point,
233 see `helm-ff-guess-ffap-filenames' for this.
234 See also `ffap-url-unwrap-remote' that may override this variable."
235   :group 'helm-files
236   :type 'boolean)
237
238 (defcustom helm-ff-no-preselect nil
239   "When non--nil `helm-find-files' starts at root of current directory."
240   :group 'helm-files
241   :type 'boolean)
242
243 (defcustom helm-find-files-ignore-thing-at-point nil
244   "Use only `default-directory' as default input in `helm-find-files'.
245 I.e text under cursor in `current-buffer' is ignored.
246 Note that when non-nil you will be unable to complete filename at point
247 in `current-buffer'."
248   :group 'helm-files
249   :type 'boolean)
250
251 (defcustom helm-substitute-in-filename-stay-on-remote nil
252   "Don't switch back to local filesystem when expanding pattern with / or ~/."
253   :group 'helm-files
254   :type 'boolean)
255
256 (defcustom helm-ff-goto-first-real-dired-exceptions '(dired-goto-file)
257   "Dired commands that are allowed moving to first real candidate."
258   :group 'helm-files
259   :type '(repeat (choice symbol)))
260
261 (defcustom helm-mounted-network-directories nil
262   "A list of directories used for mounting remotes filesystem.
263
264 When nil `helm-file-on-mounted-network-p' always return nil otherwise
265 it checks if a file is in one of these directories.
266
267 Remote filesystem are generally mounted with sshfs."
268   :group 'helm-files
269   :type '(repeat string))
270
271 (defcustom helm-browse-project-default-find-files-fn
272   #'helm-browse-project-walk-directory
273   "The default function to retrieve files in a non-vc directory.
274
275 A function that takes a directory name as only arg."
276   :group 'helm-files
277   :type 'function)
278
279 (defcustom helm-ff-kill-or-find-buffer-fname-fn
280   #'helm-ff-kill-or-find-buffer-fname
281   "Default function used to expand non-directory filenames in `helm-find-files'.
282
283 This variable will take effect only in `helm-find-files', it affects
284 the behavior of persistent-action on filenames and non-existing
285 filenames.
286
287 The default is to expand filename on first hit on
288 \\<helm-map>\\[helm-execute-persistent-action], pop buffer in other
289 window on second hit and finally kill this buffer on third hit, this
290 is very handy to create several new buffers, or when navigating, show
291 quickly the buffer of file to see its contents briefly before killing
292 it and continue navigating.
293
294 However some users may not want this, so to disable this behavior just
295 set this to `ignore' function.
296
297 Of course you can also write your own function to do something else."
298   :group 'helm-files
299   :type 'function)
300
301 (defcustom helm-modes-using-escaped-strings
302   '(eshell-mode shell-mode term-mode)
303   "Modes that requires string's insertion to be escaped."
304   :group 'helm-files
305   :type '(repeat symbol))
306
307 (defcustom helm-ff-allow-recursive-deletes nil
308   "when 'always don't prompt for recursive deletion of directories.
309 When nil, will ask for recursive deletion.
310 Note that when deleting multiple directories you can answer ! when
311 prompted to avoid beeing asked for next directories, so it is probably
312 better to not modify this variable."
313   :group 'helm-files
314   :type '(choice
315           (const :tag "Delete non-empty directories" t)
316           (const :tag "Confirm for each directory" nil)))
317
318 (defcustom helm-ff-delete-files-function #'helm-delete-marked-files
319   "The function to use by default to delete files.
320
321 Default is to delete files synchronously, other choice is to delete
322 files asynchronously.
323
324 BE AWARE that when deleting async you will not be warned about
325 recursive deletion of directories, IOW non empty directories will be
326 deleted with no warnings in background!!!
327
328 It is the function that will be used when using `\\<helm-find-files-map>\\[helm-ff-run-delete-file]'
329 from `helm-find-files'."
330   :group 'helm-files
331   :type '(choice (function :tag "Delete files synchronously."
332                   helm-delete-marked-files)
333                  (function :tag "Delete files asynchronously."
334                   helm-delete-marked-files-async)))
335
336 (defcustom helm-trash-remote-files nil
337   "Allow trashing remote files when non-nil.
338
339 Deleting remote files with tramp doesn't work out of the box, it is
340 why it is disabled by default.
341
342 Following is NOT documented in tramp AFAIK but tramp is using
343 external trash command in its `delete-file' and `delete-directory'
344 handlers.
345
346 If you want to enable this you will have to install the 'trash' command
347 on remote (or locally if you want to trash as root), the package on
348 Ubuntu based distribution is 'trash-cli'."
349   :group 'helm-files
350   :type 'boolean)
351
352 (defcustom helm-list-directory-function
353   (cl-case system-type
354     (gnu/linux #'helm-list-dir-external)
355     (berkeley-unix #'helm-list-dir-external)
356     (windows-nt #'helm-list-dir-lisp)
357     (t #'helm-list-dir-lisp))
358   "The function used in `helm-find-files' to list remote directories.
359
360 Actually helm provides two functions to do this: `helm-list-dir-lisp'
361 and `helm-list-dir-external'.
362
363 Using `helm-list-dir-external' will provides a similar display to what
364 provided with local files i.e. colorized symlinks, executables files
365 etc... whereas using `helm-list-dir-lisp' will allow colorizing only
366 directories but is more portable.
367
368 NOTE that `helm-list-dir-external' needs ls and awk as dependencies."
369   :type 'function
370   :group 'helm-files)
371
372 ;;; Faces
373 ;;
374 ;;
375 (defgroup helm-files-faces nil
376   "Customize the appearance of helm-files."
377   :prefix "helm-"
378   :group 'helm-files
379   :group 'helm-faces)
380
381 (defface helm-ff-prefix
382     '((t (:background "yellow" :foreground "black")))
383   "Face used to prefix new file or url paths in `helm-find-files'."
384   :group 'helm-files-faces)
385
386 (defface helm-ff-executable
387     '((t (:foreground "green")))
388   "Face used for executable files in `helm-find-files'."
389   :group 'helm-files-faces)
390
391 (defface helm-ff-suid
392     '((t (:background "red" :foreground "white")))
393   "Face used for suid files in `helm-find-files'."
394   :group 'helm-files-faces)
395
396 (defface helm-ff-directory
397     '((t (:foreground "DarkRed" :background "LightGray")))
398   "Face used for directories in `helm-find-files'."
399   :group 'helm-files-faces)
400
401 (defface helm-ff-dotted-directory
402     '((t (:foreground "black" :background "DimGray")))
403   "Face used for dotted directories in `helm-find-files'."
404   :group 'helm-files-faces)
405
406 (defface helm-ff-dotted-symlink-directory
407     '((t (:foreground "DarkOrange" :background "DimGray")))
408   "Face used for dotted symlinked directories in `helm-find-files'."
409   :group 'helm-files-faces)
410
411 (defface helm-ff-symlink
412     '((t :inherit font-lock-comment-face))
413   "Face used for symlinks in `helm-find-files'."
414   :group 'helm-files-faces)
415
416 (defface helm-ff-invalid-symlink
417     '((t (:foreground "black" :background "red")))
418   "Face used for invalid symlinks in `helm-find-files'."
419   :group 'helm-files-faces)
420
421 (defface helm-ff-denied
422     '((t (:foreground "red" :background "black")))
423   "Face used for non accessible files in `helm-find-files'."
424   :group 'helm-files-faces)
425
426 (defface helm-ff-file
427     '((t (:inherit font-lock-builtin-face)))
428   "Face used for file names in `helm-find-files'."
429   :group 'helm-files-faces)
430
431 (defface helm-ff-truename
432     '((t (:inherit font-lock-string-face)))
433   "Face used for symlink truenames in `helm-find-files'."
434   :group 'helm-files-faces)
435
436 (defface helm-ff-dirs
437     '((t (:inherit font-lock-function-name-face)))
438   "Face used for file names in recursive dirs completion in `helm-find-files'."
439   :group 'helm-files-faces)
440
441 (defface helm-ff-socket
442     '((t (:foreground "DeepPink")))
443   "Face used for socket files in `helm-find-files'."
444   :group 'helm-files-faces)
445
446 (defface helm-ff-pipe
447     '((t (:foreground "yellow" :background "black")))
448   "Face used for named pipes and character device files in `helm-find-files'."
449   :group 'helm-files-faces)
450
451 (defface helm-history-deleted
452     '((t (:inherit helm-ff-invalid-symlink)))
453   "Face used for deleted files in `file-name-history'."
454   :group 'helm-files-faces)
455
456 (defface helm-history-remote
457     '((t (:foreground "Indianred1")))
458   "Face used for remote files in `file-name-history'."
459   :group 'helm-files-faces)
460
461 (defface helm-delete-async-message
462     '((t (:foreground "yellow")))
463   "Face used for mode-line message."
464   :group 'helm-files-faces)
465
466 ;;; Helm-find-files - The helm file browser.
467 ;;
468 ;; Keymaps
469 (defvar helm-find-files-map
470   (let ((map (make-sparse-keymap)))
471     (set-keymap-parent map helm-map)
472     (define-key map (kbd "RET")           'helm-ff-RET)
473     (define-key map (kbd "C-]")           'helm-ff-run-toggle-basename)
474     (define-key map (kbd "C-x C-f")       'helm-ff-run-locate)
475     (define-key map (kbd "C-x C-d")       'helm-ff-run-browse-project)
476     (define-key map (kbd "C-x r m")       'helm-ff-bookmark-set)
477     (define-key map (kbd "C-x r b")       'helm-find-files-toggle-to-bookmark)
478     (define-key map (kbd "C-x C-q")       'helm-ff-run-marked-files-in-dired)
479     (define-key map (kbd "C-s")           'helm-ff-run-grep)
480     (define-key map (kbd "M-g s")         'helm-ff-run-grep)
481     (define-key map (kbd "M-g p")         'helm-ff-run-pdfgrep)
482     (define-key map (kbd "M-g z")         'helm-ff-run-zgrep)
483     (define-key map (kbd "M-g a")         'helm-ff-run-grep-ag)
484     (define-key map (kbd "M-g g")         'helm-ff-run-git-grep)
485     (define-key map (kbd "M-g i")         'helm-ff-run-gid)
486     (define-key map (kbd "M-.")           'helm-ff-run-etags)
487     (define-key map (kbd "M-R")           'helm-ff-run-rename-file)
488     (define-key map (kbd "M-C")           'helm-ff-run-copy-file)
489     (define-key map (kbd "M-B")           'helm-ff-run-byte-compile-file)
490     (define-key map (kbd "M-L")           'helm-ff-run-load-file)
491     (define-key map (kbd "M-S")           'helm-ff-run-symlink-file)
492     (define-key map (kbd "M-Y")           'helm-ff-run-relsymlink-file)
493     (define-key map (kbd "M-H")           'helm-ff-run-hardlink-file)
494     (define-key map (kbd "M-D")           'helm-ff-run-delete-file)
495     (define-key map (kbd "M-K")           'helm-ff-run-kill-buffer-persistent)
496     (define-key map (kbd "M-T")           'helm-ff-run-touch-files)
497     (define-key map (kbd "C-c d")         'helm-ff-persistent-delete)
498     (define-key map (kbd "M-e")           'helm-ff-run-switch-to-eshell)
499     (define-key map (kbd "C-c i")         'helm-ff-run-complete-fn-at-point)
500     (define-key map (kbd "C-c o")         'helm-ff-run-switch-other-window)
501     (define-key map (kbd "C-c C-o")       'helm-ff-run-switch-other-frame)
502     (define-key map (kbd "C-c C-x")       'helm-ff-run-open-file-externally)
503     (define-key map (kbd "C-c C-v")       'helm-ff-run-preview-file-externally)
504     (define-key map (kbd "C-c X")         'helm-ff-run-open-file-with-default-tool)
505     (define-key map (kbd "M-!")           'helm-ff-run-eshell-command-on-file)
506     (define-key map (kbd "M-@")           'helm-ff-run-query-replace-fnames-on-marked)
507     (define-key map (kbd "M-%")           'helm-ff-run-query-replace)
508     (define-key map (kbd "C-M-%")         'helm-ff-run-query-replace-regexp)
509     (define-key map (kbd "C-c =")         'helm-ff-run-ediff-file)
510     (define-key map (kbd "M-=")           'helm-ff-run-ediff-merge-file)
511     (define-key map (kbd "M-p")           'helm-find-files-history)
512     (define-key map (kbd "C-c h")         'helm-ff-file-name-history)
513     (define-key map (kbd "M-i")           'helm-ff-properties-persistent)
514     (define-key map (kbd "C-}")           'helm-narrow-window)
515     (define-key map (kbd "C-{")           'helm-enlarge-window)
516     (define-key map (kbd "C-<backspace>") 'helm-ff-run-toggle-auto-update)
517     (define-key map (kbd "C-c <DEL>")     'helm-ff-run-toggle-auto-update)
518     (define-key map (kbd "C-c C-a")       'helm-ff-run-mail-attach-files)
519     (define-key map (kbd "C-c p")         'helm-ff-run-print-file)
520     (define-key map (kbd "C-c /")         'helm-ff-run-find-sh-command)
521     ;; Next 2 have no effect if candidate is not an image file.
522     (define-key map (kbd "M-l")           'helm-ff-rotate-left-persistent)
523     (define-key map (kbd "M-r")           'helm-ff-rotate-right-persistent)
524     (define-key map (kbd "C-l")           'helm-find-files-up-one-level)
525     (define-key map (kbd "C-r")           'helm-find-files-down-last-level)
526     (define-key map (kbd "C-c r")         'helm-ff-run-find-file-as-root)
527     (define-key map (kbd "C-x C-v")       'helm-ff-run-find-alternate-file)
528     (define-key map (kbd "C-c @")         'helm-ff-run-insert-org-link)
529     (helm-define-key-with-subkeys map (kbd "DEL") ?\d 'helm-ff-delete-char-backward
530                                   '((C-backspace . helm-ff-run-toggle-auto-update)
531                                     ([C-c DEL] . helm-ff-run-toggle-auto-update))
532                                   nil 'helm-ff-delete-char-backward--exit-fn)
533     (when helm-ff-lynx-style-map
534       (define-key map (kbd "<left>")      'helm-find-files-up-one-level)
535       (define-key map (kbd "<right>")     'helm-execute-persistent-action))
536     (delq nil map))
537   "Keymap for `helm-find-files'.")
538
539 (defvar helm-read-file-map
540   (let ((map (make-sparse-keymap)))
541     (set-keymap-parent map helm-map)
542     (define-key map (kbd "<C-return>")    'helm-cr-empty-string)
543     (define-key map (kbd "M-RET")         'helm-cr-empty-string)
544     (define-key map (kbd "C-]")           'helm-ff-run-toggle-basename)
545     (define-key map (kbd "C-.")           'helm-find-files-up-one-level)
546     (define-key map (kbd "C-l")           'helm-find-files-up-one-level)
547     (define-key map (kbd "C-r")           'helm-find-files-down-last-level)
548     (define-key map (kbd "C-c h")         'helm-ff-file-name-history)
549     (define-key map (kbd "C-<backspace>") 'helm-ff-run-toggle-auto-update)
550     (define-key map (kbd "C-c <DEL>")     'helm-ff-run-toggle-auto-update)
551     (helm-define-key-with-subkeys map (kbd "DEL") ?\d 'helm-ff-delete-char-backward
552                                   '((C-backspace . helm-ff-run-toggle-auto-update)
553                                     ([C-c DEL] . helm-ff-run-toggle-auto-update))
554                                   nil 'helm-ff-delete-char-backward--exit-fn)
555     (when helm-ff-lynx-style-map
556       (define-key map (kbd "<left>")      'helm-find-files-up-one-level)
557       (define-key map (kbd "<right>")     'helm-execute-persistent-action)
558       (define-key map (kbd "<M-left>")    'helm-previous-source)
559       (define-key map (kbd "<M-right>")   'helm-next-source))
560     (delq nil map))
561   "Keymap for `helm-read-file-name'.")
562
563
564 ;; Internal.
565 (defvar helm-find-files-doc-header " (\\<helm-find-files-map>\\[helm-find-files-up-one-level]: Go up one level)"
566   "*The doc that is inserted in the Name header of a find-files or dired source.")
567 (defvar helm-ff-auto-update-flag nil
568   "Internal, flag to turn on/off auto-update in `helm-find-files'.
569 Don't set it directly, use instead `helm-ff-auto-update-initial-value'.")
570 (defvar helm-ff-last-expanded nil
571   "Store last expanded directory or file.")
572 (defvar helm-ff-default-directory nil)
573 (defvar helm-ff-history nil)
574 (defvar helm-ff-cand-to-mark nil)
575 (defvar helm-ff-url-regexp
576   "\\`\\(news\\(post\\)?:\\|nntp:\\|mailto:\\|file:\\|\\(ftp\\|https?\\|telnet\\|gopher\\|www\\|wais\\):/?/?\\).*"
577   "Same as `ffap-url-regexp' but match earlier possible url.")
578 ;; helm-tramp-file-name-regexp is based on old version of
579 ;; tramp-file-name-regexp i.e. "\\`/\\([^[/:]+\\|[^/]+]\\):" but it
580 ;; seems it is wrong and a simpler regexp is enough, let's try it and
581 ;; watch out!
582 (defvar helm-tramp-file-name-regexp "\\`/\\([^/:|]+\\):")
583 (defvar helm-marked-buffer-name "*helm marked*")
584 (defvar helm-ff--auto-update-state nil)
585 (defvar helm-ff--deleting-char-backward nil)
586 (defvar helm-multi-files--toggle-locate nil)
587 (defvar helm-ff--move-to-first-real-candidate t)
588 (defvar helm-find-files--toggle-bookmark nil)
589 (defvar helm-ff--tramp-methods nil)
590 (defvar helm-ff--directory-files-hash (make-hash-table :test 'equal))
591 (defvar helm-ff-history-buffer-name "*helm-find-files history*")
592
593 ;;; Helm-find-files
594 ;;
595 ;;
596 (defcustom helm-find-files-actions
597   (helm-make-actions
598    "Find File" 'helm-find-file-or-marked
599    "Find file in Dired" 'helm-point-file-in-dired
600    "View file" 'view-file
601    "Query replace fnames on marked `M-@'" 'helm-ff-query-replace-fnames-on-marked
602    "Marked files in dired `C-x C-q, C-u wdired'" 'helm-marked-files-in-dired
603    "Query replace contents on marked `M-%'" 'helm-ff-query-replace
604    "Query replace regexp contents on marked `C-M-%'" 'helm-ff-query-replace-regexp
605    "Attach file(s) to mail buffer `C-c C-a'" 'helm-ff-mail-attach-files
606    "Serial rename files" 'helm-ff-serial-rename
607    "Serial rename by symlinking files" 'helm-ff-serial-rename-by-symlink
608    "Serial rename by copying files" 'helm-ff-serial-rename-by-copying
609    "Open file with default tool" 'helm-open-file-with-default-tool
610    "Find file in hex dump" 'hexl-find-file
611    "Browse project `C-x C-d'" 'helm-ff-browse-project
612    "Complete at point `C-c i'" 'helm-insert-file-name-completion-at-point
613    "Insert as org link `C-c @'" 'helm-files-insert-as-org-link
614    "Find shell command `C-c /'" 'helm-ff-find-sh-command
615    "Add marked files to file-cache" 'helm-ff-cache-add-file
616    "Open file externally `C-c C-x, C-u to choose'" 'helm-open-file-externally
617    "Grep File(s) `C-s, C-u Recurse'" 'helm-find-files-grep
618    "Grep current directory with AG `M-g a, C-u select type'" 'helm-find-files-ag
619    "Git grep `M-g g, C-u from root'" 'helm-ff-git-grep
620    "Zgrep File(s) `M-g z, C-u Recurse'" 'helm-ff-zgrep
621    "Gid `M-g i'" 'helm-ff-gid
622    "Switch to Eshell `M-e'" 'helm-ff-switch-to-eshell
623    "Etags `M-., C-u reload tag file'" 'helm-ff-etags-select
624    "Eshell command on file(s) `M-!, C-u take all marked as arguments.'"
625    'helm-find-files-eshell-command-on-file
626    "Find file as root `C-c r'" 'helm-find-file-as-root
627    "Find alternate file `C-x C-v'" 'find-alternate-file
628    "Ediff File `C-c ='" 'helm-find-files-ediff-files
629    "Ediff Merge File `M-='" 'helm-find-files-ediff-merge-files
630    (lambda () (format "Delete File(s)%s `M-D' (C-u reverse trash)"
631                       (if (eq helm-ff-delete-files-function
632                               'helm-delete-marked-files-async)
633                           " async" "")))
634    'helm-ff-delete-files
635    "Touch File(s) `M-T'" 'helm-ff-touch-files
636    "Copy file(s) `M-C, C-u to follow'" 'helm-find-files-copy
637    "Rename file(s) `M-R, C-u to follow'" 'helm-find-files-rename
638    "Backup files" 'helm-find-files-backup
639    "Symlink files(s) `M-S, C-u to follow'" 'helm-find-files-symlink
640    "Relsymlink file(s) `M-Y, C-u to follow'" 'helm-find-files-relsymlink
641    "Hardlink file(s) `M-H, C-u to follow'" 'helm-find-files-hardlink
642    "Find file other window `C-c o'" 'helm-find-files-other-window
643    "Find file other frame `C-c C-o'" 'find-file-other-frame
644    "Print File `C-c p, C-u to refresh'" 'helm-ff-print
645    "Locate `C-x C-f, C-u to specify locate db'" 'helm-ff-locate)
646   "Actions for `helm-find-files'."
647   :group 'helm-files
648   :type '(alist :key-type string :value-type function))
649
650 (defvar helm-source-find-files nil
651   "The main source to browse files.
652 Should not be used among other sources.")
653
654 (defclass helm-source-ffiles (helm-source-sync)
655   ((header-name
656     :initform (lambda (name)
657                 (concat name (substitute-command-keys
658                               helm-find-files-doc-header))))
659    (init
660     :initform (lambda ()
661                 (setq helm-ff-auto-update-flag
662                       helm-ff-auto-update-initial-value)
663                 (setq helm-ff--auto-update-state
664                       helm-ff-auto-update-flag)
665                 (helm-set-local-variable 'bookmark-make-record-function
666                                          #'helm-ff-make-bookmark-record)
667                 (require 'helm-external)))
668    (candidates :initform 'helm-find-files-get-candidates)
669    (filtered-candidate-transformer
670     :initform '(helm-ff-sort-candidates
671                 (lambda (candidates _source)
672                   (cl-loop for f in candidates
673                            for ff = (helm-ff-filter-candidate-one-by-one f)
674                            when ff collect ff))))
675    (persistent-action-if :initform 'helm-find-files-persistent-action-if)
676    (persistent-help :initform "Hit1 Expand Candidate, Hit2 or (C-u) Find file")
677    (help-message :initform 'helm-ff-help-message)
678    (mode-line :initform (list "File(s)" helm-mode-line-string))
679    (volatile :initform t)
680    (cleanup :initform 'helm-find-files-cleanup)
681    (migemo :initform t)
682    (nohighlight :initform t)
683    (keymap :initform helm-find-files-map)
684    (candidate-number-limit :initform 'helm-ff-candidate-number-limit)
685    (action-transformer
686     :initform 'helm-find-files-action-transformer)
687    (action :initform 'helm-find-files-actions)
688    (before-init-hook :initform 'helm-find-files-before-init-hook)
689    (after-init-hook :initform 'helm-find-files-after-init-hook)
690    (group :initform 'helm-files)))
691
692 ;; Bookmark handlers.
693 ;;
694 (defun helm-ff-make-bookmark-record ()
695   "The `bookmark-make-record-function' for `helm-find-files'."
696   (with-helm-buffer
697     `((filename . ,helm-ff-default-directory)
698       (presel . ,(helm-get-selection))
699       (handler . helm-ff-bookmark-jump))))
700
701 (defun helm-ff-bookmark-jump (bookmark)
702   "bookmark handler for `helm-find-files'."
703   (let ((fname (bookmark-prop-get bookmark 'filename))
704         (presel (bookmark-prop-get bookmark 'presel)))
705     ;; Force tramp connection with `file-directory-p' before lauching
706     ;; hff otherwise the directory name is inserted on top before
707     ;; tramp starts and display candidates.  FNAME is here always a
708     ;; directory.
709     (when (file-directory-p fname)
710       (helm-find-files-1 fname (if helm-ff-transformer-show-only-basename
711                                    (helm-basename presel)
712                                  presel)))))
713
714 (defun helm-ff-bookmark-set ()
715   "Record `helm-find-files' session in bookmarks."
716   (interactive)
717   (with-helm-alive-p
718     (with-helm-buffer
719       (bookmark-set
720        (concat helm-find-files-bookmark-prefix
721                (abbreviate-file-name helm-ff-default-directory))))
722     (message "Helm find files session bookmarked! ")))
723 (put 'helm-ff-bookmark-set 'helm-only t)
724
725 (defcustom helm-dwim-target nil
726   "Default target directory for file actions.
727
728 Define the directory where you want to start navigating for the target
729 directory when copying, renaming etc... You can use the
730 `default-directory' of `next-window', the current
731 `default-directory' or have completion on all the directories
732 belonging to each window."
733   :group 'helm-files
734   :type '(radio :tag "Define default target directory for file actions."
735           (const :tag "Directory belonging to next window" next-window)
736           (const :tag "Completion on directories belonging to each window" completion)
737           (const :tag "Use initial directory or `default-directory'" nil)))
738
739 (defun helm-dwim-target-directory ()
740   "Try to return a suitable directory according to `helm-dwim-target'."
741   (with-helm-current-buffer
742     (let* ((wins (remove (get-buffer-window helm-marked-buffer-name)
743                          (window-list)))
744            (num-windows (length wins)))
745       (expand-file-name
746        (cond ((and (> num-windows 1)
747                    (eq helm-dwim-target 'completion))
748               (helm-comp-read "Browse target starting from: "
749                               (append (list (or (car-safe helm-ff-history)
750                                                 default-directory)
751                                             default-directory)
752                                       (cl-loop for w in wins collect
753                                                (with-selected-window w
754                                                  default-directory)))))
755              ((and (> num-windows 1)
756                    (eq helm-dwim-target 'next-window))
757               (with-selected-window (next-window)
758                 default-directory))
759              ((or (= num-windows 1)
760                   (null helm-dwim-target))
761               ;; Using the car of *ff-history allow
762               ;; staying in the directory visited instead of
763               ;; current.
764               (or (car-safe helm-ff-history) default-directory)))))))
765
766 (defun helm-ff--count-and-collect-dups (files)
767   (cl-loop with dups = (make-hash-table :test 'equal)
768            for f in files
769            for file = (if (file-directory-p f)
770                           (concat (helm-basename f) "/")
771                           (helm-basename f))
772            for count = (gethash file dups)
773            if count do (puthash file (1+ count) dups)
774            else do (puthash file 1 dups)
775            finally return (cl-loop for k being the hash-keys in dups
776                                    using (hash-value v)
777                                    if (> v 1)
778                                    collect (format "%s(%s)" k v)
779                                    else
780                                    collect k)))
781
782 (defun helm-find-files-do-action (action)
783   "Generic function for creating actions from `helm-source-find-files'.
784 ACTION must be an action supported by `helm-dired-action'."
785   (require 'dired-async)
786   (let* ((ifiles (mapcar 'expand-file-name ; Allow modify '/foo/.' -> '/foo'
787                          (helm-marked-candidates :with-wildcard t)))
788          (cand   (helm-get-selection)) ; Target
789          (prefarg helm-current-prefix-arg)
790          (prompt (format "%s %s file(s) to: "
791                          (if (and (and (fboundp 'dired-async-mode)
792                                        dired-async-mode)
793                                   (null prefarg))
794                              (concat "Async " (symbol-name action))
795                            (capitalize (symbol-name action)))
796                          (length ifiles)))
797          helm-ff--move-to-first-real-candidate
798          helm-display-source-at-screen-top ; prevent setting window-start.
799          helm-ff-auto-update-initial-value
800          ;; It is not possible to rename a file to a boring name when
801          ;; helm-ff-skip-boring-files is enabled
802          helm-ff-skip-boring-files
803          ;; If HFF is using a frame use a frame as well.
804          (helm-actions-inherit-frame-settings t)
805          helm-use-frame-when-more-than-two-windows
806          (dest   (with-helm-display-marked-candidates
807                    helm-marked-buffer-name
808                    (helm-ff--count-and-collect-dups ifiles)
809                    (with-helm-current-buffer
810                      (helm-read-file-name
811                       prompt
812                       :preselect (unless (cdr ifiles)
813                                    (concat
814                                     "^"
815                                     (regexp-quote
816                                      (if helm-ff-transformer-show-only-basename
817                                          (helm-basename cand) cand))))
818                       :initial-input (helm-dwim-target-directory)
819                       :history (helm-find-files-history nil :comp-read nil)))))
820          (dest-dir-p (file-directory-p dest))
821          (dest-dir   (helm-basedir dest)))
822     (unless (or dest-dir-p (file-directory-p dest-dir))
823       (when (y-or-n-p (format "Create directory `%s'? " dest-dir))
824         (make-directory dest-dir t)))
825     (helm-dired-action
826      dest :files ifiles :action action :follow prefarg)))
827
828 (defun helm-find-files-copy (_candidate)
829   "Copy files from `helm-find-files'."
830   (helm-find-files-do-action 'copy))
831
832 (defun helm-find-files-backup (_candidate)
833   "Backup files from `helm-find-files'.
834 This reproduce the behavior of \"cp --backup=numbered from to\"."
835   (cl-assert (and (fboundp 'dired-async-mode) dired-async-mode) nil
836              "Backup only available when `dired-async-mode' is enabled")
837   (helm-find-files-do-action 'backup))
838
839 (defun helm-find-files-rename (_candidate)
840   "Rename files from `helm-find-files'."
841   (helm-find-files-do-action 'rename))
842
843 (defun helm-find-files-symlink (_candidate)
844   "Symlink files from `helm-find-files'."
845   (helm-find-files-do-action 'symlink))
846
847 (defun helm-find-files-relsymlink (_candidate)
848   "Relsymlink files from `helm-find-files'."
849   (helm-find-files-do-action 'relsymlink))
850
851 (defun helm-find-files-hardlink (_candidate)
852   "Hardlink files from `helm-find-files'."
853   (helm-find-files-do-action 'hardlink))
854
855 (defun helm-find-files-other-window (_candidate)
856   "Keep current-buffer and open files in separate windows.
857 When a prefix arg is detected files are opened in a vertical windows
858 layout."
859   (let* ((files (helm-marked-candidates))
860          (buffers (mapcar 'find-file-noselect files)))
861     (helm-window-show-buffers buffers t)))
862
863 (defun helm-find-files-byte-compile (_candidate)
864   "Byte compile elisp files from `helm-find-files'."
865   (let ((files    (helm-marked-candidates :with-wildcard t))
866         (parg     helm-current-prefix-arg))
867     (cl-loop for fname in files
868           do (byte-compile-file fname parg))))
869
870 (defun helm-find-files-load-files (_candidate)
871   "Load elisp files from `helm-find-files'."
872   (let ((files    (helm-marked-candidates :with-wildcard t)))
873     (cl-loop for fname in files
874           do (load fname))))
875
876 (defun helm-find-files-ediff-files-1 (candidate &optional merge)
877   "Generic function to ediff/merge files in `helm-find-files'."
878   (let* ((helm-dwim-target 'next-window)
879          (bname  (helm-basename candidate))
880          (marked (helm-marked-candidates :with-wildcard t))
881          (prompt (if merge "Ediff Merge `%s' With File: "
882                    "Ediff `%s' With File: "))
883          (fun    (if merge 'ediff-merge-files 'ediff-files))
884          (input  (helm-dwim-target-directory))
885          (presel (if helm-ff-transformer-show-only-basename
886                      (helm-basename candidate)
887                    (expand-file-name
888                     (helm-basename candidate)
889                     input))))
890     (if (= (length marked) 2)
891         (funcall fun (car marked) (cadr marked))
892       (funcall fun candidate (helm-read-file-name
893                               (format prompt bname)
894                               :initial-input input
895                               :preselect presel)))))
896
897 (defun helm-find-files-ediff-files (candidate)
898   (helm-find-files-ediff-files-1 candidate))
899
900 (defun helm-find-files-ediff-merge-files (candidate)
901   (helm-find-files-ediff-files-1 candidate 'merge))
902
903 (defun helm-find-files-grep (_candidate)
904   "Default action to grep files from `helm-find-files'."
905   (helm-do-grep-1 (helm-marked-candidates :with-wildcard t)
906                   helm-current-prefix-arg))
907
908 (defun helm-ff-git-grep (_candidate)
909   "Default action to git-grep `helm-ff-default-directory'."
910   (helm-grep-git-1 helm-ff-default-directory helm-current-prefix-arg))
911
912 (defun helm-find-files-ag (_candidate)
913   (helm-grep-ag helm-ff-default-directory
914                 helm-current-prefix-arg))
915
916 (defun helm-ff-zgrep (_candidate)
917   "Default action to zgrep files from `helm-find-files'."
918   (helm-ff-zgrep-1 (helm-marked-candidates :with-wildcard t) helm-current-prefix-arg))
919
920 (defun helm-ff-pdfgrep (_candidate)
921   "Default action to pdfgrep files from `helm-find-files'."
922   (let* ((recurse nil)
923          (cands (cl-loop for file in (helm-marked-candidates :with-wildcard t)
924                          for dir = (file-directory-p file)
925                          when dir do (setq recurse t)
926                          when (or dir
927                                   (string= (file-name-extension file) "pdf")
928                                   (string= (file-name-extension file) "PDF"))
929                          collect file)))
930     (when cands
931       (helm-do-pdfgrep-1 cands recurse))))
932
933 (defun helm-ff-etags-select (candidate)
934   "Default action to jump to etags from `helm-find-files'."
935   (when (get-buffer helm-action-buffer)
936     (kill-buffer helm-action-buffer))
937   (let* ((source-name (assoc-default 'name (helm-get-current-source)))
938          (default-directory (if (string= source-name "Find Files")
939                                 helm-ff-default-directory
940                               (file-name-directory candidate))))
941     (helm-etags-select helm-current-prefix-arg)))
942
943 (defvar eshell-command-aliases-list nil)
944 (defvar helm-eshell-command-on-file-input-history nil)
945 (defun helm-find-files-eshell-command-on-file-1 (&optional map)
946   "Run `eshell-command' on CANDIDATE or marked candidates.
947 This is done possibly with an eshell alias, if no alias found, you can type in
948 an eshell command.
949
950 Only aliases accepting a file as argument at the end of command line
951 are collected, i.e aliases ending with \"$1\" or \"$*\".
952
953 Basename of CANDIDATE can be a wild-card.
954 e.g you can do \"eshell-command command *.el\"
955 Where \"*.el\" is the CANDIDATE.
956
957 It is possible to do eshell-command command <CANDIDATE> <some more args>
958 like this: \"command %s some more args\".
959
960 If MAP is given run `eshell-command' on all marked files at once,
961 Otherwise, run `eshell-command' on each marked files.
962 In other terms, with a prefix arg do on the three marked files
963 \"foo\" \"bar\" \"baz\":
964
965 \"eshell-command command foo bar baz\"
966
967 otherwise do
968
969 \"eshell-command command foo\"
970 \"eshell-command command bar\"
971 \"eshell-command command baz\"
972
973 Note:
974 You have to setup some aliases in eshell with the `alias' command or
975 by editing yourself the file `eshell-aliases-file' to make this
976 working."
977   (require 'em-alias) (eshell-read-aliases-list)
978   (when (or eshell-command-aliases-list
979             (y-or-n-p "No eshell aliases found, run eshell-command without alias anyway? "))
980     (let* ((cand-list (helm-marked-candidates))
981            (default-directory (or helm-ff-default-directory
982                                   ;; If candidate is an url *-ff-default-directory is nil
983                                   ;; so keep value of default-directory.
984                                   default-directory))
985            (command (helm-comp-read
986                      "Command: "
987                      (cl-loop for (a c) in (eshell-read-aliases-list)
988                               ;; Positional arguments may be double
989                               ;; quoted (Issue #1881).
990                               when (string-match "[\"]?.*\\(\\$1\\|\\$\\*\\)[\"]?\\'" c)
991                               collect (propertize a 'help-echo c) into ls
992                               finally return (sort ls 'string<))
993                      :buffer "*helm eshell on file*"
994                      :name "Eshell command"
995                      :mode-line
996                      '("Eshell alias"
997                        "C-h m: Help, \\[universal-argument]: Insert output at point")
998                      :help-message 'helm-esh-help-message
999                      :input-history
1000                      'helm-eshell-command-on-file-input-history))
1001            (alias-value (car (assoc-default command eshell-command-aliases-list)))
1002            cmd-line)
1003       (if (or (equal helm-current-prefix-arg '(16))
1004               (equal map '(16)))
1005           ;; Two time C-u from `helm-comp-read' mean print to current-buffer.
1006           ;; i.e `eshell-command' will use this value.
1007           (setq current-prefix-arg '(16))
1008           ;; Else reset the value of `current-prefix-arg'
1009           ;; to avoid printing in current-buffer.
1010           (setq current-prefix-arg nil))
1011       (if (and (or
1012                 ;; One prefix-arg have been passed before `helm-comp-read'.
1013                 ;; If map have been set with C-u C-u (value == '(16))
1014                 ;; ignore it.
1015                 (and map (equal map '(4)))
1016                 ;; One C-u from `helm-comp-read'.
1017                 (equal helm-current-prefix-arg '(4))
1018                 ;; An alias that finish with $*
1019                 (and alias-value
1020                      ;; If command is an alias be sure it accept
1021                      ;; more than one arg i.e $*.
1022                      (string-match "\\$\\*$" alias-value)))
1023                (cdr cand-list))
1024
1025           ;; Run eshell-command with ALL marked files as arguments.
1026           ;; This wont work on remote files, because tramp handlers depends
1027           ;; on `default-directory' (limitation).
1028           (let ((mapfiles (mapconcat 'eshell-quote-argument cand-list " ")))
1029             (if (string-match "'%s'\\|\"%s\"\\|%s" command)
1030                 (setq cmd-line (format command mapfiles)) ; See [1]
1031                 (setq cmd-line (format "%s %s" command mapfiles)))
1032             (helm-log "%S" cmd-line)
1033             (eshell-command cmd-line))
1034
1035           ;; Run eshell-command on EACH marked files.
1036           ;; To work with tramp handler we have to call
1037           ;; COMMAND on basename of each file, using
1038           ;; its basedir as `default-directory'.
1039           (cl-loop for f in cand-list
1040                    for dir = (and (not (string-match helm--url-regexp f))
1041                                   (helm-basedir f))
1042                    for file = (eshell-quote-argument
1043                                (format "%s" (if (and dir (file-remote-p dir))
1044                                                 (helm-basename f) f)))
1045                    for com = (if (string-match "'%s'\\|\"%s\"\\|%s" command)
1046                                  ;; [1] This allow to enter other args AFTER filename
1047                                  ;; i.e <command %s some_more_args>
1048                                  (format command file)
1049                                  (format "%s %s" command file))
1050                    do (let ((default-directory (or dir default-directory)))
1051                         (eshell-command com)))))))
1052
1053 (defun helm-find-files-eshell-command-on-file (_candidate)
1054   "Run `eshell-command' on CANDIDATE or marked candidates.
1055 See `helm-find-files-eshell-command-on-file-1' for more info."
1056   (helm-find-files-eshell-command-on-file-1 helm-current-prefix-arg))
1057
1058 (defun helm-ff-switch-to-eshell (_candidate)
1059   "Switch to eshell and cd to `helm-ff-default-directory'.
1060
1061 With a numeric prefix arg switch to numbered eshell buffer, if no
1062 prefix arg provided and more than one eshell buffer exists, provide
1063 completions on those buffers.  If only one eshell buffer exists,
1064 switch to this one, if no eshell buffer exists or if the numeric
1065 prefix arg eshell buffer doesn't exists, create it and switch to it."
1066   (let ((cd-eshell (lambda ()
1067                      (eshell/cd helm-ff-default-directory)
1068                      (eshell-reset)))
1069         (bufs (cl-loop for b in (mapcar 'buffer-name (buffer-list))
1070                        when (helm-ff--eshell-interactive-buffer-p b)
1071                        collect b)))
1072     (helm-aif (and (null helm-current-prefix-arg)
1073                    (if (cdr bufs)
1074                        (helm-comp-read "Switch to eshell buffer: " bufs
1075                                        :must-match t)
1076                      (car bufs)))
1077         (switch-to-buffer it)
1078       (eshell helm-current-prefix-arg))
1079     (unless (get-buffer-process (current-buffer))
1080       (funcall cd-eshell))))
1081
1082 (defun helm-ff--eshell-interactive-buffer-p (buffer)
1083   (with-current-buffer buffer
1084     (and (eq major-mode 'eshell-mode)
1085          (save-excursion
1086            (goto-char (point-min))
1087            (eshell-next-prompt 1)
1088            (null (eql (point) (point-min)))))))
1089
1090 (defun helm-ff-touch-files (_candidate)
1091   "The touch files action for helm-find-files."
1092   (let* ((files (helm-marked-candidates))
1093          (split (cl-loop for f in files
1094                          for spt = (unless helm-current-prefix-arg
1095                                      (cons (helm-basedir f)
1096                                            (split-string f ", ?")))
1097                          if spt
1098                          append (cl-loop with dir = (car spt)
1099                                          for ff in (cdr spt)
1100                                          collect (expand-file-name ff dir))
1101                          else collect f))
1102          (timestamp (helm-comp-read
1103                      "Timestamp (default Now): "
1104                      (cl-loop for f in split
1105                               for time = (file-attributes f)
1106                               for date = (and time
1107                                               (format-time-string
1108                                                "%Y-%m-%d %H:%M:%S"
1109                                                (nth 5 time)))
1110                               when date
1111                               collect (cons (format "%s: %s"
1112                                                     (helm-basename f) date)
1113                                             date))
1114                      :default
1115                      (format-time-string "%Y-%m-%d %H:%M:%S"
1116                                          (current-time))))
1117          (failures
1118           (cl-loop with default-directory = helm-ff-default-directory
1119                    for f in split
1120                    for file = (or (file-remote-p f 'localname) f)
1121                    when (> (process-file
1122                             "touch" nil nil nil "-d" timestamp file)
1123                            0)
1124                    collect f)))
1125     (when failures
1126       (message "Failed to touch *%s files:\n%s"
1127                (length failures)
1128                (mapconcat (lambda (f) (format "- %s\n" f)) failures "")))))
1129
1130 (defun helm-ff-run-touch-files ()
1131   "Used to interactively run touch file action from keyboard."
1132   (interactive)
1133   (with-helm-alive-p
1134     (helm-exit-and-execute-action 'helm-ff-touch-files)))
1135 (put 'helm-ff-run-touch-files 'helm-only t)
1136
1137 (defun helm-ff-serial-rename-action (method)
1138   "Rename all marked files in `helm-ff-default-directory' with METHOD.
1139 See `helm-ff-serial-rename-1'."
1140   (let* ((helm--reading-passwd-or-string t)
1141          (cands     (helm-marked-candidates :with-wildcard t))
1142          (def-name  (car cands))
1143          (name      (helm-read-string "NewName: "
1144                                       (replace-regexp-in-string
1145                                        "[0-9]+$" ""
1146                                        (helm-basename
1147                                         def-name
1148                                         (file-name-extension def-name)))))
1149          (start     (read-number "StartAtNumber: "))
1150          (extension (helm-read-string "Extension: "
1151                                       (file-name-extension (car cands))))
1152          (dir       (expand-file-name
1153                      (helm-read-file-name
1154                       "Serial Rename to directory: "
1155                       :initial-input
1156                       (expand-file-name helm-ff-default-directory)
1157                       :test 'file-directory-p
1158                       :must-match t)))
1159          done)
1160     (with-helm-display-marked-candidates
1161       helm-marked-buffer-name (helm-ff--count-and-collect-dups cands)
1162       (if (y-or-n-p
1163            (format "Rename %s file(s) to <%s> like this ?\n%s "
1164                    (length cands) dir (format "%s <-> %s%s.%s"
1165                                               (helm-basename (car cands))
1166                                               name start extension)))
1167           (progn
1168             (helm-ff-serial-rename-1
1169              dir cands name start extension :method method)
1170             (setq done t)
1171             (message nil))))
1172     (if done
1173         (with-helm-current-buffer (helm-find-files-1 dir))
1174       (message "Operation aborted"))))
1175
1176 (defun helm-ff-member-directory-p (file directory)
1177   (let ((dir-file (expand-file-name
1178                    (file-name-as-directory (file-name-directory file))))
1179         (cur-dir  (expand-file-name (file-name-as-directory directory))))
1180     (string= dir-file cur-dir)))
1181
1182 (cl-defun helm-ff-serial-rename-1
1183     (directory collection new-name start-at-num extension &key (method 'rename))
1184   "rename files in COLLECTION to DIRECTORY with the prefix name NEW-NAME.
1185 Rename start at number START-AT-NUM - ex: prefixname-01.jpg.
1186 EXTENSION is the file extension to use, in empty prompt,
1187 reuse the original extension of file.
1188 METHOD can be one of rename, copy or symlink.
1189 Files will be renamed if they are files of current directory, otherwise they
1190 will be treated with METHOD.
1191 Default METHOD is rename."
1192   ;; Maybe remove directories selected by error in collection.
1193   (setq collection (cl-remove-if 'file-directory-p collection))
1194   (let* ((tmp-dir  (file-name-as-directory
1195                     (concat (file-name-as-directory directory)
1196                             (symbol-name (cl-gensym "tmp")))))
1197          (fn       (cl-case method
1198                      (copy    'copy-file)
1199                      (symlink 'make-symbolic-link)
1200                      (rename  'rename-file)
1201                      (t (error "Error: Unknown method %s" method)))))
1202     (make-directory tmp-dir)
1203     (unwind-protect
1204          (progn
1205            ;; Rename all files to tmp-dir with new-name.
1206            ;; If files are not from start directory, use method
1207            ;; to move files to tmp-dir.
1208            (cl-loop for i in collection
1209                  for count from start-at-num
1210                  for fnum = (if (< count 10) "0%s" "%s")
1211                  for nname = (concat tmp-dir new-name (format fnum count)
1212                                      (if (not (string= extension ""))
1213                                          (format ".%s" (replace-regexp-in-string
1214                                                         "[.]" "" extension))
1215                                        (file-name-extension i 'dot)))
1216                  do (if (helm-ff-member-directory-p i directory)
1217                         (rename-file i nname)
1218                       (funcall fn i nname)))
1219            ;; Now move all from tmp-dir to destination.
1220            (cl-loop with dirlist = (directory-files
1221                                     tmp-dir t directory-files-no-dot-files-regexp)
1222                  for f in dirlist do
1223                  (if (file-symlink-p f)
1224                      (make-symbolic-link (file-truename f)
1225                                          (concat (file-name-as-directory directory)
1226                                                  (helm-basename f)))
1227                    (rename-file f directory))))
1228       (delete-directory tmp-dir t))))
1229
1230 (defun helm-ff-serial-rename (_candidate)
1231   "Serial rename all marked files to `helm-ff-default-directory'.
1232 Rename only file of current directory, and symlink files coming from
1233 other directories.
1234 See `helm-ff-serial-rename-1'."
1235   (helm-ff-serial-rename-action 'rename))
1236
1237 (defun helm-ff-serial-rename-by-symlink (_candidate)
1238   "Serial rename all marked files to `helm-ff-default-directory'.
1239 Rename only file of current directory, and symlink files coming from
1240 other directories.
1241 See `helm-ff-serial-rename-1'."
1242   (helm-ff-serial-rename-action 'symlink))
1243
1244 (defun helm-ff-serial-rename-by-copying (_candidate)
1245   "Serial rename all marked files to `helm-ff-default-directory'.
1246 Rename only file of current directory, and copy files coming from
1247 other directories.
1248 See `helm-ff-serial-rename-1'."
1249   (helm-ff-serial-rename-action 'copy))
1250
1251 (defvar helm-ff-query-replace-fnames-history-from nil)
1252 (defvar helm-ff-query-replace-fnames-history-to nil)
1253 (defun helm-ff-query-replace-on-filenames (candidates)
1254   "Query replace on filenames of CANDIDATES.
1255 This doesn't replace inside the files, only modify filenames."
1256   (with-helm-display-marked-candidates
1257     helm-marked-buffer-name
1258     (mapcar 'helm-basename candidates)
1259     (let* ((regexp (read-string "Replace regexp on filename(s): "
1260                                 nil 'helm-ff-query-replace-history-from
1261                                 (helm-basename (car candidates))))
1262            (rep    (read-string (format "Replace regexp `%s' with: " regexp)
1263                                 nil 'helm-ff-query-replace-history-to))
1264            subexp)
1265       (cl-loop with query = "y"
1266                with count = 0
1267                with target = nil
1268                for old in candidates
1269                for new = (concat (helm-basedir old)
1270                                  (helm--replace-regexp-in-buffer-string
1271                                   (save-match-data
1272                                     (cond ((string= regexp "%.")
1273                                            (setq subexp 1)
1274                                            (helm-ff--prepare-str-with-regexp
1275                                             (setq target (helm-basename old t))))
1276                                           ((string= regexp ".%")
1277                                            (setq subexp 1)
1278                                            (helm-ff--prepare-str-with-regexp
1279                                             (setq target (file-name-extension old))))
1280                                           ((string= regexp "%")
1281                                            (regexp-quote
1282                                             (setq target (helm-basename old))))
1283                                           ((string-match "%:\\([0-9]+\\):\\([0-9]+\\)" regexp)
1284                                            (setq subexp 1)
1285                                            (let ((beg (match-string 1 regexp))
1286                                                  (end (match-string 2 regexp))
1287                                                  (str (helm-basename old)))
1288                                              (setq target (substring str
1289                                                                      (string-to-number beg)
1290                                                                      (string-to-number end)))
1291                                              (helm-ff--prepare-str-with-regexp str beg end)))
1292                                           (t regexp)))
1293                                   (save-match-data
1294                                     (cond (;; Handle incremental
1295                                            ;; replacement with \# in
1296                                            ;; search and replace
1297                                            ;; feature in placeholder \@.
1298                                            (string-match
1299                                             "\\\\@/\\(.*\\)/\\(\\(?99:.*\\)\\\\#\\)/"
1300                                             rep)
1301                                            (replace-regexp-in-string
1302                                             (match-string 1 rep)
1303                                             (concat (match-string 99 rep)
1304                                                     (format "%03d" (1+ count)))
1305                                             target))
1306                                           ;; Incremental replacement
1307                                           ;; before or after \@.
1308                                           ((and (string-match-p "\\\\#" rep)
1309                                                 (string-match "\\\\@" rep))
1310                                            (replace-regexp-in-string
1311                                             "\\\\#" (format "%03d" (1+ count))
1312                                             (replace-match target t t rep)))
1313                                           ;; Simple incremental replacement.
1314                                           ((string-match "\\\\#" rep)
1315                                            (replace-match
1316                                             (format "%03d" (1+ count)) t t rep))
1317                                           ;; Substring replacement in placeholder.
1318                                           ((string-match
1319                                             "\\\\@:\\([0-9]*\\):\\([0-9]*\\)" rep)
1320                                            (replace-match (substring
1321                                                            target
1322                                                            (string-to-number
1323                                                             (match-string 1 rep))
1324                                                            (pcase (match-string 2 rep)
1325                                                              ((pred (string= ""))
1326                                                               (length target))
1327                                                              (res (string-to-number res))))
1328                                                           t t rep))
1329                                           ;; Search and replace in
1330                                           ;; placeholder. Doesn't
1331                                           ;; handle incremental here.
1332                                           ((string-match "\\\\@/\\(.*\\)/\\(.*\\)/" rep)
1333                                            (replace-match (replace-regexp-in-string
1334                                                            (match-string 1 rep)
1335                                                            (match-string 2 rep)
1336                                                            target t)
1337                                                           t t rep))
1338                                           ;; Simple replacement by placeholder.
1339                                           ((string-match "\\\\@" rep)
1340                                            (replace-match target t t rep))
1341                                           ;; Replacement with
1342                                           ;; upcase, downcase or
1343                                           ;; capitalized text.
1344                                           ((string= rep "%u") #'upcase)
1345                                           ((string= rep "%d") #'downcase)
1346                                           ((string= rep "%c") #'capitalize)
1347                                           ;; Simple replacement with
1348                                           ;; whole replacement regexp.
1349                                           (t rep)))
1350                                   (helm-basename old) t nil subexp))
1351                ;; If `regexp' is not matched in `old'
1352                ;; `replace-regexp-in-string' will
1353                ;; return `old' unmodified.
1354                unless (string= old new)
1355                do (progn
1356                     (when (file-exists-p new)
1357                       (setq new (concat (file-name-sans-extension new)
1358                                         (format "(%s)" count)
1359                                         (file-name-extension new t))))
1360                     (unless (string= query "!")
1361                       (setq query (helm-read-answer (format
1362                                                      "Replace `%s' by `%s' [!,y,n,q]"
1363                                                      old new)
1364                                                     '("y" "n" "!" "q"))))
1365                     (when (string= query "q")
1366                       (cl-return (message "Operation aborted")))
1367                     (unless (string= query "n")
1368                       (rename-file old new)
1369                       (cl-incf count)))
1370                finally (message "%d Files renamed" count))))
1371   ;; This fix the emacs bug where "Emacs-Lisp:" is sent
1372   ;; in minibuffer (not the echo area).
1373   (sit-for 0.1)
1374   (with-current-buffer (window-buffer (minibuffer-window))
1375     (delete-minibuffer-contents)))
1376
1377 (defun helm-ff--prepare-str-with-regexp (str &optional rep1 rep2)
1378   ;; This is used in `helm-ff-query-replace-on-filenames' to prepare
1379   ;; STR when REGEXP is specified as substring e.g %:1:3 in this case
1380   ;; substring from 1 to 3 in STR will be enclosed with parenthesis to
1381   ;; match this substring as a subexp e.g %:1:3 on string "emacs" will
1382   ;; be replaced by "e\\(ma\\)cs" using subexp 1 like this:
1383   ;; (helm--replace-regexp-in-buffer-string "e\\(ma\\)cs" "fo" "emacs" nil t 1)
1384   ;; => "efocs"
1385   ;;      ^^
1386   ;; Where "1" and "3" will be strings extracted with match-string
1387   ;; from regexp and refered respectively in this function as REP1 and
1388   ;; REP2.
1389   (let* ((from   (or (and rep1 (string-to-number rep1)) 0))
1390          (to     (or (and rep2 (string-to-number rep2)) (length str)))
1391          (subexp (concat "\\(" (regexp-quote (substring str from to)) "\\)"))
1392          (before-str (unless (zerop from)
1393                        (regexp-quote (substring str 0 from))))
1394          (after-str (unless (= to (length str))
1395                       (regexp-quote (substring str to (length str))))))
1396     (concat before-str subexp after-str)))
1397
1398 ;; The action.
1399 (defun helm-ff-query-replace-fnames-on-marked (_candidate)
1400   (let ((marked (helm-marked-candidates :with-wildcard t)))
1401     (helm-ff-query-replace-on-filenames marked)))
1402
1403 ;; The command for `helm-find-files-map'.
1404 (defun helm-ff-run-query-replace-fnames-on-marked ()
1405   (interactive)
1406   (with-helm-alive-p
1407     (helm-exit-and-execute-action 'helm-ff-query-replace-fnames-on-marked)))
1408 (put 'helm-ff-run-query-replace-fnames-on-marked 'helm-only t)
1409
1410 (defun helm-ff-query-replace (_candidate)
1411   (let ((bufs (cl-loop for f in (helm-marked-candidates :with-wildcard t)
1412                        collect (buffer-name (find-file-noselect f)))))
1413     (helm-buffer-query-replace-1 nil bufs)))
1414
1415 (defun helm-ff-query-replace-regexp (_candidate)
1416   (let ((bufs (cl-loop for f in (helm-marked-candidates :with-wildcard t)
1417                        collect (buffer-name (find-file-noselect f)))))
1418     (helm-buffer-query-replace-1 'regexp bufs)))
1419
1420 (defun helm-ff-run-query-replace ()
1421   (interactive)
1422   (with-helm-alive-p
1423     (helm-exit-and-execute-action 'helm-ff-query-replace)))
1424 (put 'helm-ff-run-query-replace 'helm-only t)
1425
1426 (defun helm-ff-run-query-replace-regexp ()
1427   (interactive)
1428   (with-helm-alive-p
1429     (helm-exit-and-execute-action 'helm-ff-query-replace-regexp)))
1430 (put 'helm-ff-run-query-replace-regexp 'helm-only t)
1431
1432 (defun helm-ff-toggle-auto-update (_candidate)
1433   (if helm-ff--deleting-char-backward
1434       (progn
1435         (message "[Auto expansion disabled]")
1436         (sit-for 1) (message nil)
1437         (setq helm-ff--auto-update-state nil))
1438     (setq helm-ff-auto-update-flag (not helm-ff-auto-update-flag))
1439     (setq helm-ff--auto-update-state helm-ff-auto-update-flag)
1440     (message "[Auto expansion %s]"
1441              (if helm-ff-auto-update-flag "enabled" "disabled"))))
1442
1443 (defun helm-ff-run-toggle-auto-update ()
1444   (interactive)
1445   (with-helm-alive-p
1446     (helm-attrset 'toggle-auto-update '(helm-ff-toggle-auto-update . never-split))
1447     (helm-execute-persistent-action 'toggle-auto-update)))
1448 (put 'helm-ff-run-toggle-auto-update 'helm-only t)
1449
1450 (defun helm-ff-delete-char-backward ()
1451   "Disable helm find files auto update and delete char backward."
1452   (interactive)
1453   (with-helm-alive-p
1454     (setq helm-ff-auto-update-flag nil)
1455     (setq helm-ff--deleting-char-backward t)
1456     (call-interactively
1457      (lookup-key (current-global-map)
1458                  (read-kbd-macro "DEL")))
1459     (helm--update-header-line)))
1460 (put 'helm-ff-delete-char-backward 'helm-only t)
1461
1462 (defun helm-ff-delete-char-backward--exit-fn ()
1463   (setq helm-ff-auto-update-flag helm-ff--auto-update-state)
1464   (setq helm-ff--deleting-char-backward nil))
1465
1466 (defun helm-ff-RET-1 (&optional must-match)
1467   "Used for RET action in `helm-find-files'.
1468 See `helm-ff-RET' for details.
1469 If MUST-MATCH is specified exit with
1470 `helm-confirm-and-exit-minibuffer' which handle must-match mechanism."
1471   (let ((sel   (helm-get-selection)))
1472     (cl-assert sel nil "Trying to exit with no candidates")
1473     (if (and (file-directory-p sel)
1474              (not (string= "." (helm-basename sel))))
1475         (helm-execute-persistent-action)
1476       (if must-match
1477           (helm-confirm-and-exit-minibuffer)
1478         (helm-maybe-exit-minibuffer)))))
1479
1480 (defun helm-ff-RET ()
1481   "Default action for RET in `helm-find-files'.
1482
1483 Behave differently depending of `helm-selection':
1484
1485 - candidate basename is \".\" => open it in dired.
1486 - candidate is a directory    => expand it.
1487 - candidate is a file         => open it."
1488   (interactive)
1489   (helm-ff-RET-1))
1490
1491 (defun helm-ff-RET-must-match ()
1492   "Same as `helm-ff-RET' but used in must-match map."
1493   (interactive)
1494   (helm-ff-RET-1 t))
1495
1496 (defun helm-ff-run-grep ()
1497   "Run Grep action from `helm-source-find-files'."
1498   (interactive)
1499   (with-helm-alive-p
1500     (helm-exit-and-execute-action 'helm-find-files-grep)))
1501 (put 'helm-ff-run-grep 'helm-only t)
1502
1503 (defun helm-ff-run-git-grep ()
1504   "Run git-grep action from `helm-source-find-files'."
1505   (interactive)
1506   (with-helm-alive-p
1507     (helm-exit-and-execute-action 'helm-ff-git-grep)))
1508 (put 'helm-ff-run-git-grep 'helm-only t)
1509
1510 (defun helm-ff-run-grep-ag ()
1511   (interactive)
1512   (with-helm-alive-p
1513     (helm-exit-and-execute-action 'helm-find-files-ag)))
1514 (put 'helm-ff-run-grep-ag 'helm-only t)
1515
1516 (defun helm-ff-run-pdfgrep ()
1517   "Run Pdfgrep action from `helm-source-find-files'."
1518   (interactive)
1519   (with-helm-alive-p
1520     (helm-exit-and-execute-action 'helm-ff-pdfgrep)))
1521 (put 'helm-ff-run-pdfgrep 'helm-only t)
1522
1523 (defun helm-ff-run-zgrep ()
1524   "Run Grep action from `helm-source-find-files'."
1525   (interactive)
1526   (with-helm-alive-p
1527     (helm-exit-and-execute-action 'helm-ff-zgrep)))
1528 (put 'helm-ff-run-zgrep 'helm-only t)
1529
1530 (defun helm-ff-run-copy-file ()
1531   "Run Copy file action from `helm-source-find-files'."
1532   (interactive)
1533   (with-helm-alive-p
1534     (helm-exit-and-execute-action 'helm-find-files-copy)))
1535 (put 'helm-ff-run-copy-file 'helm-only t)
1536
1537 (defun helm-ff-run-rename-file ()
1538   "Run Rename file action from `helm-source-find-files'."
1539   (interactive)
1540   (with-helm-alive-p
1541     (helm-exit-and-execute-action 'helm-find-files-rename)))
1542 (put 'helm-ff-run-rename-file 'helm-only t)
1543
1544 (defun helm-ff-run-byte-compile-file ()
1545   "Run Byte compile file action from `helm-source-find-files'."
1546   (interactive)
1547   (with-helm-alive-p
1548     (helm-exit-and-execute-action 'helm-find-files-byte-compile)))
1549 (put 'helm-ff-run-byte-compile-file 'helm-only t)
1550
1551 (defun helm-ff-run-load-file ()
1552   "Run Load file action from `helm-source-find-files'."
1553   (interactive)
1554   (with-helm-alive-p
1555     (helm-exit-and-execute-action 'helm-find-files-load-files)))
1556 (put 'helm-ff-run-load-file 'helm-only t)
1557
1558 (defun helm-ff-run-eshell-command-on-file ()
1559   "Run eshell command on file action from `helm-source-find-files'."
1560   (interactive)
1561   (with-helm-alive-p
1562     (helm-exit-and-execute-action
1563      'helm-find-files-eshell-command-on-file)))
1564 (put 'helm-ff-run-eshell-command-on-file 'helm-only t)
1565
1566 (defun helm-ff-run-ediff-file ()
1567   "Run Ediff file action from `helm-source-find-files'."
1568   (interactive)
1569   (with-helm-alive-p
1570     (helm-exit-and-execute-action 'helm-find-files-ediff-files)))
1571 (put 'helm-ff-run-ediff-file 'helm-only t)
1572
1573 (defun helm-ff-run-ediff-merge-file ()
1574   "Run Ediff merge file action from `helm-source-find-files'."
1575   (interactive)
1576   (with-helm-alive-p
1577     (helm-exit-and-execute-action
1578      'helm-find-files-ediff-merge-files)))
1579 (put 'helm-ff-run-ediff-merge-file 'helm-only t)
1580
1581 (defun helm-ff-run-symlink-file ()
1582   "Run Symlink file action from `helm-source-find-files'."
1583   (interactive)
1584   (with-helm-alive-p
1585     (helm-exit-and-execute-action 'helm-find-files-symlink)))
1586 (put 'helm-ff-run-symlink-file 'helm-only t)
1587
1588 (defun helm-ff-run-relsymlink-file ()
1589   "Run Symlink file action from `helm-source-find-files'."
1590   (interactive)
1591   (with-helm-alive-p
1592     (helm-exit-and-execute-action 'helm-find-files-relsymlink)))
1593 (put 'helm-ff-run-relsymlink-file 'helm-only t)
1594
1595 (defun helm-ff-run-hardlink-file ()
1596   "Run Hardlink file action from `helm-source-find-files'."
1597   (interactive)
1598   (with-helm-alive-p
1599     (helm-exit-and-execute-action 'helm-find-files-hardlink)))
1600 (put 'helm-ff-run-hardlink-file 'helm-only t)
1601
1602 (defun helm-ff-delete-files (candidate)
1603   "Delete files default action."
1604   (funcall helm-ff-delete-files-function candidate))
1605
1606 (defun helm-ff-run-delete-file ()
1607   "Run Delete file action from `helm-source-find-files'."
1608   (interactive)
1609   (with-helm-alive-p
1610     (helm-exit-and-execute-action #'helm-ff-delete-files)))
1611 (put 'helm-ff-run-delete-file 'helm-only t)
1612
1613 (defun helm-ff-run-complete-fn-at-point ()
1614   "Run complete file name action from `helm-source-find-files'."
1615   (interactive)
1616   (with-helm-alive-p
1617     (helm-exit-and-execute-action
1618      'helm-insert-file-name-completion-at-point)))
1619 (put 'helm-ff-run-complete-fn-at-point 'helm-only t)
1620
1621 (defun helm-ff-run-switch-to-eshell ()
1622   "Run switch to eshell action from `helm-source-find-files'."
1623   (interactive)
1624   (with-helm-alive-p
1625     (helm-exit-and-execute-action 'helm-ff-switch-to-eshell)))
1626 (put 'helm-ff-run-switch-to-eshell 'helm-only t)
1627
1628 (defun helm-ff-run-switch-other-window ()
1629   "Run switch to other window action from `helm-source-find-files'.
1630 When a prefix arg is provided, split is done vertically."
1631   (interactive)
1632   (with-helm-alive-p
1633     (helm-exit-and-execute-action 'helm-find-files-other-window)))
1634 (put 'helm-ff-run-switch-other-window 'helm-only t)
1635
1636 (defun helm-ff-run-switch-other-frame ()
1637   "Run switch to other frame action from `helm-source-find-files'."
1638   (interactive)
1639   (with-helm-alive-p
1640     (helm-exit-and-execute-action 'find-file-other-frame)))
1641 (put 'helm-ff-run-switch-other-frame 'helm-only t)
1642
1643 (defun helm-ff-run-open-file-externally ()
1644   "Run open file externally command action from `helm-source-find-files'."
1645   (interactive)
1646   (with-helm-alive-p
1647     (helm-exit-and-execute-action 'helm-open-file-externally)))
1648 (put 'helm-ff-run-open-file-externally 'helm-only t)
1649
1650 (defun helm-ff-run-open-file-with-default-tool ()
1651   "Run open file externally command action from `helm-source-find-files'."
1652   (interactive)
1653   (with-helm-alive-p
1654     (helm-exit-and-execute-action 'helm-open-file-with-default-tool)))
1655 (put 'helm-ff-run-open-file-with-default-tool 'helm-only t)
1656
1657 (defun helm-ff-locate (candidate)
1658   "Locate action function for `helm-find-files'."
1659   (helm-locate-set-command)
1660   (let ((default (concat (helm-basename
1661                         (expand-file-name
1662                          candidate
1663                          helm-ff-default-directory))
1664                          (unless (or
1665                                   ;; "-b" is already added when fuzzy matching.
1666                                   helm-locate-fuzzy-match
1667                                   ;; The locate '-b' option doesn't exists
1668                                   ;; in everything (es).
1669                                   (and (eq system-type 'windows-nt)
1670                                        (string-match "^es" helm-locate-command)))
1671                            " -b"))))
1672     (helm-locate-1 helm-current-prefix-arg nil 'from-ff default)))
1673
1674 (defun helm-ff-run-locate ()
1675   "Run locate action from `helm-source-find-files'."
1676   (interactive)
1677   (with-helm-alive-p
1678     (helm-exit-and-execute-action 'helm-ff-locate)))
1679 (put 'helm-ff-run-locate 'helm-only t)
1680
1681 (defun helm-files-insert-as-org-link (candidate)
1682   (insert (format "[[%s][]]" candidate))
1683   (goto-char (- (point) 2)))
1684
1685 (defun helm-ff-run-insert-org-link ()
1686   (interactive)
1687   (with-helm-alive-p
1688     (helm-exit-and-execute-action 'helm-files-insert-as-org-link)))
1689 (put 'helm-ff-run-insert-org-link 'helm-only t)
1690
1691 (defun helm-ff-run-find-file-as-root ()
1692   (interactive)
1693   (with-helm-alive-p
1694     (helm-exit-and-execute-action 'helm-find-file-as-root)))
1695 (put 'helm-ff-run-find-file-as-root 'helm-only t)
1696
1697 (defun helm-ff-run-find-alternate-file ()
1698   (interactive)
1699   (with-helm-alive-p
1700     (helm-exit-and-execute-action 'find-alternate-file)))
1701 (put 'helm-ff-run-find-alternate-file 'helm-only t)
1702
1703 (defun helm-ff-run-mail-attach-files ()
1704   "Run mail attach files command action from `helm-source-find-files'."
1705   (interactive)
1706   (with-helm-alive-p
1707     (helm-exit-and-execute-action 'helm-ff-mail-attach-files)))
1708 (put 'helm-ff-run-mail-attach-files 'helm-only t)
1709
1710 (defun helm-ff-run-etags ()
1711   "Run Etags command action from `helm-source-find-files'."
1712   (interactive)
1713   (with-helm-alive-p
1714     (helm-exit-and-execute-action 'helm-ff-etags-select)))
1715 (put 'helm-ff-run-etags 'helm-only t)
1716
1717 (defvar lpr-printer-switch)
1718 (defun helm-ff-print (_candidate)
1719   "Print marked files.
1720
1721 You may to set in order
1722 variables `lpr-command',`lpr-switches' and/or `printer-name',
1723 but with no settings helm should detect your printer(s) and
1724 print with the default `lpr' settings.
1725
1726 NOTE: DO NOT set the \"-P\" flag in `lpr-switches', if you really
1727 have to modify this, do it in `lpr-printer-switch'.
1728
1729 Same as `dired-do-print' but for helm."
1730   (require 'lpr)
1731   (when (or helm-current-prefix-arg
1732             (not helm-ff-printer-list))
1733     (setq helm-ff-printer-list
1734           (helm-ff-find-printers)))
1735   (let* ((file-list (helm-marked-candidates :with-wildcard t))
1736          (len (length file-list))
1737          (printer-name (if helm-ff-printer-list
1738                            (helm-comp-read
1739                             "Printer: " helm-ff-printer-list)
1740                          printer-name))
1741          (lpr-switches
1742       (if (and (stringp printer-name)
1743            (string< "" printer-name))
1744           (cons (concat lpr-printer-switch printer-name)
1745             lpr-switches)
1746               lpr-switches))
1747          (command (helm-read-string
1748                    (format "Print *%s File(s):\n%s with: "
1749                            len
1750                            (mapconcat
1751                             (lambda (f) (format "- %s\n" f))
1752                             file-list ""))
1753                    (when (and lpr-command lpr-switches)
1754                      (mapconcat 'identity
1755                                 (cons lpr-command
1756                                       (if (stringp lpr-switches)
1757                                           (list lpr-switches)
1758                                           lpr-switches))
1759                                 " "))))
1760          (file-args (mapconcat (lambda (x)
1761                                    (format "'%s'" x))
1762                                file-list " "))
1763          (cmd-line (concat command " " file-args)))
1764     (if command
1765         (start-process-shell-command "helm-print" nil cmd-line)
1766       (error "Error: Please verify your printer settings in Emacs."))))
1767
1768 (defun helm-ff-run-print-file ()
1769   "Run Print file action from `helm-source-find-files'."
1770   (interactive)
1771   (with-helm-alive-p
1772     (helm-exit-and-execute-action 'helm-ff-print)))
1773 (put 'helm-ff-run-print-file 'helm-only t)
1774
1775 (defun helm-ff-checksum (file)
1776   "Calculate the checksum of FILE.
1777 The checksum is copied to kill-ring."
1778   (cl-assert (file-regular-p file)
1779              nil "`%s' is not a regular file" file)
1780   (let ((algo (intern (helm-comp-read
1781                        "Algorithm: "
1782                        '(md5 sha1 sha224 sha256 sha384 sha512))))
1783         (bn (helm-basename file)))
1784     (message "Calculating %s checksum for %s..." algo bn)
1785     (async-let ((sum (with-temp-buffer
1786                        (insert-file-contents-literally file)
1787                        (secure-hash algo (current-buffer)))))
1788       (kill-new sum)
1789       (message "Calculating %s checksum for `%s' done and copied to kill-ring" algo bn))))
1790
1791 (defun helm-ff-toggle-basename (_candidate)
1792   (with-helm-buffer
1793     (setq helm-ff-transformer-show-only-basename
1794           (not helm-ff-transformer-show-only-basename))
1795     (let* ((cand   (helm-get-selection nil t))
1796            (target (if helm-ff-transformer-show-only-basename
1797                        (helm-basename cand) cand)))
1798       (helm-force-update (concat (regexp-quote target) "$")))))
1799
1800 (defun helm-ff-run-toggle-basename ()
1801   (interactive)
1802   (with-helm-alive-p
1803     (unless (helm-empty-source-p)
1804       (helm-ff-toggle-basename nil))))
1805 (put 'helm-ff-run-toggle-basename 'helm-only t)
1806
1807 (defun helm-reduce-file-name (fname level)
1808   "Reduce FNAME by number LEVEL from end."
1809   ;; This version comes from issue #2004 (UNC paths) and should fix it.
1810   (while (> level 0)
1811     (unless (or (string= fname "/")
1812                 (string= (file-remote-p fname 'localname) "/"))
1813       (setq fname (expand-file-name
1814                    (concat (expand-file-name fname) "/../"))))
1815     (setq level (1- level)))
1816   fname)
1817
1818 (defvar helm-find-files--level-tree nil)
1819 (defvar helm-find-files--level-tree-iterator nil)
1820 (defun helm-find-files-up-one-level (arg)
1821   "Go up one level like unix command `cd ..'.
1822 If prefix numeric arg is given go ARG level up."
1823   (interactive "p")
1824   (with-helm-alive-p
1825     (let ((src (helm-get-current-source)))
1826       (when (and (helm-file-completion-source-p src)
1827                  (not (helm-ff--invalid-tramp-name-p)))
1828         (with-helm-window
1829           (when (helm-follow-mode-p)
1830             (helm-follow-mode -1) (message nil)))
1831         ;; When going up one level we want to be at the line
1832         ;; corresponding to actual directory, so store this info
1833         ;; in `helm-ff-last-expanded'.
1834         (let ((cur-cand (helm-get-selection nil nil src))
1835               (new-pattern (helm-reduce-file-name helm-pattern arg)))
1836           ;; Ensure visibility on all candidates for preselection.
1837           (helm-attrset 'candidate-number-limit
1838                         (if helm-ff-up-one-level-preselect
1839                             (max (gethash new-pattern
1840                                           helm-ff--directory-files-hash
1841                                           helm-ff-candidate-number-limit)
1842                                  helm-ff-candidate-number-limit)
1843                           helm-ff-candidate-number-limit))
1844           (cond ((file-directory-p helm-pattern)
1845                  (setq helm-ff-last-expanded helm-ff-default-directory))
1846                 ((file-exists-p helm-pattern)
1847                  (setq helm-ff-last-expanded helm-pattern))
1848                 ((and cur-cand (file-exists-p cur-cand))
1849                  (setq helm-ff-last-expanded cur-cand)))
1850           (unless helm-find-files--level-tree
1851             (setq helm-find-files--level-tree
1852                   (cons helm-ff-default-directory
1853                         helm-find-files--level-tree)))
1854           (setq helm-find-files--level-tree-iterator nil)
1855           (push new-pattern helm-find-files--level-tree)
1856           (helm-set-pattern new-pattern helm-suspend-update-flag)
1857           (with-helm-after-update-hook (helm-ff-retrieve-last-expanded)))))))
1858 (put 'helm-find-files-up-one-level 'helm-only t)
1859
1860 (defun helm-find-files-down-last-level ()
1861   "Retrieve previous paths reached by `C-l' in helm-find-files."
1862   (interactive)
1863   (with-helm-alive-p
1864     (when (and (helm-file-completion-source-p)
1865                (not (helm-ff--invalid-tramp-name-p)))
1866       (unless helm-find-files--level-tree-iterator
1867         (setq helm-find-files--level-tree-iterator
1868               (helm-iter-list (cdr helm-find-files--level-tree))))
1869       (setq helm-find-files--level-tree nil)
1870       (helm-aif (helm-iter-next helm-find-files--level-tree-iterator)
1871           (helm-set-pattern it)
1872         (setq helm-find-files--level-tree-iterator nil)))))
1873 (put 'helm-find-files-down-last-level 'helm-only t)
1874
1875 (defun helm-find-files--reset-level-tree ()
1876   (setq helm-find-files--level-tree-iterator nil
1877         helm-find-files--level-tree nil))
1878
1879 (add-hook 'helm-cleanup-hook 'helm-find-files--reset-level-tree)
1880 (add-hook 'post-self-insert-hook 'helm-find-files--reset-level-tree)
1881 (add-hook 'helm-after-persistent-action-hook 'helm-find-files--reset-level-tree)
1882
1883 (defun helm-ff-retrieve-last-expanded ()
1884   "Move overlay to last visited directory `helm-ff-last-expanded'.
1885 This happen after using `helm-find-files-up-one-level',
1886 or hitting C-j on \"..\"."
1887   (when helm-ff-last-expanded
1888     (let ((presel (if helm-ff-transformer-show-only-basename
1889                       (helm-basename
1890                        (directory-file-name helm-ff-last-expanded))
1891                     (directory-file-name helm-ff-last-expanded))))
1892       (with-helm-window
1893         (when (re-search-forward (concat "^" (regexp-quote presel) "$") nil t)
1894           (forward-line 0)
1895           (helm-mark-current-line)))
1896       (setq helm-ff-last-expanded nil))))
1897
1898 (defun helm-ff-move-to-first-real-candidate ()
1899   "When candidate is an incomplete file name move to first real candidate."
1900   (let* ((src (helm-get-current-source))
1901          (name (assoc-default 'name src))
1902          ;; Ensure `helm-file-completion-source-p' returns nil on
1903          ;; `helm-read-file-name' history.
1904          minibuffer-completing-file-name)
1905     (helm-aif (and (helm-file-completion-source-p src)
1906                    (not (helm-empty-source-p))
1907                    ;; Prevent dired commands moving to first real
1908                    ;; (Issue #910).
1909                    (or (memq (intern-soft name)
1910                              helm-ff-goto-first-real-dired-exceptions)
1911                        (not (string-match "\\`[Dd]ired-" name)))
1912                    helm-ff--move-to-first-real-candidate
1913                    (helm-get-selection nil nil src))
1914         (unless (or (not (stringp it))
1915                     (and (string-match helm-tramp-file-name-regexp it)
1916                          (not (file-remote-p it nil t)))
1917                     (file-exists-p it))
1918           (helm-next-line)))))
1919
1920 ;;; Auto-update - helm-find-files auto expansion of directories.
1921 ;;
1922 ;;
1923 (defun helm-ff-update-when-only-one-matched ()
1924   "Expand to directory when sole completion.
1925 When only one candidate is remaining and it is a directory,
1926 expand to this directory.
1927 This happen only when `helm-ff-auto-update-flag' is non--nil
1928 or when `helm-pattern' is equal to \"~/\"."
1929   (let ((src (helm-get-current-source)))
1930     (when (and (helm-file-completion-source-p src)
1931                (not (get-buffer-window helm-action-buffer 'visible))
1932                (not (helm-ff--invalid-tramp-name-p))
1933                (not (string-match-p "\\`[.]\\{2\\}[^/]+"
1934                                     (helm-basename helm-pattern))))
1935       (with-helm-buffer
1936         (let* ((history-p   (string= (assoc-default 'name src)
1937                                      "Read File Name History"))
1938                (pat         (if (string-match helm-tramp-file-name-regexp
1939                                               helm-pattern)
1940                                 (helm-ff--create-tramp-name helm-pattern)
1941                                 helm-pattern))
1942                (completed-p (string= (file-name-as-directory
1943                                       (expand-file-name
1944                                        (substitute-in-file-name pat)))
1945                                      helm-ff-default-directory))
1946                (candnum (helm-get-candidate-number))
1947                (lt2-p   (and (<= candnum 2)
1948                              (>= (string-width (helm-basename helm-pattern)) 2)))
1949                (cur-cand (prog2
1950                              (unless (or completed-p
1951                                          (file-exists-p pat)
1952                                          history-p (null lt2-p))
1953                                ;; Only one non--existing candidate
1954                                ;; and one directory candidate, move to it,
1955                                ;; but not when renaming, copying etc...,
1956                                ;; so for this use
1957                                ;; `helm-ff-move-to-first-real-candidate'
1958                                ;; instead of `helm-next-line' (Issue #910).
1959                                (helm-ff-move-to-first-real-candidate))
1960                              (helm-get-selection nil nil src))))
1961           (when (and (or (and helm-ff-auto-update-flag
1962                               (null helm-ff--deleting-char-backward)
1963                               ;; Issue #295
1964                               ;; File predicates are returning t
1965                               ;; with paths like //home/foo.
1966                               ;; So check it is not the case by regexp
1967                               ;; to allow user to do C-a / to start e.g
1968                               ;; entering a tramp method e.g /sudo::.
1969                               (not (string-match "\\`//" helm-pattern))
1970                               (not (eq last-command 'helm-yank-text-at-point)))
1971                          ;; Fix issue #542.
1972                          (string= helm-pattern "~/")
1973                          ;; Only one remaining directory, expand it.
1974                          (and (= candnum 1)
1975                               helm-ff--auto-update-state
1976                               (file-accessible-directory-p pat)
1977                               (null helm-ff--deleting-char-backward)))
1978                      (or
1979                       ;; Only one candidate remaining
1980                       ;; and at least 2 char in basename.
1981                       lt2-p
1982                       ;; Already completed.
1983                       completed-p)
1984                      (not history-p) ; Don't try to auto complete in history.
1985                      (stringp cur-cand)
1986                      (file-accessible-directory-p cur-cand))
1987             (if (and (not (helm-dir-is-dot cur-cand)) ; [1]
1988                      ;; Maybe we are here because completed-p is true
1989                      ;; but check this again to be sure. (Windows fix)
1990                      (<= candnum 2))    ; [2]
1991                 ;; If after going to next line the candidate
1992                 ;; is not one of "." or ".." [1]
1993                 ;; and only one candidate is remaining [2],
1994                 ;; assume candidate is a new directory to expand, and do it.
1995                 (helm-set-pattern (file-name-as-directory cur-cand))
1996                 ;; The candidate is one of "." or ".."
1997                 ;; that mean we have entered the last letter of the directory name
1998                 ;; in prompt, so expansion is already done, just add the "/" at end
1999                 ;; of name unless helm-pattern ends with "."
2000                 ;; (i.e we are writing something starting with ".")
2001                 (unless (string-match "\\`.*[.]\\{1\\}\\'" helm-pattern)
2002                   (helm-set-pattern
2003                    ;; Need to expand-file-name to avoid e.g /ssh:host:./ in prompt.
2004                    (expand-file-name (file-name-as-directory helm-pattern)))))
2005             (helm-check-minibuffer-input)))))))
2006
2007 (defun helm-ff-auto-expand-to-home-or-root ()
2008   "Allow expanding to home/user directory or root or text yanked after pattern."
2009   (when (and (helm-file-completion-source-p)
2010              (with-current-buffer (window-buffer (minibuffer-window)) (eolp))
2011              (not (string-match helm-ff-url-regexp helm-pattern)))
2012     (cond ((and (not (file-remote-p helm-pattern))
2013                 (null (file-exists-p helm-pattern))
2014                 (string-match-p
2015                  "\\`\\([.]\\|\\s-\\)\\{2\\}[^/]+"
2016                  (helm-basename helm-pattern))
2017                 (string-match-p "/\\'" helm-pattern))
2018            (helm-ff-recursive-dirs helm-pattern)
2019            (with-helm-window (helm-check-minibuffer-input)))
2020           ((string-match
2021             "\\(?:\\`~/\\)\\|/?\\$.*/\\|/\\./\\|/\\.\\./\\|/~.*/\\|//\\|\\(/[[:alpha:]]:/\\|\\s\\+\\)"
2022             helm-pattern)
2023            (let* ((match (match-string 0 helm-pattern))
2024                   (input (cond ((string= match "/./")
2025                                 (expand-file-name default-directory))
2026                                ((string= helm-pattern "/../") "/")
2027                                ((string-match-p "\\`/\\$" match)
2028                                 (let ((sub (substitute-in-file-name match)))
2029                                   (if (file-directory-p sub)
2030                                       sub (replace-regexp-in-string "/\\'" "" sub))))
2031                                (t (helm-ff--expand-substitued-pattern helm-pattern)))))
2032              ;; `file-directory-p' returns t on "/home/me/." (issue #1844).
2033              (if (and (file-directory-p input)
2034                       (not (string-match-p "[^.]\\.\\'" input)))
2035                  (setq helm-ff-default-directory
2036                        (setq input (file-name-as-directory input)))
2037                  (setq helm-ff-default-directory (file-name-as-directory
2038                                                   (file-name-directory input))))
2039              (with-helm-window
2040                (helm-set-pattern input)
2041                (helm-check-minibuffer-input)))))))
2042
2043 (defun helm-ff--expand-file-name-no-dot (name &optional directory)
2044   "Prevent expanding \"/home/user/.\" to \"/home/user\"."
2045   ;; Issue #1844 - If user enter "~/." to type an hidden filename
2046   ;; don't expand to /home/him e.g.
2047   ;; (expand-file-name "~/.") =>"/home/thierry"
2048   ;; (helm-ff--expand-substitued-pattern "~/.") =>"/home/thierry/."
2049   (concat (expand-file-name name directory)
2050           (and (string-match "[^.]\\.\\'" name) "/.")))
2051
2052 (defun helm-ff--expand-substitued-pattern (pattern)
2053   ;; [Windows] On UNC paths "/" expand to current machine,
2054   ;; so use the root of current Drive. (i.e "C:/")
2055   (let* ((directory (and (memq system-type '(windows-nt ms-dos))
2056                          (getenv "SystemDrive")))
2057          ;; On Windows use a simple call to `expand-file-name' to
2058          ;; avoid Issue #2004.
2059          (expand-fn (if directory
2060                         #'expand-file-name
2061                       #'helm-ff--expand-file-name-no-dot)))
2062     (funcall expand-fn (helm-substitute-in-filename pattern)
2063              ;; directory is nil on Nix.
2064              directory)))
2065
2066 (defun helm-substitute-in-filename (fname)
2067   "Substitute all parts of FNAME from start up to \"~/\" or \"/\".
2068 On windows system substitute from start up to \"/[[:lower:]]:/\".
2069 This function is needed for `helm-ff-auto-expand-to-home-or-root'
2070 and should be used carefully elsewhere, or not at all, using
2071 `substitute-in-file-name' instead."
2072   (cond ((and helm--url-regexp
2073               (string-match-p helm--url-regexp fname))
2074          fname)
2075         ((and (file-remote-p fname)
2076               helm-substitute-in-filename-stay-on-remote)
2077          (let ((sub (substitute-in-file-name fname)))
2078            (if (file-directory-p sub)
2079                sub (replace-regexp-in-string "/\\'" "" sub))))
2080         (t
2081          (with-temp-buffer
2082            (insert fname)
2083            (goto-char (point-min))
2084            (when (memq system-type '(windows-nt ms-dos))
2085              (skip-chars-forward "/")) ;; Avoid infloop in UNC paths Issue #424
2086            (if (re-search-forward "~.*/?\\|//\\|/[[:alpha:]]:/" nil t)
2087                (let ((match (match-string 0)))
2088                  (goto-char (if (or (string= match "//")
2089                                     (string-match-p "/[[:alpha:]]:/" match))
2090                                 (1+ (match-beginning 0))
2091                                 (match-beginning 0)))
2092                  (buffer-substring-no-properties (point) (point-at-eol)))
2093                fname)))))
2094
2095 (defun helm-point-file-in-dired (file)
2096   "Put point on filename FILE in dired buffer."
2097   (unless (and helm--url-regexp
2098                (string-match-p helm--url-regexp file))
2099     (let ((target (expand-file-name (helm-substitute-in-filename file))))
2100       (dired (file-name-directory target))
2101       (dired-goto-file target))))
2102
2103 (defun helm-marked-files-in-dired (_candidate)
2104   "Open a dired buffer with only marked files.
2105
2106 With a prefix arg toggle dired buffer to wdired mode."
2107   (advice-add 'wdired-finish-edit :override #'helm--advice-wdired-finish-edit)
2108   (advice-add 'wdired-get-filename :override #'helm--advice-wdired-get-filename)
2109   (let* ((marked (helm-marked-candidates :with-wildcard t))
2110          (current (car marked)))
2111     (unless (and helm--url-regexp
2112                  (string-match-p helm--url-regexp current))
2113       (let ((target (expand-file-name (helm-substitute-in-filename current))))
2114         (dired (cons helm-ff-default-directory marked))
2115         (dired-goto-file target)
2116         (when (or helm-current-prefix-arg current-prefix-arg)
2117           (call-interactively 'wdired-change-to-wdired-mode))))))
2118
2119 (defun helm-ff-run-marked-files-in-dired ()
2120   "Execute `helm-marked-files-in-dired' interactively."
2121   (interactive)
2122   (with-helm-alive-p
2123     (helm-exit-and-execute-action 'helm-marked-files-in-dired)))
2124 (put 'helm-ff-run-marked-files-in-dired 'helm-only t)
2125
2126 (defun helm-ff--create-tramp-name (fname)
2127   "Build filename from `helm-pattern' like /su:: or /sudo::."
2128   ;; `tramp-make-tramp-file-name' takes 7 args on emacs-26 whereas it
2129   ;; takes only 5 args in emacs-24/25.
2130   (apply #'tramp-make-tramp-file-name
2131          ;; `tramp-dissect-file-name' returns a list in emacs-26
2132          ;; whereas in 24.5 it returns a vector, thus the car is a
2133          ;; symbol (`tramp-file-name') which is not needed as argument
2134          ;; for `tramp-make-tramp-file-name' so transform the cdr in
2135          ;; vector, and for 24.5 use directly the returned value.
2136          (cl-loop with v = (helm-ff--tramp-cons-or-vector
2137                             (tramp-dissect-file-name fname))
2138                   for i across v collect i)))
2139
2140 (defun helm-ff--tramp-cons-or-vector (vector-or-cons)
2141   "Return VECTOR-OR-CONS as a vector."
2142   (pcase vector-or-cons
2143     (`(,_l . ,ll) (vconcat ll))
2144     ((and vec (pred vectorp)) vec)))
2145
2146 (defun helm-ff--get-tramp-methods ()
2147   "Returns a list of the car of `tramp-methods'."
2148   (or helm-ff--tramp-methods
2149       (setq helm-ff--tramp-methods (mapcar 'car tramp-methods))))
2150
2151 (defun helm-ff--previous-mh-tramp-method (str)
2152   (save-match-data
2153     (with-temp-buffer
2154       (insert str)
2155       (when (re-search-backward
2156              (concat "\\([|]\\)\\("
2157                      (mapconcat 'identity (helm-ff--get-tramp-methods) "\\|")
2158                      "\\):")
2159              nil t)
2160         (list
2161          (buffer-substring-no-properties (point-at-bol) (match-beginning 2))
2162          (buffer-substring-no-properties (match-beginning 2) (match-end 2)))))))
2163
2164 (defun helm-ff--get-host-from-tramp-invalid-fname (fname)
2165   "Extract hostname from an incomplete tramp file name.
2166 Return nil on valid file name remote or not."
2167   ;; Check first if whole file is remote (file-remote-p is inefficient
2168   ;; in this case) otherwise we are matching e.g. /home/you/ssh:foo/
2169   ;; which is not a remote name.
2170   ;; FIXME this will not work with a directory or a file named like
2171   ;; "ssh:foo" and located at root (/) but it seems there is no real
2172   ;; solution apart disabling tramp-mode when a file/dir located at /
2173   ;; is matching helm-tramp-file-name-regexp; This would prevent usage
2174   ;; of tramp if one have such a directory at / (who would want to
2175   ;; have such a dir at / ???)  See emacs-bug#31489.
2176   (when (string-match-p helm-tramp-file-name-regexp fname)
2177     (let* ((bn    (helm-basename fname))
2178            (bd    (replace-regexp-in-string (regexp-quote bn) "" fname))
2179            (split (split-string bn ":" t))
2180            (meth  (car (member (car split)
2181                                (helm-ff--get-tramp-methods)))))
2182       (and meth (string= bd "/") (car (last split))))))
2183
2184 (cl-defun helm-ff--tramp-hostnames (&optional (pattern helm-pattern))
2185   "Get a list of hosts for tramp method found in `helm-pattern'.
2186 Argument PATTERN default to `helm-pattern', it is here only for debugging
2187 purpose."
2188   (when (string-match helm-tramp-file-name-regexp pattern)
2189     (let* ((mh-method   (helm-ff--previous-mh-tramp-method pattern))
2190            (method      (or (cadr mh-method) (match-string 1 pattern)))
2191            (current-mh-host (helm-aif (and mh-method
2192                                            (helm-ff--get-host-from-tramp-invalid-fname pattern))
2193                                 (concat (car mh-method) method ":"
2194                                         (car (split-string it "|" t)))))
2195            (all-methods (helm-ff--get-tramp-methods))
2196            (comps (cl-loop for (f . h) in (tramp-get-completion-function method)
2197                            append (cl-loop for e in (funcall f (car h))
2198                                            for host = (and (consp e) (cadr e))
2199                                            when (and host (not (member host all-methods)))
2200                                            collect (concat (or (car mh-method) "/")
2201                                                            method ":" host)))))
2202       (helm-fast-remove-dups
2203        (delq nil (cons current-mh-host comps))
2204        :test 'equal))))
2205
2206 (defun helm-ff-before-action-hook-fn ()
2207   "Exit helm when user try to execute action on an invalid tramp fname."
2208   (let* ((src (helm-get-current-source))
2209          (cand (helm-get-selection nil nil src)))
2210     (when (and (helm-file-completion-source-p src)
2211                (stringp cand)
2212                (helm-ff--invalid-tramp-name-p cand) ; Check candidate.
2213                (helm-ff--invalid-tramp-name-p)) ; check helm-pattern.
2214       (error "Error: Unknown file or directory `%s'" cand))))
2215 (add-hook 'helm-before-action-hook 'helm-ff-before-action-hook-fn)
2216
2217 (cl-defun helm-ff--invalid-tramp-name-p (&optional (pattern helm-pattern))
2218   "Return non--nil when PATTERN is an invalid tramp filename."
2219   (string= (helm-ff-set-pattern pattern)
2220            "Invalid tramp file name"))
2221
2222 (defun helm-ff--tramp-postfixed-p (str)
2223   (let (result)
2224     (save-match-data
2225       (with-temp-buffer
2226         (save-excursion (insert str))
2227         (helm-awhile (search-forward ":" nil t)
2228           (if (save-excursion
2229                 (forward-char -1)
2230                 (looking-back
2231                  (mapconcat 'identity (helm-ff--get-tramp-methods) "\\|")
2232                  (point-at-bol)))
2233               (setq result nil)
2234               (setq result it)))))
2235     result))
2236
2237 (defun helm-ff-set-pattern (pattern)
2238   "Handle tramp filenames in `helm-pattern'."
2239   (let* ((methods (helm-ff--get-tramp-methods))
2240          ;; Returns the position of last ":" entered.
2241          (postfixed (helm-ff--tramp-postfixed-p pattern))
2242          (reg "\\`/\\([^[/:]+\\|[^/]+]\\):.*:")
2243          cur-method tramp-name)
2244     ;; In some rare cases tramp can return a nil input,
2245     ;; so be sure pattern is a string for safety (Issue #476).
2246     (unless pattern (setq pattern ""))
2247     (cond ((string-match helm-ff-url-regexp pattern) pattern)
2248           ((string-match "\\`\\$" pattern)
2249            (substitute-in-file-name pattern))
2250           ((string= pattern "") "")
2251           ((string-match "\\`[.]\\{1,2\\}/\\'" pattern)
2252            (expand-file-name pattern))
2253           ;; Directories ending by a dot (issue #1940)
2254           ((string-match "[^/][.]/\\'" pattern)
2255            (expand-file-name pattern))
2256           ((string-match ".*\\(~?/?[.]\\{1\\}/\\)\\'" pattern)
2257            (expand-file-name default-directory))
2258           ((string-match ".*\\(~//\\|//\\)\\'" pattern)
2259            (expand-file-name "/"))      ; Expand to "/" or "c:/"
2260           ((string-match "\\`\\(~/\\|.*/~/\\)\\'" pattern)
2261            (expand-file-name "~/"))
2262           ((string-match "\\`~/" pattern)
2263            (expand-file-name pattern))
2264           ;; Match "/method:maybe_hostname:~"
2265           ((and (string-match (concat reg "~") pattern)
2266                 postfixed
2267                 (setq cur-method (match-string 1 pattern))
2268                 (member cur-method methods))
2269            (setq tramp-name (expand-file-name
2270                              (helm-ff--create-tramp-name
2271                               (match-string 0 pattern))))
2272            (replace-match tramp-name nil t pattern))
2273           ;; Match "/method:maybe_hostname:"
2274           ((and (string-match reg pattern)
2275                 postfixed
2276                 (setq cur-method (match-string 1 pattern))
2277                 (member cur-method methods))
2278            (setq tramp-name (helm-ff--create-tramp-name
2279                              (match-string 0 pattern)))
2280            (replace-match tramp-name nil t pattern))
2281           ;; Match "/hostname:"
2282           ((and (string-match helm-tramp-file-name-regexp pattern)
2283                 postfixed
2284                 (setq cur-method (match-string 1 pattern))
2285                 (and cur-method (not (member cur-method methods))))
2286            (setq tramp-name (helm-ff--create-tramp-name
2287                              (match-string 0 pattern)))
2288            (replace-match tramp-name nil t pattern))
2289           ;; Match "/method:" in this case don't try to connect.
2290           ((and (null postfixed)
2291                 (string-match helm-tramp-file-name-regexp pattern)
2292                 (member (match-string 1 pattern) methods))
2293            "Invalid tramp file name")   ; Write in helm-buffer.
2294           ;; Return PATTERN unchanged.
2295           (t pattern))))
2296
2297 (defun helm-find-files-get-candidates (&optional require-match)
2298   "Create candidate list for `helm-source-find-files'."
2299   (let* ((path          (helm-ff-set-pattern helm-pattern))
2300          (dir-p         (file-accessible-directory-p path))
2301          basedir
2302          invalid-basedir
2303          non-essential
2304          (tramp-verbose helm-tramp-verbose)) ; No tramp message when 0.
2305     ;; Tramp check if path is valid without waiting a valid
2306     ;; connection and may send a file-error.
2307     (setq helm--ignore-errors (file-remote-p path))
2308     (set-text-properties 0 (length path) nil path)
2309     ;; Issue #118 allow creation of newdir+newfile.
2310     (unless (or
2311              ;; A tramp file name not completed.
2312              (string= path "Invalid tramp file name")
2313              ;; An empty pattern
2314              (string= path "")
2315              (and (string-match-p ":\\'" path)
2316                   (helm-ff--tramp-postfixed-p path))
2317              ;; Check if base directory of PATH is valid.
2318              (helm-aif (file-name-directory path)
2319                  ;; If PATH is a valid directory IT=PATH,
2320                  ;; else IT=basedir of PATH.
2321                  (file-directory-p it)))
2322       ;; BASEDIR is invalid, that's mean user is starting
2323       ;; to write a non--existing path in minibuffer
2324       ;; probably to create a 'new_dir' or a 'new_dir+new_file'.
2325       (setq invalid-basedir t))
2326     ;; Don't set now `helm-pattern' if `path' == "Invalid tramp file name"
2327     ;; like that the actual value (e.g /ssh:) is passed to
2328     ;; `helm-ff--tramp-hostnames'.
2329     (unless (or (string= path "Invalid tramp file name")
2330                 invalid-basedir)      ; Leave  helm-pattern unchanged.
2331       (setq helm-ff-auto-update-flag  ; [1]
2332             ;; Unless auto update is disabled start auto updating only
2333             ;; at third char.
2334             (unless (or (null helm-ff--auto-update-state)
2335                         ;; But don't enable auto update when
2336                         ;; deleting backward.
2337                         helm-ff--deleting-char-backward
2338                         (and dir-p (not (string-match-p "/\\'" path))))
2339               (or (>= (length (helm-basename path)) 3) dir-p)))
2340       ;; At this point the tramp connection is triggered.
2341       (helm-log
2342        "Pattern=%S"
2343        (setq helm-pattern (helm-ff--transform-pattern-for-completion path)))
2344       ;; This have to be set after [1] to allow deleting char backward.
2345       (setq basedir (or (helm-aand
2346                          (if (and dir-p helm-ff-auto-update-flag)
2347                              ;; Add the final "/" to path
2348                              ;; when `helm-ff-auto-update-flag' is enabled.
2349                              (file-name-as-directory path)
2350                            (if (string= path "")
2351                                "/" (file-name-directory path)))
2352                          (expand-file-name it))
2353                         default-directory))
2354       (setq helm-ff-default-directory
2355             (if (string= helm-pattern "")
2356                 (expand-file-name "/")  ; Expand to "/" or "c:/"
2357                 ;; If path is an url *default-directory have to be nil.
2358                 (unless (or (string-match helm-ff-url-regexp path)
2359                             (and helm--url-regexp
2360                                  (string-match helm--url-regexp path)))
2361                   basedir))))
2362     (when (and (string-match ":\\'" path)
2363                (file-remote-p basedir nil t))
2364       (setq helm-pattern basedir))
2365     (cond ((string= path "Invalid tramp file name")
2366            (or (helm-ff--tramp-hostnames) ; Hostnames completion.
2367                (prog2
2368                    ;; `helm-pattern' have not been modified yet.
2369                    ;; Set it here to the value of `path' that should be now
2370                    ;; "Invalid tramp file name" and set the candidates list
2371                    ;; to ("Invalid tramp file name") to make `helm-pattern'
2372                    ;; match single candidate "Invalid tramp file name".
2373                    (setq helm-pattern path)
2374                    ;; "Invalid tramp file name" is now printed
2375                    ;; in `helm-buffer'.
2376                    (list path))))
2377           ((or (and (file-regular-p path)
2378                     (eq last-repeatable-command 'helm-execute-persistent-action))
2379                ;; `ffap-url-regexp' don't match until url is complete.
2380                (string-match helm-ff-url-regexp path)
2381                invalid-basedir
2382                (and (not (file-exists-p path)) (string-match "/$" path))
2383                (and helm--url-regexp (string-match helm--url-regexp path)))
2384            (list path))
2385           ((string= path "") (helm-ff-directory-files "/"))
2386           ;; Check here if directory is accessible (not working on Windows).
2387           ((and (file-directory-p path) (not (file-readable-p path)))
2388            (list (format "file-error: Opening directory permission denied `%s'" path)))
2389           ;; A fast expansion of PATH is made only if `helm-ff-auto-update-flag'
2390           ;; is enabled.
2391           ((and dir-p helm-ff-auto-update-flag)
2392            (helm-ff-directory-files path))
2393           (t (append (unless (or require-match
2394                                  ;; When `helm-ff-auto-update-flag' has been
2395                                  ;; disabled, whe don't want PATH to be added on top
2396                                  ;; if it is a directory.
2397                                  dir-p)
2398                        (list path))
2399                      (helm-ff-directory-files basedir))))))
2400
2401 (defun helm-list-directory (directory)
2402   "List directory DIRECTORY.
2403
2404 If DIRECTORY is remote use `helm-list-directory-function' otherwise use
2405 `directory-files'."
2406   (if (file-remote-p directory)
2407       (funcall helm-list-directory-function directory)
2408     (directory-files directory t directory-files-no-dot-files-regexp)))
2409
2410 (defun helm-list-dir-lisp (directory)
2411   "List DIRECTORY with `file-name-all-completions' as backend.
2412
2413 Add a `helm-ff-dir' property on each fname ending with \"/\"."
2414   ;; NOTE: `file-name-all-completions' and `directory-files' and most
2415   ;; tramp file handlers don't handle cntrl characters in fnames, so
2416   ;; the displayed files will be plain wrong in this case, even worst
2417   ;; the filenames will be splitted in two or more filenames.
2418   (cl-loop for f in (sort (file-name-all-completions "" directory)
2419                           'string-lessp)
2420            unless (or (string= f "")
2421                       (member f '("./" "../"))
2422                       ;; Ignore the tramp names from /
2423                       ;; completion, e.g. ssh: scp: etc...
2424                       (char-equal (aref f (1- (length f))) ?:))
2425            if (and (helm--dir-name-p f)
2426                    (helm--dir-file-name f directory))
2427            collect (propertize it 'helm-ff-dir t)
2428            else collect (propertize (expand-file-name f directory)
2429                                     'helm-ff-file t)))
2430
2431 (defun helm-list-dir-external (dir)
2432   "List directory DIR with external shell command as backend.
2433
2434 This function is fast enough to be used for remote files and save the
2435 type of files at the same time in a property for using it later in the
2436 transformer."
2437   (let ((default-directory (file-name-as-directory
2438                             (expand-file-name dir))))
2439     (with-temp-buffer
2440       (when (eq (process-file-shell-command
2441                  (format
2442                   ;; -A remove dot files, -F append [*=@|/>] at eof
2443                   ;; and -Q quote the real filename.  If not using -Q,
2444                   ;; there is no way to distinguish if foo* is a real
2445                   ;; file or if it is foo the executable file so with
2446                   ;; -Q we have "foo"* for the executable file foo and
2447                   ;; "foo*" for the real file foo. The downside is
2448                   ;; that we need an extra step to remove the quotes
2449                   ;; at the end which impact performances.
2450                   "ls -A -1 -F -b -Q | awk -v dir=%s '{print dir $1}'"
2451                   (shell-quote-argument default-directory))
2452                  nil t nil)
2453                 0)
2454         (goto-char (point-min))
2455         (save-excursion
2456           (while (re-search-forward "[*=@|/>]$" nil t)
2457             ;; A line looks like /home/you/"foo"@
2458             (helm-acase (match-string 0)
2459               ("*" (replace-match "")
2460                    (put-text-property
2461                     (point-at-bol) (point-at-eol) 'helm-ff-exe t))
2462               ("@" (replace-match "")
2463                    (put-text-property
2464                     (point-at-bol) (point-at-eol) 'helm-ff-sym t))
2465               ("/" (replace-match "")
2466                    (put-text-property
2467                     (point-at-bol) (point-at-eol) 'helm-ff-dir t))
2468               (("=" "|" ">") (replace-match "")))))
2469         (while (re-search-forward "[\"]" nil t)
2470           (replace-match ""))
2471         (add-text-properties (point-min) (point-max) '(helm-ff-file t))
2472         (split-string (buffer-string) "\n" t)))))
2473
2474 (defun helm-ff-directory-files (directory)
2475   "List contents of DIRECTORY.
2476 Argument FULL mean absolute path.
2477 It is same as `directory-files' but always returns the
2478 dotted filename '.' and '..' even on root directories in Windows
2479 systems."
2480   (setq directory (file-name-as-directory
2481                    (expand-file-name directory)))
2482   (let* (file-error
2483          (ls   (condition-case err
2484                    (helm-list-directory directory)
2485                  ;; Handle file-error from here for Windows
2486                  ;; because predicates like `file-readable-p' and friends
2487                  ;; seem broken on emacs for Windows systems (always returns t).
2488                  ;; This should never be called on GNU/Linux/Unix
2489                  ;; as the error is properly intercepted in
2490                  ;; `helm-find-files-get-candidates' by `file-readable-p'.
2491                  (file-error
2492                   (prog1
2493                       (list (format "%s:%s"
2494                                     (car err)
2495                                     (mapconcat 'identity (cdr err) " ")))
2496                     (setq file-error t)))))
2497         (dot  (concat directory "."))
2498         (dot2 (concat directory "..")))
2499     (puthash directory (+ (length ls) 2) helm-ff--directory-files-hash)
2500     (append (and (not file-error) (list dot dot2)) ls)))
2501
2502 (defun helm-ff-handle-backslash (fname)
2503   ;; Allow creation of filenames containing a backslash.
2504   (cl-loop with bad = '((92 . ""))
2505         for i across fname
2506         if (assq i bad) concat (cdr it)
2507         else concat (string i)))
2508
2509 (defun helm-ff-fuzzy-matching-p ()
2510   (and helm-ff-fuzzy-matching
2511        (not (memq helm-mm-matching-method '(multi1 multi3p)))))
2512
2513 (defun helm-ff--transform-pattern-for-completion (pattern)
2514   "Maybe return PATTERN with it's basename modified as a regexp.
2515 This happen only when `helm-ff-fuzzy-matching' is enabled.
2516 This provide a similar behavior as `ido-enable-flex-matching'.
2517 See also `helm--mapconcat-pattern'.
2518 If PATTERN is an url returns it unmodified.
2519 When PATTERN contain a space fallback to multi-match.
2520 If basename contain one or more space fallback to multi-match.
2521 If PATTERN is a valid directory name,return PATTERN unchanged."
2522   ;; handle bad filenames containing a backslash (no more needed in
2523   ;; emacs-26, also prevent regexp matching with e.g. "\|").
2524   ;; (setq pattern (helm-ff-handle-backslash pattern))
2525   (let ((bn      (helm-basename pattern))
2526         (bd      (or (helm-basedir pattern) ""))
2527         ;; Trigger tramp connection with file-directory-p.
2528         (dir-p   (file-directory-p pattern))
2529         (tramp-p (cl-loop for (m . f) in tramp-methods
2530                        thereis (string-match m pattern))))
2531     ;; Always regexp-quote base directory name to handle
2532     ;; crap dirnames such e.g bookmark+
2533     (cond
2534       ((or (and dir-p tramp-p (string-match ":\\'" pattern))
2535            (string= pattern "")
2536            (and dir-p (<= (length bn) 2))
2537            ;; Fix Issue #541 when BD have a subdir similar
2538            ;; to BN, don't switch to match plugin
2539            ;; which will match both.
2540            (and dir-p (string-match (regexp-quote bn) bd)))
2541        ;; Use full PATTERN on e.g "/ssh:host:".
2542        (regexp-quote pattern))
2543       ;; Prefixing BN with a space call multi-match completion.
2544       ;; This allow showing all files/dirs matching BN (Issue #518).
2545       ;; FIXME: some multi-match methods may not work here.
2546       (dir-p (concat (regexp-quote bd) " " (regexp-quote bn)))
2547       ((or (not (helm-ff-fuzzy-matching-p))
2548            (string-match "\\s-" bn))    ; Fall back to multi-match.
2549        (concat (regexp-quote bd) bn))
2550       ((or (string-match "[*][.]?.*" bn) ; Allow entering wilcard.
2551            (string-match "/$" pattern)     ; Allow mkdir.
2552            (string-match helm-ff-url-regexp pattern)
2553            (and (string= helm-ff-default-directory "/") tramp-p))
2554        ;; Don't treat wildcards ("*") as regexp char.
2555        ;; (e.g ./foo/*.el => ./foo/[*].el)
2556        (concat (regexp-quote bd)
2557                (replace-regexp-in-string "[*]" "[*]" bn)))
2558       (t (concat (regexp-quote bd)
2559                  (if (>= (length bn) 2) ; wait 2nd char before concating.
2560                      (helm--mapconcat-pattern bn)
2561                      (concat ".*" (regexp-quote bn))))))))
2562
2563 (defun helm-dir-is-dot (dir)
2564   (string-match "\\(?:/\\|\\`\\)\\.\\{1,2\\}\\'" dir))
2565
2566 (defun helm-ff-save-history ()
2567   "Store the last value of `helm-ff-default-directory' in `helm-ff-history'.
2568 Note that only existing directories are saved here."
2569   (when (and helm-ff-default-directory
2570              (helm-file-completion-source-p)
2571              (file-directory-p helm-ff-default-directory))
2572     (set-text-properties 0 (length helm-ff-default-directory)
2573                          nil helm-ff-default-directory)
2574     (push helm-ff-default-directory helm-ff-history)))
2575 (add-hook 'helm-cleanup-hook 'helm-ff-save-history)
2576
2577 (defun helm-files-save-file-name-history (&optional force)
2578   "Save marked files to `file-name-history'."
2579   (let* ((src (helm-get-current-source))
2580          (src-name (assoc-default 'name src)))
2581     (when (or force (helm-file-completion-source-p src)
2582               (member src-name helm-files-save-history-extra-sources))
2583       (let ((mkd (helm-marked-candidates :with-wildcard t))
2584             (history-delete-duplicates t))
2585         (cl-loop for sel in mkd
2586               when (and sel
2587                         (stringp sel)
2588                         (file-exists-p sel)
2589                         (not (file-directory-p sel)))
2590               do
2591               ;; we use `abbreviate-file-name' here because
2592               ;; other parts of Emacs seems to,
2593               ;; and we don't want to introduce duplicates.
2594               (add-to-history 'file-name-history
2595                               (abbreviate-file-name sel)))))))
2596 (add-hook 'helm-exit-minibuffer-hook 'helm-files-save-file-name-history)
2597
2598 (defun helm-ff-valid-symlink-p (file)
2599   (helm-aif (condition-case-unless-debug nil
2600                 ;; `file-truename' send error
2601                 ;; on cyclic symlinks (Issue #692).
2602                 (file-truename file)
2603               (error nil))
2604       (file-exists-p it)))
2605
2606 (defun helm-get-default-mode-for-file (filename)
2607   "Return the default mode to open FILENAME."
2608   (let ((mode (cl-loop for (r . m) in auto-mode-alist
2609                     thereis (and (string-match r filename) m))))
2610     (or (and (symbolp mode) mode) "Fundamental")))
2611
2612 (defun helm-ff-properties (candidate)
2613   "Show file properties of CANDIDATE in a tooltip or message."
2614   (require 'helm-external) ; For `helm-get-default-program-for-file'.
2615   (helm-aif (helm-file-attributes candidate)
2616       (let* ((all                it)
2617              (dired-line         (helm-file-attributes
2618                                   candidate :dired t :human-size t))
2619              (type               (cl-getf all :type))
2620              (mode-type          (cl-getf all :mode-type))
2621              (owner              (cl-getf all :uid))
2622              (owner-right        (cl-getf all :user t))
2623              (group              (cl-getf all :gid))
2624              (group-right        (cl-getf all :group))
2625              (other-right        (cl-getf all :other))
2626              (size               (helm-file-human-size (cl-getf all :size)))
2627              (modif              (cl-getf all :modif-time))
2628              (access             (cl-getf all :access-time))
2629              (ext                (helm-get-default-program-for-file candidate))
2630              (tooltip-hide-delay (or helm-tooltip-hide-delay tooltip-hide-delay)))
2631         (if (and (display-graphic-p) tooltip-mode)
2632             (tooltip-show
2633              (concat
2634               (helm-basename candidate) "\n"
2635               dired-line "\n"
2636               (format "Mode: %s\n" (helm-get-default-mode-for-file candidate))
2637               (format "Ext prog: %s\n" (or (and ext (replace-regexp-in-string
2638                                                      " %s" "" ext))
2639                                            "Not defined"))
2640               (format "Type: %s: %s\n" type mode-type)
2641               (when (string= type "symlink")
2642                 (format "True name: '%s'\n"
2643                         (cond ((string-match "^\.#" (helm-basename candidate))
2644                                "Autosave symlink")
2645                               ((helm-ff-valid-symlink-p candidate)
2646                                (file-truename candidate))
2647                               (t "Invalid Symlink"))))
2648               (format "Owner: %s: %s\n" owner owner-right)
2649               (format "Group: %s: %s\n" group group-right)
2650               (format "Others: %s\n" other-right)
2651               (format "Size: %s\n" size)
2652               (format "Modified: %s\n" modif)
2653               (format "Accessed: %s\n" access)))
2654           (message dired-line) (sit-for 5)))
2655     (message "Permission denied, file not readable")))
2656
2657 (defun helm-ff-properties-persistent ()
2658   "Show properties without quitting helm."
2659   (interactive)
2660   (with-helm-alive-p
2661     (helm-attrset 'properties-action '(helm-ff-properties . never-split))
2662     (helm-execute-persistent-action 'properties-action)))
2663 (put 'helm-ff-properties-persistent 'helm-only t)
2664
2665 (defun helm-ff-persistent-delete ()
2666   "Delete current candidate without quitting."
2667   (interactive)
2668   (with-helm-alive-p
2669     (helm-attrset 'quick-delete '(helm-ff-quick-delete . never-split))
2670     (helm-execute-persistent-action 'quick-delete)))
2671 (put 'helm-ff-persistent-delete 'helm-only t)
2672
2673 (defun helm-ff-dot-file-p (file)
2674   "Check if FILE is `.' or `..'."
2675   (member (helm-basename file) '("." "..")))
2676
2677 (defun helm-ff-kill-buffer-fname (candidate)
2678   (let* ((buf      (get-file-buffer candidate))
2679          (buf-name (buffer-name buf)))
2680     (cond ((and buf (eq buf (get-buffer helm-current-buffer)))
2681            (user-error
2682             "Can't kill `helm-current-buffer' without quitting session"))
2683           (buf (kill-buffer buf) (message "Buffer `%s' killed" buf-name))
2684           (t (message "No buffer to kill")))))
2685
2686 (defun helm-ff-kill-or-find-buffer-fname (candidate)
2687   "Find file CANDIDATE or kill it's buffer if it is visible.
2688 Never kill `helm-current-buffer'.
2689 Never kill buffer modified.
2690 This is called normally on third hit of \
2691 \\<helm-map>\\[helm-execute-persistent-action]
2692 in `helm-find-files-persistent-action-if'."
2693   (let* ((buf      (get-file-buffer candidate))
2694          (buf-name (buffer-name buf))
2695          (win (get-buffer-window buf))
2696          (helm--reading-passwd-or-string t))
2697     (cond ((and buf win (eq buf (get-buffer helm-current-buffer)))
2698            (user-error
2699             "Can't kill `helm-current-buffer' without quitting session"))
2700           ((and buf win (buffer-modified-p buf))
2701            (message "Can't kill modified buffer, please save it before"))
2702           ((and buf win)
2703            (kill-buffer buf)
2704            (if (and helm-persistent-action-display-window
2705                     (window-dedicated-p (next-window win 1)))
2706                (delete-window helm-persistent-action-display-window)
2707              (set-window-buffer win helm-current-buffer))
2708            (message "Buffer `%s' killed" buf-name))
2709           (t (find-file candidate)))))
2710
2711 (defun helm-ff-run-kill-buffer-persistent ()
2712   "Execute `helm-ff-kill-buffer-fname' without quitting."
2713   (interactive)
2714   (with-helm-alive-p
2715     (helm-attrset 'kill-buffer-fname 'helm-ff-kill-buffer-fname)
2716     (helm-execute-persistent-action 'kill-buffer-fname)))
2717 (put 'helm-ff-run-kill-buffer-persistent 'helm-only t)
2718
2719 ;; Preview with external tool
2720 (defun helm-ff-persistent-open-file-externally (file)
2721   (require 'helm-external)
2722   (if (helm-get-default-program-for-file file)
2723       (helm-open-file-externally file)
2724     (message "Please configure an external program for `*%s' file in `helm-external-programs-associations'"
2725              (file-name-extension file t))))
2726
2727 (defun helm-ff-run-preview-file-externally ()
2728   (interactive)
2729   (with-helm-alive-p
2730     (helm-attrset 'open-file-externally '(helm-ff-persistent-open-file-externally . never-split))
2731     (helm-execute-persistent-action 'open-file-externally)))
2732 (put 'helm-ff-run-preview-file-externally 'helm-only t)
2733
2734 (defun helm-ff-prefix-filename (fname &optional file-or-symlinkp new-file)
2735   "Return filename FNAME maybe prefixed with [?] or [@].
2736 If FILE-OR-SYMLINKP is non--nil this mean we assume FNAME is an
2737 existing filename or valid symlink and there is no need to test it.
2738 NEW-FILE when non--nil mean FNAME is a non existing file and
2739 return FNAME prefixed with [?]."
2740   (let* ((prefix-new (propertize
2741                       " " 'display
2742                       (propertize "[?]" 'face 'helm-ff-prefix)))
2743          (prefix-url (propertize
2744                       " " 'display
2745                       (propertize "[@]" 'face 'helm-ff-prefix))))
2746     (cond (file-or-symlinkp fname)
2747           ((or (string-match helm-ff-url-regexp fname)
2748                (and helm--url-regexp (string-match helm--url-regexp fname)))
2749            (concat prefix-url " " fname))
2750           (new-file (concat prefix-new " " fname)))))
2751
2752 (defun helm-ff-score-candidate-for-pattern (str pattern)
2753   (if (member str '("." ".."))
2754       200
2755       (helm-score-candidate-for-pattern str pattern)))
2756
2757 (defun helm-ff-sort-candidates-1 (candidates input)
2758   "Sort function for `helm-source-find-files'.
2759 Return candidates prefixed with basename of INPUT first."
2760   (if (or (and (file-directory-p input)
2761                (string-match "/\\'" input))
2762           (string-match "\\`\\$" input)
2763           (null candidates))
2764       candidates
2765       (let* ((c1        (car candidates))
2766              (cand1real (if (consp c1) (cdr c1) c1))
2767              (cand1     (unless (file-exists-p cand1real) c1))
2768              (rest-cand (if cand1 (cdr candidates) candidates))
2769              (memo-src  (make-hash-table :test 'equal))
2770              (all (sort rest-cand
2771                         (lambda (s1 s2)
2772                             (let* ((score (lambda (str)
2773                                             (helm-ff-score-candidate-for-pattern
2774                                              str (helm-basename input))))
2775                                    (bn1 (helm-basename (if (consp s1) (cdr s1) s1)))
2776                                    (bn2 (helm-basename (if (consp s2) (cdr s2) s2)))
2777                                    (sc1 (or (gethash bn1 memo-src)
2778                                             (puthash bn1 (funcall score bn1) memo-src)))
2779                                    (sc2 (or (gethash bn2 memo-src)
2780                                             (puthash bn2 (funcall score bn2) memo-src))))
2781                               (cond ((= sc1 sc2)
2782                                      (< (string-width bn1)
2783                                         (string-width bn2)))
2784                                     ((> sc1 sc2))))))))
2785         (if cand1 (cons cand1 all) all))))
2786
2787 (defun helm-ff-sort-candidates (candidates _source)
2788   "Sort function for `helm-source-find-files'.
2789 Return candidates prefixed with basename of `helm-input' first."
2790   (helm-ff-sort-candidates-1 candidates helm-input))
2791
2792 (defun helm-ff-boring-file-p (file)
2793   ;; Prevent user doing silly thing like
2794   ;; adding the dotted files to boring regexps (#924).
2795   (and (not (string-match "\\.$" file))
2796        (string-match  helm-ff--boring-regexp file)))
2797
2798 (defun helm-ff-filter-candidate-one-by-one (file)
2799   "`filter-one-by-one' Transformer function for `helm-source-find-files'."
2800   ;; Handle boring files
2801   (let ((basename (helm-basename file))
2802         dot)
2803     (unless (and helm-ff-skip-boring-files
2804                  (helm-ff-boring-file-p basename))
2805
2806       ;; Handle tramp files with minimal highlighting.
2807       (if (and (or (string-match-p helm-tramp-file-name-regexp helm-pattern)
2808                    (helm-file-on-mounted-network-p helm-pattern)))
2809           (let* (hostp
2810                  (disp (if (and helm-ff-transformer-show-only-basename
2811                                 (not (setq dot (helm-dir-is-dot file))))
2812                            (or (setq hostp
2813                                      (helm-ff--get-host-from-tramp-invalid-fname
2814                                       file))
2815                                basename)
2816                          file)))
2817             ;; Filename with cntrl chars e.g. foo^J
2818             ;; This will not work as long as most tramp file handlers doesn't
2819             ;; handle such case, e.g. file-name-all-completions,
2820             ;; directory-files, file-name-nondirectory etc...
2821             ;; Keep it though in case they fix this upstream...
2822             (setq disp (replace-regexp-in-string "[[:cntrl:]]" "?" disp))
2823             (cond (;; Dot directories . and ..
2824                    dot (propertize file 'face 'helm-ff-dotted-directory))
2825                   ;; Directories.
2826                   ((get-text-property 1 'helm-ff-dir file)
2827                    (cons (propertize disp 'face 'helm-ff-directory) file))
2828                   ;; Executable files.
2829                   ((get-text-property 1 'helm-ff-exe file)
2830                    (cons (propertize disp 'face 'helm-ff-executable) file))
2831                   ;; Symlinks.
2832                   ((get-text-property 1 'helm-ff-sym file)
2833                    (cons (propertize disp 'face 'helm-ff-symlink) file))
2834                   ;; Regular files.
2835                   ((get-text-property 1 'helm-ff-file file)
2836                    (cons (propertize disp 'face 'helm-ff-file) file))
2837                   ;; non existing files.
2838                   (t (cons (helm-ff-prefix-filename
2839                             (propertize disp 'face 'helm-ff-file)
2840                             hostp (unless hostp 'new-file))
2841                            file))))
2842
2843         ;; Highlight local files showing everything, symlinks, exe,
2844         ;; dirs etc...
2845         (let* ((disp (if (and helm-ff-transformer-show-only-basename
2846                               (not (setq dot (helm-dir-is-dot file)))
2847                               (not (and helm--url-regexp
2848                                         (string-match helm--url-regexp file)))
2849                               (not (string-match helm-ff-url-regexp file)))
2850                          (or (helm-ff--get-host-from-tramp-invalid-fname file)
2851                              basename)
2852                        file))
2853                (attr (file-attributes file))
2854                (type (car attr))
2855                x-bit)
2856           ;; Filename cntrl chars e.g. foo^J
2857           (setq disp (replace-regexp-in-string "[[:cntrl:]]" "?" disp))
2858           (cond ((string-match "file-error" file) file)
2859                 (;; A dead symlink.
2860                  (and (stringp type)
2861                       (not (helm-ff-valid-symlink-p file))
2862                       (not (string-match "^\\.#" basename)))
2863                  (cons (propertize disp 'face 'helm-ff-invalid-symlink)
2864                        file))
2865                 ;; A dotted directory symlinked.
2866                 ((and dot (stringp type))
2867                  (cons (propertize disp 'face 'helm-ff-dotted-symlink-directory)
2868                        file))
2869                 ;; A dotted directory.
2870                 ((helm-ff-dot-file-p file)
2871                  (cons (propertize disp 'face 'helm-ff-dotted-directory)
2872                        file))
2873                 ;; A symlink.
2874                 ((stringp type)
2875                  (cons (propertize disp 'display
2876                                    (concat (propertize disp 'face 'helm-ff-symlink)
2877                                            " -> "
2878                                            (propertize (abbreviate-file-name type)
2879                                                        'face 'helm-ff-truename)))
2880                        file))
2881                 ;; A directory.
2882                 ((eq t type)
2883                  (cons (propertize disp 'face 'helm-ff-directory)
2884                        file))
2885                 ;; A character device file.
2886                 ((and attr (string-match
2887                             "\\`[cp]" (setq x-bit (substring (nth 8 attr) 0 4))))
2888                  (cons (propertize disp 'face 'helm-ff-pipe)
2889                        file))
2890                 ;; A socket file.
2891                 ((and attr (string-match "\\`[s]" x-bit))
2892                  (cons (propertize disp 'face 'helm-ff-socket)
2893                        file))
2894                 ;; An executable file.
2895                 ((and attr
2896                       (string-match
2897                        "x\\'" x-bit))
2898                  (cons (propertize disp 'face 'helm-ff-executable)
2899                        file))
2900                 ;; An executable file with suid
2901                 ((and attr (string-match "s\\'" x-bit))
2902                  (cons (propertize disp 'face 'helm-ff-suid)
2903                        file))
2904                 ;; A file.
2905                 ((and attr (null type))
2906                  (cons (propertize disp 'face 'helm-ff-file)
2907                        file))
2908                 ;; A non--existing file.
2909                 (t (cons (helm-ff-prefix-filename
2910                           (propertize disp 'face 'helm-ff-file) nil 'new-file)
2911                          file))))))))
2912
2913 (defun helm-find-files-action-transformer (actions candidate)
2914   "Action transformer for `helm-source-find-files'."
2915   (let ((str-at-point (with-helm-current-buffer
2916                         (buffer-substring-no-properties
2917                          (point-at-bol) (point-at-eol)))))
2918     (when (file-regular-p candidate)
2919       (setq actions (helm-append-at-nth
2920                      actions '(("Checksum File" . helm-ff-checksum)) 4)))
2921     (cond ((and (string-match "Trash/files/?\\'" (helm-basedir candidate))
2922                 (not (member (helm-basename candidate) '("." "..")))
2923                 (file-exists-p candidate)
2924                 (executable-find "trash"))
2925            (helm-append-at-nth
2926             actions
2927             '(("Restore file(s) from trash" . helm-restore-file-from-trash)
2928               ("Delete file(s) from trash" . helm-ff-trash-rm))
2929             1))
2930           ((and helm--url-regexp
2931                 (not (string-match-p helm--url-regexp str-at-point))
2932                 (not (with-helm-current-buffer (eq major-mode 'dired-mode)))
2933                 (string-match-p ":\\([0-9]+:?\\)" str-at-point))
2934            (append '(("Find file to line number" . helm-ff-goto-linum))
2935                    actions))
2936           ((string-match (image-file-name-regexp) candidate)
2937            (helm-append-at-nth
2938             actions
2939             '(("Rotate image right `M-r'" . helm-ff-rotate-image-right)
2940               ("Rotate image left `M-l'" . helm-ff-rotate-image-left))
2941             3))
2942           ((string-match "\\.el$" (helm-aif (helm-marked-candidates)
2943                                      (car it) candidate))
2944            (helm-append-at-nth
2945             actions
2946             '(("Byte compile lisp file(s) `M-B, C-u to load'"
2947                . helm-find-files-byte-compile)
2948               ("Load File(s) `M-L'" . helm-find-files-load-files))
2949             2))
2950           ((and (string-match "\\.html?$" candidate)
2951                 (file-exists-p candidate))
2952            (helm-append-at-nth
2953             actions '(("Browse url file" . browse-url-of-file)) 2))
2954           ((or (string= (file-name-extension candidate) "pdf")
2955                (string= (file-name-extension candidate) "PDF"))
2956            (helm-append-at-nth
2957             actions '(("Pdfgrep File(s)" . helm-ff-pdfgrep)) 4))
2958           (t actions))))
2959
2960 (defun helm-ff-trash-action (fn names &rest args)
2961   "Execute a trash action FN on marked files.
2962
2963 Arg NAMES is a list of strings to pass to messages
2964 e.g. '(\"delete\" \"deleting\"), ARGS are other args to be passed to FN."
2965   (let ((mkd (helm-marked-candidates))
2966         errors)
2967     (with-helm-display-marked-candidates
2968         helm-marked-buffer-name
2969         (helm-ff--count-and-collect-dups (mapcar 'helm-basename mkd))
2970         (when (y-or-n-p (format "%s %s files from trash? "
2971                                 (capitalize (car names))
2972                                 (length mkd)))
2973           (message "%s files from trash..." (capitalize (cadr names)))
2974           (cl-loop for f in mkd do
2975                    (condition-case err
2976                        (apply fn f args)
2977                      (error (push (format "%s" (cadr err)) errors)
2978                             nil)))))
2979     (if errors
2980         (display-warning 'helm
2981                          (with-temp-buffer
2982                            (insert (format-time-string "%Y-%m-%d %H:%M:%S\n"
2983                                                        (current-time)))
2984                            (insert (format
2985                                     "Failed to %s %s/%s files from trash\n"
2986                                     (car names) (length errors) (length mkd)))
2987                            (insert (mapconcat 'identity errors "\n") "\n ")
2988                            (buffer-string))
2989                          :error
2990                          "*helm restore warnings*")
2991       (message "%s %s files from trash done"
2992                (capitalize (cadr names)) (length mkd)))))
2993
2994 (defun helm-ff-trash-rm (_candidate)
2995   "Delete marked-files from a Trash directory.
2996
2997 The Trash directory should be a directory compliant with
2998 <http://freedesktop.org/wiki/Specifications/trash-spec> and each file
2999 should have its '*.trashinfo' correspondent file in Trash/info
3000 directory."
3001   (helm-ff-trash-action 'helm-ff-trash-rm-1 '("delete" "deleting")))
3002
3003 (defun helm-restore-file-from-trash (_candidate)
3004   "Restore marked-files from a Trash directory.
3005
3006 The Trash directory should be a directory compliant with
3007 <http://freedesktop.org/wiki/Specifications/trash-spec> and each file
3008 should have its '*.trashinfo' correspondent file in Trash/info
3009 directory."
3010   (let* ((default-directory (file-name-as-directory
3011                              helm-ff-default-directory))
3012          (trashed-files (with-temp-buffer
3013                           (process-file "trash-list" nil t nil)
3014                           (split-string (buffer-string) "\n"))))
3015     (helm-ff-trash-action 'helm-restore-file-from-trash-1
3016                           '("restore" "restoring")
3017                           trashed-files)))
3018
3019 (defun helm-ff-trash-rm-1 (file)
3020   (let ((info-file (concat (helm-reduce-file-name file 2)
3021                            "info/" (helm-basename file)
3022                            ".trashinfo")))
3023     (cl-assert (file-exists-p file)
3024                nil (format "No such file or directory `%s'"
3025                            file))
3026     (cl-assert (file-exists-p info-file)
3027                nil (format "No such file or directory `%s'"
3028                            info-file))
3029     (delete-file file)
3030     (delete-file info-file)))
3031
3032 (defun helm-restore-file-from-trash-1 (file trashed-files)
3033   "Restore FILE from a trash directory.
3034 Arg TRASHED-FILES is the list of files in the trash directory obtained
3035 with 'trash-list' command."
3036   (let ((info-file (concat (helm-reduce-file-name file 2)
3037                            "info/"
3038                            (helm-basename file)
3039                            ".trashinfo"))
3040         (dest-file (helm-ff--get-dest-file-from-trash
3041                     trashed-files file)))
3042     (cl-assert (not (file-exists-p dest-file)) nil
3043                (format "File `%s' already exists" dest-file))
3044     (cl-assert dest-file nil "No such file in trash")
3045     (rename-file file dest-file)
3046     (delete-file info-file)))
3047
3048 (defun helm-ff--get-dest-file-from-trash (trashed-files file)
3049   (cl-loop for f in trashed-files
3050            when (string-match
3051                  (concat (regexp-quote (helm-basename file))
3052                          "\\'")
3053                  f)
3054            return
3055            (replace-regexp-in-string
3056             "\\`\\([0-9]\\{2,4\\}[-:][0-9]\\{2\\}[:-][0-9]\\{2\\} \\)\\{2\\}"
3057             "" f)))
3058
3059 (defun helm-ff-goto-linum (candidate)
3060   "Find file CANDIDATE and maybe jump to line number found in fname at point.
3061 line number should be added at end of fname preceded with \":\".
3062 e.g \"foo:12\"."
3063   (let ((linum (with-helm-current-buffer
3064                  (let ((str (buffer-substring-no-properties
3065                              (point-at-bol) (point-at-eol))))
3066                    (when (string-match ":\\([0-9]+:?\\)" str)
3067                      (match-string 1 str))))))
3068     (find-file candidate)
3069     (and linum (not (string= linum ""))
3070          (helm-goto-line (string-to-number linum) t))))
3071
3072 (defun helm-ff-mail-attach-files (_candidate)
3073   "Run `mml-attach-file' on `helm-marked-candidates'."
3074   (require 'mml)
3075   (let ((flist (helm-marked-candidates :with-wildcard t))
3076         (dest-buf (and (derived-mode-p 'message-mode 'mail-mode)
3077                        (current-buffer)))
3078         bufs)
3079     (unless dest-buf
3080       (setq bufs (cl-loop for b in (buffer-list)
3081                           when (with-current-buffer b
3082                                  (derived-mode-p 'message-mode 'mail-mode))
3083                           collect (buffer-name b)))
3084       (if (and bufs (y-or-n-p "Attach files to existing mail composition buffer? "))
3085           (setq dest-buf
3086                 (if (cdr bufs)
3087                     (helm-comp-read "Attach to buffer: " bufs :nomark t)
3088                   (car bufs)))
3089         (compose-mail)
3090         (setq dest-buf (current-buffer))))
3091     (switch-to-buffer dest-buf)
3092     (save-restriction
3093       (widen)
3094       (save-excursion
3095         (goto-char (point-max))
3096         (cl-loop for f in flist
3097                  do (mml-attach-file f (or (mm-default-file-encoding f)
3098                                            "application/octet-stream")))))))
3099
3100 (defvar image-dired-display-image-buffer)
3101 (defun helm-ff-rotate-current-image-1 (file &optional num-arg)
3102   "Rotate current image at NUM-ARG degrees.
3103 This is a destructive operation on FILE made by external tool mogrify."
3104   (setq file (file-truename file)) ; For symlinked images.
3105   ;; When FILE is not an image-file, do nothing.
3106   (when (string-match (image-file-name-regexp) file)
3107     (if (executable-find "mogrify")
3108         (progn
3109           (shell-command (format "mogrify -rotate %s %s"
3110                                  (or num-arg 90)
3111                                  (shell-quote-argument file)))
3112           (when (buffer-live-p image-dired-display-image-buffer)
3113             (kill-buffer image-dired-display-image-buffer))
3114           (image-dired-display-image file)
3115           (message nil)
3116           (display-buffer (get-buffer image-dired-display-image-buffer)))
3117       (error "mogrify not found"))))
3118
3119 (defun helm-ff-rotate-image-left (candidate)
3120   "Rotate image file CANDIDATE left.
3121 This affect directly file CANDIDATE."
3122   (helm-ff-rotate-current-image-1 candidate -90))
3123
3124 (defun helm-ff-rotate-image-right (candidate)
3125   "Rotate image file CANDIDATE right.
3126 This affect directly file CANDIDATE."
3127   (helm-ff-rotate-current-image-1 candidate))
3128
3129 (defun helm-ff-rotate-left-persistent ()
3130   "Rotate image left without quitting helm."
3131   (interactive)
3132   (with-helm-alive-p
3133     (helm-attrset 'image-action1 'helm-ff-rotate-image-left)
3134     (helm-execute-persistent-action 'image-action1)))
3135 (put 'helm-ff-rotate-left-persistent 'helm-only t)
3136
3137 (defun helm-ff-rotate-right-persistent ()
3138   "Rotate image right without quitting helm."
3139   (interactive)
3140   (with-helm-alive-p
3141     (helm-attrset 'image-action2 'helm-ff-rotate-image-right)
3142     (helm-execute-persistent-action 'image-action2)))
3143 (put 'helm-ff-rotate-right-persistent 'helm-only t)
3144
3145 (defun helm-ff-exif-data (candidate)
3146   "Extract exif data from file CANDIDATE using `helm-ff-exif-data-program'."
3147   (if (and helm-ff-exif-data-program
3148            (executable-find helm-ff-exif-data-program))
3149       (shell-command-to-string (format "%s %s %s"
3150                                        helm-ff-exif-data-program
3151                                        helm-ff-exif-data-program-args
3152                                        candidate))
3153     (format "No program %s found to extract exif"
3154             helm-ff-exif-data-program)))
3155
3156 (cl-defun helm-find-files-persistent-action-if (candidate)
3157   "Open subtree CANDIDATE without quitting helm.
3158 If CANDIDATE is not a directory expand CANDIDATE filename.
3159 If CANDIDATE is alone, open file CANDIDATE filename.
3160 That's mean:
3161 First hit on C-j expand CANDIDATE second hit open file.
3162 If a prefix arg is given or `helm-follow-mode' is on open file."
3163   (let* ((follow        (or (helm-follow-mode-p)
3164                             helm--temp-follow-flag))
3165          (image-cand    (string-match-p (image-file-name-regexp) candidate))
3166          (new-pattern   (helm-get-selection))
3167          (num-lines-buf (with-current-buffer helm-buffer
3168                           (count-lines (point-min) (point-max))))
3169          (insert-in-minibuffer (lambda (fname)
3170                                    (with-selected-window (or (active-minibuffer-window)
3171                                                              (minibuffer-window))
3172                                      (unless follow
3173                                        (delete-minibuffer-contents)
3174                                        (set-text-properties 0 (length fname)
3175                                                             nil fname)
3176                                        (insert fname))))))
3177     (helm-attrset 'candidate-number-limit helm-ff-candidate-number-limit)
3178     (unless image-cand
3179       (when follow
3180         (helm-follow-mode -1)
3181         (cl-return-from helm-find-files-persistent-action-if
3182           (message "Helm-follow-mode allowed only on images, disabling"))))
3183     (cond ((and (helm-ff--invalid-tramp-name-p)
3184                 (string-match helm-tramp-file-name-regexp candidate))
3185            (cons (lambda (_candidate)
3186                    ;; First hit insert hostname and
3187                    ;; second hit insert ":" and expand.
3188                    (if (string= candidate helm-pattern)
3189                        (funcall insert-in-minibuffer (concat candidate ":"))
3190                      (funcall insert-in-minibuffer candidate)))
3191                  'never-split))
3192           (;; A symlink directory, expand it but not to its truename
3193            ;; unless a prefix arg is given.
3194            (and (file-directory-p candidate) (file-symlink-p candidate))
3195            (cons (lambda (_candidate)
3196                    (funcall insert-in-minibuffer
3197                             (file-name-as-directory
3198                              (if current-prefix-arg
3199                                  (file-truename (expand-file-name candidate))
3200                                (expand-file-name candidate)))))
3201                  'never-split))
3202           ;; A directory, open it.
3203           ((file-directory-p candidate)
3204            (cons (lambda (_candidate)
3205                    (when (string= (helm-basename candidate) "..")
3206                      (setq helm-ff-last-expanded helm-ff-default-directory))
3207                    (funcall insert-in-minibuffer (file-name-as-directory
3208                                                   (expand-file-name candidate))))
3209                  'never-split))
3210           ;; A symlink file, expand to it's true name. (first hit)
3211           ((and (file-symlink-p candidate) (not current-prefix-arg) (not follow))
3212            (cons (lambda (_candidate)
3213                    (funcall insert-in-minibuffer (file-truename candidate)))
3214                  'never-split))
3215           ;; A regular file, expand it, (first hit)
3216           ((and (>= num-lines-buf 3) (not current-prefix-arg) (not follow))
3217            (cons (lambda (_candidate)
3218                    (setq helm-pattern "")       ; Force update.
3219                    (funcall insert-in-minibuffer new-pattern))
3220                  'never-split))
3221           ;; An image file and it is the second hit on C-j,
3222           ;; show the file in `image-dired'.
3223           (image-cand
3224            (lambda (_candidate)
3225              (require 'image-dired)
3226              (let* ((win (get-buffer-window
3227                           image-dired-display-image-buffer 'visible))
3228                     (fname (and win
3229                                 (with-selected-window win
3230                                   (get-text-property (point-min)
3231                                                      'original-file-name))))
3232                     (remove-buf-only (and win
3233                                           fname
3234                                           (with-helm-buffer
3235                                             (file-equal-p candidate fname)))))
3236                (when remove-buf-only
3237                  (with-helm-window
3238                    (if (and helm-persistent-action-display-window
3239                             (window-dedicated-p (next-window win 1)))
3240                        (delete-window helm-persistent-action-display-window)
3241                      (set-window-buffer win helm-current-buffer))))
3242                (when (buffer-live-p (get-buffer image-dired-display-image-buffer))
3243                  (kill-buffer image-dired-display-image-buffer))
3244                (unless remove-buf-only
3245                  ;; Fix emacs bug never fixed upstream.
3246                  (unless (file-directory-p image-dired-dir)
3247                    (make-directory image-dired-dir))
3248                  (switch-to-buffer image-dired-display-image-buffer)
3249                  (message "Resizing image...")
3250                  (cl-letf (((symbol-function 'message) #'ignore))
3251                    (image-dired-display-image candidate))
3252                  (message "Resizing image done")
3253                  (with-current-buffer image-dired-display-image-buffer
3254                    (let ((exif-data (helm-ff-exif-data candidate)))
3255                      (setq default-directory helm-ff-default-directory)
3256                      (image-dired-update-property 'help-echo exif-data)))))))
3257           ;; Allow browsing archive on avfs fs.
3258           ;; Assume volume is already mounted with mountavfs.
3259           ((helm-aand helm-ff-avfs-directory
3260                       (file-name-directory candidate)
3261                       (string-match
3262                        (regexp-quote (expand-file-name helm-ff-avfs-directory))
3263                        it)
3264                       (helm-ff-file-compressed-p candidate))
3265            (cons (lambda (_candidate)
3266                    (funcall insert-in-minibuffer (concat candidate "#/")))
3267                  'never-split))
3268           ;; File doesn't exists and basename starts with ".." or "  ",
3269           ;; Start a recursive search for directories.
3270           ((and (not (file-exists-p candidate))
3271                 (not (file-remote-p candidate))
3272                 (string-match-p "\\`\\([.]\\|\\s-\\)\\{2\\}[^/]+"
3273                                 (helm-basename candidate)))
3274            ;; As soon as the final "/" is added the job is passed
3275            ;; to `helm-ff-auto-expand-to-home-or-root'.
3276            (cons (lambda (_candidate)
3277                    (funcall insert-in-minibuffer (concat candidate "/")))
3278                  'never-split))
3279           ;; File is not existing and have no basedir, typically when
3280           ;; user hit C-k (minibuffer is empty) and then write foo and
3281           ;; hit C-j. This make clear that when no basedir, helm will
3282           ;; create the file in default-directory.
3283           ((and (not (file-exists-p candidate))
3284                 (not (helm-basedir candidate)))
3285            (cons (lambda (_candidate)
3286                    (funcall insert-in-minibuffer
3287                             (expand-file-name candidate default-directory)))
3288                  'never-split))
3289           ;; On second hit we open file.
3290           ;; On Third hit we kill it's buffer maybe.
3291           (t
3292            (lambda (_candidate)
3293              (funcall helm-ff-kill-or-find-buffer-fname-fn candidate))))))
3294
3295
3296 ;;; Recursive dirs completion
3297 ;;
3298 (defun helm-find-files-recursive-dirs (directory &optional input)
3299   (when (string-match "\\(\\s-+\\|[.]\\)\\{2\\}" input)
3300     (setq input (replace-match "" nil t input)))
3301   (message "Recursively searching %s from %s ..."
3302            input (abbreviate-file-name directory))
3303   ;; Ensure to not create a new frame
3304   (let (helm-actions-inherit-frame-settings)
3305     (helm :sources
3306           (helm-make-source
3307               "Recursive directories" 'helm-locate-subdirs-source
3308             :basedir (if (string-match-p
3309                           "\\`es" helm-locate-recursive-dirs-command)
3310                          directory
3311                        (shell-quote-argument directory))
3312             :subdir (shell-quote-argument input)
3313             :candidate-transformer
3314             `((lambda (candidates)
3315                 (cl-loop for c in candidates
3316                          when (and (file-directory-p c)
3317                                    (null (helm-boring-directory-p
3318                                           c helm-boring-file-regexp-list))
3319                                    (string-match-p ,(regexp-quote input)
3320                                                    (helm-basename c)))
3321                          collect (propertize c 'face 'helm-ff-dirs)))
3322               helm-w32-pathname-transformer
3323               (lambda (candidates)
3324                 (helm-ff-sort-candidates-1 candidates ,input)))
3325             :persistent-action 'ignore
3326             :action (lambda (c)
3327                       (helm-set-pattern
3328                        (file-name-as-directory (expand-file-name c)))))
3329           :candidate-number-limit 999999
3330           :allow-nest t
3331           :resume 'noresume
3332           :ff-transformer-show-only-basename nil
3333           :buffer "*helm recursive dirs*")))
3334
3335 (defun helm-ff-recursive-dirs (_candidate)
3336   "Launch a recursive search in `helm-ff-default-directory'."
3337   (with-helm-default-directory helm-ff-default-directory
3338       (helm-find-files-recursive-dirs
3339        (helm-current-directory)
3340        (helm-basename (helm-get-selection)))))
3341
3342 (defun helm-ff-file-compressed-p (candidate)
3343   "Whether CANDIDATE is a compressed file or not."
3344   (member (file-name-extension candidate)
3345           helm-ff-file-compressed-list))
3346
3347 (defun helm-ff--fname-at-point ()
3348   "Try to guess fname at point."
3349   (let ((end (point))
3350         (limit (helm-aif (bounds-of-thing-at-point 'filename)
3351                    (car it)
3352                  (point))))
3353     (save-excursion
3354       (while (re-search-backward "\\(~\\|/\\|[[:lower:][:upper:]]:/\\)"
3355                                  limit t))
3356       (buffer-substring-no-properties (point) end))))
3357
3358 (defun helm-insert-file-name-completion-at-point (_candidate)
3359   "Insert file name completion at point.
3360
3361 When completing i.e. there is already something at point, insert
3362 filename abbreviated, relative or full according to initial input,
3363 whereas when inserting i.e. there is nothing at point, insert filename
3364 full, abbreviated or relative according to prefix arg, respectively no
3365 prefix arg, one prefix arg or two prefix arg."
3366   (with-helm-current-buffer
3367     (if buffer-read-only
3368         (error "Error: Buffer `%s' is read-only" (buffer-name))
3369       (let* ((mkds        (helm-marked-candidates :with-wildcard t))
3370              (candidate   (car mkds))
3371              (end         (point))
3372              (tap         (helm-ff--fname-at-point))
3373              (guess       (and (stringp tap)
3374                                (substring-no-properties tap)))
3375              (beg         (if guess (- (point) (length guess)) (point)))
3376              (full-path-p (and (stringp guess)
3377                                (or (string-match-p
3378                                     (concat "^" (getenv "HOME"))
3379                                     guess)
3380                                    (string-match-p
3381                                     "\\`\\(/\\|[[:lower:][:upper:]]:/\\)"
3382                                     guess))))
3383              (escape-fn (with-helm-current-buffer
3384                           (if (memq major-mode
3385                                     helm-modes-using-escaped-strings)
3386                               #'shell-quote-argument #'identity))))
3387         (insert
3388          (funcall escape-fn (helm-ff--insert-fname
3389                              candidate beg end full-path-p guess))
3390          (if (cdr mkds) " " "")
3391          (mapconcat escape-fn
3392                     (cl-loop for f in (cdr mkds)
3393                              collect (helm-ff--insert-fname f))
3394                     " "))))))
3395
3396 (defun helm-ff--insert-fname (candidate &optional beg end full-path guess)
3397   (set-text-properties 0 (length candidate) nil candidate)
3398   (if (and beg end guess (not (string= guess ""))
3399            (or (string-match
3400                 "^\\(~/\\|/\\|[[:lower:][:upper:]]:/\\)"
3401                 guess)
3402                (file-exists-p candidate)))
3403       (prog1
3404           (cond (full-path
3405                  (expand-file-name candidate))
3406                 ((string= (match-string 1 guess) "~/")
3407                  (abbreviate-file-name candidate))
3408                 (t (file-relative-name candidate)))
3409         (delete-region beg end))
3410     (cond ((equal helm-current-prefix-arg '(4))
3411            (abbreviate-file-name candidate))
3412           ((equal helm-current-prefix-arg '(16))
3413            (file-relative-name candidate))
3414           (t candidate))))
3415
3416 (cl-defun helm-find-files-history (arg &key (comp-read t))
3417   "The `helm-find-files' history.
3418 Show the first `helm-ff-history-max-length' elements of
3419 `helm-ff-history' in an `helm-comp-read'."
3420   (interactive "p")
3421   (let ((history (when helm-ff-history
3422                    (helm-fast-remove-dups helm-ff-history
3423                                           :test 'equal))))
3424     (when history
3425       (setq helm-ff-history
3426             (if (>= (length history) helm-ff-history-max-length)
3427                 (cl-subseq history 0 helm-ff-history-max-length)
3428               history))
3429       (if comp-read
3430           (let ((src (helm-build-sync-source "Helm Find Files History"
3431                        :candidates helm-ff-history
3432                        :fuzzy-match (helm-ff-fuzzy-matching-p)
3433                        :persistent-action 'ignore
3434                        :migemo t
3435                        :action (lambda (candidate)
3436                                  (if arg
3437                                      (helm-set-pattern
3438                                       (expand-file-name candidate))
3439                                    (identity candidate))))))
3440             (helm :sources src
3441                   :resume 'noresume
3442                   :buffer helm-ff-history-buffer-name
3443                   :allow-nest t))
3444         helm-ff-history))))
3445 (put 'helm-find-files-history 'helm-only t)
3446
3447 (defun helm-find-files-1 (fname &optional preselect)
3448   "Find FNAME filename with PRESELECT filename preselected.
3449
3450 Use it for non--interactive calls of `helm-find-files'."
3451   (require 'tramp)
3452   ;; Resolve FNAME now outside of helm.
3453   ;; [FIXME] When `helm-find-files-1' is used directly from lisp
3454   ;; and FNAME is an abbreviated path, for some reasons
3455   ;; `helm-update' is called many times before resolving
3456   ;; the abbreviated path (Issue #1939) so be sure to pass a
3457   ;; full path to helm-find-files-1.
3458   (unless (string-match-p helm-ff-url-regexp fname)
3459     (setq fname (expand-file-name (substitute-in-file-name fname))))
3460   (when (get-buffer helm-action-buffer)
3461     (kill-buffer helm-action-buffer))
3462   (setq helm-find-files--toggle-bookmark nil)
3463   (let* ( ;; Be sure we don't erase the precedent minibuffer if some.
3464          (helm-ff-auto-update-initial-value
3465           (and helm-ff-auto-update-initial-value
3466                (not (minibuffer-window-active-p (minibuffer-window)))))
3467          (tap (thing-at-point 'filename))
3468          (def (and tap (or (file-remote-p tap)
3469                            (expand-file-name tap)))))
3470     (helm-set-local-variable 'helm-follow-mode-persistent nil)
3471     (unless helm-source-find-files
3472       (setq helm-source-find-files (helm-make-source
3473                                     "Find Files" 'helm-source-ffiles)))
3474     (when (helm-attr 'follow helm-source-find-files)
3475       (helm-attrset 'follow -1 helm-source-find-files))
3476     (helm-ff-setup-update-hook)
3477     (add-hook 'helm-resume-after-hook 'helm-ff--update-resume-after-hook)
3478     (unwind-protect
3479          (helm :sources 'helm-source-find-files
3480                :input fname
3481                :case-fold-search helm-file-name-case-fold-search
3482                :preselect preselect
3483                :ff-transformer-show-only-basename
3484                helm-ff-transformer-show-only-basename
3485                :default def
3486                :prompt "Find files or url: "
3487                :buffer "*helm find files*")
3488       (helm-ff--update-resume-after-hook nil t)
3489       (setq helm-ff-default-directory nil))))
3490
3491 (defun helm-ff--update-resume-after-hook (sources &optional nohook)
3492   "Meant to be used in `helm-resume-after-hook'.
3493 When NOHOOK is non nil run inconditionally, otherwise only when source
3494 is helm-source-find-files."
3495   (when (or nohook (string= "Find Files"
3496                             (assoc-default 'name (car sources))))
3497     (helm-attrset 'resume `(lambda ()
3498                              (helm-ff-setup-update-hook)
3499                              (setq helm-ff-default-directory
3500                                    ,helm-ff-default-directory
3501                                    helm-ff-last-expanded
3502                                    ,helm-ff-last-expanded))
3503                   helm-source-find-files)))
3504
3505 (defun helm-ff-clean-initial-input ()
3506   ;; When using hff in an external frame initial input is printed in
3507   ;; the minibuffer of initial-frame, delete it.
3508   (with-selected-frame helm-initial-frame
3509     (helm-clean-up-minibuffer)))
3510
3511 (defun helm-ff-setup-update-hook ()
3512   (dolist (hook '(helm-ff-clean-initial-input ; Add to be called first.
3513                   helm-ff-move-to-first-real-candidate
3514                   helm-ff-update-when-only-one-matched
3515                   helm-ff-auto-expand-to-home-or-root))
3516     (add-hook 'helm-after-update-hook hook)))
3517
3518 (defun helm-find-files-cleanup ()
3519   (mapc (lambda (hook)
3520           (remove-hook 'helm-after-update-hook hook))
3521         '(helm-ff-auto-expand-to-home-or-root
3522           helm-ff-update-when-only-one-matched
3523           helm-ff-move-to-first-real-candidate
3524           helm-ff-clean-initial-input)))
3525
3526 (defun helm-find-files-toggle-to-bookmark ()
3527   "Toggle helm-bookmark for `helm-find-files' and `helm-find-files.'"
3528   (interactive)
3529   (require 'helm-bookmark)
3530   (with-helm-alive-p
3531     (with-helm-buffer
3532       (if (setq helm-find-files--toggle-bookmark
3533                 (not helm-find-files--toggle-bookmark))
3534           (progn
3535             (helm-set-pattern "" t)
3536             (helm-set-sources '(helm-source-bookmark-helm-find-files)))
3537           ;; Switch back to helm-find-files.
3538           (helm-set-pattern "./" t) ; Back to initial directory of hff session.
3539           (helm-set-sources '(helm-source-find-files))
3540           (helm--maybe-update-keymap)))))
3541 (put 'helm-find-files-toggle-to-bookmark 'helm-only t)
3542
3543 (defun helm-find-files-initial-input (&optional input)
3544   "Return INPUT if present, otherwise try to guess it."
3545   (unless (eq major-mode 'image-mode)
3546     (or (and input (or (and (file-remote-p input) input)
3547                        (expand-file-name input)))
3548         (helm-find-files-input
3549          (helm-ffap-guesser)
3550          (thing-at-point 'filename)))))
3551
3552 (defun helm-ffap-guesser ()
3553   "Same as `ffap-guesser' but without gopher and machine support."
3554   (require 'ffap)
3555   ;; Avoid "Stack overflow in regexp matcher" error
3556   ;; in evil `ffap-guesser' by removing crap `ffap-gopher-at-point'
3557   ;; (bug fixed in emacs-26 #25391) .
3558   ;; `ffap-machine-at-point' have been removed too as it was anyway
3559   ;; disabled with `ffap-machine-p-known' bound to 'reject.
3560   ;; `ffap-file-at-point' can be neutralized with
3561   ;; `helm-ff-guess-ffap-filenames' and `ffap-url-at-point' with
3562   ;; `helm-ff-guess-ffap-urls'
3563   ;; Note also that `ffap-url-unwrap-remote' can override these
3564   ;; variables.
3565   (let ((ffap-alist (and helm-ff-guess-ffap-filenames ffap-alist))
3566         (ffap-url-regexp helm--url-regexp))
3567     (if (eq major-mode 'dired-mode)
3568         (let ((beg  (save-excursion (dired-move-to-filename)))
3569               (end  (save-excursion (dired-move-to-end-of-filename t))))
3570           (helm-aif (and beg end (member (buffer-substring beg end)
3571                                          '("." "..")))
3572               (concat (file-name-as-directory
3573                        (expand-file-name dired-directory))
3574                       (car it))
3575             (dired-get-filename 'no-dir t)))
3576       (let* ((beg (and (use-region-p) (region-beginning)))
3577              (end (and (use-region-p) (region-end)))
3578              (str (and beg end (buffer-substring-no-properties beg end)))
3579              (ffap (or (and helm-ff-guess-ffap-urls ffap-url-regexp
3580                             (ffap-fixup-url (ffap-url-at-point)))
3581                        (ffap-file-at-point))))
3582         ;; Workaround emacs bugs:
3583         ;; When the region is active and a file is detected
3584         ;; `ffap-string-at-point' returns the region prefixed with
3585         ;; "/", e.g. at a beginning of a patch (first bug) and make
3586         ;; `file-remote-p' returning an error (second bug), so in such
3587         ;; case returns the region itself instead of the region
3588         ;; corrupted by ffap. 
3589         (if (and str ffap) str ffap)))))
3590
3591 (defun helm-find-files-input (file-at-pt thing-at-pt)
3592   "Try to guess a default input for `helm-find-files'."
3593   (let* ((non-essential t)
3594          (remp    (or (and file-at-pt (file-remote-p file-at-pt))
3595                       (and thing-at-pt (file-remote-p thing-at-pt))))
3596          (def-dir (helm-current-directory))
3597          (urlp    (and file-at-pt helm--url-regexp
3598                        (string-match helm--url-regexp file-at-pt)))
3599          (lib     (when helm-ff-search-library-in-sexp
3600                     (helm-find-library-at-point)))
3601          (hlink   (helm-ff-find-url-at-point))
3602          (file-p  (and file-at-pt
3603                        (not (string= file-at-pt ""))
3604                        (not remp)
3605                        (file-exists-p file-at-pt)
3606                        thing-at-pt
3607                        (not (string= thing-at-pt ""))
3608                        (file-exists-p
3609                         (file-name-directory
3610                          (expand-file-name thing-at-pt def-dir))))))
3611     (cond (lib)      ; e.g we are inside a require sexp.
3612           (hlink)    ; String at point is an hyperlink.
3613           (file-p    ; a regular file
3614            (and file-at-pt (if (not (member (helm-basename file-at-pt)
3615                                             '("." "..")))
3616                                (expand-file-name file-at-pt)
3617                              file-at-pt)))
3618           (urlp (helm-html-decode-entities-string file-at-pt)) ; possibly an url or email.
3619           ((and file-at-pt
3620                 (not remp)
3621                 (file-exists-p file-at-pt))
3622            (expand-file-name file-at-pt)))))
3623
3624 (defun helm-ff-find-url-at-point ()
3625   "Try to find link to an url in text-property at point."
3626   (let* ((he      (get-text-property (point) 'help-echo))
3627          (ov      (overlays-at (point)))
3628          (ov-he   (and ov (overlay-get
3629                            (car (overlays-at (point))) 'help-echo)))
3630          (w3m-l   (get-text-property (point) 'w3m-href-anchor))
3631          (nt-prop (get-text-property (point) 'nt-link)))
3632     ;; Org link.
3633     (when (and (stringp he) (string-match "^LINK: " he))
3634       (setq he (replace-match "" t t he)))
3635     (cl-loop for i in (list he ov-he w3m-l nt-prop)
3636           thereis (and (stringp i) helm--url-regexp (string-match helm--url-regexp i) i))))
3637
3638 (defun helm-find-library-at-point ()
3639   "Try to find library path at point.
3640 Find inside `require' and `declare-function' sexp."
3641   (require 'find-func)
3642   (let* ((beg-sexp (save-excursion (search-backward "(" (point-at-bol) t)))
3643          (end-sexp (save-excursion (search-forward ")" (point-at-eol) t)))
3644          (sexp     (and beg-sexp end-sexp
3645                         (buffer-substring-no-properties
3646                          (1+ beg-sexp) (1- end-sexp)))))
3647     (ignore-errors
3648       (cond ((and sexp (string-match "require \'.+[^)]" sexp))
3649              (find-library-name
3650               (replace-regexp-in-string
3651                "'\\|\)\\|\(" ""
3652                ;; If require use third arg, ignore it,
3653                ;; always use library path found in `load-path'.
3654                (cl-second (split-string (match-string 0 sexp))))))
3655             ((and sexp (string-match-p "^declare-function" sexp))
3656              (find-library-name
3657               (replace-regexp-in-string
3658                "\"\\|ext:" ""
3659                (cl-third (split-string sexp)))))
3660             (t nil)))))
3661
3662
3663 ;;; Handle copy, rename, symlink, relsymlink and hardlink from helm.
3664 ;;
3665 ;;
3666 (defun helm-ff--valid-default-directory ()
3667   (with-helm-current-buffer
3668     (cl-loop for b in (buffer-list)
3669              for cd = (with-current-buffer b default-directory)
3670              when (eq (car (file-attributes cd)) t)
3671              return cd)))
3672
3673 (cl-defun helm-dired-action (candidate
3674                              &key action follow (files (dired-get-marked-files)))
3675   "Execute ACTION on FILES to CANDIDATE.
3676 Where ACTION is a symbol that can be one of:
3677 'copy, 'rename, 'symlink,'relsymlink, 'hardlink or 'backup.
3678 Argument FOLLOW when non--nil specify to follow FILES to destination for the actions
3679 copy and rename."
3680   (require 'dired-async)
3681   (require 'dired-x) ; For dired-keep-marker-relsymlink
3682   (when (get-buffer dired-log-buffer) (kill-buffer dired-log-buffer))
3683   ;; When default-directory in current-buffer is an invalid directory,
3684   ;; (e.g buffer-file directory have been renamed somewhere else)
3685   ;; be sure to use a valid value to give to dired-create-file.
3686   ;; i.e start-process is creating a process buffer based on default-directory.
3687   (let ((default-directory (helm-ff--valid-default-directory))
3688         (fn     (cl-case action
3689                   (copy       'dired-copy-file)
3690                   (rename     'dired-rename-file)
3691                   (symlink    'make-symbolic-link)
3692                   (relsymlink 'dired-make-relative-symlink)
3693                   (hardlink   'dired-hardlink)
3694                   (backup     'backup-file)))
3695         (marker (cl-case action
3696                   ((copy rename backup) dired-keep-marker-copy)
3697                   (symlink              dired-keep-marker-symlink)
3698                   (relsymlink           dired-keep-marker-relsymlink)
3699                   (hardlink             dired-keep-marker-hardlink)))
3700         (dirflag (and (= (length files) 1)
3701                       (file-directory-p (car files))
3702                       (not (file-directory-p candidate))))
3703         (dired-async-state (if (and (boundp 'dired-async-mode)
3704                                     dired-async-mode)
3705                                1 -1)))
3706     (and follow (fboundp 'dired-async-mode) (dired-async-mode -1))
3707     (when (and (cdr files) (not (file-directory-p candidate)))
3708       (error "%s: target `%s' is not a directory" action candidate))
3709     (unwind-protect
3710          (dired-create-files
3711           fn (symbol-name action) files
3712           ;; CANDIDATE is the destination.
3713           (if (file-directory-p candidate)
3714               ;; When CANDIDATE is a directory, build file-name in this directory.
3715               ;; Else we use CANDIDATE.
3716               (lambda (from)
3717                   (expand-file-name (file-name-nondirectory from) candidate))
3718               (lambda (_from) candidate))
3719           marker)
3720       (and (fboundp 'dired-async-mode)
3721            (dired-async-mode dired-async-state)))
3722     (push (file-name-as-directory
3723            (if (file-directory-p candidate)
3724                (expand-file-name candidate)
3725              (file-name-directory candidate)))
3726           helm-ff-history)
3727     ;; If follow is non--nil we should not be in async mode.
3728     (when (and follow
3729                (not (memq action '(symlink relsymlink hardlink)))
3730                (not (get-buffer dired-log-buffer)))
3731       (let ((target (directory-file-name candidate)))
3732         (unwind-protect
3733              (progn
3734                (setq helm-ff-cand-to-mark
3735                      (helm-get-dest-fnames-from-list files candidate dirflag))
3736                (with-helm-after-update-hook (helm-ff-maybe-mark-candidates))
3737                (if (and dirflag (eq action 'rename))
3738                    (helm-find-files-1 (file-name-directory target)
3739                                       (if helm-ff-transformer-show-only-basename
3740                                           (helm-basename target) target))
3741                  (helm-find-files-1 (file-name-as-directory
3742                                      (expand-file-name candidate)))))
3743           (setq helm-ff-cand-to-mark nil))))))
3744
3745 (defun helm-get-dest-fnames-from-list (flist dest-cand rename-dir-flag)
3746   "Transform filenames of FLIST to abs of DEST-CAND.
3747 If RENAME-DIR-FLAG is non--nil collect the `directory-file-name' of transformed
3748 members of FLIST."
3749   ;; At this point files have been renamed/copied at destination.
3750   ;; That's mean DEST-CAND exists.
3751   (cl-loop
3752         with dest = (expand-file-name dest-cand)
3753         for src in flist
3754         for basename-src = (helm-basename src)
3755         for fname = (cond (rename-dir-flag (directory-file-name dest))
3756                           ((file-directory-p dest)
3757                            (concat (file-name-as-directory dest) basename-src))
3758                           (t dest))
3759         when (file-exists-p fname)
3760         collect fname into tmp-list
3761         finally return (sort tmp-list 'string<)))
3762
3763 (defun helm-ff-maybe-mark-candidates ()
3764   "Mark all candidates of list `helm-ff-cand-to-mark'.
3765 This is used when copying/renaming/symlinking etc... and
3766 following files to destination."
3767   (when (and (string= (assoc-default 'name (helm-get-current-source))
3768                       (assoc-default 'name helm-source-find-files))
3769              helm-ff-cand-to-mark)
3770     (with-helm-window
3771       (while helm-ff-cand-to-mark
3772         (if (string= (car helm-ff-cand-to-mark) (helm-get-selection))
3773             (progn
3774               (helm-make-visible-mark)
3775               (helm-next-line)
3776               (setq helm-ff-cand-to-mark (cdr helm-ff-cand-to-mark)))
3777           (helm-next-line)))
3778       (unless (helm-this-visible-mark)
3779         (helm-prev-visible-mark)))))
3780
3781
3782 ;;; Routines for files
3783 ;;
3784 ;;
3785 (defun helm-file-buffers (filename)
3786   "Returns a list of buffer names corresponding to FILENAME."
3787   (cl-loop with name = (expand-file-name filename)
3788         for buf in (buffer-list)
3789         for bfn = (buffer-file-name buf)
3790         when (and bfn (string= name bfn))
3791         collect (buffer-name buf)))
3792
3793 (defun helm-ff--delete-by-moving-to-trash (file)
3794   "Decide to trash or delete FILE.
3795 Returns non-nil when FILE needs to be trashed."
3796   (let ((remote (file-remote-p file)))
3797     (or
3798      (and delete-by-moving-to-trash
3799           (null helm-current-prefix-arg)
3800           (null current-prefix-arg)
3801           (or (and remote helm-trash-remote-files)
3802               (null remote)))
3803      (and (null delete-by-moving-to-trash)
3804           (or helm-current-prefix-arg
3805               current-prefix-arg)
3806           (or (and remote helm-trash-remote-files)
3807               (null remote))))))
3808
3809 (defun helm-ff-quick-delete (_candidate)
3810   "Delete file CANDIDATE without quitting.
3811
3812 When a prefix arg is given, meaning of `delete-by-moving-to-trash' is
3813 inversed."
3814   (with-helm-window
3815     (let ((marked (helm-marked-candidates)))
3816       (unwind-protect
3817            (cl-loop with trash = (helm-ff--delete-by-moving-to-trash (car marked))
3818                     for c in marked do
3819                     (progn (helm-preselect
3820                             (concat "^" (regexp-quote
3821                                          (if (and helm-ff-transformer-show-only-basename
3822                                                   (not (helm-ff-dot-file-p c)))
3823                                              (helm-basename c) c))))
3824                            (when (y-or-n-p
3825                                   (format "Really %s file `%s'? "
3826                                           (if trash "Trash" "Delete")
3827                                           (abbreviate-file-name c)))
3828                              (helm-delete-file
3829                               c helm-ff-signal-error-on-dot-files 'synchro trash)
3830                              (helm-delete-current-selection)
3831                              (message nil)
3832                              (helm--remove-marked-and-update-mode-line c))))
3833         (setq helm-marked-candidates nil
3834               helm-visible-mark-overlays nil)
3835         (helm-force-update
3836          (let ((presel (helm-get-selection)))
3837            (concat "^" (regexp-quote (if (and helm-ff-transformer-show-only-basename
3838                                               (not (helm-ff-dot-file-p presel)))
3839                                          (helm-basename presel) presel)))))))))
3840
3841 (defun helm-delete-file (file &optional error-if-dot-file-p synchro trash)
3842   "Delete FILE after querying the user.
3843
3844 When a prefix arg is given, meaning of `delete-by-moving-to-trash' is
3845 inversed.
3846
3847 Return error when ERROR-IF-DOT-FILE-P is non nil and user tries to
3848 delete a dotted file i.e. \".\" or \"..\".
3849
3850 Ask user when directory are not empty to allow recursive deletion
3851 unless `helm-ff-allow-recursive-deletes' is non nil.
3852 When user is asked and reply with \"!\" don't ask for remaining
3853 directories.
3854
3855 Ask to kill buffers associated with that file, too.
3856
3857 When TRASH is non nil, trash FILE even if `delete-by-moving-to-trash'
3858 is nil."
3859   (require 'dired)
3860   (cl-block nil
3861     (when (and error-if-dot-file-p
3862                (helm-ff-dot-file-p file))
3863       (error "Error: Cannot operate on `.' or `..'"))
3864     (let ((buffers (helm-file-buffers file))
3865           (helm--reading-passwd-or-string t)
3866           (file-attrs (file-attributes file))
3867           (trash (or trash (helm-ff--delete-by-moving-to-trash file)))
3868           (delete-by-moving-to-trash trash))
3869       (cond ((and (eq (nth 0 file-attrs) t)
3870                   (directory-files file t dired-re-no-dot))
3871              ;; Synchro means persistent deletion from HFF.
3872              (if synchro
3873                  (when (or helm-ff-allow-recursive-deletes
3874                            trash
3875                            (y-or-n-p (format "Recursive delete of `%s'? "
3876                                              (abbreviate-file-name file))))
3877                    (delete-directory file 'recursive trash))
3878                ;; Avoid using dired-delete-file really annoying in
3879                ;; emacs-26 but allows using ! (instead of all) to not
3880                ;; confirm anymore for recursive deletion of
3881                ;; directory. This is not persistent for all session
3882                ;; like emacs-26 does with dired-delete-file (think it
3883                ;; is a bug).
3884                (if (or helm-ff-allow-recursive-deletes trash)
3885                    (delete-directory file 'recursive trash)
3886                  (helm-acase (helm-read-answer (format "Recursive delete of `%s'? [y,n,!,q]"
3887                                                       (abbreviate-file-name file))
3888                                               '("y" "n" "!" "q"))
3889                    ("y" (delete-directory file 'recursive trash))
3890                    ("!" (setq helm-ff-allow-recursive-deletes t)
3891                          (delete-directory file 'recursive trash))
3892                    ("n" (cl-return 'skip))
3893                    ("q" (throw 'helm-abort-delete-file
3894                            (progn
3895                              (message "Abort file deletion") (sleep-for 1))))))))
3896             ((eq (nth 0 file-attrs) t)
3897              (delete-directory file nil trash))
3898             (t (delete-file file trash)))
3899       (when buffers
3900         (cl-dolist (buf buffers)
3901           (when (y-or-n-p (format "Kill buffer %s, too? " buf))
3902             (kill-buffer buf)))))))
3903
3904 (defun helm-delete-marked-files (_ignore)
3905   "Delete marked files with `helm-delete-file'.
3906
3907 When a prefix arg is given, meaning of `delete-by-moving-to-trash' is
3908 inversed."
3909   (let* ((files (helm-marked-candidates :with-wildcard t))
3910          (len 0)
3911          (trash (helm-ff--delete-by-moving-to-trash (car files)))
3912          (prmt (if trash "Trash" "Delete"))
3913          (old--allow-recursive-deletes helm-ff-allow-recursive-deletes))
3914     (with-helm-display-marked-candidates
3915       helm-marked-buffer-name
3916       (helm-ff--count-and-collect-dups files)
3917       (if (not (y-or-n-p (format "%s *%s File(s)" prmt (length files))))
3918           (message "(No deletions performed)")
3919         (catch 'helm-abort-delete-file
3920           (unwind-protect
3921                (cl-dolist (i files)
3922                  (set-text-properties 0 (length i) nil i)
3923                  (let ((res (helm-delete-file
3924                              i helm-ff-signal-error-on-dot-files nil trash)))
3925                    (if (eq res 'skip)
3926                        (progn (message "Directory is not empty, skipping")
3927                               (sleep-for 1))
3928                      (cl-incf len))))
3929             (setq helm-ff-allow-recursive-deletes old--allow-recursive-deletes)))
3930         (message "%s File(s) %s" len (if trash "trashed" "deleted"))))))
3931
3932 ;;; Delete files async
3933 ;;
3934 ;;
3935 (defvar helm-ff-delete-log-file
3936   (expand-file-name "helm-delete-file.log" user-emacs-directory)
3937   "The file use to communicate with emacs child when deleting files async.")
3938
3939 (defvar helm-ff--trash-flag nil)
3940
3941 (define-minor-mode helm-ff--delete-async-modeline-mode
3942     "Notify mode-line that an async process run."
3943   :group 'dired-async
3944   :global t
3945   ;; FIXME: Handle jobs like in dired-async, needs first to allow
3946   ;; naming properly processes in async, they are actually all named
3947   ;; emacs and running `async-batch-invoke', so if one copy a file and
3948   ;; delete another file at the same time it may clash.
3949   :lighter (:eval (propertize (format " %s file(s) async ..."
3950                                       (if helm-ff--trash-flag
3951                                           "Trashing" "Deleting"))
3952                               'face 'helm-delete-async-message))
3953   (unless helm-ff--delete-async-modeline-mode
3954     (let ((visible-bell t)) (ding))
3955     (setq helm-ff--trash-flag nil)))
3956
3957 (defun helm-delete-async-mode-line-message (text face &rest args)
3958   "Notify end of async operation in `mode-line'."
3959   (message nil)
3960   (let ((mode-line-format (concat
3961                            " " (propertize
3962                                 (if args
3963                                     (apply #'format text args)
3964                                     text)
3965                                 'face face))))
3966     (force-mode-line-update)
3967     (sit-for 3)
3968     (force-mode-line-update)))
3969
3970 (defun helm-delete-marked-files-async (_ignore)
3971   "Same as `helm-delete-marked-files' but async.
3972
3973 When a prefix arg is given, meaning of `delete-by-moving-to-trash' is
3974 inversed.
3975
3976 This function is not using `helm-delete-file' and BTW not asking user
3977 for recursive deletion of directory, be warned that directories are
3978 always deleted with no warnings."
3979   (let* ((files (helm-marked-candidates :with-wildcard t))
3980          (trash (helm-ff--delete-by-moving-to-trash (car files)))
3981          (prmt (if trash "Trash" "Delete"))
3982          (buffers (cl-loop for file in files
3983                            for buf = (helm-file-buffers file)
3984                            when buf append buf))
3985          (callback (lambda (result)
3986                      (helm-ff--delete-async-modeline-mode -1)
3987                      (when (file-exists-p helm-ff-delete-log-file)
3988                        (display-warning 'helm
3989                                         (with-temp-buffer
3990                                           (insert-file-contents
3991                                            helm-ff-delete-log-file)
3992                                           (buffer-string))
3993                                         :error
3994                                         "*helm delete files*")
3995                        (fit-window-to-buffer (get-buffer-window
3996                                               "*helm delete files*"))
3997                        (delete-file helm-ff-delete-log-file))
3998                      (when buffers
3999                        (dolist (buf buffers)
4000                          (let ((last-nonmenu-event t))
4001                            (when (y-or-n-p (format "Kill buffer %s, too? " buf))
4002                              (kill-buffer buf)))))
4003                      (run-with-timer
4004                       0.1 nil
4005                       (lambda ()
4006                         (helm-delete-async-mode-line-message
4007                          "%s (%s/%s) file(s) async done"
4008                          'helm-delete-async-message
4009                          (if trash "Trashing" "Deleting")
4010                          result (length files))))))
4011          ;; Workaround emacs-26 bug with tramp see
4012          ;; https://github.com/jwiegley/emacs-async/issues/80.
4013          (async-quiet-switch "-q"))
4014     (setq helm-ff--trash-flag trash)
4015     (with-helm-display-marked-candidates
4016       helm-marked-buffer-name
4017       (helm-ff--count-and-collect-dups files)
4018       (if (not (y-or-n-p (format "%s *%s File(s)" prmt (length files))))
4019           (message "(No deletions performed)")
4020         (async-start
4021          `(lambda ()
4022             ;; `delete-by-moving-to-trash' have to be set globally,
4023             ;; using the TRASH argument of delete-file or
4024             ;; delete-directory is not enough.
4025             (setq delete-by-moving-to-trash ,trash)
4026             (let ((result 0))
4027               (dolist (file ',files result)
4028                 (condition-case err
4029                     (cond ((eq (nth 0 (file-attributes file)) t)
4030                            (delete-directory file 'recursive ,trash)
4031                            (setq result (1+ result)))
4032                           (t (delete-file file ,trash)
4033                              (setq result (1+ result))))
4034                   (error (with-temp-file ,helm-ff-delete-log-file
4035                            (insert (format-time-string "%x:%H:%M:%S\n"))
4036                            (insert (format "%s:%s\n "
4037                                            (car err)
4038                                            (mapconcat 'identity (cdr err) " ")))))))))
4039          callback)
4040         (helm-ff--delete-async-modeline-mode 1)))))
4041
4042 (defun helm-find-file-or-marked (candidate)
4043   "Open file CANDIDATE or open helm marked files in separate windows.
4044 Called with one prefix arg open files in separate windows in a
4045 vertical split.
4046 Called with two prefix arg open files in background without selecting them."
4047   (let ((marked (helm-marked-candidates :with-wildcard t))
4048         (url-p (and helm--url-regexp ; we should have only one candidate.
4049                     (string-match helm--url-regexp candidate)))
4050         (ffap-newfile-prompt helm-ff-newfile-prompt-p)
4051         (find-file-wildcards nil)
4052         (helm--reading-passwd-or-string t))
4053     (if (cdr marked)
4054         (if (equal helm-current-prefix-arg '(16))
4055             (mapcar 'find-file-noselect marked)
4056           ;; If helm-current-prefix-arg is detected split is done
4057           ;; vertically.
4058           (helm-window-show-buffers (mapcar 'find-file-noselect marked)))
4059       (let ((dir (and (not url-p) (helm-basedir candidate))))
4060         (cond ((and dir (file-directory-p dir))
4061                (find-file (substitute-in-file-name candidate)))
4062               (url-p (find-file-at-point candidate))
4063               ;; A a non--existing filename ending with /
4064               ;; Create a directory and jump to it.
4065               ((and (not (file-exists-p candidate))
4066                     (string-match "/$" candidate))
4067                (helm-ff--mkdir candidate 'helm-ff))
4068               ;; A non--existing filename NOT ending with / or
4069               ;; an existing filename, create or jump to it.
4070               ;; If the basedir of candidate doesn't exists,
4071               ;; ask for creating it.
4072               (dir
4073                (helm-ff--mkdir dir)
4074                (find-file candidate))
4075               ;; Find file at `default-directory' when basedir is
4076               ;; unspecified e.g user hit C-k foo RET.
4077               (t (find-file candidate)))))))
4078
4079 (defun helm-ff--mkdir (dir &optional helm-ff)
4080   (when (or (not confirm-nonexistent-file-or-buffer)
4081             (y-or-n-p (format "Create directory `%s'? "
4082                               (abbreviate-file-name
4083                                (expand-file-name dir)))))
4084     (let ((dirfname (directory-file-name dir)))
4085       (if (file-exists-p dirfname)
4086           (error
4087            "Mkdir: Unable to create directory `%s': file exists."
4088            (helm-basename dirfname))
4089         (make-directory dir 'parent)))
4090     (when helm-ff
4091       ;; Allow having this new dir in history
4092       ;; to be able to retrieve it immediately
4093       ;; if we want to e.g copy a file from somewhere in it.
4094       (setq helm-ff-default-directory
4095             (file-name-as-directory (expand-file-name dir)))
4096       (push helm-ff-default-directory helm-ff-history))
4097     (or (and helm-ff (helm-find-files-1 dir)) t)))
4098
4099 (defun helm-transform-file-load-el (actions candidate)
4100   "Add action to load the file CANDIDATE if it is an emacs lisp
4101 file.  Else return ACTIONS unmodified."
4102   (if (member (file-name-extension candidate) '("el" "elc"))
4103       (append actions '(("Load Emacs Lisp File" . load-file)))
4104     actions))
4105
4106 (defun helm-transform-file-browse-url (actions candidate)
4107   "Add an action to browse the file CANDIDATE if it is a html file or URL.
4108 Else return ACTIONS unmodified."
4109   (let ((browse-action '("Browse with Browser" . browse-url)))
4110     (cond ((string-match "^http\\|^ftp" candidate)
4111            (cons browse-action actions))
4112           ((string-match "\\.html?$" candidate)
4113            (append actions (list browse-action)))
4114           (t actions))))
4115
4116 (defun helm-file-on-mounted-network-p (file)
4117   "Returns non-nil when FILE is part of a mounted remote directory.
4118
4119 This function is checking `helm-mounted-network-directories' list."
4120   (when helm-mounted-network-directories
4121     (cl-loop for dir in helm-mounted-network-directories
4122              thereis (file-in-directory-p file dir))))
4123
4124 ;; helm-find-files bindings for filecache
4125 (defvar file-cache-alist)
4126
4127 (defun helm-ff-cache-add-file (_candidate)
4128   (require 'filecache)
4129   (let ((mkd (helm-marked-candidates :with-wildcard t)))
4130     (mapc 'file-cache-add-file mkd)))
4131
4132 (defun helm-ff-file-cache-remove-file-1 (file)
4133   "Remove FILE from `file-cache-alist'."
4134   (let ((entry (assoc (helm-basename file) file-cache-alist))
4135         (dir   (helm-basedir file))
4136         new-entry)
4137     (setq new-entry (remove dir entry))
4138     (when (= (length entry) 1)
4139       (setq new-entry nil))
4140     (setq file-cache-alist
4141           (cons new-entry (remove entry file-cache-alist)))))
4142
4143 (defun helm-ff-file-cache-remove-file (_file)
4144   "Remove marked files from `file-cache-alist.'"
4145   (let ((mkd (helm-marked-candidates)))
4146     (mapc 'helm-ff-file-cache-remove-file-1 mkd)))
4147
4148
4149 ;;; File name history
4150 ;;
4151 ;;
4152 (defvar helm-source-file-name-history
4153   (helm-build-sync-source "File Name History"
4154     :candidates 'file-name-history
4155     :persistent-action #'ignore
4156     :filtered-candidate-transformer #'helm-file-name-history-transformer
4157     :action 'helm-type-file-actions))
4158
4159 (defvar helm-source--ff-file-name-history nil
4160   "[Internal] This source is build to be used with `helm-find-files'.
4161 Don't use it in your own code unless you know what you are doing.")
4162
4163 (defun helm-file-name-history-transformer (candidates _source)
4164   (cl-loop for c in candidates collect
4165         (cond ((or (file-remote-p c)
4166                    (and (fboundp 'tramp-archive-file-name-p)
4167                         (tramp-archive-file-name-p c)))
4168                (cons (propertize c 'face 'helm-history-remote) c))
4169               ((file-exists-p c)
4170                (cons (propertize c 'face 'helm-ff-file) c))
4171               (t (cons (propertize c 'face 'helm-history-deleted) c)))))
4172
4173 (defun helm-ff-file-name-history ()
4174   "Switch to `file-name-history' without quitting `helm-find-files'."
4175   (interactive)
4176   (unless helm-source--ff-file-name-history
4177     (setq helm-source--ff-file-name-history
4178           (helm-build-sync-source "File name history"
4179             :init (lambda ()
4180                     (with-helm-alive-p
4181                       (require 'tramp-archive nil t)
4182                       (when helm-ff-file-name-history-use-recentf
4183                         (require 'recentf)
4184                         (or recentf-mode (recentf-mode 1)))))
4185             :candidates (lambda ()
4186                           (if helm-ff-file-name-history-use-recentf
4187                               recentf-list
4188                               file-name-history))
4189             :fuzzy-match t
4190             :persistent-action 'ignore
4191             :migemo t
4192             :filtered-candidate-transformer 'helm-file-name-history-transformer
4193             :action (helm-make-actions
4194                      "Find file" (lambda (candidate)
4195                                    (helm-set-pattern
4196                                     (expand-file-name candidate))
4197                                    (with-helm-after-update-hook (helm-exit-minibuffer)))
4198                      "Find file in helm" (lambda (candidate)
4199                                            (helm-set-pattern
4200                                             (expand-file-name candidate)))))))
4201   (with-helm-alive-p
4202     (helm :sources 'helm-source--ff-file-name-history
4203           :buffer "*helm-file-name-history*"
4204           :allow-nest t
4205           :resume 'noresume)))
4206 (put 'helm-ff-file-name-history 'helm-only t)
4207
4208 ;;; Browse project
4209 ;; Need dependencies:
4210 ;; <https://github.com/emacs-helm/helm-ls-git>
4211 ;; <https://github.com/emacs-helm/helm-ls-hg>
4212 ;; Only hg and git are supported for now.
4213 (defvar helm--browse-project-cache (make-hash-table :test 'equal))
4214 (defvar helm-buffers-in-project-p)
4215
4216 (defun helm-browse-project-get-buffers (root-directory)
4217   (cl-loop for b in (helm-buffer-list)
4218            ;; FIXME: Why default-directory is root-directory
4219            ;; for current-buffer when coming from helm-quit-and-find-file.
4220            for cd = (with-current-buffer b default-directory)
4221            for bn = (buffer-file-name (get-buffer b))
4222            if (or (and bn (file-in-directory-p bn root-directory))
4223                   (and (null bn)
4224                        (not (file-remote-p cd))
4225                        (file-in-directory-p cd root-directory)))
4226            collect b))
4227
4228 (defun helm-browse-project-build-buffers-source (directory)
4229   (helm-make-source "Buffers in project" 'helm-source-buffers
4230     :header-name (lambda (name)
4231                    (format
4232                     "%s (%s)"
4233                     name (abbreviate-file-name directory)))
4234     :buffer-list (lambda () (helm-browse-project-get-buffers directory))))
4235
4236 (defun helm-browse-project-walk-directory (directory)
4237   "Default function for `helm-browse-project-default-find-files-fn'."
4238   (helm-walk-directory
4239    directory
4240    :directories nil :path 'full :skip-subdirs t))
4241
4242 (defun helm-browse-project-ag-find-files (directory)
4243   "A suitable function for `helm-browse-project-default-find-files-fn'.
4244
4245 Needs AG as backend."
4246   (with-temp-buffer
4247     (call-process-shell-command
4248      (format "ag --hidden -g '.*' %s" directory)
4249      nil t nil)
4250     (mapcar (lambda (f) (expand-file-name f directory))
4251             (split-string (buffer-string) "\n"))))
4252
4253 (defun helm-browse-project-find-files (directory &optional refresh)
4254   (when refresh (remhash directory helm--browse-project-cache))
4255   (unless (gethash directory helm--browse-project-cache)
4256     (puthash directory (funcall helm-browse-project-default-find-files-fn
4257                                 directory)
4258              helm--browse-project-cache))
4259   (helm :sources `(,(helm-browse-project-build-buffers-source directory)
4260                    ,(helm-build-in-buffer-source "Browse project"
4261                      :data (gethash directory helm--browse-project-cache)
4262                      :header-name (lambda (name)
4263                                     (format
4264                                      "%s (%s)"
4265                                      name (abbreviate-file-name directory)))
4266                      :match-part (lambda (c)
4267                                    (if (with-helm-buffer
4268                                          helm-ff-transformer-show-only-basename)
4269                                        (helm-basename c) c))
4270                      :filter-one-by-one
4271                      (lambda (c)
4272                        (if (with-helm-buffer
4273                              helm-ff-transformer-show-only-basename)
4274                            (cons (propertize (helm-basename c)
4275                                              'face 'helm-ff-file)
4276                                  c)
4277                            (propertize c 'face 'helm-ff-file)))
4278                      :keymap helm-generic-files-map
4279                      :action 'helm-type-file-actions))
4280         :ff-transformer-show-only-basename nil
4281         :buffer "*helm browse project*"))
4282
4283 (defvar helm-browse-project-history nil)
4284
4285 ;;;###autoload
4286 (defun helm-projects-history ()
4287   (interactive)
4288   (helm :sources
4289         (helm-build-sync-source "Project history"
4290           :candidates helm-browse-project-history
4291           :action (lambda (candidate)
4292                     (with-helm-default-directory candidate
4293                         (helm-browse-project nil))))
4294         :buffer "*helm browse project history*"))
4295
4296 ;;;###autoload
4297 (defun helm-browse-project (arg)
4298   "Preconfigured helm to browse projects.
4299 Browse files and see status of project with its vcs.
4300 Only HG and GIT are supported for now.
4301 Fall back to `helm-browse-project-find-files'
4302 if current directory is not under control of one of those vcs.
4303 With a prefix ARG browse files recursively, with two prefix ARG
4304 rebuild the cache.
4305 If the current directory is found in the cache, start
4306 `helm-browse-project-find-files' even with no prefix ARG.
4307 NOTE: The prefix ARG have no effect on the VCS controlled directories.
4308
4309 Needed dependencies for VCS:
4310 <https://github.com/emacs-helm/helm-ls-git>
4311 and
4312 <https://github.com/emacs-helm/helm-ls-hg>."
4313   (interactive "P")
4314   (let ((helm-type-buffer-actions
4315          (remove (assoc "Browse project from buffer"
4316                         helm-type-buffer-actions)
4317                  helm-type-buffer-actions))
4318         (helm-buffers-in-project-p t))
4319     (cl-flet ((push-to-hist (root)
4320                 (setq helm-browse-project-history
4321                       (cons root (delete root helm-browse-project-history)))))
4322       (helm-acond ((and (require 'helm-ls-git nil t)
4323                         (fboundp 'helm-ls-git-root-dir)
4324                         (helm-ls-git-root-dir))
4325                    (push-to-hist it)
4326                    (helm-ls-git-ls))
4327                   ((and (require 'helm-ls-hg nil t)
4328                         (fboundp 'helm-hg-root)
4329                         (helm-hg-root))
4330                    (push-to-hist it)
4331                    (helm-hg-find-files-in-project))
4332                   ((helm-browse-project-get--root-dir (helm-current-directory))
4333                    (if (or arg (gethash it helm--browse-project-cache))
4334                        (progn
4335                          (push-to-hist it)
4336                          (helm-browse-project-find-files it (equal arg '(16))))
4337                        (helm :sources (helm-browse-project-build-buffers-source it)
4338                              :buffer "*helm browse project*")))))))
4339
4340 (defun helm-browse-project-get--root-dir (directory)
4341   (cl-loop with dname = (file-name-as-directory directory)
4342            while (and dname (not (gethash dname helm--browse-project-cache)))
4343            if (file-remote-p dname)
4344            do (setq dname nil) else
4345            do (setq dname (helm-basedir (substring dname 0 (1- (length dname)))))
4346            finally return (or dname (file-name-as-directory directory))))
4347
4348 (defun helm-ff-browse-project (_candidate)
4349   "Browse project in current directory.
4350 See `helm-browse-project'."
4351   (with-helm-default-directory helm-ff-default-directory
4352       (helm-browse-project helm-current-prefix-arg)))
4353
4354 (defun helm-ff-run-browse-project ()
4355   (interactive)
4356   (with-helm-alive-p
4357     (helm-exit-and-execute-action 'helm-ff-browse-project)))
4358 (put 'helm-ff-run-browse-project 'helm-only t)
4359
4360 (defun helm-ff-gid (_candidate)
4361   (with-helm-default-directory helm-ff-default-directory
4362       (helm-gid)))
4363
4364 (defun helm-ff-run-gid ()
4365   (interactive)
4366   (with-helm-alive-p
4367     (helm-exit-and-execute-action 'helm-ff-gid)))
4368 (put 'helm-ff-run-gid 'helm-only t)
4369
4370 ;; helm-find bindings for helm-find-files.
4371 (defun helm-ff-find-sh-command (_candidate)
4372   "Run `helm-find' from `helm-find-files'."
4373   (require 'helm-find)
4374   (helm-find-1 helm-ff-default-directory))
4375
4376 (defun helm-ff-run-find-sh-command ()
4377   "Run find shell command action with key from `helm-find-files'."
4378   (interactive)
4379   (with-helm-alive-p
4380     (helm-exit-and-execute-action 'helm-ff-find-sh-command)))
4381 (put 'helm-ff-run-find-sh-command 'helm-only t)
4382
4383
4384 ;;;###autoload
4385 (defun helm-find-files (arg)
4386   "Preconfigured `helm' for helm implementation of `find-file'.
4387 Called with a prefix arg show history if some.
4388 Don't call it from programs, use `helm-find-files-1' instead.
4389 This is the starting point for nearly all actions you can do on files."
4390   (interactive "P")
4391   (let* ((hist            (and arg helm-ff-history (helm-find-files-history nil)))
4392          (smart-input     (or hist (helm-find-files-initial-input)))
4393          (default-input   (expand-file-name (helm-current-directory)))
4394          (input           (cond ((and (null hist)
4395                                       helm-find-files-ignore-thing-at-point)
4396                                  default-input)
4397                                 ((and (eq major-mode 'org-agenda-mode)
4398                                       org-directory
4399                                       (not smart-input))
4400                                  (expand-file-name org-directory))
4401                                 ((and (eq major-mode 'dired-mode) smart-input)
4402                                  (file-name-directory smart-input))
4403                                 ((and (not (string= smart-input ""))
4404                                       smart-input))
4405                                 (t default-input)))
4406          (input-as-presel (null (nth 0 (file-attributes input))))
4407          (presel          (helm-aif (or hist
4408                                         (and input-as-presel input)
4409                                         (buffer-file-name (current-buffer))
4410                                         (and (eq major-mode 'dired-mode)
4411                                              smart-input))
4412                               (if (and helm-ff-transformer-show-only-basename
4413                                        (null hist)
4414                                        (not (string-match-p "[.]\\{1,2\\}\\'" it)))
4415                                   (helm-basename it) it))))
4416     ;; Continue using the same display function as history which used
4417     ;; probably itself the same display function as inner HFF call,
4418     ;; i.e. if history was using frame use a frame otherwise use a window.
4419     (when (and hist (buffer-live-p (get-buffer helm-ff-history-buffer-name)))
4420       (helm-set-local-variable 'helm-display-function
4421                                (with-current-buffer helm-ff-history-buffer-name
4422                                  helm-display-function)
4423                                'helm--last-frame-parameters
4424                                (with-current-buffer helm-ff-history-buffer-name
4425                                  helm--last-frame-parameters)))
4426     (set-text-properties 0 (length input) nil input)
4427     (setq current-prefix-arg nil)
4428     ;; Allow next helm session to reuse helm--last-frame-parameters as
4429     ;; resume would do.
4430     (let ((helm--executing-helm-action (not (null hist))))
4431       (helm-find-files-1 input (and presel (null helm-ff-no-preselect)
4432                                     (concat "^" (regexp-quote presel)))))))
4433
4434 ;;;###autoload
4435 (defun helm-delete-tramp-connection ()
4436   "Allow deleting tramp connection or marked tramp connections at once.
4437
4438 This replace `tramp-cleanup-connection' which is partially broken in
4439 emacs < to 25.1.50.1 (See Emacs Bug#24432).
4440
4441 It allows additionally to delete more than one connection at once."
4442   (interactive)
4443   (let ((helm-quit-if-no-candidate
4444          (lambda ()
4445            (message "No Tramp connection found"))))
4446     (helm :sources (helm-build-sync-source "Tramp connections"
4447                      :candidates (tramp-list-connections)
4448                      :candidate-transformer (lambda (candidates)
4449                                               (cl-loop for v in candidates
4450                                                        for name = (apply #'tramp-make-tramp-file-name
4451                                                                          (cl-loop with v = (helm-ff--tramp-cons-or-vector v)
4452                                                                                   for i across v collect i))
4453                                                        when (or (processp (tramp-get-connection-process v))
4454                                                                 (buffer-live-p (get-buffer (tramp-buffer-name v))))
4455                                                        collect (cons name v)))
4456                      :action (lambda (_vec)
4457                                (let ((vecs (helm-marked-candidates)))
4458                                  (cl-loop for v in vecs
4459                                           do (progn
4460                                                (tramp-cleanup-connection v)
4461                                                (remhash v tramp-cache-data))))))
4462           :buffer "*helm tramp connections*")))
4463
4464
4465 (provide 'helm-files)
4466
4467 ;; Local Variables:
4468 ;; byte-compile-warnings: (not obsolete)
4469 ;; coding: utf-8
4470 ;; indent-tabs-mode: nil
4471 ;; End:
4472
4473 ;;; helm-files.el ends here