第 7 部分 · 走向生产:工程化与实战
结构化编辑——让 DrRacket 替你管括号
手调括号是 Lisp 新手最大的挫败感来源,也是老手从不担心的事——差别只在是否用 paredit 这类结构化编辑:括号永远成对,移动、增删、调整嵌套都以 S 表达式为单位进行。这篇带你装好 drracket-paredit,并把值得记的快捷键过一遍。
结构化编辑
在 DrRacket 里装上 drracket-paredit 插件,编辑器就从「文本编辑」升级成「结构编辑」:你的操作对象不再是字符,而是 S 表达式,括号平衡由插件保证,手滑再也删不坏代码结构。它主要给你五件事:
- 保持括号平衡:在插入或删除括号时,自动保证括号是成对出现的,避免语法错误。例如,当你输入 (时插件会自动补全一个) 。
- 结构化导航:提供快捷键,以 S 表达式为单位进行导航,而不是逐字符或逐词移动。例如,可以快速跳到下一个或上一个完整的表达式。
- 结构化编辑:支持对代码块(S 表达式)进行剪切、复制、粘贴等操作,而不仅仅是文本操作。例如,调整表达式的嵌套层次、合并括号或分解括号。
- 安全删除:删除操作会自动避免破坏代码结构。例如,删除一个括号时,会同时删除匹配的括号。
- 高级括号操作:支持括号的 “ 吞吐 ”(slurping & barfing)操作,即调整括号范围,使表达式更容易读或更符合语义。
在使用快捷键之前,必须先进入 Edit → Preferences → Editing,取消勾选「Enable keybindings in menus (overrides Emacs keybinds)」,否则所有 paredit 快捷键都不会生效。修改后需要重启 DrRacket。
快捷键介绍
drracket-paredit 包含的快捷键如下:
Movement:
("c:m:f")paredit-forward-sexp("c:m:b")paredit-backward-sexp("c:m:d")down-sexp ;rebind to"c:m:d"("m:right")forward-atom ;this is not paredit shortcuts, but alternative for forward-word("m:left")backward-atom ;ditto
Depth-Changing:
("m:s")paredit-splice-sexp("m:(")paredit-wrap-round("m:up")paredit-splice-sexp-killing-backward("m:down")paredit-splice-sexp-killing-forward("m:r")paredit-raise-sexp("m:?")paredit-convolute-sexp
Slurpage & barfage
("c:right" "c:)" "c:]")paredit-slurp-forward("c:m:left" "c:(" "c:[")paredit-slurp-backward("c:left" "c:}")paredit-barf-forward("c:m:right" "c:{")paredit-barf-backward
这些快捷键各自的使用场景,配着例子过一遍:
如果不了解快捷键的含义,可以参考 Keyboard Shortcuts
移动(Movement)
这些快捷键让你能够快速地在代码中以 S 表达式为单位导航,而不是逐字符或逐词移动。
c:m:f(paredit-forward-sexp)
场景:跳转到下一个 S 表达式的末尾
示例:
(define (square x) (* x |x))
; 光标在 | 处,按 c:m:f 跳到 x 后面
(define (square x) (* x x|))
c:m:b(paredit-backward-sexp)
场景:跳到当前或上一个 S 表达式的开头
示例:
(define (square x) (* x x|))
; 光标在 | 处,按 c:m:b 跳到当前 x 的开头
(define (square x) (* x |x))
c:m:d(down-sexp)
场景:进入当前 S 表达式的下一层
示例:
(define |(square x) (* x x))
; 光标在 | 处,按 c:m:d 进入子表达式
(define (|square x) (* x x))
m:right(forward-atom)
场景:跳到下一个符号或原子(类似 forward-word)
示例:
(define |square x)
; 从 | 处按 m:right 跳到下一个原子的末尾
(define square| x)
m:left(backward-atom)
场景:与 m:right 相反,跳到上一个符号或原子
示例:
(define square |x)
; 从 | 处按 m:left 跳到前一个原子
(define |square x)
深度变化(Depth-Changing)
这些快捷键用于修改表达式的结构(如合并、提取或包装)。
m:s(paredit-splice-sexp)
场景:移除当前表达式的外围括号,保持内部结构
示例:
(foo (bar |(+ 1 2)) baz)
; 光标在 | 处,按 m:s 移除 (bar ...) 的括号
(foo bar (+ 1 2) baz)
m:((paredit-wrap-round)
场景:用一对括号包裹当前表达式
示例:
(foo |bar baz)
; 光标在 | 处,按 m:( 包裹 bar
(foo (bar) baz)
m:up(paredit-splice-sexp-killing-backward)
场景:移除外围括号,并删除光标前面的所有内容
示例:
(foo (bar |(+ 1 2)) baz)
; 光标在 | 处,按 m:up,删除光标前面的内容
(foo (+ 1 2) baz)
m:down(paredit-splice-sexp-killing-forward)
场景:移除外围括号,并删除光标位置及其后面的所有内容
示例:
(foo (|bar (+ 1 2)) baz)
; 光标在 | 处,按 m:down,删除光标及其后面的内容
(foo baz)
m:r(paredit-raise-sexp)
场景:将光标所在的 S 表达式提升,替换其父表达式
示例:
(* 10 (+ |(- 5 2) 3))
; 光标在 | 处,按 m:r,(- 5 2) 替换整个 (+ ...) 表达式
(* 10 (- 5 2))
m:?(paredit-convolute-sexp)
场景:重新排列嵌套结构,将光标所在表达式与外层表达式互换位置
示例:
(let ([x 3]) (+ |(* 2 x) 1))
; 光标在 | 处,按 m:?,(* 2 x) 提升与 + 互换
(+ (let ([x 3]) (* 2 x)) 1)
吞吐与吐出(Slurpage & Barfage)
这些快捷键是操作括号范围的神器,可以让括号快速 “ 吞 ” 或 “ 吐 ” 邻近的表达式。
c:right/c:)/c:](paredit-slurp-forward)
场景:将光标所在括号 右边紧邻 的表达式 “ 吞 ” 进括号内(扩大括号范围)
示例:
(foo (bar |baz) qux)
; 光标在括号内任意位置,按 c:right
(foo (bar baz qux))
; qux 被吞入括号内,右括号向右移动
c:m:left/c:(/c:[(paredit-slurp-backward)
场景:将光标所在括号 左边紧邻 的表达式 “ 吞 ” 进括号内(扩大括号范围)
示例:
(foo bar (|baz qux))
; 光标在括号内任意位置,按 c:m:left
(foo (bar baz qux))
; bar 被吞入括号内,左括号向左移动
c:left/c:}(paredit-barf-forward)
场景:将光标所在括号内 最右侧 的表达式 “ 吐 ” 出括号外(缩小括号范围)
示例:
(foo (bar baz |qux))
; 光标在括号内任意位置,按 c:left
(foo (bar baz) qux)
; qux 被吐出括号外,右括号向左移动
c:m:right/c:{(paredit-barf-backward)
场景:将光标所在括号内 最左侧 的表达式 “ 吐 ” 出括号外(缩小括号范围)
示例:
(foo (bar |baz qux))
; 光标在括号内任意位置,按 c:m:right
(foo bar (baz qux))
; bar 被吐出括号外,左括号向右移动
总结
- 移动快捷键:快速定位到需要操作的 S 表达式。
- 深度操作快捷键:修改 S 表达式的嵌套结构。
- 括号操作快捷键:高效调整括号范围
三组快捷键配合起来,你就再也不是在「改文本」,而是在「改结构」——这正是 Lisp 说代码即数据时,编辑器理应配套的体验。装好它,前面各章的示例代码怎么调整括号都不慌。