Renyddd Site
Created: 25 Feb 2023

Extend the switching effect in projectile

背景

projectile 已本身是个很优秀的多项目文件管理插件,同时因为预留了很多 hook 点便给我们带来了更多的可扩展性。

需求

  • [X] 使用 rg 代替 grep 进行匹配查找
  • [X] 如果在开启了 NeoTree buffer,则在切换项目时自动更新根目录
  • [X] 快速复制文件相对路径
  • [X] 在项目切换时,恢复至最后的 window configuration

配置 rg 至 commander

通过 C-c p m c r 即可快速调用:

(when (executable-find "rg")
  (setq-default projectile-generic-command "rg --files --hidden"))

(with-eval-after-load 'projectile
  (define-key projectile-mode-map (kbd "C-c p") 'projectile-command-map)
  (global-set-key (kbd "s-p") 'projectile-command-map )

  (when (executable-find "rg")
    (def-projectile-commander-method ?c
      "Grep files in project by rg, with context."
      (rg-menu)))

自动更新 NewTree buffer

after-switch hook 添加即可:

(defun my/neotree-dir-hook ()
  (if (get-buffer " *NeoTree*")
      (progn
        (neotree-dir (projectile-project-root))
        (windmove-right))))

(add-hook 'projectile-after-switch-project-hook #'my/neotree-dir-hook)

复制相对路径

如果需要复制绝对路径, C-x C-j 0 wdired 快速完成。

(defun my/projectile-copy-relative-filename-as-kill ()
  (interactive)
  (kill-new (file-relative-name buffer-file-name (projectile-project-root))))

切换项目时还原 window configuration

项目间的切换行为是 projectile-find-file 即文件模糊匹配 ,这确实为我带来了些不便。第一次尝试的改造是,打开该项目的第一条最近访问文件。

(defun my/projectile-find-first-recently-file ()
  "Open first recently file."
  (find-file (concat (projectile-project-root)
                     (car (projectile-recently-active-files)))))

着重介绍下窗口配置的还原部分,主要思路是以 projectile-project-root 为 key,hash 存储最新的 window configuration

(defvar my/projectile-hash-newest-wc
  (make-hash-table :test 'equal)
  "Newest window configuration of each project.")

(defun my/projectile-puthash-current-wc ()
  (puthash (projectile-project-root)
           (current-window-configuration)
           my/projectile-hash-newest-wc))

(defun my/projectile-gethash-wc ()
  (gethash (projectile-project-root)
           my/projectile-hash-newest-wc))

由实际的使用中发现问题,在 killed 一个项目后,也必须的从该 hash 中清除,否则无法正确地再次打开该项目。

(defun if-opend-file-name-match-p (regexp)
  (let ((found '()))
    (maphash (lambda (_ data)
               (cond ((string-match-p regexp
                                      (file-notify--watch-absolute-filename data))
                      (setq found t))))
             file-notify-descriptors)
    found))

(defun my/projectile-remhash-killed-projects ()
  "Delete killed projects' window configurations."
  (interactive)
  (maphash (lambda (pkey _)
             (cond ((or (not pkey)
                        (not (if-opend-file-name-match-p pkey)))
                    (remhash pkey my/projectile-hash-newest-wc))))
           my/projectile-hash-newest-wc))

由于 projectile 未提供有关 kill 的回调,我们只能将其放到 after switch 钩子中。

最后再考虑一种某项目还未被缓存时情况,我们则进行返回最近最新使用文件。

(add-hook 'projectile-before-switch-project-hook
          #'my/projectile-puthash-current-wc) ;; store or update
(add-hook 'projectile-after-switch-project-hook
          #'my/projectile-remhash-killed-projects)

(defun my/projectile-switch-project ()
  (let ((configuration (my/projectile-gethash-wc)))
    (cond ((not configuration) (progn
                                 (my/projectile-find-first-recently-file)
                                 (delete-other-windows)))
          (t (set-window-configuration configuration)))))

(setq projectile-switch-project-action 'my/projectile-switch-project)

file notify 扩展

file-notify-descriptors 获取当前 Emacs 进程所占用的文件描述符 - file-notify--watch 映射,具体请参考 filenotify.el.gz 文件。

Creative Commons License
renyddd by Renyddd is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.