Slides 终端演示实战:用 Markdown 编写 ASCII 主题幻灯片的完整指南

原创2026-09-22 16:15:501,619 阅读
文章标签:CLI开发工具

Slides 终端演示实战:用 Markdown 编写 ASCII 主题幻灯片的完整指南

本篇指南以仓库中的官方示例 examples/ascii_slides.md 为主线,系统讲解 Terminal 演示工具 Slides 的幻灯片编写语法:从 YAML front matter 主题配置、--- 分隔符的切页规则,到 Markdown 组件支持、代码执行与预处理等进阶能力。读完你将掌握直接编写、运行、导航、搜索乃至通过 SSH 远程展示一套终端幻灯片的全套方法。

Slides 终端演示工具运行效果示意图
Slides 在终端中渲染 Markdown 幻灯片的实际效果(来源:仓库 README.md)

从官方示例开始:一份最小的 ASCII 幻灯片

仓库的 examples/ascii_slides.md 是一份完整可运行的示例文件,它同时演示了 front matter 配置、标题层级、列表、表格和代码块等多种语法。全文如下:

---
theme: ascii
---

# Welcome to Slides
A terminal based presentation tool

```go
package main

import "fmt"

func main() {
  fmt.Println("Written in Go!")
}
```

---

## Everything is markdown
In fact this entire presentation is a markdown file

---

# h1
## h2
### h3
#### h4
##### h5
###### h6

# Markdown components
You can use everything in markdown!
* Like bulleted list
* You know the deal

1. Numbered lists too

| Tables | Too    |
| ------ | ------ |
| Even   | Tables |

---

All you need to do is separate slides with triple dashes `---` on a separate line,
like so:

```markdown
# Slide 1
Some stuff

---

# Slide 2
Some other stuff
```

这份示例文件可以被直接运行:

slides examples/ascii_slides.md

它展示了 Slides 的核心设计理念:演示文稿就是一个 Markdown 文件,无需任何 GUI 编辑器或转换工具,一切都在终端内完成。

幻灯片切页规则:--- 分隔符

Slides 使用独占一行的三个连字符 --- 来分隔幻灯片,这是整份语法的基础:

# Slide 1
Some stuff

---

# Slide 2
Some other stuff

从源码实现看,--- 分隔符在 internal/model/model.go 中被定义为常量 delimiter = "\n---\n",Load() 方法通过 strings.Split(content, delimiter) 将整个 Markdown 文件按分隔符切分成幻灯片数组:

const (
	delimiter = "\n---\n"
)

// ...
slides := strings.Split(content, delimiter)

两点关键细节:

  1. 分隔符前后必须换行:只有独占一行的 --- 才会被识别为分页符,行内出现的 --- 不会触发切页;
  2. front matter 不占页:如果文件头部存在 YAML front matter(--- 包裹的元数据),meta.Parse() 解析成功后会将其识别为配置节而非第一张幻灯片,并自动跳过(见 internal/model/model.go)。

一切皆 Markdown:支持的组件

示例文件第二张幻灯片直言:"In fact this entire presentation is a markdown file"(事实上整份演示就是一个 Markdown 文件)。Slides 基于 charmbracelet/glamour 渲染 Markdown,因此标准 Markdown 语法均可直接使用:

  • 标题层级:# h1 到 ###### h6 共六级标题;
  • 无序列表:* item / - item;
  • 有序列表:1. item;
  • 表格:管道符语法 | A | B |;
  • 代码块:围栏式代码块(详见下文"代码执行");
  • 引用、粗体、斜体、链接等常规 Markdown 元素。

渲染流程在 internal/model/model.go 的 View() 方法中:glamour 将当前页的 Markdown 渲染为终端彩色文本,再经由 styles.Slide.Render() 添加内边距,最终与底部状态栏(作者、日期、页码)拼合输出。

主题系统:theme: ascii 是如何生效的

示例文件头部第一行就是主题配置:

---
theme: ascii
---

theme 字段决定整份演示的配色与样式。主题选择逻辑集中在 styles/styles.go 的 SelectTheme() 函数中,它支持三类取值:

