223 lines
5.3 KiB
Markdown
223 lines
5.3 KiB
Markdown
# 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 结构
|
|
|
|
```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`: 继承父级 label
|
|
- `Dir`: `~` 展开为用户主目录
|
|
|
|
## 4. 数据模型
|
|
|
|
### 4.1 Workspace
|
|
|
|
```go
|
|
type Workspace struct {
|
|
Index int // 全局索引
|
|
N int // 编号
|
|
Title string // 短名
|
|
Prompt string // 描述
|
|
Tech string // 技术栈
|
|
Deploy string // 部署情况
|
|
Dir string // 目录路径
|
|
Group string // 分组标签
|
|
}
|
|
```
|
|
|
|
### 4.2 Group
|
|
|
|
```go
|
|
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
|
|
|
|
```go
|
|
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宽,不会切断多字节字符:
|
|
|
|
```go
|
|
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 脚本模板
|
|
|
|
```powershell
|
|
$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 编码
|
|
|
|
```go
|
|
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 |
|