crux icon indicating copy to clipboard operation
crux copied to clipboard

Enhance crux-rename-file-and-buffer to accept new-name as an argument

Open kisp opened this issue 9 months ago • 2 comments

Hi there!

I really like the crux-rename-file-and-buffer function and have been using it interactively via M-x. Today, I thought about calling it from Elisp and noticed that I can't pass in a new-name argument.

Would it be possible to change the function so I can provide a filename directly when calling it from Elisp? That would make it even more useful!

Thanks for considering this!

kisp avatar May 18 '25 05:05 kisp

Hey everyone!

I wanted to share my use case for the crux-rename-file-and-buffer function. I've patched it to accept a new-name argument, which has been super helpful for me when renaming Ruby files based on the class/module inside.

Here's the function I came up with, my/projectile-rails-rename-file-to-class. It automatically generates snake_case filenames:

(defun my/projectile-rails-rename-file-to-class ()
    "Rename the buffer and file based on the class/module name, converting CamelCase to snake_case."
    (interactive)
    (require 'string-inflection)
    (require 'crux)
    (let* ((class-name (save-excursion
                         (goto-char (point-min))
                         (if (re-search-forward "class \\([A-Za-z0-9_]+\\)" nil t)
                             (match-string 1)
                           (if (re-search-forward "module \\([A-Za-z0-9_]+\\)" nil t)
                               (match-string 1)
                             nil))))
           (snake-case (string-inflection-underscore-function class-name))
           (new-name (format "%s.rb" snake-case))
           (new-path (expand-file-name new-name (file-name-directory buffer-file-name))))
      (cond
       ((null class-name)
        (message "No class or module found"))
       ((string= (buffer-name) new-name)
        (message "Buffer is already named: %s" new-name))
       ((file-exists-p new-path)
        (error "A file named '%s' already exists." new-path))
       (t
        (message "New filename should be: %s" new-path)
        (crux-rename-file-and-buffer new-path)))))

I'd love to hear your thoughts or any feedback! Thanks!

kisp avatar May 18 '25 05:05 kisp

Here’s the patched version of crux-rename-file-and-buffer that lets you provide a new-name argument directly:

(defun crux-rename-file-and-buffer (&optional new-name)
    "Rename current buffer and if the buffer is visiting a file, rename it too.
If NEW-NAME is provided, use it as the new filename. Otherwise, prompt for it."
    (interactive (list (read-file-name "New name: " (file-name-directory (buffer-file-name)) nil 'confirm)))
    (let ((filename (buffer-file-name)))
      (when filename
        (let ((containing-dir (file-name-directory new-name)))
          ;; Make sure the current buffer is saved and backed by some file
          (when (or (buffer-modified-p) (not (file-exists-p filename)))
            (if (y-or-n-p "Can't move file before saving it. Would you like to save it now?")
                (save-buffer)))
          (if (get-file-buffer new-name)
              (message "There already exists a buffer named %s" new-name)
            (progn
              (make-directory containing-dir t)
              (cond
               ((vc-backend filename)
                ;; vc-rename-file seems not able to cope with remote filenames?
                (let ((vc-filename (if (tramp-tramp-file-p filename) (tramp-file-local-name filename) filename))
                      (vc-new-name (if (tramp-tramp-file-p new-name) (tramp-file-local-name filename) new-name)))
                  (vc-rename-file vc-filename vc-new-name)))
               (t
                (rename-file filename new-name t)
                (set-visited-file-name new-name t t)))))))))

I think this makes it a lot more flexible! Should I go ahead and submit a PR for this?

Let me know what you think!

kisp avatar May 18 '25 05:05 kisp