Tôi có thể tạo thư mục không tồn tại trong khi tạo tệp mới trong emacs không?


29

Trong emacs, tôi tạo một tệp bằng cách truy cập nó với Cx Cf. Hãy nói rằng tôi muốn tạo ra /home/myself/new_directory/file.txt.

Nếu new_directory chưa tồn tại, có cách nào để nó được tạo trong quá trình tạo file.txtmà không cần thêm bước nào không? (Tôi đang nghĩ về một cái gì đó giống như sử dụng -pcờ mkdirtrong Linux.)

Tôi cảm thấy như có một tổ hợp phím khác thay vì Cx Cf có thể làm điều này, nhưng tôi không thể nhớ nó là gì.


Không có tương đương trong emacs để cho mượn một lệnh trong khi chỉnh sửa ?? trong vi bạn có thể:!mkdir -p ~/new_dir/to/some/crazy/path
DaveParillo

3
@DaveParillo: Tất nhiên là có, M-!ví dụ.
Nikana Reklawyks

Tôi sử dụng plugin prelude ( github.com/bbatsov/prelude ). Bất cứ khi nào tôi tạo các tệp như trên, nó sẽ nhắc tôi với "Tạo thư mục ...". Tôi chỉ có thể chọn "y". Tiếp theo, nó hỏi tôi "Tệp không tồn tại, tạo? (Y hoặc n)". Tôi chọn y, tạo ra một tập tin mới. Khi tôi lưu tệp, nó tạo ra tệp có thông tin ở trên.
Pramod

Câu trả lời:


25

Bạn cũng có thể tư vấn chức năng find-fileđể minh bạch tạo các thư mục cần thiết.

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

Đơn giản chỉ cần đặt nó trong .emacsmột nơi nào đó của bạn và sử dụng C-x C-fnhư bình thường.


2
Giải pháp tuyệt vời. Tôi thích cách cải thiện những điều nhỏ nhặt có thể mở ra một thế giới gợi ý hoàn toàn mới để cho emacs làm mọi thứ tốt hơn (vâng, tôi không biết về điều defadviceđó trước đây).
Nikana Reklawyks

18

Khi tôi cung cấp tên đường dẫn với thành phần không tồn tại, find-file(ví dụ Cx Cf ), sẽ cho tôi một thông báo bổ sung có nội dung

Sử dụng Mx make-thư mục RET RET để tạo thư mục và cha mẹ của nó

Vì tệp không được tạo cho đến khi bạn lưu bộ đệm trước tiên, bạn có thể chạy make-directoryngay sau khi bộ đệm mới xuất hiện hoặc bạn có thể thực hiện bất kỳ lúc nào khác trước khi bạn cần lưu tệp. Sau đó, từ bộ đệm cần một thư mục, gõ Mx make-thư mục RET RET (nó sẽ nhắc thư mục tạo (mặc định được lấy từ tên đường dẫn của bộ đệm); RET thứ hai là chấp nhận mặc định).


14

Các Ido chế độ cung cấp ido-find-fileđó là một sự thay thế của find-filevà mang đến cho bạn nhiều tính năng hơn. Chẳng hạn, nó cho phép bạn tạo thư mục mới trong khi bạn mở tệp.

  • Nhập C-x C-fnhư bình thường (được ánh xạ lại ido-find-file),

  • cung cấp con đường không tồn tại,

  • nhấn M-mmà sẽ nhắc cho thư mục mới để tạo,

  • và sau đó chỉ định tên tệp sẽ truy cập trong thư mục vừa tạo.


Tôi không hiểu điều đó: tại sao lại nhấn M-mvào một lúc nào đó, và C-x C-ftại tất cả nếu điều đó không tạo ra mọi thứ tự động? Nếu được nhắc để thư mục tạo, M-! mkdirhoặc chuyển hướng sẽ thực hiện công việc quá công bằng
Nikana Reklawyks

Có cách nào để ido-find-filetự động tạo thư mục không? Hoặc thậm chí tốt hơn, hỏi tôi nếu tôi muốn tạo ra nó? Tôi đã thử sử dụng lời khuyên trong câu trả lời của Török Gábor, nhưng tôi không thể tìm ra chức năng nào để áp dụng nó (vì ido không gọi find-filetrực tiếp.
Troy Daniels

1

Giải pháp của tôi đi kèm với một phần thưởng: nếu bạn tắt bộ đệm mà không lưu nó, Emacs sẽ đề nghị xóa những thư mục trống đã được tạo (nhưng chỉ khi chúng không tồn tại trước khi bạn gọi 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))

Phiên bản Canonical ở đây .


0

Ngoài đề xuất của @Chris về thư mục Mx make-thư mục, bạn có thể viết một hàm elisp ngắn sẽ tìm tệp và sau đó tạo thư mục ... Bạn có thể thử điều này:

(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ó không đẹp, và nó hiện lên "thư mục tạo MX ...." trước khi nói "Tạo thư mục ..." nhưng nếu không có gì khác, nó sẽ cho bạn một sự khởi đầu.


2
Trong trường hợp của phương pháp này, tốt hơn là nên tư vấn cho find-filechức năng ban đầu thay vì xác định một chức năng mới để các chức năng khác gọi find-filetrực tiếp thậm chí có thể có lợi cho hành vi được sửa đổi.
Török Gábor

nhưng find-file dường như không có bất kỳ đối số nào có thể bảo nó làm điều này ... Trừ khi có điều gì đó hữu ích mà bạn có thể làm trong hook-file-hook ...
Brian Postow


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

Nếu thư mục của bạn đã tồn tại, nó sẽ chỉ đưa ra một cảnh báo.

Từ C-h f make-directory REThướng dẫn ( ):

thư mục là một chức năng Lisp tương tác được biên dịch.

(make-directory DIR &optional PARENTS)

Tạo thư mục DIRvà tùy chọn bất kỳ thư mục cha không tồn tại. Nếu DIRđã tồn tại dưới dạng thư mục, báo hiệu lỗi, trừ khi PARENTSkhông phải là không.

Tương tác, lựa chọn mặc định của thư mục cần tạo là thư mục mặc định hiện tại của bộ đệm. Điều đó hữu ích khi bạn đã truy cập một tệp trong thư mục không tồn tại.

Không tương tác, đối số thứ hai (tùy chọn) PARENTS, nếu không phải là số không, cho biết có tạo thư mục cha không tồn tại hay không. Tương tác, điều này xảy ra theo mặc định.

Nếu tạo thư mục hoặc thư mục không thành công, một lỗi sẽ được đưa ra.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.