Czy mogę tworzyć katalogi, które nie istnieją podczas tworzenia nowego pliku w emacsie?

29

W emacsie tworzę plik, odwiedzając go za pomocą Cx Cf. Powiedzmy, że chciałbym stworzyć /home/myself/new_directory/file.txt.

Jeśli new_directory jeszcze nie istnieje, czy istnieje sposób, aby utworzyć go podczas tworzenia file.txtbez dodatkowych kroków? (Myślę o czymś takim, jak użycie -pflagi mkdirw systemie Linux).

Wydaje mi się, że istnieje inna kombinacja klawiszy zamiast Cx Cf, która może to zrobić, ale nie pamiętam, co to jest.

Jim Hunziker
źródło
W emacsie nie ma odpowiednika do edytowania polecenia podczas edycji? in vi mógłbyś:!mkdir -p ~/new_dir/to/some/crazy/path
DaveParillo 16.04.2010
3
@DaveParillo: Oczywiście, że tak M-!.
Nikana Reklawyks
Używam wtyczki prelude ( github.com/bbatsov/prelude ). Za każdym razem, gdy tworzę pliki jak wyżej, pojawia się monit „Utwórz katalog ...”. Mogę po prostu wybrać „y”. Następnie pyta mnie: „Plik nie istnieje, utworzyć? (Y lub n)”. Wybieram y, który tworzy nowy plik. Kiedy zapisuję plik, tworzy plik z powyższymi informacjami.
Pramod

Odpowiedzi:

25

Możesz także doradzić funkcji find-fileprzejrzyste tworzenie niezbędnych katalogów.

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

Po prostu włóż to .emacsgdzieś i używaj C-x C-fjak zwykle.

Török Gábor
źródło
2
Cudowne rozwiązanie. Uwielbiam to, jak ulepszanie małych rzeczy może otworzyć zupełnie nowy świat wskazówek, aby dać emacsom lepsze rzeczy do zrobienia (tak, wcześniej nie wiedziałem defadvice).
Nikana Reklawyks
18

Gdy podam nazwę ścieżki z nieistniejącym składnikiem find-file(np. Cx Cf ), wyświetla mi dodatkowy komunikat

Użyj Mx make-directory RET RET, aby utworzyć katalog i jego rodziców

Ponieważ plik nie jest tworzony do momentu pierwszego zapisania bufora, możesz albo uruchomić make-directoryzaraz po uruchomieniu nowego bufora, albo możesz to zrobić w dowolnym momencie przed zapisaniem pliku. Następnie z bufora, który potrzebuje katalogu, wpisz Mx make-directory RET RET (wyświetli monit o utworzenie katalogu (domyślnie pochodzi z nazwy ścieżki bufora); drugi RET ma zaakceptować domyślny).

Chris Johnsen
źródło
14

Ido tryb zapewnia ido-find-file, że jest wymiana find-filei daje o wiele więcej możliwości. Na przykład umożliwia utworzenie nowego katalogu w międzyczasie podczas otwierania pliku.

  • Wpisz C-x C-fjak zwykle (który jest odwzorowany ido-find-file),

  • podać nieistniejącą ścieżkę,

  • naciśnij, M-mktóry poprosi o utworzenie nowego katalogu,

  • a następnie określ nazwę pliku do odwiedzenia w nowo utworzonym katalogu.

Török Gábor
źródło
Nie rozumiem: M-mpo co C-x C-fw ogóle naciskać , a jeśli w ogóle nie tworzy wszystkiego automatycznie? Jeśli zostanie wyświetlony monit o utworzenie katalogu M-! mkdirlub dired również wykona dobrą robotę…
Nikana Reklawyks,
Czy istnieje sposób na ido-find-fileautomatyczne utworzenie katalogu? A nawet lepiej, zapytaj mnie, czy chcę to stworzyć? Próbowałem skorzystać z porady zawartej w odpowiedzi Töröka Gábora, ale nie mogłem dowiedzieć się, do której funkcji ją zastosować (ponieważ ido nie dzwoni find-filebezpośrednio.
Troy Daniels
1

Moje rozwiązanie ma dodatkową zaletę: jeśli zabijesz bufor bez zapisywania go, Emacs zaoferuje usunięcie tych pustych katalogów, które zostały utworzone (ale tylko jeśli nie istniały przed wywołaniem 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))

Wersja kanoniczna tutaj .

Radon Rosborough
źródło
0

Oprócz sugestii @Chris dotyczącej tworzenia katalogu Mx, możesz napisać krótką funkcję elisp, która wykona wyszukiwanie pliku, a następnie utworzenie katalogu ... Możesz spróbować:

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

To nie jest ładne i miga „it MX make-directory ....” zanim powie „Creating directory ...”, ale jeśli nic więcej, powinno dać ci początek.

Brian Postow
źródło
2
W przypadku takiego podejścia lepiej jest doradzić pierwotną find-filefunkcję zamiast definiować nową, aby inne funkcje wywołujące find-filebezpośrednio mogły nawet skorzystać ze zmodyfikowanego zachowania.
Török Gábor
ale wydaje się, że find-file nie przyjmuje żadnych argumentów, które mogłyby to powiedzieć ... Chyba że jest coś użytecznego, co można zrobić w
hookach
Miałem na myśli to: superuser.com/questions/131538/...
Török Gábor
0
(make-directory "~/bunch/of/dirs" t)

Jeśli twoje katalogi już istnieją, po prostu wygeneruje ostrzeżenie.

Z ( C-h f make-directory RET) instrukcji:

make-directory to interaktywna skompilowana funkcja Lisp.

(make-directory DIR &optional PARENTS)

Utwórz katalog DIRi opcjonalnie wszelkie nieistniejące katalogi nadrzędne. Jeśli DIRjuż istnieje jako katalog, zasygnalizuj błąd, chyba że PARENTSjest inny niż zero.

Interaktywnie domyślnym wyborem katalogu do utworzenia jest katalog domyślny bieżącego bufora. Jest to przydatne, gdy odwiedziłeś plik w nieistniejącym katalogu.

Nieinteraktywnie drugi (opcjonalny) argument PARENTS, jeśli nie jest pusty, mówi, czy utworzyć katalogi nadrzędne, które nie istnieją. Interaktywnie dzieje się to domyślnie.

Jeśli utworzenie katalogu lub katalogów zakończy się niepowodzeniem, pojawi się błąd.

yPhil
źródło