针对 Scheme 的 Emacs 编程环境设置

    Mac Tools

GNU Emacs 是开源社区中 GNU 操作系统的一款古老而嚣张的编辑器。软件的上手涉及一些麻烦的初始设置,对新手来说不算友好。但之所以要用它,主要是为了更好地用 Scheme 语言来编程。相比对于新手最友好的上手软件 Racket 来说,Emacs 可以通过设置,方便地(也没有特别方便)使用由 R. Kent Dybvig 设计的、世界上性能和可靠性都最强的 Chez Scheme 来运行我们的代码。

I am the principal developer of the now open-source Chez Scheme, a highly reliable, highly efficient dynamic language and implementation.

R. Kent Dybvig

本文内容将以 MacOS 系统为例进行讲解,其他系统的操作是类似的。


安装使用 Chez Scheme

Chez Scheme 的安装在介绍页面中的描述看似很复杂(对新手算是不友好的),实际则十分简单:

## 这里准备将 Chez Scheme 安装在 ~/apps 这个目录下
## 打开终端 terminal 输入 cd 命令进入该目录(你也可以选择其他的目录安装)
$ cd ~/apps

## 运行 git clone 命令,将 Chez Scheme 下载到当前目录中
## 内容大小 1.48 GB 左右,确保你的网络畅通,不然下载过程容易出错
$ git clone git@github.com:cisco/ChezScheme.git

## 下载完成后进入文件夹,准备运行安装命令
$ cd ChezScheme/
$ pwd
~/apps/ChezScheme

## 如果你电脑的 CPU 是 x86_64 架构的,直接运行这个配置命令(程序)即可
## 确保网络畅通,配置过程需要下载相关依赖库
$ ./configure

## 如果你的电脑是 Apple Silicon 的 Mac ,比如 CPU 是 M1 的 Mac
## 那么就要在上面这个配置命令(程序)前面加上 arch -x86_64
## 这是强制让 M1 Mac 的 arm 架构的 CPU 模拟 x86_64 架构来运行配置命令(程序)
$ arch -x86_64 ./configure

## 上面的配置命令成功结束后,就可以运行下面的命令进行安装了
## 这个环节需要输入电脑的密码进行授权
## Chez Scheme 自编译速度很快,整个过程不会超过 1 分钟(一般是 30 秒内)
$ sudo make install

## 安装完成后就能在终端中使用 scheme 命令来运行代码文件了(scheme 代码的文件名常用后缀是 .scm)
## 运行(加载)文件后会进入 REPL 的交互解释器模式。测试完代码后,可以用 (exit) 退出
$ scheme ~/Documents/prog/demo.scm
Chez Scheme Version 9.5.9
Copyright 1984-2022 Cisco Systems, Inc.

Hello World ! 
> words
"Hello World"
> punctuation
"!"
> (+ 2 3)
5
> (exit)      ## 调用函数 (exit) 退出 REPL 交互模式

实际上,如果只是要用 Chez Scheme 来运行我们的代码,完全可以只使用终端来运行。搭配你自己喜欢的编辑器来写代码即可,不一定非要用对新手不友好的 Emacs 。比如我就用 DrRacket 作为主力编辑器写 Scheme 代码。有需要再在终端里用 scheme 命令运行。

## 注意,终端不像 DrRacket 那样会自动输出结果,
## 如果要在代码文件中实现输出,得用 printf 函数
## 可以看一下上文 demo.scm 文件中的代码内容
$ cat ~/Documents/prog/demo.scm
(define words "Hello World")
(define punctuation "!")

(printf "~a ~a ~n" words punctuation)

Emacs 安装

Emacs 的安装并不难,从官网下载 .dmg 文件到本地正常安装即可。它对于新手的主要障碍在于后续的配置和软件的使用。


开始使用 Emacs

刚安装好的 Emacs 十分简陋。需要对它进行配置,才能勉强达到一个稍微好用的状态。但是从顺序上来说,知道如何使用,后续配置的过程才会更加游刃有余。首次打开 Emacs 界面如下图:

上图中我标注了 Emacs 的一些基本概念术语,这样后续你就会知道这些名词分别是在说什么区域:

  • 红色标注区的 Echo Area:显示你的各种输入,进行简单的交互,比如查找文件,查找关键词;
  • 绿色标注区的 Mode Line:显示当前 Buffer 的状态信息。后期可以自行定义显示内容;
  • 紫色标注区的 Window:内容区。显示当前 Buffer 的内容。
  • 青色标注区的 Frame:一个 Frame 内部区域可以分割成多个 Window,这样可以同时看多个 Buffer

