Bisakah saya membuat direktori yang tidak ada saat membuat file baru di emacs?

29

Dalam emacs, saya membuat file dengan mengunjunginya dengan Cx Cf. Katakanlah saya ingin membuat /home/myself/new_directory/file.txt.

Jika direktori baru belum ada, apakah ada cara untuk membuatnya dibuat selama pembuatan file.txttanpa langkah tambahan? (Saya sedang memikirkan sesuatu seperti menggunakan -pflag mkdirdi Linux.)

Saya merasa ada keystroke yang berbeda daripada Cx Cf yang bisa melakukan ini, tapi saya tidak ingat apa itu.

Jim Hunziker
sumber
Tidak ada yang setara dengan emacs untuk memberikan perintah saat mengedit ?? in vi you :!mkdir -p ~/new_dir/to/some/crazy/path
can
3
@DaveParillo: Tentu saja ada, M-!misalnya.
Nikana Reklawyks
Saya menggunakan plugin prelude ( github.com/bbatsov/prelude ). Setiap kali saya membuat file seperti di atas, ia meminta saya dengan "Buat direktori ...". Saya cukup memilih "y". Selanjutnya, ia bertanya kepada saya "File tidak ada, buat? (Y atau n)". Saya pilih y, yang membuat file baru. Ketika saya menyimpan file itu membuat file dengan informasi di atas.
Pramod

Jawaban:

25

Anda juga dapat menyarankan fungsi find-fileuntuk membuat direktori yang diperlukan secara transparan.

(defadvice find-file (before make-directory-maybe (filename &optional wildcards) activate)
  "Create parent directory if not exists while visiting file."
  (unless (file-exists-p filename)
    (let ((dir (file-name-directory filename)))
      (unless (file-exists-p dir)
        (make-directory dir)))))

Cukup letakkan ini di .emacstempat Anda dan gunakan C-x C-fseperti biasa.

Török Gábor
sumber
2
Solusi luar biasa. Saya suka bagaimana memperbaiki hal-hal kecil dapat membuka seluruh dunia baru petunjuk untuk memberikan emacs untuk melakukan hal-hal yang lebih baik (ya, saya tidak tahu defadvicesebelumnya).
Nikana Reklawyks
18

Ketika saya memberikan nama path dengan komponen yang tidak ada, find-file(yaitu Cx Cf ), memberi saya pesan tambahan yang mengatakan

Gunakan Mx make-directory RET RET untuk membuat direktori dan orang tuanya

Karena file tidak dibuat sampai Anda pertama kali menyimpan buffer, Anda dapat menjalankannya make-directorytepat setelah buffer baru muncul atau Anda dapat melakukannya di lain waktu sebelum Anda perlu menyimpan file tersebut. Kemudian dari buffer yang membutuhkan direktori, ketik Mx make-directory RET RET (itu akan meminta direktori untuk membuat (default berasal dari pathname buffer); RET kedua adalah untuk menerima default).

Chris Johnsen
sumber
14

The Ido modus memberikan ido-find-fileyang merupakan pengganti dari find-filedan memberi Anda lebih banyak fitur. Sebagai contoh, ini memungkinkan Anda untuk membuat direktori baru sementara Anda membuka file.

  • Ketik C-x C-fseperti biasa (yang dipetakan ulang ke ido-find-file),

  • menyediakan jalur yang tidak ada,

  • tekan M-myang akan meminta direktori baru untuk dibuat,

  • dan kemudian tentukan nama file untuk dikunjungi di direktori yang baru dibuat.