theme 取值 效果 实现依据
ascii 纯 ASCII 风格(无 ANSI 颜色码,兼容性最好) glamour.WithStyles(glamour.ASCIIStyleConfig)
light 浅色背景配色 glamour.LightStyleConfig
dark 深色背景配色 glamour.DarkStyleConfig
notty 非 TTY 输出(管道/重定向场景) glamour.NoTTYStyleConfig
本地 JSON 路径 如 theme: ./path/to/theme.json,加载自定义 glamour 主题 os.Open + WithStylesFromJSONBytes
HTTP(S) 链接 如 theme: https://example.com/theme.json,演示前自动拉取 http.Get + WithStylesFromJSONBytes

仓库内置的默认主题文件是 styles/theme.json。当未指定主题或指定的文件/链接读取失败时,getDefaultTheme() 会根据终端背景色自动选择 light/dark 或内置默认主题(styles/styles.go)。

提示:仓库的 examples/theme.json 和 examples/custom_theme.md 展示了自定义主题的具体写法,examples/custom_remote_theme.md 则演示了远程主题的用法,可作为参考。

运行与实时刷新

基本用法

slides presentation.md

如果传入的是文件名,Slides 会自动监听文件变更并实时刷新演示(每秒钟检查一次文件的修改时间,见 internal/model/model.go 的 fileWatchCmd 与 fileWatchMsg 处理逻辑),非常适合边编辑边演示的工作流。

通过 stdin 输入

slides 同样接受标准输入:

curl http://example.com/slides.md | slides

当没有传入文件名且 stdin 为空时,Slides 会显示内置的教学演示(tutorial),其内容嵌入在 internal/model/tutorial.md 中。

导航快捷键

Slides 借鉴了 Vim 的键位设计,导航状态机实现在 internal/navigation/navigation.go。完整键位如下:

操作 按键
回到第一页 g g
下一页 space、right、down、enter、n、j、l、Page Down
上一页 left、up、p、h、k、N、Page Up
跳转到指定页 数字 + G(如 5G 跳到第 5 页)
跳转到最后一页 G
退出 q、ctrl+c

数字前缀是这套导航的精髓:先按数字再按翻页键,可一次前进/后退 N 张幻灯片。从源码看,数字会累积进缓冲区(Buffer),随后由 repeatableAction() 循环执行 navigateNext/navigatePrevious;其中 0 前缀遵循 Vim 惯例不生效(internal/navigation/navigation.go)。

搜索:按正则跳转

按 / 进入搜索模式,输入关键词后回车即可跳转到匹配的幻灯片:

  • 搜索词按正则表达式解析;
  • 以 /i 结尾可实现大小写不敏感搜索(实现见 internal/navigation/search.go:追加 (?i) 内联标志);
  • 搜索会从当前页向后找,找不到再回绕到开头(internal/navigation/search.go);
  • 搜索后按 ctrl+n 跳到下一个匹配结果(搜索缓冲会保留,见 Done() 的设计注释);
  • esc / ctrl+c 取消搜索。

代码执行:在幻灯片里跑代码

示例文件中的 Go 代码块并不是装饰——当光标停留在包含代码块的幻灯片上时:

  1. 按 ctrl+e,Slides 会解析当前页的所有代码块、逐个执行,并把输出以"虚拟文本"(virtual text)的形式显示在幻灯片末尾(internal/model/model.go);
  2. 按 y 可将当前页代码块的源码复制到系统剪贴板。

执行流程见 internal/code/code.go:

  • 代码块必须显式标注语言(围栏后紧跟语言名),否则无法执行;
  • 代码被写入系统临时目录中的临时文件(如 slides-*.go);
  • 命令支持三个占位符:<file>(临时文件完整路径)、<name>(不含扩展名的文件名)、<path>(所在目录)。

目前支持 20 种语言,完整映射表见 internal/code/languages.go,摘录如下:

语言 围栏标识 执行命令
Go go go run <file>
Python python python <file>
JavaScript javascript node <file>
Bash bash bash <file>
Rust rust rustc <file> -o <path>/<name>.run 后运行
C++ cpp g++ -std=c++20 -o <path>/<name>.run <file> 后运行
Java / Ruby / Lua / Perl / OCaml / Elixir / Julia / Swift / Dart / Scala / Haskell / Zsh / Fish / V 对应标识 各语言的解释器/编译器

示例文件里的 Go 代码块按下 ctrl+e 后,会在幻灯片底部输出:

package main

import "fmt"

func main() {
  fmt.Println("Written in Go!")
}

即 Written in Go! 这一行文本。

预处理:演示前执行命令

