内容
# lisp-sitter
基于 [tree-sitter](https://tree-sitter.github.io/) 的 Emacs Lisp、Scheme 和 Common Lisp 结构化编辑命令行工具。
编码代理(Claude CLI、Cursor CLI 等)通常使用基于行的搜索/替换编辑文件。这在 Lisp 代码中很容易出错,因为结构很重要。**lisp-sitter** 以整个顶级表单为操作对象:按名称查找 `defun`/`define`,替换或插入完整的 s 表达式,在写入前验证。所有结构化操作都作为 **MCP 工具** 公开,以便于代理访问。
## 支持的语言
| 语言 | 扩展名 | 顶级表单 |
| --- | --- | --- |
| Emacs Lisp | `.el` | `defun`、`defmacro`、`defsubst`、`cl-defun`、`defvar`、`defconst`、`defcustom` |
| Common Lisp | `.lisp`、`.cl` | `defun`、`defmacro`、`defclass`、`defgeneric`、`defmethod` |
| Scheme | `.scm`、`.ss`、`.sld` | `define`、`define-syntax`、`define-library` |
语言根据文件扩展名推断。
## 安装
### Homebrew(macOS / Linux)
```bash
brew install etyurkin/tap/lisp-sitter
```
安装 `lisp-sitter` 二进制文件和 `lisp-sitter.el`(到 `$(brew --prefix)/share/emacs/site-lisp/lisp-sitter`)。
### 从源代码编译
需要 Rust 1.70+ 和 C 编译器(用于 tree-sitter 语法)。
```bash
git clone <仓库网址> lisp-sitter && cd lisp-sitter
make install # 构建发布版本,安装到 ~/.cargo/bin
```
或不使用 Make:
```bash
cargo install --path crates/lisp-sitter
```
## 快速入门
```bash
# 列出顶级表单(每个标签的行:列后缀)
lisp-sitter tree src/foo.el
# 命名表单的字节范围
lisp-sitter bounds src/foo.el my-function
# 打印表单的完整文本
lisp-sitter get src/foo.el my-function
# 替换表单(标准输出);添加 --write 保存
lisp-sitter replace src/foo.el my-function \
--body '(defun my-function () 42)' --write
# 在符号后、文件开头或结尾插入
lisp-sitter insert src/foo.el my-function \
--node '(defun helper () t)' --write
lisp-sitter insert new.scm __start__ --node '(define version 1)' --write
lisp-sitter insert lib.lisp __end__ --node '(defun tail () nil)' --write
# 完成缺失的括号
lisp-sitter complete --lang scheme --body '(define (fib n) (if (< n 2)'
# → (define (fib n) (if (< n 2) n ...))
# 重新缩进文件
lisp-sitter fmt src/foo.el --write
# 验证
lisp-sitter check src/foo.el
lisp-sitter check-node --lang scheme --body '(define x 1)'
```
### 锚点
| 锚点 | 含义 |
| --- | --- |
| `__start__` | 作为第一个表单插入(仅限空文件) |
| `__end__` | 在最后一个顶级表单后追加 |
| *符号* | 在命名表单后立即插入 |
### 标准输入
使用 `--body-file -` 或 `--node-file -` 从标准输入读取值:
```bash
echo '(defun foo (x)' | lisp-sitter complete --lang elisp --body-file -
# → (defun foo (x))
```
### 编辑工作流
获取表单,管道替换,然后格式化:
```bash
echo '(defun greet (name) (message "hi" name))' > greet.el
echo '(defun greet (name)\n(message "hello, %s" name))' | \
lisp-sitter replace greet.el greet --body-file - --write
lisp-sitter fmt greet.el --write
```
### Shell 补全
在终端中补全子命令:
```bash
eval "$(lisp-sitter completions bash)" # bash
eval "$(lisp-sitter completions zsh)" # zsh
lisp-sitter completions fish | source # fish
```
## 命令
| 命令 | 描述 |
| --- | --- |
| `tree PATH` | 顶级定义的轮廓,每行一个(`defun:foo@12:1`)。 `--all` 还列出非定义表单(`require`、`provide`、`setq`,...) |
| `bounds PATH SYMBOL` | 命名表单的字节位置 `START:END` |
| `get PATH SYMBOL` | 打印命名顶级表单的完整文本 |
| `replace PATH SYMBOL` | 替换表单;需要 `--body` 或 `--body-file` |
| `insert PATH AFTER` | 插入表单;需要 `--node` 或 `--node-file` |
| `complete` | 在不平衡的 s 表达式中追加缺失的 `)` |
| `fmt PATH` | 重新缩进文件(基于深度的,2 个空格缩进)。 `--write` 保存。 `--align` 用于续行对齐(参数列而不是深度×2) |
| `eval PATH` | 运行特定于方言的验证(字节编译,sbcl,guile...) |
| `remove PATH SYMBOL` | 删除表单;可选地用 ignore 替换调用站点 |
| `move PATH SYMBOL --after ANCHOR` | 在另一个符号、`__start__` 或 `__end__` 之后重新排序表单 |
| `substitute PATH SYMBOL` | 使用 `--pattern` / `--replacement` 替换表单内的子表达式 |
| `extract PATH SYMBOL` | 将子表达式提取到新函数中 |
| `rename PATH OLD NEW` | 重命名表单、其调用站点和 `#'old` / `'old` 引用。 `PATH` 可以是文件、目录或 **项目范围** 重命名的 glob。 `--refs` 还重命名普通 `'old`; `--no-refs` 仅重命名头部位置调用站点 |
| `wrap PATH SYMBOL` | 在 `progn`、`let` 或 `if` 中包装正文 |
| `analyze PATH` | 项目范围的语义分析,针对目录或 glob:未使用的定义、未解析的调用和 arity 不匹配。 `--unused` / `--unresolved` / `--arity` 运行子集(默认:全部) |
| `callers PATH SYMBOL` | 符号的调用者。单个文件或项目范围,当 `PATH` 是目录/ glob |
| `callees PATH SYMBOL` | 直接从定义正文调用的符号 |
| `explore PATH SYMBOL` | 某个符号的源 + 定义 + 调用者 + 被调用者 |
| `impact PATH SYMBOL` | 传递调用者(爆炸半径)。 `--depth N` (默认 5) |
| `diff REF PATH` | 自 `REF` 以来的 Git diff → `PATH` 中的受影响符号。 `--impact` 添加每个符号的爆炸半径 |
| `check PATH` | 验证文件 → `OK` 或标准错误上的语法错误 |
| `check PATH --semantic` | 深入验证 — 文档字符串、缺少的 `provide` / `in-package` / 库导出警告(elisp、commonlisp、scheme) |
| `check-node` | 验证一个表单; `--lang elisp\|commonlisp\|scheme` |
| `mcp serve` | 在标准输入/输出上运行 MCP 服务器 |
| `mcp install` | 将服务器添加到 `~/.cursor/mcp.json`(或 `--claude-code`, `--claude-desktop`) |
`replace`、`insert`、`fmt`、`remove`、`move`、`substitute`、`extract`、`rename` 和 `wrap` 除非设置 `--write`,否则将更新的文件打印到标准输出。设置 `--write` 后,它们会原子地替换文件并打印 `OK`。
`tree`、`replace`、`insert` 和 `fmt` 接受 `--diff` 以在标准错误上显示基于行的 diff。 `tree` 接受 `--depth N` 用于子表单导航:
```bash
lisp-sitter tree src/foo.el --depth 2
# → defun:my-func@12:1
# → let:bindings@15:3
# → if:condition@18:3
```
文件参数接受 glob 模式和目录用于批量操作:
```bash
lisp-sitter check "src/**/*.el"
lisp-sitter fmt lib/
lisp-sitter remove "*.lisp" dead-func --write
```
退出码 `0` 表示成功,`1` 表示错误。
### 安全
- `--diff` — 在更改前在标准错误上显示基于行的 diff
- `--confirm` — 显示 diff + 提示写入
- 自动备份 — 将以前版本保存到 `$TMPDIR/lisp-sitter-backups/`
- 原子写入 — 临时文件 + 重命名,在崩溃时永远不会损坏
### 配置
语言根据文件扩展名推断。可以使用 `LISP_SITTER_LANG=elisp|commonlisp|scheme` 或全局标志 `--lang` 重写。
自定义扩展名映射和项目特定的定义宏可以设置在
`~/.config/lisp-sitter/config.json` 或 `~/.lisp-sitter.json`(或 `LISP_SITTER_CONFIG` 给出的路径,该路径优先)。配置的扩展名按目录、glob 和项目范围的操作处理,而不仅仅是直接命名文件:
```json
{
"extensions": {
".foo": "elisp",
".bar": "scheme"
},
"extra_definers": {
"elisp": ["define-widget", "transient-define-prefix"],
"commonlisp": ["define-app-command"]
}
}
```
`extra_definers` 为每个语言(`elisp`、`commonlisp`、`scheme`)注册额外的顶级定义表单,以便您的自定义定义宏被 `tree` 列出并可通过 `bounds` / `get` / `replace` / `rename` 地址。每个都被视为 `defun` / `define` — 名称是第二个元素。
## 项目范围的操作
`rename`、`analyze` 和调用图命令在给定目录或 glob 时跨整个项目运行。
```bash
# 重命名函数和项目中每个调用站点(预览,然后应用)
lisp-sitter rename src/ old-name new-name
lisp-sitter rename src/ old-name new-name --write
# 调用图导航(按需扫描,无索引文件)
lisp-sitter callers src/ my-func
lisp-sitter callees src/ my-func
lisp-sitter explore src/ my-func
lisp-sitter impact src/ my-func --depth 5
# Git 感知:自 main 以来符号变更(+ 可选爆炸半径)
lisp-sitter diff main src/
lisp-sitter diff main src/ --impact
# 语义分析:未使用的定义、未解析的调用、arity 不匹配
lisp-sitter analyze src/
lisp-sitter analyze "src/**/*.el" --unused --arity
```
`analyze` 报告三类问题:
- **unused** — 在分析的文件中没有引用的 `defun` / `define` / 宏
- **arity** — 调用参数计数与定义的参数列表不匹配(理解 `&optional` / `&rest` / 可变)
- **unresolved** — 对在分析的文件中未定义或已知内置函数的名称的调用
未解析符号的检测是启发式的:它依赖于每个方言的精选(非详尽)内置函数表,因此动态构建的调用或表外的内置函数可能会被报告。 将输出视为警告,而不是错误。
## 编辑器集成(Emacs)
[`editor/lisp-sitter.el`](editor/lisp-sitter.el) 是围绕 CLI 的薄 Emacs 包装器,因此交互式编辑获得与命令行相同的解析和验证保证。
```elisp
(add-to-list 'load-path "/path/to/lisp-sitter/editor")
(require 'lisp-sitter)
(add-hook 'emacs-lisp-mode-hook #'lisp-sitter-mode)
(add-hook 'scheme-mode-hook #'lisp-sitter-mode)
(add-hook 'lisp-mode-hook #'lisp-sitter-mode)
;; 可选:保存后使用 lisp-sitter 验证
(setq lisp-sitter-check-on-save t)
```
| 键 | 命令 | 操作 |
| --- | --- | --- |
| `C-c s t` | `lisp-sitter-tree` | 顶级表单的轮廓 |
| `C-c s g` | `lisp-sitter-get` | 显示表单的文本 |
| `C-c s r` | `lisp-sitter-replace-defun` | 重新验证和重写光标处的表单 |
| `C-c s R` | `lisp-sitter-rename` | 重命名符号(`C-u` 用于项目范围) |
| `C-c s f` | `lisp-sitter-format-buffer` | 重新缩进文件 |
| `C-c s c` | `lisp-sitter-check` | 验证文件 |
| `C-c s a` | `lisp-sitter-analyze` | 语义分析(`C-u` 用于项目范围) |
## 代理工作流
对于 `.el`、`.lisp`、`.cl`、`.scm`、`.ss`、`.sld` 文件,请优先使用 **lisp-sitter** 而不是基于行的编辑:
1. `lisp-sitter tree PATH` — 查看存在哪些表单
2. `lisp-sitter get PATH SYMBOL` — 读取表单的完整文本(可选)
3. `lisp-sitter replace PATH SYMBOL` 或 `substitute` — 传递 **完整** 表单文本
4. `lisp-sitter check PATH` — 验证重构后
用于结构重构:`wrap`、`extract`、`move` 和 `rename`。
编写新表单时,使用 `complete` 修复不平衡的括号,然后使用 `fmt` 重新缩进。
`CLAUDE.md` 或 Cursor 规则示例:
```markdown
对于 Lisp 文件(.el .lisp .cl .scm .ss .sld),请使用 lisp-sitter 进行编辑:
tree → get → replace/substitute(仅限完整表单)→ check。
使用 complete 进行括号平衡,使用 fmt 进行缩进,使用 rename 进行重命名。
不要在结构化 Lisp 文件上使用基于行的搜索/替换。
```
## MCP 服务器
向 Cursor、Claude Code 或任何 MCP 客户端公开相同的结构化工具:
| 工具 | 描述 |
| --- | --- |
| `check_structural_file` | 验证整个文件(带有 `semantic: true` 进行深入检查) |
| `check_structural_node` | 验证一个顶级表单 |
| `structural_tree` | 顶级表单的轮廓(`depth` 用于子表单) |
| `structural_bounds` | 符号的字节范围 `START:END` |
| `structural_get` | 命名表单的完整文本 |
| `structural_context` | 完整的结构上下文:树 + 边界 + 完整文本 |
| `structural_replace` | 替换表单(验证并保存) |
| `structural_insert` | 在 `__start__`、`__end__` 或符号后插入 |
| `structural_complete` | 在不平衡的表单中追加缺失的 `)` |
| `structural_format` | 重新缩进文件(基于深度) |
| `structural_eval` | 运行特定于方言的验证(字节编译,sbcl,guile...) |
| `structural_remove` | 删除表单(带有 `keep_calls` 选项) |
| `structural_move` | 在锚点后移动表单 |
| `structural_substitute` | 替换表单内的子表达式 |
| `structural_extract` | 将子表达式提取到新函数中 |
| `structural_rename` | 重命名表单、调用站点和引用(`refs: true` 用于普通 `'old`) |
| `structural_rename_project` | 在目录或 glob 上重命名符号(定义 + 每个引用);除非 `write: true`,否则显示差异预览 |
| `structural_analyze` | 项目范围的未使用定义、未解析调用和 arity 分析,针对目录或 glob |
| `structural_callers` | 符号的调用者(`path` = 文件、目录或 glob) |
| `structural_callees` | 直接被调用的被调用者 |
| `structural_explore` | 某个符号的源 + 调用者 + 被调用者 |
| `structural_impact` | 传递调用者(爆炸半径);可选 `depth` |
| `structural_diff` | 自 `ref` 以来的 Git diff → 触及的符号;`impact: true` 用于爆炸半径 |
| `structural_wrap` | 用构造包装表单的正文 |
所有工具都接受 `write: true` 以就地保存。`structural_replace`、`structural_insert` 和 `structural_format` 接受 `diff: true` 以显示行级 diff,然后再应用。
### 加强 MCP 服务器的安全性
服务器根据调用代理选择的文件路径执行操作。两个环境变量允许操作员约束它:
| 变量 | 效果 |
| --- | --- |
| `LISP_SITTER_ENABLE_EVAL` | `structural_eval` 运行文件通过原生解释器(Emacs / SBCL / Guile)— 执行任意代码。**默认情况下禁用**;设置为 `1` 允许。 |
| `LISP_SITTER_ROOT` | 设置后,每个读取或写入的文件必须解析为此目录内的路径(首先解析符号链接和 `..`)。未设置意味着没有限制。将其设置为项目根目录以沙箱服务器。 |
安装到 Cursor(默认)或 Claude:
```bash
make install
lisp-sitter mcp install # ~/.cursor/mcp.json
lisp-sitter mcp install --claude-code # ~/.claude.json(Claude Code CLI)
lisp-sitter mcp install --claude-desktop # ~/.claude/settings.json(Claude Desktop)
lisp-sitter mcp install --claude-code --claude-desktop # 两个 Claude 目标
lisp-sitter mcp install --cursor --claude-code --claude-desktop # 所有三个
```
手动配置条目:
```json
{
"mcpServers": {
"lisp-sitter": {
"command": "/path/to/lisp-sitter",
"args": ["mcp", "serve"]
}
}
}
```
独立运行:`lisp-sitter mcp serve`(stdio 传输)。
## 它是如何工作的
- **解析** — tree-sitter 语法([elisp](https://github.com/Wilfred/tree-sitter-elisp),[commonlisp](https://github.com/theHamsta/tree-sitter-commonlisp),[scheme](https://github.com/6cdh/tree-sitter-scheme))
- **导航** — 顶级表单名称和字节范围
- **编辑** — 拼接替换文本,重新解析以验证
- **回退** — 不完整的解析树时使用 s 表达式扫描器
Tree-sitter 看到了语法,而不是语义:`(foo (let ...))` 是一个列表,而不是“宏 vs 函数调用”。这足以进行结构化替换/插入。
## 开发
```bash
make help # 列出目标
make test # 运行所有测试
make release # 构建 target/release/lisp-sitter
make lint # clippy
make fmt # rustfmt
```
### 测试
```bash
cargo test # 单元测试(169+)
cargo test -p lisp-sitter # 仅限主 crate
```
对于覆盖率报告(可选 - 不需要开发):
```bash
cargo install cargo-llvm-cov
cargo llvm-cov --workspace --ignore-filename-regex "tests/explore" --open
```
外部 Lisp 解释器(Emacs,SBCL,Guile)不是必需的 - `eval` 模块使用可模拟的 `Runner` 特质。基本测试不需要外部测试框架。
### 工作区布局
```
crates/
lisp-sitter-core/ # 插件特质、编辑引擎、s 表达式回退
lisp-sitter-elisp/
lisp-sitter-cl/
lisp-sitter-scheme/
lisp-sitter/ # CLI 二进制文件 + MCP 服务器 + 项目分析
editor/
lisp-sitter.el # 围绕 CLI 的薄 Emacs 包装器
```
## 许可证
MIT — 参见 [LICENSE](LICENSE)。
MCP 配置
以下是此 MCP Server 的配置信息,可直接复制到 Cursor 或其他 MCP 客户端中使用。
mcp.json
连接信息
猜你喜欢
暂无推荐服务器