summaryrefslogtreecommitdiff
path: root/helm-files.el
diff options
context:
space:
mode:
Diffstat (limited to 'helm-files.el')
-rw-r--r--helm-files.el566
1 files changed, 430 insertions, 136 deletions
diff --git a/helm-files.el b/helm-files.el
index cb00a5c8..947753b5 100644
--- a/helm-files.el
+++ b/helm-files.el
@@ -51,6 +51,7 @@
(declare-function helm-gid "helm-id-utils.el")
(declare-function helm-find-1 "helm-find")
(declare-function helm-get-default-program-for-file "helm-external")
+(declare-function helm-open-file-externally "helm-external")
(defvar recentf-list)
(defvar helm-mm-matching-method)
@@ -96,19 +97,6 @@ See `helm-ff--transform-pattern-for-completion' for more info."
:group 'helm-files
:type 'boolean)
-(defcustom helm-ff-tramp-not-fancy 'dirs-only
- "Colorize remote files when non nil.
-
-When 'dirs-only is passed as value (default) only directories are
-shown.
-
-Be aware that a nil value will make tramp display very slow."
- :group 'helm-files
- :type '(choice
- (const :tag "Show directories only" dirs-only)
- (const :tag "No colors" t)
- (const :tag "Colorize all" nil)))
-
(defcustom helm-ff-exif-data-program "exiftran"
"Program used to extract exif data of an image file."
:group 'helm-files
@@ -241,7 +229,8 @@ This doesn't disable url or mail at point, see
(defcustom helm-ff-guess-ffap-urls t
"Use ffap to guess local urls at point in `helm-find-files'.
This doesn't disable guessing filenames at point,
-see `helm-ff-guess-ffap-filenames' for this."
+see `helm-ff-guess-ffap-filenames' for this.
+See also `ffap-url-unwrap-remote' that may override this variable."
:group 'helm-files
:type 'boolean)
@@ -324,6 +313,44 @@ better to not modify this variable."
:type '(choice
(const :tag "Delete non-empty directories" t)
(const :tag "Confirm for each directory" nil)))
+
+(defcustom helm-ff-delete-files-function #'helm-delete-marked-files
+ "The function to use by default to delete files.
+
+Default is to delete files synchronously, other choice is to delete
+files asynchronously.
+
+BE AWARE that when deleting async you will not be warned about
+recursive deletion of directories, IOW non empty directories will be
+deleted with no warnings in background!!!
+
+It is the function that will be used when using `\\<helm-find-files-map>\\[helm-ff-run-delete-file]'
+from `helm-find-files'."
+ :group 'helm-files
+ :type '(choice (function :tag "Delete files synchronously."
+ helm-delete-marked-files)
+ (function :tag "Delete files asynchronously."
+ helm-delete-marked-files-async)))
+
+(defcustom helm-list-directory-function
+ (cl-case system-type
+ (gnu/linux #'helm-list-dir-external)
+ (berkeley-unix #'helm-list-dir-external)
+ (windows-nt #'helm-list-dir-lisp)
+ (t #'helm-list-dir-lisp))
+ "The function used in `helm-find-files' to list remote directories.
+
+Actually helm provides two functions to do this: `helm-list-dir-lisp'
+and `helm-list-dir-external'.
+
+Using `helm-list-dir-external' will provides a similar display to what
+provided with local files i.e. colorized symlinks, executables files
+etc... whereas using `helm-list-dir-lisp' will allow colorizing only
+directories but is more portable.
+
+NOTE that `helm-list-dir-external' needs ls and awk as dependencies."
+ :type 'function
+ :group 'helm-files)
;;; Faces
;;
@@ -374,16 +401,36 @@ better to not modify this variable."
"Face used for invalid symlinks in `helm-find-files'."
:group 'helm-files-faces)
+(defface helm-ff-denied
+ '((t (:foreground "red" :background "black")))
+ "Face used for non accessible files in `helm-find-files'."
+ :group 'helm-files-faces)
+
(defface helm-ff-file
'((t (:inherit font-lock-builtin-face)))
"Face used for file names in `helm-find-files'."
:group 'helm-files-faces)
+(defface helm-ff-truename
+ '((t (:inherit font-lock-string-face)))
+ "Face used for symlink truenames in `helm-find-files'."
+ :group 'helm-files-faces)
+
(defface helm-ff-dirs
'((t (:inherit font-lock-function-name-face)))
"Face used for file names in recursive dirs completion in `helm-find-files'."
:group 'helm-files-faces)
+(defface helm-ff-socket
+ '((t (:foreground "DeepPink")))
+ "Face used for socket files in `helm-find-files'."
+ :group 'helm-files-faces)
+
+(defface helm-ff-pipe
+ '((t (:foreground "yellow" :background "black")))
+ "Face used for named pipes and character device files in `helm-find-files'."
+ :group 'helm-files-faces)
+
(defface helm-history-deleted
'((t (:inherit helm-ff-invalid-symlink)))
"Face used for deleted files in `file-name-history'."
@@ -394,6 +441,10 @@ better to not modify this variable."
"Face used for remote files in `file-name-history'."
:group 'helm-files-faces)
+(defface helm-delete-async-message
+ '((t (:foreground "yellow")))
+ "Face used for mode-line message."
+ :group 'helm-files-faces)
;;; Helm-find-files - The helm file browser.
;;
@@ -421,6 +472,7 @@ better to not modify this variable."
(define-key map (kbd "M-B") 'helm-ff-run-byte-compile-file)
(define-key map (kbd "M-L") 'helm-ff-run-load-file)
(define-key map (kbd "M-S") 'helm-ff-run-symlink-file)
+ (define-key map (kbd "M-Y") 'helm-ff-run-relsymlink-file)
(define-key map (kbd "M-H") 'helm-ff-run-hardlink-file)
(define-key map (kbd "M-D") 'helm-ff-run-delete-file)
(define-key map (kbd "M-K") 'helm-ff-run-kill-buffer-persistent)
@@ -431,6 +483,7 @@ better to not modify this variable."
(define-key map (kbd "C-c o") 'helm-ff-run-switch-other-window)
(define-key map (kbd "C-c C-o") 'helm-ff-run-switch-other-frame)
(define-key map (kbd "C-c C-x") 'helm-ff-run-open-file-externally)
+ (define-key map (kbd "C-c C-v") 'helm-ff-run-preview-file-externally)
(define-key map (kbd "C-c X") 'helm-ff-run-open-file-with-default-tool)
(define-key map (kbd "M-!") 'helm-ff-run-eshell-command-on-file)
(define-key map (kbd "M-@") 'helm-ff-run-query-replace-fnames-on-marked)
@@ -557,13 +610,20 @@ Don't set it directly, use instead `helm-ff-auto-update-initial-value'.")
"Find alternate file `C-x C-v'" 'find-alternate-file
"Ediff File `C-c ='" 'helm-find-files-ediff-files
"Ediff Merge File `M-='" 'helm-find-files-ediff-merge-files
- "Delete File(s) `M-D'" 'helm-delete-marked-files
+ (lambda () (format "Delete File(s)%s (C-u no trash)" (if (eq helm-ff-delete-files-function
+ 'helm-delete-marked-files)
+ " `M-D'" "")))
+ 'helm-delete-marked-files
+ (lambda () (format "Delete File(s) async%s (C-u no trash)" (if (eq helm-ff-delete-files-function
+ 'helm-delete-marked-files-async)
+ " `M-D'" "")))
+ 'helm-delete-marked-files-async
"Touch File(s) `M-T'" 'helm-ff-touch-files
"Copy file(s) `M-C, C-u to follow'" 'helm-find-files-copy
"Rename file(s) `M-R, C-u to follow'" 'helm-find-files-rename
"Backup files" 'helm-find-files-backup
"Symlink files(s) `M-S, C-u to follow'" 'helm-find-files-symlink
- "Relsymlink file(s) `C-u to follow'" 'helm-find-files-relsymlink
+ "Relsymlink file(s) `M-Y, C-u to follow'" 'helm-find-files-relsymlink
"Hardlink file(s) `M-H, C-u to follow'" 'helm-find-files-hardlink
"Find file other window `C-c o'" 'helm-find-files-other-window
"Find file other frame `C-c C-o'" 'find-file-other-frame
@@ -1505,6 +1565,13 @@ Behave differently depending of `helm-selection':
(helm-exit-and-execute-action 'helm-find-files-symlink)))
(put 'helm-ff-run-symlink-file 'helm-only t)
+(defun helm-ff-run-relsymlink-file ()
+ "Run Symlink file action from `helm-source-find-files'."
+ (interactive)
+ (with-helm-alive-p
+ (helm-exit-and-execute-action 'helm-find-files-relsymlink)))
+(put 'helm-ff-run-relsymlink-file 'helm-only t)
+
(defun helm-ff-run-hardlink-file ()
"Run Hardlink file action from `helm-source-find-files'."
(interactive)
@@ -1516,7 +1583,7 @@ Behave differently depending of `helm-selection':
"Run Delete file action from `helm-source-find-files'."
(interactive)
(with-helm-alive-p
- (helm-exit-and-execute-action 'helm-delete-marked-files)))
+ (helm-exit-and-execute-action helm-ff-delete-files-function)))
(put 'helm-ff-run-delete-file 'helm-only t)
(defun helm-ff-run-complete-fn-at-point ()
@@ -2311,22 +2378,74 @@ purpose."
(defun helm-list-directory (directory)
"List directory DIRECTORY.
-If DIRECTORY is remote use `file-name-all-completions' and add a
-`helm-ff-dir' property on each one ending with \"/\" otherwise use
+If DIRECTORY is remote use `helm-list-directory-function' otherwise use
`directory-files'."
(if (file-remote-p directory)
- (cl-loop for f in (sort (file-name-all-completions "" directory)
- 'string-lessp)
- unless (or (member f '("./" "../"))
- ;; Ignore the tramp names from /
- ;; completion, e.g. ssh: scp: etc...
- (char-equal (aref f (1- (length f))) ?:))
- if (and (helm--dir-name-p f)
- (helm--dir-file-name f directory))
- collect (propertize it 'helm-ff-dir t)
- else collect (expand-file-name f directory))
+ (funcall helm-list-directory-function directory)
(directory-files directory t directory-files-no-dot-files-regexp)))
+(defun helm-list-dir-lisp (directory)
+ "List DIRECTORY with `file-name-all-completions' as backend.
+
+Add a `helm-ff-dir' property on each fname ending with \"/\"."
+ ;; NOTE: `file-name-all-completions' and `directory-files' and most
+ ;; tramp file handlers don't handle cntrl characters in fnames, so
+ ;; the displayed files will be plain wrong in this case, even worst
+ ;; the filenames will be splitted in two or more filenames.
+ (cl-loop for f in (sort (file-name-all-completions "" directory)
+ 'string-lessp)
+ unless (or (string= f "")
+ (member f '("./" "../"))
+ ;; Ignore the tramp names from /
+ ;; completion, e.g. ssh: scp: etc...
+ (char-equal (aref f (1- (length f))) ?:))
+ if (and (helm--dir-name-p f)
+ (helm--dir-file-name f directory))
+ collect (propertize it 'helm-ff-dir t)
+ else collect (expand-file-name f directory)))
+
+(defun helm-list-dir-external (dir)
+ "List directory DIR with external shell command as backend.
+
+This function is fast enough to be used for remote files and save the
+type of files at the same time in a property for using it later in the
+transformer."
+ (let ((default-directory (file-name-as-directory
+ (expand-file-name dir))))
+ (with-temp-buffer
+ (when (eq (process-file-shell-command
+ (format
+ ;; -A remove dot files, -F append [*=@|/>] at eof
+ ;; and -Q quote the real filename. If not using -Q,
+ ;; there is no way to distinguish if foo* is a real
+ ;; file or if it is foo the executable file so with
+ ;; -Q we have "foo"* for the executable file foo and
+ ;; "foo*" for the real file foo. The downside is
+ ;; that we need an extra step to remove the quotes
+ ;; at the end which impact performances.
+ "ls -A -1 -F -b -Q | awk -v a=%s '{print a $1}'"
+ default-directory)
+ nil t nil)
+ 0)
+ (goto-char (point-min))
+ (save-excursion
+ (while (re-search-forward "[*=@|/>]$" nil t)
+ ;; A line looks like /home/you/"foo"@
+ (pcase (match-string 0)
+ ("*" (replace-match "")
+ (put-text-property
+ (point-at-bol) (point-at-eol) 'helm-ff-exe t))
+ ("@" (replace-match "")
+ (put-text-property
+ (point-at-bol) (point-at-eol) 'helm-ff-sym t))
+ ("/" (replace-match "")
+ (put-text-property
+ (point-at-bol) (point-at-eol) 'helm-ff-dir t))
+ ((or "=" "|" ">") (replace-match "")))))
+ (while (re-search-forward "[\"]" nil t)
+ (replace-match ""))
+ (split-string (buffer-string) "\n" t)))))
+
(defun helm-ff-directory-files (directory)
"List contents of DIRECTORY.
Argument FULL mean absolute path.
@@ -2466,46 +2585,48 @@ Note that only existing directories are saved here."
(defun helm-ff-properties (candidate)
"Show file properties of CANDIDATE in a tooltip or message."
- (require 'helm-external) ; For `helm-get-default-program-for-file'.
- (let* ((all (helm-file-attributes candidate))
- (dired-line (helm-file-attributes
- candidate :dired t :human-size t))
- (type (cl-getf all :type))
- (mode-type (cl-getf all :mode-type))
- (owner (cl-getf all :uid))
- (owner-right (cl-getf all :user t))
- (group (cl-getf all :gid))
- (group-right (cl-getf all :group))
- (other-right (cl-getf all :other))
- (size (helm-file-human-size (cl-getf all :size)))
- (modif (cl-getf all :modif-time))
- (access (cl-getf all :access-time))
- (ext (helm-get-default-program-for-file candidate))
- (tooltip-hide-delay (or helm-tooltip-hide-delay tooltip-hide-delay)))
- (if (and (window-system) tooltip-mode)
- (tooltip-show
- (concat
- (helm-basename candidate) "\n"
- dired-line "\n"
- (format "Mode: %s\n" (helm-get-default-mode-for-file candidate))
- (format "Ext prog: %s\n" (or (and ext (replace-regexp-in-string
- " %s" "" ext))
- "Not defined"))
- (format "Type: %s: %s\n" type mode-type)
- (when (string= type "symlink")
- (format "True name: '%s'\n"
- (cond ((string-match "^\.#" (helm-basename candidate))
- "Autosave symlink")
- ((helm-ff-valid-symlink-p candidate)
- (file-truename candidate))
- (t "Invalid Symlink"))))
- (format "Owner: %s: %s\n" owner owner-right)
- (format "Group: %s: %s\n" group group-right)
- (format "Others: %s\n" other-right)
- (format "Size: %s\n" size)
- (format "Modified: %s\n" modif)
- (format "Accessed: %s\n" access)))
- (message dired-line) (sit-for 5))))
+ (require 'helm-external) ; For `helm-get-default-program-for-file'.
+ (helm-aif (helm-file-attributes candidate)
+ (let* ((all it)
+ (dired-line (helm-file-attributes
+ candidate :dired t :human-size t))
+ (type (cl-getf all :type))
+ (mode-type (cl-getf all :mode-type))
+ (owner (cl-getf all :uid))
+ (owner-right (cl-getf all :user t))
+ (group (cl-getf all :gid))
+ (group-right (cl-getf all :group))
+ (other-right (cl-getf all :other))
+ (size (helm-file-human-size (cl-getf all :size)))
+ (modif (cl-getf all :modif-time))
+ (access (cl-getf all :access-time))
+ (ext (helm-get-default-program-for-file candidate))
+ (tooltip-hide-delay (or helm-tooltip-hide-delay tooltip-hide-delay)))
+ (if (and (window-system) tooltip-mode)
+ (tooltip-show
+ (concat
+ (helm-basename candidate) "\n"
+ dired-line "\n"
+ (format "Mode: %s\n" (helm-get-default-mode-for-file candidate))
+ (format "Ext prog: %s\n" (or (and ext (replace-regexp-in-string
+ " %s" "" ext))
+ "Not defined"))
+ (format "Type: %s: %s\n" type mode-type)
+ (when (string= type "symlink")
+ (format "True name: '%s'\n"
+ (cond ((string-match "^\.#" (helm-basename candidate))
+ "Autosave symlink")
+ ((helm-ff-valid-symlink-p candidate)
+ (file-truename candidate))
+ (t "Invalid Symlink"))))
+ (format "Owner: %s: %s\n" owner owner-right)
+ (format "Group: %s: %s\n" group group-right)
+ (format "Others: %s\n" other-right)
+ (format "Size: %s\n" size)
+ (format "Modified: %s\n" modif)
+ (format "Accessed: %s\n" access)))
+ (message dired-line) (sit-for 5)))
+ (message "Permission denied, file not readable")))
(defun helm-ff-properties-persistent ()
"Show properties without quitting helm."
@@ -2528,7 +2649,10 @@ Note that only existing directories are saved here."
(member (helm-basename file) '("." "..")))
(defun helm-ff-quick-delete (_candidate)
- "Delete file CANDIDATE without quitting."
+ "Delete file CANDIDATE without quitting.
+
+When a prefix arg is given, files are deleted and not trashed even if
+\`delete-by-moving-to-trash' is non nil."
(with-helm-window
(let ((marked (helm-marked-candidates)))
(unwind-protect
@@ -2539,7 +2663,10 @@ Note that only existing directories are saved here."
(not (helm-ff-dot-file-p c)))
(helm-basename c) c))))
(when (y-or-n-p
- (format "Really Delete file `%s'? "
+ (format "Really %s file `%s'? "
+ (if (and delete-by-moving-to-trash
+ (null current-prefix-arg))
+ "Trash" "Delete")
(abbreviate-file-name c)))
(helm-delete-file
c helm-ff-signal-error-on-dot-files 'synchro)
@@ -2596,6 +2723,21 @@ in `helm-find-files-persistent-action-if'."
(helm-execute-persistent-action 'kill-buffer-fname)))
(put 'helm-ff-run-kill-buffer-persistent 'helm-only t)
+;; Preview with external tool
+(defun helm-ff-persistent-open-file-externally (file)
+ (require 'helm-external)
+ (if (helm-get-default-program-for-file file)
+ (helm-open-file-externally file)
+ (message "Please configure an external program for `*%s' file in `helm-external-programs-associations'"
+ (file-name-extension file t))))
+
+(defun helm-ff-run-preview-file-externally ()
+ (interactive)
+ (with-helm-alive-p
+ (helm-attrset 'open-file-externally '(helm-ff-persistent-open-file-externally . never-split))
+ (helm-execute-persistent-action 'open-file-externally)))
+(put 'helm-ff-run-preview-file-externally 'helm-only t)
+
(defun helm-ff-prefix-filename (fname &optional file-or-symlinkp new-file)
"Return filename FNAME maybe prefixed with [?] or [@].
If FILE-OR-SYMLINKP is non--nil this mean we assume FNAME is an
@@ -2663,32 +2805,43 @@ Return candidates prefixed with basename of `helm-input' first."
(defun helm-ff-filter-candidate-one-by-one (file)
"`filter-one-by-one' Transformer function for `helm-source-find-files'."
;; Handle boring files
- (let ((basename (helm-basename file)))
+ (let ((basename (helm-basename file))
+ dot)
(unless (and helm-ff-skip-boring-files
(helm-ff-boring-file-p basename))
- ;; Handle tramp files.
+
+ ;; Handle tramp files with minimal highlighting.
(if (and (or (string-match-p helm-tramp-file-name-regexp helm-pattern)
- (helm-file-on-mounted-network-p helm-pattern))
- helm-ff-tramp-not-fancy)
- (if helm-ff-transformer-show-only-basename
- (if (helm-dir-is-dot file)
- (if (eq helm-ff-tramp-not-fancy 'dirs-only)
- (propertize file 'face 'helm-ff-dotted-directory)
- file)
- (cons (or (helm-ff--get-host-from-tramp-invalid-fname file)
- (if (and (get-text-property 1 'helm-ff-dir file)
- (eq helm-ff-tramp-not-fancy 'dirs-only))
- (propertize basename 'face 'helm-ff-directory)
- basename))
- file))
- (cons (if (and (get-text-property 1 'helm-ff-dir file)
- (eq helm-ff-tramp-not-fancy 'dirs-only))
- (propertize file 'face 'helm-ff-directory)
- file)
- file))
- ;; Now highlight.
+ (helm-file-on-mounted-network-p helm-pattern)))
+ (let ((disp (if (and helm-ff-transformer-show-only-basename
+ (not (setq dot (helm-dir-is-dot file))))
+ (or (helm-ff--get-host-from-tramp-invalid-fname file)
+ basename)
+ file)))
+ ;; Filename with cntrl chars e.g. foo^J
+ ;; This will not work as long as most tramp file handlers doesn't
+ ;; handle such case, e.g. file-name-all-completions,
+ ;; directory-files, file-name-nondirectory etc...
+ ;; Keep it though in case they fix this upstream...
+ (setq disp (replace-regexp-in-string "[[:cntrl:]]" "?" disp))
+ (cond (;; Dot directories . and ..
+ dot (propertize file 'face 'helm-ff-dotted-directory))
+ ;; Directories.
+ ((get-text-property 1 'helm-ff-dir file)
+ (cons (propertize disp 'face 'helm-ff-directory) file))
+ ;; Executable files.
+ ((get-text-property 1 'helm-ff-exe file)
+ (cons (propertize disp 'face 'helm-ff-executable) file))
+ ;; Symlinks.
+ ((get-text-property 1 'helm-ff-sym file)
+ (cons (propertize disp 'face 'helm-ff-symlink) file))
+ ;; Any other files.
+ (t (cons (propertize disp 'face 'helm-ff-file) file))))
+
+ ;; Highlight local files showing everything, symlinks, exe,
+ ;; dirs etc...
(let* ((disp (if (and helm-ff-transformer-show-only-basename
- (not (helm-dir-is-dot file))
+ (not (setq dot (helm-dir-is-dot file)))
(not (and helm--url-regexp
(string-match helm--url-regexp file)))
(not (string-match helm-ff-url-regexp file)))
@@ -2698,57 +2851,62 @@ Return candidates prefixed with basename of `helm-input' first."
(attr (file-attributes file))
(type (car attr))
x-bit)
-
+ ;; Filename cntrl chars e.g. foo^J
+ (setq disp (replace-regexp-in-string "[[:cntrl:]]" "?" disp))
(cond ((string-match "file-error" file) file)
- ( ;; A not already saved file.
+ (;; A dead symlink.
(and (stringp type)
(not (helm-ff-valid-symlink-p file))
- (not (string-match "^\.#" basename)))
- (cons (helm-ff-prefix-filename
- (propertize disp 'face 'helm-ff-invalid-symlink) t)
+ (not (string-match "^\\.#" basename)))
+ (cons (propertize disp 'face 'helm-ff-invalid-symlink)
file))
;; A dotted directory symlinked.
- ((and (helm-ff-dot-file-p file) (stringp type))
- (cons (helm-ff-prefix-filename
- (propertize disp 'face 'helm-ff-dotted-symlink-directory) t)
+ ((and dot (stringp type))
+ (cons (propertize disp 'face 'helm-ff-dotted-symlink-directory)
file))
;; A dotted directory.
((helm-ff-dot-file-p file)
- (cons (helm-ff-prefix-filename
- (propertize disp 'face 'helm-ff-dotted-directory) t)
+ (cons (propertize disp 'face 'helm-ff-dotted-directory)
file))
;; A symlink.
((stringp type)
- (cons (helm-ff-prefix-filename
- (propertize disp 'face 'helm-ff-symlink) t)
+ (cons (propertize disp 'display
+ (concat (propertize disp 'face 'helm-ff-symlink)
+ " -> "
+ (propertize (abbreviate-file-name type)
+ 'face 'helm-ff-truename)))
file))
;; A directory.
((eq t type)
- (cons (helm-ff-prefix-filename
- (propertize disp 'face 'helm-ff-directory) t)
+ (cons (propertize disp 'face 'helm-ff-directory)
+ file))
+ ;; A character device file.
+ ((and attr (string-match
+ "\\`[cp]" (setq x-bit (substring (nth 8 attr) 0 4))))
+ (cons (propertize disp 'face 'helm-ff-pipe)
+ file))
+ ;; A socket file.
+ ((and attr (string-match "\\`[s]" x-bit))
+ (cons (propertize disp 'face 'helm-ff-socket)
file))
;; An executable file.
((and attr
(string-match
- "x\\'" (setq x-bit (substring (nth 8 attr) 0 4))))
- (cons (helm-ff-prefix-filename
- (propertize disp 'face 'helm-ff-executable) t)
+ "x\\'" x-bit))
+ (cons (propertize disp 'face 'helm-ff-executable)
file))
;; An executable file with suid
((and attr (string-match "s\\'" x-bit))
- (cons (helm-ff-prefix-filename
- (propertize disp 'face 'helm-ff-suid) t)
+ (cons (propertize disp 'face 'helm-ff-suid)
file))
;; A file.
((and attr (null type))
- (cons (helm-ff-prefix-filename
- (propertize disp 'face 'helm-ff-file) t)
+ (cons (propertize disp 'face 'helm-ff-file)
file))
;; A non--existing file.
- (t
- (cons (helm-ff-prefix-filename
- (propertize disp 'face 'helm-ff-file) nil 'new-file)
- file))))))))
+ (t (cons (helm-ff-prefix-filename
+ (propertize disp 'face 'helm-ff-file) nil 'new-file)
+ file))))))))
(defun helm-find-files-action-transformer (actions candidate)
"Action transformer for `helm-source-find-files'."
@@ -3289,7 +3447,9 @@ is helm-source-find-files."
;; disabled with `ffap-machine-p-known' bound to 'reject.
;; `ffap-file-at-point' can be neutralized with
;; `helm-ff-guess-ffap-filenames' and `ffap-url-at-point' with
- ;; `helm-ff-guess-ffap-urls'.
+ ;; `helm-ff-guess-ffap-urls'
+ ;; Note also that `ffap-url-unwrap-remote' can override these
+ ;; variables.
(let ((ffap-alist (and helm-ff-guess-ffap-filenames ffap-alist))
(ffap-url-regexp helm--url-regexp))
(if (eq major-mode 'dired-mode)
@@ -3406,6 +3566,7 @@ Where ACTION is a symbol that can be one of:
Argument FOLLOW when non--nil specify to follow FILES to destination for the actions
copy and rename."
(require 'dired-async)
+ (require 'dired-x) ; For dired-keep-marker-relsymlink
(when (get-buffer dired-log-buffer) (kill-buffer dired-log-buffer))
;; When default-directory in current-buffer is an invalid directory,
;; (e.g buffer-file directory have been renamed somewhere else)
@@ -3518,7 +3679,19 @@ following files to destination."
collect (buffer-name buf)))
(defun helm-delete-file (file &optional error-if-dot-file-p synchro)
- "Delete the given file after querying the user.
+ "Delete FILE after querying the user.
+
+When a prefix arg is given, files are deleted and not trashed even if
+\`delete-by-moving-to-trash' is non nil.
+
+Return error when ERROR-IF-DOT-FILE-P is non nil and user tries to
+delete a dotted file i.e. \".\" or \"..\".
+
+Ask user when directory are not empty to allow recursive deletion
+unless `helm-ff-allow-recursive-deletes' is non nil.
+When user is asked and reply with \"!\" don't ask for remaining
+directories.
+
Ask to kill buffers associated with that file, too."
(require 'dired)
(cl-block nil
@@ -3526,50 +3699,59 @@ Ask to kill buffers associated with that file, too."
(helm-ff-dot-file-p file))
(error "Error: Cannot operate on `.' or `..'"))
(let ((buffers (helm-file-buffers file))
- (helm--reading-passwd-or-string t))
- (cond ((and (not (file-symlink-p file))
- (file-directory-p file)
+ (helm--reading-passwd-or-string t)
+ (file-attrs (file-attributes file))
+ (trash (and delete-by-moving-to-trash
+ (null helm-current-prefix-arg)
+ (null current-prefix-arg))))
+ (cond ((and (eq (nth 0 file-attrs) t)
(directory-files file t dired-re-no-dot))
;; Synchro means persistent deletion from HFF.
(if synchro
- (when (y-or-n-p (format "Recursive delete of `%s'? "
- (abbreviate-file-name file)))
- (delete-directory file 'recursive delete-by-moving-to-trash))
+ (when (or helm-ff-allow-recursive-deletes
+ trash
+ (y-or-n-p (format "Recursive delete of `%s'? "
+ (abbreviate-file-name file))))
+ (delete-directory file 'recursive trash))
;; Avoid using dired-delete-file really annoying in
;; emacs-26 but allows using ! (instead of all) to not
;; confirm anymore for recursive deletion of
;; directory. This is not persistent for all session
;; like emacs-26 does with dired-delete-file (think it
;; is a bug).
- (if helm-ff-allow-recursive-deletes
- (delete-directory file 'recursive delete-by-moving-to-trash)
+ (if (or helm-ff-allow-recursive-deletes trash)
+ (delete-directory file 'recursive trash)
(pcase (helm-read-answer (format "Recursive delete of `%s'? [y,n,!,q]"
(abbreviate-file-name file))
'("y" "n" "!" "q"))
- ("y" (delete-directory file 'recursive delete-by-moving-to-trash))
+ ("y" (delete-directory file 'recursive trash))
("!" (setq helm-ff-allow-recursive-deletes t)
- (delete-directory file 'recursive delete-by-moving-to-trash))
+ (delete-directory file 'recursive trash))
("n" (cl-return 'skip))
("q" (throw 'helm-abort-delete-file
(progn
(message "Abort file deletion") (sleep-for 1))))))))
- ((and (not (file-symlink-p file))
- (file-directory-p file))
- (delete-directory file nil delete-by-moving-to-trash))
- (t (delete-file file delete-by-moving-to-trash)))
+ ((eq (nth 0 file-attrs) t)
+ (delete-directory file nil trash))
+ (t (delete-file file trash)))
(when buffers
(cl-dolist (buf buffers)
(when (y-or-n-p (format "Kill buffer %s, too? " buf))
(kill-buffer buf)))))))
(defun helm-delete-marked-files (_ignore)
+ "Delete marked files with `helm-delete-file'."
(let* ((files (helm-marked-candidates :with-wildcard t))
(len 0)
+ (trash (and delete-by-moving-to-trash
+ (null helm-current-prefix-arg)
+ (null current-prefix-arg)))
+ (prmt (if trash "Trash" "Delete"))
(old--allow-recursive-deletes helm-ff-allow-recursive-deletes))
(with-helm-display-marked-candidates
helm-marked-buffer-name
(helm-ff--count-and-collect-dups files)
- (if (not (y-or-n-p (format "Delete *%s File(s)" (length files))))
+ (if (not (y-or-n-p (format "%s *%s File(s)" prmt (length files))))
(message "(No deletions performed)")
(catch 'helm-abort-delete-file
(unwind-protect
@@ -3582,7 +3764,119 @@ Ask to kill buffers associated with that file, too."
(sleep-for 1))
(cl-incf len))))
(setq helm-ff-allow-recursive-deletes old--allow-recursive-deletes)))
- (message "%s File(s) deleted" len)))))
+ (message "%s File(s) %s" len (if trash "trashed" "deleted"))))))
+
+;;; Delete files async
+;;
+;;
+(defvar helm-ff-delete-log-file
+ (expand-file-name "helm-delete-file.log" user-emacs-directory)
+ "The file use to communicate with emacs child when deleting files async.")
+
+(defvar helm-ff--trash-flag nil)
+
+(define-minor-mode helm-ff--delete-async-modeline-mode
+ "Notify mode-line that an async process run."
+ :group 'dired-async
+ :global t
+ ;; FIXME: Handle jobs like in dired-async, needs first to allow
+ ;; naming properly processes in async, they are actually all named
+ ;; emacs and running `async-batch-invoke', so if one copy a file and
+ ;; delete another file at the same time it may clash.
+ :lighter (:eval (propertize (format " %s file(s) async ..."
+ (if helm-ff--trash-flag
+ "Trashing" "Deleting"))
+ 'face 'helm-delete-async-message))
+ (unless helm-ff--delete-async-modeline-mode
+ (let ((visible-bell t)) (ding))
+ (setq helm-ff--trash-flag nil)))
+
+(defun helm-delete-async-mode-line-message (text face &rest args)
+ "Notify end of async operation in `mode-line'."
+ (message nil)
+ (let ((mode-line-format (concat
+ " " (propertize
+ (if args
+ (apply #'format text args)
+ text)
+ 'face face))))
+ (force-mode-line-update)
+ (sit-for 3)
+ (force-mode-line-update)))
+
+(defun helm-delete-marked-files-async (_ignore)
+ "Same as `helm-delete-marked-files' but async.
+
+When a prefix arg is given, files are deleted and NOT trashed even if
+\`delete-by-moving-to-trash' is non nil.
+
+This function is not using `helm-delete-file' and BTW not asking user
+for recursive deletion of directory, be warned that directories are
+always deleted with no warnings."
+ (let* ((files (helm-marked-candidates :with-wildcard t))
+ (trash (and delete-by-moving-to-trash
+ (null helm-current-prefix-arg)
+ (null current-prefix-arg)))
+ (prmt (if trash "Trash" "Delete"))
+ (buffers (cl-loop for file in files
+ for buf = (helm-file-buffers file)
+ when buf append buf))
+ (callback (lambda (result)
+ (helm-ff--delete-async-modeline-mode -1)
+ (when (file-exists-p helm-ff-delete-log-file)
+ (display-warning 'helm
+ (with-temp-buffer
+ (insert-file-contents
+ helm-ff-delete-log-file)
+ (buffer-string))
+ :error
+ "*helm delete files*")
+ (fit-window-to-buffer (get-buffer-window
+ "*helm delete files*"))
+ (delete-file helm-ff-delete-log-file))
+ (when buffers
+ (dolist (buf buffers)
+ (let ((last-nonmenu-event t))
+ (when (y-or-n-p (format "Kill buffer %s, too? " buf))
+ (kill-buffer buf)))))
+ (run-with-timer
+ 0.1 nil
+ (lambda ()
+ (helm-delete-async-mode-line-message
+ "%s (%s/%s) file(s) async done"
+ 'helm-delete-async-message
+ (if trash "Trashing" "Deleting")
+ result (length files))))))
+ ;; Workaround emacs-26 bug with tramp see
+ ;; https://github.com/jwiegley/emacs-async/issues/80.
+ (async-quiet-switch "-q"))
+ (setq helm-ff--trash-flag trash)
+ (with-helm-display-marked-candidates
+ helm-marked-buffer-name
+ (helm-ff--count-and-collect-dups files)
+ (if (not (y-or-n-p (format "%s *%s File(s)" prmt (length files))))
+ (message "(No deletions performed)")
+ (async-start
+ `(lambda ()
+ ;; `delete-by-moving-to-trash' have to be set globally,
+ ;; using the TRASH argument of delete-file or
+ ;; delete-directory is not enough.
+ (setq delete-by-moving-to-trash ,delete-by-moving-to-trash)
+ (let ((result 0))
+ (dolist (file ',files result)
+ (condition-case err
+ (cond ((eq (nth 0 (file-attributes file)) t)
+ (delete-directory file 'recursive ,trash)
+ (setq result (1+ result)))
+ (t (delete-file file ,trash)
+ (setq result (1+ result))))
+ (error (with-temp-file ,helm-ff-delete-log-file
+ (insert (format-time-string "%x:%H:%M:%S\n"))
+ (insert (format "%s:%s\n "
+ (car err)
+ (mapconcat 'identity (cdr err) " ")))))))))
+ callback)
+ (helm-ff--delete-async-modeline-mode 1)))))
(defun helm-find-file-or-marked (candidate)
"Open file CANDIDATE or open helm marked files in separate windows.