除了上述四个描述用户界面的概念,Emacs 里还有个重要概念是 Buffer 。对新手来说,现阶段只需要把 Buffer 简单理解为「任务」就行了。比如,你打开了 3 个文件,就相当于有了 3 个 Buffer ,每个文件对应一个;浏览器开了 2 个页面,就有 2 个 Buffer ……所以,在不同 Buffer 之间切换,就相当于在你的 3 个文件之间、2 个浏览器页面之间切换,然后 Buffer 的内容会显示在你的 Window 区域里。

接下来就是「操作」了。虽然也能用鼠标,但 Emacs 的主流操作是以键盘快捷键为主的。因为它多数情况下比鼠标效率高,尽管这也增加了新手的学习成本。

Emacs 的快捷键组合多数会配合 CtrlMeta 这两个按键。这两个键分别会用大写字母 CM 来表示。比如 Ctrl + G 这样的组合按键会写成 C-g ,表示同时按键盘上的 Ctrl 键和字母 G 键。同理,M-x 表示同时按键盘上的 Meta 键和字母 X 键。C-x C-s 表示先按 C-x 组合键,再按 C-s 组合键。现在再看上文的界面图,就能知道 echo area 区域在提示你可以用快捷键 C-x C-s 来查看更多信息。

苹果电脑的键盘上没有 Meta 按键,默认会用 Option 按键来替代(可自定义)。

下面是最基础的快捷键,耐心适应后,会感觉还算好用:

  • C-g :这个是撤销操作。比如你快捷键输入到一半错了想撤销就按它;
  • C-x C-f打开文件的操作。在 echo area 输入文件路径后如果文件不存在就会新建
  • C-x C-s保存当前文件。不过在 MacOS 下,你也可以直接用 cmd + S 这样通用的保存操作;
  • C-x C-w :将当前文件另存为。相当于复制文件的操作;
  • C-x b : 切换 Buffer 。可以用 Tab 键来自动补全或查看当前可切换的 Buffer ;
  • C-x C-c : 关闭 Emacs 编辑器;

掌握了上述基础操作后,就可以进行下一个部分的配置了。其他额外知识可以随着 Emacs 的使用慢慢掌握,我们首要目标是能尽快地写 Scheme 代码和运行它。


配置 Emacs

