面向新手、但不牺牲 FOEK 理念深度的入门教程
🧭 tmux‑fsm 新手入门教程
从“按键”到“事实”的第一次迁移
在开始之前(非常重要)
tmux‑fsm 不是 Vim,
也不是 tmux 的一组快捷键。
它是一个 常驻内存的编辑内核(FOEK),
而 tmux 只是它的 输入输出设备。
👉 如果你期待的是:
- “装上就跟 Vim 一模一样”
- “一个 key binding 文件”
- “按一下就跑、跑完就退出的脚本”
请现在就停下。
你会用得非常痛苦。
tmux‑fsm 到底在做什么?
一句话版本:
tmux‑fsm 不编辑字符,它编辑「意义在时间中的变化」。
它做了三件传统 tmux 插件做不到的事:
- 拥有状态(而不是存到 tmux option)
- 记住时间线(事务、撤销、审计)
- 用事实定位文本(而不是光标猜测)
这也是为什么它需要一个 Go 守护进程。
🧱 核心概念速读(新手必看)
在真正开始用之前,你只需要记住 5 个词。
1️⃣ Kernel(内核)
- tmux‑fsm 的 Go Server
- 常驻后台
- 唯一的真相来源
- FSM、Undo、事务、锚点都在这里
tmux ≠ 状态来源
tmux = 键盘 + 屏幕
2️⃣ Mode(模式)
和 Vim 类似,但由 显式 FSM 驱动:
NORMALOPERATOR_PENDINGMOTION_PENDINGVISUALREGISTER_SELECT
你看到的状态栏文字,来自 内核实时状态。
3️⃣ Fact(事实)
一次编辑 ≠ 一串按键
一次编辑 = 一个 事实
一个 Fact 包含:
- Range(范围)
- Anchor(锚点)
- Operation(意图)
撤销时,内核会重新寻找这个事实的位置。
4️⃣ Transaction(事务)
像 3dw 这样的操作:
- ✅ 要么 全部成功
- ❌ 要么 整体跳过
永远不会留下半删的垃圾状态。
5️⃣ Anchor Resolver(定位引擎)
撤销时,内核会按顺序尝试:
- Exact:精确匹配
- Fuzzy:模糊匹配(会提示
~UNDO) - Fail:失败(安全拒绝,
!UNDO_FAIL)
🚀 第一步:安装并启动内核
安装
./install.sh
这一步会:
- 编译 Go 内核
- 启动守护进程
- 自动配置 tmux
- 预热 Kernel
✅ 安装完成后,不需要手动启动服务。
验证是否成功
进入 tmux 后执行:
tmux show-messages
或检查 socket:
ls ~/.tmux-fsm.sock
🎹 第二步:进入 FSM 模式
在 tmux 中:
<prefix> f
你会看到状态栏显示:
NORMAL
这意味着:
✅ 内核在线
✅ FSM 正常
✅ tmux 已连接成功
退出 FSM:
Esc 或 Ctrl-c
🧠 第三步:从最基础的操作开始
✅ 移动(不会修改任何东西)
h j k l 左 下 上 右
w b e 单词移动
0 $ 行首 / 行尾
gg G 顶部 / 底部
这些操作:
- 不创建 Fact
- 不进入事务
- 只是改变视角
✅ 第一次真正的“编辑事实”
试试:
dw
FSM 路径是:
NORMAL → OPERATOR_PENDING → EXECUTE → NORMAL
内核会:
- 创建一个 delete Fact
- 记录精确范围
- 推入事务栈
✅ 数字前缀是“事务的一部分”
3dw
这 不是 3 次 dw
而是 一个事务,包含 3 个事实
撤销时:
- ✅ 要么全部撤
- ❌ 要么全部跳过
↩️ 第四步:真正理解 Undo / Redo
Undo
u
Redo
Ctrl-r
但要注意:
Undo 不是保证一定成功的。
如果环境变化太大(Prompt 刷新、外部输出):
- 内核会选择 保护现场
- 状态栏显示:
!UNDO_FAIL
这是 设计目标,不是 Bug。
🔍 查询为什么 Undo 失败
在 FSM 模式中输入:
__WHY_FAIL__
你会看到类似:
Anchor mismatch due to prompt shift
这叫 可审计性(Auditability)。
🌌 第五步:第一次接触 Spatial Echo(进阶)
这是 tmux‑fsm 最“反直觉”,也是最强大的地方。
示例
3dw
现在,这 3 个删除事实被“武装”了。
移动到任意位置,然后:
gd
内核会:
- 瞬移到 3 个锚点
- 重新执行同一个删除意图
✅ 没有多光标
✅ 没有新模式
✅ 只是时间线的再投影
🧪 常见新手误区
❌ “为什么有时候 Undo 不工作?”
✅ 因为 tmux‑fsm 优先保护现场一致性
❌ 它不会为了“看起来成功”而破坏状态
❌ “为什么不用 tmux option 存状态?”
因为:
- tmux option ≠ 原子
- tmux option ≠ 并发安全
- tmux option ≠ 时间线
FOEK 必须 拥有状态
❌ “我能把它当 Vim 用吗?”
可以学 Vim 的 语义模型
但不要期待 Vim 的 实现方式
✅ 你现在已经会什么了?
到这里,你已经:
- ✅ 理解 tmux‑fsm 的定位
- ✅ 会进入 / 退出 FSM
- ✅ 会安全地编辑
- ✅ 会 Undo / Redo
- ✅ 知道为什么系统会拒绝你
- ✅ 见过 Spatial Echo 的雏形
🧭 下一步建议
当你准备好了,可以继续探索:
- VISUAL 模式与 Range Fact
- 文本对象(
iw,i",ap) - 寄存器与追加语义
- 审计日志与事务栈可视化
- FOEK Kernel 内部接口
tmux‑fsm 不要求你“记住快捷键”。
它要求你 理解时间、状态与意图。
如果这正是你想要的,
欢迎来到 FOEK。
tmux-fsm: Fact-Oriented Editing Kernel (FOEK)
tmux‑fsm
tmux‑fsm is not a tmux plugin.
tmux‑fsm is a headless editing kernel running as a long‑lived daemon.
tmux is merely its TTY frontend for input and display.
This project is NOT for you if you want:
- A drop‑in key binding collection
- A stateless script that runs and exits
- Something that stores its state in tmux options
- “Just another tmux plugin”
tmux‑fsm persists in memory, owns the state machine, and enforces its own timeline.
To tmux‑fsm, tmux is strictly a dumb I/O device — never the source of truth.
This architecture exists to enable things traditional tmux plugins cannot do:
semantic undo, spatial replay, multi‑step FSM reasoning, and sub‑millisecond reaction time.
If this sounds excessive, unfamiliar, or unnecessary —
you should stop reading here.
—
Who this project is for
tmux‑fsm is designed for users who:
- are comfortable running background daemons
- understand client/server architectures
- care about temporal continuity and state ownership
- want an editing kernel, not a shortcut collection
Everyone else will be happier with a conventional tmux plugin.
tmux‑fsm does not edit text.
It edits meaning over time.
一个基于 FOEK (事实导向编辑内核) 理念的 tmux 模式插件。它不仅为 tmux 提供了 Vim 风格的导航,更在终端层面上实现了一套具备 空间感 (Spatial Awareness) 与 时间线感 (Timeline Awareness) 的编辑内核。
🌌 内核核心:FOEK (Fact-Oriented Editing Kernel)
tmux-fsm 不仅仅是一个插件,它是一个高性能、常驻内存的编辑内核,专注于三个核心领域:高性能响应、语义化一致性、以及工业级安全性。
为极致性能而生:Go Daemon 内核
- 服务端 (Daemon): 全 Go 编写,常驻内存,处理 FSM 状态转换与复杂逻辑。响应时间 < 1ms。
- 客户端 (Client): 极简二进制,仅负责通过 Unix Socket 发送按键,瞬间退出,零感知。
从“命令”到“事实”的飞跃
在 FOEK 中,编辑不是“按键的模拟”,而是“意图对空间事实的投影”。
- Fact (事实):每个动作(删除、插入、修改)都被记录为一个具备精确范围(Range)和定位锚点(Anchor)的语义事实。
- Transaction (事务):复合操作(如
5dw)被视为原子事务。撤销时要么完整还原,要么为了安全拒绝执行,绝不留下中间错误状态。 - Anchor Resolver (定位引擎):撤销不再依赖光标位置,而是通过 Exact -> Fuzzy -> Fail 三层策略在面板中搜索文本。
🛡️ 工业级安全:撤销安全公理 (Undo Safety Axioms)
tmux-fsm 实现了目前终端插件中最先进的撤销保护机制。我们遵循一套严格的撤销安全公理:
- 保护现场高于还原文本:当环境发生剧烈变动(如 Shell Prompt 刷新或文本被外部篡改)导致无法 100% 确定位置时,系统会选择 Safe Skip (安全跳过),并标记
!UNDO_FAIL。 - 原子化一致性:事务中任何一步由于安全原因无法执行,整个事务都会被标记为
Skipped,且禁止 Redo。 - 模糊透明度:当系统通过模糊匹配成功找回文本时,状态栏会显示
~UNDO指示,告知用户当前环境已发生偏移。
诊断与审计 (Auditability)
系统不再是一个“黑盒”。如果撤销失败,您可以询问系统:
p键 (STATUS):查看内核当前完整的事务栈。__WHY_FAIL__指令:返回最近一次撤销失败的具体审计原因(例如:Anchor mismatch due to Prompt detection)。
✨ 魔法特性:Spatial Echo (空间回声)
Spatial Echo 是 FOEK 内核成熟后的第一次自然共振。它在无多光标、无新模式的前提下,实现了多点、可重放的编辑。
- Armed Facts (武装事实):执行如
3dw的复合操作时,系统会生成 3 个独立的 Range 事实 并存入缓冲区。 - Global Apply (全局意图):按下
g + 操作符(如gd,g~),内核会瞬间“瞬移”到所有武装锚点并重新执行编辑意图。
🛠 功能特性
- Vim 风格导航:
h/j/k/l,w/b/e,0/$,gg/G,f{char}。 - 结构化操作符:
d(delete),y(yank),c(change),v/V(visual),p/P(paste)。 - 工业级 Undo/Redo:基于事务和 Anchor Resolver 的原子化撤销系统。
- 文本对象:支持
aw,iw,i",ap等高级语义操作。 - 寄存器系统:26 个命名寄存器,支持追加模式,并与系统剪贴板实时同步。
📜 执行架构 (C/S 架构)
graph TD
Key[按键按下] --> Client[tmux-fsm 客户端]
Client -- "Unix Socket" --> Server[tmux-fsm 守护进程]
Server --> Kernel{FOEK Kernel}
Kernel --> Trans[Transaction Management]
Trans --> Resolver[Anchor Resolver: Exact/Fuzzy/Fail]
Resolver --> TMUX[TMUX Surface]
Trans --> History[(Transactional History)]
History -- "Audit Query" --> Why[__WHY_FAIL__]
History -- "Status Display" --> SB[~UNDO / !UNDO_FAIL]
🚀 快速开始
安装
依赖:需要安装 Go (用于编译高性能内核)。
# 1. 克隆仓库并编译安装
./install.sh
安装脚本会自动:
- 编译 Go 二进制文件 (FOEK Kernel)
- 部署插件到
~/.tmux/plugins/tmux-fsm - 自动在
~/.tmux.conf中配置加载项并重新加载
基础操作
- 进入/退出:
<prefix> f进入,Esc退出。 - 工业级撤销:
u(Undo),C-r(Redo)。 - 空间回声:执行
3dw后,在任意位置按gd即可触发全局回声。 - 文本对象:
diw(删除词内),ci"(修改引号内)。 - 诊断失败:输入
__WHY_FAIL__查询最后一次撤销被拒的原因。
许可证
本项目遵循 FOEK 内核宣言,采用 MIT License 授权。
“我们不只是在模拟 Vim,我们是在隔离终端的复杂性。”
卸载
rm -rf ~/.tmux/plugins/tmux-fsm
并从 tmux 配置文件中删除:
source-file "$HOME/.tmux/plugins/tmux-fsm/plugin.tmux"
故障排除
- 确保已安装 Go:编译内核需要 Go 环境。
- 确认 Socket 状态:守护进程会在
~/.tmux-fsm.sock创建连接点。 - 重新加载配置:
tmux source-file ~/.tmux.conf - 手动停止/重启服务端:
pkill -f "tmux-fsm -server" - 如果有问题,可在 tmux 中查看错误信息:
tmux show-messages
FSM 状态转移图(FSM Diagram)
1️⃣ 总览(高层 FSM)
stateDiagram-v2
[*] --> NORMAL
NORMAL --> OPERATOR_PENDING : d / y / c
NORMAL --> MOTION_PENDING : g / f
NORMAL --> REGISTER_SELECT : "
NORMAL --> NORMAL : motion (h j k l w b e 0 $ G)
NORMAL --> NORMAL : count (1-9)
NORMAL --> [*] : Esc / C-c
OPERATOR_PENDING --> MOTION_PENDING : motion
OPERATOR_PENDING --> MODIFIER : a / i
OPERATOR_PENDING --> NORMAL : invalid / cancel
MOTION_PENDING --> NORMAL : motion complete
MOTION_PENDING --> MOTION_PENDING : g (gg)
MOTION_PENDING --> NORMAL : invalid / timeout
MODIFIER --> MOTION_PENDING : text-object (w $ j ...)
MODIFIER --> NORMAL : invalid
REGISTER_SELECT --> NORMAL : register selected
2️⃣ 各状态说明(和代码一一对应)
🟢 NORMAL
默认状态
- 等待:
- 操作符:
d y c - 移动命令:
h j k l w b e 0 $ G - 前缀数字:
1-9 - 特殊前缀:
g、f - 寄存器选择:
"
- 操作符:
特点:
- 所有命令的 起点
- 可直接执行 纯移动
- 可累计数字前缀
🟡 OPERATOR_PENDING
操作符等待状态
由以下进入:
d(delete)y(yank)c(change)
等待:
- 一个 motion
- 或 modifier(
a/i)
示例:
d→ OPERATOR_PENDINGdw→ 执行 delete(word)diw→ delete(inside word)
🔵 MOTION_PENDING
需要更多按键的移动命令
典型场景:
g→ 等待第二个gf→ 等待目标字符
示例:
g→ MOTION_PENDINGgg→ goto topf a→ find nexta
🟣 MODIFIER
文本对象修饰符
进入方式:
- 在 OPERATOR_PENDING 后输入:
a(around)i(inside)
等待:
- 一个 motion / text-object
示例:
diwyawci"
🟠 REGISTER_SELECT
寄存器选择状态
进入方式:
- 输入
"
等待:
- 寄存器名:
a-zA-Z(追加)0-9+(系统剪贴板)
示例:
"a yw"A dw"+p
3️⃣ 典型命令的 FSM 路径示例
✅ 3dw
NORMAL
→ (3) count
→ d → OPERATOR_PENDING
→ w → execute(delete, word, count=3)
→ NORMAL
✅ "a y2w
NORMAL
→ " → REGISTER_SELECT
→ a → NORMAL (register=a)
→ y → OPERATOR_PENDING
→ 2 → count
→ w → execute(yank, word, count=2, register=a)
→ NORMAL
✅ gg
NORMAL
→ g → MOTION_PENDING
→ g → execute(goto_top)
→ NORMAL
4️⃣ 设计亮点(可以直接写进 README)
tmux-fsm 使用 显式 FSM 状态建模,而不是 tmux key-table 级联:
- ✅ 避免 key table 组合爆炸
- ✅ Vim 风格操作符-动作模型天然表达
- ✅ 新命令 = 新状态或新转移,不影响已有逻辑
- ✅ 所有按键路径 可视化、可推理、可测试
在 tmux-fsm 插件的“Phase 2”优化中,迷你客户端(Client)是实现零延迟响应的关键一环。它将原本厚重的逻辑处理转化为轻量级的消息传递。
以下是关于迷你客户端的实现细节、代码量及作用的详细介绍:
- 迷你客户端的实现原理
在 C/S(服务端/客户端)架构中,迷你客户端不再负责任何复杂的逻辑判断或 tmux 状态读写。它的唯一任务是:获取用户的按键输入,并将其通过 Unix Socket 快速发送给常驻内存的服务端(Server)。
这种设计的优势在于:
- 极速启动:客户端代码极简,Go 语言编译后的二进制文件执行一次 fork/exec 到退出仅需不到 5ms。
- 非阻塞:客户端发送完数据即退出,不等待服务端处理完成,从而消除了用户按键时的“粘滞感”。
-
代码实现及作用
迷你客户端的代码通常集成在主程序中,通过命令行参数区分运行模式。
核心代码示例(基于 main.go)
func runClient() {
// 1. 参数校验:确保有按键传入
if len(os.Args) < 2 { return }
key := os.Args[1]// 2. 连接服务端:通过本地 Unix Socket 文件通信
socketPath := os.Getenv("HOME") + "/.tmux-fsm.sock"
conn, err := net.Dial("unix", socketPath)
if err != nil {
// 如果服务端未启动,静默退出或记录错误
return
}
defer conn.Close()// 3. 发送按键数据:将按键字符写入 Socket
conn.Write([]byte(key))// 4. 立即退出:客户端生命周期结束
}
各部分作用详解:
- 参数接收 (os.Args[1]):通过 tmux 的按键绑定(如 bind-key -T fsm Any run-shell "tmux-fsm '#{key}'")捕获用户当前按下的键位。
- Unix Socket 连接 (net.Dial):这是客户端与服务端通信的桥梁。相比 TCP,Unix Socket 在本地通信中开销更小。
- 数据写入 (conn.Write):将简单的按键信息(如 "j", "k", "3")推送到服务端的处理队列中。
- defer conn.Close():确保在发送完成后正确关闭连接,释放系统资源。
- 代码量统计
迷你客户端的实现体现了“小而美”的工程哲学:
- 逻辑代码:约 10-15 行 Go 代码。
- 总二进制体积:由于 Go 的静态链接特性,虽然整体二进制文件较大(约 2MB),但客户端执行路径非常短。
- 调用开销:由于省去了原先版本中频繁读写 tmux option 的操作( load/save 状态),每次调用的系统资源占用降低了约 90%。
- 总结
迷你客户端就像是神经系统的末梢神经,它只负责感应(捕获按键)并传递信号,而繁重的大脑功能(FSM 状态机逻辑、Undo 栈、状态栏重绘)全部交由服务端处理。这种设计让 tmux-fsm 在功能日益复杂的同时,依然保持了原生 Vim 般的清脆响应。
优化过程
这份优化报告记录了将 tmux-fsm 插件从 Python 后端迁移到 Go 语言后端(称为 “Phase 1” 迁移),并进一步优化为服务端/客户端架构(“Phase 2” 守护进程化)的全过程。
以下是该优化报告的总结:
- 核心问题与诊断
- 状态栏显示延迟:最初用户反馈右下角显示状态(如 NORMAL)无法及时更新。诊断发现 tmux 的刷新机制导致状态栏仅在有输入或定时刷新(默认 15 秒)时更新,且脚本在后台运行使 tmux 无法感知变量变化。
- 配置路径冲突:修复过程中发现,尽管编译了 Go 版本,但 ~/.tmux.conf 仍在加载旧的 Python 路径,导致逻辑不生效。
- 性能瓶颈:原有的 Python 脚本及初版 Go 脚本在每次按键时都会进行多次 fork/exec 系统调用(读写 tmux option),产生了微小的“粘滞感”。
- 主要改进措施
- 后端语言迁移:将 FSM(有限状态机)逻辑从 Python 完美复刻到 Go 结构体中,提升了基础运行效率。
- 引入强制刷新:在代码中增加了 tmux refresh-client -S 命令,确保每次状态变化都能实时重绘状态栏。
- Server/Client 架构(Phase 2):
- 守护进程化:实现了一个常驻内存的 Server,通过 Unix Socket 与 Client 通信。
- 内存状态管理:状态(Mode、Count、Undo 栈等)存储在内存中,无需频繁读写磁盘或 tmux 变量,响应速度提升约 10 倍。
- 并发安全:引入了全局互斥锁 (sync.Mutex),防止快速按键(如 3dw)导致的状态竞争和数据错乱。
- 逻辑完善:修复了初始状态为空导致按键被忽略的 Bug,并添加了对 VISUAL 模式和数字计数的实时显示支持。
- 最终状态与成果
- 零延迟响应:通过内存驻留逻辑和极简客户端,消除了按键时的顿挫感,达到了类似原生 Vim 的清脆响应。
- 架构稳固:成功实现了名为 “FOEK” 的内核迁移,具备了物理锁和数据结构保留能力,为后续实现复杂功能(如空间回声、宏录制)打下了基础。
- 自动化部署:更新了 install.sh 和 plugin.tmux,确保 Server 能够自动启动并正确挂载 Go 版本的二进制文件。
当前结论:该插件已完成从“脚本调用”到“系统级服务”的质变,解决了所有已知的显示同步与性能延迟问题。