面向产品经理的Emacs教程:20. 在Emacs里进行文件管理

· 2797字 · 6分钟

1 课程回顾 🔗

上节课我们学习了如何在Emacs里通过Org mode来进行日程管理,我们可以在日常工作和生活中,将自己每天的计划安排结合日记一起记录下来,然后通过Org mode的机制进行追踪和提醒,刚开始肯定会有一些不习惯,但如果长期坚持下去,就会发现它的好处,它会让你养成一个良好的习惯,而所有的这些记录,都可追溯、可查询,而你在用的过程中也会感到喜悦!

今天我们来学习如何在Emacs里进行文件管理。

2 Emacs Dired文件管理 🔗

Emacs自带一个插件叫 Dired (Directory Editor),可以让Emacs对系统里的文件进行管理。我们可以通过 C-x d 来启动Dired。启动后,它会让你选择要进入哪个目录(默认为当前打开文件所在的目录):

我们对文件的常用操作大致有如下几种:

  • 拷贝文件(夹)
  • 剪切文件(夹)
  • 粘贴文件(夹)
  • 删除文件(夹)
  • 创建文件(夹)
  • 跳转文件(夹)
  • 查找文件(夹)
  • 批量操作文件(夹)
  • 改名

所有的这些操作都可以在Emacs里通过Dired、Consult、Embark等手段完成,非常方便。下面我们逐步来讲解如何配置和使用Dired。

2.1 Dired基础配置 🔗

