刚接触Linux的时候一直学习的都是VIM,这个编辑器也是系统默认的,功能非常强大。我也根据网上教程把VIM打造成了IDE,但是没有实际地使用它阅读过代码。后来看到网上争论Emacs和VIM孰优孰劣,突然对Emacs也有了兴趣,最近一直在折腾Emacs。学习的感受可以用一个幽默段子来描述:听说emacs这个东西,试了一下,感觉就像记事本一样,没什么学习曲线,就是……咋也找不到退出的办法……
网上也搜了一些相关教程以及一些配置,个人感觉如果想在配置过程中少受折磨还得了解点Elisp相关的知识,对自定义配置也能得心应手,不然配置出错了会一脸迷茫不知所措。以下几篇文章是我学习Emacs Lisp入门的时候做的备忘笔记。等对入门知识有了一些了解,可以根据自己的需求从网上搜集相关的插件进程定制。
缓冲区. Emacs并不直接对文件进行操作, 它是把文件加载进buffer, 用户直接操作buffer, 只有当用户确定把buffer中所做的修改写人文件时, Emacs才把buffer中的内容写入文件. buffer实际上就是一个内存缓冲区, 这和一般编辑器中都一样, 应该很好理解.
窗口. 由于Emacs很早就诞生了, 它的窗口概念和现在基于窗口的操作系统中的窗口概念不是一样的. Emacs中的窗口是用来显示buffer的一个区域. 它并不像操作系统中的窗口拥有自己的标题栏,系统菜单栏.
Emacs中的frame就是操作系统中的窗口.buffer是文件的内存表示, windows则是显示buffer的区域, 一个frame中可以有多个window, Emacs则可以有多个frame.
Emacs最基础的部分是用c写的,其他部分都是用Emacs Lisp语言写的,Emacs Lisp语言是基于lisp语言的. lisp是LISt Processor的缩写, 即链表处理语言, 所以lisp语言的操作都是基于链表的, 它的语法也是链表结构, 链表第一个元素表示函数名, 其他元素是参数, 比如通常语言中的a+b, 在lisp就是(+ a b)
mode有major mode和minor mode之分, 每个缓冲区对应一个major mode, 也只有一个major mode, 但是可以有多个minor mode. Emacs对每一种文件都有一个mode.
- 主模式(major mode)
主模式决定了emacs对当前buffer编辑行为。一个buffer只能同时开启一个主模式,可以在mode line中查看当前buffer所使用的主模式。默认模式叫做Fundamental Mode,在该模式中没有太多的特殊设置,所有的变量一般也都被设置为默认值。 主模式一般由emacs自动选择,可以通过m-x command手动指定。c-h m查看当前主模式帮助信息和按键绑定。
主模式可以大致分为三类:
- 为普通文本文件而生的主模式(Test mode)
- 为一些编程语言而生的主模式(C mode)
- 为特殊用途而生的主模式(如Dired mode)
- 辅模式(minor mode)
和主模式最大的区别就是可以同时启用多个辅模式,默认情况下大多数辅模式是关闭的,只有少数辅模式是开启的。有些辅模式的作用范围是局部,只对某些buffer生效;有些辅模式的作用范围是全局,对整个emacs会话生效。
常见的局部辅模式有:
Abbre mode、Auto Fill mode、 Auto Save mode、Enriched mode、Flyspell mode等
常见的全局辅模式有:
Column Number mode、Delete Selection mode、Icomplete mode、Menu Bar mode、Tool Bar mode等
主模式只能开启不能关闭,但是辅模式既可以开启也可以关闭:
- m-x command开启辅模式,如果已经相应的辅模式已经开启,则会关闭相应的辅模式;
- m-x number command,如果number <= 0则无条件关闭,如果number > 0则无条件开启相应的辅模式;
- lisp语句中开启辅模式,如果参数为nil或者省略则无条件开启,如果有非nil参数则参考上一条;
模式行是用来描述当前buffer状态的, 显示格式如下:
cs:ch-fr buf posline (major minor1 minor2 ...)
* cs,coding system,当前编码相关的状态;
* ch,change,表示文件修改状态;
* fr,frame name,表示frame的名字,终端程序默认显示为F1;
* buf,buffer name,表示buffer的名字,一般和关联的文件的名字相同;
* pos,position,表示当前显示的位置;
* line,表示光标所在的行;
* major,表示当前buffer使用的主模式,一个buffer只能同时关联一个主模式;
* minor,表示当前buffer使用的辅模式,一个buffer可以同时关联多个辅模式;
你对Emacs所有的操作都是对Emacs的命令的调用。比如,你在text-mode里,当你按下任何字母键进行编辑的时候,实际上是调用的emacs的命令self-insert-command.
通过组合按键序列实现快速调用Command。Emacs中Control键用C表示,Alt键用M表示, 即: C-c表示Control C, M-x表示Alt x.
除掉以Alt键开头的,比如Alt a,Emacs中的快捷键基本上都有一个前缀,Emacs中最多的快捷键前缀就是C-x,C-c,前缀表示,你不必要一起按下前缀和后缀,可以先按下前缀,Emacs会等待你按下剩余的快捷键,这样Emacs中的快捷键按起来非常的方便。
键盘映射. Emacs的快捷键是通过keymap来控制的. 有全局和局部的keymap. 每个mode都会有一个自己的局部的keymap, 局部的keymap会覆盖全局的keymap, 另外如果对应的major mode有开启的minor mode, 而且这个minor mode有keymap的话, 这个minor mode的keymap会覆盖major mode的keymap.
Emacs的文档非常丰富, 有Elisp自己的自文档, 还有更详细的info。 Elisp中的变量和函数都有文档,对于大多数情况都够用了.
功能 | 快捷键 |
---|---|
查看变量的值和文档 | C-h v (describe-variable) |
查看函数的文档 | C-h f (describe-function) |
查看face的文档 | M-x describe-face |
查看某个mode的文档 | C-h m (describe-mode) |
查看某个快捷键对应的命令 | C-h k (describe-key) |
查看某个命令对应的快捷键 | C-h w (where-is) |
查看当前buffer所有的快捷键 | C-h b (describe-bindings) |
查看当前buffer中以某个快捷键序列开头的快捷键列表 | <待查看的快捷键序列> C-h |
查看函数的代码 | find-function |
查看变量的代码 | find-variable |
查看face的代码 | find-face-definition |
查看包含某个关键词的文档 | M-x apropos |
Emacs的插件一般都是一个以el为后缀名的文件, 把这个文件下载下来后放到一个目录, 比如~/emacs/lisps, 然后执行下面这两条语句:
( add-to-list 'load-path "~/emacs/lisps")
( require 'pluginname)
全局绑定global-set-key, 绑定某个mode的快捷键用define-key. global-set-key的定义为:(global-set-key key command)
比如想把C-j绑定到”到达指定行上”(goto-line), 这样就可以了:
(global-set-key ( kbd "C-j") 'goto-line)
由空格分隔的、由括号括起来的单词、数字或者其他列表。
例如(this list has (a list inside of it))是一个包含列表元素的列表,而(apple, banana, orange)由于使用了逗号来分隔不同的元素,所以不是一个有效的列表。列表是用一系列成对的指针保存的。在这个成对的指针系列中,每一对指针的第一个指针要么指向一个原子,要么指向另外一个列表;而其第二个指针要么指向下一个指针对,要么指向符号nil表示列表结束。
从语法角度不能再被分隔的元素被称为原子。在一个列表中,原子是由空格分隔,原子可以紧接着括号。
lisp中只有以下几种原子:
- 数字(比如“10”,“25”)
- 符号(比如“foo”,“+”)
- 串(双引号括起来的文本)
说明:多余的空格和换行符只不过是为了使人们易于阅读而设计的,当lisp读取表达式时会剔除所有多余的空格
原子和列表的书面表示都被称作符号表达式,即sexp。
列表前面加单引号代表不要对这个列表做任何操作,将这种动作称为引用。如果列表前面没有引号,这个列表中的第一个符号会被当做函数,并把后续的元素当参数。
符号名是临时用于定位函数或者一组指令的。
用于实现特殊功能的符号。
实现函数定义,语法格式如下:
(defun function-name (arguments...);参量列表,包含将要传送给这个函数的参量 "optional-documentation..." ;C-h f显示的内容 (interactive argument-passing-info); 是函数成为交互函数,用户键入`M-x`和函数名或者键入绑定的键序列也可以激活它。 body...)
interactive函数的不同选项,多个选项之间用\n隔开
选项 | 功能 |
---|---|
p | 告诉Emacs要传送一个前缀参量并用于函数的参量 |
r | 告诉Emacs将位点所在区域的开始值和结束值作为函数的两个参量 |
B | 告诉Emacs传递缓冲区的名字用于函数的参量 |
* | 如果缓冲区是只读的就产生一个错误信号 |
c | 传递一个字符用于函数的参量 |
定义局部变量,语法格式如下
(let ((variable value) ;变量列表,由一个符号或者一个两元素的列表组成,初始值被赋为nil (variable value) ...) body...) ;主题,由一个或者多个列表组成
用于条件判断,语法格式如下:
(if true-or-false-test (action-to-carry-out-if-test-is-true) (action-to-carry-out-if-test-not-true))
保存当前的位点、标记和当前缓冲去,以备执行函数后恢复使用,语法格式如下:
(save-execursion body...)
只要这个符号前没有引号也没有括号包围他,Lisp解释器将试图像变量一样来去确定符号的值。试图对一个没有赋值的符号求值,将收到一个错误提示。
- Lisp解释器总是首先处理最内层的列表,对列表求值可能产生的结果有三种
* 返回列表本身
* 提示出错信息
* 将列表中的第一个符号当命令并执行该命令
(message "this message appears in the echo area!")
当直接使用message函数时,打印出来的文本是带引号的。这是因为由message函数返回的值将显示在回显区;而将其嵌入到一个函数中时,message打印出来的文本是作为一个附带效果出现的,以此不带引号。
C-x C-e
对表达式求值,并在回显区域显示结果
C-u C-x C-e
对表达式求值,并把结果显示在表达式后面
函数 | 功能 |
---|---|
buffer-name | 返回buffer的名字 |
buffer-file-name | 返回buffer关联的文件的名字 |
current-buffer | 返回当前buffer本身而不是名字 |
other-buffer | 返回最近使用过的buffer本身而不是名字 |
switch-to-buffer | 切换到另一个缓冲区,屏幕内容改变 |
set-buffer | 切换计算机注意力到另一个buffer,屏幕显示内容并不改变 |
buffer-size | 返回当前缓冲区的大小 |
point | 返回光标在当前缓冲区的第几个字符 |
point-min | 返回当前缓冲区中位点的最小值 |
point-max | 返回当前缓冲区中位点的最大值 |
- Lisp程序由表达式组成,表达式是列表或者单个原子。
- 列表由0个或者更多的原子或者内部列表组成,原子或者列表之间由空格隔开,并由括号括起来。列表也可以是空的。
- 原子是多字符的符号(forward−paragraph)、单字符符号(+)、双引号之间的字符串、或者数字。
- 对数字求值就是它本身。
- 对双引号之间的字符串求值也是其本身。
- 对一个符号求值时,返回它的值。
- 对一个列表求值时,Lisp解释器查看列表中的第一个符号以及绑定在其上的函数定义。然后这个函数定义中的指令被执行。
- 单引号告诉Lisp解释器返回后续表达式的书写形式,而不是像没有单引号时那样对其求值。
- 参量是传递给函数的信息。除了作为列表的第一个元素的函数之外,通过对列表的其余元素求值来计算函数的参量。
- 当对一个函数求值时总是返回一个值或者得到一个错误提示。
- 在许多情况下,一个函数的主要目的是产生一个附带效果。