这次能够成功,核心原因在于解决了 "多进程状态隔离" 导致的信息不对称问题。
简单来说:你启动 FSM 的那个进程,和负责处理按键的后台进程,其实是两个完全独立的"大脑"。
这里是详细的复盘分析:
1. 之前为什么失败?(大脑失联)
当你执行 tmux-fsm -enter 时:
- CLI 进程启动:它在自己的内存里把
FSMActive设为true,告诉 Tmux 设置@fsm_active=1,然后进程结束销毁。 - 后台 Daemon 进程:它一直在后台运行。它并没有收到通知说"我们进入 FSM 模式了"。在它的内存里,
FSMActive仍然是false。
导致的结果:
- 当你按下
h,请求发给 Daemon。 - Daemon 处理完
h后,代码走到了这里:// 旧代码 if fsm.FSMActive { // Daemon 觉得:我内存里这是 false 啊 reconcileFSMState(...) // 所以这行救命代码被跳过了! } - 与此同时,Tmux 处理完按键命令后,因为某些机制(比如焦点变化或命令结束),自动把键盘表切回了默认的
root。 - Daemon 没有去纠正它(因为它以为没在 FSM 模式)。
- 你按第二次键时,已经是
root模式了,所以直接上屏。
2. 这次为什么成功?(以 Tmux 为真理)
我们在 main.go 中去掉了 if 判断:
// 新代码
// 不管 Daemon 内存里怎么想,无条件执行状态核对
reconcileFSMState(actualClient)
现在的流程是:
- 你按
h。 - Daemon 处理完
h。 - Daemon 被迫去运行
reconcileFSMState。 reconcileFSMState实际上是去问 Tmux:"喂,Tmux,你的
@fsm_active变量现在是 1 吗?"- Tmux 回答:"是的,之前那个 CLI 进程把它设为 1 了"。
reconcileFSMState接着检查:"那你当前的 KeyTable 是fsm吗?"- 如果 Tmux 说:"哎呀,我不小心切回
root了"。 reconcileFSMState立即下令:"不行!给我切回fsm!"
3. 那个空格的作用 (" @fsm_active")
之前 enter_exit.go 里多了一个空格:backend.GlobalBackend.SetUserOption(" @fsm_active", "1")。
- 这导致 Tmux 里设置了一个叫
" @fsm_active"(带空格)的变量。 - 但是
reconcileFSMState读取的是"@fsm_active"(不带空格)。 - 后果:即使逻辑通了,Daemon 去读变量时读到的是空值(0),它会以为 FSM 已经关闭了,从而主动帮你退出了 FSM 模式。
总结
这次成功的关键在于确立了唯一的真理来源 (Source of Truth):
- 不再相信 Go 程序的内存变量(因为进程是分离的)。
- 只相信 Tmux 的用户选项 (
@fsm_active)。 - 并且每次按键后,都强制根据 Tmux 的变量值来"锁死"键盘表状态。