
如果你也经常为了找一个 Docker 启动命令、一段 awk 文本处理、或一条 git 回滚操作在浏览器、笔记软件和终端之间来回折腾那你大概能理解我为什么会写 t3code——一个跑在终端里的代码片段管理器。它不是一个界面花哨的收藏夹而是一个用键盘就能快速检索、预览、复制代码片段的本地工具特别适合那些频繁复用固定命令、又厌烦鼠标和跨应用切换的开发者。最开始我只是想解决自己的一个小问题我的 shell history 里躺着大量重复输入的高频命令浏览器收藏夹里存了一堆下次可能用得上的代码片段本地备忘录里还散落着各种夹带私货的配置模板。这些东西彼此割裂真到用的时候反而找不着。后来我花了一个周末把 t3code 的雏形写了出来又在之后两个多月里陆续补上了搜索、标签、模板变量、导出和同步能力现在它已经是我日常开发里绕不开的一环。这篇文章就围绕 t3code 的实际设计和落地过程展开从名字里三个 T 的含义、数据格式、三栏交互到我在技术选型和性能优化上踩过的坑再到现在每天怎么用它、以及哪些场景最推荐。如果你正准备做自己的终端工具或者只是想找一个更顺手的代码片段管理方案可以按需跳着看。1. 折腾 t3code 的起因我的代码片段管理混乱史1.1 从浏览器收藏夹到本地草稿箱先说我的真实使用场景。我每天的工作有相当一部分是在终端里完成的启动本地服务、查数据库、处理日志、批量重命名文件、调 CI 脚本。这些操作里有一类特别尴尬的代码它们不是那种需要仔细斟酌的业务逻辑而是我记得用过、但记不全参数的命令。举个例子我想用 find 排除若干目录、只保留特定扩展名的文件再执行批量操作。这条命令我敢说每个 Linux 用户都查过不止一次但每次都要重新翻文档再比如给 docker 容器设置资源限制、清空构建缓存、导出镜像之类的操作参数一多就容易忘。我过去的解决办法是塞进浏览器收藏夹但收藏夹的搜索机制对代码片段几乎不友好时间一长就是一长串docker tips、awk 常用写法、git 高级命令点开全是网页标题没有内容预览。后来我换成在本地备忘录里开一个技术碎片页面用 Markdown 记录下来。短期看挺好可按 tag 迅速定位也还行但问题在于写起来很零碎没有任何检索权重几百条之后翻页就开始痛苦而且每次复制都要先打开编辑器再滚动到目标位置。最要命的是当我在 SSH 到一台服务器上排查问题时这台机器根本没有图形界面只给我一个终端浏览器和本地笔记全都帮不上忙。1.2 现有工具的三种硬伤我也试过专门的代码片段工具比如 VS Code 的 Snippets、在线 Gist、剪贴板增强工具等。它们各有优点但在我看来都有很明显的硬伤简单归纳如下方案优势主要问题VS Code Snippets编辑器中补全方便只覆盖编辑器场景终端里的命令用不上GitHub Gist 网页在线保存、可分享检索需要联网私密性不好控制不适合敏感脚本剪贴板管理器复制粘贴速度快没有目录和标签概念片段一多就只看得到最近几条本地 Markdown 笔记可自由排版无法快速预览需要额外打开工具没有权重排序这些工具本质上都是通用知识管理或者编辑器功能的副产品而不是专门解决终端里高频复用代码这个窄问题。我在服务器上排查问题时最理想的交互是在终端里敲一条命令弹出候选列表键盘上下移动回车就把内容送到剪贴板甚至直接执行。整个过程不需要图形界面、不需要离开终端、不需要记忆代码片段存在哪个分类里。1.3 t3code 想解决的核心问题所以 t3code 的定位从一开始就非常明确面向终端使用者的本地代码片段库。它需要满足三个核心指标保存片段足够快一条命令完成不打开编辑器二次操作找到片段足够快支持标题、标签、正文多个维度的模糊搜索使用片段足够快一键复制到系统剪贴板或者直接输出到 stdout 供管道处理。我不需要它成为一个多么庞大的知识库管理软件也不需要在线协作、权限系统、Web UI 这些附加价值。核心就一条让那句我记得在哪儿见过的代码以最短路径回到我的手指下面。2. t3code 的“三个T”设计Terminal、Tag、Type2.1 名字里三个 T 到底是什么起名的时候我给这个项目定了个规则每个关键设计都要对应一个以 T 开头的词这样既好记也能在日后约束自己不要加太多花哨功能。t3code 里的3T分别是Terminal第一使用场景是终端运行环境是 TUI不是 Web、不是桌面客户端Tag组织方式以标签为骨架每个片段必须至少有一个标签Type每条代码片段都有明确的语言或命令类型比如 bash、python、sql、yaml、docker。这个设计也回应了一个常见的疑问为什么不用数据库、不用网页界面因为终端本身就是一个被低估的知识管理终端。你平时敲的命令、跑过的脚本、出的错误日志其实都长在终端里。t3code 要做的是把这些信息变成可检索、可复用的资产而不是把它们搬到另一个不见天日的 App 里。2.2 三栏交互界面t3code 的主界面是一个典型的三栏 TUI。第一次打开时左侧是标签栏中间是片段标题列表右侧是代码预览区。标签栏按使用频率显示所有标签方便按主题聚焦片段列表显示匹配当前搜索条件的标题、语言类型和最近使用时间预览区展示当前选中片段的完整正文以及备注信息。操作完全围绕键盘设计。默认情况下Tab在三个区域之间切换焦点↑/↓移动候选Ctrlj/k在列表内快速跳跃输入/可以直接进入搜索模式Enter复制选中片段y把内容输出到 stdout 并退出e打开外部编辑器编辑这条片段。这里面最让我坚持的一点是任何时候按?都能看到快捷键帮助。做了几个终端工具以后我最大的体会是TUI 类工具最大的用户门槛不是功能少而是快捷键全靠记。所以我在 v0.3 版本里加入了可悬浮的帮助面板把上下文相关的键位提示放在界面底部只在按下?时展开。2.3 每一条片段到底怎么存t3code 的数据格式我选了 YAML没有用 SQLite也没有用 JSON。选择 YAML 的原因有三个人工编辑友好如果我需要批量改标签、修正文直接打开文件改再保存就行版本控制友好YAML 文件丢进 Git 里做 diff 的体验远好于二进制数据库维护成本低一个文件对应一个库备份等于拷贝文件。每条片段的结构大致如下- id: 7f2e5c91 title: docker compose 清空缓存并重建 tags: [docker, compose, build] lang: bash note: 慎用 --no-cache适合依赖变更时 created_at: 2024-11-06T10:23:0008:00 used_at: 2025-01-12T11:00:0008:00 used_count: 18 body: | docker compose build --no-cache docker compose up -d所有片段默认存放在~/.local/share/t3code/snippets.yaml配置文件放在~/.config/t3code/config.yaml。之所以遵循 XDG 目录规范是为了避免用户一不小心把数据文件丢进 Git 仓库、又或者因为清理临时目录把辛辛苦苦攒的片段删掉。这块细节我后面会再展开。3. 搭建 t3code 时我做的几个关键技术决策3.1 为什么选用 Go Bubble Teat3code 的核心技术栈是 GoTUI 层用的是 Bubble Tea。这个选择不是我拍脑袋定的而是在写第一版之前把 Rust、Python 和 Go 都列出来对比过一遍。方案优势我放弃的原因Python Textual开发速度快、动态类型灵活分发时用户需要 Python 环境不够“单文件”干净Rust Ratatui性能好、类型安全编译交叉编译成本高首个版本迭代速度会比 Go 慢Go Bubble Tea部署简单、协程顺手、生态成熟泛型支持不如 Rust但本项目用不到复杂泛型最终选 Go最重要的原因是交叉编译太省心了。我日常在 macOS 上开发但部署时会需要在 Linux 服务器上跑同一套工具。Go 一条GOOSlinux GOARCHamd64 go build就能出可执行文件别提有多方便。而且 Bubble Tea 的模型就是 Elm 架构组件状态、消息循环、副作用都分得比较清楚写复杂交互界面时不会变成一团浆糊。3.2 CLI、TUI 和存储之间的边界t3code 整体被拆成三层命令行参数层、交互界面层、存储层。三层之间通过接口隔离没有互相渗透。命令行参数层只负责接收指令并分发比如t3 add、t3 search、t3 export html这类子命令。交互界面层不直接读写 YAML 文件而是向存储层询问所有匹配某个搜索条件的片段。存储层则封装了加载、解析、保存、去重等逻辑。这样分工的好处是未来如果我想把 YAML 换成 SQLite或者增加一个远程同步后端只需要替换存储层界面和 CLI 基本不动。# 保存一条片段 t3 add \ --title find 排除目录并批量去掉后缀 \ --tags find,bash,rename \ --lang bash \ --body find . -type f -name *.tmp -not -path ./node_modules/* # 搜索 t3 search docker compose build # 打开交互界面 t3存储层还有一个很小的优化每次启动时把整个 YAML 读入内存建立标签索引和标题索引。对于几万条以内的片段来说这个策略简单且快不需要引入数据库。只有当单库超过五万条、或者需要复杂查询时我才会考虑引入 SQLite。3.3 检索排序标签权重与最近使用加权t3code 没有用外部搜索引擎检索逻辑是自己实现的一套带权打分。每次搜索时用户输入的若干关键字会分别和标题、标签、正文做包含匹配然后加权求和标题命中一个关键字权重 3标签完全命中一个关键字权重 5正文命中一个关键字权重 1最近使用加分每使用过一次加 0.2但设有上限 5 分时间衰减近 7 天使用过的片段额外加 2 分近 30 天加 1 分。举例说明用户搜索docker build一条标题叫docker 多阶段构建优化、标签含docker、使用过 12 次的片段得分会明显高于一条正文里沾边但标题和标签都没命中的片段。这个逻辑并不复杂但它很好地把用户输入意图和历史使用偏好结合在了一起。要注意的是这套实现目前只做了子串匹配没有做中文分词。比如搜索清缓存标题里的清空缓存能匹配到但搜索缓存反而匹配不到清空缓存中的缓存子串这里有一个坑如果正文里是清空缓存关键词缓存是可以匹配到的因为正文做了子串匹配。但如果你搜索空缓就没有办法命中清空缓存因为它是连续子串匹配。对这种问题我目前的建议是标题里尽量写大家会搜索的稳定词组标签保持短小不要又长又碎。4. 把 t3code 真正用进日常工作流4.1 一条命令保存三秒找到一键复制把 t3code 放进日常流程后我使用频率最高的三条路径是保存一个新片段写完一个比较长的命令确认能跑通后马上执行t3 add --title ...把它固化找历史片段执行t3打开交互界面输入关键字回车复制管道输出执行t3 search keyword --copy可以直接把第一条匹配结果的正文放到系统剪贴板适合写脚本的时候内嵌。这里分享一个小技巧如果你经常用 tmux可以把 t3code 开在一个侧边窗格里。主窗格写代码或敲命令侧边窗格随时t3搜索片段选中后y输出到 stdout再用 tmux 的send-keys把内容直接发送到主窗格。整个过程甚至不需要经过系统剪贴板交互更自然。4.2 与 Vim / VS Code 的衔接t3code 不只是终端工具它也能反哺编辑器。最简单的用法是把t3 search的输出接到编辑器里t3 search golang json marshal --copy # 然后在编辑器里直接 CtrlV 粘贴如果你用 Vim可以给它配一个映射nnoremap leadert :r !t3 search这样按下leadert输入关键字后匹配到的片段正文会直接插入当前光标位置。我在 VS Code 里也配了一个类似的 keybinding只是把命令调用换成终端任务。核心思路都是让 t3code 成为一个随时可调用的外部命令来源而不是依赖编辑器插件生态。这样无论编辑器怎么换习惯都能带走。4.3 用 Git 同步片段库t3code 的存储是纯文本 YAML这让同步变得异常简单。我把~/.local/share/t3code/初始化为一个 Git 仓库然后把 remote 指向自己的私有仓库每次大量操作后执行一次git add -A git commit git push。如果你也想这么做有几点要留意不要直接同步整个主目录否则容易把不相干的配置文件也带进去注意排除临时文件和备份文件比如给 snippets.yaml 加的*.bak忽略规则敏感内容不要入库尤其是那些包含密钥、token、内网地址的片段最好单独管理或者使用支持加密字段的方案。我目前的做法是分两个库一个同步所有不敏感的标准操作片段另一个只放在本机专门存公司内部系统相关的脚本。这样既不牺牲便利性也能守住安全边界。5. 实测踩坑中文、窄终端和万条片段5.1 中文内容导致界面错位第一个比较典型的坑出现在预览区。YAML 片段正文里如果有中文注释或者标题本身就是中文三栏列表会莫名出现对齐错乱。原因很好理解大多数终端库计算字符串宽度时默认把每个字符当作等宽但中文是全角字符在终端里占两个英文字符宽度。如果直接用len(str)截断或计算表格宽度界面就会歪。我最后用的方案是引入go-runewidth库来计算实际像素宽度在列表列宽计算和截断逻辑里都走这个函数。如果你的终端工具同样需要处理中文内容建议从第一天就统一使用runewidth不要只在遇到 bug 时才补。另外还有一个体验层面的问题中文搜索的关键词本来就短按 3/5/1 的权重打分时命中标题和未命中标题的区分度可能会不足。所以我后来在搜索逻辑里加了一条补充规则标题命中时即使标签没命中也要把匹配到的起始位置作为子排序依据让标题开头命中的片段排在更前面。5.2 窄窗口下的布局问题另一个容易忽视的场景是终端窗口太窄。我的开发机是宽屏平时开两个 pane 一边一个没问题但如果你在笔记本上把终端分成三栏或者在远程 SSH 的默认窗口里运行宽度可能只有 80 列甚至更少。窄窗口下最容易崩的是预览区。标签栏占了 18 列片段列表占了 40 列剩下的给正文预览正文每行能显示的字符数就很少很多代码看起来支离破碎。我做了以下处理当整个终端宽度小于 100 列时自动隐藏左侧标签栏只保留列表和预览两栏当宽度小于 60 列时改成单栏模式只显示片段列表按回车后再打开全屏预览预览区内的自动换行策略从不换行改成按空格换行最大限度地避免横向滚动。这套自适应布局在真实使用中很有用尤其是应急在服务器上查看片段时不会因为窗口小就不可用。5.3 数据量上来之后的启动慢t3code 刚写出来的时候加载几百条片段完全是秒开。但当我从旧笔记里导入了 4000 多条片段之后启动时间一下子到了 600 毫秒肉眼可见地卡顿。排查后发现问题出在每次启动都重新解析整个 YAML、重新构建标签索引和标题索引没有任何缓存。后来我加了两层优化第一层是原始 YAML 的哈希缓存只在文件内容变化时重新解析否则直接反序列化上次解析好的内存结构第二层是关键词索引的增量更新维护一个map[tag][]int的标签索引每次添加片段只更新对应标签的条目而不重建整个索引。优化之后8000 条片段的启动时间稳定在 40 毫秒左右体感已经完全感知不到。如果你以后也写类似的本地内容工具切记一点启动时的冷启动和热启动要分开设计很多启动慢的问题其实只是因为每次都在重复做全量计算。6. 进阶玩法模板变量、导出 HTML 与团队知识库6.1 模板变量让一段代码适配多个场景t3code 的正文支持一种简单的模板变量语法用{变量名}占位复制的时候会先弹出输入框把变量替换成用户输入的值再送到剪贴板。例如我保存了一条用 python 快速起一个 HTTP 文件服务的片段cd {目录} python3 -m http.server {端口}复制时会让你输入目录和端口的值然后生成完整的命令。这个功能看起来简单实际使用率非常高尤其是处理一些结构相同但参数不同的命令比如docker run、scp、rsync、ffmpeg转换等。不需要为每个参数组合建一条片段一条模板就够。模板解析我用了最朴素的正则替换刻意没有引入完整模板引擎因为代码片段里经常有{}这种字符如果模板语法过于聪慧反而会误伤正常内容。目前的设计是只有{变量名}这种明确形态才触发替换其余花括号原样保留。6.2 从 shell history 里挖片段很多人其实不知道自己最常用的命令是什么直到某天翻.bash_history才感叹我怎么每天敲这么多重复的东西。t3code 提供了一个导入脚本的思路把高频出现的命令自动转成片段草案。大致的逻辑是history | awk {print $2} | sort | uniq -c | sort -nr | head -50拿到排名前 50 的命令后再去重、去敏感信息、补标签然后批量导入。这个办法的好处是你不需要凭空回忆自己常用什么历史数据已经替你说清楚了。导入之后我会手动给每条片段补充正文和备注把一条裸命令升级成带说明和示例的可复用片段。这里要提醒一句shell history 里经常混着数据库连接串、临时 token、个人目录路径。批量导入前一定要先做一轮肉眼审查尤其是你事后准备把这些片段同步到 Git 远端仓库的时候。宁可多花十分钟清理也不要给自己埋一颗隐私炸弹。6.3 导出静态 HTML 分享不是每个合作者都想装终端工具。t3code 支持t3 export html把整个片段库导出成一个自带搜索框的静态 HTML 页面双击就能打开或者扔到任意静态托管平台后分享给团队。导出的页面里每一条片段保留了标签、语言、正文和备注。页面用一个不到 200 行的原生 JavaScript 实现了简单的关键字过滤不需要构建工具也不需要联网加载 CDN保证离线也能用。这个功能是我给团队里的非重度终端用户准备的他们不需要学 t3code 的快捷键只需要偶尔翻一翻运维常用命令部署检查清单这类知识库也是足够的。我个人还做过一次尴尬的测试把一个包含 3000 条片段的 HTML 导出后文件大小居然有 2MB 多。后来优化了渲染方式改为按需展开代码内容文件降到 800KB 左右浏览器打开也不卡了。7. 给想写终端 TUI 的人架构上的三个实在建议7.1 焦点管理和数据模型分开Bubble Tea 这类 TUI 框架很容易把人带进一个陷阱把所有状态都塞进一个巨大的 model最后 update 函数里全是各种分支代码越来越难读。我在 t3code 的第二个大版本里做过一次重构把焦点管理单独抽成一个状态对象和数据模型分离。简单说界面里所有可能获得焦点的组件都通过一个统一的FocusManager来管理它负责记录当前焦点在标签栏、列表栏还是预览区负责处理Tab循环切换、禁用某些没有数据的区域负责在焦点切换时触发对应组件的刷新逻辑。这样数据模型只关心有哪些片段、匹配哪些搜索条件界面组件只关心当前焦点在哪、怎么渲染。两者解耦之后加新功能的速度明显提升比如后来我加最近使用排序时只动数据层的排序函数完全不需要碰界面逻辑。7.2 别把配置随手放在当前目录我早期开发工具时有个坏习惯喜欢在项目根目录或者当前工作目录生成.xxx.json配置。这对个人工具偶尔能用但一旦你愿意把工具分享出去各种怪问题就来了用户在临时目录运行一次发现配置跑丢了或者不同项目目录下打开 t3code表现完全不一样。t3code 从一开始就遵循 XDG 目录规范数据文件如果设置了XDG_DATA_HOME放在$XDG_DATA_HOME/t3code/否则用~/.local/share/t3code/配置文件优先$XDG_CONFIG_HOME/t3code/config.yaml否则~/.config/t3code/config.yaml。在 Go 里可以直接用os.UserConfigDir()和os.UserCacheDir()但os.UserConfigDir()在 Linux 上返回的是~/.config并不是$XDG_CONFIG_HOME。所以更稳妥的做法是手动检查环境变量再 fallback 到系统默认路径。这段代码虽然多写几行但以后每个用户都能少踩一个我的数据到底存哪了的坑。7.3 版本号别乱来用户会当真这是我在发布 t3code v0.1 之后才发现的问题。当时我只是把它当内部小工具版本号随意递增结果有同事跟着我更新发现某个小版本把数据库目录名改了导致之前保存的片段全部消失其实是路径变了没有自动迁移。虽然最后找回来了但从那时起我记住了两个原则遵循语义化版本破坏性变更必须升大版本新功能走 minorbug fix 走 patch数据目录必须有迁移机制如果路径要变先在旧路径里做迁移而不是直接读新路径。现在 t3code 会在启动时检查数据目录版本如果发现旧版本的数据路径存在而新路径为空会弹一个提示让用户决定是否自动迁移。这个功能不复杂但能把版本升级导致的数据丢失恐惧扼杀在摇篮里。8. 下一阶段规划与真实使用心得8.1 下一个版本想做的三件事t3code 目前的形态我已经用了两个多月稳定性和效率都符合预期。下一步最想做的三件事是插件机制允许用户通过 Lua 脚本自定义复制前处理逻辑比如自动格式化 SQL、给命令加上当前日期等全局快捷键在桌面环境里设置一个全局热键无论焦点在哪都能呼出 t3code把片段直接粘贴到当前输入框标签自动补全在搜索框输入时根据历史标签做一个高亮推荐减少想不起来标签叫什么的问题。插件机制是其中最复杂的因为要定义一套可控的脚本执行环境又不能影响 TUI 主循环的响应速度。好在当时选 Go 的时候已经预留了接口层启动一个 Lua 虚拟机做事件回调是比较自然的设计方向。8.2 使用之后的真实体会如果让我一句话总结 t3code 给我带来的改变那就是让复用这件事的成本低到了不需要犹豫的程度。以前我遇到一个有点眼熟的命令脑子里会先想是不是要花时间翻历史记录还是重新 Google 一下现在没有任何纠结直接在 t3code 里搜一下有就复制没有就补一条。积累越久这个库的价值越高因为它记录的是我个人真实曾经用过的代码而不是网上那些通用示例。最后一个建议不要追求一开始就把工具做得面面俱到。t3code 第一版连搜索都没有只有一个列表和一个预览框但就是因为每天高频使用才知道哪些功能值得加、哪些只是想象中的需求。如果你也在考虑管理自己的代码片段不妨从一条t3 add命令和一个 YAML 文件开始。