5.3 KiB
5.3 KiB
u-tabs 架构设计
最后更新:2026-05-15 | 基于 u-tabs v0.3.0
1. 项目概述
u-tabs 是一个 TUI 工作空间启动器,用 Bubble Tea v2 实现。管理多个工作空间(YAML 配置驱动),通过 Windows Terminal + Claude Code 标签页启动。
技术栈: Go 1.26 / BubbleTea v2 / LipGloss v2
2. 架构
main.go → Model (app.go)
├── 分组 Tab 栏
├── 左侧项目列表
├── 右侧详情面板
└── Enter → wt.exe → claude
3. 配置系统
3.1 加载优先级
~/.u-tabs/config.yaml → exe同目录/config.yaml → 内置默认值 (workspace.go)
3.2 YAML 结构
groups:
- label: CORE # 分组标签,决定编号基数
desc: 核心业务
items:
- title: flux # 短名
prompt: 描述 # AI prompt
tech: 技术栈 # 精确到版本
deploy: 部署 # 服务器/端口/域名
dir: 目录路径 # 启动目录
3.3 编号规则
由 GroupConfig.Base 字段决定,若未设置默认从 0 开始。
3.4 数据转换
Config.ToInternal() 将 YAML 配置转为运行时结构,自动生成:
Index: 全局索引 (0, 1, 2...)N: 编号 (base + item索引)Group: 继承父级 labelDir:~展开为用户主目录
4. 数据模型
4.1 Workspace
type Workspace struct {
Index int // 全局索引
N int // 编号
Title string // 短名
Prompt string // 描述
Tech string // 技术栈
Deploy string // 部署情况
Dir string // 目录路径
Group string // 分组标签
}
4.2 Group
type Group struct {
Label string
Desc string
}
4.3 运行时索引
| 变量 | 类型 | 用途 |
|---|---|---|
Groups |
[]Group |
分组定义 |
AllWorkspaces |
[]Workspace |
全部工作空间 |
wsByNum |
map[int]*Workspace |
编号→Workspace O(1) 索引 |
5. 主模型 (app.go)
5.1 Model
type Model struct {
activeGroup int // 当前分组索引
cursor int // 分组内光标
selected map[int]bool // 多选标记
inputBuf string // 数字快捷输入缓冲
width, height int
launched string // 启动提示
}
5.2 Update 消息路由
KeyPressMsg
├── q, ctrl+c → tea.Quit
├── tab, right, l → activeGroup++ → cursor=0
├── shift+tab, left, h → activeGroup--
├── 1, 2, 3, 4 → 跳转分组
├── up, k → cursor--
├── down, j → cursor++
├── Space → toggleMultiSelect
├── enter → inputBuf非空? launchByInput() : launchSelected()
├── c → copyCommand()
└── [0-9] → 追加到 inputBuf
5.3 View 渲染
View()
└── 启动器模式:
├─ 第1行: "u-tabs"标题 + 分组 Tab 栏
├─ 分隔线
├─ 左右布局 (JoinHorizontal):
│ ├── 左侧列表: 编号 + 标题 + 描述 (CJK安全截断)
│ └── 右侧详情: 目录/编号/描述/技术/部署/命令预览
├─ 输入缓冲提示
├─ 启动成功提示
└─ 帮助栏 (快捷键高亮)
5.4 CJK 安全截断
truncateByWidth() 按 rune 显示宽度截断,中文=2宽,英文=1宽,不会切断多字节字符:
func truncateByWidth(s string, maxW int) string {
w := 0
for i := 0; i < len(s); {
_, size := utf8.DecodeRuneInString(s[i:])
rw := runeWidth(rune(s[i]))
if w+rw > maxW { return s[:i] }
w += rw
i += size
}
return s
}
6. 启动逻辑
6.1 流程
用户按 Enter
↓
buildLaunchScript(ws) → PS 脚本
↓
encodePSCommand(script) → UTF-16LE → Base64
↓
exec.Command("wt.exe", "-w", "0",
"-d", ws.Dir,
"--tabColor", randomColor,
"pwsh", "-NoExit", "-EncodedCommand", encoded).Start()
↓
Windows Terminal 新标签页 → claude 交互模式
6.2 PS 脚本模板
$Host.UI.RawUI.WindowTitle = "{Title}"
Write-Host "=== {Title} ===" -ForegroundColor Cyan
Write-Host "Prompt: {Prompt}" -ForegroundColor Yellow
cd "{Dir}"
claude --name "{Title}" --permission-mode bypassPermissions
6.3 UTF-16LE 编码
func encodePSCommand(script string) string {
u16 := utf16.Encode([]rune(script))
b := make([]byte, len(u16)*2)
for i, r := range u16 {
b[i*2] = byte(r)
b[i*2+1] = byte(r >> 8)
}
return base64.StdEncoding.EncodeToString(b)
}
7. 样式系统 (Tokyo Night 主题)
7.1 色板
| 常量 | Hex | 用途 |
|---|---|---|
| BgDark | #1a1b26 | 深底色 |
| BgPanel | #292e42 | 面板/边框/分隔线 |
| Dim | #565f89 | 次要文字 |
| Fg | #a9b1d6 | 正文 |
| Bright | #c0caf5 | 高亮文字 |
| Accent | #7aa2f7 | 主强调 (蓝色) |
| Success | #9ece6a | 绿色 (部署/标记) |
| Warning | #e0af68 | 黄色 (输入提示) |
| Cyan | #7dcfff | 青色 (编号/详情) |
| Purple | #bb9af7 | 紫色 (选中行) |
| Red | #f7768e | 红色 (CORE分组) |
7.2 分组颜色
| 分组 | Hex |
|---|---|
| CORE | #f7768e |
| LAB | #9ece6a |
| TOOLS | #e0af68 |
| ME | #bb9af7 |
| TEMP | #7dcfff |