我经常要在近期文件列表中同时打开多个文件,用 recentf-open-file 或者 recentf-open-more-file 都不太理想,我想要的方式是:
- 打开近期文件列表.
- 勾选一个或多个要打开的文件
- 按键打开选中的文件
想着逻辑应该简单,就自己动手写了个,使用方法是,先按 C-x r,会打开一个近期文件列表的 buffer,带 org-mode 模式,因此可以使用 C-c C-c 选择/取消选择,勾选好后再次按下 C-x r,被选中的多个文件就会一次性都打开,效果如下:
下面是代码:
;; 批量打开近期文件
;; 将近期文件列表显示在一个 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 个赞
Youmu
2
counsel-recentf
配合ivy-next-line-and-call
(默认绑定在C-M-n
上) 和 ivy-previous-line-and-call
(默认绑定在C-M-p上)。
就是不能自己勾选
Youmu
4
只能 选一个,但是这是直接把下一个自己要打开的文件打开了,而且没有退出当前的minibuffer。所以连续的调用也可以达到批量打开的目的
没退出 select buffer 那实际上也符合我的需求,不过我目前用的是 helm,它的 helm-recentf-file 只能选一个,一旦确定候选 buffer 就关闭了。
造个轮子的冲动谁都有,不知道原生desktop-save-mode能否满足你的想法,另一方面desktop-save-mode是有些不如人意的毛病。
主意和代码都很好,能自己动手实现自己的需求是用 Emacs 的乐趣。
就你的代码,几个建议:
- 你的代码缩进看起来有点问题,if 后面 progn 看起来少缩进,我把你的代码复制出来发现你同时用了 Tab + Space,我觉得只用 Space 最简单。考虑调整 indent-tabs-mode。
- if 的 else 分支隐含了 progn,不需要再写,当然写了不算错。
- 正则表达式匹配成功之后,直接用 match-string 也能获得子串,比 substring 方便
- 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 个赞
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)
cireu
14
为什么不用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)))