面向新手、但不牺牲 FOEK 理念深度的入门教程

面向新手、但不牺牲 FOEK 理念深度的入门教程


🧭 tmux‑fsm 新手入门教程

从“按键”到“事实”的第一次迁移


在开始之前(非常重要)

tmux‑fsm 不是 Vim
不是 tmux 的一组快捷键

它是一个 常驻内存的编辑内核(FOEK)
而 tmux 只是它的 输入输出设备

👉 如果你期待的是:

  • “装上就跟 Vim 一模一样”
  • “一个 key binding 文件”
  • “按一下就跑、跑完就退出的脚本”

请现在就停下。
你会用得非常痛苦。


tmux‑fsm 到底在做什么?

一句话版本:

tmux‑fsm 不编辑字符,它编辑「意义在时间中的变化」。

它做了三件传统 tmux 插件做不到的事:

  1. 拥有状态(而不是存到 tmux option)
  2. 记住时间线(事务、撤销、审计)
  3. 用事实定位文本(而不是光标猜测)

这也是为什么它需要一个 Go 守护进程


🧱 核心概念速读(新手必看)

在真正开始用之前,你只需要记住 5 个词。

1️⃣ Kernel(内核)

  • tmux‑fsm 的 Go Server
  • 常驻后台
  • 唯一的真相来源
  • FSM、Undo、事务、锚点都在这里

tmux ≠ 状态来源
tmux = 键盘 + 屏幕


2️⃣ Mode(模式)

和 Vim 类似,但由 显式 FSM 驱动

  • NORMAL
  • OPERATOR_PENDING
  • MOTION_PENDING
  • VISUAL
  • REGISTER_SELECT

你看到的状态栏文字,来自 内核实时状态


3️⃣ Fact(事实)

一次编辑 ≠ 一串按键
一次编辑 = 一个 事实

一个 Fact 包含:

  • Range(范围)
  • Anchor(锚点)
  • Operation(意图)

撤销时,内核会重新寻找这个事实的位置


4️⃣ Transaction(事务)

3dw 这样的操作:

  • ✅ 要么 全部成功
  • ❌ 要么 整体跳过

永远不会留下半删的垃圾状态。


5️⃣ Anchor Resolver(定位引擎)

撤销时,内核会按顺序尝试:

  1. Exact:精确匹配
  2. Fuzzy:模糊匹配(会提示 ~UNDO
  3. 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 实现了目前终端插件中最先进的撤销保护机制。我们遵循一套严格的撤销安全公理

  1. 保护现场高于还原文本:当环境发生剧烈变动(如 Shell Prompt 刷新或文本被外部篡改)导致无法 100% 确定位置时,系统会选择 Safe Skip (安全跳过),并标记 !UNDO_FAIL
  2. 原子化一致性:事务中任何一步由于安全原因无法执行,整个事务都会被标记为 Skipped,且禁止 Redo。
  3. 模糊透明度:当系统通过模糊匹配成功找回文本时,状态栏会显示 ~UNDO 指示,告知用户当前环境已发生偏移。

诊断与审计 (Auditability)

系统不再是一个“黑盒”。如果撤销失败,您可以询问系统:

  • p 键 (STATUS):查看内核当前完整的事务栈。
  • __WHY_FAIL__ 指令:返回最近一次撤销失败的具体审计原因(例如:Anchor mismatch due to Prompt detection)。

✨ 魔法特性:Spatial Echo (空间回声)

Spatial Echo 是 FOEK 内核成熟后的第一次自然共振。它在无多光标、无新模式的前提下,实现了多点、可重放的编辑。

  1. Armed Facts (武装事实):执行如 3dw 的复合操作时,系统会生成 3 个独立的 Range 事实 并存入缓冲区。
  2. 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"  

故障排除

  1. 确保已安装 Go:编译内核需要 Go 环境。
  2. 确认 Socket 状态:守护进程会在 ~/.tmux-fsm.sock 创建连接点。
  3. 重新加载配置
    tmux source-file ~/.tmux.conf  
    
  4. 手动停止/重启服务端
    pkill -f "tmux-fsm -server"  
    
  5. 如果有问题,可在 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
    • 特殊前缀:gf
    • 寄存器选择:"

特点:

  • 所有命令的 起点
  • 可直接执行 纯移动
  • 可累计数字前缀

🟡 OPERATOR_PENDING

操作符等待状态

由以下进入:

  • d(delete)
  • y(yank)
  • c(change)

等待:

  • 一个 motion
  • modifiera / i

示例:

  • d → OPERATOR_PENDING
  • dw → 执行 delete(word)
  • diw → delete(inside word)

🔵 MOTION_PENDING

需要更多按键的移动命令

典型场景:

  • g → 等待第二个 g
  • f → 等待目标字符

示例:

  • g → MOTION_PENDING
  • gg → goto top
  • f a → find next a

🟣 MODIFIER

文本对象修饰符

进入方式:

  • 在 OPERATOR_PENDING 后输入:
    • a(around)
    • i(inside)

等待:

  • 一个 motion / text-object

示例:

  • diw
  • yaw
  • ci"

🟠 REGISTER_SELECT

寄存器选择状态

进入方式:

  • 输入 "

等待:

  • 寄存器名:
    • a-z
    • A-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)是实现零延迟响应的关键一环。它将原本厚重的逻辑处理转化为轻量级的消息传递。
