Puis-je créer des répertoires qui n'existent pas lors de la création d'un nouveau fichier dans emacs?

29

Dans emacs, je crée un fichier en le visitant avec Cx Cf. Disons que j'aimerais créer /home/myself/new_directory/file.txt.

Si new_directory n'existe pas encore, existe-t-il un moyen de le faire créer lors de la création de file.txtsans étapes supplémentaires? (Je pense à quelque chose comme utiliser le -pdrapeau mkdirsous Linux.)

J'ai l'impression qu'il y a une frappe différente au lieu de Cx Cf qui peut le faire, mais je ne me souviens pas de quoi il s'agit.

Jim Hunziker
la source
Il n'y a pas d'équivalent dans emacs pour lancer une commande lors de l'édition ?? dans vi vous pourriez:!mkdir -p ~/new_dir/to/some/crazy/path
DaveParillo
3
@DaveParillo: Bien sûr qu'il y en a, M-!par exemple.
Nikana Reklawyks
J'utilise le plugin prelude ( github.com/bbatsov/prelude ). Chaque fois que je crée des fichiers comme ci-dessus, il m'invite avec "Créer un répertoire ...". Je peux simplement sélectionner "y". Ensuite, il me demande "Le fichier n'existe pas, créez? (Y ou n)". Je sélectionne y, ce qui crée un nouveau fichier. Lorsque j'enregistre le fichier, il crée le fichier avec les informations ci-dessus.
Pramod

Réponses:

25

Vous pouvez également conseiller à la fonction find-filede créer de manière transparente les répertoires nécessaires.

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

Mettez-le simplement .emacsquelque part et utilisez-le C-x C-fcomme d'habitude.

Török Gábor
la source
2
Magnifique solution. J'adore comment l'amélioration de petites choses peut ouvrir un tout nouveau monde d'indices pour donner à emacs de mieux faire les choses (ouais, je ne le savais pas defadviceauparavant).
Nikana Reklawyks
18

Lorsque je fournis un nom de chemin avec un composant inexistant find-file(par exemple Cx Cf ), il me donne un message supplémentaire qui dit

Utilisez Mx make-directory RET RET pour créer le répertoire et ses parents

Étant donné que le fichier n'est pas créé tant que vous n'avez pas enregistré le tampon pour la première fois, vous pouvez soit l'exécuter make-directoryjuste après la création de votre nouveau tampon, soit le faire à tout autre moment avant d'enregistrer le fichier. Ensuite, à partir du tampon qui a besoin d'un répertoire, tapez Mx make-directory RET RET (il demandera au répertoire de créer (la valeur par défaut est dérivée du nom de chemin du tampon); la seconde RET consiste à accepter la valeur par défaut).

Chris Johnsen
la source
14

Le mode Ido fournit ido-find-fileun remplacement find-fileet vous offre beaucoup plus de fonctionnalités. Par exemple, il vous permet de créer un nouveau répertoire pendant que vous ouvrez le fichier.

  • Tapez C-x C-fcomme d'habitude (qui est remappé ido-find-file),

  • fournir le chemin inexistant,

  • appuyez sur M-mce qui vous invite à créer le nouveau répertoire,

  • puis spécifiez le nom de fichier à visiter dans le répertoire nouvellement créé.

Török Gábor
la source
Je ne comprends pas: pourquoi appuyer M-mà un moment donné, et C-x C-fdu tout si cela ne crée pas tout automatiquement? Si être invité à créer le répertoire, M-! mkdirou dired fera également du bon travail…
Nikana Reklawyks
Existe-t-il un moyen de ido-find-filecréer automatiquement le répertoire? Ou encore mieux, demandez-moi si je veux le créer? J'ai essayé d'utiliser les conseils de la réponse de Török Gábor, mais je n'ai pas pu déterminer à quelle fonction l'appliquer (car ido n'appelle pas find-filedirectement.
Troy Daniels
1

Ma solution est livrée avec un bonus: si vous tuez le tampon sans l'enregistrer, Emacs vous proposera de supprimer les répertoires vides qui ont été créés (mais seulement s'ils n'existaient pas avant que vous les invoquiez 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))

Version canonique ici .

Radon Rosborough
la source
0

En plus de la suggestion de @Chris de Mx make-directory, vous pouvez écrire une courte fonction elisp qui fera find-file puis make-directory ... Vous pouvez essayer ceci:

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

Ce n'est pas joli, et il affiche le "it MX make-directory ...." avant de dire "Création d'un répertoire ..." mais si rien d'autre, cela devrait vous donner un début.

Brian Postow
la source
2
Dans le cas de cette approche, il est préférable de conseiller la find-filefonction d' origine au lieu d'en définir une nouvelle afin que d'autres fonctions appelant find-filedirectement puissent même bénéficier du comportement modifié.
Török Gábor
mais find-file ne semble pas prendre d'arguments qui peuvent lui dire de faire ça ... Sauf s'il y a quelque chose d'utile que vous pouvez faire dans find-file-hooks ...
Brian Postow
Je voulais dire ceci: superuser.com/questions/131538/…
Török Gábor
0
(make-directory "~/bunch/of/dirs" t)

Si vos répertoires existent déjà, un simple avertissement s'affichera.

Dans le C-h f make-directory RETmanuel ( ):

make-directory est une fonction Lisp compilée interactive.

(make-directory DIR &optional PARENTS)

Créez le répertoire DIRet éventuellement tout répertoire parent inexistant. S'il DIRexiste déjà en tant que répertoire, signalez une erreur, sauf s'il PARENTSest non nul.

De manière interactive, le choix par défaut du répertoire à créer est le répertoire par défaut du tampon actuel. Cela est utile lorsque vous avez visité un fichier dans un répertoire inexistant.

De manière non interactive, le deuxième argument (facultatif) PARENTS, s'il n'est pas nul, indique s'il faut créer des répertoires parents qui n'existent pas. De manière interactive, cela se produit par défaut.

Si la création du ou des répertoires échoue, une erreur sera générée.

yPhil
la source