Posso criar diretórios que não existem enquanto cria um novo arquivo no emacs?

29

No emacs, crio um arquivo visitando-o com Cx Cf. Digamos que eu gostaria de criar /home/myself/new_directory/file.txt.

Se o new_directory ainda não existir, existe uma maneira de fazê-lo ser criado durante a criação file.txtsem etapas adicionais? (Estou pensando em algo como usar a -pbandeira mkdirno Linux.)

Sinto que há um pressionamento de tecla diferente em vez de Cx Cf que pode fazer isso, mas não consigo me lembrar o que é.

Jim Hunziker
fonte
Não há equivalente no emacs para lanch um comando durante a edição? em vi que podia:!mkdir -p ~/new_dir/to/some/crazy/path
DaveParillo
3
@ DaveParillo: Claro que existe, M-!por exemplo.
Nikana Reklawyks
Eu uso o plugin prelude ( github.com/bbatsov/prelude ). Sempre que eu crio arquivos como acima, ele me solicita "Criar diretório ...". Eu posso simplesmente selecionar "y". Em seguida, ele me pergunta "O arquivo não existe, cria? (Y ou n)". Eu seleciono y, que cria um novo arquivo. Quando eu salvo o arquivo, ele cria o arquivo com as informações acima.
Pramod

Respostas:

25

Você também pode aconselhar a função find-filea criar transparentemente os diretórios necessários.

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

Basta colocar isso no seu .emacslugar e usar C-x C-fcomo de costume.

Török Gábor
fonte
2
Solução maravilhosa. Eu amo como melhorar pequenas coisas pode abrir um novo mundo de dicas para dar ao emacs que faça coisas melhores (sim, eu não sabia defadviceantes).
Nikana Reklawyks
18

Quando eu forneço um nome de caminho com um componente inexistente find-file(por exemplo , Cx Cf ), recebo uma mensagem extra dizendo:

Use Mx make-directory RET RET para criar o diretório e seus pais

Como o arquivo não é criado até você salvar o buffer pela primeira vez, você pode executar make-directorylogo após a instalação do novo buffer ou fazê-lo qualquer outra hora antes de salvar o arquivo. Em seguida, no buffer que precisa de um diretório, digite Mx make-directory RET RET (ele solicitará a criação do diretório (o padrão é derivado do nome do caminho do buffer); o segundo RET é aceitar o padrão).

Chris Johnsen
fonte
14

O modo Ido fornece ido-find-fileuma substituição find-filee oferece muito mais recursos. Por exemplo, ele permite que você crie um novo diretório enquanto abre o arquivo.

  • Digite C-x C-fcomo de costume (que é remapeado para ido-find-file),

  • fornecer o caminho inexistente,

  • pressione M-mque solicitará a criação do novo diretório,

  • e especifique o nome do arquivo a ser visitado no diretório recém-criado.

Török Gábor
fonte
Não entendi: por que pressionar M-mem algum momento e C-x C-fse isso não cria tudo automaticamente? Se a ser solicitado para o diretório para criar, M-! mkdirou dired vai fazer um bom trabalho também ...
Nikana Reklawyks
Existe uma maneira de ido-find-filecriar automaticamente o diretório? Ou melhor ainda, pergunte-me se eu quero criá-lo? Eu tentei usar o conselho na resposta de Török Gábor, mas eu não conseguia descobrir qual função para aplicá-lo ao (como ido não está ligando find-filediretamente.
Troy Daniels
1

Minha solução vem com um bônus: se você matar o buffer sem salvá-lo, o Emacs oferecerá a exclusão dos diretórios vazios que foram criados (mas apenas se eles não existissem antes de você invocar 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))

Versão canônica aqui .

Radon Rosborough
fonte
0

Além da sugestão do @Chris de Mx make-directory, você pode escrever uma função elisp curta que fará o find-file e depois o make-directory ... Você pode tentar o seguinte:

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

Não é bonito e exibe o "diretório de criação MX ...." antes de dizer "Criando diretório ...", mas se nada mais, isso deve lhe dar um começo.

Brian Postow
fonte
2
No caso dessa abordagem, é melhor aconselhar a find-filefunção original em vez de definir uma nova, para que outras funções que chamam find-filediretamente possam se beneficiar do comportamento modificado.
Török Gábor
mas encontrar-arquivo não parecem ter quaisquer argumentos que podem dizer que ele faça isso ... A menos que haja alguma coisa útil que você pode fazer em find-file-ganchos ...
Brian Postow
Eu quis dizer isso: superuser.com/questions/131538/…
Török Gábor
0
(make-directory "~/bunch/of/dirs" t)

Se os seus diretórios já existirem, apenas será emitido um aviso.

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

O make-directory é uma função Lisp compilada interativa.

(make-directory DIR &optional PARENTS)

Crie o diretório DIRe, opcionalmente, qualquer diretório pai inexistente. Se DIRjá existir como um diretório, sinalize um erro, a menos que PARENTSseja nulo.

Interativamente, a escolha padrão do diretório a ser criado é o diretório padrão do buffer atual. Isso é útil quando você visita um arquivo em um diretório inexistente.

De maneira não interativa, o segundo argumento (opcional) PARENTS, se não for nulo, informa se é necessário criar diretórios pai que não existem. Interativamente, isso acontece por padrão.

Se a criação do diretório ou dos diretórios falhar, um erro será gerado.

yPhil
fonte