Török Gábor
sumber
Saya tidak mengerti: mengapa harus menekan M-mdi beberapa titik, dan C-x C-fsama sekali jika itu tidak membuat semuanya secara otomatis? Jika diminta untuk membuat direktori, M-! mkdiratau dired akan melakukan pekerjaan yang adil juga ...
Nikana Reklawyks
Apakah ada cara untuk secara ido-find-fileotomatis membuat direktori? Atau bahkan lebih baik, tanyakan apakah saya ingin membuatnya? Saya mencoba menggunakan saran dalam jawaban Török Gábor, tapi saya tidak tahu fungsi mana yang akan diterapkan (karena ido tidak menelepon find-filelangsung.
Troy Daniels
1

Solusi saya datang dengan bonus: jika Anda membunuh buffer tanpa menyimpannya, Emacs akan menawarkan untuk menghapus direktori kosong yang dibuat (tetapi hanya jika mereka tidak ada sebelum Anda dipanggil find-file):

;; Automatically create any nonexistent parent directories when
;; finding a file. If the buffer for the new file is killed without
;; being saved, then offer to delete the created directory or
;; directories.

(defun radian--advice-find-file-automatically-create-directory
    (original-function filename &rest args)
  "Automatically create and delete parent directories of files.
This is an `:override' advice for `find-file' and friends. It
automatically creates the parent directory (or directories) of
the file being visited, if necessary. It also sets a buffer-local
variable so that the user will be prompted to delete the newly
created directories if they kill the buffer without saving it."
  ;; The variable `dirs-to-delete' is a list of the directories that
  ;; will be automatically created by `make-directory'. We will want
  ;; to offer to delete these directories if the user kills the buffer
  ;; without saving it.
  (let ((dirs-to-delete ()))
    ;; If the file already exists, we don't need to worry about
    ;; creating any directories.
    (unless (file-exists-p filename)
      ;; It's easy to figure out how to invoke `make-directory',
      ;; because it will automatically create all parent directories.
      ;; We just need to ask for the directory immediately containing
      ;; the file to be created.
      (let* ((dir-to-create (file-name-directory filename))
             ;; However, to find the exact set of directories that
             ;; might need to be deleted afterward, we need to iterate
             ;; upward through the directory tree until we find a
             ;; directory that already exists, starting at the
             ;; directory containing the new file.
             (current-dir dir-to-create))
        ;; If the directory containing the new file already exists,
        ;; nothing needs to be created, and therefore nothing needs to
        ;; be destroyed, either.
        (while (not (file-exists-p current-dir))
          ;; Otherwise, we'll add that directory onto the list of
          ;; directories that are going to be created.
          (push current-dir dirs-to-delete)
          ;; Now we iterate upwards one directory. The
          ;; `directory-file-name' function removes the trailing slash
          ;; of the current directory, so that it is viewed as a file,
          ;; and then the `file-name-directory' function returns the
          ;; directory component in that path (which means the parent
          ;; directory).
          (setq current-dir (file-name-directory
                             (directory-file-name current-dir))))
        ;; Only bother trying to create a directory if one does not
        ;; already exist.
        (unless (file-exists-p dir-to-create)
          ;; Make the necessary directory and its parents.
          (make-directory dir-to-create 'parents))))
    ;; Call the original `find-file', now that the directory
    ;; containing the file to found exists. We make sure to preserve
    ;; the return value, so as not to mess up any commands relying on
    ;; it.
    (prog1 (apply original-function filename args)
      ;; If there are directories we want to offer to delete later, we
      ;; have more to do.
      (when dirs-to-delete
        ;; Since we already called `find-file', we're now in the buffer
        ;; for the new file. That means we can transfer the list of
        ;; directories to possibly delete later into a buffer-local
        ;; variable. But we pushed new entries onto the beginning of
        ;; `dirs-to-delete', so now we have to reverse it (in order to
        ;; later offer to delete directories from innermost to
        ;; outermost).
        (setq-local radian--dirs-to-delete (reverse dirs-to-delete))
        ;; Now we add a buffer-local hook to offer to delete those
        ;; directories when the buffer is killed, but only if it's
        ;; appropriate to do so (for instance, only if the directories
        ;; still exist and the file still doesn't exist).
        (add-hook 'kill-buffer-hook
                  #'radian--kill-buffer-delete-directory-if-appropriate
                  'append 'local)
        ;; The above hook removes itself when it is run, but that will
        ;; only happen when the buffer is killed (which might never
        ;; happen). Just for cleanliness, we automatically remove it
        ;; when the buffer is saved. This hook also removes itself when
        ;; run, in addition to removing the above hook.
        (add-hook 'after-save-hook
                  #'radian--remove-kill-buffer-delete-directory-hook
                  'append 'local)))))

;; Add the advice that we just defined.
(advice-add #'find-file :around
            #'radian--advice-find-file-automatically-create-directory)

;; Also enable it for `find-alternate-file' (C-x C-v).
(advice-add #'find-alternate-file :around
            #'radian--advice-find-file-automatically-create-directory)

;; Also enable it for `write-file' (C-x C-w).
(advice-add #'write-file :around
            #'radian--advice-find-file-automatically-create-directory)

(defun radian--kill-buffer-delete-directory-if-appropriate ()
  "Delete parent directories if appropriate.
This is a function for `kill-buffer-hook'. If
`radian--advice-find-file-automatically-create-directory' created
the directory containing the file for the current buffer
automatically, then offer to delete it. Otherwise, do nothing.
Also clean up related hooks."
  (when (and
         ;; Stop if there aren't any directories to delete (shouldn't
         ;; happen).
         radian--dirs-to-delete
         ;; Stop if `radian--dirs-to-delete' somehow got set to
         ;; something other than a list (shouldn't happen).
         (listp radian--dirs-to-delete)
         ;; Stop if the current buffer doesn't represent a
         ;; file (shouldn't happen).
         buffer-file-name
         ;; Stop if the buffer has been saved, so that the file
         ;; actually exists now. This might happen if the buffer were
         ;; saved without `after-save-hook' running, or if the
         ;; `find-file'-like function called was `write-file'.
         (not (file-exists-p buffer-file-name)))
    (cl-dolist (dir-to-delete radian--dirs-to-delete)
      ;; Ignore any directories that no longer exist or are malformed.
      ;; We don't return immediately if there's a nonexistent
      ;; directory, because it might still be useful to offer to
      ;; delete other (parent) directories that should be deleted. But
      ;; this is an edge case.
      (when (and (stringp dir-to-delete)
                 (file-exists-p dir-to-delete))
        ;; Only delete a directory if the user is OK with it.
        (if (y-or-n-p (format "Also delete directory `%s'? "
                              ;; The `directory-file-name' function
                              ;; removes the trailing slash.
                              (directory-file-name dir-to-delete)))
            (delete-directory dir-to-delete)
          ;; If the user doesn't want to delete a directory, then they
          ;; obviously don't want to delete any of its parent
          ;; directories, either.
          (cl-return)))))
  ;; It shouldn't be necessary to remove this hook, since the buffer
  ;; is getting killed anyway, but just in case...
  (radian--remove-kill-buffer-delete-directory-hook))

(defun radian--remove-kill-buffer-delete-directory-hook ()
  "Clean up directory-deletion hooks, if necessary.
This is a function for `after-save-hook'. Remove
`radian--kill-buffer-delete-directory-if-appropriate' from
`kill-buffer-hook', and also remove this function from
`after-save-hook'."
  (remove-hook 'kill-buffer-hook
               #'radian--kill-buffer-delete-directory-if-appropriate
               'local)
  (remove-hook 'after-save-hook
               #'radian--remove-kill-buffer-delete-directory-hook
               'local))

Versi kanonik di sini .

Radon Rosborough
sumber
0

Selain saran @Chris tentang direktori-make Mx, Anda dapat menulis fungsi elisp pendek yang akan melakukan find-file dan kemudian membuat-direktori ... Anda dapat mencoba ini:

(defun bp-find-file(filename &optional wildcards)
  "finds a file, and then creates the folder if it doesn't exist"

  (interactive (find-file-read-args "Find file: " nil))
  (let ((value (find-file-noselect filename nil nil wildcards)))
    (if (listp value)
    (mapcar 'switch-to-buffer (nreverse value))
      (switch-to-buffer value)))
  (when (not (file-exists-p default-directory))
       (message (format "Creating  %s" default-directory))
       (make-directory default-directory t))
  )

Itu tidak cantik, dan itu mem-flash "itu direktori make MX ...." sebelum mengatakan "Membuat direktori ..." tetapi jika tidak ada yang lain, itu harus memberi Anda permulaan.

Brian Postow
sumber
2
Dalam hal pendekatan ini, lebih baik memberi saran find-filefungsi asli daripada mendefinisikan yang baru sehingga fungsi lain yang menelepon find-filelangsung bahkan dapat mengambil manfaat dari perilaku yang dimodifikasi.
Török Gábor
tetapi find-file tampaknya tidak mengambil argumen yang dapat memerintahkannya untuk melakukan ini ... Kecuali ada sesuatu yang berguna yang dapat Anda lakukan di find-file-hooks ...
Brian Postow
Saya maksudkan ini: superuser.com/questions/131538/…
Török Gábor
0
(make-directory "~/bunch/of/dirs" t)

Jika direktori Anda sudah ada, itu hanya akan memunculkan peringatan.

Dari manual ( C-h f make-directory RET):

make-directory adalah fungsi Lisp kompilasi interaktif.

(make-directory DIR &optional PARENTS)

Buat direktori DIRdan secara opsional setiap dir induk tidak ada. Jika DIRsudah ada sebagai direktori, isyarat kesalahan, kecuali jika PARENTStidak nol.

Secara interaktif, pilihan direktori default untuk dibuat adalah direktori default buffer saat ini. Itu berguna ketika Anda telah mengunjungi file di direktori tidak ada.

Noninteraktif, argumen kedua (opsional) PARENTS, jika bukan nol, mengatakan apakah akan membuat direktori induk yang tidak ada. Secara interaktif, ini terjadi secara default.

Jika membuat direktori atau direktori gagal, kesalahan akan dimunculkan.

yPhil
sumber