Function: move-file-to-trash
move-file-to-trash is an interactive and byte-compiled function
defined in files.el.gz.
Signature
(move-file-to-trash FILENAME)
Documentation
Move the file (or directory) named FILENAME to the trash.
When delete-by-moving-to-trash is non-nil, this function is
called by delete-file and delete-directory instead of
deleting files outright.
If the function system-move-file-to-trash is defined, call it
with FILENAME as an argument.
Otherwise, if trash-directory is non-nil, move FILENAME to that
directory.
Otherwise, trash FILENAME using the freedesktop.org conventions,
like the GNOME, KDE and XFCE desktop environments. Emacs moves
files only to "home trash", ignoring per-volume trashcans.
Probably introduced at or before Emacs version 23.2.
Key Bindings
Source Code
;; Defined in /usr/src/emacs/lisp/files.el.gz
(defun move-file-to-trash (filename)
"Move the file (or directory) named FILENAME to the trash.
When `delete-by-moving-to-trash' is non-nil, this function is
called by `delete-file' and `delete-directory' instead of
deleting files outright.
If the function `system-move-file-to-trash' is defined, call it
with FILENAME as an argument.
Otherwise, if `trash-directory' is non-nil, move FILENAME to that
directory.
Otherwise, trash FILENAME using the freedesktop.org conventions,
like the GNOME, KDE and XFCE desktop environments. Emacs moves
files only to \"home trash\", ignoring per-volume trashcans."
(interactive "fMove file to trash: ")
;; If `system-move-file-to-trash' is defined, use it.
(cond ((fboundp 'system-move-file-to-trash)
(system-move-file-to-trash filename))
(trash-directory
;; If `trash-directory' is non-nil, move the file there.
(let* ((trash-dir (expand-file-name trash-directory))
(fn (directory-file-name (expand-file-name filename)))
(new-fn (concat (file-name-as-directory trash-dir)
(file-name-nondirectory fn))))
;; We can't trash a parent directory of trash-directory.
(if (string-prefix-p fn trash-dir)
(error "Trash directory `%s' is a subdirectory of `%s'"
trash-dir filename))
(unless (file-directory-p trash-dir)
(make-directory trash-dir t))
;; Ensure that the trashed file-name is unique.
(if (file-attributes new-fn)
(let ((version-control t)
(backup-directory-alist nil))
(setq new-fn (car (find-backup-file-name new-fn)))))
(let (delete-by-moving-to-trash)
(rename-file fn new-fn))))
;; Otherwise, use the freedesktop.org method, as specified at
;; https://freedesktop.org/wiki/Specifications/trash-spec
(t
(let* ((xdg-data-dir
(directory-file-name
(expand-file-name "Trash"
(or (getenv "XDG_DATA_HOME")
"~/.local/share"))))
(trash-files-dir (expand-file-name "files" xdg-data-dir))
(trash-info-dir (expand-file-name "info" xdg-data-dir))
(fn (directory-file-name (expand-file-name filename))))
;; Check if we have permissions to delete.
(unless (file-writable-p (directory-file-name
(file-name-directory fn)))
(error "Cannot move %s to trash: Permission denied" filename))
;; The trashed file cannot be the trash dir or its parent.
(if (string-prefix-p fn trash-files-dir)
(error "The trash directory %s is a subdirectory of %s"
trash-files-dir filename))
(if (string-prefix-p fn trash-info-dir)
(error "The trash directory %s is a subdirectory of %s"
trash-info-dir filename))
;; Ensure that the trash directory exists; otherwise, create it.
(with-file-modes #o700
(unless (file-exists-p trash-files-dir)
(make-directory trash-files-dir t))
(unless (file-exists-p trash-info-dir)
(make-directory trash-info-dir t)))
;; Try to move to trash with .trashinfo undo information
(save-excursion
(with-temp-buffer
(set-buffer-file-coding-system 'utf-8-unix)
(insert "[Trash Info]\nPath=")
;; Perform url-encoding on FN. For compatibility with
;; other programs (e.g. XFCE Thunar), allow literal "/"
;; for path separators.
(unless (boundp 'trash--hexify-table)
(setq trash--hexify-table (make-vector 256 nil))
(let ((unreserved-chars
(list ?/ ?a ?b ?c ?d ?e ?f ?g ?h ?i ?j ?k ?l ?m
?n ?o ?p ?q ?r ?s ?t ?u ?v ?w ?x ?y ?z ?A
?B ?C ?D ?E ?F ?G ?H ?I ?J ?K ?L ?M ?N ?O
?P ?Q ?R ?S ?T ?U ?V ?W ?X ?Y ?Z ?0 ?1 ?2
?3 ?4 ?5 ?6 ?7 ?8 ?9 ?- ?_ ?. ?! ?~ ?* ?'
?\( ?\))))
(dotimes (byte 256)
(aset trash--hexify-table byte
(if (memq byte unreserved-chars)
(char-to-string byte)
(format "%%%02x" byte))))))
(mapc (lambda (byte)
(insert (aref trash--hexify-table byte)))
(if (multibyte-string-p fn)
(encode-coding-string fn 'utf-8)
fn))
(insert "\nDeletionDate="
(format-time-string "%Y-%m-%dT%T")
"\n")
;; Make a .trashinfo file. Use O_EXCL, as per trash-spec 1.0.
(let* ((files-base (file-name-nondirectory fn))
(is-directory (and (file-directory-p fn)
(not (file-symlink-p fn))))
(overwrite nil)
info-fn)
;; We're checking further down whether the info file
;; exists, but the file name may exist in the trash
;; directory even if there is no info file for it.
(when (file-attributes
(file-name-concat trash-files-dir files-base))
(setq overwrite t
files-base (file-name-nondirectory
(make-temp-file
(file-name-concat
trash-files-dir files-base)
is-directory))))
(setq info-fn (file-name-concat
trash-info-dir
(concat files-base ".trashinfo")))
;; Re-check the existence (sort of).
(condition-case nil
(write-region nil nil info-fn nil 'quiet info-fn 'excl)
(file-already-exists
;; Uniquify new-fn. Some file managers do not
;; like Emacs-style backup file names. E.g.:
;; https://bugs.kde.org/170956
(setq info-fn (make-temp-file
(file-name-concat trash-info-dir files-base)
nil ".trashinfo"))
(setq files-base (substring (file-name-nondirectory info-fn)
0 (- (length ".trashinfo"))))
(write-region nil nil info-fn nil 'quiet info-fn)))
;; Finally, try to move the item to the trashcan. If
;; it's a file, just move it. Things are more
;; complicated for directories. If the target
;; directory already exists (due to uniquification)
;; and the trash directory is in a different
;; filesystem, rename-file will error out, even when
;; 'overwrite' is non-nil. Rather than worry about
;; whether we're crossing filesystems, just check if
;; we've moving a directory and the target directory
;; already exists. That handles both the
;; same-filesystem and cross-filesystem cases.
(let ((delete-by-moving-to-trash nil)
(new-fn (file-name-concat trash-files-dir files-base)))
(if (or (not is-directory)
(not (file-attributes new-fn)))
(rename-file fn new-fn overwrite)
(copy-directory fn
(file-name-as-directory new-fn)
t nil t)
(delete-directory fn t))))))))))