project.el 使用说明
文章目录
在之前的文章中就多次提到过 28 版本的 project.el 是如何好用,但一直没去详细介绍,这篇文章就来填上这个坑。一句话总结,project.el 完全可以替换 projectile。
下载
直接编译 28 版本或者在 elpa 下载最新版。project 相关命令绑定在 C-x p ,笔者使用的相关配置在 project-config.el,共读者参考。
根目录
在日常编程中,Emacs 的很多包都依赖一个功能:查找当前项目的根目录,比如 lsp-mode、citre 等。在 project.el 中,默认只支持 VC 中的 Git,即 project-try-vc,但可以自定义找根目录的方式,配置如下:
1(defun my/project-try-local (dir)
2 "Determine if DIR is a non-Git project."
3 (catch 'ret
4 (let ((pr-flags '((".project")
5 ("go.mod" "Cargo.toml" "project.clj" "pom.xml" "package.json") ;; higher priority
6 ("Makefile" "README.org" "README.md"))))
7 (dolist (current-level pr-flags)
8 (dolist (f current-level)
9 (when-let ((root (locate-dominating-file dir f)))
10 (throw 'ret (cons 'local root))))))))
11
12(setq project-find-functions '(my/project-try-local project-try-vc))project-find-functions 是 project.el 中用于查找根目录的函数列表,默认只有 project-try-vc ,这里额外定义了一个 my/project-try-local 。
该函数使用一些文件作为根目录标识,只要某个文件夹内有 pr-flags 定义的文件,都可以看作项目根目录,并且具有如下优先级顺序:
.project,与.projectile类似- 常用编程语言的依赖描述文件
- Makefile/README 文件
比如有如下文件目录结构:
1project-demo/
2├── .project
3├── bar
4│ └── pom.xml
5└── foo
6 └── package.json由于 project-demo 内有 .project 文件,所以 bar/foo 不再是根目录,这个特性主要用在 monorepo 中。
该函数返回一个 cons,car 为项目类型,这里定义为 local,cdr 为根目录。
查找文件
查找文件是非常高频的操作,可以使用 Rust 编写的 fd 来代替 find,速度更快。配置如下:
1(defun my/project-files-in-directory (dir)
2 "Use `fd' to list files in DIR."
3 (let* ((default-directory dir)
4 (localdir (file-local-name (expand-file-name dir)))
5 (command (format "fd -H -t f -0 . %s" localdir)))
6 (project--remote-file-names
7 (sort (split-string (shell-command-to-string command) "\0" t)
8 #'string<))))
9
10(cl-defmethod project-files ((project (head local)) &optional dirs)
11 "Override `project-files' to use `fd' in local projects."
12 (mapcan #'my/project-files-in-directory
13 (or dirs (list (project-root project)))))通过重载 project-files , M-x project-find-file 时就会用 fd 来搜索文件了。
常用命令
project.el 提供了很多命令来方便在项目中进行操作,这里列举几个常用的:
- project-remember-projects-under,增加项目
- project-forget-project,删除项目
- project-switch-project,切换项目
- project-compile,执行项目的 compile 命令
- project-search,在项目中搜索指定关键字
- project-query-replace-regexp,在项目中执行正则替换
- project-shell-command,执行 shell 命令
- project-eshell,在根目录打开 eshell
- project-dired,在根目录打开 dired
- project-find-dir,在项目指定目录打开 dired
- project-switch-to-buffer,切换到项目中已经打开的 buffer
扩展命令
1(defun my/project-info ()
2 (interactive)
3 (message "%s" (project-current t)))
4
5(defun my/add-dot-project ()
6 (interactive)
7 (let* ((root-dir (read-directory-name "Root: "))
8 (f (expand-file-name ".project" root-dir)))
9 (message "Create %s..." f)
10 (make-empty-file f)))
11
12(defun my/project-discover ()
13 "Add dir under search-path to project."
14 (interactive)
15 (dolist (search-path '("~/code/" "~/git/"))
16 (dolist (file (file-name-all-completions "" search-path))
17 (when (not (member file '("./" "../")))
18 (let ((full-name (expand-file-name file search-path)))
19 (when (file-directory-p full-name)
20 (when-let ((pr (project-current nil full-name)))
21 (project-remember-project pr)
22 (message "add project %s..." pr))))))))上面函数的功能通过名字即可看出,这里不再赘述。
收听方式

反馈
- 对节目有想法或发现内容错误?欢迎来信交流️