cider 中文文档
Table of Contents
- 1. CIDER
- 2. 入门
- 2.1. Installation
- 2.2. Up and Running
- 2.2.1. 从 Emacs 启动 nREPL 服务器
- 2.2.2. 自动注入依赖
- 2.2.3. 启用 nREPL JVMTI agent
- 2.2.4. 无项目 Jacking-in
- 2.2.5. Universal jack-in
- 2.2.6. 自定义 Jack-in 命令行为
- 2.2.7. 覆盖 Jack-In 命令
- 2.2.8. 启动 nREPL 服务器而不尝试连接它
- 2.2.9. 连接到正在运行的 nREPL 服务器
- 2.2.10. 使用远程主机
- 2.2.11. 使用容器 (Docker 或其他)
- 2.2.12. 通过 unix domain file socket 连接
- 2.2.13. 下一步做什么?
- 2.3. nREPL Middleware Setup
- 3. ClojureScript 设置
- 4. JVM以外平台
- 5. 使用 CIDER
- 6. 使用 REPL
- 6.1. Basic Usage
- 6.2. Keybindings
- 6.3. Configuration
- 6.3.1. 连接时的行为
- 6.3.2. 切换时的行为
- 6.3.3. 自定义 REPL 提示符
- 6.3.4. TAB 补全
- 6.3.5. 自定义 Return 键的行为
- 6.3.6. REPL 输出时自动滚动
- 6.3.7. 显示 REPL 窗口边界前的输出
- 6.3.8. 自动修剪 REPL 缓冲区
- 6.3.9. 结果前缀
- 6.3.10. 在 REPL 中设置 ns
- 6.3.11. 自定义初始 REPL 命名空间
- 6.3.12. 自定义 REPL 缓冲区的名称
- 6.3.13. Font-locking
- 6.3.14. 结果的 Font-locking
- 6.3.15. 在 REPL 中进行漂亮打印
- 6.3.16. 在 REPL 中显示图像
- 6.3.17. REPL 类型检测
- 6.3.18. REPL 历史记录
- 6.4. REPL History
- 7. 测试
- 8. 调试
- 9. 配置
- 10. 缩进规范
- 11. 已知问题
- 12. 问题排查
- 13. FAQ
- 13.1. 为什么这个项目叫 CIDER?
- 13.2. CIDER 是 SLIME 的一个分支吗?
- 13.3. 为什么 CIDER 不使用官方的 Clojure REPL?
- 13.4. 为什么 CIDER 不使用 tooling REPL?
- 13.5. 为什么 CIDER 对 nREPL 有自己的依赖?
- 13.6. 我可以和 Socket REPL 一起使用 CIDER 吗?
- 13.7. 我可以在其他编辑器中使用 CIDER 吗?
- 13.8. 我可以在没有
cider-nrepl的情况下使用 CIDER 吗? - 13.9. 我可以在没有 Leiningen/Boot/~tools.deps~ 的情况下使用 CIDER 吗?
- 13.10.
cider-jack-in和cider-connect有什么区别? - 13.11. 我可以在哪里寻求帮助?
- 14. Additional Packages
- 15. Additional Resources
- 16. 贡献
1. CIDER
CIDER 是 Clojure(Script) 的摇滚交互(Clojure Interactive Development Environment that Rocks)式开发环境!
CIDER 通过支持 Clojure 中的交互式编程来扩展 Emacs. 这些功能围绕 cider-mode 展开, cider-mode 是一个 Emacs minor-mode, 它补充了 clojure-mode 和 clojure-ts-mode.
clojure-mode 支持编辑 Clojure 源文件, 而 cider-mode 增加了与正在运行的 Clojure 进程交互的支持, 用于编译, 调试, 定义和文档查找, 运行测试等.
1.1. 概述
CIDER 旨在提供一种交互式开发体验, 类似于在 Emacs Lisp, Common Lisp (使用 SLIME), Scheme (使用 Geiser) 和 Smalltalk 中编程时所获得的体验.
1.1.1. SLIME 的启发
CIDER 最初的灵感来源于强大的 Common Lisp 交互式开发环境 SLIME. 最初我们开始将 SLIME 的核心功能应用于 Clojure, 但随着时间的推移, CIDER 在许多方面变得与 SLIME 大相径庭.
想了解更多关于 CIDER 早期历史的信息, 请查看此演示文稿.
程序员被期望以一种非常动态和增量的方式进行编程, 不断地重新求值现有的 Clojure 定义, 并向他们正在运行的应用程序中添加新的定义.
在使用 CIDER 时, 永远不会停止/启动一个 Clojure 应用程序 —— 会不断地与它互动并改变它, 这种方法被称为“交互式编程/开发”或“REPL 驱动编程”.
我们更喜欢前一个术语, 并将在 CIDER 的整个文档中使用它.
可以在“交互式编程”部分找到有关典型 CIDER 工作流程的更多详细信息.
CIDER 构建于 nREPL (Clojure 的网络化 REPL 服务器) 之上.
CIDER 的基本架构如下所示:
Clojure 代码由 nREPL 服务器执行. CIDER 向服务器发送请求并处理其响应. 服务器的功能由额外的 nREPL middleware 增强, 这些 middleware 专为满足像 CIDER 这样的交互式开发环境的需求而设计.
一个与编辑器无关的基础
为 CIDER 开发的许多 nREPL middleware 是与编辑器无关的, 并且也被其他 Clojure 开发环境所使用 (例如 vim-fireplace & Calva).
1.2. 功能
CIDER 包含大量功能. 以下是其中的一部分 (排名不分先后):
- 交互式代码求值
- 强大的 REPL
- 代码补全
- 代码重载
- 定义和文档查找
- 增强的语法高亮和缩进
- 增强的错误报告
clojure.test集成clojure.spec集成- 交互式调试器
- 数据检查器
- 与 Java 日志框架集成
- 性能分析与追踪
- ClojureScript 支持
- 支持其他 Clojure 平台 (例如 ClojureCLR, babashka 和 nbb)
以及更多… 本手册的其余部分将详细探讨 CIDER 的功能.
1.3. CIDER 实战
下面可以看到一个典型的 CIDER 会话.
Figure 1: CIDER Screenshot
在这里, 用户直接从源缓冲区检查了 clojure.core/merge 的文档, 然后跳转到 REPL 缓冲区尝试一些东西.
这里还有一个 CIDER 核心功能的视频演示:
可以在“其他资源”页面上找到其他几个演示视频.
1.4. 下一步是什么?
那么, 接下来该做什么呢? 虽然可以按任何自己喜欢的方式阅读文档, 但这里有一些建议:
- 安装 CIDER 并使其运行起来
- 熟悉交互式编程和
cider-mode - 根据喜好配置 CIDER
- 探索更高效的其他包
2. 入门
2.1. Installation
安装 CIDER 的规范方法是通过 package.el (Emacs 的内置包管理器).
也可以使用其他包管理器安装 CIDER, 但这超出了本指南的范围.
2.1.1. 先决条件
需要安装 Emacs, 最好是其最新的稳定版. Emacs 新手就先过一遍 Emacs 导览 和内置教程 (只需按 C-h t).
CIDER 官方支持 Emacs 27.1+, Java 8+, 和 Clojure(Script) 1.10+. 如果需要使用早期版本, 请查看兼容性矩阵.
还需要最新版本的构建工具 (Clojure CLI, Leiningen, 或 Gradle) 才能通过 cider-jack-in 启动 CIDER. 建议使用最新的稳定版本.
2.1.2. 通过 package.el 安装
CIDER 在所有主要的 package.el 社区维护的仓库中都可用 - NonGNU ELPA, MELPA Stable 和 MELPA.
NonGNU ELPA 是 Emacs 的标准仓库之一, 在 Emacs 28+ 上默认启用. 如果想使用 MELPA 和 MELPA Stable, 需要自己设置.
可以使用以下命令安装 CIDER:
M-x package-install <RET> cider <RET>
或者通过将这段 Emacs Lisp 代码添加到 Emacs 初始化文件 (.emacs 或 init.el) 中:
(unless (package-installed-p 'cider) (package-install 'cider))
如果安装失败, 尝试刷新包列表:
M-x package-refresh-contents <RET>
需要注意的是, MELPA 包是从 master 分支自动构建的, 这意味着处于开发的最前沿. 这有利有弊; 能尝鲜新功能, 也可能不时遇到一些 bug.
尽管如此, 从 MELPA 安装是获取 CIDER 的一种合理方式.
master 分支通常非常稳定, 其中的严重回归问题通常会很快得到修复.
如果不想 (或不能) 等待 MELPA 重建 CIDER, 可以在本地自己构建和安装最新的 MELPA 包. 请查看这篇文章以获取有关该主题的详细信息.
如果对处于 CIDER 开发的最前沿感到担忧, 可以随时将 CIDER 固定到使用 NonGNU ELPA 或 MELPA Stable, 如下所示:
;; 固定到 NonGNU ELPA (add-to-list 'package-pinned-packages '(cider . "nongnu") t) ;; 固定到 MELPA Stable (add-to-list 'package-pinned-packages '(cider . "melpa-stable") t)
CIDER 有一些依赖项 (例如 queue & seq) 仅在 GNU ELPA 仓库中可用. 这是 Emacs 中唯一默认启用的包仓库, 不应该禁用它!
2.1.3. 通过 use-package 安装
use-package 可以用于通过 package.el 的仓库 NonGNU ELPA, MELPA Stable 和 MELPA 来安装 CIDER.
如果想安装 master 分支中的 CIDER 版本, 在 Emacs 初始化文件 (.emacs 或 init.el) 中声明以下内容:
(use-package cider :ensure t)
然而, 如果想更保守一些, 只使用 CIDER 的稳定版本, 可以这样声明:
(use-package cider :ensure t :pin melpa-stable)
放置上述 s-expression 之一后, 通过输入 C-x C-e 来求值它, 使其生效.
有关 use-package 的更多配置选项, 请查阅官方的 use-package 仓库.
2.1.4. 手动安装
不鼓励手动安装 CIDER. 手动安装相对复杂,因为它需要手动安装依赖项. 请查看 Hacking on CIDER 部分了解更多详情.
2.2. Up and Running
要使用 CIDER, 需要将其连接到一个正在运行的, 程序关联的 nREPL 服务器. 大多数 Clojure 开发者使用标准的构建工具, 如 tools.deps, Leiningen 或 Gradle, CIDER 可以自动与这些工具配合, 快速上手.
但这些工具不是必需的; CIDER 可以连接到一个已经启动并独立管理的 nREPL 服务器.
CIDER 会自动与 Leiningen 2.9.0+ 或最新的 tools.deps 配合使用. 不支持旧版本.
有两种方法可以将 CIDER 连接到 nREPL 服务器:
- CIDER 可以从 Emacs 为项目启动一个 nREPL 服务器.
- 可以将 CIDER 连接到一个已经运行的, 独立管理的 nREPL 服务器.
以下各节将描述这两种方法.
2.2.1. 从 Emacs 启动 nREPL 服务器
对Clojure 项目, 使用 CIDER 为其启动一个 nREPL 会话, 只需访问该项目的一个文件, 然后输入 M-x cider-jack-in RET.
CIDER 将启动一个 nREPL 服务器并自动连接到它.
在 Clojure(Script) 缓冲区中, cider-jack-in 命令绑定到 C-c C-x (C-)j (C-)j.
jacking-in 的过程非常简单:
- CIDER 确定项目的构建系统 (例如 Leiningen) 并选择必要的命令来启动 nREPL 服务器.
- CIDER 调用 shell 并运行像
lein repl :headless这样的命令来启动 nREPL 服务器. - CIDER 等待 nREPL 服务器启动. CIDER 通过解析命令的输出来确定这一点, 等待出现类似
nREPL server started on port 53005 on host localhost - nrepl://localhost:53005的行. - CIDER 从前面的消息中提取 nREPL 的端口.
- 它连接到正在运行的 nREPL 服务器.
在等待 nREPL 启动时, 可以在 minibuffer 中看到 cider-jack-in 调用的确切命令. 也可以在 Emacs 的 *Messages* 缓冲区中找到这个命令.
在某些情况下, 一个项目可能包含多个项目标记, 例如 project.clj 和 deps.edn. 发生这种情况时, CIDER 会提示选择要使用的构建工具.
可以通过设置变量 cider-preferred-build-tool 来覆盖此行为. 虽然可以在 Emacs 配置中全局设置它, 但大多数情况下, 在 .dir-locals.el 中为其设置一个项目特定的配置更佳:
((clojure-mode (cider-preferred-build-tool . lein)))
cider-jack-in 主要为本地开发设计 (文件在本地机器上, nREPL 进程也在同一台机器上运行). 它确实支持各种常见的远程/容器场景, 如本节后面所述.
由于远程场景的多样性, 它无法支持所有情况, 因此在某些情况下, 更好的选择是手动启动 nREPL 并使用 cider-connect 连接到它.
2.2.2. 自动注入依赖
虽然 CIDER 的核心功能只需要一个 nREPL 服务器, 但许多高级功能依赖于额外的 nREPL middleware 的存在. 而使用 cider-jack-in, 这一切都是自动处理的.
如果项目使用 lein 或 tools.deps (deps.edn), CIDER 在启动服务器时会自动注入所有必要的 nREPL 依赖 (例如 cider-nrepl 或 piggieback).
注入过程非常简单 - CIDER 在运行启动 nREPL 服务器的命令时, 将额外的依赖和 nREPL 配置传递给构建工具. 对于 tools.deps, 它看起来像这样:
$ clojure -Sdeps '{:deps {nrepl {:mvn/version "1.5.1"} cider/cider-nrepl {:mvn/version "0.58.0"}}}' -m nrepl.cmdline --middleware '["cider.nrepl/cider-middleware"]'
如果不希望 cider-jack-in 自动注入依赖, 请将 cider-inject-dependencies-at-jack-in 设置为 nil. 请注意, 就自己设置依赖 (参见 nREPL Middleware Setup).
通常, cider-jack-in 只会注入 cider-nrepl, 而 cider-jack-in-cljs 还会添加 piggieback. 注入机制是可配置的, 可以轻松地在那里添加更多库. 一些 CIDER 扩展会使用此机制自动注入它们自己的依赖.
以下是如何为 cider-jack-in-clj 修改注入的依赖:
;; 自动注入 1.0 版本的库 foo/bar (cider-add-to-alist 'cider-jack-in-dependencies "foo/bar" "1.0") ;; 如果想完全控制坐标描述, 将其设置为 alist ;; 为库 org.clojure/tools.deps 自动注入 {:git/sha "6ae2b6f71773de7549d7f22759e8b09fec27f0d9" ;; :git/url "https://github.com/clojure/tools.deps/"} (cider-add-to-alist 'cider-jack-in-dependencies "org.clojure/tools.deps" '(("git/sha" . "6ae2b6f71773de7549d7f22759e8b09fec27f0d9") ("git/url" . "https://github.com/clojure/tools.deps/")))
在这些依赖中始终使用完整的 group/artifact (例如 re-frame/re-frame), 因为只有 Leiningen 支持裸 re-frame 语法.
CIDER 还会注入它支持的最新版本的 nREPL. 这是一个简单的技巧, 用于覆盖构建工具 (例如 Leiningen) 捆绑的 nREPL 版本, 以使用最新的 nREPL 功能.
可以通过自定义 cider-injected-middleware-version 和 cider-injected-nrepl-version 来覆盖注入的 cider-nrepl 和 nREPL 版本.
通常应该避免这样做, 但如果想尝试新版本或遇到一些回归问题必须退回旧版本, 这可能会很有用.
CIDER 还可以将 Clojure 依赖注入到项目中, 这在项目默认使用比 CIDER middleware 支持的更旧版本的 Clojure 时非常有用.
适当地设置 cider-jack-in-auto-inject-clojure 来启用此功能.
2.2.3. 启用 nREPL JVMTI agent
从 1.2.0 版本开始, nREPL 包含一个原生的 JVMTI agent, 它使得 eval 中断在 Java 21 及更高版本上能够正常工作.
要启用该 agent, Java 进程应以 -Djdk.attach.allowAttachSelf 启动.
如果 cider-enable-nrepl-jvmti-agent 变量设置为 t, CIDER 将在 jack-in 期间自动执行此操作.
cider-enable-nrepl-jvmti-agent 在 Emacs 外部启动 REPL 进程并使用 cider-connect 连接时无效. 在那种情况下, 必须通过构建工具的方式手动添加 -Djdk.attach.allowAttachSelf Java 属性.
在 Leiningen 中, 将此添加到 project.clj:
:jvm-opts ["-Djdk.attach.allowAttachSelf"]
在 tools.deps 中, 将此添加到 REPL 启用的某个别名中:
:aliases {:dev {:jvm-opts ["-Djdk.attach.allowAttachSelf"] ...}}
2.2.4. 无项目 Jacking-in
如果尝试在项目目录之外运行 cider-jack-in, CIDER 会警告并要求确认是否真的要这样做; 多数情况下, 这是一个意外.
如果决定继续, CIDER 将调用在 cider-jack-in-default 中配置的命令 (默认为 clj, Clojure 的基本启动命令).
禁用在项目外 jacking-in 时显示的警告, 可以将 cider-allow-jack-in-without-project 设置为 t.
2.2.5. Universal jack-in
cider-jack-in-universal C-c C-x j u 是另一种在没有项目的情况下快速 jack-in 的方法, 从预配置的 Clojure 构建工具列表中选择.
当此命令在项目外部调用时, 用户可以选择使用其中一个预配置的工具进行 jack-in, 并确认用作基础的根目录.
如果该命令在项目目录内调用, 其行为与 c cider-jack-in 完全相同.
它利用 Emacs 的数字前缀参数来快速使用特定的构建工具进行 jack-in. 数字前缀参数可以通过 Meta 键后跟一个数字来设置.
目前支持以下 Clojure 构建工具:
M-1 C-c C-x j u使用 clojure-cli 进行 jack-in.M-2 C-c C-x j u使用 leiningen 进行 jack-in.M-3 C-c C-x j u使用 babashka 进行 jack-in.M-4 C-c C-x j u使用 nbb 进行 jack-in.M-5 C-c C-x j u使用 basilisp 进行 jack-in.
这是一个如何绑定 F12 以快速启动 babashka REPL 的示例:
(global-set-key (kbd "<f12>") (lambda () (interactive) (cider-jack-in-universal 3)))
可供考虑的构建工具列表配置在 cider-jack-in-universal-options 中. 列表中的每个元素都包含工具名称及其设置选项. 以列表中的 nbb 为例:
(nbb (:prefix-arg 4 :cmd (:jack-in-type cljs :project-type nbb :cljs-repl-type nbb :edit-project-dir t)))
其中:
:prefix-arg将 nbb 工具名称分配给数字参数前缀 4.:cmd如何调用该命令.:jack-in-type使用 cljs repl.:project-type使用 nbb (参见jack-in-command) 来启动 nREPL 服务器.:cljs-repl-type客户端使用 nbb cljs repl 类型 (参见cider-cljs-repl-types) 来初始化服务器.:edit-project-dir要求用户确认用作基础的根目录.
2.2.6. 自定义 Jack-in 命令行为
可以使用 C-u M-x cider-jack-in RET 来指定 cider-jack-in 将运行的确切命令. 想指定额外的 Leiningen profiles 或 deps.edn aliases, 这个选项非常有用.
或者使用 C-u C-u M-x cider-jack-in RET, 这是前一个命令的变体. 这个命令会首先提示在哪个项目中启动 cider-jack-in, 在其他目录中, 这非常方便.
如果项目包含 project.clj 和 deps.edn 的某种组合, 想为其中一个启动 REPL, 这个选项也很有用.
这些示例仅使用 cider-jack-in, 但此行为对于所有 cider-jack-in-* 命令都是一致的.
可以通过修改一些选项来进一步自定义 CIDER 用于 cider-jack-in 的命令行. 这些选项在不同的工具之间略有不同, 所以我们将逐个工具地检查它们.
- Leiningen 选项
cider-lein-command- Leiningen 可执行文件的名称 (默认为lein)cider-lein-parameters- 启动 REPL 的命令行参数 (例如repl :headless或-o启用离线模式)
- Clojure CLI 选项
cider-clojure-cli-command-clojure可执行文件的名称 (默认为clojure)cider-clojure-cli-parameters- 启动 REPL 的命令行参数cider-clojure-cli-aliases- 在 jack-in 时使用的项目特定别名列表 (意在通过.dir-locals.el设置)cider-clojure-cli-global-aliases- 附加到项目特定cider-clojure-cli-aliases的全局别名列表
在 MS-Windows 上, 如果在 PATH 中找不到
clojure可执行文件 (例如deps.clj提供的那个), CIDER 将使用 PowerShell 来执行 Clojure.默认使用的可执行文件是
powershell, 它在所有 Windows 平台上都可用. 使用 PowerShell 会在将clojure启动命令传递给 PowerShell 之前对其进行 Base64 编码 , 从而避免 shell 转义问题.cider-clojure-cli-command的功能已通过以下替代方案验证:pwsh: 当用户在pwsh上安装 ClojureTools 模块的目录对系统的 PowerShell 安装不可访问时, 此选项很有用.deps.exe: 此可执行文件是deps.clj提供的工具的一部分, 作为clojure.exe的替代名称.或者可以使用 WSL (例如在那里运行 nREPL 和 Emacs), 这可能会带来更好的整体开发体验.
- Gradle 选项
cider-gradle-command- Gradle 可执行文件的名称 (默认为./gradlew)cider-gradle-parameters- 调用repl任务的 Gradle 参数 (例如--no-daemon或--configuration-cache) (默认为clojureRepl)
- shadow-cljs
cider-shadow-cljs-command- 运行shadow-cljs的命令 (默认为npx shadow-cljs). 默认情况下, 我们倾向于使用项目特定的shadow-cljs而不是系统范围的.cider-shadow-cljs-parameters- 启动 REPL 服务器的任务 (默认为server)
2.2.7. 覆盖 Jack-In 命令
使用哪个 Jack-In 命令是基于项目类型的. 可以在项目范围内或作为 Lisp 中的参数来覆盖 Jack-In 命令. 这允许对 CIDER 如何启动 nrepl-server 进行细粒度控制.
确定 Jack-In 命令的优先顺序是: 1. 如果作为参数提供了 :jack-in-cmd, 2. 如果 cider-jack-in-command 设置为目录本地变量, 以及 3. 从项目类型推断 (默认).
- 设置项目范围的命令
可以设置一个本地变量
cider-jack-in-command来覆盖 jack-in 命令.((nil (cider-jack-in-cmd . "nbb nrepl-server")))
- 以编程方式作为参数传递命令
可以提供一个覆盖 Jack-In 命令作为
cider-jack-in的参数. 这里有一个 Nbb Jack-In 命令的例子, 提供了一个自定义的:jack-in-cmd.(defun cider-jack-in-nbb-2 () "Start a Cider nREPL server with the 'nbb nrepl-server' command." (interactive) (cider-jack-in-clj '(:jack-in-cmd "nbb nrepl-server")))
2.2.8. 启动 nREPL 服务器而不尝试连接它
在某些情况下, 只启动一个 nREPL 服务器进程而不连接它可能会很有用. 这可以支持复杂的设置, CIDER 无法可靠地检测到要连接到哪个服务器/端口, 因此会失败.
这假设用户之后会手动执行 cider-connect 命令, 指定主机/端口.
对于这种情况, 提供了 cider-start-nrepl-server (C-c C-x (C-)j (C-)n) 命令, 它接受与 cider-jack-in 相同的参数.
2.2.9. 连接到正在运行的 nREPL 服务器
一个已经运行的 nREPL 服务器, CIDER 可以连接到它. 一个基于 Leiningen 的项目, 在终端会话中进入项目目录并输入:
$ lein repl :headless
这将启动项目的 nREPL 服务器.
对于普通的 clj 也是可能的, 尽管命令有点长:
$ clj -Sdeps '{:deps {cider/cider-nrepl {:mvn/version "0.58.0"}}}' -m nrepl.cmdline --middleware "[cider.nrepl/cider-middleware]"
或者, 可以手动启动 nREPL 或使用项目构建工具 (Gradle, Maven 等) 提供的设施.
在 nREPL 服务器运行后, 返回 Emacs 并连接到它: M-x cider-connect RET. CIDER 会提示输入主机和端口信息, 这些信息应该在前面的命令启动项目中的 nREPL 服务器时打印出来了.
在 Clojure(Script) 缓冲区中, cider-connect 命令绑定到 C-c C-x c s.
如果经常连接到相同的主机和端口, 可以告诉 CIDER 关于它们的信息, 当调用 cider-connect 时, 它将使用这些信息为主机和端口提示进行补全读取.
可以用一个可选的标签来识别每个主机.
(setq cider-known-endpoints '(("host-a" "10.10.10.1" "7888") ("host-b" "7888")))
全绕过提示, 可以在每个项目的基础上使用 .dir-locals.el 配置变量 cider-connect-default-params 和 cider-connect-default-cljs-params.
以下配置允许输入 M-x cider-connect-clj&cljs RET 并立即连接到 JVM 和 shadow-cljs REPLs, 无需回答任何提示 - 这在项目总是为其 nREPL 服务器使用相同的固定端点时很有用.
((clojure-mode . ((cider-connect-default-params . (:host "localhost" :port 1234)) (cider-connect-default-cljs-params . (:host "localhost" :port 5678)) (cider-default-cljs-repl . shadow))))
通用参数 C-u 可以在命令前使用, 以覆盖这些设置并强制显示提示.
2.2.10. 使用远程主机
虽然大多数时候连接到本地运行的 nREPL 服务器, 无论是手动启动还是通过 cider-jack-in-* 启动, 也有连接到远程 nREPL 主机的选项.
为了安全起见, CIDER 在这种情况下能够通过 SSH 隧道建立连接. 此行为由 nrepl-use-ssh-fallback-for-remote-hosts 控制: 当为 true 时, CIDER 在无法直接连接时会尝试通过 ssh 连接到远程主机. 默认为 nil.
还有一个 nrepl-force-ssh-for-remote-hosts, 它将无条件地强制对远程连接使用 ssh.
由于 nREPL 连接默认是不安全的, 建议在连接到网络外部的服务器时仅使用 SSH 隧道.
还有另一种情况, CIDER 可以选择性地利用 ssh 命令 - 当执行 cider-connect-* 时试图找出潜在的目标主机和端口.
如果 cider-infer-remote-nrepl-ports 为 true, CIDER 将使用 ssh 尝试推断远程主机上的 nREPL 端口 (用于直接连接). 该选项默认也设置为 nil.
启用这些选项中的任何一个都会导致 CIDER 对某些 SSH 操作使用 TRAMP, TRAMP 会解析 ~/.ssh/config 和 ~/.ssh/known_hosts 等配置文件.
这已知会与复杂或非标准的 ssh 配置产生问题.
可以在通过 TRAMP 处理远程文件时运行 cider-jack-in-*. CIDER 将重用现有的 SSH 连接参数 (如端口和用户名) 来建立 SSH 隧道.
如果尝试 cider-connect-* 到与当前连接的主机匹配的主机, 也会发生同样的情况.
对于 Docker 容器, 通过 TRAMP 运行 cider-jack-in-* 可能在技术上可行, 但可能会产生混合结果. 请查看下一节以了解推荐的方法.
2.2.11. 使用容器 (Docker 或其他)
我们所说的“容器”是指 Docker 容器或类似技术, 用于运行将承载我们 nREPL 服务器的 JVM. 我们编辑的文件可能/可能不使用 TRAMP 进行编辑. 它们也可以挂载在容器内部, 从而显示为本地文件.
因为 CIDER 并不总能检测到它是否在处理远程文件, 所以建议不要依赖上面描述的 cider-jack-in 及其远程支持, 而是从容器内部通过命令行启动 nREPL 服务器, 然后 cider-connect 到它.
这需要首先在运行的容器内获得一个 shell, 然后手动启动 nREPL 服务器, 或者配置容器在启动时自动启动 nREPL.
为了将 Emacs 连接到 nREPL, 我们需要确保 nREPL 的端口对我们的本地 Emacs 是可达的, 这样我们才能 cider-connect 到它. 根据具体情况, 有几种解决方案.
- 在本地主机上运行容器
nREPL 端口应该设置为一个固定值, 因为我们需要在
docker start命令期间给出这个值, 以便将端口从容器转发到主机. 这也要求 nREPL 服务器监听 "0.0.0.0" 而不仅仅是 "localhost". - 在远程主机上运行容器
如果我们的容器在远程机器上运行, 我们需要做与上面相同的设置, 并额外使用 ssh 将已经转发的端口再次转发到我们的本地机器.
这可以通过类似
ssh -L 12345:localhost:12345 remote-server的命令来完成, 假设 12345 是容器暴露的 nREPL 端口. - 在本地或远程使用 devcontainers
开发容器 (Development Containers) 是描述基于容器的开发环境的标准. 它包括一个 CLI. 它在幕后使用 Docker/Podman. 因此, 确保 nREPL 端口可用的原则保持不变, 但有略微不同的配置方式 (由 devcontainer 标准给出).
有几种 CLI 工具可以管理 devcontainers, 因为有几种容器技术. 以下示例使用
devcontainerscli, 但还有其他工具 (devpod, gitpod…).- 示例: 在远程服务器上使用容器 (使用 devcontainer)
在这种情况下, 假设一个包含相关 devcontainer 配置文件 (
devcontainer.json) 的文件夹/home/me/my-clj-code, 我们可以首先通过以下方式远程启动一个 devcontainer:# 在 MY_REMOTE_SERVER 上执行 devcontainer up --workspace-folder /home/me/my-clj-code
然后我们可以在远程主机上的容器内启动一个 nREPL 服务器, 如下所示 (从本地机器执行). 该命令还将远程端口 12345 隧道传输到本地机器的端口 12345:
ssh -t -L 12345:localhost:12345 MY_REMOTE_SERVER \ devcontainer exec --workspace-folder /home/me/my-clj-code \ "clojure -Sdeps '{:deps {nrepl/nrepl {:mvn/version \"1.5.1\"} cider/cider-nrepl {:mvn/version \"0.58.0\"}}}' -m nrepl.cmdline -p 12345 -b 0.0.0.0 --middleware '[\"cider.nrepl/cider-middleware\"]' "
为了使其工作, 我们还需要在
devcontainer.json中配置一个片段, 将端口 12345 从容器暴露给 (远程) 主机:"appPort": [ // will make container port 12345 available as 12345 on remote host // (which will be further tunneled to 12345 on local machine) "12345:12345" ],
这样一来, 端口 12345 就在本地可用了, 我们可以使用
localhost:12345来cider-connect到它. 文件的编辑可以通过 TRAMP 进行.由于文件在“远程机器上”并且也挂载在远程机器上的容器内, 我们有两种可能的 TRAMP 文件语法来编辑它们 - 其中之一:
/ssh:MY_REMOTE_SERVER:/home/me/my-clj-code/.../ssh:MY_REMOTE_SERVER|docker:DOCKER_CONTAINER_ID:/workspaces/my-clj-code/...
- 示例: 在远程服务器上使用容器 (使用 devcontainer)
2.2.12. 通过 unix domain file socket 连接
Unix 套接字支持是在 nREPL 0.9 中引入的. 目前 CIDER 对 Unix 套接字的支持被认为是实验性的, 其接口可能会在未来的 CIDER 版本中改变.
在本地运行 nREPL 服务器时, 可以选择在套接字文件上监听, 而不是打开网络端口. 只要对套接字父目录的访问得到充分保护, 这比网络端口安全得多, 因为任何本地用户都可以访问端口提供的 REPL.
在其他情况下, 例如在使用虚拟网络 (容器) 时, 共享文件套接字可能比管理桥接网络和防火墙设置要简单得多.
在文件套接字上启动 nREPL 服务器后, 例如使用 clj 命令 (有关其他示例, 请参见 https://nrepl.org/nrepl/usage/server.html),
$ clj -R:nREPL -m nrepl.cmdline --socket nrepl.sock
可以通过使用 local-unix-domain-socket 特殊主机名与 cider-connect 来连接 CIDER: M-x cider-connect RET local-unix-domain-socket RET nrepl.sock RET.
目前只有 Leiningen, cider-jack-in 等命令会检测并使用通过 :socket 参数请求的 unix domain socket. 这可以通过为 cider-jack-in 指定前缀参数 (例如 C-u M-x cider-jack-in) 或调整 cider-lein-parameters 来安排.
2.2.13. 下一步做什么?
那么, 现在 CIDER 已经准备就绪, 接下来该做什么呢? 这里有一些想法:
- 熟悉交互式编程和
cider-mode - 根据喜好配置 CIDER
- 探索更高效的其他包
是的, 这是对 "Neuromancer" 的引用.
2.3. nREPL Middleware Setup
如果不打算使用 cider-connect 或者不关心需要 cider-nrepl 的高级功能, 可以跳过本节.
CIDER 的许多功能都依赖于它自己的 nREPL middleware. cider-jack-in (C-c C-x (C-)j (C-)j) 会根据需要自动注入这个 middleware 和其他依赖项.
更喜欢一个独立的 REPL, 需要调用 cider-connect 而不是 cider-jack-in, 并手动将依赖项添加到 Clojure 项目中 (在以下各节中解释).
2.3.1. 设置一个独立的 REPL
- 使用 Leiningen
在项目的
project.clj文件中或~/.lein/profiles.clj的:replprofile 中使用方便的插件来设置默认值.:plugins [[cider/cider-nrepl "0.58.0"]]
一个用于 CIDER 的最小
profiles.clj文件是:{:repl {:plugins [[cider/cider-nrepl "0.58.0"]]}}
注意不要把它放在
:userprofile 中, 因为那样 CIDER 的 middleware 将总是被加载, 导致lein启动变慢. 只在lein repl时需要它, 这就是:replprofile 的作用. - 使用 tools.deps
可以将以下别名添加到
deps.edn中, 以便从命令行使用类似clj -A:cider-clj的命令启动一个带有 CIDER middleware 的独立 Clojure(Script) nREPL 服务器. 然后从 emacs 运行cider-connect或cider-connect-cljs.:cider-clj {:extra-deps {cider/cider-nrepl {:mvn/version "0.58.0"}} :main-opts ["-m" "nrepl.cmdline" "--middleware" "[cider.nrepl/cider-middleware]"]} :cider-cljs {:extra-deps {org.clojure/clojurescript {:mvn/version "1.10.339"} cider/cider-nrepl {:mvn/version "0.58.0"} cider/piggieback {:mvn/version "0.6.1"}} :main-opts ["-m" "nrepl.cmdline" "--middleware" "[cider.nrepl/cider-middleware,cider.piggieback/wrap-cljs-repl]"]}
建议的 ClojureScript 设置适用于例如库开发, 但不适用于前端开发. 对于那些情况, 查看 shadow-cljs 或 figwheel 设置.
- 使用 Gradle
请确保正在使用 Clojurephant 0.4.0 或更新版本.
build.gradledependencies { devImplementation 'nrepl:nrepl:1.5.1' devImplementation 'cider:cider-nrepl:0.58.0' } tasks.named('clojureRepl') { middleware = ['cider.nrepl/cider-middleware'] }通过命令行启动 nREPL 服务器:
./gradlew clojureRepl.更多信息, 请参见 Clojurephant 文档.
- 使用 Maven
本节目前是一个存根. 欢迎贡献!
- 使用嵌入式 nREPL 服务器
应用程序中嵌入 nREPL, 将需要使用 CIDER 自己的 nREPL 处理程序来启动服务器.
(ns my-app (:require [nrepl.server :as nrepl-server] [cider.nrepl :refer (cider-nrepl-handler)])) (defn -main [] (nrepl-server/start-server :port 7888 :handler cider-nrepl-handler))
项目应该依赖于
cider-nrepl.CIDER 和
cider-nrepl项目是协同开发的, 但它们的发布不是同步的 —— 它们的版本不同. 通常,cider-nrepl的任何近期版本都应与 CIDER 的近期版本 (大部分) 兼容. 以通过查看cider-required-middleware-version来检查 CIDER 版本所需的cider-nrepl版本. 另请参见兼容性矩阵.
3. ClojureScript 设置
3.1. Overview
CIDER 与 ClojureScript 配合得很好, 但并非所有 CIDER 功能都在 ClojureScript 可用. 例如, 测试运行器和调试器目前是仅限 Clojure 的功能.
与被单一 REPL 主导的 Clojure 生态系统不同, ClojureScript 生态系统有许多不同的 REPL 选择 (例如, browser, node, weasel, figwheel 和 shadow-cljs). 需要决定要运行哪一个, 以及希望 CIDER 如何与之交互.
本节介绍 ClojureScript 支持在 CIDER 中是如何实现的, 在接下来的部分中, 讨论如何启动 ClojureScript REPL 以及如何设置最流行的 ClojureScript REPL.
3.1.1. nREPL 和 ClojureScript
nREPL 本身不支持 ClojureScript 求值, 需要一个额外的 middleware. 对于大多数 REPL (除了 shadow-cljs 和 nbb 这两个显著的例外), CIDER 依赖 Piggieback middleware 来提供其 ClojureScript 支持.
Piggieback 的工作方式如下:
- 启动一个常规的 Clojure REPL
- 在其中运行一些 Clojure 代码, 将其转换为 ClojureScript REPL
这意味着对于 ClojureScript, jacking-in 是一个双重过程, 与 Clojure 相比, 因为现在我们有了额外的 REPL"升级"步骤.
好的一面是可以在单个 nREPL 连接中并存 Clojure 和 ClojureScript REPL! 这开启了各种有趣的可能, 我们稍后会讨论.
shadow-cljs 的 REPL 以非常相似的方式实现, 但其机制由其自己的 nREPL middleware 提供 - 而不是 Piggieback.
3.1.2. Piggieback 与标准 ClojureScript REPL 的差异
虽然由 Piggieback 驱动的 ClojureScript REPL 的行为或多或少与标准 ClojureScript REPL 相同, 但有一些微妙的差异是每个人都需要注意的.
- 处理多个 Forms
这是标准 ClojureScript 对多个输入 forms 的行为方式:
cljs.user> (declare is-odd?) (defn is-even? [n] (if (= n 0) true (is-odd? (dec n)))) (defn is-odd? [n] (if (= n 0) false (is-even? (dec n)))) #'cljs.user/is-odd? #'cljs.user/is-even? #'cljs.user/is-odd? cljs.user> (is-even? 4) true
而这是由 Piggieback 驱动的 REPL 的行为方式:
cljs.user> (declare is-odd?) (defn is-even? [n] (if (= n 0) true (is-odd? (dec n)))) (defn is-odd? [n] (if (= n 0) false (is-even? (dec n)))) #'cljs.user/is-odd? cljs.user> (is-even? 4) Compile Warning <cljs repl> line:1 column:2 Use of undeclared Var cljs.user/is-even? 1 (is-even? 4) ^--- #object[TypeError TypeError: Cannot read property 'call' of undefined] (<NO_SOURCE_FILE>) cljs.user>
这种差异来自于 Piggieback 的性能优化, 它避免为它求值的每个 ClojureScript form 创建不同的 REPL.
你可以在这里了解更多关于这种差异的信息.
3.1.3. 处理依赖关系
当你执行 cider-jack-in-cljs 时, CIDER 不会自动处理 ClojureScript REPL 的依赖关系. 你需要按照本手册后续章节的说明手动配置这些依赖.
实际上, CIDER 会自动处理最重要的依赖 - 即 Piggieback. 然而, 其他依赖的问题在于, 你可能需要安装一些外部工具 (例如, node, shadow-cljs), 并且像 Figwheel 和 shadow-cljs 这样的 ClojureScript 开发工具也需要一些设置才能使用.
在尝试将 Clojure REPL 升级为 ClojureScript REPL 之前, CIDER 会通过运行检查来帮助你识别缺失的需求. 这种检查的性质因不同的 REPL 类型而异:
- 对于 node REPL, 我们检查
node二进制文件是否在你的exec-path(Emacs 版本的 PATH) 中 - 对于像 figwheel 这样的工具, 我们检查它们是否在 classpath 上可用 (通过尝试
require它们的一些命名空间)
我们将在接下来的章节中进一步讨论这些检查.
3.1.4. 限制
CIDER 目前不支持自托管的 ClojureScript 实现. 原因在于目前还没有可用的自托管版本的 nREPL (用 ClojureScript 实现).
另一个不支持的 REPL 是 Rhino. 在 Piggieback 中支持它需要很多丑陋的 hack, 最终决定我们最好不要使用 Rhino. 鉴于今天有大量更好的解决方案, 我怀疑没有人会怀念 Rhino.
此外, 正如本页前面提到的 - CIDER 的许多高级功能目前不适用于 ClojureScript.
3.1.5. 下一步
在下一节中, 我们将向你展示如何使用 CIDER 启动 ClojureScript REPL.
3.2. Up and Running
3.2.1. Piggieback 设置
ClojureScript 支持依赖于 piggieback nREPL middleware 存在于你的 REPL 会话中. 不过, 有一个例外: shadow-cljs. 它有自己的 nREPL middleware, 完全不依赖 piggieback.
如果 cider-inject-dependencies-at-jack-in 被启用 (默认是这样), 那么在执行 cider-jack-in-cljs 时, piggieback 将被自动添加并为你的项目配置好.
如果 cider-inject-dependencies-at-jack-in 被禁用, 或者你打算使用 cider-connect-cljs 连接到一个已经运行的 nREPL 服务器, 请使用下一节中的配置.
3.2.2. 手动 Piggieback 设置
要设置 piggieback, 请将以下依赖项添加到你的项目 (project.clj 或 deps.edn) 中:
;; 这里使用最新的版本 [cider/piggieback "0.6.1"] [org.clojure/clojure "1.12.0"]
以及 piggieback nREPL middleware:
在 project.clj 中:
:repl-options {:nrepl-middleware [cider.piggieback/wrap-cljs-repl]}
或者在 deps.edn 中:
{:aliases { :cider-cljs { :main-opts ["-m" "nrepl.cmdline" "--middleware" "[cider.nrepl/cider-middleware,cider.piggieback/wrap-cljs-repl]"]}}}
或者在 build.gradle 中:
dependencies {
devImplementation 'nrepl:nrepl:1.5.1'
devImplementation 'cider:cider-nrepl:0.58.0'
devImplementation 'cider:cider-piggieback:0.6.1'
}
tasks.named('clojureRepl') {
middleware = ['cider.nrepl/cider-middleware', 'cider.piggieback/wrap-cljs-repl']
}
3.2.3. 启动 ClojureScript REPL
在你的项目中打开一个 ClojureScript 文件, 然后输入 M-x cider-jack-in-cljs RET. 在正确的配置下并回答几个提示后, 这将启动 nREPL 服务器并创建一个 ClojureScript REPL 缓冲区.
在 CIDER 0.18 之前, cider-jack-in-cljs 会同时创建一个 Clojure 和一个 ClojureScript REPL. 在 CIDER 0.18+ 中, 如果你想同时创建两个 REPL, 你需要使用 cider-jack-in-clj&cljs.
当你同时拥有 Clojure 和 ClojureScript REPL 时, CIDER 会根据你当前访问的是 .clj 还是 .cljs 文件, 自动将所有常规 CIDER 命令定向到相应的 REPL.
cider-jack-in-cljs 会提示你想要启动的 ClojureScript REPL 类型. 请记住, 某些 REPL 需要你配置额外的设置. 例如, 你需要安装 Node.js 才能启动 Node REPL.
唯一不需要任何额外设置的 ClojureScript REPL 类型是浏览器 REPL. 要使用 Node.js REPL, 你需要安装 Node.js.
CIDER 会自动尝试检查启动某个 ClojureScript REPL 所需的依赖项 (例如 Clojure 库和/或额外的工具如 Node.js) 是否存在. 如果你遇到错误的依赖项检查, 你可以像这样禁用它们:
(setq cider-check-cljs-repl-requirements nil)
3.2.4. 使用 .cljc 文件
通常情况下, CIDER 会将来自 clj 文件的代码分派到 Clojure REPL, 将来自 cljs 文件的代码分派到 ClojureScript REPL. 但是 cljc 文件有两个可能的连接目标, 两者都是有效的. 因此, 默认情况下, 如果存在匹配的 clj 和 cljs 连接缓冲区, CIDER 会尝试在所有这些缓冲区中求值 cljc 文件.
因此, 如果你在一个 cljc 文件中求值代码 (+ 2 2), 并且你同时有一个活动的 Clojure 和 ClojureScript REPL, 那么这段代码将被求值两次, 每个 REPL 一次. 实际上, 你可以在一个 CIDER 会话中创建多个 clj 和 cljs 兄弟连接 (C-c C-x C-s C-s/j), 求值将被同时定向到所有的 REPL. 更多细节请参见管理连接.
如果你希望只在匹配的 REPL 中的一个里面求值 cljc 文件, 你可以自定义变量 cider-clojurec-eval-destination 为 clj 或 cljs. 例如, 要只在 ClojureScript REPL 中求值 cljc 文件, 请将以下内容添加到你的 Emacs 配置中:
(setq cider-clojurec-eval-destination 'cljs)
注意: 这个变量也可以通过 setq-local 在每个缓冲区的基础上设置, 或者在 dir-locals.el 中项目范围内设置.
3.3. Configuration
在本节中, 我们将简要探讨一些与 ClojureScript 相关的常见配置选项. 特定于某个 REPL (例如, shadow-cljs) 的配置将在各自的 REPL 设置部分单独介绍.
3.3.1. 修改 ClojureScript jack-in 依赖
开箱即用, CIDER 会向 ClojureScript REPL 注入一个依赖——即 piggieback. 如果你想注入更多的依赖, 你需要修改 cider-jack-in-cljs-dependencies 变量. 这里有一个例子:
;; 让我们在 jack-in 时添加 Weasel (cider-add-to-alist 'cider-jack-in-cljs-dependencies "weasel/weasel" "0.7.1")
在这些依赖项中始终使用完整的 group/artifact (例如 re-frame/re-frame), 因为只有 Leiningen 支持裸 re-frame 语法.
通常情况下, 不需要修改这个变量, 因为 ClojureScript 依赖项大多数时候都在你的项目配置中明确声明了.
修改此变量将影响所有 ClojureScript REPL 类型.
3.3.2. 设置默认的 ClojureScript REPL 类型
如果你经常使用相同的 ClojureScript REPL, 你可以设置 cider-default-cljs-repl, CIDER 将跳过提示并使用这个设置. 例如, 以下代码将使 Node.js 成为默认设置:
(setq cider-default-cljs-repl 'node)
3.3.3. 定义自定义 ClojureScript REPL 类型
所有支持的 ClojureScript REPL都存储在 cider-cljs-repl-types 中. 如果你需要扩展它, 你应该在你的 Emacs 配置中使用 cider-register-cljs-repl-type.
(cider-register-cljs-repl-type 'super-cljs "(do (...))" optional-requirements-function)
如果你正在注册一个自托管 (原生) 的 ClojureScript REPL (例如, scittle), "升级" 表单应为 nil.
你也可以使用 .dir-locals.el 在每个项目的基础上修改已知的 ClojureScript REPL:
;; 替换 REPL 类型列表并设置一些默认值 ((nil (cider-default-cljs-repl . super-cljs) (cider-cljs-repl-types . ((super-cljs "(do (foo) (bar))"))))) ;; 修改已知 REPL 列表并设置一些默认值 ((nil (eval . (cider-register-cljs-repl-type 'super-cljs "(do (foo) (bar))")) (cider-default-cljs-repl . super-cljs)))
对于一次性的 REPL, 你也可以像这样使用自定义的 REPL 初始化表单:
;; 修改已知 REPL 列表并设置一些默认值 ((nil (cider-custom-cljs-repl-init-form . "(do (foo) (bar))" (cider-default-cljs-repl . custom)))
如果你已经有一个正在运行的 Clojure REPL 并想添加一个 ClojureScript REPL, 你可以调用 cider-jack-in-sibling-clojurescript 来添加它.
3.3.4. 增强的补全
默认情况下, CIDER 会使用合适的库来丰富 ClojureScript 的补全结果. 如果你遇到任何与 suitable 相关的问题, 你可以像这样禁用增强补全:
(setq cider-enhanced-cljs-completion-p nil)
3.4. Using Figwheel
Figwheel 是当今最流行的 ClojureScript REPL 之一. 下面你将看到如何设置并与 CIDER 一起使用它.
市面上有两个版本的 Figwheel - 传统的 figwheel 和现代的 figwheel-main. 强烈建议你使用 figwheel-main.
3.4.1. 使用 Figwheel-main
接下来的设置说明重点在于让你能用 CIDER 运行 Figwheel 所需的最低限度操作. 更多细节, 你应该查阅 Figwheel 出色的文档.
- Leiningen 设置
只有当
cider-inject-dependencies-at-jack-in从其默认设置t(启用) 更改时, 这部分设置才是必要的, 如前所述. 如果你打算使用cider-connect-cljs, 你还必须手动配置 Piggieback.- 将此添加到你的
dev:dependencies(对于cider-jack-in-cljs不是必需的):
[cider/piggieback "0.6.1"]
- 将此添加到你的
dev:repl-options(对于cider-jack-in-cljs不是必需的):
:nrepl-middleware [cider.piggieback/wrap-cljs-repl]
现在是时候将
figwheel-main添加到你的依赖中了, 然后你就准备好了. 这是一个最小的project.clj示例 (不包括可选的 Piggieback 设置):(defproject example-project "0.1.0-SNAPSHOT" :dependencies [[org.clojure/clojure "1.10.1"]] :profiles {:dev {:dependencies [[org.clojure/clojurescript "1.10.339"] [com.bhauman/figwheel-main "0.2.3"]]}})
现在你可以通过运行以下命令来验证一切是否设置正确:
$ lein run -m figwheel.main
应该会弹出一个浏览器窗口, 在终端中, 你应该会看到一个带有
cljs.user⇒提示的 REPL, 等待求值 ClojureScript 代码. - 将此添加到你的
- Clojure CLI 设置
- 确保你的
deps.edn包含这些依赖项, 可以从像fig这样的别名中获得. 如果你使用figwheel-main-template和clj-new生成deps.edn, 这些应该已经存在了.
{:aliases {:fig {:extra-deps {com.bhauman/rebel-readline-cljs {:mvn/version "0.1.4"} com.bhauman/figwheel-main {:mvn/version "0.2.17"}}} :build {:main-opts ["-m" "figwheel.main" "-b" "dev" "-r"]}}}
- 在从缓冲区启动 REPL 之前, 将此选项添加到你项目的
.dir-locals.el中. (如果文件已经打开, 你可以关闭并重新打开它, 或者使用revert-buffer.)
((clojurescript-mode . ((cider-clojure-cli-aliases . ":fig:build"))))
如果你没有设置
.dir-locals.el, 你可以在 minibuffer 中编辑命令行, 在clojure可执行文件之后, 在开头插入-A:fig. 要做到这一点, 在调用cider-jack-in-cljs前加上通用参数 (例如C-u). - 确保你的
- 启动一个 REPL
现在 Figwheel 已经正确配置, 是时候在 CIDER 中启动一个由 Figwheel 驱动的 REPL 了:
- 使用
cider-jack-in-cljs(C-c C-x (C-)j (C-)s) 启动 REPL. 当 CIDER 询问 ClojureScript REPL 类型时, 选择figwheel-main. - 当提示时选择要运行的 Figwheel build. (例如
:dev). - 当 CIDER 提示 ClojureScript REPL 类型时, 选择
figwheel-main. 当 CIDER 提示 build 名称时, 选择一个 build 名称选项, 这些选项是通过在项目根目录中查找名为
<build>.cljs.edn的文件生成的.如果你输入的 build 名称不存在 (没有匹配的
.cljs.edn文件) 或 build 名称为空, 而不是选择提供的 build 名称选项之一, CIDER 会将其转发给figwheel-main, 后者将报告错误.对于更高级的用法, 你可以在提示 build 名称时或使用
cider-figwheel-main-default-options提供figwheel.main/start支持的任何选项. 详情请参见figwheel.main.api/start.
- 使用
3.4.2. 使用旧版 Figwheel (仅限 Leiningen)
这已被弃用, 以支持使用 figwheel-main. 请查看下一节中的说明.
你也可以将 Figwheel 与 CIDER 一起使用.
- 正常设置 Figwheel, 但要确保
:cljsbuild和:figwheel设置位于你的 Leiningen 项目定义的根部. - 将这些添加到你的
dev:dependencies中:
[cider/piggieback "0.6.1"] ; 对于 cider-jack-in-cljs 不是必需的 [figwheel-sidecar "0.5.19"] ; 这里使用 figwheel 的当前版本
请记住, CIDER 不支持早于 0.4 版本的 Piggieback. 请确保你使用的是兼容版本的 Figwheel.
- 将此添加到你的
dev:repl-options(对于cider-jack-in-cljs不是必需的):
:nrepl-middleware [cider.piggieback/wrap-cljs-repl]
- 使用
cider-jack-in-cljs(C-c C-x (C-)j (C-)s) 启动 REPL. 当提示 ClojureScript REPL 类型时, 选择figwheel. - 打开浏览器到 Figwheel URL, 以便它可以连接到你的应用程序.
你也应该查看 Figwheel 的 wiki.
3.4.3. 其他资源
官方 Figwheel 文档
- 安装
- 将 Figwheel 与 CIDER 一起使用.
教程
- tools.deps, figwheel-main, Devcards, 和 Emacs
- 将 Figwheel 与 CIDER 一起使用
3.5. Using shadow-cljs
shadow-cljs 是当今进行 ClojureScript 开发最流行的工具链之一. 在本节中, 我们将讨论如何设置它并与 CIDER 一起使用.
3.5.1. 设置 shadow-cljs
本节假设已经安装了 node.js.
安装 shadow-cljs 非常直接. 可以通过 npm 或 yarn 来安装:
$ npm install -g shadow-cljs $ yarn global add shadow-cljs
虽然不是必须进行全局安装, 但这通常是推荐的方法.
3.5.2. 启动 shadow-cljs REPL
- 使用 cider-jack-in-cljs
如果已经正确配置了你的项目, 可以简单地使用
cider-jack-in-cljs:- 按
C-c C-x j s(或执行M-x cider-jack-in-cljs) - 当提示要启动的 ClojureScript REPL 类型时, 选择
shadow
这将自动启动
shadow-cljs服务器并连接到它. 还会被提示使用哪个shadow-cljsbuild. 选择想要的 build (例如app), 应该会看到类似这样的东西:shadow.user> To quit, type: :cljs/quit [:selected :app] cljs.repl>
CIDER 会自动从当前项目根目录下的
shadow-cljs.edn文件中提取可用 build 的列表.可以通过在项目根目录下创建一个包含以下内容的
.dir-locals.el文件来去掉对 REPL 类型和目标 build 的提示.((nil . ((cider-default-cljs-repl . shadow) (cider-shadow-default-options . "<your-build-name-here>") (cider-shadow-watched-builds . ("<first-build>" "<other-build>")))))
- 按
- 使用 cider-connect-cljs
或者, 你可以手动启动服务器, 比如:
$ shadow-cljs watch app
然后使用
cider-connect连接到它.要使其工作, 假设
shadow-cljs.edn的内容如下::dependencies [[cider/cider-nrepl "0.58.0"] ;; 强制性的 (除非它从 deps.edn 继承或以其他方式存在于 shadow-cljs 的 JVM 进程的 classpath 中) [refactor-nrepl/refactor-nrepl "3.9.0"]] ;; refactor-nrepl 是可选的 :nrepl {:middleware [cider.nrepl/cider-middleware ;; 建议明确添加此 middleware. 它由 shadow-cljs 自动添加 (如果在 classpath 中可用), 除非 ~:nrepl {:cider false}~ refactor-nrepl.middleware/wrap-refactor] ;; refactor-nrepl 是可选的 :port 50655} ;; 可选的 - 如果未指定, 将使用一个随机的空闲端口
如果
cider-nrepl不在 classpath 中, 应该确保它在那里. 可以通过正确填写项目根目录下的shadow-cljs.edn配置文件来做到这一点, 如上所述. 或者, 你可以将cider-repl-auto-detect-type设置为nil, 因为在没有cider-nrepl的情况下, REPL 类型的自动检测无法工作.如果你已经有一个正在运行的服务器在监视一个 build (例如, 你已经运行了
npx shadow-cljs watch :dev), 你可以使用shadow-selectCLJS REPL 并在提示时指定:dev. - 使用 shadow-cljs, deps.edn 和自定义 repl 初始化
如果你想通过
deps.edn管理你的依赖, 你可以使用一个自定义的cljs-repl初始化表单. 这假设你的deps.edn依赖中有shadow-cljs.{:paths ["src"] :deps {... thheller/shadow-cljs {:mvn/version "2.15.6"} ... } :aliases {:dev {:extra-paths ["dev"]}}}
创建一个带有额外源路径 "dev" 的
:dev别名, 并添加以下命名空间(ns user (:require [shadow.cljs.devtools.api :as shadow] [shadow.cljs.devtools.server :as server])) (defn cljs-repl "Connects to a given build-id. Defaults to ~:app~." ([] (cljs-repl :app)) ([build-id] (server/start!) (shadow/watch build-id) (shadow/nrepl-select build-id)))
假设你的 build-id 是
:app, 将以下内容添加到你的.dir-locals.el中((nil . ((cider-clojure-cli-aliases . ":dev") (cider-preferred-build-tool . clojure-cli) (cider-default-cljs-repl . custom) (cider-custom-cljs-repl-init-form . "(do (user/cljs-repl))") (eval . (progn (make-variable-buffer-local 'cider-jack-in-nrepl-middlewares) (add-to-list 'cider-jack-in-nrepl-middlewares "shadow.cljs.devtools.server.nrepl/middleware"))))))
cider-jack-in-cljs应该就可以开箱即用了.
3.5.3. 配置
你可以通过以下配置变量调整 cider-jack-in-cljs 用于启动 shadow-cljs 服务器的命令:
cider-shadow-cljs-command(其默认值为npx shadow-cljs)cider-shadow-cljs-parameters(其默认值为server)
所有这些导致了以下默认命令来启动 shadow-cljs 服务器:
$ npx shadow-cljs server
当你执行 cider-jack-in-cljs 时, 该命令在 minibuffer 中可见.
如前所述, 你也可以通过 cider-shadow-default-options 设置一个默认的 build:
(setq cider-shadow-default-options "app")
3.5.4. 其他资源
官方 shadow-cljs 文档
这里是 shadow-cljs 自身文档中的一些有用部分:
- 安装
- nREPL 设置
- 与 CIDER 集成
3.6. Using Other REPLs
尽管现在大多数人都在使用 figwheel 和 shadow-cljs, CIDER 也支持其他的 ClojureScript REPL. 本用户手册的这一部分专门介绍它们.
3.6.1. Node.js REPL
确保 node.js 已安装, 并且 node 二进制文件在 Emacs 的 exec-path 中.
ClojureScript 的 Node.js REPL 设置起来非常简单, 因为它不需要你修改项目的依赖项. 你需要做的就是:
- 在你的项目中打开某个文件.
- 输入
M-x cider-jack-in-cljs RET. - 当提示你想要使用的 ClojureScript REPL 类型时, 选择
node选项.
3.6.2. Weasel
使用 Weasel, 你可以有一个连接到浏览器的 REPL.
- 将
[weasel "0.7.1"]添加到你项目的:dependencies. - 输入
M-x cider-jack-in-cljs RET并在提示你想要使用的 ClojureScript REPL 类型时选择Weasel选项. - 将此添加到你的 ClojureScript 代码中:
(ns my.cljs.core (:require [weasel.repl :as repl])) (repl/connect "ws://localhost:9001")
- 连接后, 你可以开始在 REPL 中求值代码, 你将在浏览器中看到结果.
cljs.user> (js/alert "Hello world!")
只要在你的 REPL 会话中有一个启用了 Piggieback 的 ClojureScript 环境, 代码加载和求值将无缝工作, 无论是否存在 cider-nrepl middleware. 如果 middleware 存在, 那么 CIDER 的大多数其他功能也将被启用 (包括代码补全, 文档查找, 命名空间浏览器和宏展开).
有关 Weasel 的更多信息, 你应查阅其文档.
3.6.3. nbb (node.js babashka)
CIDER 内置了对 nbb 的支持. 你可以使用 M-x clojure-jack-in-cljs 来 jack in到一个 nbb 项目.
或者启动其捆绑的 nREPL 服务器:
$ nbb nrepl-server
然后使用 M-x cider-connect-cljs 连接到它.
更多详情请参见专用页面.
3.6.4. 其他自托管 REPL
对于所有其他自托管 REPL, 你可以按照此处的说明进行操作. 这将与任何行为良好的 nREPL 实现正常工作, 例如:
nbbscittlejoyride
4. JVM以外平台
4.1. Overview
本节正在进行中.
尽管 CIDER 是为作为 Clojure(Script) 编程环境而创建的, 但由于 nREPL 协议的灵活性, 它实际上也可以与其他编程语言/平台一起使用.
4.1.1. 挑战
支持多种编程语言的最大问题是 CIDER 假设它与 Clojure 一起使用. 在实践中, 这意味着 CIDER 在某些情况下会求值 Clojure 代码以支持其功能.
一个简单的例子是切换 REPL 的命名空间 - CIDER 只是在幕后求值 in-ns 来实现这一点.
你还必须记住, 许多高级功能 (例如, 与调试相关的所有功能) 不属于核心 nREPL 协议, 而是由 cider-nrepl middleware 提供. 其他平台需要提供该功能的兼容实现, 以便 CIDER 能够与之协作.
另一个问题是, 某些功能是 Clojure 特有的, 在其他语言中没有多大意义, 但这些功能在不适用时可以简单地忽略. 还存在一些与 clojure-mode 的耦合, 但这并不难克服.
一些例子是 REPL (它使用 clojure-mode 的语法表和缩进规则), 查找当前命名空间的逻辑 (它使用 clojure-find-ns), 等等.
所有这些实例最终都需要被更通用的版本所取代, 这些版本会根据 nREPL 连接的类型 (例如, Clojure, Racket, Fennel 等) 进行分派.
4.1.2. 当前状态
目前 CIDER 在某种程度上支持以下平台:
- Babashka
- nbb
- ClojureCLR
- scittle, joyride & friends
- Basilisp
所有这些都源于 Clojure, 因此支持它们并不需要太多工作.
4.1.3. 未来计划
尽管优先级不是很高, 但使 CIDER 能够与任何 nREPL 服务器一起使用肯定是该项目的路线图之一. 在这里, 你可以找到关于让 CIDER 与 Fennel 协作所需的更改的讨论, 以及一些说明这些更改的提交.
在这方面的任何帮助都将不胜感激!
4.2. Babashka
4.2.1. 概述
Babashka 与 Clojure 高度兼容, 因此它与 CIDER 开箱即用.
4.2.2. 用法
你所需要做的就是启动它捆绑的 nREPL 服务器:
$ bb --nrepl-server
然后使用 C-c C-x c j (cider-connect-clj) 连接到它.
Babashka 的 nREPL 服务器支持所有核心 nREPL 操作, 加上代码补全, 所以你将通过它获得 CIDER 的所有基本功能.
根据你的 CIDER 版本, 你可能会收到一些关于缺少 Clojure/nREPL 版本的警告. 你可以安全地忽略这些.
从 CIDER 1.2 开始, cider-jack-in-clj 可以与使用 bb.edn 的 Babashka 项目一起工作.
4.2.3. 与 Clojure 的差异
Babashka 和 Clojure 之间有一些你应该记住的差异:
- 内置的 vars (例如
clojure.core/map) 没有定义位置元数据. 在实践中, 这意味着你不能在 CIDER 中导航到它们的定义. javadoc(clojure.java.javadoc/javadoc) REPL 实用函数目前在 Babashka 中不可用.
4.2.4. 其他资源
- Babashka 的 nREPL 文档
- babashka.nrepl
4.3. Nbb
4.3.1. 概述
Nbb 的主要目标是让在 Node.js 上进行临时的 ClojureScript 脚本编写变得容易.
它与 ClojureScript 高度兼容, 因此可以与 CIDER 开箱即用.
4.3.2. 用法
你可以使用 M-x clojure-jack-in-cljs 来 jack in 到一个 nbb 项目.
或者启动其捆绑的 nREPL 服务器:
$ nbb nrepl-server
然后使用 M-x cider-connect-cljs 连接到它.
cider-jack-in-cljs 与使用 nbb.edn 的 nbb 项目一起工作.
4.3.3. 配置
jack-in 命令可以通过几个 defcustoms 进行配置:
cider-nbb-command(默认为nbb)cider-nbb-parameters(默认为nrepl-server)
4.4. Basilisp
4.4.1. Basilisp 与 CIDER 的集成
Basilisp 支持是在 CIDER 1.14 中添加的.
4.4.2. 概述
Basilisp 旨在实现在 Python 上编写具有完全互操作性的 Clojure 程序. 它与 Clojure 高度兼容.
要安装 Basilisp, 运行:
$ pip install basilisp
4.4.3. 用法
有几种方法可以连接到 Basilisp.
- jack-in
M-x cider-jack-in和M-5 M-x cider-jack-in-universal如果你在项目树的根目录创建了一个
basilisp.edn项目文件, 你可以使用M-x cider-jack-in来 jack-in 到该项目.basilisp.edn类似于 clojure-cli 项目的deps.edn. 它可以是空的, 只是为了标记项目的根目录.如果你没有或不想要 basilisp 项目文件, 你可以使用带有数字参数 5 的 universal jack-in:
M-5 M-x cider-jack-in-universal, 或在
clojure-mode的文件中,M-5 C-c C-x j uM-5的替代方案是C-u 5
你还可以将 universal jack-in to Basilisp 绑定到一个函数作为快捷方式, 例如:
(global-set-key (kbd "<f12>") (lambda () (interactive) (cider-jack-in-universal 5)))
- connect
M-x cider-connect你可以启动其捆绑的 nREPL 服务器:
$ basilisp nrepl-server
然后使用
M-x cider-connect连接到它.要查看可用选项, 在 shell 提示符下输入
basilisp nrepl-server -h.
4.4.4. 配置
jack-in 命令可以通过以下 defcustoms 进行配置:
cider-basilisp-command
(默认为 basilisp)
如果 Basilisp 安装在虚拟环境中, 请将其更新为该虚拟环境中 basilisp 可执行文件的完整路径.
cider-basilisp-parameters
(默认为 nrepl-server)
4.4.5. 自定义
在 Emacs 中有几种设置 (自定义) 变量的方法
- 检查和设置变量
C-h v cider-basilisp-command, 和C-h v cider-basilisp-parameters
Per-Diretory Local Variables 使用
.dir-locals.el来设置每个模式的变量. 这个文件通常存储在项目的根目录.例如, 设置虚拟环境中
basilisp可执行文件的路径M-x add-dir-local-variable- Mode or subdirectory:
clojure-mode - Add directory-local variable:
cider-basilisp-command - Add
cider-basilisp-commandwith value:"c:/dev/venvs/312/Scripts/basilisp"
这应该会导致更新或创建一个如下所示的
.dir-local.el文件
;;; Directory Local Variables -*- no-byte-compile: t -*- ;;; For more information see (info "(emacs) Directory Variables") ((clojure-mode . ((cider-basilisp-command . "c:/dev/venvs/312/Scripts/basilisp"))))
指定文件变量 最好将此放在你项目的
basilisp.edn文件的顶部, 并始终从那里进行 jack-in.例如, 将
cider-basilisp-command设置为从虚拟环境中启动 basilisp:M-x add-dir-local-variable- Add file-local variable:
cider-basilisp-command - Add
cider-basilisp-commandwith value:"c:/dev/venvs/312/Scripts/basilisp"
这将导致在
basilisp.edn中出现以下内容
;; Local Variables: ;; cider-basilisp-command: "c:/dev/venvs/312/Scripts/basilisp" ;; End: {}
4.5. Other Platforms
4.5.1. 概述
这里的“其他平台”基本上指的是任何未在文档的专用部分中特别提到的 (Clojure) 平台.
如文档前面所述, CIDER 将来可能会支持非 Clojure 平台. 然而, 就目前情况而言, 这在我们的优先事项列表中并不特别高.
4.5.2. 用法
从 CIDER 1.6 开始, 默认的 CIDER 连接命令 cider-connect-clj 能够连接到任何实现了核心 nREPL 协议接口的 nREPL 服务器. 所以, 你需要做的就是以下几步:
- 启动一个 nREPL 服务器 (项目的 README 通常有一个关于启动 nREPL 服务器的部分).
M-x cider-connect-clj <RET>
就是这样! 你将获得你正在使用的 nREPL 服务器所实现的每一个功能.
4.5.3. 支持的平台
这里是一个不完整的 Clojure 平台列表, 你可以如上所述使用它们.
- nbb
- scittle
joyride
对于 nbb, 你也可以通过
cider-connect-cljs连接, 参见 nbb.
4.5.4. 限制与注意事项
- 无论底层平台是什么, 所有东西都将被视为 Clojure 连接.
- 错误将只作为 overlays 显示. (默认的 CIDER 错误缓冲区目前未实现).
- 你获得的功能数量将取决于你正在使用的 nREPL 服务器实现核心 nREPL 协议的程度.
5. 使用 CIDER
5.1. 交互式编程
5.1.1. 概述
传统的编程语言和开发环境通常使用一个“编辑, 编译, 运行”的循环. 在这种环境中, 程序员修改代码, 编译它, 然后运行它看它是否做了她想做的事. 然后程序终止, 程序员回到编辑程序.
这个循环一遍又一遍地重复, 直到程序行为符合程序员的期望. 虽然现代 IDE 已经优化了这个过程, 使其快速且相对无痛, 但这仍然是一种缓慢的工作方式.
Clojure 和 CIDER 提供了一种更好的工作方式, 称为交互式编程. 事实上, 这个想法是 CIDER 的核心.
使用 CIDER 的交互式编程环境, 程序员以一种非常动态和增量的方式工作. 程序员不是反复地编辑, 编译和重启应用程序, 而是启动应用程序一次, 然后在程序继续运行时添加和更新单个 Clojure 定义.
使用 CIDER REPL, 程序员可以访问不同定义的值, 并用测试数据调用程序函数, 立即看到结果. 这种方法比典型的“编辑, 编译, 运行”循环效率高得多, 因为程序在程序员与之交互时继续运行并保持其状态完整.
事实上, 有些 Clojure 程序员会保持一个 CIDER 会话运行数周甚至数月, 同时继续编写代码.
5.1.2. 实现
CIDER 的交互式编程环境部分地是使用一个名为 cider-mode 的 Emacs minor mode 实现的. cider-mode 补充了 clojure-mode, 允许你从源文件缓冲区求值 Clojure 代码, 并通过 CIDER REPL 将其直接发送到你正在运行的程序中.
使用 cider-mode 提供的函数将提高你的生产力, 使你成为一个更高效的 Clojure 程序员.
5.1.3. 演示
不幸的是, 没有人能被告知什么是交互式编程. 你必须亲眼看看.
— Clorpheus
上述描述可能听起来有点太“元”, 所以可能查看一些演示交互式编程工作流程的视频将帮助你更好地理解关键概念. 这里有一些想法:
- 深入 CIDER - CIDER 基本功能概述
- Emacs & Clojure, 一段 Lispy 的恋情 - 所有流行的 Emacs Clojure 开发包 (包括 CIDER) 的概述
Spacemacs 和 CIDER 的 Clojure 开发工作流
由于 CIDER 发展迅速, 这些视频中的一些信息在你观看时可能已经过时. 尽管如此, 交互式编程的核心思想是不变的, 所以你观察和体验到的任何差异都可能是表面的.
你可以在“附加资源”页面上找到更多 CIDER 演示.
5.2. 使用 cider-mode
cider-mode 是一个相当标准的 Emacs minor mode, 它主要的存在是为了为所有 CIDER 的 REPL 驱动的命令提供一个通用的键映射, 这些命令旨在从 Clojure(Script) 源缓冲区中使用.
在这里, 你会找到用于求值, 代码和文档查找, 调试以及其他相关功能的命令. 这个模式是 CIDER 的核心, 掌握它对于提高生产力至关重要.
5.2.1. 启用 cider-mode
cider-mode 通常在你启动 CIDER 时自动启用, 但你也可以像这样为 Clojure(Script) 缓冲区显式启用它:
(add-hook 'clojure-mode-hook #'cider-mode)
或者如果你正在使用 clojure-ts-mode:
(add-hook 'clojure-ts-mode-hook #'cider-mode)
对于从 clojure-mode 或 clojure-ts-mode 派生的模式, 如 clojurescript-mode 和 clojure-ts-clojurescript-mode, 无需显式启用它.
5.2.2. 禁用 cider-mode
cider-mode 通常在你退出 CIDER 时自动禁用, 但你也可以通过输入 M-x cider-mode (前提是它已在该缓冲区中启用) 在任何缓冲区中显式禁用它.
5.2.3. 基本工作流程
通常, 你与任何源缓冲区的交互都始于用 C-c C-k 求值它. 之后, 你通常只会用 C-c C-e (求值前一个 form) 或 C-c C-c (求值当前顶层 form) 来求值单个 form.
有时某个求值会花费很长时间, 你可以用 C-c C-b 来中断它.
在源文件和 REPL 之间跳转就像按 C-c C-z 一样简单. 如果你在源缓冲区中用 C-u 前缀调用该命令, 它还会将 REPL 缓冲区的命名空间更改为与源缓冲区的命名空间匹配.
你还很可能会用 M-. 查找某个定义或用 C-c C-d C-d 查找一些文档.
定义和文档查找命令对 Java 和 Clojure 都有效.
如果你记不起某个需要的 var 的名字, cider-apropos (C-c C-d a 或 C-c C-d C-a) 和 cider-apropos-documentation (C-c C-d f 或 C-c C-d C-f) 会帮助你找到匹配某个字符串的 vars.
cider-mode 还为你提供了开箱即用的代码补全功能, 无需额外设置, 并集成了 eldoc.
这几个基本命令可以让你在日常工作中走得很远. 继续阅读以获取更多信息!
5.2.4. 按键参考
这是 cider-mode 的按键绑定列表:
| 命令 | 键盘快捷键 | 描述 |
|---|---|---|
cider-eval-last-sexp |
C-x C-e / C-c C-e |
求值点之前的 form, 并在回显区和/或缓冲区 overlay 中显示结果 (根据 cider-use-overlays). 如果带前缀参数调用, 则将结果插入当前缓冲区. |
cider-eval-last-sexp-and-replace |
C-c C-v w |
求值点之前的 form, 并用其结果替换它. |
cider-eval-last-sexp-to-repl |
C-c M-e |
求值点之前的 form, 并将其结果输出到 REPL 缓冲区. 如果带前缀参数调用, 调用后将带你到 REPL 缓冲区. |
cider-insert-last-sexp-in-repl |
C-c M-p |
将点之前的 form 加载到 REPL 缓冲区中. |
cider-insert-last-sexp-in-repl |
C-u C-c M-p |
将点之前的 form 加载到 REPL 缓冲区中并求值. |
cider-pprint-eval-last-sexp |
C-c C-v C-f e |
求值点之前的 form, 并在弹出缓冲区中漂亮地打印结果. 如果带前缀参数调用, 则将结果作为注释插入当前缓冲区. |
cider-pprint-eval-defun-at-point |
C-c C-v C-f d |
求值点下的顶层 form, 并在弹出缓冲区中漂亮地打印结果. 如果带前缀参数调用, 则将结果作为注释插入当前缓冲区. |
cider-eval-defun-at-point |
C-M-x C-c C-c |
求值点下的顶层 form, 并在回显区显示结果. |
cider-eval-dwim |
C-c C-v s C-c C-v C-s |
如果没有激活的区域, 则用 cider-eval-defun-at-point 求值顶层 form. 如果有激活的区域, 则运行 cider-eval-region. |
cider-eval-list-at-point |
C-c C-v l C-c C-v C-l |
求值点周围的列表. |
cider-eval-sexp-at-point |
C-c C-v v C-c C-v C-v |
求值点周围的 form. |
cider-eval-defun-at-point |
C-u C-M-x C-u C-c C-c |
调试点下的顶层 form, 并逐步执行其求值过程. |
cider-eval-defun-up-to-point |
C-c C-v z |
求值直到点的顶层 form. |
cider-eval-region |
C-c C-v r |
求值区域并在回显区显示结果. |
cider-interrupt |
C-c C-b |
中断任何待处理的求值. |
cider-macroexpand-1 |
C-c C-m |
对点处的 form 调用 macroexpand-1, 并在宏展开缓冲区中显示结果. 如果带前缀参数调用, 则使用 macroexpand 而不是 macroexpand-1. |
cider-macroexpand-all |
C-c M-m |
对点处的 form 调用 clojure.walk/macroexpand-all, 并在宏展开缓冲区中显示结果. |
cider-eval-ns-form |
C-c C-v n |
求值 ns form. 如果带前缀参数调用, 则先取消定义 ns 中的所有 vars 和别名. |
cider-repl-set-ns |
C-c M-n (M-)n |
将 REPL 缓冲区的命名空间切换到当前缓冲区的命名空间. |
cider-switch-to-repl-buffer |
C-c C-z |
切换到相关的 REPL 缓冲区. 使用前缀参数将 REPL 缓冲区的命名空间更改为与当前访问的源文件匹配. |
cider-switch-to-repl-buffer |
C-u C-u C-c C-z |
根据用户提示的目录切换到 REPL 缓冲区. |
cider-load-buffer-and-switch-to-repl-buffer |
C-c M-z |
加载 (求值) 当前缓冲区并切换到相关的 REPL 缓冲区. 使用前缀参数将 REPL 缓冲区的命名空间更改为与当前访问的源文件匹配. |
cider-describe-connection |
C-c M-d |
显示默认 REPL 连接详情, 包括项目目录名, 缓冲区命名空间, 主机和端口. |
cider-find-and-clear-repl-output |
C-c C-o |
清除 REPL 缓冲区中的最后一次输出. 带前缀参数时, 它会清除整个 REPL 缓冲区, 只留下一个提示符. 如果你在并排的缓冲区中运行 REPL 缓冲区, 这很有用. |
cider-load-buffer |
C-c C-k |
加载 (求值) 当前缓冲区. 如果带前缀参数调用, 则在加载前取消定义 ns 中的所有 vars 和别名. |
cider-load-file |
C-c C-l |
加载 (求值) 一个 Clojure 文件. 如果带前缀参数调用, 则在加载前取消定义 ns 中的所有 vars 和别名. |
cider-load-all-files |
C-c C-M-l |
加载 (求值) 目录下所有 Clojure 文件. 如果带前缀参数调用, 则在加载前取消定义每个文件中的所有 vars 和别名. |
cider-ns-refresh |
C-c M-n (M-)r |
重新加载 classpath 上所有修改过的文件. 如果带前缀参数调用, 则重新加载 classpath 上的所有文件. 如果带双前缀参数调用, 则在重新加载前清除命名空间跟踪器的状态. |
cider-doc |
C-c C-d d C-c C-d C-d |
显示点处符号的文档字符串. 如果带前缀参数调用, 则反转 cider-prompt-for-symbol 的值. |
cider-javadoc |
C-c C-d j C-c C-d C-j |
显示点处符号的 JavaDoc (在你的默认浏览器中). 如果带前缀参数调用, 则反转 cider-prompt-for-symbol 的值. |
cider-clojuredocs |
C-c C-d c C-c C-d C-c |
在 ClojureDocs 中查找符号. 如果带前缀参数调用, 则反转 cider-prompt-for-symbol 的值. |
cider-clojuredocs-web |
C-c C-d w C-c C-d C-w |
在 web 浏览器中打开符号的 ClojureDocs 文档. 如果带前缀参数调用, 则反转 cider-prompt-for-symbol 的值. |
cider-apropos |
C-c C-d a C-c C-d C-a |
Apropos 搜索函数/vars. |
cider-apropos-documentation |
C-c C-d f C-c C-d C-f |
Apropos 搜索文档. |
cider-apropos-documentation-select |
C-c C-d e C-c C-d C-e |
Apropos 搜索文档并选择. |
cider-inspect |
C-c M-i |
检查表达式. 如果点处有表达式, 则对其操作. |
cider-toggle-trace-var |
C-c M-t v |
切换 var 追踪. 如果带前缀参数调用, 则反转 cider-prompt-for-symbol 的值. |
cider-toggle-trace-ns |
C-c M-t n |
切换命名空间追踪. |
cider-undef |
C-c C-u |
取消定义一个符号. 如果带前缀参数调用, 则反转 cider-prompt-for-symbol 的值. |
cider-undef-all |
C-c C-M-u |
取消定义命名空间中的所有符号和别名. |
cider-test-run-test |
C-c C-t t C-c C-t C-t |
运行点处的测试. 如果点下的 form 是一个函数, 尝试搜索并运行相应的测试. |
cider-test-rerun-test |
C-c C-t a C-c C-t C-a |
重新运行你上次运行的测试. |
cider-test-run-ns-tests |
C-c C-t n C-c C-t C-n |
运行当前命名空间的测试. |
cider-test-run-loaded-tests |
C-c C-t l C-c C-t C-l |
运行所有已加载命名空间的测试. |
cider-test-run-project-tests |
C-c C-t p C-c C-t C-p |
运行所有项目命名空间的测试. 这会加载额外的命名空间. |
cider-test-rerun-failed-tests |
C-c C-t r C-c C-t C-r |
重新运行失败/错误的测试. |
cider-test-show-report |
C-c C-t b C-c C-t C-b |
显示测试报告缓冲区. |
cider-find-var |
M-. |
跳转到符号的定义. 如果带前缀参数调用, 则反转 cider-prompt-for-symbol 的值. |
cider-find-keyword |
C-c C-: |
查找点处关键字的命名空间及其主要出现位置. 如果带前缀参数调用, 则反转 cider-prompt-for-symbol 的值. |
cider-find-dwim-at-mouse |
mouse-5 or mouse-9 |
使用鼠标跳转到符号的定义. |
xref-pop-marker-stack |
mouse-4 or mouse-8 |
跳回到调用 cider-find-dwim-at-mouse 的位置. |
cider-find-resource |
C-c M-. |
跳转到点处字符串引用的资源. |
cider-find-ns |
C-c C-. |
跳转到 classpath 上的某个命名空间. |
cider-xref-fn-refs |
C-c C-? r |
在专用缓冲区中显示已加载命名空间中的函数用法. |
cider-xref-fn-refs-select |
C-c C-? C-r |
在 minibuffer 选择器中显示已加载命名空间中的函数用法. |
cider-xref-fn-deps |
C-c C-? d |
在专用缓冲区中显示函数依赖 (它使用的其他函数). |
cider-xref-fn-deps-select |
C-c C-? C-d |
在 minibuffer 选择器中显示函数依赖 (它使用的其他函数). |
cider-pop-back |
M-, |
返回到你跳转前的位置. |
complete-symbol |
M-TAB |
补全点处的符号. |
cider-quit |
C-c C-q |
退出当前的 nREPL 连接. |
无需记住此列表. 如果你在一个启用了 cider-mode 的 Clojure 缓冲区中, 你将有一个 CIDER 菜单可用. 该菜单列出了所有最重要的命令及其键绑定. 你也可以调用 C-h f RET cider-mode 来获取 cider-mode 的键绑定列表.
CIDER 交互菜单
一个更好的解决方案是安装 which-key, 它会在你开始输入一些键时自动向你显示可用键绑定的列表. 这将大大简化你与 CIDER 的交互, 特别是在开始时. 如果你在 Clojure 缓冲区中输入 C-c C-d, 你会看到以下内容:
CIDER which-key
5.3. 代码求值
代码求值是交互式编程的核心. CIDER 提供了大量的求值相关命令, 几乎可以涵盖任何可以想象到的用例.
所有求值命令都在 cider-eval.el 中定义.
5.3.1. 术语
CIDER 的求值命令使用了一套在 Emacs 中流行但对不熟悉它的人来说有些困惑的术语. 让我们来看看它:
defun- 顶层表达式sexp- s-expression, formlast-sexp- 光标 (在 Emacs lingo 中称为 point) 前的 formsexp-at-point- 光标下/周围的 formbuffer- Emacs 用于表示任何内容的抽象; 通常由文件支持.region- 缓冲区的选中部分load- "evaluate" 的同义词; 最常用于缓冲区/文件的上下文中
5.3.2. 基本求值
由于 CIDER 几乎所有的功能都是通过检查应用程序的运行时 (REPL) 状态来实现的, 所以在像代码补全, eldoc 或定义查找这样的功能工作之前, 你需要先求值一些东西.
通常, 当访问 Clojure 缓冲区时, 你做的第一件事就是用 cider-load-buffer (C-c C-k) 加载 (求值) 缓冲区.
之后, 大多数时候你会一次求值一个表达式, 使用 cider-eval-last-sexp (C-c C-e 或 C-x C-e) 或 cider-eval-defun-at-point (C-c C-c 或 C-M-x).
- 如果我没有先求值整个缓冲区会发生什么?
你可能想知道为什么需要求值整个源缓冲区. 毕竟, 如果你能只开始求值你感兴趣的 forms, 不是会更好吗?
技术上讲, 你不是必须先求值整个源缓冲区, 但不这样做会引入一些微妙之处和一点复杂性. 总的来说, 对于后续代码求值真正至关重要的唯一部分是
ns声明. 因为缓冲区内的表达式是在其命名空间的上下文中求值的, 你必须先创建那个命名空间.在 CIDER 的早期, 当一个命名空间不存在时, CIDER 只会抛出一个错误. 很多人对这种行为感到困惑和不满. 最终, CIDER 变得更聪明了, 现在它总能知道某个源缓冲区中的
nsform 是否已经被求值过, 或者在最初求值后是否发生了变化. 有了这些信息, 如果你没有自己求值它们, CIDER 会在求值该命名空间中的代码之前自动求值已更改的nsforms. 该行为由变量cider-auto-track-ns-form-changes控制.通常, 这类求值命令会提供双重反馈——你会在 Emacs minibuffer 和源缓冲区中的内联 overlay 中看到结果.
如果求值结果很大 (例如, 一个有几十个键的 map), 最好在一个专用的缓冲区中显示它. 你可以用
cider-pprint-eval-last-sexp(C-c C-p) 来做这件事. 另外一个好处是——结果将在结果缓冲区中被漂亮地打印出来. - 为什么结果不总是被漂亮地打印?
许多人可能想知道为什么 CIDER 不总是漂亮地打印所有结果. 毕竟——漂亮打印的结果总是更容易阅读, 对吗?
不这样做的原因很简单——多行结果在 minibuffer 和 overlays 中看起来不太好. 这就是为什么在那里漂亮打印结果没什么意义.
由于大多数时候你会事先知道期望什么样的结果, 我们觉得最好由你来决定根据情况使用哪个求值命令.
5.3.3. 奇异的求值命令
WIP
虽然前面讨论的基本求值命令对大多数人来说应该足够了, 但 CIDER 提供了大量的附加求值命令. 其中一些非常奇特, 以至于可以轻易地称之为“奇异”. 本节将简要介绍其中一些.
广义上讲, 奇异的求值命令可以分为以下几类:
- 某些基本命令的变体 (例如
cider-eval-list-at-point和cider-eval-sexp-at-point) - 将结果插入当前缓冲区的命令 (例如
cider-eval-last-sexp-and-replace) - 在不同上下文中求值表达式的命令 (例如使用用户提供的绑定)
- 求值缓冲区某个特定部分的命令 (例如
cider-eval-ns-form)
大多数奇异的求值命令没有“顶层”键绑定, 应通过 CIDER 的求值命令键映射 (cider-eval-commands-map) 访问, 这意味着它们共享标准前缀 C-c C-v. 除此之外, 几个漂亮打印其结果的相关命令被分组在 C-c C-v C-f 下.
5.3.4. 在 Minibuffer 中求值 Clojure 代码
你几乎可以在任何时候使用 M-x cider-read-and-eval (在 cider-mode 缓冲区中绑定到 C-c M-:) 在 minibuffer 中求值 Clojure 代码. TAB 补全将在 minibuffer 中工作, 就像在 REPL 和源缓冲区中一样.
在 Clojure 缓冲区中输入 C-c C-v . 会将点处的 defun 插入到 minibuffer 中进行求值. 这样你可以向函数传递参数并求值它, 并在 minibuffer 中看到结果.
你还可以在 minibuffer 中启用其他方便的模式. 例如, 你可能希望同时拥有 eldoc-mode 和 paredit-mode:
(add-hook 'eval-expression-minibuffer-setup-hook #'eldoc-mode) (add-hook 'eval-expression-minibuffer-setup-hook #'paredit-mode)
5.3.5. 求值钩子
你可以使用 cider-file-loaded-hook 来在文件被求值后触发某个动作.
cider-auto-test-mode 是基于这个钩子实现的——每次你求值一个文件时, 测试都会重新运行.
你可以使用 cider-before-eval-hook 来在求值请求发送到 nrepl 服务器之前触发某个动作.
你可以使用 cider-after-eval-done-hook 来在从 nrepl 服务器收到状态为 "done" 的求值响应后触发某个动作.
5.3.6. 同步 vs 异步求值
nREPL 有一个异步求值模型, 其中求值请求被排队, 响应在可用时发送回客户端. 这个模型在大多数情况下工作得很好, 因为它不需要客户端在等待响应时阻塞, 但它也要求客户端能够舒适地处理响应回调.
不幸的是, Emacs 的一些内部 API 与异步求值不兼容 (例如补全, eldoc 等), 在这些情况下, CIDER 会模拟同步求值. 理解几点很重要:
- CIDER 的同步求值命令应该谨慎使用
- 同步求值 API 最常见的用例是求值一些简单且快速运行的工具相关代码
- 同步求值可能导致客户端锁定, 因为 Emacs (大部分) 是单线程设计的
CIDER 试图通过为同步求值施加 10 秒的求值请求超时来缓解后者. 如果需要, 你可以调整这个默认值:
;; 将同步请求超时增加到 1 分钟 (setq nrepl-sync-request-timeout 60) ;; 禁用同步请求超时 (setq nrepl-sync-request-timeout nil)
CIDER 内部将其执行的第一个同步求值请求的超时增加到 30 秒, 因为它可能需要很多命名空间并且需要更多时间来完成. 详情请参见 cider—prep-interactive-eval.
5.3.7. 配置
- 在 Java 21 及更新版本上启用求值中断
必须启用配置变量
cider-enable-nrepl-jvmti-agent才能使中断在 Java 21+ 上正常工作. 参见 JVMTI agent 部分. - 求值期间显示 Spinner
一些求值操作需要一段时间才能完成, 因此 CIDER 会在 modeline 中显示一个视觉指示器 (一个 spinner) 来表明这一点. 求值进行中的 spinner 可以由几个变量控制:
cider-eval-spinner-type(默认值'progress-bar) - 控制 spinner 的外观. 所有可能的选项请参见spinner-types.cider-eval-spinner-delay控制显示 spinner 之前的时间 (以秒为单位). 默认值为 1 秒.cider-show-eval-spinner控制是否显示 spinner. 将此变量设置为nil以禁用它.CIDER 内部使用了优秀的包
spinner.el.
- 结果的语法高亮
默认情况下, 交互式求值的结果 (无论是在 minibuffer 中显示还是在 overlays 中显示) 都会作为 Clojure 代码进行 font-locked. 你可以通过调整配置选项
cider-result-use-clojure-font-lock来禁用它:(setq cider-result-use-clojure-font-lock nil)
- Overlays
当你在 Clojure 文件中求值代码时, 结果会显示在缓冲区本身, 在被求值代码之后的一个 overlay 中. 如果你希望这个 overlay 像 Clojure 代码一样被 font-locked (语法高亮), 请设置以下变量.
(setq cider-overlays-use-font-lock t)
只要错误不是已经被
cider-stacktrace-mode显示, 它们也会显示 overlays. 为了总是优先使用错误 overlays 而不是*cider-error*缓冲区, 请自定义:(setq cider-show-error-buffer nil)
如果你只想看到错误的 overlays (而不是成功求值的), 你可以限制 overlays 只显示错误 (并在底部的回显区显示非错误结果), 通过自定义
cider-use-overlays变量:(setq cider-use-overlays 'errors-only)
你可以完全禁用 overlays (并在底部的回显区显示结果), 使用
cider-use-overlays变量:(setq cider-use-overlays nil)
指示错误的 Overlays 默认会去掉文件/行/阶段信息. (例如: 整个
Syntax error compiling at (src/ns.clj:227:3).前导部分)你可以通过自定义来防止任何修剪:
(setq cider-inline-error-message-function #'identity)
默认情况下, 结果 overlays 显示在行尾. 你可以设置变量
cider-result-overlay-position来在其各自的 forms 结尾显示结果. 请注意, 这也影响调试器 overlays 的位置.(setq cider-result-overlay-position 'at-point)
你还可以使用变量
cider-eval-result-duration来自定义 overlays 的持久化方式.默认情况下, 其值为
'command, 意味着结果 overlays 在下一次用户执行的命令 (如移动光标或滚动) 后消失.将该变量设置为一个数字表示 overlays 被移除前的持续时间 (以秒为单位), 而将其设置为
'change'则使 overlays 持续到下一次缓冲区内容的更改.(setq cider-eval-result-duration 5.0) (setq cider-eval-result-duration 'change)
- 加载时自动保存 Clojure 缓冲区
通常, 当你输入
C-c C-k(cider-load-buffer) 时, CIDER 会提示你保存一个修改过的 Clojure 缓冲区. 你可以通过调整cider-save-file-on-load来改变这种行为.不提示也不保存:
(setq cider-save-file-on-load nil)
不提示直接保存:
(setq cider-save-file-on-load t)
- 更改交互式求值的结果前缀
更改交互式求值的结果前缀 (不是 REPL 前缀). 默认前缀是 ~⇒ ~.
(setq cider-eval-result-prefix ";; => ")
要完全移除前缀, 只需将其设置为空字符串 ("").
- 更改输出目的地
默认情况下, CIDER 会将某些求值产生的输出显示在 REPL 缓冲区中, 但你也可以将输出导入到一个专用的缓冲区. 你可以通过
cider-interactive-eval-output-destination来配置此行为.(setq cider-interactive-eval-output-destination 'output-buffer)
此外, 还有一个变量
cider-redirect-server-output-to-repl, 它会捕获通常会进入*nrepl-server*缓冲区的输出 (前提是它是通过cider-jack-in启动的) 并将其重定向到 REPL 缓冲区. 你可以像这样禁用此重定向:(setq cider-redirect-server-output-to-repl nil)
重定向功能是在
cider-nrepl中作为 nREPL middleware 实现的. 如果你在没有cider-nrepl的情况下使用 CIDER, 将不会发生任何输出重定向. - 存储求值结果
默认情况下, CIDER 将最近一次求值命令的返回值存储在文本寄存器
e中. 你可以通过insert-register(C-x r i) 访问这些内容.这通常对于仔细检查或文本操作一个临时显示的求值结果很有用, 而不必使用像
cider-insert-last-sexp-in-repl这样的专门命令重新求值 form.你可以使用变量
cider-eval-register来自定义使用哪个寄存器, 或者将其设置为nil以禁用该功能.(setq cider-eval-register nil)
内置的 inspector 可以用来查看和导航复杂的嵌套结果.
你还可以在任何求值命令后使用命令
cider-kill-last-result~(~C-c C-v k) 将其结果存储在 kill ring 中. 即使cider-eval-register功能被禁用, 这个命令也有效. - 在注释内部求值代码
默认情况下, 在顶层
commentform 内部使用 defun 级别的求值命令时, 返回值总是nil. 在<point>处调用cider-eval-defun-up-to-point也返回nil:(comment (let [b "str"] (-> b keyword<point> ;;=> nil name)) *e)
这不是一个 bug, 这只是
commentform 的工作方式 (它在求值时简单地返回nil). 然而, 通常希望将光标所在的 form 视为它不在注释内部. 为了帮助解决这个问题,clojure-mode提供了变量clojure-toplevel-inside-comment-form. 如果这个变量设置为t, 那么 defun 级别的命令将忽略comment包装器. 让我们重新审视前面的例子:(comment (let [b "str"] (-> b keyword<point> ;;=> :str name)) *e)
使用
cider-eval-defun-at-point我们得到:(comment (let [b "str"] (-> b keyword<point> name)) ;;=> "str" *e)
5.3.8. 键绑定
你可能已经注意到, CIDER 通常为许多求值命令提供了 2-3 个不同的键绑定. 如果你想知道“为什么?”, 答案很简单——历史原因. CIDER 的主要灵感来源, Emacs 和 SLIME, 提供了或多或少相同的功能, 但使用了不同的键绑定. CIDER 试图通过同时采纳它们来找到一个共同点.
除此之外, 当 CIDER 创下了求值命令的世界纪录时, 我们为所有求值命令引入了一个专用的键映射 (即所有以 C-c C-v 为前缀的命令). 这导致了一些有趣的情况, 比如 cider-eval-defun-at-point 有 3 个键绑定:
C-M-x(Emacs 风格)C-c C-c(SLIME 风格)C-c C-v (C-)d(CIDER 风格)
好吧, 那些技术上是 4 个键绑定, 但谁在乎呢!
你们中的一些人可能想知道为什么是 C-c C-v 而不是 C-c C-e, 对吧? 还是历史原因. 历史上 C-c C-e 被映射到 cider-eval-last-sexp, 否则我们会选择这个绑定. 未来仍有可能回收它, 因为大多数人可能正在使用 C-x C-e 来执行 cider-eval-last-result, 而好的键绑定太宝贵了, 不应该像这样浪费.
下面是大多数求值命令的键绑定列表:
| 命令 | 键盘快捷键 | 描述 |
|---|---|---|
cider-eval-last-sexp |
C-x C-e C-c C-e |
求值点之前的 form, 并在回显区和/或缓冲区 overlay 中显示结果 (根据 cider-use-overlays). 如果带前缀参数调用, 则将结果插入当前缓冲区. |
cider-tap-last-sexp |
C-c C-v q C-c C-v C-q |
类似于 cider-eval-last-sexp, 但也 taps 结果. |
cider-eval-last-sexp-and-replace |
C-c C-v w |
求值点之前的 form, 并用其结果替换它. |
cider-eval-last-sexp-to-repl |
C-c M-e |
求值点之前的 form, 并将其结果输出到 REPL 缓冲区. 如果带前缀参数调用, 调用后将带你到 REPL 缓冲区. |
cider-insert-last-sexp-in-repl |
C-u C-c M-p |
将点之前的 form 加载到 REPL 缓冲区中并求值. |
cider-pprint-eval-last-sexp |
C-c C-p C-c C-v C-f e |
求值点之前的 form, 并在弹出缓冲区中漂亮地打印结果. 如果带前缀参数调用, 则将结果作为注释插入当前缓冲区. |
cider-pprint-eval-defun-at-point |
C-c C-v C-f d |
求值点下的顶层 form, 并在弹出缓冲区中漂亮地打印结果. 如果带前缀参数调用, 则将结果作为注释插入当前缓冲区. |
cider-eval-defun-at-point |
C-M-x C-c C-c |
求值点下的顶层 form, 并在回显区显示结果. |
cider-eval-list-at-point |
C-c C-v l C-c C-v C-l |
求值点周围的列表. |
cider-eval-sexp-at-point |
C-c C-v v C-c C-v C-v |
求值点周围的 form. |
cider-eval-defun-at-point |
C-u C-M-x C-u C-c C-c |
调试点下的顶层 form, 并逐步执行其求值过程. |
cider-tap-sexp-at-point |
C-c C-v t C-c C-v C-t |
求值并 tap 点周围的 form. |
cider-eval-defun-up-to-point |
C-c C-v z |
求值直到点的顶层 form. |
cider-eval-region |
C-c C-v r |
求值区域并在回显区显示结果. |
cider-interrupt |
C-c C-b |
中断任何待处理的求值. |
cider-eval-ns-form |
C-c C-v n |
求值 ns form. |
cider-load-buffer-and-switch-to-repl-buffer |
C-c M-z |
加载 (求值) 当前缓冲区并切换到相关的 REPL 缓冲区. 使用前缀参数将 REPL 缓冲区的命名空间更改为与当前访问的源文件匹配. |
cider-load-buffer |
C-c C-k |
加载 (求值) 当前缓冲区. |
cider-load-file |
C-c C-l |
加载 (求值) 一个 Clojure 文件. |
cider-load-all-files |
C-c C-M-l |
加载 (求值) 目录下所有 Clojure 文件. |
cider-kill-last-result |
C-c C-v k |
将最后求值的结果保存到 kill ring 中. |
你会在 CIDER Eval 菜单中找到所有求值命令及其键绑定.
5.4. 代码补全
CIDER 为源缓冲区 (由 cider-mode 提供支持) 和 REPL 缓冲区都提供了智能代码补全功能.
CIDER 内部利用 compliment 进行 Clojure 补全, 利用 clj-suitable 进行 ClojureScript 补全.
这两个库的改进会自动转化为 CIDER 的改进.
5.4.1. 标准补全
开箱即用, CIDER 使用标准的 Emacs 工具进行代码补全. 当你按 TAB 或 M-TAB 时, 你将在一个专用的缓冲区中获得补全候选.
代码补全
关于标准补全, 有两点需要注意:
- 默认的
M-TAB键绑定在将其用于在打开的应用程序之间切换的桌面环境中无法使用. - 你必须通过在你的 Emacs 配置中添加此代码片段来手动配置
TAB进行补全:
(setq tab-always-indent 'complete)
通常 TAB 只进行缩进, 但现在如果代码已经正确缩进, 它也会进行补全.
5.4.2. 自动补全
虽然标准的 Emacs 工具工作得很好, 但我们建议 CIDER 用户考虑使用 company-mode 或 corfu-mode 代替. 这些可以用于源代码和 REPL 缓冲区中的自动补全, 具有以下优点:
- 更好的 UI.
- 与 Clojure docstrings 和 Java doc 注释的集成.
5.4.3. 配置
company-mode 和 corfu-mode 都受以下 CIDER 配置选项的影响:
cider-docstring-max-lines(默认 20) 控制在提供补全时将包含多少行 docstring (最多) (在弹出窗口或回显区, 取决于你的设置). 值得注意的是, 对于 Java 文档, CIDER 不仅仅是修剪行, 而是查看结构并尝试找到适合cider-docstring-max-lines的最大组合:- 整个注释体, 后跟其“块标签” (Returns/Throws/Params 信息)
- 注释的第一句话, 后跟块标签
- 块标签
- 注释的第一句话.
- company-mode 安装
要安装
company-mode:M-x package-install RET company RET安装后, 你可以全局启用
company-mode:(global-company-mode)
或者通过特定于模式的钩子:
(add-hook 'cider-repl-mode-hook #'company-mode) (add-hook 'cider-mode-hook #'company-mode)
当
company-mode启用时, 它将从cider-complete-at-point接收补全信息, 不需要额外的设置或插件.如果你更喜欢手动触发补全, 你可以将其添加到你的配置中:
(setq company-idle-delay nil) ; 从不自动开始补全 (global-set-key (kbd "M-TAB") #'company-complete) ; 使用 M-TAB, a.k.a. C-M-i,作为手动触发器
要使
TAB进行补全, 同时不失去手动缩进的能力, 你可以将其添加到你的配置中:(global-set-key (kbd "TAB") #'company-indent-or-complete-common)
Company 的文档机制和 CIDER 的文档设施是集成的.
当向你提供补全时, 你可以按
(F1)(默认的company-show-doc-buffer键绑定) 来在临时的cider-doc缓冲区下显示文档和参数列表.为了让 Company 总是在临时的
cider-doc缓冲区下显示 docstrings 和其他元数据, 而无需按额外的键, 请自定义:;; (你可能希望在 clojure-mode-hook 中将其作为 setq-local 来做) (custom-set-variables '(company-auto-update-doc t))
5.4.4. 丰富的候选匹配
从 1.18 版本开始, CIDER 默认启用一个自定义的补全样式, 提供更丰富和更有用的候选匹配, 例如:
- 通过各个部分的首字母匹配包含破折号的长变量, 例如
mi或mai补全为map-indexed. - 通过部分的首字母匹配命名空间, 例如
cji补全为clojure.java.io. - 通过短名称前缀匹配未导入的类名, 例如
BiFun补全为java.util.function.BiFunction.
你可以在这里学习所有的补全场景和功能.
如果你只想接收标准的前缀限制补全 (候选者必须在开头逐字包含前缀), 你可以通过将其添加到你的配置中来禁用此功能:
(cider-enable-cider-completion-style -1)
5.4.5. 补全注解
补全候选将默认用对应其类型的缩写进行注解, 并且 (根据上下文) 还会注解其命名空间. 用于格式化注解的函数可以通过 cider-annotate-completion-function 进行配置. 使用的缩写由 cider-completion-annotations-alist 配置, 命名空间包含的上下文由 cider-completion-annotations-include-ns 配置.
补全注解
可以通过将 cider-annotate-completion-candidates 设置为 nil 来禁用补全注解.
5.4.6. 补全样式
CIDER 的 complete-at-point 函数支持大多数补全样式, 包括 partial-completion, orderless, flex, 以及其自定义的补全样式 cider. 后者默认启用. 有时用户可能希望为 CIDER 的 complete-at-point 函数使用不同的补全样式. 这可以通过设置 completion-category-overrides 来实现, 覆盖 CIDER complete-at-point 函数的补全样式. 以下代码片段完成了这个任务:
(add-to-list 'completion-category-overrides '(cider (styles basic)))
关于这些补全样式如何运作的更详细描述, 请参考官方 Emacs 手册中关于如何选择补全替代方案的部分.
这指定了 cider 补全类别默认应使用 basic 补全样式.
5.4.7. 关于类消歧的说明
有时, 补全用户体验可能会被一个要求 Member in class 的 completing-read 中断. 这用于更好的 Java 补全和文档.
然而, 如果你对当前候选不感兴趣, 对其进行消歧就毫无用处, 并且该提示可能会很烦人.
如果你正在使用 Company 进行补全, 并使用 IDO 进行 completing-read, 你可以通过自定义使 <up> 和 <down> 键取消提示:
(advice-add 'cider-class-choice-completing-read :around (lambda (f a b) (cider--with-temporary-ido-keys "<up>" "<down>" (funcall f a b))))
5.4.8. 更新过时的类和方法缓存
有时, 补全无法识别在 REPL 启动后动态加载的依赖项带来的新类 (例如通过 Clojure 1.12 的 add-lib). 执行 M-x cider-completion-flush-caches (或通过菜单 CIDER Interaction->Misc->Flush completion cache) 会强制补全后端重新读取它能在 classpath 上找到的所有类.
5.4.9. 实现细节
如果你只使用 cider-jack-in, 你真的不需要知道这些.
代码补全逻辑的大部分位于 cider-nrepl 的 completion middleware 中. 在内部, 它委托给 compliment 进行 Clojure 补全, 委托给 clj-suitable 进行 ClojureScript 补全.
nREPL 也有一个内置的 completions op, 在没有 cider-nrepl 的情况下, CIDER 将会回退到它. 它的 API 与 cider-nrepl 中的 complete op 类似, 并且可以配置为使用不同的补全函数. 内置的 op 目前只支持 Clojure. 更多细节请参见 nREPL 文档.
基本上, 在有 cider-nrepl 的情况下你会得到很棒的代码补全, 否则就是基本的补全.
5.5. 代码重新载入
虽然 Clojure 和 CIDER 的交互式编程风格意味着你重启应用程序的次数远少于其他语言和开发环境, 但有时你会想要清理所有东西并重新加载一个或多个命名空间, 以确保它们是最新的, 并且没有临时的定义残留.
5.5.1. "Reloaded" 工作流
下面描述的工作流因这篇博客文章而流行, 它也是 CIDER 对此的灵感来源.
输入 C-c M-n r 或 C-c M-n M-r 将调用 cider-ns-refresh 并重新加载 classpath 上所有已修改的 Clojure 文件.
添加一个前缀参数, C-u C-c M-n r, 将无条件地重新加载 classpath 上的所有命名空间, 而不管它们的修改状态如何.
添加一个双前缀参数, C-u C-u C-c M-n r, 将在重新加载之前首先清除命名空间跟踪器的状态. 这对于从某些类型的错误中恢复很有用, 而正常的重新加载无法恢复这些错误. 一个很好的例子是循环依赖. 权衡之处在于, 任何已删除文件中的陈旧代码可能不会被完全卸载.
cider-ns-refresh 包装了 clojure.tools.namespace, 因此, 关于编写可重载代码的相同好处和注意事项也适用.
上述三个操作分别类似于 clojure.tools.namespace.repl/refresh, clojure.tools.namespace.repl/refresh-all 和 clojure.tools.namespace.repl/clear (后跟一个正常的 refresh).
5.5.2. 配置
你可以定义在重新加载之前和成功重新加载之后调用的 Clojure 函数, 当使用 cider-ns-refresh 时:
(setq cider-ns-refresh-before-fn "user/stop-system!" cider-ns-refresh-after-fn "user/start-system!")
这些必须设置为绑定到无参数函数的 vars 的命名空间限定名称. 这些函数必须是同步的 (阻塞的), 并且期望是具有副作用的 - 它们将总是串行执行, 不会重试.
默认情况下, 关于正在进行的重载状态的消息将在你调用 cider-ns-refresh 后显示在回显区. 同样的信息也将记录在 *cider-ns-refresh-log* 缓冲区中, 连同 cider-ns-refresh-before-fn 和 cider-ns-refresh-start-fn 打印到 *out* 或 *err* 的任何内容.
你可以通过将 cider-ns-refresh-show-log-buffer 变量设置为非 nil 值, 使 *cider-ns-refresh-log* 缓冲区在你调用 cider-ns-refresh 后自动显示. 这也将防止任何相关的消息同时显示在回显区.
(setq cider-ns-refresh-show-log-buffer t)
默认情况下, CIDER 会提示是否保存 classpath 上所有已修改的 clojure-mode 缓冲区访问的文件. 你可以使用 cider-ns-save-files-on-refresh 和 cider-ns-save-files-on-refresh-modes 来自定义此行为.
5.5.3. 使用 clj-reload
clj-reload 的支持是在 CIDER 1.14 中引入的.
你也可以使用 cider-ns-refresh 配合 clj-reload 而不是 clojure.tools.namespace. 它支持在重载之间保留 vars, 以及与 tools.namespace 的一些其他差异.
(setq cider-ns-code-reload-tool 'clj-reload)
使用 clj-reload, 你应该像使用文档中描述的那样设置源目录. 如果你不手动设置它们, 它将默认使用当前项目的资源目录, 方式与 tools.namespace 相同.
未来我们可能会将 cider-ns-refresh 重命名为更具工具无关性的名称 (例如 cider-ns-smart-reload), 以反映它现在支持不同的代码重载工具.
5.5.4. 基本代码重载
有时, cider-ns-refresh 可能不适合你. 如果你正在寻找一种更强制的重载方式, 可以使用 cider-ns-reload (C-c M-n l) 和 cider-ns-reload-all (C-c M-n M-l) 命令. 这些命令在 REPL 中调用 Clojure 的 (require ... :reload) 和 (require ... :reload-all) 命令.
这些命令不依赖于 cider-nrepl, 所以它们总是可用的.
5.5.5. 键绑定
| 命令 | 键盘快捷键 | 描述 |
|---|---|---|
cider-ns-refresh |
C-c M-n r C-c M-n M-r |
重新加载 classpath 上所有已修改的 Clojure 文件. 添加一个前缀参数, C-u C-c M-n r, 将无条件地重新加载 classpath 上的所有命名空间, 而不管它们的修改状态如何. 添加一个双前缀参数, C-u C-u C-c M-n r, 将在重新加载之前首先清除命名空间跟踪器的状态. |
cider-ns-reload |
C-c M-n l |
使用 (require :reload) 重新加载 |
cider-ns-reload-all |
C-c M-n M-l |
使用 (require :reload-all) 重新加载 |
5.6. 高级打印
5.6.1. 配置一个打印函数
CIDER 依赖于 nREPL 自己的值打印机制. 详情请参考 nREPL 的文档.
你可以使用选项 cider-print-fn 来配置 CIDER 用于漂亮打印求值结果和其他数据的函数, 它可以取以下可能的值:
nil让 nREPL 选择打印函数. 这将使用nrepl.middleware.print/*print-fn*的绑定值, 默认为等价于clojure.core/pr.pr使用等价于clojure.core/pr.pprint使用内置的clojure.pprint/pprint(这是默认值).fipp使用 Fast Idiomatic Pretty-Printer. 这比clojure.core/pprint快大约 5-10 倍.puget使用 Puget, 它在fipp之上提供了数据的规范序列化, 但会带来轻微的性能成本.zprint使用 zprint, 这是一个快速灵活的替代方案, 替代上面提到的库.要使
fipp,puget和zprint打印机工作, 你需要在你的项目中明确添加相应的依赖项.
或者, cider-print-fn 可以设置为一个 Clojure var 的命名空间限定名称, 其函数接受三个参数:
- 要打印的对象
- 用于打印的
java.io.PrintWriter - 一个 (可能为 nil) 的选项 map.
(setq cider-print-fn "user/my-pprint")
这里有一个例子:
(ns cider.pprint (:require [clojure.pprint :as pp])) (defn pprint "A simple wrapper around ~clojure.pprint/write~. Its signature is compatible with the expectations of nREPL's wrap-print middleware." [value writer options] (apply pp/write value (mapcat identity (assoc options :stream writer))))
5.6.2. 限制打印输出
你可以设置 cider-print-quota 来限制任何打印操作将返回的字节数. 默认值为 1MB, 如果不希望有任何限制, 可以设置为 nil. 请注意, 如果没有设置配额, 某些打印操作可能永远不会终止 —— 你仍然可以使用 cider-interrupt 来停止它们.
你配置的打印函数也可能支持限制打印对象的长度和深度 —— 或者使用 clojure.core/*print-length* 和 clojure.core/*print-level*, 或者在提供的选项 map 中.
5.6.3. 打印选项
你可以通过设置 cider-print-options 向打印函数传递一个选项 map. 这里有一个例子:
(setq cider-print-options '(("length" 50) ("right-margin" 70)))
每个打印引擎都有自己的配置选项, 所以你必须确保相应地设置 cider-print-options.
这里有一个表格, 描述了每个打印引擎支持的最常见打印选项的名称差异.
clojure.core/pr |
clojure.pprint |
Fipp & Puget | zprint |
|---|---|---|---|
clojure.core/*print-length* |
length |
print-length |
max-length |
clojure.core/*print-level* |
level |
print-level |
max-depth |
clojure.pprint/*print-right-margin* |
right-margin |
width |
width |
并非所有打印引擎在所有情况下都使用 (或默认使用) 动态变量, 因此在 REPL 中设置它们可能不会产生预期的效果. 请参阅每个引擎的相应文档:
clojure.core/pr: https://clojure.github.io/clojure/clojure.core-api.html#clojure.core/*print-dup*clojure.pprint: https://clojuredocs.org/clojure.pprint/write- Fipp: https://github.com/brandonbloom/fipp/#printer-usage
- Puget: https://github.com/greglook/puget#usage
- zprint: https://github.com/kkinnear/zprint/#what-is-configurable
5.6.4. 打印输出的宽度
如果你正在使用 CIDER 提供的打印引擎之一, fill-column 的值将用于选项 map 中的相关宽度选项. 你可以通过在 cider-print-options 中硬编码相关选项来覆盖此设置.
5.7. 错误处理
时不时地, 你会在代码中犯一些错误, 这会导致求值错误. Clojure 的错误因其复杂性而臭名昭著, CIDER 花了很大力气来使其更容易解读.
大多数时候, CIDER 会在一个专用的缓冲区中显示错误, 与你当前求值代码的缓冲区并排.
使用 q 可以快速关闭一个错误缓冲区.
5.7.1. 配置
默认情况下, 当发生异常时, CIDER 会使用 cider-stacktrace-mode 在一个错误缓冲区中显示异常. 你可以抑制这种行为, 这将导致只将错误消息作为临时 overlay 或在回显区输出:
(setq cider-show-error-buffer nil)
只有当 cider-use-overlays 不为 nil 时, 你才会看到 overlay.
从 CIDER 1.8.0 开始, 只有运行时异常 (而不是编译错误) 会导致显示堆栈跟踪缓冲区. 这更好地遵循了 Clojure 1.10 的预期语义. 此行为由 cider-clojure-compilation-error-phases 配置选项控制. 如果你希望忽略错误阶段, 并且只考虑 cider-show-error-buffer, 请自定义:
(setq cider-clojure-compilation-error-phases nil)
有时, 显示的错误可能源于 CIDER 本身的 bug. 这些内部错误可能会频繁发生并中断你的工作流程, 但你可能不想通过使用 cider-show-error-buffer 来抑制所有堆栈跟踪缓冲区. 相反, 你可能只想抑制这种特定类型的内部错误. 堆栈跟踪缓冲区在显示内部错误时提供了这样的选项. 一个带有错误类型名称的切换按钮将会显示, 你可以切换这种特定类型的错误是否会导致堆栈跟踪缓冲区自动显示. 切换按钮只在当前 Emacs 会话期间控制此行为, 但如果你想使抑制更持久, 你可以通过自定义 cider-stacktrace-suppressed-errors 变量来实现. 缓冲区还将提供一个直接链接到 bug 报告页面, 以帮助其诊断和修复.
与 cider-show-error-buffer 或 cider-stacktrace-suppressed-errors 的值无关, CIDER 总是会在后台生成错误缓冲区. 如果你决定需要, 你可以使用 cider-selector (C-c M-s) 来访问这个缓冲区.
对于错误缓冲区, 还有两种更具选择性的策略:
(setq cider-show-error-buffer 'except-in-repl) ; or (setq cider-show-error-buffer 'only-in-repl)
要禁用在显示错误缓冲区时自动选择它:
(setq cider-auto-select-error-buffer nil)
默认情况下, 当你跳转到给定堆栈帧的源时, 将选择除 *cider-error* 之外的 Emacs 窗口. 如果你希望重用 *cider-error* 的窗口, 请配置:
(setq cider-stacktrace-navigate-to-other-window nil)
如果你自定义了此设置, 当你导航到给定的源文件时, 你可以使用 C-x <left> (previous-buffer) 或 M-, (xref-pop-marker-stack) 导航回 *cider-error*.
5.7.2. 导航堆栈跟踪
CIDER 附带了一个强大的解决方案来处理 Clojure 的堆栈跟踪. CIDER 在一个特殊的 major mode, cider-stacktrace-mode 中呈现堆栈跟踪, 它为你提供了一些关键功能:
- 能够过滤掉某些堆栈帧以减少混乱
- 一些方便的方法来导航到异常的原因
- 能够通过一次按键直接跳转到代码
- 按键绑定
命令 键盘快捷键 描述 cider-stacktrace-previous-causeM-p将点移动到上一个原因 cider-stacktrace-next-causeM-n将点移动到下一个原因 cider-stacktrace-jumpM-.orReturn导航到堆栈跟踪帧的源位置 (如果可用) cider-stacktrace-cycle-current-causeTab循环当前原因的详细信息 cider-stacktrace-cycle-all-causes0orS-Tab循环所有原因的详细信息 cider-stacktrace-cycle-cause-11循环原因 #1 的详细信息 cider-stacktrace-cycle-cause-22循环原因 #2 的详细信息 cider-stacktrace-cycle-cause-33循环原因 #3 的详细信息 cider-stacktrace-cycle-cause-44循环原因 #4 的详细信息 cider-stacktrace-cycle-cause-55循环原因 #5 的详细信息 cider-stacktrace-toggle-javaj切换 Java 帧的显示 cider-stacktrace-toggle-cljc切换 Clojure 帧的显示 cider-stacktrace-toggle-replr切换 REPL 帧的显示 cider-stacktrace-toggle-toolingt切换工具帧的显示 (例如, 编译器, nREPL middleware) cider-stacktrace-toggle-duplicatesd切换重复帧的显示 cider-stacktrace-show-only-projectp切换仅显示项目帧 cider-stacktrace-toggle-alla切换所有帧的显示
5.7.3. 过滤堆栈帧
CIDER 通过允许你使用 cider-stacktrace-default-filters 变量应用一个过滤器列表来帮助你减少 Clojure 堆栈跟踪的混乱. 有效的过滤器类型包括 java, clj, repl, tooling 和 dup. 指定这些过滤器之一将从堆栈跟踪显示中移除相应的帧. 还有一些“正向”过滤类型 (反向过滤器), 它们指定应该显示什么. 例如, project 的值将导致只显示项目帧, 而 all 将强制显示所有堆栈帧. 请注意, project 和 all 是互斥的. 如果它们都存在, 第一个将决定行为.
(setq cider-stacktrace-default-filters '(tooling dup)) ;; or (setq cider-stacktrace-default-filters '(project))
5.7.4. 包装错误消息
最后, CIDER 可以在缓冲区中显示错误消息时将其包装起来, 以帮助提高其可读性. CIDER 为此使用了 cider-stacktrace-fill-column, 它可以取三种类型的值:
nil: 错误不被包装.numeric: 错误消息被包装到指定的填充列.- 其他真值但非数字: 错误消息使用
fill-column的值进行包装.
例如, 以下将导致错误消息被包装到 80 列:
(setq cider-stacktrace-fill-column 80)
5.8. 文档
快速访问文档对于高效开发非常重要. 这就是为什么 CIDER 非常重视这一点, 并提供了许多与文档相关的功能.
5.8.1. 查找文档
最基本的事情是转到某个符号并按 C-c C-d C-d. 这将打开一个文档缓冲区, 其中包含有关该符号引用的所有相关信息 (特殊形式, var, Java 方法等).
可以使用快捷键 C-c C-d d. 大多数 CIDER 键映射都提供了相同快捷键的两个版本 (带或不带最后的 Control), 因为有些人喜欢一直按住 Control, 而有些人则不喜欢.
通常, 该命令作用于点处的符号. 如果带前缀参数调用, 或者在点处没有找到符号, 它会提示输入一个符号.
5.8.2. 本地 JavaDoc
大多数 JDK 发行版都附带一个 src.zip 文件 (一个包含所有基础 Java 源文件的归档). 如果你的 JDK 中有这样的归档文件, 当你查询 Java 类 (例如 java.lang.Thread) 或方法 (例如 java.lang.Thread/currentThread)
的文档时, CIDER 会自动解析源文件, 并在文档缓冲区中显示格式正确的 JavaDoc.
你还会看到更好的 Eldoc 文档 (minibuffer 提示) 用于 Java 方法. 如果源文件存在, 你可以通过在类名或方法名上按 M-. 跳转到类或方法的定义.
此外, 如果你下载了某个第三方 Java 库的特殊 -sources.jar 文件, CIDER 能够解析 JavaDoc 源文件并跳转到定义. 请参阅下一节关于如何下载源 JAR 的说明.
5.8.3. 获取源 JAR
从 1.17 版本开始, CIDER 能够在您请求 Java 类/方法的文档或跳转到 Java 类/方法的定义时自动下载必要的源 JAR 文件. 为了下载源文件, 变量 cider-download-java-sources 必须启用 (默认是启用的).
当下载触发时, CIDER 会在 minibuffer 中显示一条消息. 获取单个源 JAR 通常需要几秒钟. CIDER 对每个进程的特定依赖项只会尝试下载一次源 JAR——如果下载失败 (通常是因为该依赖项没有发布源 JAR 到 Maven) , CIDER 在下次重启之前不会重试.
虽然 Eldoc 功能受益于 Java 源码, 但 eldoc 本身不会触发 Java 源 JAR 的下载. 你必须手动查找一次文档或跳转到定义, 以便下载 JAR. 之后, Eldoc 将获取 Java 源码并显示更好的提示.
或者, 你可以使用 enrich-classpath 一次性下载当前项目使用的所有源 JAR. 这会增加启动时间, 但不会在运行时触发单个 JAR 的获取.
5.8.4. 在线 JavaDoc
CIDER 通过命令 cider-javadoc (C-c C-d j 或 C-c C-d C-j) 提供了对在线 Javadoc 文档的快速访问, 使用你的默认浏览器.
通常, 该命令作用于点处的符号. 如果带前缀参数调用, 或者在点处没有找到符号, 它会提示输入一个符号.
如果你不希望 CIDER 使用外部浏览器显示 JavaDoc, 你可以像这样使用内置的 EWW 浏览器:
(setq browse-url-browser-function 'eww-browse-url)
5.8.5. 在文档字符串中搜索
CIDER 提供了 clojure.repl/find-doc 的一个方便的替代品 - cider-apropos-documentation (C-c C-d f 或 C-c C-d C-f). 这允许你在所有已加载的 vars 的文档字符串中搜索, 结果会呈现在 Emacs 的 apropos 界面中.
或者, 你可以使用 cider-apropos-documentation-select (C-c C-d e 或 C-c C-d C-e), 它会将匹配的结果作为列表呈现在 minibuffer 中, 这样你就可以快速选择你需要的内容
(特别是如果你正在使用像 ido 或 vertico 这样的包).
5.8.6. ClojureDocs
CIDER 提供了与流行的 ClojureDocs 服务的集成. 你有两种主要的方式与 ClojureDocs 交互:
- 通过
cider-clojuredocs(C-c C-d C-c) 在 Emacs 的一个专用缓冲区中显示文档 - 通过
cider-clojuredocs-web(C-c C-d C-w) 在你的默认浏览器中打开文档
该文档作为 EDN 资源与 cider-nrepl (更准确地说是与 cider-nrepl 的依赖项 orchard) 捆绑在一起, 并且是 ClojureDocs 数据的快照. 你可以使用 M-x cider-clojuredocs-refresh-cache 手动将其更新到最新版本.
请记住, ClojureDocs 中的文档仅限于 Clojure 和一些 Clojure Contrib 库 (例如 core.async). 目前不支持 ClojureScript.
如果你对 CIDER 如何与 ClojureDocs 交互的内部机制感到好奇, 请查看这篇文章.
5.8.7. 生成文档交叉引用
有时在你的文档字符串中, 你可能希望能够向其他程序员指出不同的定义. 如果你将一个定义的名称指定为完全限定的符号, 或者将其用反引号 (...) 或 Codox 风格的分隔符 ([[...]]) 括起来
, 当 CIDER 在文档缓冲区中显示文档字符串时, 会将这些引用转换为活动链接.
如果名称在另一个命名空间中, 那么你必须在文档字符串中包含完全限定的名称.
带有包含引用的文档字符串的示例函数:
(defn test-fn "Test function. Also see: clojure.core/map, clojure.core/reduce, `defn`. You can reference variables like `thor`, [[kubaru.data.zookeeper/yoda]]. Also works with references to java interop forms, `java.lang.String/.length`." [] (+ 1 1))
如果你想支持其他引用格式, 你可以更改 CIDER 用于查找引用的分隔符. 只需更新 cider-doc-xref-regexp 中的正则表达式以匹配你喜欢的格式. 正则表达式的第一个组应始终匹配交叉引用名称.
例如, 如果你想使用 Latex 风格的引用 (\ref{...}), 正则表达式将是:
(setq cider-doc-xref-regexp "\\\\ref{\\(?1:[^}]+\\)}")
CIDER 另见
5.8.8. 将文档显示为工具提示
默认情况下, CIDER 会将鼠标下的符号的文档显示在一个工具提示中 (使用一个 overlay) 或你的回显区. 这由配置设置 cider-use-tooltips 控制. 你可以通过将该变量设置为 nil 来禁用此行为.
(setq cider-use-tooltips nil)
如果 tooltip-mode 被禁用或 help-at-pt-display-when-idle 设置为 t, 工具提示也将被禁用.
请查看 help-at-pt-display-when-idle 的文档, 以更好地理解 help-echo Emacs 功能的工作原理.
5.8.9. 按键绑定
| 命令 | 键盘快捷键 | 描述 |
|---|---|---|
cider-doc |
C-c C-d d C-c C-d C-d |
显示点处符号的文档字符串. 如果带前缀参数调用, 或在点处没有找到符号, 则提示输入一个符号. |
cider-javadoc |
C-c C-d j C-c C-d C-j |
显示点处符号的 JavaDoc (在你的默认浏览器中). 如果带前缀参数调用, 或在点处没有找到符号, 则提示输入一个符号. |
cider-clojuredocs |
C-c C-d c C-c C-d C-c |
在 ClojureDocs 中查找符号. |
cider-clojuredocs-web |
C-c C-d w C-c C-d C-w |
在 web 浏览器中打开符号的 ClojureDocs 文档. |
cider-apropos |
C-c C-d a C-c C-d C-a |
Apropos 搜索函数/vars. |
cider-apropos-documentation |
C-c C-d f C-c C-d C-f |
Apropos 搜索文档. |
cider-apropos-documentation-select |
C-c C-d e C-c C-d C-e |
Apropos 搜索文档并选择. |
5.9. 处理 repl 连接
因为 nREPL 连接与 CIDER 中的 REPL 缓冲区一一对应, 所以在本节中, 我们交替使用“REPL”和“连接”.
当你只在一个项目上工作, 并且只有一个 Clojure 连接时, 生活是轻松的. 如果你幸运地是这些人之一, 那么你可以安全地跳过阅读本节. 它的目标受众是那些必须同时在多个项目上工作, 或者为同一个项目打开多个连接的人 (例如, 一个 Clojure 和一个 ClojureScript 连接).
事实证明, 将源缓冲区中的操作 (例如, 代码求值) 可靠地映射到正确的连接缓冲区真的很难, 因为你必须考虑很多事情:
- 连接是否映射到某个项目?
- 你是否有多个连接映射到同一个项目? (例如, 一个 Clojure 和一个 ClojureScript REPL)
- 你是否有多个相同类型的连接映射到同一个项目? (例如, 几个 Clojure REPL)
- 如果你尝试求值项目外的代码, 会发生什么?
.cljc文件中的操作应该分派到哪里? Clojure REPL? ClojureScript REPL? 还是两个 REPL?
CIDER 最初没有任何多连接支持, 多年来我们对如何支持多连接有几种不同的想法:
- 最初有一个静态分派机制. 你只需从连接列表中手动选择活动连接, 所有操作都会路由到它. 这种方法简单有效, 但要求你不断跟踪是否正在使用正确的连接. ClojureScript 的出现进一步加剧了这个问题.
- 最终, 我们引入了动态分派的概念, 我们会结合文件名扩展名和项目目录来确定将操作发送到哪里. 这种方法工作得相当好, 但当你有多个相同类型的连接用于同一个项目时, 就会有问题. 也没有相关连接的逻辑分组.
- 这就引出了现在——今天管理多连接的方法是动态分派的演进. 相关连接被分组到会话中, 这些会话与某个上下文 (通常是项目目录) 绑定. 如果某个操作分派不明确, 它将以错误消息失败. 在这种情况下, 你可以手动链接 (附加) 缓冲区, 文件和目录到会话.
在接下来的部分中, 我们将介绍连接管理的基本术语, 并涵盖你通常需要处理的典型场景. 这个主题可能看起来有点吓人, 但一旦你了解了术语并掌握了基本思想, 它实际上相当直接.
5.9.1. 会话
在本节的上下文中, "会话" 指的是一组 nREPL 连接. 这不应与 nREPL 自己的会话概念相混淆 (每个 nREPL 连接可以分为多个会话).
CIDER 通过 Sesman 会话维护一个分组的已打开 nREPL 连接视图. 每个会话是共享同一个 nREPL 服务器的连接集合. 然而, 你可以有多个会话共享同一个 nREPL 服务器 (虽然你不太可能需要这样做).
- 启动新会话
你可以使用以下命令启动新会话:
C-c C-x j j(cider-jack-in-clj)C-c C-x j s(cider-jack-in-cljs)C-c C-x j m(cider-jack-in-clj&cljs)C-c C-x c j(cider-connect-clj)C-c C-x c s(cider-connect-cljs)C-c C-x c m(cider-connect-clj&cljs)
如果一个命令创建了多个 REPL (例如,
cider-jack-in-clj&cljs), 它们将被添加到同一个会话中.如果你尝试为某个项目创建一个新会话, 而该项目已经有一个现有会话 (例如, 在为同一个项目运行
cider-jack-in-clj之后又运行cider-jack-in-cljs), 你通常会收到一个警告.通常在这种情况下, 首选使用可以向现有会话添加更多 REPL 的命令. 我们将在下一节中介绍这些命令.
- 向会话添加 REPL
你可以使用以下命令向当前会话添加新的 REPL:
C-c C-x s j(cider-connect-sibling-clj)C-c C-x s s(cider-connect-sibling-cljs)
一个非常常见的用例是为某个项目运行
cider-jack-in-clj, 然后接着运行cider-connect-sibling-cljs.除非同一个会话中同时存在 Clojure 和 ClojureScript REPL, 否则智能分派命令 (例如, 在正确的 Clojure/ClojureScript REPL 中求值代码, 在 Clojure 和 ClojureScript REPL 之间切换) 将无法工作.
新手遇到的一个非常常见的问题是, 在不同的会话中创建 Clojure REPL 和 ClojureScript REPL, 然后想知道为什么它们不能正确地相互作用.
在使用单独的配置文件来管理 clj 和 cljs 依赖项的情况下 (例如, clj 依赖项在
deps.edn中, cljs 依赖项在shadow-cljs.edn中), 目前无法将这两个 repl 分组到同一个会话中.然而, 这可以通过
cider-merge-sessions来解决. 将其设置为'host将合并与项目内同一主机相关联的所有会话. 将其设置为'project将合并同一项目中的所有会话. - 会话生命周期管理
会话生命周期管理命令位于 Sesman 键映射 (
C-c C-s) 上C-c C-s s(sesman-start)C-c C-s r(sesman-restart)C-c C-s q(sesman-quit)
sesman-start命令包装了所有前面提到的 jack-in 和 connect 命令. 你也可以使用M-x cider或C-c M-x调用相同的功能.要退出或重启单个连接, 请使用 CIDER 命令
C-c C-q(cider-quit)C-c M-r(cider-restart)
5.9.2. 当前会话
所有 CIDER 命令 (求值, 补全, 切换到 REPL 等) 都在当前会话中的相关 REPL 上操作. 当前会话是所有链接会话 (或者在没有链接时是友好会话) 中最相关的会话.
会话的相关性由链接上下文的特异性和 REPL 缓冲区的最近性决定.
如果当前上下文链接到单个会话, 那么该会话就是当前会话. 如果多个会话链接到同一个上下文 (比如说, 一个项目), 那么当前会话是包含最近查看的 REPL 的那个会话.
链接到更具体上下文的优先级更高. 例如, 如果你有两个会话链接到同一个项目, 另一个会话链接到该项目内的一个目录, 那么链接到该目录的会话是当前会话. 因此, 同样, 没有歧义.
默认情况下, Sesman 允许同时链接到项目和目录的多个链接, 但每个缓冲区只有一个链接. 如果你想改变这一点, 请参见 sesman-single-link-contexts.
5.9.3. 当前 REPL
当前 REPL 是来自当前会话的最相关的 REPL. REPL 的相关性由当前缓冲区的类型决定. 例如, 如果当前缓冲区是 clj 缓冲区, 则选择 clj REPL.
当会话中有多个 clj REPL, 或者当前缓冲区是 cljc 缓冲区并且会话中同时存在 clj 和 cljs REPL 时, 可能会出现歧义情况. 在这种情况下, 当前 REPL 是相关类型中最近查看的 REPL.
使用 C-c C-z 切换到当前 REPL 缓冲区. 然后你可以使用相同的组合键切换回你来自的 Clojure(Script) 缓冲区.
单个前缀 C-u C-c C-z 将切换到当前 REPL 缓冲区, 并根据当前 Clojure(Script) 缓冲区中的命名空间设置该缓冲区中的命名空间.
5.9.4. 上下文链接
会话可以链接到上下文 (项目, 目录和缓冲区)
C-c C-s b(sesman-link-with-buffer)C-c C-s d(sesman-link-with-directory)C-c C-s p(sesman-link-with-project)C-c C-s u(sesman-unlink)通常, 你会想在文件缓冲区中调用这些命令, 偶尔在一些特殊缓冲区中 (例如, scratch 缓冲区). 你绝对不应该在 REPL 缓冲区中运行它们, 因为 REPL 是会话的一个组成部分.
5.9.5. 友好会话
Sesman 定义了“友好”会话, 以便在没有显式链接的上下文中对会话进行即时操作. 在 CIDER 中, 友好会话由项目依赖项定义. 例如, 当你使用 cider-find-var (M-.) 导航到依赖项目中的 var 定义时
, 当前项目的会话就成为该依赖项的友好会话.
当你从依赖项目中求值一些代码并且该项目中没有显式链接时, 将使用最近的友好会话来求值代码. 显式链接的会话优先于友好会话.
你可以通过自定义 sesman-use-friendly-sessions 来禁用友好会话推断.
5.9.6. 显示会话信息
使用 C-c C-s i (sesman-info) 获取当前上下文中所有链接和友好会话的信息. 使用 C-u, 显示所有 CIDER 会话的信息. 对于连接特定的信息, 请使用 CIDER 的内置 cider-describe-connection (C-c M-d).
通过 sesman-browser (C-c C-s w) 可以获得所有 CIDER 会话的交互式视图.
5.9.7. 自定义会话和 REPL 名称
默认情况下, 会话名称由缩写的项目名称, 主机和端口组成 (例如, project/dir:localhost:1234). REPL 缓冲区名称由会话名称和 REPL 类型规范后缀组成 (例如, *project/dir:localhost:1234(cljs:node)*).
你可以使用 cider-session-name-template 自定义会话名称, 使用 nrepl-repl-buffer-name-template 自定义 REPL 名称. 另请参见 cider-format-connection-params 以获取可用格式.
5.9.8. 重用死掉的 REPL
在正常情况下, CIDER 在结束会话时会自动杀死 REPL 缓冲区并进行清理. 然而, 当会话意外终止时, 例如当它崩溃或与外部服务器进程断开连接时, REPL 缓冲区会留下一个没有活动连接的状态, 并输出一个日志:
*** Closed on < date/time > ***
在启动新连接时, CIDER 可以检测到这些“死掉的 REPL”, 并提议为新连接重用该缓冲区. 默认情况下, 每当有死掉的 REPL 缓冲区可供重用时, 它都会提示确认, 但你可以通过变量 cider-reuse-dead-repls 来自定义此行为.
将其设置为 auto 会自动重用一个死掉的 REPL 缓冲区, 仅当有多个选项可供选择时才显示提示. 要完全抑制提示, 将其设置为 nil 以总是启动一个新的 REPL 缓冲区, 或设置为 any 以重用最相关的死掉的 REPL.
5.10. 周边功能
正如电视购物广告总是说的, “但是等等, 还有更多!” 如果同时拥有 Clojure 和 ClojureScript REPL, 交互式编程, 代码补全, 堆栈跟踪导航, 测试运行和调试还不够满足你, CIDER 还提供了几个额外的功能.
5.10.1. 使用草稿板
CIDER 提供了一种通过 M-x cider-scratch 命令创建 Clojure 草稿板的简单方法. 这是一种很好的方式来尝试一些代码, 而不必创建源文件或污染 REPL 缓冲区, 与 Emacs 自己的 *scratch* 缓冲区非常相似.
5.10.2. 查找引用
该功能基于这篇文章中的思想, 并在 CIDER 0.22 中引入.
在 CIDER 中使用查找引用有两种方式:
cider-xref-fn-refs(C-c C-? r) 在一个专用的缓冲区中显示点处函数的用法cider-xref-fn-refs-select(C-c C-? C-r) 在 minibuffer 中显示用法
下面是它们在实际操作中的样子:
CIDER 查找引用
请记住以下限制:
- 这只适用于 Clojure
- 它由运行时状态分析驱动, 这意味着它只会显示已加载命名空间的数据 (像 CIDER 的大部分功能一样)
- 它 (目前) 找不到在 lambdas 中的用法
- 它没有给我们某物被使用的精确位置, 我们只知道它被使用了
好的一面是:
- 它超级快
- 它不需要任何静态代码分析
- 它仍然比 grep 更可靠
这个功能不完美, 但至少在你需要时它在那里. 另外, 你还可以使用 cider-xref-fn-deps (C-c C-? d) 和 cider-xref-fn-deps-select (C-c C-? C-d) 快速导航到某个函数使用的所有函数. 如果你不想跳转到某个函数的源代码去看它内部引用 (使用) 了哪些函数, 这些命令非常方便.
别忘了你还有几个第三方替代方案:
- 由
clj-refactor.el提供的更复杂的 AST 驱动的“查找用法” - Projectile 的“在项目中 grep” (
projectile-grep, 通常绑定到C-c p g)
5.10.3. CIDER 选择器
cider-selector (C-c M-s) 命令允许你快速导航到 Clojure 项目上下文中的重要缓冲区——例如 REPL, 堆栈跟踪缓冲区, 文档缓冲区, 最近访问的 Clojure 文件等. 该命令的使用非常简单——调用它后, 你需要输入一个标识目标缓冲区的单个键 (例如, r 代表 REPL).
关于默认键绑定 C-c M-s 需要记住的一件事是, 它只在启用了 cider-mode 的缓冲区中可用 (例如, Clojure 源缓冲区) 和 CIDER REPL 中. 如果你想让它在任何地方都可用, 在你的 Emacs 配置中添加一个全局绑定可能是个好主意:
(global-set-key (kbd "C-c s") #'cider-selector)
这是 cider-selector 所有键绑定的列表:
| 键盘快捷键 | 描述 |
|---|---|
c |
最近访问的 Clojure 缓冲区. |
e |
最近访问的 Emacs Lisp 缓冲区. |
r |
当前 REPL 缓冲区或最近访问的 REPL 缓冲区. |
m |
*nrepl-messages* 缓冲区. |
x |
*cider-error* 缓冲区. |
d |
*cider-doc* 缓冲区. |
s |
*cider-scratch* 缓冲区. |
q |
中止. |
? |
显示帮助. |
这些键中的任何一个都可以用 4 作前缀, 使目标缓冲区在不同的窗口中打开 (而不是当前窗口).
你可以使用 def-cider-selector-method 轻松地用新命令扩展选择器:
(def-cider-selector-method ?z "CIDER foo buffer." cider-foo-buffer)
5.10.4. 浏览 Classpath
你可以使用 M-x cider-classpath 命令轻松浏览 classpath 上的项目.
这里你可以看到它的实际效果:
Classpath 浏览器
在 classpath 条目上按 RET 可以进入它.
5.10.5. 浏览命名空间
你可以使用 M-x cider-browse-ns 命令浏览任何已加载命名空间的内容. CIDER 会提示你输入要浏览的命名空间.
命名空间浏览器
你也可以使用 M-x cider-browse-ns-all 浏览所有可用的命名空间.
UI 的标题栏中包含按钮, 允许你控制缓冲区的显示方式 (请参阅下面的键绑定). 你也可以配置 cider-browse-ns-default-filters 变量为一个列表, 列出你希望默认隐藏的元素类型.
在浏览器缓冲区中定义了一系列有用的键绑定.
| 键盘快捷键 | 描述 |
|---|---|
d |
显示点处项目的文档. |
RET |
浏览 ns 或显示点处项目的文档. |
s |
转到点处项目的定义. |
^ |
浏览所有命名空间. |
n |
转到下一行. |
h p |
切换私有项目的可见性. |
h t |
切换测试的可见性. |
h m |
切换宏的可见性. |
h f |
切换函数的可见性. |
h v |
切换 vars 的可见性. |
g t |
按类型 (函数, 宏, var 等) 分组项目. |
g v |
按可见性 (public vs. private) 分组项目. |
p |
转到上一行. |
5.10.6. 浏览 Clojure Spec 注册表
如果你已经知道要查找哪个 spec, 你可以输入 M-x cider-browse-spec, CIDER 会提示你输入一个 spec 名称, 然后让你进入 spec 浏览器.
Spec 浏览器
如果你不太确定你想要哪个 spec, 你可以输入 M-x cider-browse-spec-all. CIDER 然后会提示你输入一个正则表达式, 并过滤掉所有不匹配的 spec 名称.
Spec 浏览器
一旦进入浏览器, 你可以使用鼠标或下面的键绑定进行更深入的导航.
| 键盘快捷键 | 描述 |
|---|---|
RET |
浏览点处的 spec. |
^ |
在导航堆栈中向上移动. |
n |
转到下一个 spec. |
p |
转到上一个 spec. |
e |
为当前浏览器 spec 生成一个示例. |
如果你的项目包含 org.clojure/test.check 库, 你可以在浏览 spec 时输入 e 来生成一个符合该 spec 的示例.
Spec 浏览器示例
5.10.7. Clojure Spec 版本
Clojure Spec 有点历史, 有几种风格:
spec(又名clojure.spec, 原始版本, 从未随 Clojure 发布)spec-alpha(又名clojure.spec.alpha, 原始版本, 但名称不同, 随 Clojure 发布)spec-alpha-2(又名clojure.alpha.spec, 演进版, 独立库, 但仍处于实验阶段)
Cider 支持所有这些混合, 但有一个转折.
- 当 Cider 显示一个 specs 列表时, 会显示所有注册表的键. 注册表从最新到最旧合并.
- 当 Cider 对一个 spec 进行操作时, 比如查找一个 spec 或为其生成数据, 这个操作会按从最新到最旧的顺序在所有注册表上尝试, 第一个成功的操作获胜.
5.10.8. 使用 cljfmt 格式化代码
虽然 CIDER 有自己的代码格式化 (缩进) 引擎, 你也可以将它与 cljfmt 一起使用——这在你与使用不同编辑器和 IDE 的团队合作时很有用.
CIDER 提供了几个与 cljfmt 交互的命令:
cider-format-defuncider-format-regioncider-format-buffer
通常, 添加像这样的钩子是个好主意, 以确保每次保存操作时你的缓冲区都得到正确格式化:
(add-hook 'before-save-hook 'cider-format-buffer t t)
注意, 你需要在保存相关缓冲区之前应用 cljfmt.
你可以通过配置变量 cider-format-code-options 向 cljfmt 提供额外的配置. 这里有一个例子:
;; 假设你想传递以下配置 ;; ;; {:indents {org.me/foo [[:inner 0]]} ;; :alias-map {\"me\" \"org.me\"}} ;; ;; 你需要将其编码为一个 Emacs Lisp plist: (setq cider-format-code-options '(("indents" (("org.me/foo" (("inner" 0))))) ("alias-map" (("me" "org.me")))))
CIDER 不会调用 cljfmt 的 shell 命令——它通过 nREPL 与其交互 (在 cider-nrepl 中有格式化 middleware), 这比调用 shell 命令更快.
5.10.9. 格式化 EDN
与 cljfmt 集成类似, CIDER 还提供了一个方便的接口, 使用 clojure.tools.reader.edn 来格式化 EDN. 提供了以下命令:
cider-format-edn-defuncider-format-edn-regioncider-format-edn-buffer
5.10.10. Xref 集成
从 1.2.0 版本开始, CIDER 支持 Emacs 的内置 xref 功能, 这意味着 M-. 将调用 xref-find-definitions 而不是 CIDER 自己的命令 cider-find-var. 你可以像这样禁用 CIDER 的 xref 后端的使用:
(setq cider-use-xref nil)
你必须禁用并重新启用 cider-mode 才能使此设置生效.
如果你使用其他也与 xref 集成的包 (例如, lsp-mode), 你可能希望自定义 CIDER 的 xref 后端的优先级. 优先级由后端函数在 xref-backend-functions 钩子中出现的顺序控制. 默认情况下, CIDER xref 函数将以 -90 的深度添加, 因此它将 (应该?) 排在第一位. 如果你希望它的优先级较低, 你可以更改 cider-xref-fn-depth:
(setq cider-xref-fn-depth 90)
有关深度的更多信息, 请参见设置钩子.
5.10.11. Cheatsheet
在 CIDER 中有两种方式可以访问 Clojure cheatsheet.
第一种方式通过 cider-cheatsheet 命令可用, 它在一个弹出缓冲区中显示 cheatsheet. 下图是两个窗口并排显示 cheatsheet 缓冲区的样子:
在缓冲区中显示 cheatsheet
第二种方式通过 cider-cheatsheet-select 命令可用, 它在 minibuffer 中使用补全来在 cheatsheet 中查找一个 var. 默认情况下, 它提供一个多步选择过程, 你需要逐个部分地进行, 直到找到一个 var. 它在 minibuffer 中的样子如下:
在 cheatsheet 中选择部分
在 cheatsheet 中选择 var
在调用 cider-cheatsheet-select 时使用前缀参数, 我们可以改变 cider-cheatsheet-select 的行为, 使每个候选都表示为到 var 的完整路径. 这对于模糊补全样式和垂直候选显示很有用, 因为在这种情况下, 我们可以在路径的任何元素中搜索, 可能会同时得到多个类别的匹配. 这样的工作流程看起来如下:
在 cheatsheet 中选择路径
可以通过自定义 cider-cheatsheet-default-action-function 来控制在选择一个 var 时使用哪个函数. 默认情况下, var 的文档使用 cider-doc-lookup 显示, 但也可以设置为 cider-clojuredocs-lookup 来显示来自 ClojureDocs 的文档, 或任何其他接受 var 作为参数的函数.
6. 使用 REPL
6.1. Basic Usage
CIDER 配备了一个强大的 REPL, 它补充了 cider-mode 中的交互式开发功能. 使用 CIDER REPL, 你可以试验你正在运行的程序, 测试函数, 或者只是探索一个你感兴趣的新库. CIDER REPL 提供了许多高级功能:
- 代码补全
- font-locking (与
clojure-mode中相同) - 快速访问许多 CIDER 命令 (例如, 定义和文档查找, 跟踪等)
- 求值结果的漂亮打印
- 图像的内联显示
- 持久的 REPL 历史记录
- 强大的 REPL 历史浏览器
- eldoc 支持
- 高度可定制的 REPL 提示符
6.1.1. 与 REPL 交互
与 CIDER 的 REPL 交互非常简单——大多数时候你只需在那里编写表达式并按 RET 来求值它们.
但 REPL 的功能远不止于此, 它允许你做一些在其他 Clojure REPL 中可能无法做到的事情. 这类事情的一些例子是:
- 你可以用
C-RET关闭一个不完整的表达式 - 你可以通过在每行末尾按
C-j来输入一个多行表达式 - 你可以快速跳转到一个符号的定义 (.) 或其文档 (
C-c C-d d) - 你可以用
C-c C-o清除最后一个表达式的输出 - 你可以用
C-u C-c C-o清除 REPL 缓冲区 - 你可以用
C-c C-z在你的源缓冲区和 REPL 之间跳转 - 你可以用
C-c M-o在你的 Clojure 和 ClojureScript REPL 之间跳转
除此之外, REPL 是极其可配置的, 你几乎可以调整它的每一个方面.
6.1.2. 中断求值
如果你不小心尝试求值一些需要很长时间 (如果它能完成的话) 的东西, 你可以通过按 C-c C-c 来中断这个失控的求值操作.
请注意, 这与在源缓冲区中中断求值的键绑定 C-c C-b 不同.
6.1.3. 退出 REPL
当你用完一个 REPL 后, 你可以用 C-c C-q 来处理它.
避免用 C-c C-k 杀死 REPL 缓冲区. 这会跳过一些正确处理 REPL 缓冲区所需的操作.
6.1.4. 加载 REPL 实用函数
通常 Clojure REPL 会在你启动的第一个 REPL 中加载以下代码:
(clojure.core/apply clojure.core/require clojure.main/repl-requires)
CIDER 也这样做, 但更进一步, 实际上允许你通过变量 cider-repl-init-code 来控制在 REPL 初始化时求值什么代码.
此初始化代码仅在第一个 REPL 命名空间 (通常是 user) 中自动加载, 但你可以使用 REPL 快捷方式 require-repl-utils 在任何命名空间中轻松调用它. 该功能也可以通过 CIDER 的菜单 ("REPL → Require REPL utils") 访问.
在使用 CIDER 时, 你很少需要 REPL 实用函数, 因为它提供了许多行为类似的 Emacs 命令 (例如, 与其做像 (doc str) 这样的事情, 你可能更愿意使用 cider-doc (C-c C-d C-d) 命令).
6.1.5. 已知限制
当 REPL 缓冲区变得非常大时, 性能可能会下降. 如果启用了 cider-repl-use-clojure-font-lock 或 nrepl-log-messages, 情况尤其如此.
非常长的行肯定会使 Emacs 变得迟缓, 因此建议使用一个 cider-print-fn 的值, 该值会将超过一定宽度的行进行换行 (即除了 pr 之外的任何内置选项).
你可以使用 cider-repl-clear-output 来清除上一次求值的结果, 或者带前缀参数清除整个 REPL 缓冲区. 你还可以设置 cider-repl-buffer-size-limit, 这将在每次求值后自动修剪缓冲区. 这种修剪也可以通过 cider-repl-trim-buffer 手动调用.
6.2. Keybindings
6.2.1. REPL 键绑定
这是 CIDER 的 REPL 中可用的键绑定列表:
| 键盘快捷键 | 描述 |
|---|---|
RET |
如果当前输入是完整的, 则在 Clojure 中求值它. 如果不完整, 则打开一个新行并缩进. 如果当前输入是空字符串 (只包含包括换行符在内的空白字符), 则清除输入而不求值, 并打印一个新的提示符. 如果带前缀参数调用, 则不检查完整性就求值输入. |
C-RET |
关闭任何不匹配的括号, 然后在 Clojure 中求值当前输入. |
C-j |
打开一个新行并缩进. |
C-c C-o |
从 REPL 缓冲区中移除上一次求值的输出. 带前缀参数时, 它将清除整个 REPL 缓冲区, 只留下一个提示符. |
C-c M-o |
在当前项目的 Clojure 和 ClojureScript REPL 之间切换. |
C-c C-u |
删除从提示符到当前点的所有文本. |
C-c C-b |
中断任何待处理的求值. |
C-c C-c |
|
C-up |
转到历史记录中的上一个/下一个输入. |
C-down |
|
M-p |
使用当前输入作为搜索模式, 搜索历史记录中的上一个/下一个项目. 如果连续两次输入 M-p~/~M-n, 第二次调用将使用相同的搜索模式 (即使当前输入已更改). |
M-n |
|
M-s |
使用正则表达式在命令历史中向前/向后搜索. |
M-r |
|
C-c C-n |
在 REPL 缓冲区中的当前和上一个提示符之间移动. 在有旧输入的行上按 RET 会将该行复制到最新的提示符. |
C-c C-p |
|
TAB |
补全点处的符号. |
C-c C-d d |
显示点处符号的文档字符串. 如果带前缀参数调用, 或在点处没有找到符号, 则提示输入一个符号. |
C-c C-d C-d |
|
C-c C-d j |
显示点处符号的 JavaDoc (在你的默认浏览器中). 如果带前缀参数调用, 或在点处没有找到符号, 则提示输入一个符号. |
C-c C-d C-j |
|
C-c C-d c |
在 ClojureDocs 中查找符号. |
C-c C-d C-c |
|
C-c C-d a |
Apropos 搜索函数/vars. |
C-c C-d C-a |
|
C-c C-d f |
Apropos 搜索文档. |
C-c C-d C-f |
|
C-c C-z |
切换到上一个 Clojure 缓冲区. 这补充了在 cider-mode 中使用的 C-c C-z. |
C-c M-i |
检查表达式. 如果点处有表达式, 则对其操作. |
C-c M-n |
选择一个命名空间并切换到它. |
C-c C-. |
跳转到 classpath 上的某个命名空间. |
C-c M-t v |
切换 var 追踪. |
C-c M-t n |
切换命名空间追踪. |
C-c C-t t |
运行点处的测试. |
C-c C-t C-t |
|
C-c C-t a |
重新运行你上次运行的测试. |
C-c C-t C-a |
|
C-c C-t n |
运行当前命名空间的测试. |
C-c C-t C-n |
|
C-c C-t l |
运行所有已加载命名空间的测试. |
C-c C-t C-l |
|
C-c C-t p |
运行所有项目命名空间的测试. 这会加载项目的所有命名空间. |
C-c C-t C-p |
|
C-c C-t r |
重新运行失败和错误的测试. |
C-c C-t C-r |
|
C-c C-t b |
显示测试报告缓冲区. |
C-c C-t C-b |
|
C-c C-q |
退出当前的 nREPL 连接. 带前缀参数时, 它将退出所有连接. |
无需记住此列表. 在任何 REPL 缓冲区中, 你都可以使用 REPL 菜单, 其中列出了最重要的命令及其键绑定. 你也可以调用 C-h f RET cider-repl-mode 来获取 cider-repl-mode 的键绑定列表.
6.2.2. REPL 快捷方式
在 REPL 中, 你还可以在 REPL 行的开头按 , 来使用“快捷命令”. 你将看到一个可以快速运行的命令列表 (如退出, 显示一些信息, 清除 REPL 等). 用于触发快捷方式的字符可通过 cider-repl-shortcut-dispatch-char 进行配置. 以下是如何将其更改为 ;:
(setq cider-repl-shortcut-dispatch-char ?\;)
6.3. Configuration
6.3.1. 连接时的行为
通常, 当你第一次建立 REPL 连接时, REPL 缓冲区会自动在一个单独的窗口中显示. 你可以像这样抑制这种行为:
(setq cider-repl-pop-to-buffer-on-connect nil)
如果你希望 REPL 缓冲区自动显示, 但不希望它获得焦点, 请使用:
(setq cider-repl-pop-to-buffer-on-connect 'display-only)
6.3.2. 切换时的行为
默认情况下, C-c C-z 会在不同的窗口中显示 REPL 缓冲区. 你可以使 C-c C-z 在当前窗口中切换到 CIDER REPL 缓冲区:
(setq cider-repl-display-in-current-window t)
6.3.3. 自定义 REPL 提示符
你可以通过将 cider-repl-prompt-function 设置为一个接受一个参数 (命名空间名称) 的函数来自定义 REPL 缓冲区提示符. 为方便起见, CIDER 提供了三个实现常见格式的函数:
cider-repl-prompt-lastname:
ssl>
cider-repl-prompt-abbreviated:
l.c.ssl>
cider-repl-prompt-default:
leiningen.core.ssl>
默认情况下, CIDER 使用 cider-repl-prompt-default.
当然, 你可以编写自己的函数. 例如, 在 leiningen 中有两个名称相似的命名空间 - leiningen.classpath 和 leiningen.core.classpath. 为了让它们易于识别, 你可以使用默认值, 或者你可以选择只显示命名空间的两个部分, 并且仍然能够知道哪个是 REPL 的当前命名空间. 这里是一个可以做到这一点的示例函数:
(defun cider-repl-prompt-show-two (namespace) "Return a prompt string with the last 2 segments of NAMESPACE." (let ((names (reverse (subseq (reverse (split-string namespace "\\.")) 0 2)))) (concat (car names) "." (cadr names) "> ")))
6.3.4. TAB 补全
你可以使用 cider-repl-tab-command 变量来控制 REPL 中 TAB 键的行为. 虽然默认的命令 cider-repl-indent-and-complete-symbol 对大多数用户来说应该是一个足够的选择, 但如果你希望, 切换到另一个命令也非常容易. 例如, 如果你希望 TAB 只进行缩进 (也许因为你习惯于用 M-TAB 进行补全), 请使用以下设置:
(setq cider-repl-tab-command #'indent-for-tab-command)
6.3.5. 自定义 Return 键的行为
通常, Return 会立即发送一个 form 进行求值. 如果你在编辑时想在 REPL 缓冲区中插入一个换行符, 你可以使用 C-j. 如果你输入很多跨越多行的较长的 forms, 更改键绑定可能会更方便:
(define-key cider-repl-mode-map (kbd "RET") #'cider-repl-newline-and-indent) (define-key cider-repl-mode-map (kbd "C-<return>") #'cider-repl-return)
这将使 Return 在 REPL 缓冲区中插入一个换行符, 而 C-Return 则发送 form 进行求值.
6.3.6. REPL 输出时自动滚动
在 0.21.0 版本之前, 每当有任何输出被打印时, REPL 缓冲区都会自动重新居中, 以便提示符位于窗口的底行, 从而在其上方显示尽可能多的输出. 这不再是默认行为——你现在可以通过设置内置选项 scroll-conservatively 来复制它, 例如:
(add-hook 'cider-repl-mode-hook '(lambda () (setq scroll-conservatively 101)))
6.3.7. 显示 REPL 窗口边界前的输出
如果你的 REPL 提示符位于 REPL 窗口的开头 (例如, 你按了几次 C-l 来重新居中你的 REPL 窗口), 并且在 Clojure 缓冲区中的交互式求值中有一些输出显示在那里, 这个输出不会自动滚动到视图中. 如果你想强制显示这样的输出, 你需要在你的配置中添加以下内容:
(setq cider-repl-display-output-before-window-boundaries t)
这个行为在 CIDER 1.7 中被改变了, 因为在 REPL 提示符前自动滚动输出很少需要, 但它当前的实现非常慢.
6.3.8. 自动修剪 REPL 缓冲区
此功能默认是禁用的.
如前所述, 如果允许 REPL 缓冲区的大小无限增长, 其性能将会下降. 你显然可以不时地手动清除 REPL, 但 CIDER 也有一些自动修剪功能, 可以为你简化这个过程.
通过将 cider-repl-buffer-size-limit 设置为一个整数, 可以启用自动修剪. 通过设置缓冲区中的字符数限制, 如果超过了设定的限制, 缓冲区可以在每次求值后 (从其开头) 被修剪. 以下是如何在你的 Emacs 配置中设置大小限制:
(setq cider-repl-buffer-size-limit 100000)
你也可以通过调用命令 cider-repl-trim-buffer 或 REPL 快捷方式 trim 来手动触发自动修剪.
6.3.9. 结果前缀
你可以更改用于前缀 REPL 结果的字符串:
(setq cider-repl-result-prefix ";; => ")
这将导致以下 REPL 输出:
user> (+ 1 2) ;; => 3
默认情况下, REPL 结果没有前缀.
6.3.10. 在 REPL 中设置 ns
默认情况下, cider-repl-set-ns 不会 require 目标 ns, 只是设置它. 这是基于这样的假设: 你可能在切换到目标 ns 之前已经求值了它 (例如, 在其源缓冲区中执行 C-c C-k (cider-load-buffer)). 如果你想改变这种行为 (以避免手动调用 cider-repl-set-ns 然后再调用 (require 'my-ns)), 你可以设置:
(setq cider-repl-require-ns-on-set t)
6.3.11. 自定义初始 REPL 命名空间
通常, CIDER REPL 会在 user 命名空间中启动. 你可以在你的 Leiningen 项目配置的 repl-options 部分为 REPL 会话提供一个初始命名空间:
:repl-options {:init-ns 'my-ns}
6.3.12. 自定义 REPL 缓冲区的名称
你可以使用变量 cider-session-name-template 来自定义缓冲区名称. 详情请参见该变量的文档.
6.3.13. Font-locking
通常, REPL 中的代码会像在 clojure-mode 中一样被 font-locked. 在 CIDER 0.10 之前, 默认情况下, REPL 输入会用 cider-repl-input-face 进行 font-locked (在按 Return 之后), 结果会用 cider-repl-result-face 进行 font-locked. 如果你想恢复旧的行为, 请使用:
(setq cider-repl-use-clojure-font-lock nil)
你可以使用 M-x cider-repl-toggle-clojure-font-lock 或 REPL 快捷方式 toggle-font-lock 来临时禁用 Clojure font-locking.
请记住, 默认情况下 cider-repl-input-face 只是将输入加粗, 而 cider-repl-result-face 是空白的 (意味着它实际上没有对结果应用任何 font-locking), 所以你可能想根据你的偏好调整这些 faces. 一些 Emacs 颜色主题可能会为它们提供不同的默认值.
在 REPL 中使用 Clojure font-locking 可能会对性能产生负面影响, 特别是对于 font-locking 巨大的结果. 然而, 这在很大程度上被结果流所缓解.
6.3.14. 结果的 Font-locking
关于结果的 Clojure font-locking, 你需要记住几件事:
- 当启用流时, 只有单块的结果会被作为 Clojure 进行 font-locked, 因为每个块都是独立进行 font-locked 的, 结果无法真正合并
- 结果的 font-locking 是一个昂贵的操作, 它涉及将值复制到一个临时缓冲区, 在那里我们检查其完整性并进行实际的 font-locking.
默认情况下, CIDER 指示 nREPL 以 4K 的块进行数据流传输, 但你可以轻松修改这个设置:
;; 让我们以 8K 的块进行数据流传输 (setq cider-print-buffer-size (8 * 1024))
将其设置为 nil 将导致使用 nREPL 的默认缓冲区大小 1024 字节. 打印缓冲区越小, 你在 REPL 中得到的反馈/更新就越快, 所以通常坚持使用相对较小的大小是个好主意.
如果你想了解更多关于结果的 font-locking, 你可以查看 cider-util.el 中 clojure-font-lock-as 和 clojure-font-lock-as-clojure 的定义.
6.3.15. 在 REPL 中进行漂亮打印
默认情况下, REPL 总是使用 cider-print-fn 指定的打印函数来打印你的求值结果.
这个行为在 CIDER 0.20 中被改变了. 在之前的 CIDER 版本中, 漂亮打印默认是禁用的.
你可以使用 M-x cider-repl-toggle-pretty-printing 或 REPL 快捷方式 toggle-pprint 来临时禁用此行为, 并恢复到默认行为 (等同于 clojure.core/pr).
如果你想完全禁用 cider-print-fn 的使用, 请使用:
(setq cider-repl-use-pretty-printing nil)
请注意, 不建议禁用漂亮打印. Emacs 处理非常长的行效果不佳, 因此使用一个能将超过一定宽度的行进行换行的打印函数 (即除了 pr 之外的任何函数) 将使你的 REPL 运行顺畅.
有关配置打印的更多信息, 请参阅本文档的这一部分.
6.3.16. 在 REPL 中显示图像
从 CIDER 0.17 (Andalucía) 开始, 求值为图像的表达式可以在 REPL 中呈现为图像. 你可以像这样启用此行为:
(setq cider-repl-use-content-types t)
这个设置在 CIDER 0.25 之前是默认启用的, 后来由于该功能的一些粗糙边缘从未得到妥善解决而被禁用. 详情请参见这个 bug 报告.
或者, 你可以使用 M-x cider-repl-toggle-content-types 或 REPL 快捷方式 toggle-content-types 来切换此行为.
6.3.17. REPL 类型检测
通常 CIDER 会根据从 cider-nrepl 的一部分 track-state middleware 收到的信息, 自动检测 REPL 的类型 (Clojure 或 ClojureScript).
在一些罕见的情况下 (例如, cider-nrepl 或 shadow-cljs 中的一个 bug), 这种自动检测可能会失败并返回错误的类型 (例如, Clojure 而不是 ClojureScript). 你可以像这样禁用自动检测逻辑:
(setq cider-repl-auto-detect-type nil)
之后, 你可以使用 cider-repl-set-type 来手动设置正确的类型.
在不禁用 cider-repl-auto-detect-type 的情况下使用 cider-repl-set-type 不会有太大作用, 因为 REPL 类型会不断地被 track-state middleware 自动重置.
6.3.18. REPL 历史记录
- 要让 REPL 历史在 CIDER 到达末尾时循环:
(setq cider-repl-wrap-history t)
- 要调整 REPL 历史中保留的最大项目数:
(setq cider-repl-history-size 1000) ; 默认是 500
- 要将所有项目的 REPL 历史存储在单个文件中:
(setq cider-repl-history-file "path/to/file")
- 要按项目存储 REPL 历史 (通过在每个项目的根目录下创建一个
.cider-history文件):
(setq cider-repl-history-file 'per-project)
请注意, CIDER 会在你杀死 REPL 缓冲区时 (包括调用 cider-quit) 或退出 Emacs 时将历史记录写入文件.
6.4. REPL History
6.4.1. REPL 历史浏览器
你可以使用 M-x cider-repl-history 命令浏览你的 REPL 输入历史. 这个命令在 cider-repl-mode 缓冲区中绑定到 C-c M-p, 也可以通过 history 快捷方式使用.
历史记录以相反的顺序列出, 最新的输入在缓冲区的顶部, 最旧的输入在底部. 你可以滚动历史记录, 当你找到你想要的历史项目时, 你可以从历史缓冲区将其插入到你的 REPL 缓冲区中.
历史浏览器
6.4.2. 模式
历史缓冲区有自己的主模式, cider-repl-history-mode. 它派生自 clojure-mode, 所以你在历史缓冲区中可以得到字体化. 这个模式支持预期的 defcustom 钩子变量, cider-repl-history-hook.
6.4.3. 插入
当你使用历史缓冲区向 REPL 缓冲区插入文本时, 确切的行为取决于插入前缓冲区中光标的位置.
通常, 当你积极使用 REPL 时, 你的光标会位于 REPL 缓冲区的末尾 (point-max). 在这种情况下, 文本被插入到缓冲区的末尾, 并且点会前进到插入文本的末尾 (就好像点被文本推着前进一样).
在不寻常的情况下, 当你调用历史浏览器时, 你的光标不在 REPL 缓冲区的末尾, 插入的文本仍将被插入到缓冲区的末尾 (point-max), 但点不会被修改.
CIDER 插入的文本没有最后的换行符, 允许你编辑它. 当你准备好后, 按 Return 键, 让它被 REPL 求值.
6.4.4. 退出
如果你选择一个输入, 文本将被插入到 REPL 缓冲区中, 并且历史缓冲区将自动退出. 如果你决定不插入任何文本就退出, 你可以运行 cider-repl-history-quit (参见键盘快捷键) 显式退出. 因为在使用历史缓冲区时会进行初始化和清理, 所以正确退出比仅仅切换离开历史缓冲区要好.
当你退出历史缓冲区时, CIDER 可以用几种不同的方式恢复缓冲区和窗口配置. 该行为由 cider-repl-history-quit-action 控制, 它可以被赋予几个值之一:
quit-window恢复窗口配置到之前的状态. 这是默认值.delete-and-restore恢复窗口配置到之前的状态, 并杀死*cider-repl-history*缓冲区.kill-and-delete-window杀死*cider-repl-history*缓冲区, 并删除窗口.bury-buffer只是埋藏*cider-repl-history*缓冲区, 但保留窗口.bury-and-delete-window埋藏缓冲区, 如果有多个窗口, 则删除窗口.- 任何其他值都被解释为要调用的函数的名称.
6.4.5. 过滤
通过从历史缓冲区调用 cider-repl-history-occur, 你将被提示输入一个正则表达式. 历史缓冲区将被过滤, 只显示那些匹配该正则表达式的输入.
6.4.6. 预览和高亮
当 cider-repl-history-show-preview 不为 nil 时, CIDER 会在 REPL 缓冲区中显示当前选定的历史条目的 overlay.
如果你没有正确地从浏览历史中退出 (即, 如果你只是 C-x b 离开缓冲区), 你可能会在你的 REPL 缓冲区中留下一个不想要的 overlay. 如果发生这种情况, 你可以用 M-x cider-repl-history-clear-preview 清理它.
默认情况下, cider-repl-history-show-preview 为 nil (禁用).
有一个相关的功能是在条目实际插入到 REPL 缓冲区后高亮显示它, 由变量 cider-repl-history-highlight-inserted-item 控制, 它可以设置为以下值:
solid在固定时间内高亮插入的文本.pulse使高亮逐渐淡出.t选择默认的高亮样式, 目前是pulse.nil禁用高亮. 这是cider-repl-history-highlight-inserted-item的默认值.
当 cider-repl-history-highlight-inserted-item 不为 nil 时, 你可以使用变量 cider-repl-history-inserted-item-face 自定义插入文本的 face.
6.4.7. 其他自定义
除了已经提到的, 还有相当多的自定义可用.
cider-repl-history-display-duplicates- 当设置为nil时, 将不会在历史缓冲区中显示任何重复的条目. 默认是t.cider-repl-history-display-duplicate-highest- 当不显示重复项时, 这控制了重复文本的一个实例在历史中的显示位置. 当为t时, 它在适用的最高位置显示条目; 当为nil时, 它在最低位置显示.cider-repl-history-display-style- 历史条目通常会超过一行. 该包为你提供了两种显示条目的选项:separated- 在条目之间插入一个分隔符字符串; 条目可能跨越多行. 这是默认值.one-line- 任何换行符都被替换为字面上的\n字符串, 因此不需要分隔符. 当文本插入到 REPL 中时, 每个\n都变成一个真正的换行符.
cider-repl-history-separator- 当cider-repl-history-display-style为separated时, 这给出了用作分隔符的文本. 默认是一系列十个分号, 这当然是 Clojure 中的注释. 分隔符可以是任何东西, 但如果你把它设置成奇怪的东西, 可能会搞乱字体化.cider-repl-history-separator-face- 指定分隔符的 face.cider-repl-history-maximum-display-length- 当为nil(默认) 时, 所有历史项目都完整显示. 如果你希望长项目被缩写, 你可以将此变量设置为一个整数, 每个项目将被限制为该字符数. (此变量不影响显示的项目数, 只影响每个项目的最大长度.)cider-repl-history-recenter- 当不为nil时, 总是将当前条目保持在历史窗口的顶部. 默认是nil.cider-repl-history-resize-window- 是否调整历史窗口以适应其内容. 值可以是t, 表示是, 或者是一个整数的 cons 对,(MAXIMUM . MINIMUM)用于窗口的大小.MAXIMUM默认为pop-to-buffer选择的窗口大小;MINIMUM默认为window-min-height.cider-repl-history-highlight-current-entry- 如果不为nil, 则高亮历史缓冲区中当前选定的条目. 默认是nil.cider-repl-history-current-entry-face- 指定历史条目高亮的 face.cider-repl-history-text-properties- 当设置为t时, 维护条目上的 Emacs 文本属性. 默认是nil.
6.4.8. 键绑定
在历史缓冲区中有许多重要的键绑定.
| 键盘快捷键 | 描述 |
|---|---|
n |
转到下一个 (更低, 更旧的) 历史项目. |
p |
转到上一个 (更高, 最近的) 历史项目. |
RET or SPC |
在 REPL 缓冲区的末尾插入历史项目 (在点处), 并退出. |
l (小写 L) |
过滤命令历史 (见上文过滤部分). |
s |
正则表达式向前搜索. |
r |
正则表达式向后搜索. |
q |
退出 (并执行退出操作). |
U |
在 REPL 缓冲区中撤销. |
D |
删除历史项目 (在点处). |
7. 测试
7.1. Running Tests
Clojure 生态系统为测试驱动开发 (TDD) 和其他以测试为中心的模式提供了大量支持. 首先, Clojure 提供了一个用于开发测试的标准化框架, 称为 clojure.test.
许多其他测试库都接入了这个框架. 其次, 像 Leiningen 这样的工具创建了标准化的应用程序和库项目结构, 为测试代码提供了位置和惯用的命名.
最后, CIDER 提供了几种简单的方法来运行这些测试, 查看测试结果, 并快速跳转到未通过给定测试的代码.
CIDER 只支持 clojure.test 以及其他提供与 clojure.test 集成的库. 对其他库的原生支持可能通过 CIDER 插件提供.
7.1.1. 基本用法
CIDER 有几个函数可以运行所有测试或它们的选定子集. 所有的 CIDER 测试命令在源代码和 REPL 缓冲区中都可用. 在 REPL 缓冲区中, 也可以使用 , 来调用一些测试命令.
CIDER 只会运行已经加载 (求值) 的测试. 这意味着在运行某些测试之前, 你总是需要先求值它们.
相应地, 如果你对测试做了一些更改, 你需要重新求值它, 以便 CIDER 能够获取更新后的版本.
首先, 你可以用 C-c C-t p 或 C-c C-t C-p 运行项目中的所有测试. 需要注意的是, 这会加载你项目中的所有命名空间, 这可能比你预期的要多.
你可以用 C-c C-t l 或 C-c C-t C-l 运行所有已加载的测试.
如果你用一个前缀 (例如 C-u C-c C-t l) 调用这些命令中的任何一个, CIDER 将会提示测试选择器过滤器, 并只运行那些匹配选择器包含/排除的测试.
7.1.2. 测试选择器
测试开发者使用选择器来定义整个测试套件的子集, 这些子集会针对不同的测试任务一起运行.
例如可以用 ^:smoke 元数据标记来标记一些测试, 用 ^:integration 标记其他. 能够在构建管道中分别运行这些测试. CIDER 帮助你在你的开发环境中运行这些相同的测试子集.
测试选择器最初是 leiningen 的一个功能, 你可以通过执行以下命令获取更多信息:
$ lein help test
使用 C-c C-t n 或 C-c C-t C-n 运行当前命名空间中的所有测试, 无论是由源文件还是由 REPL 指定.
注意, Clojure 项目的惯例是将测试放在与被测试代码不同的命名空间中. CIDER 使用一个简单的算法来确定测试的位置. 该算法工作如下:
- 在一个实现命名空间中 (例如
some.ns), CIDER 将尝试找到一个匹配的测试命名空间 (默认为some.ns-test) 并在那里运行测试. - 如果在一个看起来像测试命名空间的地方 (例如
some.ns-test), CIDER 将简单地在该命名空间中运行测试.
如果已经将一些测试放在了实现命名空间中, 例如使用 clojure.test/with-test, 要抑制命名空间推断逻辑, 并强制 CIDER 无条件地在当前命名空间中运行测试.
可以通过给命名空间命令添加一个前缀来实现这一点: C-u C-c C-t C-n. 这将简单地运行当前访问或活动的命名空间中存在的任何测试.
也可以使用 C-c C-t C-s 运行命名空间中定义的测试子集, 按测试选择器过滤. CIDER 会在 minibuffer 中提示选择器.
如果用一个前缀 (C-u C-c C-t C-s) 调用这个命令, 可以像对 C-u C-c C-t C-n 一样抑制命名空间推断逻辑.
最后, 可以使用 C-c C-t t 或 C-c C-t C-t 执行点处的特定测试. 它也适用于实现函数, 通过搜索一个匹配的测试命名空间和一个匹配的 deftest 名称.
7.1.3. 配置
可以通过多种方式配置 CIDER 的测试执行行为.
- Fail-fast
从 1.8.0 版本开始, CIDER 具有标准的 fail-fast 功能, 由
cider-test-fail-fastdefcustom 控制 (默认为t).可以使用
cider-test-toggle-fail-fast(C-c C-t f或C-c C-t C-f) 来切换当前缓冲区的值.fail-fast 永远不会被选择用于 "retest" 功能, 因为那会导致你丢失大部分之前失败的测试.
- 测试命名空间命名约定
如果测试不遵循
some.ns-test命名约定, 可以将变量cider-test-infer-test-ns设置为一个函数, 该函数接受当前命名空间并返回匹配的测试命名空间 (可能与当前命名空间相同).这为使用任何你想要的约定来结构化你的测试套件提供了完全的灵活性. 以下是如何配置测试运行以查找
some.test-ns而不是some.ns-test:(defun cider-custom-test-ns-fn (ns) "For a NS, return the test namespace, which may be the argument itself. This uses the convention of prepending 'test-' to the namespace name." (when ns (let ((prefix "test-")) (if (string-prefix-p prefix ns) ns (concat prefix ns))))) (setq cider-test-infer-test-ns #'cider-custom-test-ns-fn)
- 默认测试选择器
如果有一些选择器想要自动应用, 可以将变量
cider-test-default-include-selectors和cider-test-default-exclude-selectors设置为一个要使用的字符串列表.以下是一个设置默认排除选择器的示例, 以便标记为 "integration" 或 "flakey" 的测试不运行.
(setq cider-test-default-exclude-selectors '("integration" "flakey"))
通常会希望将默认选择器放在项目配置中, 而不是全局配置中.
- 成功时显示测试报告
默认情况下, 只有当有测试失败或错误时才会显示测试报告. 如果想无论测试是否通过都查看测试报告:
(setq cider-test-show-report-on-success t)
- 在错误时运行回调
有时, 为了进行细粒度的集成或调试, 希望在测试因异常失败时运行一个任意的回调.
为此, 可以安全地使用普通的 Clojure 重新定义
cider.nrepl.middleware.test/test-error-handler; 更多详情请参考其 docstring.
7.1.4. 自动运行测试 (测试驱动开发)
CIDER 提供了一个 minor-mode, 每当加载一个文件 (用 C-c C-k) 时, 都会自动运行该命名空间的所有测试. 可以用 M-x cider-auto-test-mode 手动切换它, 或者可以使用:
(cider-auto-test-mode 1)
这与每次加载 Clojure 缓冲区时手动输入 C-c C-t C-n 是相同的. 如前所述, CIDER 会尝试自动确定包含测试的命名空间.
7.2. Test Reports
7.2.1. 与测试结果报告交互
如果测试失败, CIDER 会在 *cider-test-report* 缓冲区中显示一个测试结果报告.
这个缓冲区使用 cider-test-report-mode, 这使得审查可能发生的任何失败并直接跳转到失败测试的定义变得容易.
再次调用测试命令将会更新测试报告.
也可以配置测试报告在成功时显示.
| 键盘快捷键 | 命令名称 | 描述 |
|---|---|---|
g |
cider-test-run-test |
运行点处的测试. |
n |
cider-test-run-ns-tests |
运行当前命名空间的测试. |
s |
cider-test-run-ns-tests-with-filters |
使用选择器过滤器运行当前命名空间的测试. |
l |
cider-test-run-loaded-tests |
运行所有已加载命名空间的测试. |
p |
cider-test-run-project-tests |
运行所有项目命名空间的测试. 这会加载额外的命名空间. |
f |
cider-test-rerun-failed-tests |
重新运行失败/错误的测试. |
M-p |
cider-test-previous-result |
将点移动到上一个测试. |
M-n |
cider-test-next-result |
将点移动到下一个测试. |
t or M-. or kbd:click |
cider-test-jump |
跳转到测试定义. |
d |
cider-test-ediff |
显示实际与期望的差异. |
e |
cider-test-stacktrace |
显示测试错误原因和堆栈跟踪信息. |
大多数键绑定在 cider-test-report-mode-map 中定义, 而点击行为在 cider-test-var-keymap 中定义.
7.3. Supported Libraries
7.3.1. 将 cider-test 与替代测试库一起使用
clojure.test 机制被设计为可插拔的. 任何测试库都可以与之集成并利用 cider-test 生态系统.
作为测试框架的作者, 支持内置的 clojure.test 机制 (因此也支持 cider-test) 非常直接:
- 向与测试函数对应的 vars 添加
:test元数据.clojure-test机制使用此元数据来查找测试. - 实现
clojure.test/reportmultimethod 来捕获测试结果.
例如, test.check 是独立于 clojure.test 设计的, 但与之集成. 因此, cider-test 处理 defspec 的方式就像处理 deftest 一样.
test.check 只是在这个命名空间中添加了兼容性.
7.3.2. 支持的库
test.checkclojure-expectations在 2.2 版本中添加了对clojure.test的支持, 应该也能与 CIDER 一起工作.fudje
7.3.3. Midje 支持
Emidje 是一个第三方扩展, 不与 CIDER 捆绑.
Emidje 是 Emacs 内 Midje 的一个测试运行器, 报告查看器和格式化工具.
Emidje 扩展了 CIDER 以提供对 Midje 测试的支持, 其方式与 cider-test.el 对 clojure.test 测试的支持类似. 事实上, Emidje 的大多数功能都受到了 cider-test.el 功能的强烈启发.
8. 调试
8.1. Debugger
CIDER 带有一个强大的交互式 Clojure 调试器, 灵感来源于 Emacs 自己的 Edebug.
CIDER 调试器
调试器不支持 ClojureScript. 如果需要调试 ClojureScript 代码, 请查看 Cider Storm.
8.1.1. 使用调试器
在正常的 CIDER 开发过程中, 程序员通常会通过输入 C-M-x (cider-eval-defun-at-point) 来求值一个 form, 通常是一个函数定义.
当为求值命令添加一个前缀时, CIDER 也可以为一个 form 进行调试插桩: C-u C-M-x. 在插桩过程中, CIDER 会在 form 中插入尽可能多的断点.
每当执行到达一个断点时, CIDER 会进入调试模式并提示下一步该怎么做. 可以通过正常地再次求值该 form, 使用 C-M-x 来移除插桩.
也可以通过在任何代码中放置 #break 来手动插入一个断点, 放在希望断点触发的 form 前面, 然后用 C-M-x 求值该 form. 当执行到达 #break 后的 form 时, 将被带入调试器.
例如, 如果对下面的代码执行 C-M-x, 每次求值 (inspector msg) 时都会触发一个断点.
(defn eval-msg [{:keys [inspect] :as msg}] (if inspect #break (inspector msg) msg))
除了 #break, 也可以在 form 前面写 #dbg. 这将在 form 的前面放置一个断点, 就像 #break 一样, 并且也会在它内部的所有地方放置断点.
在上面的例子中, 这会在 (inspector msg) 周围放置一个断点, 并在 msg 周围放置另一个断点.
事实上, 输入 C-u C-M-x 来插桩一个顶层 form 只是一种方便的方式, 用一个隐含的 #dbg 来求值该 form; 行为是相同的.
在任何时候, 都可以使用 M-x cider-browse-instrumented-defs 命令来调出所有当前已插桩的 defs 的列表. 协议和类型也可以被插桩, 但它们不会被此命令列出.
8.1.2. 理解断点
在 CIDER 调试器中, "断点" 这个术语指的是调试器可以暂停执行并显示表达式值的地方.
可以用 #break 设置单个断点, 或者用 #dbg (或通过用 C-u C-M-x 求值) 在整个 form 中设置断点, 如前所述.
当使用 #dbg 或 C-u C-M-x 时, 并非每个 form 都会被断点包装. 调试器会尽量避免在不感兴趣的表达式上设置断点.
例如, 在代码中一个字面量数字 23 处停止执行并求值得到 23 完全没有意义.
8.1.3. 按键
一旦进入 CIDER 调试器, 有许多命令可用来单步执行代码, 求值其他 forms, 检查值, 注入新值, 或查看当前堆栈.
cider-debug 试图与 Edebug 命令键保持一致, 尽管有一些差异.
| 键盘快捷键 | 描述 |
|---|---|
n |
下一步 |
i |
进入一个函数 |
o |
跳出当前 sexp (像 up-list) |
O |
强制跳出当前 sexp |
h |
跳过所有 sexps 直到“这里” (当前位置). 在此之前移动光标. |
H |
强制步进到“这里” |
c |
在当前断点不停止继续 |
C |
对所有断点不停止继续 |
e |
在当前上下文中求值代码 |
p |
检查当前值 |
P |
检查任意表达式 |
l |
检查局部变量 |
L |
切换局部变量的显示 |
j |
向运行中的代码注入一个值 |
s |
显示当前堆栈 |
t |
追踪. 继续, 打印表达式及其值. |
q |
退出执行 |
此外, 所有通常的求值命令, 如 C-x C-e 或 C-c M-: 在调试器激活时都将被限定在当前的词法上下文中, 允许访问局部变量.
8.1.4. 单步命令详情
这些命令会继续执行直到到达一个断点.
next: 步进到下一个断点in: 步进到即将被调用的函数中. 如果下一个断点不在一个函数调用周围, 行为与next相同. 请注意, 并非所有函数都可以步入——只有存储在 vars 中的普通函数, CIDER 可以为其找到源代码. 你目前无法步入多方法(multimethod), 协议函数(protocol function), 或clojure.core中的函数 (尽管多方法和协议可以手动插桩).out: 步进到当前 sexp 之外的下一个断点.Out: 与o相同, 但跳过其他函数中的断点. 也就是说, 如果被跳过的代码包含对另一个插桩函数的调用, 如果你用o步出, 调试器会停在该函数中, 但如果你用O步出则不会.here: 将光标放在被调试函数中更远的地方, 在你想要下次停止的地方. 然后按h, 调试器会跳过所有断点直到那个位置.Here: 与h相同, 但像O一样跳过其他函数中的断点.continue: 在当前断点不停止继续.continue-all: 不停止地继续, 跳过所有断点.
8.1.5. 其他命令详情
eval: 提示输入一个 clojure 表达式, 它可以引用在调试器停止处作用域内的局部变量. 结果显示在一个 overlay 中.inspect: 在一个cider-inspector缓冲区中检查当前求值的值.inspect-prompt: 像eval, 但在一个cider-inspector缓冲区中显示值.locals: 打开一个cider-inspector缓冲区, 显示在调试器停止的上下文中定义的所有局部变量.inject: 用你输入的表达式的值替换当前显示的值. 后续代码将看到你输入的新值.stacktrace: 显示调试器停止点的堆栈跟踪.trace: 继续执行, 但在每个断点处, 不是停止并在 overlay 中显示值, 而是将 form 及其值打印到 REPL.quit: 立即退出执行. 与continue不同, 被调试函数中的其余代码不会被执行.
8.1.6. 条件断点
断点可以是条件性的, 这样调试器只会在条件为真时停止.
条件使用附加到 form 的 :break/when 元数据指定.
(dotimes [i 10] #dbg ^{:break/when (= i 7)} (prn i))
用 C-M-x 求值上述代码, 调试器只会停止一次, 当 i 等于 7 时.
可以让 CIDER 将断点条件插入到代码中. 将光标放在希望条件所在的位置, 然后用 C-u C-u C-M-x 或 C-u C-u C-c C-c 求值.
CIDER 然后会在 minibuffer 中提示你输入条件, 并在你的代码中插入适当的 #dbg 加上元数据注解. 请注意, 需要手动删除这个注解;
不能像取消插桩 C-u C-M-x 那样简单地使用 C-M-x.
8.1.7. 注意事项
由于调试器目前的实现方式, 在处理某些 forms 时存在一些限制. 集合字面量目前完全不被插桩.
映射字面量目前只有在它们很小或者键有某种自然顺序时才被插桩. 例如, 以下表达式不会被插桩.
#dbg (count {:foo 2 :bar (inc 4) "foo" 6 "bar" 8 9 10 11 12 13 14 15 (inc 16) 17 (inc 18)})
调试器目前受限的另一个构造是 loop/recur. 由于 recur 总是必须出现在 loop 或 fn 内部的尾部位置, 而调试器使用宏在 forms 中插入断点, 可能会发生 recur 不再出现在尾部位置的情况. 在这种情况下, 我们必须避免设置断点. 这种情况的一个例子是:
(loop [i 0] #break (when (< i 10) (println i) (recur (inc i))))
这里的断点恰好在一个 form 的前面, 该 form 的最后一个表达式是一个 recur, 它没有被包裹在一个 loop 中.
目前这个断点没有效果. 这并不意味着不能在 loop 中使用调试器, 只是意味着必须更小心地设置调试语句.
8.1.8. 调试器内部
本节解释了调试器的一些内部工作原理. 它旨在帮助那些有兴趣贡献的人, 并且不教授任何关于调试器用法的内容.
CIDER 在插桩代码时分几个步骤工作:
- CIDER 遍历代码, 向 forms 和符号添加元数据, 以识别它们在代码中的位置 (坐标).
- 它对所有东西进行宏展开以去除宏.
- 它再次遍历代码, 对其进行插桩.
- CIDER 理解所有现有的special forms, 并注意不在不应该插桩的地方插桩. 例如, CIDER 不会插桩
fn*的参数列表或let绑定的左侧. - 无论在哪里找到先前注入的元数据, 假设该位置对于插桩是有效的, 它都会用一个名为
breakpoint-if-interesting的宏来包装该 form 或符号.
- CIDER 理解所有现有的special forms, 并注意不在不应该插桩的地方插桩. 例如, CIDER 不会插桩
- 当结果代码实际被编译时, Clojure 编译器将展开
breakpoint-if-interesting宏. 这个宏决定了 form 或符号的返回值是否是用户可能想看到的东西. 如果是, form 或符号就会被一个breakpoint宏包装, 否则就原样返回. breakpoint宏获取在第 1 步中提供的坐标信息, 并将其发送到 Emacs (前端). 它还发送 form 的返回值和一个可用命令的提示. Emacs 然后使用这些信息来显示实际代码 forms 的值, 并提示下一个操作.
一些返回值不有趣的示例 forms (因此不会被断点包装):
- 在
(fn [x] (inc x))中, 返回值是一个函数对象, 不携带任何信息. 请注意, 这与你调用此函数时的返回值不同 (那是有趣的). 此外, 即使这个 form 没有被断点包装, 它内部的 forms 也会被包装 ((inc x)和x). - 类似地, 在像
(map inc (range 10))这样的 form 中, 符号inc指向clojure.core中的一个函数. 这也是无关紧要的 (除非它被一个局部变量遮蔽, 但调试器可以识别出这一点).
8.2. Enlighten
Enlighten Mode 实时显示局部变量的值, 当你的代码运行时. 这个功能有点类似于 Light Table 编辑器中的一个功能.
要打开它, 执行 M-x cider-enlighten-mode. 然后, 使用 C-M-x 或 C-x C-e 逐个求值你的函数. 请注意 C-c C-k 不会起作用.
就是这样! 一旦你的代码执行, 左边那个普通的旧缓冲区就会变成右边那个绚丽的灯光秀.
| Enlighten Mode 禁用 | Enlighten Mode 启用 |
|---|---|
| #+CAPTION: Disabled | #+CAPTION: Enabled |
| 禁用 | 启用 |
要停止显示局部变量, 你必须禁用 cider-enlighten-mode 并重新求值你之前插桩过的定义.
你也可以通过在 (def 前面写 #light 并在特定函数上触发这个功能 (而不必打开 minor mode) 并重新求值它.
8.3. Inspector
值检查器允许你检查和导航数据的结构. 虽然你几乎可以对任何东西使用它 (例如, 原始数据类型, var, ref 类型), 但当你在处理 (深度) 嵌套的类集合数据类型 (例如, map 的向量) 时, 它最有用.
8.3.1. 用法
在源缓冲区或 REPL 中, 在某个 form 之后输入 C-c M-i (cider-inspect) 会在一个新缓冲区中向你显示该 form 结果的结构. 你也可以使用 C-u C-c M-i 来检查当前顶层 form 的结果, 以及 C-u C-u C-c M-i 从 minibuffer 读取一个表达式并检查其结果.
或者, 在一个常规的 eval 命令之后, 你可以使用 cider-inspect-last-result 来检查最后求值的值. 当一个检查器缓冲区在后台可见时, 它会自动更新为最后的结果. 这个行为可以通过变量 cider-auto-inspect-after-eval 来控制.
检查器也可以在调试会话中途调用, 更多详情请看这里.
调试器的当前值也可以发送到 Clojure 的 tap> 设施. 这可以用来将 CIDER 与各种在 web 浏览器中渲染 tapped 值的外部工具集成, 例如.
你将在检查器缓冲区 (内部使用 cider-inspector-mode) 中拥有额外的键绑定:
| 键盘快捷键 | 命令 | 描述 |
|---|---|---|
Tab 和 Shift-Tab / n 和 p |
cider-inspector-next-inspectable-object |
导航可检查的子对象 |
Return |
cider-inspector-operate-on-point |
检查子对象 |
l |
cider-inspector-pop |
弹出到父对象 |
g |
cider-inspector-refresh |
刷新检查器 (例如, 如果正在查看 atom/ref/agent) |
SPC or Next |
cider-inspector-next-page |
在分页视图中跳转到下一页 |
M-SPC or Prev |
cider-inspector-prev-page |
在分页视图中跳转到上一页 |
y |
cider-inspector-display-analytics |
计算并显示被检查对象的分析数据. 支持对数字, 字符串, 元组, map 的列表; 以及大型键值 map 进行分析. |
s |
cider-inspector-set-page-size |
在分页视图中设置新的页面大小 |
c |
cider-inspector-set-max-coll-size |
设置一个新的最大大小, 超过该大小的嵌套集合将被截断 |
C |
cider-inspector-set-max-nested-depth |
设置一个新的最大嵌套级别, 超过该级别的集合将被截断 |
a |
cider-inspector-set-max-atom-length |
设置一个新的最大长度, 超过该长度的嵌套原子 (非集合) 将被截断 |
v |
cider-inspector-toggle-view-mode |
在 :normal, :table 和 :object 视图模式之间切换当前值的渲染. 在 :table 模式下, 将值渲染为表格 (仅支持 map 或元组的序列). 在 :object 模式下, 任何值都渲染为普通的 Java 对象 (通过显示其字段), 而不是 Inspector 在 :normal 模式下应用的自定义渲染规则. |
P |
cider-inspector-toggle-pretty-print |
切换检查器中值的漂亮打印. 如果你总是希望值被漂亮打印, 你可以将 cider-inspector-pretty-print 自定义选项设置为 t. |
S |
cider-inspector-toggle-sort-maps |
切换检查器中按键对 map 进行排序. 如果你总是希望 map 显示为已排序, 你可以将 cider-inspector-sort-maps 自定义选项设置为 t. |
D |
cider-inspector-toggle-only-diff |
在检查 diff 结果时, 切换只显示不同的值. 如果你总是希望只显示 diff 而不是所有值, 你可以将 cider-inspector-only-diff 自定义选项设置为 t. |
d |
cider-inspector-def-current-val |
在 REPL 命名空间中用当前检查器值定义一个 var. 如果你倾向于总是选择相同的名称, 你可能想设置 cider-inspector-preferred-var-names 自定义选项. |
C-c C-p |
cider-inspector-print-current-value |
将检查器的当前值打印到 cider-result-buffer. |
9 |
cider-inspector-previous-sibling |
导航到上一个兄弟节点, 在一个顺序集合内. |
0 |
cider-inspector-next-sibling |
导航到下一个兄弟节点, 在一个顺序集合内. |
o |
cider-inspector-open-thing-at-point |
如果找到, 打开点处的 url 或文件. |
: |
cider-inspect-expr-from-inspector |
提示输入一个新值, 在 Inspector 中渲染它. |
t |
cider-inspector-tap-current-val |
使用检查器的当前值作为其参数执行 tap>. |
1 |
cider-inspector-tap-at-point |
使用检查器的当前子值 (在 POINT 处的那个) 作为其参数执行 tap>. |
8.3.2. 配置
默认情况下, 导航会跳过像 nils, 数字和关键字这样不值得检查的值. 你可以使用变量 cider-inspector-skip-uninteresting 来控制这种行为.
检查器缓冲区默认是自动选中的. 你可以使用变量 cider-inspector-auto-select-buffer 来禁用自动选中.
你可以使用变量 cider-inspector-page-size, cider-inspector-max-coll-size, cider-inspector-max-nested-depth, 和 cider-inspector-max-atom-length 来设置默认显示的数据量. 这些值可以通过上表中的键绑定为当前检查器缓冲区进行调整.
如果启用 cider-inspector-fill-frame, 检查器窗口将填满其框架.
你可以使用 P 切换检查器中值的漂亮打印, 并通过调整 cider-inspector-pretty-print 自定义选项来定制它们的初始表示.
当你使用 d 定义一个 var 时, 可以建议一个 var 名称 (默认为无). 你可以通过 cider-inspector-preferred-var-names 配置选项来自定义这个值. 即使设置了它, 你在输入时仍然可以自由选择新的名称. 最近的名称将在后续使用中优先.
当 cider-inspector-tidy-qualified-keywords 启用时, 如果在相同命名空间的代码缓冲区中调用检查器或 require 了相应的别名, 命名空间限定的关键字如 ::foo 或 ::alias/baz 将以相同的方式显示.
8.3.3. 其他资源
- 在 Spacemacs 中使用 CIDER 的 Inspector
8.4. Logging
CIDER Log Mode 允许你捕获, 调试, 检查和查看由 Java 日志框架发出的日志事件. 捕获的日志事件可以被搜索, 流式传输到客户端, 漂亮打印, 并与 CIDER Inspector 集成. 这里是 CIDER Log Mode 运行中的一个截图.
CIDER 日志
截图左侧在 *cider-log* 缓冲区中显示了日志事件列表. 右侧, 在 *cider-inspect* 缓冲区中可以看到一个日志事件. 底部显示了 CIDER 日志菜单, 你可以从中执行与日志相关的操作.
8.4.1. 功能
- 浏览给定日志框架的 Javadocs 和网站.
- 搜索日志事件并在缓冲区中显示它们.
- 漂亮打印日志事件.
- 在 CIDER Inspector 中显示日志事件.
- 与
logview集成.
8.4.2. 依赖
Logview 是 CIDER Log Mode 的一个可选依赖. 我们推荐使用它, 因为它负责为日志事件上色, 并提供其他有用的功能, 如语法高亮, 过滤等. 当可用时, 它会自动使用, 其使用可以通过 cider-log-use-logview 进行自定义.
8.4.3. transient-mode
值得指出的是, Cider Log Mode 的大多数功能和工作流程都基于 transient-mode.
一个 transient 菜单是以下的小部件:
CIDER Log transient 菜单
它的用法大多是自解释的, 因为每个命令都有其键绑定附加在描述上.
8.4.4. 入门
要使用 CIDER Log Mode, 有两种主要的方式开始:
M-x cider-log-show-frameworks, 查看可用的日志框架. 如果你的日志框架受支持但未显示, 请参见故障排除部分.M-x cider-log-event, 使用transient-mode, 不会立即显示日志 (你应该使用transient-mode来显示*cider-log*缓冲区)M-x cider-log-show是一个较新的函数, 旨在成为一个“一体化”命令, 用于简化的体验, 这对于入门或临时使用可能很有用.- 它不使用
transient-mode- 它旨在一步完成所有事情 它会立即显示
*cider-log*缓冲区这些命令中的任何一个只有在附加了 CIDER repl (Sesman 会话) 的缓冲区中才会成功.
- 它不使用
- 通过 cider-log-show
通过使用
M-x cider-log-show, 所有的设置和渲染都将为你一步完成, 不会弹出transient-mode菜单:- 一个框架将被自动设置
- 你可能会被提示选择一个框架
- 你可以通过自定义
cider-log-framework-name来防止提示 (最好在dir-locals.el中完成)
- 一个日志 appender 和一个日志 consumer 将被自动设置
*cider-log*缓冲区将被渲染.
所有这些步骤都是幂等的, 所以多次运行
M-x cider-log-show是安全的.之后你可以通过运行
M-x cider-log,M-x cider-log-consumer等来完善设置 (例如配置过滤). - 一个框架将被自动设置
- 通过 cider-log-event
此函数的预期工作流程如下:
- 运行
M-x cider-log-event - 一个框架将被自动设置
- 你可能会被提示选择一个框架
- 你可以通过自定义
cider-log-framework-name来防止提示 (最好在dir-locals.el中完成)
- 一个日志 appender 和一个日志 consumer 将被自动设置
*cider-log*缓冲区将不会被渲染要使其被渲染, 在
transient-mode菜单中按s(搜索日志事件).*cider-log*缓冲区最初可能是空的, 你可能会看到一条No log events found的消息. 这是因为在添加 appender 和搜索事件之间没有任何日志记录. 所以, 现在是运行一些触发所选框架日志事件的代码的好时机.
- 运行
8.4.5. 高级用法
cider-log 是一个既可以用于从头开始设置日志记录, 也可以用于调整现有设置 (例如为 consumer 添加过滤) 的函数.
它被认为是一种较低级的方法.
- 通过 cider-log 设置
输入
C-c M-l l或M-x cider-log. 第一次运行该命令时, 它会提示你选择要使用的日志框架, 然后将一个日志 appender 附加到所选框架的根 logger. 日志 appender 附加后,cider-log命令将显示一个 Transient 菜单, 你可以从中采取进一步的行动, 如管理日志框架, appenders, consumers 和事件.此时你应该添加一个日志 appender 和一个日志 consumer - 否则将无法显示任何日志条目 (这就是为什么
cider-log被认为是较低级的工作流程).之后, 要查看日志事件并将其流式传输到你的客户端, 输入
es(搜索日志事件) 然后按s. 这将打开*cider-log*缓冲区, 显示到目前为止捕获的任何日志事件. 它还会向此缓冲区添加一个日志 consumer, 该 consumer 接收新到达的日志事件.
8.4.6. 按键绑定
| 命令 | 键盘快捷键 | 描述 |
|---|---|---|
cider-log-event |
C-c M-l e |
通过 transient-mode 菜单显示管理日志事件的菜单. 这是入门的两个主要入口点之一. |
cider-log-show |
C-c M-l s |
立即显示日志, 无需 transient-mode 菜单. 这是入门的两个主要入口点之一. |
cider-log |
C-c M-l l |
显示 CIDER 日志菜单. 请注意, 这被认为是高级用法. |
cider-log-framework |
C-c M-l f |
显示管理日志框架的菜单. |
cider-log-appender |
C-c M-l a |
显示管理日志框架的 appenders 的菜单. |
cider-log-consumer |
C-c M-l c |
显示管理监听日志事件的 consumers 的菜单. |
8.4.7. 日志框架
CIDER Log Mode 支持在运行时重新配置的日志框架. 更具体地说, 框架应支持将日志 appenders 附加到 loggers, 以捕获事件.
目前支持以下日志框架:
- Java Util Logging
- Logback
Timbre
如果你选择 Timbre, 请确保你的项目版本大致与 Logjam 的预期版本相匹配 - 否则不兼容的更改可能会使 Timbre 无法作为 CIDER Log Mode 框架选项出现.
支持 Log4j 的工作正在进行中, 但在运行时进行的配置更改存在一些困难, 这些更改会被 Log4j2 的重新配置机制清除掉.
如果你选择的日志框架目前不受 CIDER Log Mode 支持, 你可以选择在你的项目中使用 Clojure 官方的 tools.logging façade, 这样你就可以在本地, 不引人注目地告诉它使用一个受支持的框架 (如 Logback) 来代替你项目的默认框架. 请注意, 其日志后端实现的选择可以通过 -Dclojure.tools.logging.factory Java 系统属性来控制, 这可以在本地通过 Lein profiles 或 Clojure CLI 别名干净地进行自定义.
8.4.8. 日志 Appender
为了捕获日志事件, 需要将一个日志 appender 附加到框架的 logger 上. 一旦一个 appender 附加到一个 logger 上, 它就会在一个内存中的 atom 中捕获框架发出的日志事件. 一个日志 appender 可以配置为具有一定的大小 (默认: 100000) 和一个百分比阈值 (默认: 10). 当达到阈值 (appender 大小加阈值) 时, 日志事件会从 appender 中清除. 此外, appender 还可以配置为只捕获匹配一组过滤器的事件.
- 按键绑定
以下键绑定可用于与日志 appenders 交互.
命令 键盘快捷键 描述 cider-log-appenderC-c M-l a显示用于管理日志 appenders 的 transient 菜单. cider-log-add-appenderC-c M-l a a向 logger 添加一个日志 appender. cider-log-clear-appenderC-c M-l a c清除日志 appender 的所有捕获事件. cider-log-kill-appenderC-c M-l a k通过从 logger 中移除来终止一个日志 appender. cider-log-update-appenderC-c M-l a u更新日志 appender 的过滤器, 大小或阈值.
8.4.9. 日志 Consumer
通过将日志 consumer 附加到 appender, 可以将日志事件流式传输到客户端. 一旦日志 consumer 附加到 appender, 它将从 appender 接收事件. 与日志 appenders 类似, consumers 也可以配置一组过滤器以仅接收某些事件.
- 按键绑定
以下键绑定可用于与日志 consumers 交互.
命令 主 / Consumer 菜单 键盘快捷键 描述 cider-log-consumerC-c M-l c显示用于管理日志 consumers 的 transient 菜单. cider-log-add-consumerca / aC-c M-l c a向日志 appender 添加一个日志 consumer, 将事件流式传输到客户端. cider-log-kill-consumerck / kC-c M-l c k终止一个日志 consumer 并停止向客户端流式传输事件. cider-log-update-consumercu / uC-c M-l c u更新日志 consumer 的过滤器以更改哪些事件被流式传输到客户端.
8.4.10. 日志事件
日志事件可以被搜索, 流式传输到客户端或在 CIDER 的 Inspector Mode 中查看. 在搜索日志事件时, 用户可以指定一组过滤器. 匹配过滤器的事件会显示在 *cider-log* 缓冲区中. 此外, 一个日志 consumer 将被附加到 appender 以接收匹配搜索条件的日志事件, 在搜索命令发出之后. 一旦提交了新的搜索或 *cider-log* 缓冲区被杀死, 日志 appender 将被自动移除.
- 按键绑定
以下键绑定可用于与日志事件交互.
命令 键盘快捷键 描述 cider-log-eventC-c M-l e显示用于管理日志事件的 transient 菜单. cider-log-clear-event-bufferC-c M-l e c从日志事件缓冲区中清除所有事件. cider-log-inspect-eventC-c M-l e i在 CIDER Inspector 中显示日志事件. cider-log-print-eventC-c M-l e p在 *cider-log-event*缓冲区中漂亮地打印日志事件.cider-log-event-searchC-c M-l e s搜索日志事件并在 *cider-log*缓冲区中显示它们.
8.4.11. 日志过滤器
日志事件的过滤器可以附加到日志 appenders 和 consumers. 它们在搜索事件或将其流式传输到客户端时也生效. 如果选择了多个过滤器, 它们将使用逻辑 AND 条件进行组合. 可用以下过滤器:
| 过滤器 | 键盘快捷键 | 描述 |
|---|---|---|
end-time |
-e |
只包括在 end-time 之前发出的日志事件. |
exceptions |
-E |
只包括由 exceptions 列表中的异常引起的日志事件. |
level |
-l |
只包括日志级别高于 level 的日志事件. |
loggers |
-L |
只包括由 loggers 列表中的 logger 发出的日志事件. |
pattern |
-r |
只包括其消息匹配正则表达式 pattern 的日志事件. |
start-time |
-s |
只包括在 start-time 或之后发出的日志事件. |
threads |
-t |
只包括由 threads 列表中的线程发出的日志事件. |
8.4.12. 故障排除
- 确保日志库确实受 CIDER Log Mode 支持, 并且它在你的 classpath 上.
- 尝试
require日志库的 Logjam 命名空间, 例如(require 'logjam.framework.<jul|logback|timbre|> :reload)并确保它可以无误地加载. - Timbre 和 Encore 通常需要协同升级, 它们使用 "break versioning". 通常将 Timbre + Encore 保持在最新的稳定版本是很有用的.
8.5. Macroexpansion
在源缓冲区或 REPL 中, 在某个 form 之后输入 C-c C-m, 将在一个新缓冲区中显示该 form 的宏展开. 你将在宏展开缓冲区 (内部使用 cider-modeexpansion-mode):
| 键盘快捷键 | 描述 |
|---|---|
m |
对点处的 form 调用 macroexpand-1, 并用其展开式替换原始 form. 如果带前缀参数调用, 则使用 macroexpand 而不是 macroexpand-1. |
a |
对点处的 form 调用 clojure.walk/macroexpand-all, 并用其展开式替换原始 form. |
g |
重新执行上一次的宏展开, 并用新的展开式替换宏展开缓冲区的当前内容. |
C-/ |
撤销在宏展开缓冲区中执行的最后一次就地展开. |
u |
8.5.1. 配置
选项 cider-macroexpansion-display-namespaces 控制是否在宏展开缓冲区中显示命名空间. 它可以设置为以下之一:
qualified- Vars 在展开中是完全限定的.none- Vars 显示时没有命名空间限定.tidy~(默认) - ~:refer-ed 或在当前命名空间中定义的 Vars 以其简单名称显示, 来自其他命名空间的非引用 vars 使用该命名空间的别名 (如果已定义) 进行引用, 其他 vars 显示为完全限定.
选项 cider-macroexpansion-print-metadata 控制是否在宏展开缓冲区中打印 var 元数据. 默认设置为 nil.
8.6. Profiling
CIDER 有一个简单的内置 profiler, 你可以用它来快速测量单个函数的运行时间. 它类似于用 time 宏包装你的函数, 但它会记录每一次的计时并显示一个汇总的结果.
Profiling 与 benchmarking 不同. Benchmarking 更准确地告诉你代码执行了多长时间. 如果你需要准确的计时结果, 请使用像 Criterium 这样的专业 benchmarking 库. 如果你需要了解大部分时间花在了哪里, 请使用像 clj-async-profiler 这样的专业 profiler.
profiler 不支持 ClojureScript.
8.6.1. 用法
要开始使用 CIDER profiler, 选择你想要 profile 的 vars, 然后调用 M-x cider-profile-toggle (C-c C-= t). 默认情况下, 它作用于点处的符号, 但如果点下没有东西, 它会提示输入一个 var. 你也可以通过 cider-profile-ns-toggle (C-c C-= n) 标记命名空间中的所有函数进行 profiling.
然后, 求值一些使用这些 vars 的代码, 它们的调用将被自动 profile.
你可以使用 M-x cider-profile-summary (C-c C-= s) 显示收集到的 profiling 数据的报告.
8.6.2. 理解报告格式
Profiling 报告由 CIDER inspector 渲染. 一个典型的 profiling 报告看起来像这样:
| # | :name | :n | :mean | :std | :sum | :min | :max | :med | :samples |
|---|---|---|---|---|---|---|---|---|---|
| 0 | #'sample-ns/bar | 1000 | 3 us | ±14 us | 3 ms | 791 ns | 384 us | 2 us | [791 …] |
| 1 | #'sample-ns/baz | 1000 | 307 ns | ±710 ns | 307 us | 84 ns | 22 us | 250 ns | [84 …] |
| 2 | #'sample-ns/foo | 1000 | 7 us | ±18 us | 7 ms | 3 us | 495 us | 5 us | [2584 …] |
| 3 | #'sample-ns/qux | 1000 | 8 us | ±20 us | 8 ms | 3 us | 543 us | 5 us | [3125 …] |
让我们来解释一下所有列名:
:n: 样本数量.:mean: 在 fn 中花费的平均时间.:std: 标准差.:sum: 在 fn 中花费的总时间.:min: fn 的最小记录时间.:min: fn 的最大记录时间.:med: 中位数, 即第五十百分位数.:samples: 所有计时样本的列表. 你可以点击它在 inspector 中查看完整列表.
8.6.3. 按键绑定
| 命令 | 键盘快捷键 | 描述 |
|---|---|---|
cider-profile-toggle |
C-c C-= t |
切换 var 的 profiling. 默认为点处的 var. |
cider-profile-ns-toggle |
C-c C-= n |
切换当前 ns 的 profiling. |
cider-profile-summary |
C-c C-= s |
显示所有 vars 的 profiling 摘要. |
cider-profile-clear |
C-c C-= c |
清除 profiling 数据. |
8.7. Tracing
8.7.1. 追踪函数执行
你可以使用 C-c M-t v 来追踪提供给函数的参数以及函数产生的结果值. CIDER 会提示你输入你想要追踪的函数的名称, 默认为上一个顶层定义.
追踪
再次对同一个函数调用 C-c M-t v 将导致该函数被取消追踪.
你也可以使用 C-c M-t n 来对整个命名空间开启和关闭追踪.
9. 配置
9.1. Basic Configuration
就像 Emacs 本身一样, CIDER 的几乎每个部分都是可配置的. CIDER 的开发者们尝试实现了一些合理的默认设置, 应该能适用于大部分 Clojure 社区, 但我们知道没有一种“一刀切”的开发环境, 我们试图创建一些可以满足许多人不同偏好的自定义点. 这样, 你应该能让 CIDER 尽可能地为你所用.
本节不描述 CIDER 提供的所有可能的自定义, 但这里有一些最受欢迎的.
你可以使用命令 M-x customize-group RET cider 查看每一个可自定义的配置选项.
9.1.1. 在 clojure-mode 缓冲区中禁用自动 cider-mode
默认情况下, CIDER 在建立第一个 CIDER 连接后, 会在所有 clojure-mode 缓冲区中启用 cider-mode. 它还会添加一个 clojure-mode 钩子, 以在新创建的 clojure-mode 缓冲区上启用 cider-mode. 然而, 你可以覆盖此行为:
(setq cider-auto-mode nil)
9.1.2. 提示符号确认
默认值在 CIDER 1.0 中已更改.
默认情况下, 当 CIDER 执行需要符号的交互式命令 (例如 cider-doc) 时, 它不会提示你输入符号. 此类命令作用于点处的符号, 如果无法自动获取符号, 则提示你提供一个符号.
如果你将 cider-prompt-for-symbol 设置为 t, 此行为将被反转, CIDER 将始终提示你确认命令将要操作的符号. 此行为很有用, 因为它允许你在执行某个操作之前编辑推断出的符号 (并且你可以看到 cider-symbol-at-point 推断出了什么).
(setq cider-prompt-for-symbol t)
许多作用于点处符号的交互式命令, 接受一个前缀参数, 该参数会为当前命令调用翻转通过 cider-prompt-for-symbol 配置的行为.
9.1.3. 控制跳转到定义时使用哪个窗口
默认情况下, M-. 和其他跳转到定义的命令具有以下行为:
- 如果定义缓冲区可见, 只需切换到它.
- 否则, 使用当前窗口显示定义.
其他行为也是可能的, 并由 cider-jump-to-pop-to-buffer-actions 控制; 该值作为 action 参数传递给 pop-to-buffer.
默认值为 ((display-buffer-reuse-window display-buffer-same-window)).
有些人可能更喜欢总是在当前窗口中显示定义. 以下是如何实现这一点:
(setq cider-jump-to-pop-to-buffer-actions '((display-buffer-same-window)))
请记住, 这可能会导致一些特殊缓冲区 (例如, 测试报告缓冲区) 出现问题, 因为当你尝试导航到定义时, 这会覆盖特殊缓冲区.
关于其他可能性, 请参见 display-buffer 的文档.
- 示例 1
当
core.clj未在当前框架的另一个窗口中显示时, 你跳转到core.clj中的map.使用默认行为和上面定义的替代行为,
map的定义将显示在当前窗口中. - 示例 2
当
core.clj正在当前框架的另一个窗口中显示时, 你跳转到core.clj中的map.使用默认行为,
map的定义将显示在当前窗口中; 你现在将有两个窗口显示core.clj, 并且现有的core.clj窗口将保持不变.使用上面定义的替代行为,
map的定义将显示在现有的core.clj窗口中; 所有窗口将显示与跳转前相同的缓冲区, 并且当前窗口现在将是显示core.clj的那个.
9.1.4. Minibuffer 补全
开箱即用, CIDER 使用标准的 completing-read Emacs 机制. 虽然它不花哨, 但它肯定能完成工作 (只需按 TAB). 然而, 如果你愿意, 有办法改进标准补全.
9.1.5. 连接时显示的消息
默认情况下, CIDER 在建立新连接时会显示一条鼓舞人心的消息. 此行为可通过 cider-connection-message-fn 进行配置:
;; 让消息更具教育意义 (setq cider-connection-message-fn #'cider-random-tip) ;; 完全禁用这条额外的消息 (setq cider-connection-message-fn nil)
默认消息存储在变量 cider-words-of-inspiration 中, 你可以轻松地自己调整:
(add-to-list 'cider-words-of-inspiration "Moar inspiration!")
当然, 不用说, 你也可以对 cider-tips 做同样的事情.
这可能是 CIDER 最重要的功能之一. 禁用那些惊人的消息, 风险自负!
9.1.6. 记录 nREPL 通信
如果你想查看 CIDER 和 nREPL 服务器之间的所有通信:
(setq nrepl-log-messages t)
CIDER 然后会为每个连接创建名为 *nrepl-messages conn-name* 的缓冲区.
通信日志对于调试 CIDER-to-nREPL 问题非常有价值, 我们建议你在遇到此类问题时启用它.
9.1.7. 隐藏特殊的 nREPL 缓冲区
如果你发现 *nrepl-connection* 和 *nrepl-server* 缓冲区使你的开发环境变得混乱, 你可以禁止它们出现在某些缓冲区切换命令中, 如 switch-to-buffer~(~C-x b):
(setq nrepl-hide-special-buffers t)
如果你需要在 switch-to-buffer 中让隐藏的缓冲区出现, 请在发出命令后输入 SPC. 隐藏的缓冲区将始终在 list-buffers (C-x C-b) 中可见.
9.1.8. 优先使用本地资源而非远程资源
要优先使用本地资源而非远程资源 (tramp), 当两者都可用时:
(setq cider-prefer-local-resources t)
9.1.9. 翻译文件路径
如果你在 Docker 镜像中运行 Clojure, 或者做类似的事情 (即, 你正在 cider-connect 到一个进程, 并且你的源路径有一个目录映射), 你通常需要设置 cider-path-translations 以便 jump-to-definition 正常工作. 例如, 假设你的应用在一个 docker 容器中运行, 你的源目录作为卷挂载在那里. 你从 nREPL 获得的导航路径将是相对于 docker 容器中的源, 而不是你主机上的正确路径. 你可以通过设置以下内容轻松添加翻译映射 (通常在 .dir-locals.el 中):
((nil (cider-path-translations . (("/root/.m2" . "/Users/foo/.m2") ("/src/" . "/Users/foo/projects")))))
每个条目都将被解释为一个目录条目, 所以末尾的斜杠是可选的. 定义导航将尝试翻译这些位置, 如果它们存在, 则导航到那里, 而不是报告文件不存在. 在上面的例子中, .m2 目录挂载在 /root/.m2, 源目录挂载在 /src. 这些翻译会将这些位置映射回用户的计算机, 以便定义导航可以工作.
使用 eval 伪变量, 你可以使翻译动态化, 从而可以在具有不同配置的开发团队之间共享 .dir-locals.el.
((nil . ((eval . (customize-set-variable 'cider-path-translations (list (cons "/src" (clojure-project-dir)) (cons "/root/.m2" (concat (getenv "HOME") "/.m2"))))))))
在这个例子中, 路径 /src 将被翻译成你主机上 Clojure 项目的正确路径. 而 /root/.m2 将被翻译成主机的 ~/.m2 文件夹.
你需要在主机上运行 lein deps (或 clojure -P 等), 至少一次, 然后最好在每次 Maven 依赖项更改时都运行一次, 以便导航能够完全工作. 这使得 cider-path-translations 的 .m2 部分真正有用.
如果你不能或不想这样做, 你可以使用 TRAMP 的功能 (CIDER 支持) 来代替设置 cider-path-translations. 为此, 你通常需要在你的 Docker 镜像中设置一个 SSH 守护进程.
9.1.10. 在某些与命名空间相关的命令中过滤掉命名空间
你可以通过自定义变量 cider-filter-regexps 来从 cider-browse-ns* 和 cider-apropos* 命令中隐藏所有 nREPL middleware 的细节. 这个变量的值应该是一个正则表达式列表, 匹配你想要过滤掉的命名空间的模式.
其默认值为 '("^cider.nrepl" "^refactor-nrepl" "^nrepl"), 这是最常用的 middleware 集合/包.
一个重要的事情是, 这个正则表达式列表会不经任何预处理就传递给 middleware. 所以, 正则表达式必须是 Clojure 格式 (反斜杠数量是两倍), 而不是 Emacs Lisp. 例如, 要达到上述效果, 你也可以将 cider-filter-regexps 设置为 '(".*nrepl").
要自定义 cider-filter-regexps, 你可以使用 Emacs 的自定义 UI, M-x customize-variable RET cider-filter-regexps.
另一种方法是在其他 CIDER 配置中设置该变量.
(setq cider-filter-regexps '(".*nrepl"))
9.1.11. 在特殊缓冲区中截断长行
默认情况下, CIDER 的特殊缓冲区 (如 *cider-test-report* 或 *cider-doc*) 的内容是行截断的. 你可以将 cider-special-mode-truncate-lines 设置为 nil, 使这些缓冲区使用单词换行而不是行截断.
(setq cider-special-mode-truncate-lines nil)
这个变量应该在加载 CIDER 之前设置 (这意味着在 require 它或自动加载它之前).
9.1.12. nREPL 连接钩子
CIDER 提供了 cider-connected-hook 和 cider-disconnected-hook 钩子, 分别在 nREPL 连接建立或关闭时触发.
以下是 CIDER 内部如何使用第一个钩子在连接时显示其著名的鼓舞人心的消息:
(defun cider--maybe-inspire-on-connect () "Display an inspiration connection message." (when cider-connection-message-fn (message "Connected! %s" (funcall cider-connection-message-fn)))) (add-hook 'cider-connected-hook #'cider--maybe-inspire-on-connect)
还有更底层的 nrepl-connected-hook 和 nrepl-disconnected-hook, CIDER 内部使用它们. 大多数时候, 最终用户最好使用 CIDER 级别的钩子.
9.2. Syntax highlighting
clojure-mode 为 Clojure(Script) 代码提供了基本的语法高亮, 但 CIDER 在此基础上做了几方面的增强. clojure-mode 的最大局限性在于, 其语法高亮是基于使用正则表达式来确定各种标识符 (例如, 常量, 宏, 类型等) 的语法类别. 然而, CIDER 可以从已经加载到 nREPL 的代码中获取所有关于标识符的数据, 因此它可以提供更丰富, 更准确的语法高亮. 我们称这种功能为“动态语法高亮” (与你从 clojure-mode 获得的某种程度上静态的语法高亮相对).
9.2.1. 动态语法高亮
Emacs 中“语法高亮”的术语是“font-locking”. 这就是为什么所有与语法高亮相关的配置变量的名称中都包含“font-locking”.
CIDER 可以对已知已定义的符号进行语法高亮. 默认情况下, 这是针对 clojure.core 和 cljs.core 命名空间中的符号, 以及任何命名空间中的宏. 如果你希望 CIDER 也对任何命名空间中的函数和变量的用法进行着色, 请执行:
(setq cider-font-lock-dynamically '(macro core function var))
这是没有动态语法高亮的代码的样子.
动态 Font-lock 关闭
这是打开它时代码的样子.
动态 Font-lock 开启
你可以参考 cider-font-lock-dynamically 的 Elisp 文档以获取更多详情.
9.2.2. reader conditionals 的语法高亮
默认情况下, CIDER 会根据缓冲区的 CIDER 连接类型对未使用的 reader conditional 表达式应用 font-locking.
Reader Conditionals
你可以通过调整 cider-font-lock-reader-conditionals 来禁用此行为:
(setq cider-font-lock-reader-conditionals nil)
9.2.3. 自定义 CIDER faces
CIDER 定义了一些你可能想要调整的自定义 faces (尽管通常你的颜色主题应该会处理它们):
cider-deprecated-face- 用于语法高亮已弃用的 varscider-instrumented-face- 用于语法高亮已为调试插桩的 varscider-traced-face- 用于语法高亮已追踪和分析的 varscider-reader-conditional-face- 用于语法高亮非活动的 reader conditional 分支
9.3. Indentation
CIDER 依赖于 clojure-mode 来进行 Clojure 代码的缩进. 虽然 clojure-mode 通常会以“正确”的方式缩进代码, 但有时你可能想教它如何缩进某些宏.
有两种方法可以做到这一点——你可以在你的 Emacs 配置中添加一些缩进配置, 或者你可以将其添加到你的 Clojure 代码中, 然后让 CIDER 自动为 clojure-mode 生成必要的配置. 我们将第一种方法称为“静态缩进”, 第二种方法称为“动态缩进”.
9.3.1. 静态缩进
本节中的所有内容即使在 CIDER 不存在/不活动的情况下也会按描述工作. 静态缩进不需要 REPL 即可工作.
clojure-mode 足够智能, 可以开箱即用地正确缩进大多数 Clojure 代码, 但它无法知道某个东西是否是一个宏, 其主体应该以不同的方式缩进.
clojure-mode 在缩进配置方面非常灵活, 在这里我们将介绍基础知识.
- 缩进模式
有几种常见的缩进 Clojure 代码的方式,
clojure-mode都支持.函数形式的缩进由变量
clojure-indent-style配置. 它有三个可能的值:always-align(默认)
(some-function 10 1 2) (some-function 10 1 2)
always-indent
(some-function 10 1 2) (some-function 10 1 2)
align-arguments
(some-function 10 1 2) (some-function 10 1 2)
你可以在这里阅读更多关于
clojure-mode中缩进选项的信息. - 宏缩进
如前所述,
clojure-mode无法知道你代码中的某个东西是否是一个宏, 需要与常规函数调用以不同的方式缩进 (很可能是因为该宏接受一些 forms 作为参数). 在这种情况下, 你需要教clojure-mode如何缩进所讨论的宏. 考虑这个简单的例子:(defmacro with-in-str "[DOCSTRING]" {:style/indent 1} [s & body] ...cut for brevity...) ;; 目标缩进 (with-in-str str (foo) (bar) (baz))
要让
clojure-mode正确地缩进它, 你需要在你的 Emacs 配置中添加以下代码:(put-clojure-indent 'with-in-str 1) ;; or (define-clojure-indent (with-in-str 1)
你可以在这里找到更多详情.
9.3.2. 动态缩进
宏通常需要特殊的缩进机制. 这在以 do, def 或 with- 开头的宏中最为常见. CIDER 有一些启发式方法来检测这些宏, 但它也允许你明确指定宏应该如何缩进.
这里有一个简单的例子, 展示了某人如何为他们编写的宏指定缩进规范 (使用 core 中的一个例子):
(defmacro with-in-str "[DOCSTRING]" {:style/indent 1} [s & body] ...cut for brevity...) ;; 目标缩进 (with-in-str str (foo) (bar) (baz))
这是一个更复杂的例子:
(defmacro letfn "[DOCSTRING]" {:style/indent [1 [[:defn]] :form]} [fnspecs & body] ...cut for brevity...) ;; 目标缩进 (letfn [(six-times [y] (* (twice y) 3)) (twice [x] (* x 2))] (println "Twice 15 =" (twice 15)) (println "Six times 15 =" (six-times 15)))
如果这看起来令人生畏, 不用担心. 对于大多数宏, 缩进规范应该只是一个数字, 或者是关键字 :defn 或 :form 之一. 规范的完整描述在手册的缩进规范部分提供.
如果你不想使用这个功能, 你可以在你的 Emacs 初始化文件中将 cider-dynamic-indentation 设置为 nil 来禁用它.
(setq cider-dynamic-indentation nil)
9.4. Eldoc
9.4.1. ElDoc
Eldoc 是一个缓冲区本地的 minor mode, 有助于查找 Lisp 文档. 当它启用时, 每当点处有一个 Lisp 函数或变量时, 回显区就会显示一些有用的信息; 对于函数, 它显示参数列表, 对于变量, 它显示变量文档字符串的第一行.
CIDER 为 ElDoc 提供了一个 Clojure 后端, 只要 eldoc-mode 启用, 它就可以开箱即用.
Eldoc
9.4.2. 启用 ElDoc
global-eldoc-mode 在 Emacs 25.1 中默认是启用的, 所以你真的不需要做任何事情来启用它.
它将在源缓冲区和 REPL 缓冲区中都起作用.
9.4.3. 配置 ElDoc
- 显示点处符号的 ElDoc
通常你会看到包含函数/宏/特殊形式 (相对于你的光标位置) 的 eldoc.
CIDER 也会显示点处符号的 eldoc. 所以在
(map inc ...)中, 当光标在inc上时, 它的 eldoc 将被显示. 你可以关闭此行为:(setq cider-eldoc-display-for-symbol-at-point nil)
- 处理长 ElDoc
CIDER 在 minibuffer 中显示文档时会尊重
eldoc-echo-area-use-multiline-p的值. 你可以自定义这个变量来改变它的行为.eldoc-echo-area-use-multiline-p行为 t从不尝试截断消息. 完整的符号名称和函数参数列表或变量文档将被显示, 即使回显区必须调整大小以适应. nil消息总是被截断以适应回显区中的单行显示. truncate-sym-name-if-fit或任何非 nil 值如果能使函数参数列表或文档字符串适应单行, 符号名称可能会被截断. 否则, 行为就像 t的情况一样. - 上下文相关的 ElDoc
如果变量
cider-eldoc-display-context-dependent-info不为nil, CIDER 会尝试根据当前上下文添加预期的函数参数 (例如, 对于datomic.api/q函数, 它会显示点处查询的预期输入):(setq cider-eldoc-display-context-dependent-info t)
9.4.4. 禁用 CIDER 的 ElDoc 函数
如果你正在将 CIDER 与 clojure-lsp 一起使用, 你可能更喜欢使用 clojure-lsp 来实现 ElDoc. 这意味着你必须从已注册的 ElDoc 函数列表中移除 CIDER 的 ElDoc 函数:
(remove-hook 'eldoc-documentation-functions #'cider-eldoc)
如果你正在处理大量提供某些 ElDoc 集成的包, 了解如何使用 ElDoc 合并多个文档源是个好主意. 简而言之——Emacs 28 添加了对多个文档后端的支持. 现在你可以同时阅读你的 linter 或编译器的错误消息并查看代码文档. 很酷的东西!
9.5. Project-specific Configuration
一个关于 CIDER 的很常见的问题是如何处理项目特定的配置. 想要这样做有很多原因, 但可能首先想到的是运行 Leiningen 时使用特定的 profile, 或者在使用 Clojure CLI (又名 tools.deps) 时向 jack-in 命令添加 "-A:fig".
如果你只是需要动态地编辑一个 jack-in 命令, 你最好用 C-u 作为命令的前缀 (例如, C-u C-c C-x j j), 这将允许你在 minibuffer 中编辑整个命令字符串.
CIDER 没有任何针对项目特定配置的特殊规定, 因为这在 Emacs 本身中得到了很好的支持. 不幸的是, Emacs 中的这个功能有一个稍微奇怪的名字“目录局部变量”, 这可能不是人们会开始谷歌搜索的东西. 好的一面是——Emacs 的功能比处理项目特定配置要通用得多.
非常简单地说, 你所需要做的就是在你的项目根目录下创建一个名为 .dir-locals.el 的文件, 它应该看起来像这样:
((clojurescript-mode (cider-clojure-cli-aliases . ":fig") (eval . (cider-register-cljs-repl-type 'super-cljs "(do (foo) (bar))")) (cider-default-cljs-repl . super-cljs)))
该文件的结构是一个主模式和一些需要在其中设置的变量的映射. 由于 CIDER 不是一个主模式, 大多数时候你可能会在 clojure-mode 或 clojurescript-mode 中设置变量. 请注意, clojurescript-mode 派生自 clojure-mode, 所以任何适用于 clojure-mode 的东西也适用于 clojurescript-mode. 你也可以通过使用 eval 作为变量名来求值代码, 但这在实践中你很少需要.
通常, 你只需手动创建 .dir-locals.el 并像编辑任何其他 Emacs Lisp 代码一样编辑它. 然而, 如果你对其语法感到不知所措, 你可以简单地执行 M-x add-dir-local-variable, 然后你就可以交互式地选择主模式, 变量及其值. 这种方法的一个小问题是, 生成的 .dir-local.el 将在当前目录中创建, 这可能会根据你正在做的事情而成为一个问题. Projectile 的用户可以利用项目感知的 projectile-edit-dir-locals 命令来代替.
这是一个稍微复杂一点的 .dir-locals.el:
((emacs-lisp-mode (bug-reference-url-format . "https://github.com/clojure-emacs/cider/issues/%s") (bug-reference-bug-regexp . "#\\(?2:[[:digit:]]+\\)") (indent-tabs-mode . nil) (fill-column . 80) (sentence-end-double-space . t) (emacs-lisp-docstring-fill-column . 75) (checkdoc-symbol-words . ("top-level" "major-mode" "macroexpand-all" "print-level" "print-length")) (checkdoc-package-keywords-flag) (checkdoc-arguments-in-order-flag) (checkdoc-verb-check-experimental-flag) (elisp-lint-indent-specs . ((if-let* . 2) (when-let* . 1) (let* . defun) (nrepl-dbind-response . 2) (cider-save-marker . 1) (cider-propertize-region . 1) (cider-map-repls . 1) (cider--jack-in . 1) (cider--make-result-overlay . 1) ;; need better solution for indenting cl-flet bindings (insert-label . defun) ;; cl-flet (insert-align-label . defun) ;; cl-flet (insert-rect . defun) ;; cl-flet (cl-defun . 2) (with-parsed-tramp-file-name . 2) (thread-first . 1) (thread-last . 1)))))
你猜到这是什么了吗? 这是 CIDER 自己的 .dir-locals.el, 它确保所有在 Elisp 代码库上工作的的人都会使用一些共同的代码风格设置. 这就是为什么所有东西都被限定在 emacs-lisp-mode 中.
对于一个以 Clojure 为中心的例子, 让我们看看 cider-nrepl 的 .dir-locals.el:
((clojure-mode (clojure-indent-style . :always-align) (indent-tabs-mode . nil) (fill-column . 80)))
这里的重点是确保每个在 Clojure 代码库上使用 Emacs 的人都会共享相同的代码风格设置.
通常在野外你会看到 dir-local 条目中主模式为 nil. 这个看起来很奇怪的符号只是意味着那里指定的配置将应用于每个缓冲区, 而不管其主模式如何. 请谨慎使用这种方法, 因为很少有充分的理由这样做.
另一件要记住的事情是, 你可以在你的项目中拥有多个 .dir-locals.el 文件. 它们的总体效果将是累积的, 最里面的文件对它下面的目录中的任何文件都具有优先权. 我在实践中从未需要这个, 但我可以想象它对那些在一个 mono repo 中有多个项目的人, 或者对“真实”代码及其测试应用不同约定的人很有用.
你可能想知道对 .dir-locals.el 的更改何时会反映在受它们影响的 Emacs 缓冲区中. 这个问题的答案是“当缓冲区被创建时”. 如果你在 .dir-locals.el 中更改了某些东西, 你通常需要重新创建相关的缓冲区. 或者你可以用黑客的方式, 应用一点 Elisp 魔法.
dir-locals 还有更多方面, 但它们超出了本文的范围. 如果你对所有细节都感兴趣, 你应该查看官方的 Emacs dir-locals 文档.
10. 缩进规范
CIDER 依赖附加在 var 定义上的元数据来指定如何缩进宏. 具体来说, CIDER 会查找元数据中的 :style/indent 键. 该键的值必须是一个缩进规范 (indent spec). 缩进规范可以是数字, 关键字, 向量或列表.
10.1. 数字
数字 n 表示前 n 个参数对齐, 其余的缩进. 例如, 如果你将缩进规范设置为 2, 这意味着第一个参数与宏在同一行, 第二个参数在第一个参数下方对齐. 然后, 所有后续的 forms 都会缩进. 这对于第二个参数是绑定形式的宏很常见.
(defmacro my-macro {:style/indent 2} [arg1 bindings & body] ...) ;; 目标缩进 (my-macro "arg1" [a 1 b 2] (do-something-with a) (do-something-with b))
这是最常见的使用情况.
10.2. 关键字
关键字可以是 :defn 或 :form.
:defn 关键字用于主体类似 defn 的宏. 换句话说, 它们有一个名称, 一个可选的文档字符串, 一个可选的属性 map, 以及一个作为主体的 forms 列表. 例如:
(defmacro my-defn {:style/indent :defn} [name docstring? attr-map? & body] ...) ;; 目标缩进 (my-defn my-function "A docstring." [a b] (do-something-with a) (do-something-with b))
:form 关键字用于像普通函数一样缩进的宏. 换句话说, 所有参数都对齐. 例如:
(defmacro my-macro {:style/indent :form} [& args] ...) ;; 目标缩进 (my-macro arg1 arg2 arg3)
10.3. 向量
向量缩进规范允许你对宏的不同参数列表应用不同的缩进规范. 最常见的用例是对于具有多个 arity 的宏, 例如 defn. 规范看起来如下:
[<arity-1-spec> <arity-2-spec> ... <fallback-spec>]
一个例子应该能让这更清楚. 以下是如何缩进 core.async 的 go 宏的简单版本:
(defmacro go {:style/indent [1 :form]} [& body] ...) ;; 目标缩进 (go (do-stuff)) (go (do-stuff) (do-other-stuff))
使用向量规范, CIDER 将根据传递给宏的参数数量选择一个缩进规范. 如果参数数量超出范围, CIDER 将使用列表中的最后一个规范. 因此, 向量中的最后一个规范作为后备.
10.4. 列表
对于最复杂的情况, 你可以提供一个列表作为缩进规范. 列表的元素是以下三种之一:
- 一个数字
- 一个关键字 (
:defn或:form) - 一个形式为
[[:<keyword>]]的列表, 其中<keyword>是:defn,:form或:align之一
该列表指定了如何缩进宏的每个参数. 数字指定了作为块缩进的参数数量. 关键字指定了接下来的参数应该如何缩进. 关键字列表指定了本身就是列表的参数如何缩进. :align 关键字仅在此上下文中可用.
这是一个稍微简化版的 letfn 的缩进规范:
(defmacro letfn {:style/indent [1 [[:defn]]]} [fnspecs & body] ...) ;; 目标缩进 (letfn [(foo [a] (bar a)) (bar [b] (foo b))] (foo 1))
这个规范意味着第一个参数应该作为一个块来缩进, 而接下来的参数, 它是一个列表的列表, 应该像每个子列表都是一个 defn form 一样来缩进.
更多例子, 你可以查看 clojure-mode 的测试套件.
11. 已知问题
尽管 CIDER 非常出色, 但它仍远非完美, 并且有其自身的局限性和已知问题. 其中一些是根本性的, 不太可能被修复 (例如, 它们源于 Emacs 本身的限制), 另一些则只是在等待一个勇敢的灵魂站出来修复它们.
11.1. 通用
如果你使用的是旧版本的 Emacs (例如 24.3), 处理大型项目可能会很慢. Emacs 24.4 引入了一个原生的 Elisp 编译器, 这显著提高了性能. Emacs 25.1 引入了对动态模块和线程执行的支持, 这有可能在未来显著改善情况 (通过将一些繁重的处理卸载到后台线程).
通常我们建议用户使用较新的 Emacs 版本.
11.2. Clojure
目前, CIDER 不支持自托管的 Clojure 实现. 原因是目前没有可用的自托管版本的 nREPL (用 Clojure 实现).
另一个不支持的 REPL 是 Rhino. 在 Piggieback 中支持它需要很多丑陋的 hack, 最终我们决定没有 Rhino 会更好. 鉴于今天有大量更好的解决方案, 我怀疑没有人会怀念 Rhino.
11.3. ClojureScript
CIDER 目前不支持自托管的 ClojureScript 实现. 原因是目前没有可用的自托管版本的 nREPL (用 ClojureScript 实现).
CIDER 的许多高级功能 (例如调试器, 测试运行器等) 不适用于 ClojureScript. 随着时间的推移, 这种情况可能会改善, 但我们没有具体的路线图.
11.4. nREPL
CIDER 需要 nREPL 0.4.0+ 才能正常工作. 大多数构建工具 (例如 Leiningen 的新版本) 会提供足够新的 nREPL 版本, 所以大多数时候你不需要担心这个问题.
如果你正在使用一些依赖于古老 nREPL 版本的旧项目, CIDER 在连接时会警告你, 你需要自己升级项目的 nREPL 依赖. 请记住, cider-jack-in 总是会注入它支持的最新 nREPL 版本, 所以这个问题只与 cider-connect 用户相关.
话虽如此, 即使是古老的 nREPL 版本, CIDER 在某种程度上也可能工作——你只是无法使用 nREPL 近期版本中添加的任何功能.
12. 问题排查
在本节中, 你将找到一些用于排查 CIDER 常见问题的技巧.
12.1. 通用建议
在报告问题之前, 始终确保你正在运行 CIDER 的最新稳定版本. MELPA 的快照构建不保证稳定, 你应自担风险使用它们.
在报告新问题之前, 检查 CIDER 的问题跟踪器上是否存在类似问题也是一个好主意.
有时问题可能是由某些第三方包引起的. 运行 M-x cider-report-bug 将生成一个 bug 报告缓冲区, 其中包含 CIDER 开发人员检查你的设置所需的所有信息. 你可以尝试在一个干净的设置中重现问题, 以缩小问题的原因. 你可以像这样启动一个干净的 Emacs 会话:
$ emacs -Q
然后你需要手动加载 CIDER:
M-x package-initialize RET
M-x cider-jack-in RET
你还应该检查 *Messages* 缓冲区, 因为它通常包含有关哪里出错了的有用信息. 如果你正在连接到远程 nREPL 服务器, 你也应该检查它的日志. 你还可以启用 CIDER 和 nREPL 服务器之间 nREPL 通信的日志记录.
最后, 你可以在 Clojurians Slack 的 #cider 频道寻求帮助.
12.2. cider-jack-in 挂起或失败
cider-jack-in 挂起最常见的原因是 CIDER 调用来启动 nREPL 服务器的构建工具 (例如 Leiningen) 存在问题. CIDER 在 minibuffer 中显示它正在运行的确切命令, 所以你可以复制它并在终端中尝试运行. 这通常会揭示一些在 Emacs 内部运行命令时不明显的问题.
Windows 用户, 特别是那些使用 Cygwin 或 msys2 的用户, 另一个常见的问题是他们的 PATH 上有一个 sh.exe 可执行文件. 这可能会迷惑 Emacs, 并且它在调用外部命令时可能会尝试使用它. 由于 Windows 命令通常与 sh 不兼容, 你可能会遇到奇怪的错误或挂起. 解决方法很简单——确保你的 Windows PATH 中没有 sh.exe.
你还应该检查 CIDER 是否配置为为你的项目使用正确的构建工具.
有时 CIDER 可能会因为某个依赖项的问题而挂起. 这里的常见做法是尝试将你的依赖项更新到它们最新的稳定版本.
你的项目可能指定了一个对于 CIDER 来说太旧的 nREPL 版本. jack-in 过程应该会自动注入一个更新版本的 nREPL, 但这可以被禁用.
如果你仍然遇到问题, 你应该提交一个 bug 报告, 并提供 CIDER 正在运行的确切命令及其输出.
12.3. cider-connect 失败
cider-connect 比 cider-jack-in 简单得多, 出错的可能性也更小. 大多数情况下, 连接尝试失败的原因如下:
- 你尝试连接的主机/端口是错误的. 请记住,
0.0.0.0不是一个有效的连接地址. 你应该使用localhost或127.0.0.1. - 你和 nREPL 服务器之间有防火墙.
- 你正在尝试连接到一个普通的 nREPL 服务器, 但你使用的是
cider-connect-cljs. - CIDER 由于某种原因无法建立稳定的连接. nREPL 服务器可能负载过重, 或者 CIDER 和服务器之间可能存在一些网络问题. 有时这可能导致部分连接 (例如, 你得到了一个 REPL, 但大多数 CIDER 功能不工作). 你可以尝试连接几次, 看看这是否只是一个暂时的问题.
- 你正在从 Windows 上的 CIDER 连接到 WSL1 上的 nREPL, 并且使用的是
localhost. 你需要改用你的 WSL1 实例的 IP 地址. 这可以在你的 WSL 终端中使用ifconfig找到. 它可能会以172开头. 注意: 使用 WSL2 时不存在此问题.
12.4. 找不到匹配的 cljs REPL
当你使用 cider-connect-cljs 连接到 shadow-cljs REPL 时, 可能会发生这种情况. CLJS REPL 仅在构建编译后才可用. 在你的终端中, 你可以运行 shadow-cljs watch app, 例如, 开始监视和编译 app 构建. 一旦准备就绪, 你就可以连接到它.
12.5. 我看到 Unsupported nREPL functionality 警告
这意味着你连接到了一个没有加载 cider-nrepl middleware 的 nREPL 服务器. 通常, 当你对一个没有以必要依赖项启动的服务器执行 cider-connect 时, 就会发生这种情况. 你可以在这里了解更多关于设置 cider-nrepl 的信息.
你会得到一些基本的 CIDER 功能 (例如 REPL), 但大多数高级功能 (例如调试器, 代码补全等) 将不可用.
12.6. 快捷键不工作
这通常意味着 cider-mode 在某个 Clojure 缓冲区中没有启用. 你可以用 M-x cider-mode 手动启用它.
如果问题与 REPL 相关, 请确保你在 REPL 缓冲区中, 并且它正在运行 cider-repl-mode.
12.7. 代码补全不工作
首先, 检查你是否正确设置了 cider-nrepl. 没有它, 你只能得到非常基本的 Clojure 代码补全 (并且没有 ClojureScript 补全).
如果你已经设置好了, 检查你是否已经求值 (加载) 了你试图补全的代码. CIDER 的补全功能适用于已在运行的 REPL 进程中加载的代码.
12.8. 性能不佳
CIDER 做了很多事情, 其中一些可能会很慢 (尤其是在大项目上). 通常你可以通过禁用一些你不需要的功能来提高性能. 这里有一些可以尝试的方法:
(setq cider-font-lock-dynamically nil) (setq cider-eldoc-display-for-symbol-at-point nil) (setq cider-use-tooltips nil) (setq cider-repl-use-clojure-font-lock nil) (setq nrepl-log-messages nil)
还值得注意的是, 一些旧版本的 Emacs 在 Elisp 执行方面明显比新版本慢. 我们强烈建议使用 Emacs 25.1 或更新版本.
13. FAQ
13.1. 为什么这个项目叫 CIDER?
这是一个关于用苹果制成的饮料的书呆子引用. 毕竟, Clojure 的 logo 是苹果的组合. 有人猜测 CIDER 是 "Clojure(Script) Interactive Development Environment that Rocks!" 的首字母缩写, 但这只是我们后来想出的一个逆向首字母缩写. 名字就是 CIDER.
顺便说一下, CIDER 应该用大写字母书写, 因为它是一个专有名词.
13.2. CIDER 是 SLIME 的一个分支吗?
不. CIDER 有自己的 Elisp 代码库, 是从头开始编写的. CIDER 利用 nREPL.el 作为传输, 并且不与 SLIME 共享任何代码.
话虽如此, CIDER 受到了 SLIME 的很大启发, 最初我们试图模仿 SLIME 的大部分功能及其整体用户体验. 随着项目的发展, 我们偏离了简单地成为 SLIME 克隆的道路, 如今 CIDER 在许多方面与 SLIME 大不相同.
13.3. 为什么 CIDER 不使用官方的 Clojure REPL?
官方 REPL 功能不是很强大, 也不适合编程使用 (例如, 从中提取代码补全候选项, 文档等并不容易).
这就是为什么大多数 Clojure 开发工具都是基于 nREPL 构建的.
13.4. 为什么 CIDER 不使用 tooling REPL?
简短的回答是——我们先来的!
长篇的回答是, 当我们开始 CIDER 时 (早在 2012 年), 在为 Clojure 构建开发工具方面, nREPL 是唯一的选择. 所谓的 "tooling" REPL 出现得晚得多, 而此时 CIDER 已经是一个成熟的项目了.
虽然我们曾不时考虑切换到 tooling REPL, 但我们从未这样做, 因为:
- nREPL 比 tooling REPL 更灵活, 更强大. 用自定义中间件扩展 nREPL 比向 tooling REPL 添加新功能要容易得多.
- nREPL 比 tooling REPL 更受欢迎, 并且拥有更大的社区. 这意味着有更多与 nREPL 兼容的库和工具.
- tooling REPL 并不提供任何我们用 nREPL 做不到的东西.
- 我们需要重写大量代码才能切换到 tooling REPL.
- 我们认为, 为社区拥有一个单一的工具标准更好. 维护一个 REPL 已经够难了, 更不用说两个了. nREPL 的维护者 (包括 CIDER 的维护者) 一直在努力使 nREPL 成为 Clojure 工具的最佳基础, 我们希望最终 tooling REPL 会被淘汰.
你可以在这里和这里了解更多关于这个话题的信息.
13.5. 为什么 CIDER 对 nREPL 有自己的依赖?
nREPL 是 CIDER 的一个基本组成部分, 我们需要确保我们使用的版本与我们使用的 CIDER 版本兼容.
这种依赖关系也允许我们直接使用 nREPL 的 API, 而不必通过一个单独的进程. 这对于性能和实现 CIDER 的某些功能很重要.
最后, 通过捆绑 nREPL, 我们让用户更容易开始使用 CIDER, 因为他们不必担心自己安装和配置 nREPL. 这就是 cider-jack-in 的全部意义.
13.6. 我可以和 Socket REPL 一起使用 CIDER 吗?
目前不行. 我们正在努力增加对它的支持, 但还没有准备好.
你可以在这里了解更多关于这个话题的信息.
13.7. 我可以在其他编辑器中使用 CIDER 吗?
CIDER 的 Emacs 部分是特定于编辑器的, 但大部分繁重的工作是由 cider-nrepl middleware 完成的, 它是与编辑器无关的. 其他编辑器可以 (并且确实) 使用 cider-nrepl 来提供与 CIDER 类似的体验.
更多详情请查看此部分.
13.8. 我可以在没有 cider-nrepl 的情况下使用 CIDER 吗?
可以, 但你会错过很多功能. 普通的 nREPL 服务器只提供基本功能 (例如, 代码求值和 REPL). 其他一切都由 cider-nrepl 和其他 middleware 提供.
13.9. 我可以在没有 Leiningen/Boot/~tools.deps~ 的情况下使用 CIDER 吗?
可以. CIDER 不关心你如何启动 nREPL 服务器. 你可以手动启动它, 或者使用任何你想要的其他工具.
话虽如此, 我们建议使用支持的工具之一, 因为它们使开始使用 CIDER 变得更容易. cider-jack-in 命令是为与它们一起工作而设计的, 它将为你节省大量时间和精力.
13.10. cider-jack-in 和 cider-connect 有什么区别?
cider-jack-in 启动一个新的 nREPL 服务器并连接到它. cider-connect 连接到一个现有的 nREPL 服务器.
通常, 当你在本地机器上处理一个项目时, 你会使用 cider-jack-in, 而当你连接到一个远程 nREPL 服务器 (例如, 在一个预发布服务器或 CI 机器上) 时, 你会使用 cider-connect. 当然, 你也可以使用 cider-connect 来连接到一个你手动启动的本地 nREPL 服务器.
13.11. 我可以在哪里寻求帮助?
寻求帮助的最佳地点是 Clojurians Slack 上的 #cider 频道. 你也可以在 Clojure 邮件列表中提问.
如果你认为你发现了一个 bug, 请在 CIDER 问题跟踪器上报告它. 请确保包含尽可能多的信息, 以便我们能够重现并修复该 bug.
14. Additional Packages
虽然 CIDER 本身提供了许多开箱即用的功能, 但安装一些额外的 Emacs 包来改善你的整体 Clojure 开发体验也是一个好主意. 在本节中, 我们将简要提及一些 CIDER 用户经常安装的最流行的包.
14.1. Projectile
Projectile 是 Emacs 的一个项目交互库. 它提供了许多用于处理项目文件的命令 (例如, 查找文件, grepping 等). 它还提供了一种运行项目特定命令的方法 (例如, 运行测试, 构建项目等).
CIDER 与 Projectile 有一些集成——例如, 当你运行 cider-jack-in 时, CIDER 会使用 Projectile 来查找你的项目根目录.
14.2. clj-refactor.el
clj-refactor.el 是 Clojure 的一系列重构命令. 它提供了许多有用的命令, 用于重命名符号, 提取函数等.
它是 CIDER 的一个很好的伴侣, 我们强烈推荐它.
14.3. paredit/smartparens
paredit 和 smartparens 是两个流行的用于 Lisp 代码结构化编辑的包. 它们提供了许多用于操作 s-expressions 的命令 (例如, wrapping, slurping, barfing 等).
如果你是 Lisp 新手, 你绝对应该看看它们. 它们会让你的生活轻松很多.
14.4. rainbow-delimiters
rainbow-delimiters 是一个根据嵌套级别高亮括号, 方括号和花括号的包. 这使得看代码结构变得容易得多.
14.5. aggressive-indent-mode
aggressive-indent-mode 是一个在你输入时自动缩进代码的包. 这是确保你的代码总是正确缩进的好方法.
15. Additional Resources
本节包含一个不属于 CIDER 文档的有用资源列表.
15.1. 截屏视频
这里有一些演示 CIDER 部分功能的截屏视频:
- Clojure Weekly: CIDER Tricks (2017年10月)
- Clojure Remote REPL with CIDER, Docker and Boot (2017年4月)
- Deep Dive into CIDER (2017年2月)
- Clojure Development Workflow with Spacemacs and CIDER (2016年12月)
- Clojure, Spacemacs & CIDER (2016年11月)
- Emacs & Clojure, A Lispy Love Affair (2016年8月)
- CIDER Debugger (2016年5月)
- ClojureScript Development with CIDER (2015年2月)
- CIDER Overview (2013年11月)
15.2. 演示文稿
这里有一些讨论 CIDER 及相关主题的演示文稿:
- CIDER Internals (2018)
- SLIME 2.0 (2014)
15.3. 文章
这里有一些讨论 CIDER 及相关主题的文章:
- Debugging Clojure with CIDER
- Clojure evaluation and namespace resolution in CIDER
- CIDER’s Journey to 1.0
- CIDER: The Journey so Far (up to 0.10)
- CIDER’s 0.9 Release
- CIDER’s 0.8 Release
- clojure-mode & CIDER Indentation
- Clojure(Script) Static Code Analysis with CIDER
- ClojureDocs integration in CIDER
15.4. 相关项目
这里有一些你可能会觉得有用的相关项目:
- CIDER’s nREPL middleware
- clojure-mode
- Projectile
- clj-refactor.el
- paredit
- smartparens
- rainbow-delimiters
- aggressive-indent-mode
16. 贡献
16.1. Documentation
我们热爱对 CIDER 文档的贡献. 事实上, 我们非常热爱它们, 以至于我们已经让为它做贡献变得超级容易.
文档是用 AsciiDoc 编写的, 并托管在 GitHub 上. 你可以在这里找到源代码.
要为文档做出贡献, 你只需 fork 该仓库, 做出你的更改, 然后提交一个 pull request.
当我们向 master 分支合并一个 pull request 时, 文档会自动部署到 CIDER 网站.
如果你不熟悉 AsciiDoc, 你可以查看这个指南.
如果你有任何问题, 可以在 Clojurians Slack 的 #cider 频道上提问.
16.2. Hacking on CIDER
那么, 你对 hack CIDER 感兴趣吗? 太棒了! 我们一直在寻找新的贡献者.
本节将为你快速介绍如何开始.
16.2.1. 先决条件
要 hack CIDER, 你需要安装以下软件:
- Emacs 27.1 或更新版本
- Cask
- Leiningen
16.2.2. 获取代码
你需要做的第一件事是克隆 CIDER 仓库:
$ git clone https://github.com/clojure-emacs/cider.git
然后, 你需要安装依赖项:
$ cd cider $ cask install
16.2.3. 构建 CIDER
要构建 CIDER, 你只需运行以下命令:
$ make
这将编译 Elisp 代码并生成 autoloads 文件.
16.2.4. 运行测试
要运行测试, 你只需运行以下命令:
$ make test
这将运行项目中的所有测试.
如果你想运行一个特定的测试, 你可以这样做:
$ make test-file TEST_FILE=test/cider/cider-test.el
你也可以在 Emacs 内部运行测试, 通过打开一个测试文件并运行 M-x ert.
16.2.5. 提交 Pull Request
当你准备好提交 pull request 时, 你应该确保你的代码格式正确, 并且所有的测试都通过了.
你还应该确保你的提交消息格式正确. 我们遵循 Conventional Commits 规范.
如果你有任何问题, 可以在 Clojurians Slack 的 #cider 频道上提问.
16.3. Funding
CIDER 是一个免费的开源项目, 而且永远都是. 然而, 我们确实需要一些资金来支付我们的开销 (例如域名, 托管等).
如果你是 CIDER 用户并且想支持我们的工作, 你可以通过以下方式之一来做到:
- 在 Open Collective 上成为支持者或赞助商.
- 在 PayPal 上进行一次性捐赠.
我们感谢任何及所有的支持