Projectile 中文文档
Table of Contents
- 1. Projectile
- 2. 安装 (Installation)
- 3. 使用 (Usage)
- 3.1. 基本设置 (Basic setup)
- 3.2. 基本配置 (Basic Configuration)
- 3.3. 自动项目发现 (Automated Project Discovery)
- 3.4. 移除缺失的项目 (Removal of missing projects)
- 3.5. Minibuffer 补全 (Minibuffer Completion)
- 3.6. 安装外部工具 (Installing External Tools)
- 3.7. 基本用法 (Basic Usage)
- 3.8. 交互式命令 (Interactive Commands)
- 3.9. 自定义 Projectile 的键绑定 (Customizing Projectile’s Keybindings)
- 3.10. Projectile Commander
- 3.11. 将 Projectile 与 project.el 结合使用 (Using Projectile with project.el)
- 4. 项目 (Projects)
- 4.1. 支持的项目类型 (Supported Project Types)
- 4.2. 版本控制系统 (Version Control Systems)
- 4.3. 文件标记 (File markers)
- 4.4. 添加自定义项目类型 (Adding Custom Project Types)
- 4.5. 从函数返回 Projectile 命令 (Returning Projectile Commands from a function)
- 4.6. 相关文件位置 (Related file location)
- 4.7. 相关文件自定义函数助手 (Related file custom function helper)
- 4.8. 编辑现有项目类型 (Editing Existing Project Types)
- 4.9. :test-dir/:src-dir vs :related-files-fn
- 4.10. 自定义项目检测 (Customizing Project Detection)
- 4.11. 忽略文件 (Ignoring files)
- 4.12. 文件局部项目根定义 (File-local project root definitions)
- 4.13. 存储项目设置 (Storing project settings)
- 4.14. 配置 Projectile 的行为 (Configuring Projectile’s Behavior)
- 4.15. 项目缓冲区 (Project Buffers)
- 4.16. 配置项目的生命周期命令和其他属性 (Configure a Project’s Lifecycle Commands and Other Attributes)
- 5. 配置 (Configuration)
- 5.1. 项目索引方法 (Project indexing method)
- 5.2. Alien 索引 (Alien indexing)
- 5.3. 排序 (Sorting)
- 5.4. 缓存 (Caching)
- 5.5. 在项目目录之外使用 Projectile 命令 (Using Projectile Commands Outside of Projects Directories)
- 5.6. 切换项目 (Switching projects)
- 5.7. 补全选项 (Completion Options)
- 5.8. 项目特定的编译缓冲区 (Project-specific Compilation Buffers)
- 5.9. 重新生成标签 (Regenerate tags)
- 5.10. 空闲计时器 (Idle Timer)
- 5.11. Mode line 指示器 (Mode line indicator)
- 5.12. 特定项目类型的配置 (Project-type-specific Configuration)
- 6. 扩展 (Extensions)
- 7. Projectile vs project.el
- 8. FAQ (Frequently Asked Questions)
- 9. 故障排除 (Troubleshooting)
- 10. 贡献 (Contributing)
1. Projectile
1.1. 概述 (Overview)
Projectile 是一个用于 Emacs 的项目交互库. 它的目标是在不引入外部依赖 (在可行的情况下) 的前提下, 提供一套好用的项目级功能.
例如, 查找项目文件有一个用纯 Emacs Lisp 编写的可移植实现, 无需使用 GNU find (但为了性能, 也存在一个由外部命令支持的索引机制).
实际上, Projectile 可以在不调用 find, git 或其他任何外部命令的情况下为项目中的文件建立索引, 这意味着与许多类似工具不同, 它可以在 Windows 上无需任何额外设置即可工作.
Projectile 试图做到实用 —— 可移植性很棒, 但如果某些外部工具可以显著加快某个任务的速度并且这些工具可用, Projectile 将会利用它们.
1.2. 功能 (Features)
Projectile 提供了简单的项目管理和导航功能. 项目的概念非常基础 —— 只是一个包含特殊文件的文件夹.
目前, 大多数 VCS 仓库 (如 git, mercurial 等) 默认被视为项目, 同样被视为项目的还有包含构建工具 (如 maven, leiningen 等) 或框架标记 (如 Ruby on Rails) 的目录.
如果你想手动将一个文件夹标记为项目, 只需在其中创建一个空的 .projectile 文件即可.
你可以在这里了解更多关于 Projectile 项目概念的信息.
以下是 Projectile 的一些功能, 排名不分先后:
- 跳转到项目中的文件
- 跳转到项目中光标所在处的文件
- 跳转到项目中的目录
- 跳转到目录中的文件
- 跳转到项目缓冲区
- 跳转到项目中的测试
- 在同名但扩展名不同的文件之间切换 (例如 .h ←→ .c/.cpp, Gemfile ←→ Gemfile.lock)
- 在代码和其测试之间切换 (例如 main.service.js ←→ main.service.spec.js)
- 跳转到项目中最近访问的文件
- 在你工作过的项目之间切换
- 关闭 (kill) 所有项目缓冲区
- 在项目中替换
- 在项目缓冲区中进行 multi-occur
- 在项目中进行 grep (搜索) (支持多种后端, 如 ag, rg 等)
- 在项目中查找引用 (内部使用 xref)
- 重新生成项目 etags 或 gtags (需要 ggtags).
- 在 dired 中访问项目
- 用一个组合键在项目中运行 make
- 浏览有未提交更改的版本控制项目
- 支持多种 minibuffer 补全/选择库 (ido, ivy, helm 以及默认补全系统)
- 自动项目发现 (参见 projectile-project-search-path)
- 与内置的 project.el 库集成
还有一个丰富的第三方 Projectile 扩展生态系统, 增加了更多功能.
1.3. Projectile 演示 (Projectile in Action)
以下是 Projectile 实际操作的一瞥 (使用 ivy 进行 minibuffer 补全):
在这个简短的演示中, 你可以看到:
- 在项目中查找文件
- 在实现和测试之间切换
在项目之间切换
你可以在 modeline 中看到演示中使用的键绑定以及它们调用的命令.
1.4. 支持 Projectile (Supporting Projectile)
我从 2011 年开始开发 Projectile, 并一直维护至今. 它从一个只有一个用户的默默无闻的项目, 慢慢成长为 Emacs 领域最受欢迎的软件包之一. 这是一个有趣的项目, 但也需要大量的工作.
你可以通过以下平台之一支持我在 Projectile (以及我所有其他 Emacs 软件包) 上的工作:
- GitHub Sponsors
- ko-fi
- PayPal
- Patreon
请查看文档的 "Contributing" 部分, 了解所有可以帮助 Projectile 的方式.
给你一点小知识 —— Projectile 是我第一个开源项目, 它在我心中占有非常特殊的位置!
1.5. 下一步? (What’s Next?)
那么, 接下来该做什么? 虽然你可以按任何你喜欢的方式阅读文档, 但这里有一些建议:
- 安装 Projectile 并使其运行起来
- 熟悉 Projectile 的项目概念
- 根据你的喜好配置 Projectile
- 探索可以让你更高效的 Projectile 扩展
- 在 Projectile 的术语中, 该文件被称为 "project marker" (项目标记).
- 由 keycast-mode 提供.
2. 安装 (Installation)
Projectile 官方支持 Emacs 26.1+.
推荐的安装 Projectile 的方式是通过 package.el.
2.1. 先决条件 (Prerequisites)
你需要安装 Emacs (最好是最新稳定版). 如果你是 Emacs 新手, 你可能想先看看 Emacs 的导览和内置教程 (只需按 C-h t).
2.2. 通过 package.el 安装 (Installation via package.el)
Projectile 在所有主要的 package.el 社区维护的仓库中都可用 —— NonGNU ELPA, MELPA Stable 和 MELPA.
你可以使用以下命令安装 Projectile:
M-x package-install RET projectile RET
或者将这段 Emacs Lisp 代码添加到你的 Emacs 初始化文件 (.emacs 或 init.el) 中:
(unless (package-installed-p 'projectile) (package-install 'projectile))
如果安装失败, 尝试刷新包列表:
M-x package-refresh-contents RET
请记住, MELPA 的包是从 master 分支自动构建的, 这意味着有时可能会引入 bug. 尽管如此, 从 MELPA 安装 Projectile 是一种合理的方式, 因为 master 分支通常相当稳定, 严重的问题通常会很快被修复.
如果你不想 (或不能) 等待 MELPA 重新构建 Projectile, 你可以轻松地在本地构建和安装一个最新的 MELPA 包. 查看这篇文章了解详情.
通常, 不喜欢冒险的用户建议使用稳定版本, 可从 NonGNU ELPA 和 MELPA Stable 获取. 你可以通过在 Emacs 初始化文件中添加以下内容来将 Projectile 固定为始终使用 MELPA Stable:
(add-to-list 'package-pinned-packages '(projectile . "melpa-stable") t)
最后, 将以下内容添加到你的 Emacs 配置中:
(require 'projectile) ;; macOS 上推荐的键映射前缀 (define-key projectile-mode-map (kbd "s-p") 'projectile-command-map) ;; Windows/Linux 上推荐的键映射前缀 (define-key projectile-mode-map (kbd "C-c p") 'projectile-command-map) (projectile-mode +1)
这些键映射前缀只是一个建议. 你可以随意设置最适合你的键. C-c p 曾是 2.0 版本之前的默认前缀, 但从 2.0 版本开始, 你需要自己选择前缀键.
2.3. 通过 use-package 安装 (Installation via use-package)
use-package 是安装和配置 Projectile 的一种流行方式.
如果你想安装 master 分支中的 Projectile 版本, 在你的 Emacs 初始化文件 (.emacs 或 init.el) 中声明以下内容:
(use-package projectile :ensure t :init (projectile-mode +1) :bind (:map projectile-mode-map ("s-p" . projectile-command-map) ("C-c p" . projectile-command-map)))
然而, 如果你希望更保守一些, 只使用 Projectile 的稳定版本, 你应该声明以下内容:
(use-package projectile :ensure t :pin melpa-stable :init (projectile-mode +1) :bind (:map projectile-mode-map ("s-p" . projectile-command-map) ("C-c p" . projectile-command-map)))
放置上述 s-表达式之一后, 通过输入 C-x C-e 对其求值使其生效.
关于 use-package 的更多配置选项, 请查阅官方 use-package 仓库.
2.4. 通过 el-get 安装 (Installation via el-get)
Projectile 也可以通过 el-get 包管理器安装.
如果你已经安装了 el-get, 你可以使用以下命令安装 Projectile:
M-x el-get-install RET projectile RET
2.5. 在 Debian 和 Ubuntu 上安装 (Installation on Debian and Ubuntu)
Debian 9 或更高版本以及 Ubuntu 16.10 或更高版本的用户可以简单地使用 apt-get install elpa-projectile.
你喜欢的 Linux 发行版可能也提供了 Projectile 包.
2.6. Emacs Prelude
Projectile 捆绑在 Emacs Prelude 中. 如果你是 Prelude 用户, Projectile 已经正确配置并准备就绪.
3. 使用 (Usage)
本节中的所有内容都假定你已经启用了 projectile-mode.
3.1. 基本设置 (Basic setup)
在本节中, 我们将介绍你可能想做的最基本的设置. Projectile 无需任何设置即可正常工作, 但如果稍作调整, 你将能更好地利用它.
请查阅手册的 "Configuration" 部分, 了解更多关于配置 Projectile 的信息.
3.2. 基本配置 (Basic Configuration)
一个典型的 Projectile 配置如下所示:
;; 可选: ag 是一个与 Projectile 一起使用的 grep 的不错替代品 (use-package ag :ensure t) ;; 可选: 启用 vertico 作为与 Projectile 一起使用的选择框架 (use-package vertico :ensure t :init (vertico-mode +1)) ;; 可选: which-key 会显示部分完成的键绑定的选项 ;; 对于像 Projectile 这样有很多键绑定的包来说, 它非常有用. (use-package which-key :ensure t :config (which-key-mode +1)) (use-package projectile :ensure t :init (setq projectile-project-search-path '("~/projects/" "~/work/" "~/playground")) :config ;; 我通常在 macOS 上使用这个键映射前缀 (define-key projectile-mode-map (kbd "s-p") 'projectile-command-map) ;; 然而, 在 Linux 上, 我通常使用另一个 (define-key projectile-mode-map (kbd "C-c C-p") 'projectile-command-map) (global-set-key (kbd "C-c p") 'projectile-command-map) (projectile-mode +1))
上面的例子是在 "Installation" 部分演示的更简单设置的基础上构建的.
3.3. 自动项目发现 (Automated Project Discovery)
要将一个项目添加到 Projectile 的已知项目列表中, 只需在该项目中打开一个文件. 如果你有一个项目目录, 你可以使用命令 M-x projectile-discover-projects-in-directory 来告诉 Projectile 其中所有的项目.
你可以更进一步, 设置一个文件夹列表, Projectile 将在启动时自动检查这些文件夹中的项目.
通过在 cons cell 中指定搜索深度来配置递归发现:
(setq projectile-project-search-path '("~/projects/" "~/work/" ("~/github" . 1)))
你可以通过将 projectile-auto-discover 设置为 nil 来禁止启动时自动发现项目. 你可以使用 M-x projectile-discover-projects-in-search-path 手动触发现项目发现.
3.4. 移除缺失的项目 (Removal of missing projects)
有时, 你的已知项目列表中会有一些不再存在的项目 (例如, 它们被移除或重命名了). 你可以手动触发命令 projectile-cleanup-known-projects, 或者将变量 projectile-auto-cleanup-known-projects 设置为 t 来自动移除这些项目.
(setq projectile-cleanup-known-projects nil)
如果你是 TRAMP 的重度用户, 自动发现和清理项目可能不是一个好主意, 因为那里的文件操作较慢.
3.5. Minibuffer 补全 (Minibuffer Completion)
虽然 Projectile 与 Emacs 默认的 minibuffer 补全系统配合得很好, 但强烈建议你使用一些强大的替代品, 如 ido, ivy, vertico.
如果你要使用 ido 补全, 强烈建议你安装可选的 flx-ido 包, 它提供了比 ido 内置的 flex 匹配更强大的替代方案. 类似地, 对于 ivy 和 vertico, 启用 prescient (一个类似于 flx 的包) 是个好主意.
3.6. 安装外部工具 (Installing External Tools)
Windows 用户可以忽略此部分, 除非他们通过 WSL 或 cygwin 使用 Emacs.
Projectile 无需任何外部依赖即可开箱即用. 然而, 如果你安装了各种工具, 它们将在适当时被自动使用以提高性能.
在版本控制仓库内部, 如果安装了 VC 工具, 它们将被用来更有效地列出文件. 支持的工具包括 git, hg, fossil, bzr, darcs, pijul, svn, sapling 和 jujutsu.
在版本控制仓库外部, 如果安装了文件搜索工具, 它们将被用来进行比纯 Elisp 更快的搜索. 支持的工具包括 fd 和 GNU/BSD find.
默认情况下, 如果安装了 fd, 它也会在 Git 仓库内部用作 git ls-files 的替代品, 因为 git ls-files 有一个限制, 即它也会列出已删除的文件, 直到删除被暂存, 这可能会令人困惑. 你可以通过将 projectile-git-use-fd 设置为 nil 来在这种情况下禁用 fd.
要利用 projectile-ag 和 projectile-ripgrep 命令执行文件搜索, 建议安装 ag (thesilversearcher) 和/或 rg (ripgrep).
如果你想使用 Projectile 的 projectile-ag 和 projectile-ripgrep 命令, 你还应该安装 Emacs 包 ag, ripgrep 或 rg.
3.7. 基本用法 (Basic Usage)
只需在 Projectile 识别的版本控制 (例如 git) 或项目 (例如 maven) 目录中打开某个文件, 你就可以开始操作了. Projectile 开箱即用地识别了所有常见的 VCS 和许多流行编程语言的项目类型. 你可以在这里了解更多关于 Projectile 的项目概念.
对每种 VCS 的支持程度不同, Git 是支持最好的. Projectile 支持一些高级功能, 如处理 Git 子模块和使用 git-grep 代替 GNU grep.
你只需要知道少数几个 Projectile 命令就可以开始从中受益.
- 在当前项目中查找文件 (s-p f)
- 切换项目 (s-p p) (你也可以用 s-p q 在打开的项目之间切换)
- 在项目中进行 Grep (搜索文本/正则表达式) (s-p s g)
- 在项目中替换 (s-p r)
- 在项目中查找引用 (s-p ? 或 s-p s x)
- 通过 Projectile Commander 调用任何 Projectile 命令 (s-p m)
- 在实现和测试之间切换 (s-p t)
- 在相关文件之间切换 (例如 foo.h <→ foo.c 和 Gemfile <→ Gemfile.lock) (s-p a)
- 在项目根目录中运行 shell 命令 (s-p ! 用于同步命令, s-p & 用于异步命令)
- 运行各种预定义的项目命令, 如:
- 构建/编译项目 (s-p c c)
- 测试项目 (s-p c t)
- 安装项目 (s-p c i)
- 运行项目 (s-p c r)
下一节列出了更多命令, 但这些基础知识已经能让你走得很远了.
3.8. 交互式命令 (Interactive Commands)
Projectile 没有为其命令设置默认的按键前缀, 但手册中的所有示例都假定你选择了 s-p (super-p).
以下是 Projectile 提供的交互式 Emacs Lisp 函数列表:
| 按键绑定 | 描述 |
|---|---|
| s-p f | 显示项目中的所有文件列表. 带前缀参数时会先清除缓存. |
| s-p F | 显示所有已知项目中的所有文件列表. |
| s-p g | 显示项目中光标处的所有文件列表. 带前缀参数时会先清除缓存. |
| s-p 4 f | 使用补全跳转到项目文件, 并在另一个窗口中显示. |
| s-p 4 g | 根据光标上下文跳转到项目文件, 并在另一个窗口中显示. |
| s-p 5 f | 使用补全跳转到项目文件, 并在另一个 frame 中显示. |
| s-p 5 g | 根据光标上下文跳转到项目文件, 并在另一个 frame 中显示. |
| s-p d | 显示项目中的所有目录列表. 带前缀参数时会先清除缓存. |
| s-p 4 d | 切换到项目目录, 并在另一个窗口中显示. |
| s-p 5 d | 切换到项目目录, 并在另一个 frame 中显示. |
| s-p T | 显示项目中的所有测试文件 (specs, features 等) 列表. |
| s-p l | 显示目录中的所有文件列表 (不一定是项目). |
| s-p s g | 在项目文件上运行 grep. |
| M– s-p s g | 在项目的 projectile-grep-default-files 文件上运行 grep. |
| s-p s s | 在项目上运行 ag (thesilversearcher), 进行字面搜索. 需要 ag.el. 带前缀参数时会进行正则搜索. |
| s-p s r | 在项目上运行 rg (ripgrep), 进行字面搜索. 需要 rg.el 或 ripgrep.el. 带前缀参数时会进行正则搜索. |
| s-p s x | 在项目中查找光标处符号的引用. 内部使用 xref 库. |
| s-p v | 在项目的根目录上运行 vc-dir. |
| s-p V | 浏览有未提交更改的版本控制项目. |
| s-p b | 显示当前打开的所有项目缓冲区列表. |
| s-p 4 b | 切换到项目缓冲区, 并在另一个窗口中显示. |
| s-p 5 b | 切换到项目缓冲区, 并在另一个 frame 中显示. |
| s-p 4 C-o | 在另一个窗口中显示项目缓冲区, 但不选中它. |
| s-p a | 在同名但扩展名不同的文件之间切换. |
| s-p 4 a | 在另一个窗口中切换同名但扩展名不同的文件. |
| s-p 5 a | 在另一个 frame 中切换同名但扩展名不同的文件. |
| s-p o | 在当前打开的所有项目缓冲区上运行 multi-occur. |
| s-p r | 在项目的所有文件上运行交互式 query-replace. |
| s-p i | 使项目缓存失效 (如果存在). |
| s-p R | 重新生成项目的 TAGS 文件. |
| s-p j | 在项目的 TAGS 文件中查找标签. |
| s-p k | 关闭所有项目缓冲区. |
| s-p D | 在 dired 中打开项目根目录. |
| s-p 4 D | 在另一个窗口的 dired 中打开项目根目录. |
| s-p 5 D | 在另一个 frame 的 dired 中打开项目根目录. |
| s-p e | 显示最近访问的项目文件列表. |
| s-p left | 切换到上一个项目缓冲区. |
| s-p right | 切换到下一个项目缓冲区. |
| s-p E | 打开项目的根 dir-locals-file. |
| s-p ! | 在项目根目录中运行 shell-command. |
| s-p & | 在项目根目录中运行 async-shell-command. |
| s-p c o | 运行项目类型的标准配置命令. |
| s-p c c | 运行项目类型的标准编译命令. |
| s-p c t | 运行项目类型的标准测试命令. |
| s-p c i | 运行项目类型的标准安装命令. |
| s-p c r | 运行项目类型的标准运行命令. |
| s-p t | 在实现文件和其测试文件之间切换. |
| s-p 4 t | 在另一个窗口中跳转到实现或测试文件. |
| s-p 5 t | 在另一个 frame 中跳转到实现或测试文件. |
| s-p z | 将当前访问的文件添加到缓存中. |
| s-p p | 显示一个可以切换到的已知项目列表. |
| s-p q | 显示一个可以切换到的打开项目列表. |
| s-p S | 保存所有项目缓冲区. |
| s-p m | 运行 commander (一个用单个按键运行命令的界面). |
| s-p x e | 为项目启动或访问一个 eshell. |
| s-p x i | 为项目启动或访问一个 ielm (Elisp REPL). |
| s-p x t | 为项目启动或访问一个 ansi-term. |
| s-p x s | 为项目启动或访问一个 shell. |
| s-p x g | 为项目启动或访问一个 gdb. |
| s-p x v | 为项目启动或访问一个 vterm. |
| s-p ESC | 切换到最近选择的 Projectile 缓冲区. |
如果你忘记了 Projectile 的任何键绑定, 只需执行:
s-p C-h
3.9. 自定义 Projectile 的键绑定 (Customizing Projectile’s Keybindings)
可以向 projectile-mode-map 中引用的 projectile-command-map 添加额外的命令. 你可以为所有命令添加多个键映射前缀. 以下示例添加了 super-, 作为命令前缀:
(define-key projectile-mode-map (kbd "s-,") 'projectile-command-map)
你也可以将 projectile-command-map 绑定到任何你喜欢的其他映射 (包括全局键映射).
对于一些常用命令, 你可能想走个捷径, 利用相当不常用的 Super 键 (在 Mac 键盘上默认为 Command, 在 Win 键盘上为 Windows).
你可以在你的 Emacs 配置中添加以下内容:
(define-key projectile-mode-map [?\s-d] 'projectile-find-dir) (define-key projectile-mode-map [?\s-p] 'projectile-switch-project) (define-key projectile-mode-map [?\s-f] 'projectile-find-file) (define-key projectile-mode-map [?\s-g] 'projectile-grep)
请注意, Super 键绑定在 Windows 中不可用, 因为 Windows 本身大量使用了这类键绑定. Emacs Prelude 已经添加了这些额外的键绑定.
3.10. Projectile Commander
Projectile 的 Commander (projectile-commander) 对于那些难以记住大量键绑定的用户来说是一个非常方便的工具. 它通过在调用 commander (例如通过 s-p m) 后按下的单个字符快捷键, 为大多数 Projectile 命令提供了一个简单的界面.
Commander 的创建初衷是提供一个强大的项目切换命令 (如果你按 C-u s-p p 就会触发), 但它本身也很有用.
| 按键绑定 | 描述 |
|---|---|
| ? | Commander 帮助缓冲区. |
| D | 在 dired 中打开项目根目录. |
| R | 重新生成项目的 etags/gtags. |
| T | 在项目中查找测试文件. |
| V | 浏览有未提交更改的项目. |
| a | 在项目上运行 ag. |
| b | 切换到项目缓冲区. |
| d | 在项目中查找目录. |
| e | 在项目中查找最近访问的文件. |
| f | 在项目中查找文件. |
| g | 在项目上运行 grep. |
| j | 在项目中查找标签. |
| k | 关闭所有项目缓冲区. |
| o | 在项目缓冲区上运行 multi-occur. |
| r | 在项目中替换字符串. |
| s | 切换项目. |
| v | 在 vc-dir 或 magit 中打开项目根目录. |
你可以像这样向 commander 添加额外的命令:
(def-projectile-commander-method ?f "Find file in project." (projectile-find-file))
将此类代码片段放在 projectile-mode 的初始化代码之后.
3.11. 将 Projectile 与 project.el 结合使用 (Using Projectile with project.el)
从 2.7 版本开始, Projectile 捆绑了一些与 project.el 的集成, 使得 project.el 在 projectile-mode 启用时使用 Projectile 的项目查找函数 (projectile-project-root) 和项目文件查找函数 (projectile-project-files). 你也可以像这样手动启用集成:
(add-hook 'project-find-functions #'project-projectile)
你可以在这里阅读更多关于集成实现细节的信息.
这很有用, 因为一些包 (例如 eglot) 原生只支持 project.el 的 API 来进行项目发现. 幸运的是, project.el 使得安装额外的项目查找函数变得容易, 而这正是 Projectile 所做的.
流行的 xref 包也依赖于 project.el 来推断项目, 以便执行 xref-find-references (M-?) 等有用的命令, 因此让它了解 Projectile 的项目发现逻辑是很有用的.
Projectile 提供了自己的 xref-find-references 替代方案, 名为 projectile-find-references (s-p ? 或 s-p s-x), 它在内部使用 xref.
你可以像这样禁用 project.el 集成:
(remove-hook 'project-find-functions #'project-projectile)
4. 项目 (Projects)
4.1. 支持的项目类型 (Supported Project Types)
Projectile 的主要目标之一是在无需任何配置的情况下, 能够在广泛的项目类型上操作. 为了实现这一点, 它包含了大量的项目检测逻辑和特定项目类型的逻辑.
广义上讲, Projectile 这样识别项目:
- 包含特殊 .projectile 文件的目录
- 处于版本控制下的目录 (例如 Git 仓库)
- 包含某些项目描述文件的目录 (例如 Ruby 项目的 Gemfile 或基于 Java maven 的项目的 pom.xml)
虽然 Projectile 旨在开箱即用地识别大多数项目类型, 但它在配置方面也非常灵活, 你可以轻松地修改项目检测逻辑.
如果你想覆盖默认的项目检测函数, 你应该查看 projectile-project-root-functions. 我们稍后将在文档中更详细地讨论如何调整.
4.2. 版本控制系统 (Version Control Systems)
Projectile 将大多数版本控制的仓库视为一个项目. 开箱即用地, Projectile 支持:
- Git
- Mercurial
- Bazaar
- Subversion
- CVS
- Fossil
- Darcs
- Sapling
- Jujutsu
4.3. 文件标记 (File markers)
Projectile 认为许多文件标志着一个项目的根目录. 通常这些文件是各种构建工具的配置文件. 开箱即用地支持以下文件:
| 语言/类别 | 文件 | 项目类型 |
|---|---|---|
| Universal | xmake.lua | xmake project |
| Universal | SConstruct | Scons project file |
| Universal | meson.build | project file |
| Universal | default.nix | Nix project file |
| Universal | flake.nix | Nix flake project file |
| Universal | WORKSPACE | Bazel workspace file |
| Universal | debian/control | Debian package dpkg control file |
| Make & CMake | Makefile | Make |
| Make & CMake | GNUMakefile | GNU Make |
| Make & CMake | CMakeLists.txt | CMake |
| Go-task/Task | Taskfile.yaml | Go-task/Task project file |
| PHP | composer.json | PHP project file |
| Erlang & Elixir | rebar.config | Rebar project file |
| Erlang & Elixir | mix.exs | Elixir mix project file |
| JavaScript | Gruntfile.js | Grunt project file |
| Angular | angular.json | Angular project file |
| JavaScript | gulpfile.js | Javascript Gulp file |
| JavaScript | package.json | npm, pnpm and yarn project file |
| Python | manage.py | Django project file |
| Python | requirements.txt | Python requirements file |
| Python | setup.py | Setuptools file |
| Python | tox.ini | Python Tox file |
| Python | Pipfile | Python Pip file |
| Python | poetry.lock | Python Poetry project file |
| Python | pyproject.toml | Python project file |
| Java & friends | pom.xml | Maven project file |
| Java & friends | application.yml | Gradle project file |
| Java & friends | build.gradle | Gradle project file |
| Java & friends | gradlew | Gradle wrapper script |
| Java & friends | application.yaml | Gradle wrapper script |
| Scala | build.sbt | SBT project file |
| Scala | build.sc | Mill project file |
| Scala | .bloop | Bloop project file |
| Ensime | .ensime | Ensime configuration file |
| Clojure | project.clj | Leiningen project file |
| Clojure | build.boot | Boot-clj project file |
| Clojure | deps.edn | Clojure CLI project file |
| Ruby | Gemfile | Bundler file |
| Crystal | shard.yml | Crystal project file |
| Emacs | Cask | Emacs cask file |
| Emacs | Eask | Emacs cask file |
| Emacs | Eldev | Emacs LISP project file |
| R | DESCRIPTION | R package description file |
| Haskell | stack.yaml | Haskell’s stack tool based project |
| Rust | Cargo.toml | Cargo project file |
| Racket | info.rkt | Racket package description file |
| Dart | pubspec.yaml | Dart project file |
| Elm | elm.json | Elm project file |
| Julia | Project.toml | Julia project file |
| OCaml | dune-project | OCaml Dune project file |
| Universal | GTAGS | GNU Global tags |
| Universal | TAGS | etags/ctags are usually in the root of project |
| Universal | configure.ac | autoconf new style |
| Universal | configure.in | autoconf old style |
| C | cscope.out | cscope |
| Composer | composer.json | Composer project file |
| Zig | build.zig.zon | Zig project file |
| Swift | Package.swift | Swift package file |
还有 Projectile 自己的 .projectile, 它既是项目标记又是配置文件. 我们将在本节稍后详细讨论.
4.4. 添加自定义项目类型 (Adding Custom Project Types)
如果你正在处理的项目被错误地识别, 或者你想添加自己的项目类型, 你可以向你的 Emacs 初始化代码中添加以下内容:
(projectile-register-project-type 'npm '("package.json") :project-file "package.json" :compile "npm install" :test "npm test" :run "npm start" :test-suffix ".spec")
这样做的是:
- 添加你自己的项目类型, 在本例中是 npm package.
- 添加一个项目根目录下的文件和/或文件夹列表, 用于帮助识别类型, 在本例中只有 package.json. 这也可以是一个函数, 它接受一个项目根目录作为参数, 并验证该目录是否具有该类型的正确项目结构.
- 添加 project-file, 这通常是主要的项目配置文件. 在本例中是 package.json. 该值可以包含通配符和/或是一个包含多个项目文件的列表.
- 添加 compile-command, 在本例中是 npm install.
- 添加 test-command, 在本例中是 npm test.
- 添加 run-command, 在本例中是 npm start.
- 添加用于在实现/测试文件之间切换的测试文件后缀, 在本例中是 .spec, 因此实现/测试文件对可以是例如 service.js/service.spec.js.
让我们看几个更复杂的例子.
;; .NET C# or F# projects (projectile-register-project-type 'dotnet #'projectile-dotnet-project-p :project-file '("?*.csproj" "?*.fsproj") :compile "dotnet build" :run "dotnet run" :test "dotnet test")
这个例子使用 projectile-dotnet-project-p 来验证项目的结构. 由于 C# 和 F# 项目文件的名称包含项目名称, 它使用通配符列表来指定不同的有效 project-file 名称模式.
;; Ruby + RSpec (projectile-register-project-type 'ruby-rspec '("Gemfile" "lib" "spec") :project-file "Gemfile" :compile "bundle exec rake" :src-dir "lib/" :test "bundle exec rspec" :test-dir "spec/" :test-suffix "_spec") ;; Ruby + Minitest (projectile-register-project-type 'ruby-test '("Gemfile" "lib" "test") :project-file "Gemfile" :compile "bundle exec rake" :src-dir "lib/" :test "bundle exec rake test" :test-suffix "_test") ;; Rails + Minitest (projectile-register-project-type 'rails-test '("Gemfile" "app" "lib" "db" "config" "test") :project-file "Gemfile" :compile "bundle exec rails server" :src-dir "lib/" :test "bundle exec rake test" :test-suffix "_test") ;; Rails + RSpec (projectile-register-project-type 'rails-rspec '("Gemfile" "app" "lib" "db" "config" "spec") :project-file "Gemfile" :compile "bundle exec rails server" :src-dir "lib/" :test "bundle exec rspec" :test-dir "spec/" :test-suffix "_spec")
所有这些项目都使用 Gemfile (bundler 的项目文件), 但它们具有不同的目录结构.
下面是 projectile-register-project-type 所有可用选项的列表:
| 选项 | 文档 |
|---|---|
| :project-file | 一个相对于项目根目录的文件, 通常是主项目文件 (例如 Maven 项目的 pom.xml). |
| :compilation-dir | 一个相对于项目根目录的路径, 从那里运行测试和编译命令. |
| :compile | 编译项目的命令. |
| :configure | 配置项目的命令. %s 将被替换为项目根目录. |
| :install | 安装项目的函数. |
| :package | 打包项目的函数. |
| :run | 运行项目的命令. |
| :src-dir | 一个相对于项目根目录的路径, 源代码所在的位置. 也可以指定一个函数, 它接受一个参数 - 测试文件的目录, 并应返回实现文件应在的目录. 此选项仅用于实现/测试切换. |
| :test | 测试项目的命令. |
| :test-dir | 一个相对于项目根目录的路径, 测试代码所在的位置. 也可以指定一个函数, 它接受一个参数 - 文件的目录, 并应返回测试文件应在的目录. 此选项仅用于实现/测试切换. |
| :test-prefix | 用于生成测试文件名的前缀. |
| :test-suffix | 用于生成测试文件名的后缀. |
| :related-files-fn | 一个函数, 用于以更灵活的方式指定测试/实现/其他文件. |
4.5. 从函数返回 Projectile 命令 (Returning Projectile Commands from a function)
如果你希望动态定义编译命令, 你也可以将一个函数的符号引用传递到你的项目类型定义中:
(defun my/compile-command () "Returns a String representing the compile command to run for the given context" (cond ((and (eq major-mode 'java-mode) (not (string-match-p (regexp-quote "\\.*/test/\\.*") (buffer-file-name (current-buffer))))) "./gradlew build") ((eq major-mode 'web-mode) "./gradlew compile-templates") )) (defun my/test-command () "Returns a String representing the test command to run for the given context" (cond ((eq major-mode 'js-mode) "grunt test") ;; Test the JS of the project ((eq major-mode 'java-mode) "./gradlew test") ;; Test the Java code of the project ((eq major-mode 'my-mode) "special-command.sh") ;; Even Special conditions/test-sets can be covered )) (projectile-register-project-type 'has-command-at-point '("file.txt") :compile 'my/compile-command :test 'my/test-command)
如果你现在导航到一个扩展名为 *.java 且位于 ./tests/ 目录下的文件, 并按下 C-c c p, 你将看到 ./gradlew build 作为建议. 如果你导航到一个 HTML 文件, 编译命令将切换到 ./gradlew compile-templates.
这适用于:
- :configure
- :compile
- :compilation-dir
- :run
请注意, 你的函数必须返回一个字符串才能正常工作.
4.6. 相关文件位置 (Related file location)
:test-prefix 和 :test-suffix 无论文件扩展名或目录路径如何都应能工作, 并且对于简单的项目应该足够了. projectile-other-file-alist 变量也可以设置用于根据扩展名查找其他文件.
为了对实现/测试切换进行细粒度控制, 项目的 :test-dir 选项可以接受一个单参数函数 (实现目录的绝对路径), 并返回测试文件的目录. 这与 :test-prefix 和 :test-suffix 选项结合使用, 将用于确定测试文件的完整路径. 如果设置了此选项, 它将始终被遵守.
类似地, :src-dir 选项, 即 :test-dir 的类似物, 也可以接受一个函数, 并表现出与上面完全相同的行为, 只是它的参数对应于测试文件的目录, 它应该返回相应实现文件的目录.
为了保持行为一致, 建议这两个选项要么都设置为函数, 要么都不设置.
或者, 为了在各种项目中进行灵活的文件切换, 可以使用设置为自定义函数或自定义函数列表的 :related-files-fn 选项. 自定义函数接受从项目根目录开始的相对文件名, 并应返回一个包含以下可选键/值对的 plist 作为相关文件信息:
| 键 | 值 | 适用命令 |
|---|---|---|
| :impl | 如果给定文件是测试文件, 则为匹配的实现文件 | projectile-toggle-between-implementation-and-test, projectile-find-related-file |
| :test | 如果给定文件有测试文件, 则为匹配的测试文件 | projectile-toggle-between-implementation-and-test, projectile-find-related-file |
| :other | 如果给定文件有其他文件, 则为任何其他文件 | projectile-find-other-file, projectile-find-related-file |
| :foo | 除上述之外的任何键 | projectile-find-related-file |
对于每个值, 可以使用以下类型:
| 类型 | 含义 |
|---|---|
| string / a list of strings | 从项目根目录开始的相对路径. 实际存在于文件系统上的路径将被匹配. |
| a function | 一个谓词, 接受相对路径作为输入, 如果匹配则返回 t. |
| nil | 不存在匹配. |
注意:
- 对于包含许多源文件的大型项目, 返回字符串而不是函数会更快, 因为它不会遍历每个源文件.
- 没有键和键的值为 nil 之间存在行为差异. 只有当键不存在时, 才会尝试其他项目选项, 如 :testprefix 或 projectile-other-file-alist 机制.
- 如果 :test-dir 选项设置为一个函数, 在调用 projectile-toggle-between-implementation-and-test 时, 这将优先于为 :related-files-fn 设置的任何值.
示例 - 测试和实现使用相同的源文件名
(defun my/related-files (path) (if (string-match (rx (group (or "src" "test")) (group "/" (1+ anything) ".cpp")) path) (let ((dir (match-string 1 path)) (file-name (match-string 2 path))) (if (equal dir "test") (list :impl (concat "src" file-name)) (list :test (concat "test" file-name) :other (concat "src" file-name ".def")))))) (projectile-register-project-type ;; ... :related-files-fn #'my/related-files)
通过上面的例子, src/test 目录可以包含同名的测试文件和其实现文件. 例如, "src/foo/abc.cpp" 将匹配到 "test/foo/abc.cpp" 作为测试文件, 和 "src/foo/abc.cpp.def" 作为其他文件.
示例 - 每个扩展名有不同的测试前缀
一个自定义函数, 用于使用多种编程语言且具有不同测试前缀的项目.
(defun my/related-files(file) (let ((ext-to-test-prefix '(("cpp" . "Test") ("py" . "test_")))) (if-let* ((ext (file-name-extension file)) (test-prefix (assoc-default ext ext-to-test-prefix)) (file-name (file-name-nondirectory file))) (if (string-prefix-p test-prefix file-name) (let ((suffix (concat "/" (substring file-name (length test-prefix))))) (list :impl (lambda (other-file) (string-suffix-p suffix other-file)))) (let ((suffix (concat "/" test-prefix file-name))) (list :test (lambda (other-file) (string-suffix-p suffix other-file))))))))
projectile-find-related-file 命令也可用于查找和选择任何种类的相关文件. 例如, 自定义函数可以用 ':doc' 键指定相关文档. 请注意, projectile-find-related-file 目前只依赖于 :related-files-fn.
4.7. 相关文件自定义函数助手 (Related file custom function helper)
:related-files-fn 可以接受一个自定义函数列表, 以组合每个自定义函数的结果. 这允许用户编写多个自定义函数, 并将它们不同地应用于项目.
Projectile 包含几个用于生成常用自定义函数的助手.
| 助手名称和参数 | 目的 |
|---|---|
| groups KIND GROUPS | 将每个组中的文件关联为指定的类型. |
| extensions KIND EXTENSIONS | 将具有扩展名的文件关联为指定的类型. |
| test-with-prefix EXTENSION PREFIX | 将具有前缀和扩展名的文件关联为 :test 和 :impl. |
| test-with-suffix EXTENSION SUFFIX | 将具有后缀和扩展名的文件关联为 :test 和 :impl. |
每个助手都意味着 projectile-related-files-fn-helper-name 函数.
projectile-related-files-fn-helpers 的使用示例
(setq my/related-files (list (projectile-related-files-fn-extensions :other '("cpp" "h" "hpp")) (projectile-related-files-fn-test-with-prefix "cpp" "Test") (projectile-related-files-fn-test-with-suffix "el" "_test") (projectile-related-files-fn-groups :doc '(("doc/common.txt" "src/foo.h" "src/bar.h"))))) (projectile-register-project-type ;; ... :related-files-fn my/related-files)
4.8. 编辑现有项目类型 (Editing Existing Project Types)
你还可以编辑已存在的项目类型的特定选项:
(projectile-update-project-type 'sbt :related-files-fn (list (projectile-related-files-fn-test-with-suffix "scala" "Spec") (projectile-related-files-fn-test-with-suffix "scala" "Test")) :test-prefix nil :precedence 'high)
这将更改 related-files-fn 选项的值, 删除 test-prefix 选项, 并且 :precedence 'high 将 sbt 项目类型设置为优先于其他可能冲突的项目类型 (值 'low 会做相反的事情).
4.9. :test-dir/:src-dir vs :related-files-fn
虽然将 :test-dir 和 :src-dir 设置为字符串对于大多数目的已经足够, 但使用函数可以提供更大的灵活性. 例如, 考虑 (也使用 f.el):
(defun my-get-python-test-file (impl-file-path) "Return the corresponding test file directory for IMPL-FILE-PATH" (let* ((rel-path (f-relative impl-file-path (projectile-project-root))) (src-dir (car (f-split rel-path)))) (cond ((f-exists-p (f-join (projectile-project-root) "test")) (projectile-complementary-dir impl-file-path src-dir "test")) ((f-exists-p (f-join (projectile-project-root) "tests")) (projectile-complementary-dir impl-file-path src-dir "tests")) (t (error "Could not locate a test file for %s!" impl-file-path))))) (defun my-get-python-impl-file (test-file-path) "Return the corresponding impl file directory for TEST-FILE-PATH" (if-let* ((root (projectile-project-root)) (rel-path (f-relative test-file-path root)) (src-dir-guesses `(,(f-base root) ,(downcase (f-base root)) "src")) (src-dir (cl-find-if (lambda (d) (f-exists-p (f-join root d))) src-dir-guesses))) (projectile-complementary-dir test-file-path "tests?" src-dir) (error "Could not locate a impl file for %s!" test-file-path))) (projectile-update-project-type 'python-pkg :src-dir #'my-get-python-impl-dir :test-dir #'my-get-python-test-dir)
这试图识别使用 test 和 tests 作为顶级测试文件目录的项目. 使用 related-files-fn 选项的另一种方法可能是:
(projectile-update-project-type 'python-pkg :related-files-fn (list (projectile-related-files-fn-test-with-suffix "py" "_test") (projectile-related-files-fn-test-with-prefix "py" "test_")))
实际上, 这在不同位置查找测试文件方面要灵活得多, 但不会为你创建测试文件.
4.10. 自定义项目检测 (Customizing Project Detection)
项目检测非常简单 —— Projectile 只是运行一个项目检测函数列表 (projectile-project-root-functions), 直到其中一个返回项目目录.
这个函数列表是可定制的, 尽管 Projectile 有一些默认值, 但你可以根据需要进行调整.
让我们仔细看看 projectile-project-root-functions:
(defcustom projectile-project-root-functions '(projectile-root-local projectile-root-marked projectile-root-bottom-up projectile-root-top-down projectile-root-top-down-recurring) "A list of functions for finding project roots." :group 'projectile :type '(repeat function))
这里需要注意的重要一点是, 函数是按照它们在列表中的顺序被调用的, 因此列表前面的函数在项目检测方面具有更高的优先级. 让我们检查一下默认值:
- projectile-root-local 查找通过缓冲区局部变量 projectile-project-root 设置的项目路径. 通常你会通过 .dir-locals.el 设置这个变量, 它将优先于其他所有设置.
- projectile-root-marked 查找 .projectile (或者你设置为 projectile-dirconfig-file 值的文件). 其思想是, 通常如果你有一个 .projectile 文件, 你会希望它覆盖正常的项目根目录发现逻辑.
- projectile-root-bottom-up 将从当前文件夹 (在 Emacs lingo 中称为 default-directory) 向上查找项目标记文件/文件夹 (例如 .projectile, .hg, .git). 它将返回它发现的第一个匹配项. 假设很简单 —— 根标记只出现一次, 在项目的根文件夹中. 如果根标记出现在几个嵌套的文件夹中 (例如你有嵌套的 git 项目), 最底层的 (离当前目录最近的) 匹配项具有优先权. 你可以通过 projectile-project-root-files-bottom-up 自定义此函数识别的根标记.
- projectile-root-top-down 与之类似, 但它将返回最顶层的 (离当前目录最远的) 匹配项. 它可以通过 projectile-project-root-files 进行配置, 所有项目清单标记如 pom.xml, Gemfile, project.clj 等都放在那里.
- projectile-root-top-down-recurring 将查找可以出现在项目各个级别的项目标记 (例如 Makefile 或 .svn), 并将返回这些标记的最顶层匹配项.
默认顺序应该对大多数人来说都很好用, 但根据你项目的结构, 你可能想要调整它.
重新排序这些函数将改变项目检测, 但你也可以替换整个列表. 以下是如何将项目检测委托给 Emacs 的内置函数 vc-root-dir:
;; we need this wrapper to match Projectile's API (defun projectile-vc-root-dir (dir) "Retrieve the root directory of the project at DIR using `vc-root-dir'." (let ((default-directory dir)) (vc-root-dir))) (setq projectile-project-root-functions '(projectile-vc-root-dir))
类似地, 你可以这样利用内置的 project.el:
;; we need this wrapper to match Projectile's API (defun projectile-project-current (dir) "Retrieve the root directory of the project at DIR using `project-current'." (cdr (project-current nil dir))) (setq projectile-project-root-functions '(projectile-project-current))
4.11. 忽略文件 (Ignoring files)
4.11.1. 使用 .projectile 忽略文件 (a.k.a. dirconfig)
使用 alien 项目索引方法时, .projectile 的内容会被忽略.
如果你想在索引项目时指示 Projectile 忽略某些文件, 你可以在 .projectile 文件中这样做, 方法是添加每个要忽略的路径, 其中所有路径都相对于根目录并以斜杠开头. 所有要忽略的内容都应以 - 号开头. 或者, 根本没有任何前缀也意味着忽略后面的目录或文件模式. 这是一个典型 Rails 应用程序的例子:
-/log -/tmp -/vendor -/public/uploads
这只会忽略项目根目录下的文件夹. Projectile 也支持相对路径名忽略:
-tmp -*.rb -*.yml -models
你也可以忽略所有内容, 除了某些子目录. 当选择要保留的目录比选择要忽略的目录更容易时, 这很有用, 尽管你两者都可以做. 选择要保留的目录意味着所有其他内容都将被忽略.
例子:
+/src/foo +/tests/foo
请记住, 你只能包含子目录, 不能包含文件模式.
如果同时指定了要保留和要忽略的目录, 首先应用要保留的目录, 限制了考虑的文件. 然后将要忽略的路径和模式应用于该集合.
最后, 你可以覆盖被忽略的文件. 这在某些被你的 VCS 忽略的文件应该被 projectile 视为项目一部分时特别有用:
!/src/foo !*.yml
当一个路径被覆盖时, 其内容仍然受忽略模式的影响. 要也覆盖那些文件, 请用感叹号前缀指定它们的完整路径.
如果你想在你的 .projectile 文件中包含注释行, 你可以自定义变量 projectile-dirconfig-comment-prefix. 将其赋值为一个非 nil 的字符值, 例如 #, 将使 .projectile 文件中以该字符开头的行被视为注释而不是模式.
4.11.2. 使用项目索引工具忽略文件
如果你使用 hybrid 或 alien 索引策略, 忽略某些文件最简单的方法就是利用你用来进行项目索引的工具的配置.
例如, 在 git 的情况下, 你可以只调整 .gitignore.
然而, 有时你希望某些文件作为项目的一部分, 但出于某种原因你不想在 Projectile 中看到它们.
在这些情况下, 项目 dirconfig 文件 (.projectile) 可以成为进一步调整你想在 Projectile 中看到的内容的便捷方式.
4.12. 文件局部项目根定义 (File-local project root definitions)
如果你想为特定文件覆盖 projectile 项目根目录, 你可以设置文件局部变量 projectile-project-root. 这在你有一个项目中的文件与另一个项目相关时可能很有用 (例如, 一个 git 仓库中的 Org 文件对应于其他项目).
4.13. 存储项目设置 (Storing project settings)
不同项目之间, 即使是同一种语言, 有些东西也可能不同 —— 编码风格, 自动补全源等. 如果你需要根据所选项目设置一些变量, 你可以使用一个标准的 Emacs 功能, 叫做 Per-directory Local Variables (目录局部变量). 要使用它, 你必须在项目目录内创建一个名为 .dir-locals.el 的文件 (由常量 dir-locals-file 指定). 这个文件应该包含类似这样的内容:
((nil . ((secret-ftp-password . "secret") (compile-command . "make target-x") (eval . (progn (defun my-project-specific-function () ;; ... ))))) (c-mode . ((c-file-style . "BSD"))))
顶层 alist 中键为 nil 的成员适用于整个项目. 键名为 eval 的项将对其相应的值求值. 在上面的例子中, 这被用来创建一个函数. 它也可以用来例如将这样一个函数添加到一个键映射中.
你也可以用 s-p E (M-x projectile-edit-dir-locals RET) 快速访问或创建 dir-locals-file. 第三方包可以使用函数 projectile-add-dir-local-variable 和 projectile-delete-dir-local-variable 来存储它们的设置.
以下是一些如何将此功能与 Projectile 一起使用的例子.
4.14. 配置 Projectile 的行为 (Configuring Projectile’s Behavior)
Projectile 暴露了许多变量 (通过 defcustom), 允许用户自定义其行为. 目录变量可以用来在每个项目的基础上设置这些自定义.
你可以这样为一个项目启用缓存:
((nil . ((projectile-enable-caching . t))))
如果你的某个项目有一个你想让 Projectile 忽略的文件, 你可以通过以下方式自定义 Projectile:
((nil . ((projectile-globally-ignored-files . ("MyBinaryFile")))))
如果你想包装 Projectile 用来列出你仓库中文件的 git 命令, 你可以这样做:
((nil . ((projectile-git-command . "/path/to/other/git ls-files -zco --exclude-standard"))))
如果你想使用一个与 Projectile 为你的项目命名的不同的项目名称, 你可以用以下方式自定义它:
((nil . ((projectile-project-name . "your-project-name-here"))))
默认情况下, 编译缓冲区是不可写的, 这允许你例如按 g 来重启上一个命令. 将 projectile-<cmd>-use-comint-mode (其中 <cmd> 是 configure, compile, test, install, package, 或 run) 设置为一个非 nil 值, 可以让你的 projectile 编译缓冲区变为交互式的, 让你例如用 projectile-run-project 测试一个命令行程序.
(setq projectile-comint-mode t)
4.15. 项目缓冲区 (Project Buffers)
Projectile 提供了一系列操作, 这些操作作用于某个项目的打开的缓冲区 (例如 projectile-kill-buffers). 这里一个棘手的部分是 "special buffers" —— 基本上是没有对应文件的缓冲区 (例如 dired, scratch 等). Projectile 通过检查特殊缓冲区的 default-directory 来判断它是否属于一个项目, 这诚然可能会导致一些奇怪的结果 (例如, 如果你在访问一个属于项目的文件时, 创建了一个与项目无关的特殊缓冲区).
这就是为什么 Projectile 有几个用于处理项目缓冲区的配置选项 —— 即 projectile-globally-ignored-buffers 和 projectile-globally-ignored-modes. 它们都接受一个字符串或正则表达式列表, 这些列表将用于匹配缓冲区的名称或缓冲区的主模式.
以下是一些例子:
;; ignoring specific buffers by name (setq projectile-globally-ignored-buffers '("*scratch*" "*lsp-log*")) ;; ignoring buffers by their major mode (setq projectile-globally-ignored-modes '("erc-mode" "help-mode" "completion-list-mode" "Buffer-menu-mode" "gnus-.*-mode" "occur-mode"))
4.16. 配置项目的生命周期命令和其他属性 (Configure a Project’s Lifecycle Commands and Other Attributes)
有几个变量是打算通过 .dir-locals.el 来定制的.
- 用于配置 - projectile-project-configure-cmd
- 用于编译 - projectile-project-compilation-cmd
- 用于测试 - projectile-project-test-cmd
- 用于安装 - projectile-project-install-cmd
- 用于打包 - projectile-project-package-cmd
- 用于运行 - projectile-project-run-cmd
- 用于配置测试前缀 - projectile-project-test-prefix
- 用于配置测试后缀 - projectile-project-test-suffix
- 用于配置 related-files-fn 属性 - projectile-project-related-files-fn
- 用于配置 src-dir 属性 - projectile-project-src-dir
- 用于配置 test-dir 属性 - projectile-project-test-dir
当这些变量的值为默认的 nil 时, Projectile 会运行当前项目类型的默认命令. 你可以通过将它们设置为一个字符串来运行外部命令, 或者一个 Emacs Lisp 函数来覆盖此行为:
(setq projectile-test-cmd #'custom-test-function)
此外, 通过将变量 projectile-project-enable-cmd-caching 设置为 nil, 可以禁用命令的缓存. 这对于基于 preset 的 CMake 项目很有用.
默认情况下, Projectile 不会将连续重复的命令添加到其命令历史记录中. 要改变此行为, 你可以使用 projectile-cmd-hist-ignoredups. 默认值 t 表示连续的重复项被忽略, 值 nil 表示不忽略任何内容, 值 'erase' 表示只保留命令历史记录中的最后一个重复项.
5. 配置 (Configuration)
以 Emacs 的典型风格, Projectile 是极其可配置的. 几乎它的行为的每一个方面都可以被调整或扩展.
在本节中, 我们将介绍一些你可能想要微调的最常见的东西, 以使 Projectile 更适合你的工作流程.
5.1. 项目索引方法 (Project indexing method)
Projectile 有三种操作模式 —— 一种是可移植的, 在 Emacs Lisp 中实现 (因此它是 Emacs 的原生模式, 被称为原生索引方法), 另外两种 (hybrid 和 alien) 依赖于外部命令, 如 find, git 等来获取项目中的文件列表.
alien 索引方法最大化了 hybrid 索引方法的速度. 这意味着 Projectile 不会对外部命令返回的文件进行任何处理或排序, 你将获得可能的最大性能. 这种行为对大多数人来说很有意义, 因为他们通常会在他们的 VCS 配置中设置忽略项 (例如 .gitignore), 并且不会关心 Projectile 可能提供的任何额外的忽略/取消忽略/排序.
默认情况下, alien 方法在除 Windows 之外的所有操作系统上使用. 在 Projectile 2.0 之前, hybrid 是默认的 (但令人困惑的是, 当时 hybrid 被称为 alien).
要强制在所有操作系统中使用原生索引:
(setq projectile-indexing-method 'native)
要强制在所有操作系统中使用 hybrid 索引:
(setq projectile-indexing-method 'hybrid)
要强制在所有操作系统中使用 alien 索引:
(setq projectile-indexing-method 'alien)
这可以显著加快 Projectile 在 Windows 上的速度 (尤其是在大型项目上). 这种方法的缺点是它在 Windows 系统上支持得不好, 因为它需要在那里设置一些 Unix 工具. 如果有问题, 你总是可以使用原生索引模式.
5.2. Alien 索引 (Alien indexing)
alien 索引的工作方式非常简单 —— 它只是调用一个外部命令, 该命令返回项目内的文件列表. 对于版本控制的项目, Projectile 默认会使用 VCS 本身来获取文件列表. 例如, 这是 Projectile 用于 Git 项目的命令:
git ls-files -zco --exclude-standard
对于每个支持的 VCS, 都有一个匹配的 Projectile defcustom, 持有要为其调用的命令 (例如 projectile-git-command, projectile-hg-command 等).
如果你决定调整这些, 请记住命令应始终返回相对于项目根目录的文件列表, 并且生成的文件列表应以 0 分隔 (而不是换行分隔).
对于非 VCS 项目, Projectile 将调用 projectile-generic-command 中的任何内容. 默认是:
find . -type f -print0
安装 fd 是一个好主意, 它比 find 快得多. 如果找到 fd, projectile 将用它来替代 find.
5.3. 排序 (Sorting)
你可以通过自定义 projectile-sort-order 来选择 Projectile 如何对文件进行排序.
注意, 如果设置了 Alien 索引, Projectile 根本不会对文件进行排序.
默认是不对文件进行排序:
(setq projectile-sort-order 'default)
按最近打开的文件排序:
(setq projectile-sort-order 'recentf)
按最近活动的缓冲区, 然后是最近打开的文件排序:
(setq projectile-sort-order 'recently-active)
按修改时间 (mtime) 排序:
(setq projectile-sort-order 'modification-time)
按访问时间 (atime) 排序:
(setq projectile-sort-order 'access-time)
5.4. 缓存 (Caching)
5.4.1. 项目文件 (Project files)
由于索引一个大项目并不快 (尤其是在 Emacs Lisp 中), Projectile 支持缓存项目文件. 每当启用原生索引时, 缓存默认是启用的.
要无条件启用缓存, 使用这段代码:
(setq projectile-enable-caching t)
此时你可以尝试一个 Projectile 命令, 如 s-p f (M-x projectile-find-file RET).
运行 C-u s-p f 将在提示你跳转到文件之前使缓存失效.
按 s-p z 会将当前访问的文件添加到当前项目的缓存中. 通常在 Emacs 之外创建的文件会在你第一次打开它们时自动添加到缓存中.
通常缓存会持续到你的 Emacs 会话结束. 如果你想让缓存在 Emacs 会话之间持久化, 你应该将这个选项设置为 'persistent.
(setq projectile-enable-caching 'persistent)
现在项目缓存是持久的, 并将在 Emacs 重启期间保留. 每个项目都有自己的缓存文件, 它将被放置在项目的根文件夹中. 缓存文件的名称默认为 .projectile-cache.eld, 但如果你想, 你可以调整它:
(setq projectile-cache-file "foo.eld")
当你第一次为项目触发 "find file" 操作时, 缓存文件会自动加载到内存中.
你可以使用 M-x projectile-purge-file-from-cache 从缓存中清除单个文件, 或使用 M-x projectile-purge-dir-from-cache 清除整个目录.
在 Projectile 2.9 之前, 所有项目的缓存都被序列化到同一个文件中. 在 Projectile 2.9 中, 这一点被改变了, 现在每个项目在项目根目录下都有自己的缓存文件.
当 projectile-mode 启用时, Projectile 会在项目内文件在 Emacs 中被添加或删除时自动更新项目缓存. (这是通过文件钩子实现的) 这种行为可以这样禁用:
(setq projectile-auto-update-cache nil)
最后一件事 - 如果你正在使用 .projectile 并且它的最后修改时间比缓存文件最后更新的时间要新, 那么项目缓存将自动失效.
5.4.2. 文件存在缓存 (File exists cache)
Projectile 会进行许多文件存在性检查, 因为这是它识别项目根目录的方式. 通常这没问题, 但在某些情况下, 文件系统速度比平时慢得多, 可能会在打开文件和浏览目录时使 emacs "冻结" 很长一段时间.
最常见的例子是使用 TRAMP/ssh 与远程系统交互. 默认情况下, 所有远程文件存在性检查都被缓存.
要禁用远程文件存在缓存, 使用这段代码:
(setq projectile-file-exists-remote-cache-expire nil)
要将远程文件存在缓存的过期时间更改为 10 分钟, 使用这段代码:
(setq projectile-file-exists-remote-cache-expire (* 10 60))
你也可以为本地文件系统启用缓存, 这通常是不需要的, 但是可能的:
(setq projectile-file-exists-local-cache-expire (* 5 60))
5.5. 在项目目录之外使用 Projectile 命令 (Using Projectile Commands Outside of Projects Directories)
通常, 你会在某个项目目录内使用 Projectile 的命令. 然而, 如果你在项目之外调用一个命令, 默认情况下, 你会被提示切换到一个项目. 这个行为由 projectile-require-project-root 控制. 你可以让 Projectile 在项目文件夹之外简单地引发一个错误, 像这样:
(setq projectile-require-project-root t)
如果你想让 Projectile 在每个目录都可用 (即使没有项目文件):
(setq projectile-require-project-root nil)
通过这个设置, 如果你在项目之外调用 Projectile, 当前目录将被 Projectile 视为项目根目录.
如果你在你的主文件夹中启动 Projectile, 这可能不是一个好主意. :-)
5.6. 切换项目 (Switching projects)
默认情况下, projectile 在切换项目时不会在列表中包含当前项目. 如果你想包含当前项目, 自定义变量 projectile-current-project-on-switch.
当运行 projectile-switch-project (s-p p) 和 projectile-switch-open-project (s-p q) 时, Projectile 会调用 projectile-switch-project-action 中指定的命令 (默认为 projectile-find-file).
带前缀参数调用命令 (C-u s-p p 或 C-u s-p q) 将触发 Projectile Commander, 它让你能快速访问你可能想在项目上调用的最常见命令.
根据你的个人工作流程和习惯, 你可能更喜欢修改 projectile-switch-project-action 的值:
5.6.1. projectile-find-file
这是默认设置.
使用此设置, 一旦你通过 Projectile 的补全系统 (见下文) 选择了你的项目, 你将保留在补全系统中以选择一个要访问的文件. projectile-find-file 能够检索项目根目录下的所有子项目中的文件, 例如 Git 子模块. 目前, 只支持 Git. 未来将添加对其他 VCS 的支持.
5.6.2. projectile-commander
对于那些发现自己经常需要在项目切换时调用不同操作的人来说, 这是推荐的选项.
使用此设置, 在选择要切换到的项目后, 你将被提示用一个单字符助记符指定要执行的操作.
| 按键绑定 | 描述 |
|---|---|
| ? | Commander 帮助缓冲区. |
| D | 在 dired 中打开项目根目录. |
| R | 重新生成项目的 etags/gtags. |
| T | 在项目中查找测试文件. |
| V | 浏览有未提交更改的项目. |
| a | 在项目上运行 ag. |
| b | 切换到项目缓冲区. |
| d | 在项目中查找目录. |
| e | 在项目中查找最近访问的文件. |
| f | 在项目中查找文件. |
| g | 在项目上运行 grep. |
| j | 在项目中查找标签. |
| k | 关闭所有项目缓冲区. |
| o | 在项目缓冲区上运行 multi-occur. |
| r | 在项目中替换字符串 (用 C-u 运行将允许用户选择文件名模式和扩展名). |
| s | 切换项目. |
| v | 在 vc-dir 或 magit 中打开项目根目录. |
5.6.3. projectile-find-file-in-known-projects
与 projectile-find-file 类似, 但列出所有已知项目中的所有文件. 由于文件总数可能很大, 启用缓存以便后续使用是有益的.
5.6.4. projectile-find-file-dwim
如果光标在一个文件路径上, Projectile 首先尝试在项目中搜索该文件:
- 如果它只找到一个文件, 它会立即切换到该文件. 即使文件名不完整, 但当前项目中只有一个文件匹配光标处的文件名, 这也有效. 例如, 如果只有一个名为 "projectile/projectile.el" 的文件, 但当前文件名是 "projectile/proj" (不完整), projectile-find-file 仍然会立即切换到 "projectile/projectile.el", 因为这是唯一匹配的文件名.
- 如果它找到一个文件列表, 列表会显示出来供选择. 当一个文件名在项目中出现多次, 或者光标处的文件名是项目中两个以上文件的前缀时, 会显示一个文件列表. 例如, 如果 `projectile-find-file` 在一个像 "projectile/" 这样的文件路径上执行, 它会列出该目录的内容. 如果它在一个部分文件名如 "projectile/a" 上执行, 会呈现该目录中包含字符 'a' 的文件列表.
- 如果它什么也没找到, 显示项目中所有文件的列表供选择.
5.6.5. projectile-dired
(setq projectile-switch-project-action #'projectile-dired)
使用此设置, 一旦你选择了你的项目, 项目的顶级目录会立即在一个 dired 缓冲区中为你打开.
5.6.6. projectile-find-dir
(setq projectile-switch-project-action #'projectile-find-dir)
使用此设置, 一旦你选择了你的项目, 你将保留在 Projectile 的补全系统中以选择你项目的一个子目录, 然后该子目录会在一个 dired 缓冲区中为你打开. 如果你使用这个设置, 那么你可能也想设置
(setq projectile-find-dir-includes-top-level t)
以便在你想要选择顶级目录时允许这样做.
5.7. 补全选项 (Completion Options)
Projectile 支持当今存在的所有主要的 minibuffer 补全包. 通常它只会检测你正在使用的 (例如 ivy), 但你可以通过变量 projectile-completion-system 强制使用一个特定的补全系统.
历史上, projectile-completion-system 默认为 ido, 但这在 2.3 版本中被改变了. 如果你从旧版本的 Projectile 更新, 你可能需要在你的 Emacs 配置中启用 ido-mode.
5.7.1. 自动 (默认) (Auto (default))
默认情况下, Projectile 会根据模式变量 ido-mode, ivy-mode 和 helm-mode 检测正在使用的补全系统. 如果这些都没有被激活, 就使用默认的补全系统.
除非出于某种原因你想为 Projectile 使用一个不同于 Emacs 其余部分的补全系统 (例如, 你通常使用 icomplete-mode, 但想和 Projectile 一起使用 ido-mode), 你可能不想手动选择一个特定的补全系统.
5.7.2. 基础 (Emacs 默认) (Basic (Emacs’s default))
如果你想使用 Emacs 的标准补全 (基于 completing-read), 选择这个选项:
(setq projectile-completion-system 'default)
你可能想将默认补全与 icomplete-mode 结合以获得最佳效果. Emacs 27 向 icomplete 添加了 fido-mode.
如果你正在使用 fido-mode, Projectile 将使用默认的补全系统. 对于 vertico 也是如此, 它也依赖于默认的补全系统.
5.7.3. Ido
ido 补全系统非常流行, 并且内置在 Emacs 中.
(setq projectile-completion-system 'ido)
如上所述, 如果启用, Projectile 会自动检测 ido-mode, 所以上面的配置在大多数时候是不需要的.
如上所述, 如果你打算使用 ido 补全, 强烈建议你安装可选的 flx-ido 包, 它提供了比 ido 内置的 flex 匹配更强大的替代方案.
5.7.4. Ivy (推荐) (Ivy (recommended))
另一个补全选项是 ivy:
(setq projectile-completion-system 'ivy)
如上所述, 如果启用, Projectile 会自动检测 ivy-mode, 所以上面的配置在大多数时候是不需要的.
5.7.5. 自定义补全函数 (Custom Completion Function)
你也可以将 projectile-completion-system 设置为一个函数:
(setq projectile-completion-system #'my-custom-completion-fn) (setq projectile-completion-system (lambda (prompt choices) ;; ... ))
一个自定义补全函数的例子是这个, 它只显示文件名 (不包括路径), 如果选择的文件不唯一, 会出现另一个带有相对于项目根目录名称的补全.
5.8. 项目特定的编译缓冲区 (Project-specific Compilation Buffers)
这会影响所有构建在 projectile—run-project-cmd 之上的命令, 例如:
- projectile-configure-project
- projectile-run-project
- projectile-test-project
- projectile-install-project
- projectile-package-project
通常, 由这些命令创建的缓冲区会在项目之间共享 (覆盖), 但也可以使编译缓冲区名称是项目特定的. 这需要用户设置:
(setq projectile-per-project-compilation-buffer t)
当不在项目中时, 这两者都能正常降级.
5.9. 重新生成标签 (Regenerate tags)
要能通过 projectile-tags-command 重新生成项目的标签, 你应该安装并将 Exuberant Ctags 添加到 PATH, 而不是 Emacs 发行版附带的普通 ctags.
5.10. 空闲计时器 (Idle Timer)
Projectile 可以配置为每当 Emacs 在一个项目中并且已经空闲了 projectile-idle-timer-seconds 秒 (默认为 30 秒) 时运行钩子 projectile-idle-timer-hook. 要启用此功能, 运行:
M-x customize-group RET projectile RET
并将 projectile-enable-idle-timer 设置为非 nil. 默认情况下, projectile-idle-timer-hook 运行 projectile-regenerate-tags. 使用 add-hook 向钩子添加额外的函数:
(add-hook 'projectile-idle-timer-hook #'my-projectile-idle-timer-function)
5.11. Mode line 指示器 (Mode line indicator)
默认情况下, Projectile 的 minor mode 指示器以 " Projectile[ProjectName:ProjectType]" 的形式出现. 这可以通过几个自定义变量进行配置:
- projectile-mode-line-prefix (默认为 " Projectile") 控制 mode-line 的静态部分
- projectile-dynamic-mode-line (默认为 t) 控制是否显示 mode-line 的项目名称和类型部分
projectile-mode-line-function (默认为 projectile-default-mode-line) 控制实际被调用以生成 mode-line 的函数. 如果你想显示不同的信息, 你应该提供一个自定义函数来替换默认的, 例如 (setq projectile-mode-line-function '(lambda () (format " Proj[%s]" (projectile-project-name))))
在编辑远程文件 (通过 TRAMP) 时, 项目名称和类型不会出现, 因为在那里重新计算项目名称是一个相当慢的操作, 并且会稍微减慢打开文件的速度. 它们也不会出现在非文件缓冲区中, 因为它们是通过 find-file-hook 更新的.
5.12. 特定项目类型的配置 (Project-type-specific Configuration)
5.12.1. CMake
Projectile 支持 CMake presets. Preset 支持默认是禁用的, 但可以通过将 projectile-enable-cmake-presets 设置为非 nil 来启用. 启用 preset 支持后, Projectile 将解析 preset 文件, 并在执行生命周期命令时呈现特定于命令的 preset. 此外, 还包括一个 no preset 选项, 用于手动输入命令.
Preset 支持需要支持 preset 的 CMake 版本, 并且 json-parse-buffer 可用.
6. 扩展 (Extensions)
有许多包构建在 Projectile 提供的基本功能之上:
- counsel-projectile 提供 Ivy 集成
- helm-projectile 提供 Helm 集成
- persp-projectile 提供 perspective.el 集成
- projectile-rails 为 Ruby on Rails 项目提供额外功能
- org-projectile 提供创建与 Projectile 项目关联的 org-mode TODO 的功能.
- treemacs-projectile 提供 treemacs 和 Projectile 之间的集成.
projectile-speedbar 提供 speedbar 和 Projectile 之间的集成.
MELPA 列出了另外 20 个 Projectile 扩展, 但我太懒了, 不想在这里全部列出.
还有一些包如果存在 Projectile 就会使用它. 这里有几个例子:
- neotree
- lsp-mode
7. Projectile vs project.el
由于两个项目都是动态目标, 此页面可能无法准确反映它们之间的差异.
Projectile 是在 Emacs 没有内置项目导航功能的时候创建的. 最终, 这种情况在 Emacs 25 中随着 project.el 的引入而改变, 许多人一直在问, 使用 Projectile 而不是内置库有什么优势. 本文档的这一部分将尝试回答这个问题.
当 project.el 最初被引入时, 它的功能集相当简陋, 但它在每个 Emacs 版本中都增加了一些新功能, 到 2024 年左右, 它应该能满足大多数普通用户的需求. 我猜 Projectile 启发了 project.el 中的许多功能, 而 Projectile 本身则受到了像 IntelliJ IDEA 这样的工具的启发.
7.1. TLDR;
如果 project.el 中的功能对你来说足够好, 那么你可能应该使用 project.el.
7.2. 概览 (At a glance)
截至 2024 年初. (Projectile 2.8 和 Emacs 29 中的 project.el)
| Projectile | project.el | |
|---|---|---|
| 创建于 | 2011 | 2014 [1] |
| 支持的 Emacs 版本 | 26+ | 26+ [2] |
| 内置 | 否 | 是 |
| 包可用性 | MELPA, MELPA Stable, NonGNU ELPA | GNU ELPA |
| 索引策略 | 3 (native, hybrid, alien) | 1 (类似于 Projectile 的 alien 策略) |
| 原生项目配置 | 是 (.projectile) | 否 |
| 项目缓存 | 是 | 否 |
| 内置项目类型 | 60+ | n/a |
| 配置选项数量 | ~90 | ~20 |
| 命令数量 | 80+ | 30+ |
| Minor Mode | 是 | 否 |
| 全局键绑定 | 需要用户设置 | 开箱即用 (C-x p) |
| 与 xref 的集成 | 是 | 是 |
| 功能集 | 广泛 | 基础 |
| 扩展 | ~20 | ~10 |
| 贡献流程 | 轻量级 | 有些复杂 (Emacs 的标准) |
7.3. 显著差异 (Notable Differences)
7.3.1. Minor Mode vs 全局键映射 (Global Keymap)
Projectile 大量使用 projectile-mode, 它提供了一些额外的功能 (例如 modeline 中的项目状态). 你可以在没有它的情况下使用 Projectile, 但我猜很少有人这样做. 即使模式未激活, Projectile 的大部分功能也能正常工作.
另一方面, project.el 只是在全局键映射下添加了自己的键映射 (使用前缀 C-x p). 当然, 如果你愿意, 你也可以为 Projectile 做同样的事情.
7.3.2. 项目索引策略 (Project Indexing Strategies)
Projectile 有多种项目索引策略, 以涵盖各种用例. project.el 只有一个, 或多或少与 Projectile 的 alien 策略相同. 诚然, 这可能是最常用的策略.
7.3.3. .projectile
Projectile 有自己的项目标记/配置文件. 这是项目早期的遗留物, 当时我想构建一个不依赖第三方应用程序的工具, 它今天的重要性不那么大了. (除非你使用 native 或 hybrid 索引, 否则它将被完全忽略)
7.4. Projectile 的优点 (Projectile’s Pros)
- Projectile 针对 Emacs 26, 因此即使是旧的 Emacs 版本也能获得所有功能
- Projectile 有不同的项目索引策略, 这在不同情况下为你提供了很大的灵活性
- Projectile 开箱即用地支持许多项目类型 (例如 ruby, Rails, cabal 和 dune)
- Projectile 有更多的功能, 尽管可以说其中一些很少需要
- Projectile’s Commander 对于项目切换非常酷!
- 为 Projectile 做贡献更容易
- Projectile 托管在 GitHub 上
- 它接受 pull requests
- 你不需要签署贡献者协议
- Projectile 有更广泛的文档
- 你可以将其与 project.el 的文档进行比较, 自己决定
7.5. Projectile 的缺点 (Projectile’s Cons)
- 第三方依赖, 在 Emacs 之外开发.
这既是优点也是缺点, 取决于个人观点, 但我知道许多人更喜欢内置包, 所以我把它放在 "缺点" 下.
- 理论上, 内置包应该得到更好的维护 (或者至少更长时间), 因为它们背后有 Emacs 团队.
- 虽然 Projectile 有一个丰富的扩展生态系统, 但在足够长的时间内, project.el 可能会占据领先地位.
- 由于其更大的体积, 可以说 Projectile 比 project.el 更复杂
- 诚然, 如果我今天重新开始 Projectile, 我会做一些不同的事情, 但我不认为 Projetile 的核心复杂性很高.
- 它是在 Emacs 25.1 中引入的.
- 请注意, 与旧版 Emacs 捆绑的版本将缺少一些现代功能. project.el 也作为一个包分发, 因此你仍然可以在旧版 Emacs 上获得一些较新的功能.
8. FAQ (Frequently Asked Questions)
8.1. 为什么你把它命名为 Projectile?
我想要一个不像 project.el 那么无聊的名字, 并且暗示你与项目的交互将显著加快. :-)
有趣的是, 在 Projectile 创建几年后, 另一个类似的项目 project.el 诞生了. 尽管它的名字很无聊, 但它是一个很棒的项目, 而且恰好是 Emacs 的一部分.
8.2. Projectile 与内置的 project.el 相比如何?
project.el 是在 2016 年发布的 Emacs 25.1 中引入的, 从那时起很多人一直在问这两个包如何比较.
在内部, 它们有很多差异 (例如, 对项目发现和索引的不同方法), 但从用户的角度来看, 它们现在 (大约 2021 年) 可能非常相似. 从历史上看, Projectile 有更多的功能, 但多年来其中许多功能都进入了 project.el. 如果你所需要的只是一个在项目文件之间快速跳转或在项目中搜索/替换的方法, 那么两者都不会错.
对我来说, Projectile 最大的优势将永远是它对社区更友好 (例如, 开发在 GitHub 上进行), 并且它不受 FSF 贡献者协议的限制, 这意味着任何人都可以轻松地为该项目做出贡献. 当然, 这也带来了你必须自己安装 Projectile 的缺点.
Projectile 一个未被充分认识的优势是它有更多的文档. 好吧, 这只有当你是一个喜欢阅读文档的稀有人士时才是一个优势.
你可以在这里找到更详细的比较.
8.3. Projectile 是否提供与 project.el 的任何集成?
从 Projectile 2.7 开始, Projectile 将自动将其项目发现函数插入到 project.el 中. 你可以在文档的 "Usage" 部分找到有关此主题的更多信息.
8.4. Projectile 是否与 TRAMP 一起工作?
是的, 它支持. 然而, 我自己不使用 TRAMP, 所以我从未过多关注 TRAMP 的支持. 它主要是由社区维护的.
8.5. 为什么 Projectile 没有为其键绑定设置默认前缀?
历史上, Projectile 曾经使用 C-c p 作为其所有命令的前缀. 我选择 C-c p 前缀时完全意识到这违反了一个非常成熟的 Emacs 惯例, 主要是因为它感觉很实用, 而且因为对许多人来说按 C-c C-p 不太方便.
最终, 这在 Projectile 2.0 中被改变了. 这就是为什么目前 Projectile 要求用户明确为其命令选择一个前缀键.
8.6. Projectile 有哪些依赖?
Projectile 无需任何外部依赖即可开箱即用. 然而, 如果你安装了各种工具, 它们将在适当时被自动使用以提高性能.
在版本控制仓库内部, 如果安装了 VC 工具, 它们将被用来更有效地列出文件. 支持的工具包括 git, hg, fossil, bzr, darcs, pijul, svn, sapling 和 jujutsu.
在版本控制仓库外部, 如果安装了文件搜索工具, 它们将被用来进行比纯 Elisp 更快的搜索. 支持的工具包括 fd 和 GNU/BSD find.
默认情况下, 如果安装了 fd, 它也会在 Git 仓库内部用作 git ls-files 的替代品, 因为 git ls-files 有一个限制, 即它也会列出已删除的文件, 直到删除被暂存, 这可能会令人困惑. 你可以通过将 projectile-git-use-fd 设置为 nil 来在这种情况下禁用 fd.
8.7. 你需要帮助清理所有堆积起来的 ticket 吗?
当然! 在我们的 issue tracker 中, 我们有大量的 ticket 标记为 Help Wanted 或 Good First Issue, 如果你想帮忙, 可以尝试一下!
9. 故障排除 (Troubleshooting)
如果你遇到问题, 这里有一些可以帮助你诊断问题的技巧.
通常, 配置 Emacs 在出错时输出回溯信息 (而不是仅仅在 Messages 缓冲区中记录错误) 是一个不错的主意. 你可以使用 M-x toggle-debug-on-error 来切换此行为.
9.1. 调试 Projectile 命令 (Debugging Projectile commands)
Emacs 具有一个功能超强的内置 Emacs Lisp 调试器, 使用它是诊断任何类型问题的最佳方式.
这是一篇关于使用调试器的很棒的速成课程.
要调试某个命令, 你需要做以下事情:
- 找出你想调试的命令的名称 (例如, 通过使用 C-h k 查看哪个命令与某个键绑定相关联)
- 找到命令的源代码 (例如, 通过使用 M-x find-function RET function-name)
- 在函数体内按 C-u C-M-x
- 再次运行该命令
此时你将进入调试器, 你可以逐步前进直到找到问题所在.
9.2. 分析 Projectile 命令的性能 (Profiling Projectile commands)
Emacs 自带一个内置的性能分析器. 使用它非常简单:
- 用 M-x profiler-start 启动它.
- 调用一些命令.
用 M-x profiler-report 获取报告.
如果你打算与某人分享性能分析结果, 用 C-x C-w 将报告缓冲区保存到文件中是个好主意.
9.3. 常见问题 (以及如何解决它们) (Commonly encountered problems (and how to solve them))
9.3.1. 使用某个命令导致 Emacs 冻结一段时间
有时 Projectile 命令可能会挂起一段时间 (例如, 由于 bug 或配置问题). 这类问题非常烦人, 但相对容易调试. 在这种情况下, 你可以采取以下几个步骤:
- 执行 M-x toggle-debug-on-quit
- 重现问题
- 在挂起大约 10 秒后按 C-g
这将显示一个包含整个函数栈的回溯信息, 包括函数参数. 因此你应该能够弄清楚发生了什么 (或者至少正在请求什么).
9.3.2. 我用 package.el 升级了 Projectile, 但没有任何变化
Emacs 不会加载新文件, 它只将它们安装到磁盘上. 要看到更改的效果, 你必须重启 Emacs.
9.3.3. 有些命令在 fish shell 下工作不正常
我不确定是什么原因造成的 (可能是不同的引用规则), 但很容易修复它:
(setq shell-file-name "/bin/bash")
总的来说, 我注意到 Emacs 和 fish 的配合不是很好.
10. 贡献 (Contributing)
10.1. 问题 (Issues)
在 GitHub issue tracker 和我们的 discussions board 上报告问题并提出功能和改进建议.
如果你想提交一个 bug, 请提供我们 issue reporting template 中列出的所有必要信息 (当你创建一个新的 GitHub issue 时它会自动加载).
通常, 尝试在隔离环境中重现 (晦涩的) bug 是个好主意. 你可以通过克隆 Projectile 的 GitHub 仓库并在其中运行 eldev emacs 来做到这一点 (你需要先安装 Eldev 工具). 这将启动一个只加载了最新版本 Projectile 的 Emacs. 通过从最新的代码重新开始, 我们可以确保手头的问题不是已经修复了, 或者是由与其他包的交互引起的.
10.2. 补丁 (Patches)
任何形式的补丁总是受欢迎的! GitHub pull requests 甚至更好! :-)
在提交补丁或 pull request 之前, 请确保所有测试都通过, 并且你的补丁符合贡献指南.
10.2.1. 在批处理模式下运行测试 (Running the tests in batch mode)
$ cd /path/to/projectile $ eldev test
如果你需要确保依赖项是最新版本:
$ eldev update
在 Projectile 作为 Emacs 包安装的情况下运行所有测试 (即, 字节编译等; 这不会影响你的正常 Emacs):
$ eldev -p test
测试应该在 shell-mode 或 term-mode 中正常运行. 也可以使用 M-x compile (或 helm-make).
10.3. 文档 (Documentation)
好的文档和好的代码一样重要. 请考虑改进和扩展本手册和社区 wiki.
10.3.1. 处理文档 (Working on the Docs)
手册是从 Projectile 的 GitHub 仓库的 doc 文件夹中的 AsciiDoc 文件生成的, 并发布到 https://docs.projectile.mx. Antora 用于将手册转换为 HTML. 文件系统布局在 https://docs.antora.org/antora/latest/component-structure/ 中有描述.
要对该手册进行更改, 你只需更改 doc 下的文件. 该手册将定期手动重新生成.
10.3.2. 安装 Antora (Installing Antora)
这里的说明假定你已经安装了 (正确版本的) node.js.
安装 Antora 非常简单:
$ cd docs.projectile.mx $ npm install
如果你遇到任何问题, 请查看详细的安装说明.
10.3.3. 构建网站 (Building the Site)
你可以从 docs.projectile.mx 仓库在本地构建文档.
$ cd docs.projectile.mx $ make build
完成初始设置后, 你可以使用 make 将更改推送到网站:
$ make deploy
你需要对该仓库有提交权限才能使其工作.
要检查生成的站点, 你只需在你喜欢的浏览器中打开 docs/index.html.
如果你想更改手册的页面结构, 你需要编辑 nav.adoc.
10.4. 捐赠 (Donations)
你可以通过 PayPal, Patreon 和 GitHub Sponsors 支持 Projectile 的开发.