2

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))))
  • 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 '18 at 22:23
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
    This works great, thanks! Requiring a return press is actually better than my original idea, I'll use that method. :) – Var87 Oct 19 '17 at 21:22
0

I think this is all you need. write-file takes care of the association between the buffer and the file. Uses file operations, 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-default-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, privacy policy and cookie policy

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