ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

learnxinyminutes-docs 实战:15 分钟上手 Emacs Lisp——从 sexp 求值到 buffer 文本处理

learnxinyminutes-docs 实战:15 分钟上手 Emacs Lisp——从 sexp 求值到 buffer 文本处理 文档教程【免费下载链接】learnxinyminutes-docsCode documentation written as code! How novel and totally my idea!项目地址https://gitcode.com/gh_mirrors/le/learnxinyminutes-docs点击查看免费下载本篇基于 learnxinyminutes-docs 仓库中的 es/elisp.mdEmacs Lisp 教程的西班牙语版另有 英文原版 elisp.md 及 zh-cn/elisp.md 等翻译版本可对照编写。它以可执行的代码即文档的方式带你在 GNU Emacs 中从零开始掌握 Emacs LispElisp的核心语法与日常编辑操作你会学会 sexp 的书写与求值、用defun定义函数、操作多窗口 buffer、遍历列表并最终用while 正则表达式写出一段能自动替换并加粗文本的完整工具函数。全部示例都可以直接复制进 Emacs 的*scratch*buffer 中逐条求值验证。一、准备环境安装 GNU Emacs教程以 GNU Emacs 为运行环境要求版本 24.3 及以上。安装方式按操作系统区分Debian/Ubuntu 系apt-get install emacs或遵循你所使用发行版的软件包管理说明macOS使用 Emacs for Mac OS X 提供的安装包emacsformacosx.comWindows使用 GNU FTP 上提供的 Windows 二进制安装包。安装完成后直接启动 Emacs。看到欢迎界面时按q键关闭它即可进入干净的编辑环境。开始之前elisp.md 与 es/elisp.md 都建议先阅读 Peter Norvig 关于21 天学不会编程的经典文章以建立正确的心态——本教程全程走下来不会损坏你的电脑除非你气到把它扔在地上原作者声明对此不承担责任。二、认识scratchbuffer 与 lisp-interaction-mode启动 Emacs 后注意窗口底部那条灰色状态行*scratch*这是当前可编辑空间的名称这个可编辑空间在 Emacs 术语中叫作buffer缓冲区。*scratch*是打开 Emacs 时的默认 buffer。Emacs 的核心哲学是你从来不直接编辑文件而是编辑 buffer之后再把 buffer 保存写入到文件。Lisp interaction表示当前 buffer 可用的命令集合。Emacs 每个 buffer 都内置一套通用命令同时会随激活的**模式mode**附加额外的命令子集。*scratch*默认处于lisp-interaction-mode它自带求值与导航 Elisp 代码所需的命令——这正是我们接下来做实验的基础。三、sexpElisp 程序的基本单元Emacs Lisp 程序由**符号表达式symbolic expression**构成简称sexp本文档将其译为sexpresión即 sex 表达式。先看最小示例( 2 2)这条 sexp 读作把 2 和 2 相加。sexp 用括号包裹并且可以嵌套( 2 ( 1 1))一条 sexp 要么由**原子atom**构成要么由其他 sexp 构成。上面的例子中1和2是原子而( 2 ( 1 1))与( 1 1)是 sexp。在lisp-interaction-mode中求值 sexp 有两种方式把光标放在右括号的紧后面按住Ctrl再按j记作C-j结果会插入到 buffer 中( 3 ( 1 2)) ;; ^ 光标放在这里 ;; C-j 6使用C-x C-e先同时按Ctrl-x再按Ctrl-e结果会显示在 Emacs 窗口最底部的echo area回显区不会污染 buffer。本教程后续默认使用这种方式避免 buffer 被大量求值结果填满。分号;在行内任意位置都表示注释开始。四、变量与字符串setq 与 insert用setq把一个值存入变量(setq my-name Bastien) ;; C-x C-e Bastien显示在 echo area用insert把文本插入到光标所在位置(insert Hello!) ;; C-x C-e Hello!insert并不限定一个参数可以传入多个字符串它们会被依次插入(insert Hello world!) ;; C-x C-e Hello world!变量也可以直接参与插入(insert Hello, I am my-name) ;; C-x C-e Hello, I am Bastien五、定义并调用你的第一个函数defun用defun可以把若干 sexp 组合成一个函数。先定义一个无参数函数(defun hello () (insert Hello, I am my-name)) ;; C-x C-e hello定义完成后即可求值调用(hello) ;; C-x C-e Hello, I am Bastien函数定义中空的括号()表示该函数不接受参数。每次都写死my-name很无趣让函数接受一个参数这里命名为name(defun hello (name) (insert Hello name)) ;; C-x C-e hello现在用字符串you作为唯一参数调用它(hello you) ;; C-x C-e Hello you至此你已经掌握 Elisp 的三块基石原子与嵌套 sexp、变量存储setq、函数定义defun。歇口气进入多窗口实战。六、多窗口与 buffer 管理switch-to-buffer-other-window 与 progn接下来切换到一个名为*test*的新 buffer并把它显示在另一个窗口中(switch-to-buffer-other-window *test*) ;; C-x C-e ;; [屏幕现在分成两个窗口光标位于 *test* buffer 中]返回原来窗口的方法有两种直接用鼠标点击上方窗口或者使用C-x o先按Ctrl-x再按o在窗口间切换。可以用progn把多条 sexp 依次组合执行(progn (switch-to-buffer-other-window *test*) (hello you)) ;; C-x C-e ;; [两个窗口的屏幕中光标位于 *test* 窗口]后续示例中不再反复提示C-x C-e——请对每一条 sexp 手动执行它。每次实验后记得用鼠标或C-x o回到*scratch*buffer。重复实验会让*test*越来越乱清空 buffer 很有用(progn (switch-to-buffer-other-window *test*) (erase-buffer) (hello there))也可以执行完再切回原来的窗口(progn (switch-to-buffer-other-window *test*) (erase-buffer) (hello you) (other-window 1))七、局部变量 let 与字符串格式化 formatlet可以把值绑定到局部变量且它本身就能顺序组合多条 sexp因此这里不需要再包一层progn(let ((local-name you)) (switch-to-buffer-other-window *test*) (erase-buffer) (hello local-name) (other-window 1))format用于格式化字符串%s是字符串占位符\n是换行符(format Hello %s!\n visitor) ;; 求值结果字符串 Hello visitor! 加一个换行用format重构hello让它输出带换行的问候语(defun hello (name) (insert (format Hello %s!\n name))) (hello you)再创建一个同时使用let与format的函数greeting函数参数name与 let 局部变量your-name分别作为两个占位符的值(defun greeting (name) (let ((your-name Bastien)) (insert (format Hello %s!\n\nI am %s. name ; 函数的参数 your-name ; let 绑定的局部变量 Bastien )))) (greeting you)八、交互式输入read-from-minibuffer有些函数是交互式的。read-from-minibuffer会在 minibuffer回显区所在的那一行弹出提示并等待输入(read-from-minibuffer Enter your name: )求值这条 sexp 后函数会返回你在提示符处输入的内容。改造greeting让它现场询问你的名字(defun greeting (from-name) (let ((your-name (read-from-minibuffer Enter your name: ))) (insert (format Hello!\n\nI am %s and you are %s. from-name ; 函数的参数 your-name ; let 绑定的变量来自用户输入 )))) (greeting Bastien)最后把结果完整地展示到另一个窗口中(defun greeting (from-name) (let ((your-name (read-from-minibuffer Enter your name: ))) (switch-to-buffer-other-window *test*) (erase-buffer) (insert (format Hello %s!\n\nI am %s. your-name from-name)) (other-window 1))) (greeting Bastien)执行(greeting Bastien)后先输入你的名字然后*test*窗口就会显示一条完整的双向问候。再休息一下接下来进入数据操作。九、列表操作quote、car、cdr、push 与 mapcar把一组名字存入一个列表。注意列表前的单引号用于阻止求值——它引用数据本身而不是把(Sarah ...)当作函数调用。这一要点在英文原版 elisp.md 中被特别强调要创建字面量数据列表就用阻止它被求值字面意思即引用quote这份数据。(setq list-of-names (Sarah Chloe Mathilde))取列表的第一个元素用car(car list-of-names) ;; Sarah取除第一个元素之外的其余部分用cdr(cdr list-of-names) ;; (Chloe Mathilde)在列表头部添加元素用push(push Stephanie list-of-names)这里有一个非常重要的区别car和cdr不会修改列表而push会。也就是说有些函数没有副作用如car而有些函数有副作用如pushLisp 术语中称为 side-effects。理解这一点有助于判断一个表达式到底是读取数据还是改变状态。用mapcar把hello依次应用到列表的每个元素上——hello每收到一个名字就调用一次(mapcar hello list-of-names)重构greeting让它向list-of-names中的每一个人问好(defun greeting () (switch-to-buffer-other-window *test*) (erase-buffer) (mapcar hello list-of-names) (other-window 1)) (greeting)十、循环与文本替换while、search-forward、replace-match现在对*test*中的内容做自动化整理。goto-char配合point-min可以把光标移到 buffer 开头while构成循环search-forward搜索字符串replace-match替换当前匹配(defun replace-hello-by-bonjour () (switch-to-buffer-other-window *test*) (goto-char (point-min)) (while (search-forward Hello) (replace-match Bonjour)) (other-window 1))逐条拆解这个函数(goto-char (point-min))把光标移到 buffer 开头(search-forward Hello)向后搜索字符串Hello(while x y)只要x返回非nil即有东西就反复求值y一旦x返回nil即什么也没有就退出循环。执行(replace-hello-by-bonjour)后*test*buffer 中所有Hello都被替换成了Bonjour——但你同时会得到一个报错Search failed: Hello。原因很简单当search-forward搜到 buffer 末尾再也找不到Hello时它默认会抛错终止。要消除这个错误需要告诉search-forward两件事是否把搜索限定在某个位置以及找不到时是否静默失败。写法是(search-forward Hello nil t)参数nil表示搜索不绑定到任何位置即从当前点一直搜到 buffer 末尾参数t表示找不到时静默失败不抛错。把这个写法套进完整函数就得到一个无报错的Hello → Bonjour批量替换器(defun hello-to-bonjour () (switch-to-buffer-other-window *test*) (erase-buffer) ;; 向 list-of-names 中的每个名字问好 (mapcar hello list-of-names) (goto-char (point-min)) ;; 把 Hello 替换成 Bonjour (while (search-forward Hello nil t) (replace-match Bonjour)) (other-window 1)) (hello-to-bonjour)十一、正则表达式搜索re-search-forward 与文本属性加粗最后给名字加上视觉效果。这里引入re-search-forward与search-forward按字面字符串搜索不同它按**正则表达式Regular Expression**模式搜索前缀re-即 Regular Expression 的缩写。正则Bonjour \\(.\\)!的解读如下字符串Bonjour 后面跟着一个分组由\\( ... \\)构造表示分组内是任意一个字符由.表示且至少出现一次由表示最后是字符串!。即匹配Bonjour 至少一个任意字符 !并把中间那部分捕获为第 1 组。(defun boldify-names () (switch-to-buffer-other-window *test*) (goto-char (point-min)) (while (re-search-forward Bonjour \\(.\\)! nil t) (add-text-properties (match-beginning 1) (match-end 1) (list face bold))) (other-window 1)) (boldify-names)add-text-properties给文本添加属性text properties例如字体样式(match-beginning 1)与(match-end 1)分别取第 1 个捕获组的起止位置(list face bold)则把这一段文本的face属性设为bold粗体。执行(boldify-names)后*test*buffer 中每个名字都会以粗体显示。到这里一个完整的小项目已经闭环问候列表中的每个人 → 把问候语中的Hello批量替换为Bonjour→ 再用正则把名字加粗。整个流程涉及的全部是 Emacs 编辑器的原生能力且全程在 buffer 中进行无需触碰任何文件。十二、深入学习的路径Emacs 内置帮助系统教程结尾给出了继续深挖的自学路线全部内置于 EmacsC-h v 变量名 RET查看一个变量的帮助文档C-h f 函数名 RET查看一个函数的帮助文档C-h i m elisp RET在 Emacs 内置的 Info 阅读器中打开完整的Emacs Lisp 手册。对中文读者来说另一个低门槛的学习方式是横向对照本仓库中该教程的不同语言版本西班牙语版 es/elisp.md 与英文原版 elisp.md 结构完全一致此外还有 法语版 fr/elisp.md、马来语版 ms/elisp.md、巴西葡萄牙语版 pt-br/elisp.md 以及 简体中文版 zh-cn/elisp.md 可供对照——同一份代码、多种语言讲解正是 learnxinyminutes-docs 仓库以代码写文档理念的体现。到此为止你已经掌握了 sexp 求值、变量与函数、多窗口 buffer 管理、列表遍历、循环替换和正则匹配加粗这一整套 Emacs Lisp 入门技能。剩下的就是打开 Emacs把光标移到每条 sexp 后面按下C-x C-e亲身体验边写边求值的即时反馈。Happy hacking赞分享文档教程【免费下载链接】learnxinyminutes-docsCode documentation written as code! How novel and totally my idea!项目地址https://gitcode.com/gh_mirrors/le/learnxinyminutes-docs点击查看免费下载相关推荐AWK 快速上手用模式与动作征服文本处理的实战指南learnxinyminutes-docsAWK 快速上手用模式与动作征服文本处理的实战指南learnxinyminutes docs AWK 是每个 POSIX 兼容 UNIX 系统上都自带的标文档教程learnxinyminutes-docs 中的 Common Lisp 快速上手指南从语法基础到宏与 CLOS 的完整实战解析learnxinyminutes docs 中的 Common Lisp 快速上手指南从语法基础到宏与 CLOS 的完整实战解析 Common Lisp 是一文档教程Clojure 速成指南learnxinyminutes-docs从 Lisp 语法到 JVM 并发与 STM 状态管理Clojure 速成指南learnxinyminutes docs从 Lisp 语法到 JVM 并发与 STM 状态管理 Clojure 是一门运行在 J文档教程上一篇django-import-export部署指南生产环境配置与最佳实践下一篇phpseclib vs OpenSSL终极性能对比指南哪个更适合你的项目创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表