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.

View in manual

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))))))))))