Slides 终端演示工具实战指南:用 Markdown 在终端里完成幻灯片编写、代码执行与远程演示
Slides 终端演示工具实战指南:用 Markdown 在终端里完成幻灯片编写、代码执行与远程演示
Slides 是一个完全运行在终端里的演示文稿工具,它把「演示文稿」简化为「一个 Markdown 文件」:你用纯文本组织内容、用 --- 分隔页面,剩下的排版、高亮、翻页、代码执行全部交给工具处理。本文以仓库内置教程 internal/model/tutorial.md 为主线,结合 README 与 Go 源码(入口 main.go、核心模型 internal/model/model.go),完整讲解幻灯片编写、键盘导航、代码执行、预处理、元数据配置与 SSH 远程演示,读完即可上手制作并演示你自己的终端幻灯片。
一切皆 Markdown:演示文稿就是一个 Markdown 文件
Slides 的核心设计理念,用内置教程 tutorial.md 的原话来说就是:Everything is markdown——整场演示本身就是一份 Markdown 文件。这份文件同时也是仓库的一部分:当你直接运行 slides 而不带任何参数时,程序会通过 //go:embed tutorial.md 把教程嵌入二进制并播放(见 internal/model/model.go 与 readStdin 的兜底逻辑)。也就是说,你第一次打开这个工具时看到的,就是一份最好的入门示例。
一张幻灯片就是一个独立的 Markdown 片段,幻灯片之间用独占一行的三个连字符 --- 分隔:
# Slide 1
Some stuff
---
# Slide 2
Some other stuff
在 internal/model/model.go 中可以看到解析逻辑:文件内容先去掉开头的分隔符,再按常量 delimiter = "\n---\n" 切分,得到字符串切片 m.Slides,其中每个元素渲染为一页;如果第一页是 YAML front matter(以 --- 开头),它会被解析为配置而非幻灯片。由于 Markdown 渲染由 charmbracelet/glamour 完成,你在 GitHub、笔记软件里熟悉的一切 Markdown 语法都能直接用:
- 六级标题
# h1到###### h6 - 无序列表
* like bulleted list与有序列表1. Numbered lists too - 表格:
| Tables | Too |
|---|---|
| Even | Tables |
- 代码块与语法高亮
- 以及通过 dot 图 + 预渲染 ASCII 图表达的图表(
digraph { rankdir = LR; a -> b; b -> c; }配合手工绘制的┌───┐ ──▶方框箭头图)
安装与快速启动
在开始写幻灯片之前,先把它装到你的机器上。根据 README.md,支持以下安装途径:
- macOS(Homebrew):
brew install slides - Arch Linux(AUR):
yay -S slides - Nixpkgs(unstable):
nix-env -iA nixpkgs.slides - 任意运行
snapd的 Linux 发行版:sudo snap install slides - Go 工具链:
go install github.com/maaslalani/slides@latest - 从源码构建:先
git clone本仓库(git clone https://gitcode.com/gh_mirrors/sl/slides.git),进入目录后执行go install
安装完成后,三种启动方式:
# 1. 播放一个 Markdown 文件(推荐)
slides presentation.md
# 2. 从 stdin 接收内容,适合管道组合
curl http://example.com/slides.md | slides
# 3. 不带参数:直接播放内嵌的官方教程(即 tutorial.md)
slides
值得说明的是「实时重载」:只要传入的是文件名,Slides 会每秒检查一次该文件的修改时间(见 internal/model/model.go 中的 fileWatchMsg),文件一变就自动重新加载并刷新当前页,非常适合边写边演示——改完保存,画面立即更新,无需重启。
在幻灯片中展示并执行代码
教程的第二页展示了代码块的能力——展示代码只是基本功,执行代码才是亮点:
package main
import "fmt"
func main() {
// You can show code in slides
// Press ctrl+e to execute this code directly in slides
fmt.Println("Tada!")
}
按下 ctrl+e,Slides 会解析当前页的所有代码块,逐个执行,并把输出以「虚拟文本(VirtualText)」的形式追加在幻灯片末尾显示(实现见 internal/model/model.go:code.Parse 解析 → code.Execute 执行 → 结果拼入 m.VirtualText)。切页时虚拟文本自动清空(SetPage 中 m.VirtualText = "")。
从源码 internal/code/languages.go 可以看到,目前支持 20 种语言:bash、zsh、fish、elixir、go、javascript、lua、ocaml、perl、python、ruby、rust、java、julia、cpp、swift、dart、v、scala、haskell。
执行过程(internal/code/code.go)的底层逻辑是:把代码块写入 os.TempDir 下的临时文件(按语言扩展名命名,如 slides-*.go),再用该语言对应的命令运行它,最后把 stdout 收集为结果。语言表里还支持三个占位符,便于构造复杂命令:
<file>:临时文件的完整路径<name>:不带扩展名和路径的文件名<path>:临时文件所在目录
以 Rust 为例,它需要两步「先编译后运行」:{"rustc", "<file>", "-o", "<path>/<name>.run"} 再 {"<path>/<name>.run"};C++ 同理使用 g++ -std=c++20 编译。这意味着只要你的系统装了对应语言运行时,代码块就能真实跑起来,非常适合技术分享时做「现场演示」。
另外,代码块还支持注释隐藏与一键复制:幻灯片上的注释默认不渲染(code.HideComments),按 y 可以把当前页代码块的代码写入系统剪贴板(见 internal/model/model.go)。
键盘导航:Vim 风格的数字缓冲与跳转
README.md 完整列出了翻页键位,全部继承自 Vim 的习惯,先按数字再按动作键即可「跳 N 页」:
| 动作 | 按键 |
|---|---|
| 回到第一页 | g g |
| 下一页 | space、right、down、enter、n、j、l、Page Down;数字 + 以上任意键 = 前进 n 页 |
| 上一页 | left、up、p、h、k、N、Page Up;数字 + 以上任意键 = 后退 n 页 |
| 跳到指定页 | 数字 + G |
| 跳到最后一页 | G |
| 退出 | ctrl+c 或 q |
这套「数字缓冲」机制在 internal/navigation/navigation.go 中实现:按下数字键时先累积进 State.Buffer(如先按 3 再按 5 得到 "35"),随后按下翻页/跳转键时,repeatableAction 会把缓冲解析为重复次数逐页推进,或在 navigateSlide 中解析为具体目标页(destinationSlide-- 因为页码从 1 开始计数,且越界时收敛到首尾页)。例如 5 + n 表示前进 5 页,12 + G 表示跳到第 12 页。实现中还特意注释了「repeat 为 0 时退化为普通单步」,与 Vim 的 least astonishment 原则保持一致。
幻灯片搜索:正则定位与结果跳转
演示内容多时,可以用搜索快速定位。按 / 进入搜索模式(底部状态栏变为搜索输入框,见 internal/navigation/search.go 的 NewSearch:占位符 search、提示符 /),输入关键词后回车即跳转到第一个匹配的幻灯片。
两个细节值得注意:
- 搜索词是正则表达式:底层用
regexp.Compile(expr)编译用户输入,因此可以写foo|bar、^\d+$这类模式; /i后缀开启忽略大小写:expr以/i结尾时会被改写为(?i)expr(见Search.Execute),例如/banner/i会同时匹配Banner与banner。
搜索顺序是从「当前页的下一页」扫到末尾,再折回开头,确保循环覆盖;按 ctrl+n 可以在一次搜索后跳到下一个结果(m.Search.Execute(&m) 会被再次调用)。按 esc 或 ctrl+c 取消搜索。
预处理(Pre-processing):把命令输出直接渲染进幻灯片
这是 Slides 最具「终端原生气质」的特性:你可以在幻灯片里写一个三波浪线 ~~~ 围起来的块,块的第一行是命令,后面是喂给该命令的 stdin,Slides 在展示前会执行命令,并用命令的 stdout 替换整个块。README 与教程中的经典例子是用 graph-easy 把图描述转成 boxart 字符画:
~~~graph-easy --as=boxart
[ A ] - to -> [ B ]
~~~
渲染前被替换为:
┌───┐ to ┌───┐
│ A │ ────> │ B │
└───┘ └───┘
如果想让输出保持原有换行与格式,把预处理块用三个反引号包裹即可(README 有专门说明)。
安全机制是重点:出于安全考虑,Slides 只对「具有执行权限」的文件执行预处理,避免无意间运行恶意命令。你需要先:
chmod +x file.md
源码侧有两处印证:internal/file/file.go 的 IsExecutable 判断文件权限位 0111 是否齐全(即 owner/group/other 三者都有执行位);internal/model/model.go 的 readFile 在文件可执行时调用 process.Pre(content),若文件以 shebang #! 开头还会先剥掉首行再处理。预处理流程(internal/process/process.go)是:正则 ~~~(.+)\n(?:.|\n)*?\n~~~\s? 解析出所有块 → Execute 用 exec.Command 把块内文本经 stdin 管道喂给命令 → Pre 用输出替换原始块。因此它本质上是「幻灯片文件 = 模板 + 命令渲染」,可以把任何能输出文本的命令(图表、表格生成器、构建产物摘要等)变成幻灯片内容。
用 YAML front matter 配置演示文稿
你可以在 slides.md 顶部用 YAML front matter 定制演示文稿的外观与信息(README.md 的 Configuration 章节)。整段配置都是可选的——缺省字段会自动落到内置默认值:
---
theme: ./path/to/theme.json
author: Gopher
date: MMMM dd, YYYY
paging: Slide %d / %d
---
各字段的语义(对应 internal/meta/meta.go 的实现):
theme:指向一个 glamour 主题的json文件路径,也可以填一个远程http(s)URL——Slides 会在演示前自动拉取(styles/styles.go 的SelectTheme:http开头走 HTTP GET,否则os.Open本地文件,失败则回退默认主题)。另内置四个快捷名:ascii、light、dark、notty。author:显示在左下角的作者名。默认取系统当前用户的完整姓名(user.Current()的Name),设为空字符串""可隐藏。date:按模板格式化今天的日期,默认输出YYYY-MM-DD格式;如果填的字符串无法被解析成日期模板,就原样显示该字符串(internal/meta/meta.go 中parseDate的结果与输入相同时直接展示原文)。paging:页码格式串,可包含 0、1 或 2 个%d——第一个替换为当前页号,第二个替换为总页数。默认Slide %d / %d。注意:如果以%开头,需要用引号包起来,否则会被当作 YAML 注释语法。
paging 的实际渲染在 internal/model/model.go:用 strings.Count(m.Paging, "%d") 判断指令个数,2 个则传 (Page+1, len(Slides)),1 个则只传页码,0 个则原样输出——这也解释了为什么它可以被用来做完全自定义的进度提示。
日期格式对照表
date 模板使用如下占位符(以 January 02, 2006 为例,转换规则定义在 internal/meta/meta.go):
| 值 | 翻译为 |
|---|---|
YYYY |
2006 |
YY |
06 |
MMMM |
January |
MMM |
Jan |
MM |
01 |
mm |
1 |
DD |
02 |
dd |
2 |
注意区分:MM(两位数字月份)与 mm(不带前导零的月份)、DD(两位数字日期)与 dd(不带前导零的日期)。另外,默认主题还受环境变量 GLAMOUR_STYLE 影响(defaultTheme 优先读取它),方便你在不修改文件的前提下切换全局渲染风格。
通过 SSH 在远程终端演示
Slides 内置了一个 SSH 服务器(internal/cmd/serve.go),让你把演示「托管」在一台机器上,观众通过任意装有 ssh 的机器直接观看,无需安装 Slides、也无需拿到演示文件。
在托管机器上启动:
slides serve [file]
然后从任意机器(包括本机)SSH 连接到它输出的端口:
ssh 127.0.0.1 -p 53531
连接后即可看到演示画面。这对「用一台不装 Slides 的电脑做演示」或「让观众在自己电脑上同步看稿」都非常实用。serve 命令(别名 server)的默认参数为:端口 53531、host localhost、私钥路径 slides,均可通过命令行 flag(--keyPath、--host、--port)覆盖,也支持环境变量 SLIDES_SERVER_KEY_PATH、SLIDES_SERVER_HOST、SLIDES_SERVER_PORT(见 internal/cmd/serve.go 与 flag 定义)。服务优雅停机:收到 SIGINT/SIGTERM 后等待最多 30 秒完成收尾。
结语与仓库索引
总结一下,用 Slides 做演示的完整工作流是:写一个普通 Markdown 文件(可执行文件还能启用预处理)→ slides presentation.md 启动(自动热重载)→ 用 n/space/gg/G 等键翻页 → / 正则搜索 → ctrl+e 现场执行代码 → 需要分享时 slides serve 交给 SSH。整个渲染管线是「front matter 配置 + glamour 渲染 + lipgloss 状态栏」,实现清晰、可读性强,适合想深入研究的读者继续阅读:
- 内置教程原文:internal/model/tutorial.md
- 完整使用手册与键位表:README.md
- 演示模型、热重载与虚拟文本:internal/model/model.go
- 键盘导航与数字缓冲:internal/navigation/navigation.go
- 正则搜索:internal/navigation/search.go
- 代码执行与 20 种语言支持:internal/code/code.go、internal/code/languages.go
- 预处理管道:internal/process/process.go、internal/file/file.go
- 元数据解析与日期模板:internal/meta/meta.go
- 主题选择与状态栏样式:styles/styles.go
- SSH 服务端命令:internal/cmd/serve.go
- 开箱即用的示例演示:examples/slides.md