除了运行时代码执行,Slides 还支持预处理:在展示前把幻灯片中的特殊代码块替换为命令输出。语法是三个波浪号 ~~~ 包裹的块,第一行是命令,块内内容作为命令的 stdin 传入,整块被命令的 stdout 替换:

```
~~~graph-easy --as=boxart
[ A ] - to -> [ B ]
~~~
```

上述内容在展示前会被替换为:

┌───┐  to   ┌───┐
│ A │ ────> │ B │
└───┘       └───┘

如果想保留替换后文本的格式与换行,可以把 ~~~ 块再包进三层反引号中(如上方示例所示)。

实现细节见 internal/process/process.go:Parse() 通过正则匹配 ~~~command\n...\n~~~ 结构,Block.Execute() 将块内容通过 stdin 管道写入命令进程并捕获 stdout,Pre() 最后用输出替换原始块。

安全机制:可执行文件权限

出于安全考虑,只有具备执行权限的文件才会被预处理。这是为了防止无意中执行幻灯片中的恶意命令——readFile 在读取时检查 file.IsExecutable(s)(internal/model/model.go)。因此使用预处理功能前需要显式添加执行权限:

chmod +x file.md

如果文件带 shebang(#! 开头),Slides 会先剥离该行再处理(internal/model/model.go)。

元数据配置:front matter 详解

在 Markdown 文件顶部用 --- 包裹的 YAML 区域即 front matter,可用于自定义演示外观。该区块完全可选,省略任何字段都会使用默认值(解析逻辑见 internal/meta/meta.go)。

---
theme: ./path/to/theme.json
author: Gopher
date: MMMM dd, YYYY
paging: Slide %d / %d
---
字段 说明 默认值
theme glamour 主题 JSON 文件路径,也可以是远程 URL(演示前自动抓取) 随终端环境自动选择
author 显示在演示视图左下角的作者名,可设为空字符串隐藏 当前 OS 用户全名
date 日期格式字符串,按 YYYY-MM-DD 语义格式化当天日期;若格式非法则原样显示 YYYY-MM-DD
paging 页码模板,第一个 %d 替换为当前页码,第二个 %d 替换为总页数;以 % 开头时需要用引号包裹 Slide %d / %d

其中 paging 的取值逻辑在 internal/model/model.go 的 paging() 方法中:根据字符串中 %d 的数量(2 个、1 个或 0 个)决定格式化方式。

日期格式对照表

以日期 January 02, 2006(2006 年 1 月 2 日)为例:

值 翻译结果
YYYY 2006
YY 06
MMMM January
MMM Jan
MM 01
mm 1
DD 02
dd 2

这些占位符到 Go 时间格式的映射定义在 internal/meta/meta.go 的 parseDate() 中,替换完成后交给 time.Now().Format() 输出。

远程演示:通过 SSH 共享幻灯片

Slides 内置 SSH 服务器,可在没有安装 Slides 的机器上远程观看演示:

在一台机器上启动服务:

slides serve [file]

然后在任意一台有 ssh 的机器上连接(默认端口 53531):

ssh 127.0.0.1 -p 53531

使用场景包括:在不便安装 Slides 的电脑上演示、让观众在各自终端同步观看而无需分发文件和二进制。服务实现见 internal/cmd/serve.go,该命令还支持以下参数与环境变量:

参数 环境变量 默认值 说明
--keyPath SLIDES_SERVER_KEY_PATH slides SSH 服务器私钥路径
--host SLIDES_SERVER_HOST localhost 绑定主机
--port SLIDES_SERVER_PORT 53531 监听端口

安装方式

不同平台可用对应的包管理器安装(详见 README.md):

# macOS
brew install slides

# Arch Linux
yay -S slides

# Nixpkgs (unstable)
nix-env -iA nixpkgs.slides

# 任意支持 snapd 的 Linux
sudo snap install slides

# 通过 Go 安装
go install github.com/maaslalani/slides@latest

也可以从源码构建(需要 Go 工具链):

git clone https://github.com/maaslalani/slides.git
cd slides
go install

更多示例与深入阅读

仓库 examples 目录提供了多份可直接运行的示例文件,覆盖了本文所有主题:

若想了解项目自身的开发与构建流程,可参阅 docs/development/README.md;导航、搜索、预处理、代码执行等模块的测试用例分别在 internal/navigation、internal/process、internal/code 目录下,可作为理解各功能边界的第一手资料。

登录后查看全文
slides