写了个批量打开近期文件的函数,大家有什么建议?

我经常要在近期文件列表中同时打开多个文件,用 recentf-open-file 或者 recentf-open-more-file 都不太理想,我想要的方式是:

  • 打开近期文件列表.
  • 勾选一个或多个要打开的文件
  • 按键打开选中的文件

想着逻辑应该简单,就自己动手写了个,使用方法是,先按 C-x r,会打开一个近期文件列表的 buffer,带 org-mode 模式,因此可以使用 C-c C-c 选择/取消选择,勾选好后再次按下 C-x r,被选中的多个文件就会一次性都打开,效果如下: emacs 下面是代码:


;; 批量打开近期文件
;; 将近期文件列表显示在一个 org-mode 的 buffer 中
;; 按 C-c C-c 进行勾选和反勾选.
;; 勾选完成后再次执行,将打开勾选的多个文件,同时关闭 buffer.
;; 在勾选过程中若要中止打开文件,可以直接关闭 buffer 即可.
(defun zhcosin/open-recent-files ()
  (interactive)
  (let ((select-buffer "*SelectReccentFilesForOpen*"))
    (if (buffer-live-p (get-buffer select-buffer))
	(progn
	  (with-current-buffer select-buffer
	    (let ((content-lines (split-string (buffer-string) "\n" t)))
	      (dolist (content-line content-lines)
		(when (string-match "^\\s-*-\\s-*\\(\\[X\\]\\)\\s-*\\(.*\\)$" content-line)
		  (let ((the-file-name (substring content-line (match-beginning 2) (match-end 2))))
		    (message (concat "open recent file: " the-file-name))
		    (find-file the-file-name))))))
	  (kill-buffer select-buffer)) 
      (progn
        (get-buffer-create select-buffer)
        (switch-to-buffer (get-buffer select-buffer))
        (org-mode)
        (insert "\n  recent file list:\n\n")
        (dolist (file recentf-list)
          (insert (concat " - [ ] " file "\n")))))))