这里只需要做 3 个关键设置即可。建议直接使用 Emacs 来做各种编辑操作,这样能更快熟悉它。为了简单,我将直接列出可执行的步骤,不会深入讲解其中的原理。新手在最初的 Emacs 使用中也无须过度探索,确保尽快进入 Scheme 编程阶段。扩展知识我都给了链接,之后再慢慢深入。

  • 第 1 步
    编辑 Emacs 的初始化文件 init.el 。刚安装好的 Emacs 没有这个文件,需要我们自己新建。我们还需要删除原始的初始化文件 ~/.emacs ,避免它的内容覆盖我们之后设置好的 init.el
    使用快捷键 C-x C-f 新建并打开文件 ~/.emacs.d/init.el ,然后将下面的代码复制进去保存:

    (add-to-list 'load-path "~/.emacs.d/scheme")
    (autoload 'paredit-mode "paredit"
      "Minor mode for pseudo-structurally editing Lisp code."
      t)
    
    (require 'parenface)
    (set-face-foreground 'paren-face "DimGray")
    
    ;;;;;;;;;;;;
    ;; Yin's Scheme Configuration
    ;;;;;;;;;;;;
    
    (require 'cmuscheme)
    (setq scheme-program-name "scheme") ;; 指定用 Chez Scheme ,若用 Racket 就写 "racket"
    
    ;; bypass the interactive question and start the default interpreter
    (defun scheme-proc ()
      "Return the current Scheme process, starting one if necessary."
      (unless (and scheme-buffer
                   (get-buffer scheme-buffer)
                   (comint-check-proc scheme-buffer))
        (save-window-excursion
          (run-scheme scheme-program-name)))
      (or (scheme-get-process)
          (error "No current process. See variable `scheme-buffer'")))
    
    (defun switch-other-window-to-buffer (name)
        (other-window 1)
        (switch-to-buffer name)
        (other-window 1))
    
    (defun scheme-split-window ()
      (cond
       ((= 1 (count-windows))
        ;;(split-window-vertically (floor (* 0.68 (window-height))))
        (split-window-horizontally (floor (* 0.68 (window-width))))
        (switch-other-window-to-buffer "*scheme*"))
       ((not (member "*scheme*"
                   (mapcar (lambda (w) (buffer-name (window-buffer w)))
                           (window-list))))
        (switch-other-window-to-buffer "*scheme*"))))
    
    (defun scheme-send-last-sexp-split-window ()
      (interactive)
      (scheme-split-window)
      (scheme-send-last-sexp))
    
    (defun scheme-send-definition-split-window ()
      (interactive)
      (scheme-split-window)
      (scheme-send-definition))
    
    (add-hook 'scheme-mode-hook
      (lambda ()
        (paredit-mode 1)
        (define-key scheme-mode-map (kbd "<f5>") 'scheme-send-last-sexp-split-window)
        (define-key scheme-mode-map (kbd "<f6>") 'scheme-send-definition-split-window)))
    

    之所以用 ~/.emacs.d/init.el 而不是传统的 ~/.emacs 来作为初始化文件是为了「模块化管理」。后续所有关于 Emacs 的配置文件都将集中存放在 ~/.emacs.d 文件夹中,便于管理。比如关于 Scheme 代码的设置文件我就在 .emacs.d 文件夹下建立了一个子文件夹 scheme 来存放。顶层只放初始化文件。

  • 第 2 步
    下载 paredit.el 文件,将它放到 ~/.emacs.d/scheme 目录下。可用快捷键 C-x C-w 来将文件复制(另存为)到该目录。你可能会对 paredit.el 文件中 ^L 这样的内容感到奇怪,不用管它,参见这里
    paredit.el 文件提供了高效可靠的括号操作,提升我们 Scheme 代码的编辑体验。

  • 第 3 步(可选)
    这步是把代码的括号颜色调淡,不做也不影响我们编辑、运行代码。但因为简单,推荐做一下。
    下载 parenface.el 文件,并将其放在 ~/.emacs.d/scheme 目录下。同样可用 C-x C-w 快捷键操作。


使用 Emacs 来写 Scheme 代码

相信现在你已基本熟悉 Emacs 的快捷键 C-x C-f 了。就用它来新建文件 ~/Documents/length.scm ,看看如何编辑运行 Scheme 吧。

还是写我们的老朋友 length 函数:

(define length
  (lambda (l)
    (cond [(null? l) 0]
          [else (+ 1 (length (cdr l)))])))

(length '(1 2 3))

(length `(4 ,(+ 5 6) 7))

我们上面的配置中把 F5 这个按键设置为了「运行光标所在处的前一个表达式(scheme-send-last-sexp)」。所以我们先把光标移动到第一个 define 表达式的最末尾,按一下 F5 。这时你可以看到 Emacs 出现了一个新的 Window ,开启了 REPL 交互模式,并且成功运行了我们的 define 表达式。

同理,我们分别把光标移动到后面两个表达式的末尾,按 F5 ,就能分别得到这两个表达式的值了:

多数时候,我们的代码很多,依次运行一个个表达式太麻烦。这时,可先按 cmd + A 全选内容,然后用 Emacs 的快捷键 C-c C-r 来向 REPL 载入所选内容(scheme-send-region)。这样,整个文件的内容就都被送到 Chez Scheme 里了。然后,你就可以用 F5 一个个地运行测试你的表达式了。

你也可以用 C-c C-l 来载入(运行)某个文件(scheme-load-file)。总的来说,在用 F5 依次运行表达式之前,要先确保各种变量都已完成定义。

编辑 Scheme 文件时的常用快捷键:

  • C-c C-k:scheme-compile-file
  • C-c C-l:scheme-load-file
  • C-c C-r:scheme-send-region
  • C-c C-e:scheme-send-definition
  • C-x C-e:scheme-send-last-sexp
  • Control + 右箭头:paredit-forward-slurp-sexp 括号向右扩展吃掉右边最近的一个表达式
  • Control + 左箭头:paredit-forward-barf-sexp 括号向左收缩吐出左边最近的一个表达式

参考资料:


更多的 Emacs 操作和技巧(待完善)

Emacs 是一个好的操作系统,它只是缺少一个好的编辑器。

Ian

常用内容编辑快捷键:

  • C-p : 光标向移动;
  • C-n : 光标向移动;
  • C-f : 光标向移动;
  • C-b : 光标向移动;
  • C-e : 光标向移动到这一段文字的末尾
  • C-a : 光标向移动到这一段文字的开头
  • C-k : 删除并复制光标处开始后面的这一段文字(类似剪切操作);
  • C-y : 于光标处粘贴最近一次被 C-k 复制的内容(类似粘贴操作);
  • M-y : 在 C-y 操作后,切换 C-k 复制的内容( C-k 复制的内容都被储存在 kill ring 里);

……
……

TODO

本文参考与拓展资料:


打赏