以下是关于迷你客户端的实现细节、代码量及作用的详细介绍:

  1. 迷你客户端的实现原理
    在 C/S(服务端/客户端)架构中,迷你客户端不再负责任何复杂的逻辑判断或 tmux 状态读写。它的唯一任务是:获取用户的按键输入,并将其通过 Unix Socket 快速发送给常驻内存的服务端(Server)。
    这种设计的优势在于:
  • 极速启动:客户端代码极简,Go 语言编译后的二进制文件执行一次 fork/exec 到退出仅需不到 5ms。
  • 非阻塞:客户端发送完数据即退出,不等待服务端处理完成,从而消除了用户按键时的“粘滞感”。
  1. 代码实现及作用
    迷你客户端的代码通常集成在主程序中,通过命令行参数区分运行模式。
    核心代码示例(基于 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():确保在发送完成后正确关闭连接,释放系统资源。
  1. 代码量统计
    迷你客户端的实现体现了“小而美”的工程哲学:
  • 逻辑代码:约 10-15 行 Go 代码。
  • 总二进制体积:由于 Go 的静态链接特性,虽然整体二进制文件较大(约 2MB),但客户端执行路径非常短。
  • 调用开销:由于省去了原先版本中频繁读写 tmux option 的操作( load/save 状态),每次调用的系统资源占用降低了约 90%。
  1. 总结
    迷你客户端就像是神经系统的末梢神经,它只负责感应(捕获按键)并传递信号,而繁重的大脑功能(FSM 状态机逻辑、Undo 栈、状态栏重绘)全部交由服务端处理。这种设计让 tmux-fsm 在功能日益复杂的同时,依然保持了原生 Vim 般的清脆响应。

优化过程

这份优化报告记录了将 tmux-fsm 插件从 Python 后端迁移到 Go 语言后端(称为 “Phase 1” 迁移),并进一步优化为服务端/客户端架构(“Phase 2” 守护进程化)的全过程。
以下是该优化报告的总结:

  1. 核心问题与诊断
  • 状态栏显示延迟:最初用户反馈右下角显示状态(如 NORMAL)无法及时更新。诊断发现 tmux 的刷新机制导致状态栏仅在有输入或定时刷新(默认 15 秒)时更新,且脚本在后台运行使 tmux 无法感知变量变化。
  • 配置路径冲突:修复过程中发现,尽管编译了 Go 版本,但 ~/.tmux.conf 仍在加载旧的 Python 路径,导致逻辑不生效。
  • 性能瓶颈:原有的 Python 脚本及初版 Go 脚本在每次按键时都会进行多次 fork/exec 系统调用(读写 tmux option),产生了微小的“粘滞感”。
  1. 主要改进措施
  • 后端语言迁移:将 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 模式和数字计数的实时显示支持。
  1. 最终状态与成果
  • 零延迟响应:通过内存驻留逻辑和极简客户端,消除了按键时的顿挫感,达到了类似原生 Vim 的清脆响应。
  • 架构稳固:成功实现了名为 “FOEK” 的内核迁移,具备了物理锁和数据结构保留能力,为后续实现复杂功能(如空间回声、宏录制)打下了基础。
  • 自动化部署:更新了 install.sh 和 plugin.tmux,确保 Server 能够自动启动并正确挂载 Go 版本的二进制文件。
    当前结论:该插件已完成从“脚本调用”到“系统级服务”的质变,解决了所有已知的显示同步与性能延迟问题。