Posso creare directory che non esistono durante la creazione di un nuovo file in emacs?


29

In emacs, creo un file visitandolo con Cx Cf. Diciamo che mi piacerebbe creare /home/myself/new_directory/file.txt.

Se new_directory non esiste ancora, c'è un modo per crearlo durante la creazione di file.txtsenza ulteriori passaggi? (Sto pensando a qualcosa come usare la -pbandiera su mkdirin Linux.)

Sento che c'è una combinazione di tasti diversa da Cx Cf che può farlo, ma non ricordo di cosa si tratti.


Non c'è equivalente in emacs per lanciare un comando durante la modifica ?? in vi potresti:!mkdir -p ~/new_dir/to/some/crazy/path
DaveParillo

3
@DaveParillo: Certo che c'è, M-!per esempio.
Nikana Reklawyks,

Uso il plug-in prelude ( github.com/bbatsov/prelude ). Ogni volta che creo file come sopra, mi viene richiesto "Crea directory ...". Posso semplicemente selezionare "y". Successivamente, mi chiede "Il file non esiste, creare? (Yo n)". Seleziono y, che crea un nuovo file. Quando salvo il file crea il file con le informazioni sopra.
Pramod,

Risposte:


25

Puoi anche consigliare la funzione find-fileper creare in modo trasparente le directory necessarie.

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

Metti semplicemente questo nel tuo .emacsposto e usalo C-x C-fcome al solito.


2
Soluzione meravigliosa Adoro come migliorare le piccole cose possa aprire un nuovo mondo di suggerimenti per dare a Emacs di fare meglio le cose (sì, non lo sapevo defadviceprima).
Nikana Reklawyks,

18

Quando fornisco un percorso con un componente inesistente, find-file(ad esempio Cx Cf ), mi dà un messaggio in più che dice

Usa Mx make-directory RET RET per creare la directory e i suoi genitori

Poiché il file non viene creato fino a quando non si salva per la prima volta il buffer, è possibile eseguirlo make-directorysubito dopo la creazione del nuovo buffer oppure è possibile farlo in qualsiasi altro momento prima di dover salvare il file. Quindi dal buffer che necessita di una directory, digitare Mx make-directory RET RET (richiederà la creazione della directory (il valore predefinito è derivato dal percorso del buffer); il secondo RET è quello di accettare il valore predefinito).


14

La modalità Ido fornisce ido-find-fileuna sostituzione find-filee offre molte più funzioni. Ad esempio, ti consente di creare una nuova directory mentre apri il file.

  • Digita C-x C-fcome al solito (che è rimappato a ido-find-file),

  • fornire il percorso inesistente,

  • premere M-mper richiedere la creazione della nuova directory,

  • e quindi specificare il nome del file da visitare nella directory appena creata.


Non capisco: perché premere M-mad un certo punto, e del C-x C-ftutto se ciò non crea tutto automagicamente? Se viene richiesto di creare la directory, M-! mkdiro la regia farà anche un buon lavoro ...
Nikana Reklawyks,

C'è un modo per ido-find-filecreare automagicamente la directory? O ancora meglio, chiedimi se voglio crearlo? Ho provato a usare il consiglio nella risposta di Török Gábor, ma non sono riuscito a capire a quale funzione applicarlo (dato che ido non sta chiamando find-filedirettamente.
Troy Daniels,

1

La mia soluzione ha un bonus: se uccidi il buffer senza salvarlo, Emacs offrirà di eliminare quelle directory vuote che sono state create (ma solo se non esistevano prima che tu invocassi 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))

Versione canonica qui .


0

Oltre al suggerimento di @Chris su Mx make-directory, puoi scrivere una breve funzione elisp che farà find-file e quindi make-directory ... Puoi provare questo:

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

Non è carino e fa lampeggiare "it MX make-directory ...." prima di dire "Creazione directory ..." ma se non altro, dovrebbe darti un inizio.


2
Nel caso di questo approccio, è meglio consigliare la find-filefunzione originale invece di definirne una nuova in modo che altre funzioni che chiamano find-filedirettamente possano persino beneficiare del comportamento modificato.
Török Gábor,

ma find-file non sembra prendere alcun argomento che possa dirgli di farlo ... A meno che non ci sia qualcosa di utile che puoi fare in find-file-hooks ...
Brian Postow,


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

Se le tue directory esistono già, genererà solo un avviso.

Dal C-h f make-directory RETmanuale ( ):

make-directory è una funzione Lisp compilata interattiva.

(make-directory DIR &optional PARENTS)

Creare la directory DIRe, facoltativamente, qualsiasi directory principale inesistente. Se DIResiste già come directory, segnala un errore, a meno che PARENTSsia diverso da zero.

In modo interattivo, la scelta predefinita della directory da creare è la directory predefinita del buffer corrente. Ciò è utile quando hai visitato un file in una directory inesistente.

In modo non interattivo, il secondo argomento (facoltativo) PARENTS, se non nullo, indica se creare directory padre che non esistono. Interattivo, questo accade di default.

Se la creazione della directory o delle directory non riesce, verrà generato un errore.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.