(use-package dired
  :ensure nil
  :bind (:map dired-mode-map
              ("C-<return>" . xah-open-in-external-app)
              ("W" . dired-copy-path)
              )
  :config
  ;; Enable the disabled dired commands
  (put 'dired-find-alternate-file 'disabled nil)

  ;; open files via external program based on file types, See:
  ;; https://emacs.stackexchange.com/questions/3105/how-to-use-an-external-program-as-the-default-way-to-open-pdfs-from-emacs
  (defun xdg-open (filename)
    (interactive "fFilename: ")
    (let ((process-connection-type))
      (start-process "" nil (cond ((eq system-type 'gnu/linux) "xdg-open")
                                  ((eq system-type 'darwin) "open")
                                  ((eq system-type 'windows-nt) "start")
                                  (t "")) (expand-file-name filename))))
  ;; open files via external program when using find-file
  (defun find-file-auto (orig-fun &rest args)
    (let ((filename (car args)))
      (if (cl-find-if
           (lambda (regexp) (string-match regexp filename))
           '(
             ;; "\\.html?\\'"
             "\\.xlsx?\\'"
             "\\.pptx?\\'"
             "\\.docx?\\'"
             "\\.mp4\\'"
             "\\.app\\'"
             ))
          (xdg-open filename)
        (apply orig-fun args))))
  (advice-add 'find-file :around 'find-file-auto)

  (defun dired-copy-path ()
    "In dired, copy file path to kill-buffer.
At 2nd time it copy current directory to kill-buffer."
    (interactive)
    (let (path)
      (setq path (dired-file-name-at-point))
      (if (string= path (current-kill 0 1)) (setq path (dired-current-directory)))
      (message path)
      (kill-new path)))

  (defun xah-open-in-external-app (&optional @fname)
    "Open the current file or dired marked files in external app.
The app is chosen from your OS's preference.

When called in emacs lisp, if @fname is given, open that.

URL `http://ergoemacs.org/emacs/emacs_dired_open_file_in_ext_apps.html'
Version 2019-11-04"
    (interactive)
    (let* (
           ($file-list
            (if @fname
                (progn (list @fname))
              (if (or (string-equal major-mode "dired-mode")
                      (string-equal major-mode "dirvish-mode"))
                  (dired-get-marked-files)
                (list (buffer-file-name)))))
           ($do-it-p (if (<= (length $file-list) 5)
                         t
                       (y-or-n-p "Open more than 5 files? "))))
      (when $do-it-p
        (cond
         ((string-equal system-type "windows-nt")
          (mapc
           (lambda ($fpath)
             (w32-shell-execute "open" $fpath)) $file-list))
         ((string-equal system-type "darwin")
          (mapc
           (lambda ($fpath)
             (shell-command
              (concat "open " (shell-quote-argument $fpath))))  $file-list))
         ((string-equal system-type "gnu/linux")
          (mapc
           (lambda ($fpath) (let ((process-connection-type nil))
                              (start-process "" nil "xdg-open" $fpath))) $file-list))))))
  :custom
  ;; (dired-recursive-deletes 'always)
  (delete-by-moving-to-trash t)
  (dired-dwim-target t)
  (dired-bind-vm nil)
  (dired-bind-man nil)
  (dired-bind-info nil)
  (dired-auto-revert-buffer t)
  (dired-hide-details-hide-symlink-targets nil)
  (dired-kill-when-opening-new-dired-buffer t)
  (dired-listing-switches "-AFhlv"))

(use-package dired-aux
  :ensure nil
  :bind (:map dired-mode-map
              ("C-c +" . dired-create-empty-file))
  :config
  ;; with the help of `evil-collection', P is bound to `dired-do-print'.
  (define-advice dired-do-print (:override (&optional _))
    "Show/hide dotfiles."
    (interactive)
    (if (or (not (boundp 'dired-dotfiles-show-p)) dired-dotfiles-show-p)
        (progn
          (setq-local dired-dotfiles-show-p nil)
          (dired-mark-files-regexp "^\\.")
          (dired-do-kill-lines))
      (revert-buffer)
      (setq-local dired-dotfiles-show-p t)))
  :custom
  (dired-isearch-filenames 'dwim)
  (dired-create-destination-dirs 'ask)
  (dired-vc-rename-file t))

(use-package dired-x
  :ensure nil
  :hook (dired-mode . dired-omit-mode)
  :init
  (setq dired-guess-shell-alist-user `((,(rx "."
                                             (or
                                              ;; Videos
                                              "mp4" "avi" "mkv" "flv" "ogv" "ogg" "mov"
                                              ;; Music
                                              "wav" "mp3" "flac"
                                              ;; Images
                                              "jpg" "jpeg" "png" "gif" "xpm" "svg" "bmp"
                                              ;; Docs
                                              "pdf" "md" "djvu" "ps" "eps" "doc" "docx" "xls" "xlsx" "ppt" "pptx")
                                             string-end)
                                        ,(cond ((eq system-type 'gnu/linux) "xdg-open")
                                               ((eq system-type 'darwin) "open")
                                               ((eq system-type 'windows-nt) "start")
                                               (t "")))))
  :custom
  (dired-omit-verbose nil)
  (dired-omit-files (rx string-start
                        (or ".DS_Store"
                            ".cache"
                            ".vscode"
                            ".ccls-cache" ".clangd")
                        string-end))
  ;; Dont prompt about killing buffer visiting delete file
  (dired-clean-confirm-killing-deleted-buffers nil)
  )

2.2 Dired美化 🔗

2.2.1 diredfl多彩美化 🔗

默认的Dired只有两种颜色以区分文件和文件夹,我们可以使用 diredfl 插件让Dired变得更加多彩一些:

(use-package diredfl
  :ensure t
  :hook (dired-mode . diredfl-mode))

安装完后我们可以看到Dired多了不少其他的颜色,包括文件后缀名、文件夹、文件是否可写、文件的时间等:

2.2.2 all-the-icons-dired图标美化 🔗

我们通过 all-the-icons-dired 插件给Dired添加好看的图标。

我们需要先安装 all-the-icons.el 插件,这个插件能给Emacs很多地方如 find-file 加上好看的图标,安装完后,我们还需要通过 M-x all-the-icons-install-fonts 命令来安装相应的字体:

(use-package all-the-icons
  :ensure t
  :when (display-graphic-p)
  :commands all-the-icons-install-fonts
  )

然后我们安装 all-the-icons-dired 插件:

(use-package all-the-icons-dired
  :ensure t
  :hook (dired-mode . all-the-icons-dired-mode)
  )

我们再次打开Dired后可以看到,所有的文件和文件夹都加上了好看的图标,通过不同的图标来区分各类文件和文件夹:

顺便我们通过 all-the-icons-completion 插件给迷你缓冲区的补全系统也加上好看的图标:

(use-package all-the-icons-completion
  :ensure t
  :hook ((after-init . all-the-icons-completion-mode)
         (marginalia-mode . all-the-icons-completion-marginalia-setup))
  )

3 dirvish文件管理增强 🔗

dirvish 是在Dired基础之上的文件管理增强插件。选择它而不选择 ranger.el 也是因为 dirvish 是基于原生 Dired 做的增强,所有的快捷键使用习惯等都与 Dired 保持一致。

  1. 需要通过 brew install poppler 安装 poppler 来预览PDF;
  2. 需要通过 brew install ffmpegthumbnailder 来安装 ffmpegthumbnailer 来预览视频。
(use-package dirvish
  :ensure t
  :hook (after-init . dirvish-override-dired-mode)
  :bind (:map dired-mode-map
         ("TAB" . dirvish-toggle-subtree)
         ("SPC" . dirvish-show-history)
         ("*"   . dirvish-mark-menu)
         ("r"   . dirvish-roam)
         ("b"   . dirvish-goto-bookmark)
         ("f"   . dirvish-file-info-menu)
         ("M-n" . dirvish-go-forward-history)
         ("M-p" . dirvish-go-backward-history)
         ("M-s" . dirvish-setup-menu)
         ("M-f" . dirvish-toggle-fullscreen)
         ([remap dired-sort-toggle-or-edit] . dirvish-quicksort)
         ([remap dired-do-redisplay] . dirvish-ls-switches-menu)
         ([remap dired-summary] . dirvish-dispatch)
         ([remap dired-do-copy] . dirvish-yank-menu)
         ([remap mode-line-other-buffer] . dirvish-other-buffer))
  :config
  (dirvish-peek-mode)
  (setq dirvish-hide-details t)
  ;; open mp4 file via external program which is mpv here.
  (add-to-list 'mailcap-mime-extensions '(".mp4" . "video/mp4"))
  (add-list-to-list 'dirvish-open-with-programs '(
                                                  (("html") . ("open" "%f"))
                                                  (("xlsx") . ("open" "%f"))
                                                  (("pptx") . ("open" "%f"))
                                                  (("docx") . ("open" "%f"))
                                                  (("md")   . ("open" "%f"))
                                                  ))
  :custom
  (dirvish-menu-bookmarks '(("h" "~/"             "Home")
                            ("d" "~/Downloads/"   "Downloads")
                            ("e" "~/.emacs.d.default/"    "Emacs")
                            ("o" "~/org/"         "org")
                            ("i" "~/iCloud/"      "iCloud")
                            ;; ("t" "~/.local/share/Trash/files/" "TrashCan")
                            ))
  (dirvish-mode-line-format '(:left
                              (sort file-time " " file-size symlink) ; it's ok to place string inside
                              :right
                              ;; For `dired-filter' users, replace `omit' with `filter' segment defined below
                              (omit yank index)))
  (dirvish-attributes '(subtree-state
                        file-size
                        vc-state
                        git-msg
                        ;; all-the-icons
                        ))
  )

我们安装配置完 dirvish 后,再次通过 C-x d 打开 Dired 后,可以看到整个界面变成了下面这样,非常简洁:

我们可以在 dirvish 界面按下 ? 来了解更多的快捷键和命令:

4 通过Emacs来进行文件管理的基本操作 🔗

按照前文所述,我们对文件的常用操作大致有如下几种,下面将逐一讲解如何通过 dirvish 来进行:

  • 拷贝文件(夹)
  • 剪切文件(夹)
  • 粘贴文件(夹)
  • 删除文件(夹)
  • 创建文件(夹)
  • 跳转文件(夹)
  • 查找文件(夹)
  • 批量操作文件(夹)
  • 改名

4.1 拷贝、粘贴、剪切文件(夹) 🔗

这几个操作在 dirvish 里都是一致的,我们可以通过 m 来标记我们要操作的文件或文件夹,然后我们跳转到需要粘贴或操作的目标文件夹,按 C (dirvish-yank-menu) 来选择具体的操作动作:

  • Yank:粘贴到这里
  • Move:剪切到这里
  • Make symlinks: 设置软链接

4.2 删除文件(夹) 🔗

我们只需要在目标文件(夹)的行按下 D (dired-do-delete) 就可以删除当前文件(夹),如果要多选,可以通过 m (dired-mark) 键来标记,然后再按下 D 键进行删除:

4.3 创建文件(夹) 🔗

我们按下 C-c + (dired-create-empty-file) 来创建一个空文件(相当于是 touch 命令的效果):

我们按下 + (dired-create-directory) 来在当前文件夹下创建一个新的文件夹:

4.4 跳转文件(夹) 🔗

我们按下 j (dired-goto-file) 来跳转到某个文件(夹):

4.5 搜索文件(夹)的工作流 🔗

我个人的使用习惯是先通过 consult-locate 来找到目的文件,然后通过 embark 插件来实现迷你缓冲区的类似右键的操作。

我们先来安装配置下 embark:

(use-package embark
  :ensure t
  :bind (([remap describe-bindings] . embark-bindings)
         ("C-'" . embark-act)
         :map minibuffer-local-map
         :map minibuffer-local-completion-map
         ("TAB" . minibuffer-force-complete)
         :map embark-file-map
         ("E" . consult-file-externally)      ; Open file externally, or `we' in Ranger
         ("O" . consult-directory-externally) ; Open directory externally
         )
  :init
  ;; Optionally replace the key help with a completing-read interface
  (setq prefix-help-command #'embark-prefix-help-command)
  :config
  ;; Show Embark actions via which-key
  (setq embark-action-indicator
        (lambda (map)
          (which-key--show-keymap "Embark" map nil nil 'no-paging)
          #'which-key--hide-popup-ignore-command)
        embark-become-indicator embark-action-indicator)

  ;; open directory
  (defun consult-directory-externally (file)
    "Open directory externally using the default application of the system."
    (interactive "fOpen externally: ")
    (if (and (eq system-type 'windows-nt)
             (fboundp 'w32-shell-execute))
        (shell-command-to-string (encode-coding-string (replace-regexp-in-string "/" "\\\\"
                                                                                 (format "explorer.exe %s" (file-name-directory (expand-file-name file)))) 'gbk))
      (call-process (pcase system-type
    	              ('darwin "open")
    	              ('cygwin "cygstart")
    	              (_ "xdg-open"))
    	            nil 0 nil
    	            (file-name-directory (expand-file-name file)))))

  ;; Hide the mode line of the Embark live/completions buffers
  (add-to-list 'display-buffer-alist
               '("\\`\\*Embark Collect \\(Live\\|Completions\\)\\*"
                 nil
                 (window-parameters (mode-line-format . none))))
  )

(use-package embark-consult
  :ensure t
  :hook (embark-collect-mode . consult-preview-at-point-mode))

我们以打开 在Emacs里进行文件管理.org 这个文件所在的文件夹Dired为例:

  1. 通过 consult-locate 输入 #Emacs#wjgl 来定位此文件;
  2. 按下 C-' 来触发 embark-act
  3. embark 下按 j 键跳转到相应的Dired。

通过这个工作流,我们就能很快速的跳转到硬盘上的任意一个文件所在的文件夹了,只要自己记得一些关键词即可。

4.6 批量操作 🔗

Dired 里,批量操作都是通过 m 键先进行对象的选择,然后再执行相应的动作,操作逻辑跟我们在 Finder 等文件管理器里的思路是一样的。


我们掌握了以上的常用文件操作,就可以通过 Emacs+Dired+Dirvish 进行高效的文件管理了,我们不再需要频繁的通过鼠标打开一个又一个的访达窗口了。

4.7 改名 🔗

我们将光标移动到目标文件或文件夹上,然后按下 R (dired-do-rename) 来进行改名:

5 结语 🔗

通过今天的课程,我们学习了如何在Emacs里高效的进行文件管理,通过Emacs强大的机制,我们通过键盘就能实现绝大多数的文件管理操作,简单快捷高效!

这节课的配置文件的快照见:emacs-config-l20.org

你也可以在 这里 查看最新的配置文件。