¿Puedo crear directorios que no existen al crear un nuevo archivo en emacs?


29

En emacs, creo un archivo al visitarlo con Cx Cf. Digamos que me gustaría crear /home/myself/new_directory/file.txt.

Si new_directory aún no existe, ¿hay alguna forma de que se cree durante la creación file.txtsin ningún paso adicional? (Estoy pensando en algo como usar la -pbandera mkdiren Linux).

Siento que hay una combinación de teclas diferente en lugar de Cx Cf que puede hacer esto, pero no puedo recordar qué es.


¿No hay equivalente en emacs para lanzar un comando durante la edición? en vi que podrías:!mkdir -p ~/new_dir/to/some/crazy/path
DaveParillo

3
@DaveParillo: Por supuesto que sí, M-!por ejemplo.
Nikana Reklawyks

Utilizo el complemento prelude ( github.com/bbatsov/prelude ). Cada vez que creo archivos como el anterior, aparece el mensaje "Crear directorio ...". Simplemente puedo seleccionar "y". A continuación, me pregunta "¿El archivo no existe, crear? (Sí o no)". Selecciono y, que crea un nuevo archivo. Cuando guardo el archivo, crea el archivo con la información anterior.
Pramod

Respuestas:


25

También puede aconsejar a la función find-fileque cree de forma transparente los directorios necesarios.

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

Simplemente ponga esto en su .emacslugar y úselo C-x C-fcomo siempre.


2
Maravillosa solución Me encanta cómo mejorar las cosas pequeñas puede abrir un mundo completamente nuevo de pistas para dar a los emacs mejores cosas (sí, no lo sabía defadviceantes).
Nikana Reklawyks

18

Cuando proporciono un nombre de ruta con un componente inexistente find-file(es decir, Cx Cf ), me da un mensaje adicional que dice

Use Mx make-directory RET RET para crear el directorio y sus padres

Dado que el archivo no se crea hasta que guarde el búfer por primera vez, puede ejecutarlo make-directoryinmediatamente después de que aparezca su nuevo búfer o puede hacerlo en cualquier otro momento antes de que necesite guardar el archivo. Luego, desde el búfer que necesita un directorio, escriba Mx make-directory RET RET (solicitará que se cree el directorio (el valor predeterminado se deriva del nombre de ruta del búfer); el segundo RET es aceptar el valor predeterminado).


14

El modo Ido proporciona ido-find-fileque es un reemplazo find-filey le ofrece muchas más funciones. Por ejemplo, le permite crear un nuevo directorio mientras abre el archivo.

  • Escriba C-x C-fcomo de costumbre (que se reasigna a ido-find-file),

  • proporcionar el camino inexistente,

  • presione M-mque le pedirá que cree el nuevo directorio,

  • y luego especifique el nombre del archivo para visitar en el directorio recién creado.


No lo entiendo: ¿por qué presionar M-men algún momento, y C-x C-fen absoluto si eso no crea todo automáticamente? Si se le solicita que cree el directorio, M-! mkdiro dired también hará un trabajo justo ...
Nikana Reklawyks

¿Hay alguna manera de ido-find-filecrear automáticamente el directorio? O mejor aún, pregúntame si quiero crearlo. Traté de usar el consejo en la respuesta de Török Gábor, pero no pude averiguar a qué función aplicarlo (ya que ido no llama find-filedirectamente.)
Troy Daniels

1

Mi solución viene con una ventaja: si elimina el búfer sin guardarlo, Emacs le ofrecerá eliminar esos directorios vacíos que se crearon (pero solo si no existían antes de que lo invoque 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ón canónica aquí .


0

Además de la sugerencia de @Chris de Mx make-directory, puede escribir una función corta de elisp que hará find-file y luego make-directory ... Puede intentar esto:

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

No es bonito, y muestra "it MX make-directory ...." antes de decir "Creando directorio ...", pero si nada más, debería comenzar.


2
En este caso, es mejor aconsejar la find-filefunción original en lugar de definir una nueva para que otras funciones que llamen find-filedirectamente puedan incluso beneficiarse del comportamiento modificado.
Török Gábor

pero find-file no parece tomar ningún argumento que pueda decirle que haga esto ... A menos que haya algo útil que pueda hacer en find-file-hooks ...
Brian Postow


0
(make-directory "~/bunch/of/dirs" t)

Si sus directorios ya existen, solo generará una advertencia.

Del C-h f make-directory RETmanual ( ):

make-directory es una función interactiva de Lisp compilada.

(make-directory DIR &optional PARENTS)

Cree el directorio DIRy, opcionalmente, cualquier directorio padre no existente. Si DIRya existe como directorio, señale un error, a menos PARENTSque no sea nulo.

Interactivamente, la opción predeterminada del directorio para crear es el directorio predeterminado del búfer actual. Eso es útil cuando ha visitado un archivo en un directorio inexistente.

No interactivamente, el segundo argumento (opcional) PARENTS, si no es nulo, dice si se deben crear directorios principales que no existen. Interactivamente, esto sucede por defecto.

Si la creación del directorio o directorios falla, se generará un error.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.