;; 按 C-x C-r 打开近期文件列表,该按键原本绑定的命令是 find-file-read-only,即以只读方式打开文件,不常用.
(global-set-key "\C-x\ \C-r" 'zhcosin/open-recent-files)

感觉实现比较那啥。。。大家有没有好的意见啊?

2 个赞

counsel-recentf配合ivy-next-line-and-call(默认绑定在C-M-n上) 和 ivy-previous-line-and-call(默认绑定在C-M-p上)。

就是不能自己勾选

是不是只能选择一个?

只能 选一个,但是这是直接把下一个自己要打开的文件打开了,而且没有退出当前的minibuffer。所以连续的调用也可以达到批量打开的目的

没退出 select buffer 那实际上也符合我的需求,不过我目前用的是 helm,它的 helm-recentf-file 只能选一个,一旦确定候选 buffer 就关闭了。

造个轮子的冲动谁都有,不知道原生desktop-save-mode能否满足你的想法,另一方面desktop-save-mode是有些不如人意的毛病。

主意和代码都很好,能自己动手实现自己的需求是用 Emacs 的乐趣。

就你的代码,几个建议:

  1. 你的代码缩进看起来有点问题,if 后面 progn 看起来少缩进,我把你的代码复制出来发现你同时用了 Tab + Space,我觉得只用 Space 最简单。考虑调整 indent-tabs-mode。
  2. if 的 else 分支隐含了 progn,不需要再写,当然写了不算错。
  3. 正则表达式匹配成功之后,直接用 match-string 也能获得子串,比 substring 方便
  4. message 的第一个参数一般是手写的字符串,比如 (message “%s” string),而不是 (message string),因为万一 string 中包含了 %,message 很容易出错。
5 个赞

灰常感谢,我再改改。

一个不用Org,更Emacs的版本:m选择,u取消选择,x打开,q退出

(defvar-local selected-files nil)

(define-derived-mode select-file-mode special-mode
  "Select"
  (define-key select-file-mode-map (kbd "m")
    (lambda () (interactive)
      (let ((buffer-read-only nil))
        (push (buffer-substring
               (line-beginning-position)
               (line-end-position))
              selected-files)
        (put-text-property
         (line-beginning-position)
         (line-end-position)
         'face 'highlight))
      (next-line)))
  (define-key select-file-mode-map (kbd "u")
    (lambda () (interactive)
      (let ((buffer-read-only nil))
        (setq selected-files
              (remove (buffer-substring
                       (line-beginning-position)
                       (line-end-position))
                      selected-files))
        (put-text-property
         (line-beginning-position)
         (line-end-position)
         'face 'default))
      (next-line)))
  (define-key select-file-mode-map (kbd "x")
    (lambda () (interactive)
      (let ((file-list selected-files))
        (kill-buffer "*select file*")
        (dolist (file file-list)
          (find-file file)))))
  (define-key select-file-mode-map (kbd "n") (kbd "C-n"))
  (define-key select-file-mode-map (kbd "p") (kbd "C-p")))

(defun select-files-open (dir)
  (interactive "DDirectory: ")
  (let ((file-list (directory-files dir t)))
    (switch-to-buffer "*select file*")
    (setq buffer-read-only nil)
    (erase-buffer)
    (dolist (file file-list)
      (insert file "\n"))
    (goto-char (point-min))
    (setq selected-files nil)
    (select-file-mode)))
2 个赞

666啊,学习了。

Helm好像可以ctrl + up 预览buffer,其实就打开buffer了。

match-string 好像不是执行 string-match 之后的结果,好像是搜索的结果.

根据 #9 答案,我折腾了下,还是加上了前面的中括号复选框,我知道这样做其实更费事,但是好像是因为我这个颜色配置高亮不是特别明显,所以复选框加高亮两者一起更容易看出文件的选择状态,

效果:

代码:


(defvar-local selected-files nil)

(define-derived-mode select-file-mode special-mode "Select-Files"
  "mark/unmark files for select/unselect."
  
  (define-key select-file-mode-map (kbd "m")
    (lambda () (interactive)
      (let ((buffer-read-only nil))
        (save-restriction
          (narrow-to-region
            (line-beginning-position)
            (line-end-position))
	  (goto-char (point-min))
          (when (search-forward-regexp
                 "^\\(\\s-*-\\s-*\\)\\(\\[ \\]\\)\\(\\s-*\\)\\(.*\\)$" 
		 nil
                 t)
            (push
              (buffer-substring (match-beginning 4) (match-end 4))
              selected-files)
	    (replace-match "\\1[X]\\3\\4")
            (put-text-property
              (line-beginning-position)
              (line-end-position)
              'face 'highlight)
	  (goto-char (point-min)))))))

  (define-key select-file-mode-map (kbd "u")
    (lambda () (interactive)
      (let ((buffer-read-only nil))
        (save-restriction
          (narrow-to-region
            (line-beginning-position)
            (line-end-position))
	  (goto-char (point-min))
          (when (search-forward-regexp
                 "^\\(\\s-*-\\s-*\\)\\(\\[X\\]\\)\\(\\s-*\\)\\(.*\\)$" 
		 nil
                 t)
            (remove (buffer-substring (match-beginning 4) (match-end 4))
                      selected-files)
	    (replace-match "\\1[ ]\\3\\4")
            (put-text-property
              (line-beginning-position)
              (line-end-position)
              'face 'default)
	    (goto-char (point-min)))))))

  (define-key select-file-mode-map (kbd "x")
    (lambda () (interactive)
      (let ((file-list selected-files))
        (kill-buffer "*select file*")
        (dolist (file file-list)
          (find-file file)))))
  (define-key select-file-mode-map (kbd "n") (kbd "C-n"))
  (define-key select-file-mode-map (kbd "p") (kbd "C-p")))

(defun zhcosin/open-recent-files ()
  (interactive)
  (let ((file-list recentf-list))
    (switch-to-buffer "*select file*")
    (setq buffer-read-only nil)
    (erase-buffer)
    (insert "\n  recent files:\n\n")
    (dolist (file file-list)
      (insert " - [ ] " file "\n"))
    (goto-char (point-min))
    (setq selected-files nil)
    (select-file-mode)))

(add-to-list 'evil-emacs-state-modes 'select-file-mode)


为什么不用Emacs的widget呢?

(defvar-local ciremacs-batch-open-recentf--state nil)

(defvar ciremacs-batch-open-recentf-buffer "*Batch Open Recentf*")

(defun ciremacs--batch-open-recentf-reset-widget-buffer ()
  (let ((inhibit-read-only t))
      (erase-buffer))
      (remove-overlays)
      (setq ciremacs-batch-open-recentf--state nil))

(defun ciremacs-batch-open-recentf ()
  (interactive)
  (when (not recentf-mode)
    (user-error "Recentf mode not activated!"))
  (let ((buf (get-buffer-create ciremacs-batch-open-recentf-buffer)))
    (with-current-buffer buf
      (ciremacs--batch-open-recentf-reset-widget-buffer)
      (widget-insert "\nrecent file list:\n\n")
      (setq ciremacs-batch-open-recentf--state
            (mapcar (lambda (f)
                      (prog1 (cons (widget-create 'checkbox nil) f)
                        (widget-insert " " f "\n")))
                    recentf-list))
      (widget-insert "\n")
      (widget-create 'push-button :notify (lambda (&rest _)
                                            (dolist (s ciremacs-batch-open-recentf--state)
                                              (when (widget-value (car s))
                                                (find-file (cdr s)))))
                     "Confirm")
      (widget-create 'push-button :notify (lambda (&rest _)
                                            (ciremacs-batch-open-recentf))
                     "Reset")
      (read-only-mode 1)
      (use-local-map widget-keymap))
    (display-buffer buf)))