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 |