4

For a long time I've been using this function (originally made by Steve Yegge) to move files in emacs. This lets the user move the file associated with the currently open buffer to a different folder which is selected interactively. What I'd like to do now is to start using an additional version which always moves files to a specific "archive" folder rather than having the user select it. But looking at it for a while I'm not sure how to replace the user input with a default variable and have it always move files there. How could that be done?

(defun move-buffer-file (dir)
 "Moves both current buffer and file it's visiting to DIR." 
 (interactive "DNew directory: ")
 (let* ((name (buffer-name))
     (filename (buffer-file-name))
     (dir
     (if (string-match dir "\\(?:/\\|\\\\)$")
     (substring dir 0 -1) dir))
     (newname (concat dir "/" name)))

 (if (not filename)
    (message "Buffer '%s' is not visiting a file!" name)
   (progn   
     (copy-file filename newname 1)     
     (delete-file filename)     
     (set-visited-file-name newname)    
     (set-buffer-modified-p nil)    t))))
1
  • 1
    Don't use (if (string-match dir "\\(?:/\\|\\\\)$") (substring dir 0 -1) dir). Use just (directory-file-name dir). Don't use (concat dir "/" name). Use (expand-file-name name dir). Don't use general string operations on file names when you can use file-name operations on file names. They know about file names on different platforms etc.
    – Drew
    Nov 10, 2018 at 22:23

2 Answers 2

1

There's a bunch of ways you could refactor that function. To use the manner you describe, move the dir parameter to your let* block and refer to an external variable:

(defvar my/move-buffer-file-default-target "~/archive-folder")

(defun move-buffer-file ()
  "Moves both current buffer and file it's visiting to DIR."
  (let* ((dir my/move-buffer-file-default-target)
         (name (buffer-name))
         (filename (buffer-file-name))
         (dir
          (if (string-match dir "\\(?:/\\|\\\\)$")
              (substring dir 0 -1) dir))
         (newname (concat dir "/" name)))
    (if (not filename)
        (message "Buffer '%s' is not visiting a file!" name)
      (progn
        (copy-file filename newname 1)
        (delete-file filename)
        (set-visited-file-name newname)
        (set-buffer-modified-p nil)
        t))))

You could alternately keep the dir parameter in place and replace your existing interactive call with the following to use that target as a default (forcing the user to press 'Return' in usual use).

(interactive
 (list
  (read-directory-name
   "Target directory: "
   nil nil nil
   (expand-file-name my/move-buffer-file-default-target))))
1
  • 1
    This works great, thanks! Requiring a return press is actually better than my original idea, I'll use that method. :)
    – Var87
    Oct 19, 2017 at 21:22
1

I think this is all you need. write-file takes care of the association between the buffer and the file. Uses file-name operations (recommended), not general string operations.

(defvar my-archive-dir "~/foo/" "My archive directory.")

(defun foo ()
  "Move visited file to another directory, for which you're prompted.
Directory defaults to the value of `my-archive-dir'."
  (interactive)
  (let ((old  (or (buffer-file-name)  (user-error "Not visiting a file")))
        (dir  (read-directory-name "Move to: " my-archive-dir)))
    (write-file (expand-file-name (file-name-nondirectory old) dir) t)
    (delete-file old)))

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.