charm-ultraviolet
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseUltraviolet (charmbracelet/ultraviolet)
Ultraviolet (charmbracelet/ultraviolet)
What Is Ultraviolet (and When NOT to Use It)
什么是Ultraviolet(以及何时不应使用它)
Ultraviolet is a set of low-level primitives for terminal manipulation in Go. It provides cell-based rendering, unified input handling, and screen management. It powers Bubble Tea v2 and Lip Gloss v2 internally.
This is NOT a framework. It is infrastructure. Think of it like syscalls vs stdlib - you can use it directly, but most of the time you should not.
Ultraviolet是一套用于Go语言终端操作的底层原语。它提供基于单元格的渲染、统一的输入处理以及屏幕管理功能。它是Bubble Tea v2和Lip Gloss v2的内部支撑组件。
这不是一个框架,而是基础设施。可以把它比作系统调用与标准库的关系——你可以直接使用它,但大多数时候你不应该这么做。
When NOT to use it
何时不应使用它
- Building a standard TUI app - use Bubble Tea v2 instead
- Styling terminal output - use Lip Gloss v2 instead
- You want an architecture/framework with state management - use Bubble Tea v2
- Prototyping - too low-level, too much boilerplate
- 构建标准TUI应用——请改用Bubble Tea v2
- 为终端输出添加样式——请改用Lip Gloss v2
- 你需要带状态管理的架构/框架——请改用Bubble Tea v2
- 原型开发——层级过低,样板代码过多
When to use it directly
何时直接使用它
- Building your own TUI framework on top of these primitives
- Writing a custom renderer that needs cell-level control
- Performance-critical rendering where you need direct buffer manipulation
- Embedding terminal rendering into a non-Bubble Tea application
- Working on Bubble Tea / Lip Gloss internals
- 在这些原语之上构建你自己的TUI框架
- 编写需要单元格级控制的自定义渲染器
- 对性能要求极高的渲染场景,需要直接操作缓冲区
- 将终端渲染嵌入非Bubble Tea应用中
- 开发Bubble Tea / Lip Gloss的内部组件
API Stability Warning
API稳定性警告
The project README explicitly states: "This project currently exists to serve internal use cases. API stability is a goal, but expect no stability guarantees as of now." Plan accordingly.
项目README明确说明:“本项目目前仅用于满足内部使用场景。API稳定性是目标,但目前不提供稳定性保证。”请据此规划开发工作。
Core Abstractions
核心抽象
The library lives in a single flat Go package (import path: ), with helper sub-packages and .
uvgithub.com/charmbracelet/ultravioletscreen/layout/该库位于单个扁平化Go包(导入路径:),包含辅助子包和。
uvgithub.com/charmbracelet/ultravioletscreen/layout/Cell
Cell(单元格)
The fundamental unit. One terminal cell = one grapheme cluster.
go
type Cell struct {
Content string // single grapheme cluster
Style Style // fg, bg, attrs (bold, italic, etc.)
Link Link // OSC 8 hyperlink
Width int // columns occupied (1 for normal, 2 for wide chars like CJK)
}Key constants/values:
- - a cell with
EmptyCell, width 1, no style" " - Zero-width cells () are placeholders for wide characters
Width == 0
最基础的单元。一个终端单元格对应一个字符簇。
go
type Cell struct {
Content string // 单个字符簇
Style Style // 前景色、背景色、属性(加粗、斜体等)
Link Link // OSC 8 超链接
Width int // 占用列数(普通字符为1,中日韩等宽字符为2)
}关键常量/值:
- ——内容为
EmptyCell、宽度为1、无样式的单元格" " - 零宽度单元格()是宽字符的占位符
Width == 0
Buffer
Buffer(缓冲区)
A 2D grid of cells, organized as where .
Lines []LineLine []Cellgo
buf := uv.NewBuffer(80, 24) // width, height
buf.SetCell(x, y, &cell) // write a cell
cell := buf.CellAt(x, y) // read a cell (nil if out of bounds)
buf.Resize(newW, newH) // resize, preserving content
buf.Clear() // fill with EmptyCell
buf.Fill(&cell) // fill with custom cell
buf.FillArea(&cell, area) // fill rectangular region
clone := buf.Clone() // deep copyBuffer implements , so you can to composite buffers onto screens.
Drawablebuf.Draw(screen, area)单元格组成的二维网格,组织为,其中。
Lines []LineLine []Cellgo
buf := uv.NewBuffer(80, 24) // 宽度、高度
buf.SetCell(x, y, &cell) // 写入一个单元格
cell := buf.CellAt(x, y) // 读取单元格(超出边界则返回nil)
buf.Resize(newW, newH) // 调整大小,保留内容
buf.Clear() // 用EmptyCell填充
buf.Fill(&cell) // 用自定义单元格填充
buf.FillArea(&cell, area) // 填充矩形区域
clone := buf.Clone() // 深拷贝Buffer实现了接口,因此你可以调用将缓冲区合成到屏幕上。
Drawablebuf.Draw(screen, area)RenderBuffer
RenderBuffer(渲染缓冲区)
Wraps Buffer with change tracking. Only touched lines/cells get re-rendered.
go
rbuf := uv.NewRenderBuffer(80, 24)
rbuf.SetCell(x, y, &cell) // auto-marks line as touched
rbuf.TouchLine(x, y, n) // manually mark region dirty
rbuf.TouchedLines() // count of dirty lines对Buffer进行包装,添加变更跟踪功能。仅重新渲染被修改过的行/单元格。
go
rbuf := uv.NewRenderBuffer(80, 24)
rbuf.SetCell(x, y, &cell) // 自动标记该行为已修改
rbuf.TouchLine(x, y, n) // 手动标记区域为脏区
rbuf.TouchedLines() // 获取脏区行数Screen (Interface)
Screen(屏幕接口)
The core abstraction that anything drawable targets.
go
type Screen interface {
Bounds() Rectangle
CellAt(x, y int) *Cell
SetCell(x, y int, c *Cell)
WidthMethod() WidthMethod
}Implemented by: , , , .
BufferScreenBufferWindowTerminalScreen所有可绘制对象的核心抽象目标。
go
type Screen interface {
Bounds() Rectangle
CellAt(x, y int) *Cell
SetCell(x, y int, c *Cell)
WidthMethod() WidthMethod
}实现类包括:、、、。
BufferScreenBufferWindowTerminalScreenDrawable (Interface)
Drawable(可绘制接口)
Anything that can render itself onto a Screen.
go
type Drawable interface {
Draw(scr Screen, area Rectangle)
}Implemented by: , , , and your own components.
BufferWindowStyledString任何可以渲染到Screen上的对象。
go
type Drawable interface {
Draw(scr Screen, area Rectangle)
}实现类包括:、、,以及你自己的组件。
BufferWindowStyledStringWindow
Window(窗口)
A rectangular area that can own its own buffer or share a parent's buffer (view).
go
// Root window (owns its buffer)
root := uv.NewScreen(80, 24)
// Child window with own buffer
child := root.NewWindow(x, y, width, height)
// View into parent buffer (shared memory)
view := root.NewView(x, y, width, height)Windows support , , , .
MoveToMoveByResizeClone一个矩形区域,可以拥有自己的缓冲区或共享父级缓冲区(视图)。
go
// 根窗口(拥有自己的缓冲区)
root := uv.NewScreen(80, 24)
// 拥有独立缓冲区的子窗口
child := root.NewWindow(x, y, width, height)
// 父级缓冲区的视图(共享内存)
view := root.NewView(x, y, width, height)Window支持、、、方法。
MoveToMoveByResizeCloneTerminal
Terminal(终端)
The main entry point for standalone UV apps. Manages console I/O, raw mode, event loop.
go
t := uv.DefaultTerminal()
// or: t := uv.NewTerminal(console, opts)
t.Start() // enter raw mode, start event loop
defer t.Stop() // restore terminal, clean up
scr := t.Screen() // returns *TerminalScreen
for ev := range t.Events() {
switch ev := ev.(type) {
case uv.WindowSizeEvent:
scr.Resize(ev.Width, ev.Height)
case uv.KeyPressEvent:
if ev.MatchString("ctrl+c") { return }
}
}独立UV应用的主入口点。管理控制台I/O、原始模式、事件循环。
go
t := uv.DefaultTerminal()
// 或者:t := uv.NewTerminal(console, opts)
t.Start() // 进入原始模式,启动事件循环
defer t.Stop() // 恢复终端状态,清理资源
scr := t.Screen() // 返回*TerminalScreen
for ev := range t.Events() {
switch ev := ev.(type) {
case uv.WindowSizeEvent:
scr.Resize(ev.Width, ev.Height)
case uv.KeyPressEvent:
if ev.MatchString("ctrl+c") { return }
}
}TerminalScreen
TerminalScreen(终端屏幕)
The concrete screen for terminal output. Manages alt screen, cursor, colors, mouse mode, keyboard enhancements, synchronized updates.
go
scr := t.Screen()
// Screen modes
scr.EnterAltScreen() // alternate screen buffer
scr.ExitAltScreen()
// Rendering cycle
scr.SetCell(x, y, &cell)
scr.Render() // diff current vs previous state
scr.Flush() // write changes to terminal
// Or use Display for Drawable components
scr.Display(myDrawable) // clear + draw + render + flush
// Terminal features
scr.ShowCursor()
scr.SetCursorPosition(x, y)
scr.SetMouseMode(uv.MouseModeClick)
scr.SetBackgroundColor(color)
scr.SetWindowTitle("My App")
scr.SetSynchronizedUpdates(true) // mode 2026
scr.SetKeyboardEnhancements(enh) // kitty protocol
// Inline mode helper
scr.InsertAbove(content) // insert text above without disrupting screen用于终端输出的具体屏幕实现。管理备用屏幕、光标、颜色、鼠标模式、键盘增强功能、同步更新。
go
scr := t.Screen()
// 屏幕模式
scr.EnterAltScreen() // 切换到备用屏幕缓冲区
scr.ExitAltScreen()
// 渲染周期
scr.SetCell(x, y, &cell)
scr.Render() // 对比当前与之前的状态
scr.Flush() // 将变更写入终端
// 或者使用Display方法渲染Drawable组件
scr.Display(myDrawable) // 清理 + 绘制 + 渲染 + 刷新
// 终端功能
scr.ShowCursor()
scr.SetCursorPosition(x, y)
scr.SetMouseMode(uv.MouseModeClick)
scr.SetBackgroundColor(color)
scr.SetWindowTitle("My App")
scr.SetSynchronizedUpdates(true) // 模式2026
scr.SetKeyboardEnhancements(enh) // kitty协议
// 行内模式辅助方法
scr.InsertAbove(content) // 在上方插入文本,不干扰当前屏幕StyledString
StyledString(带样式的字符串)
Converts ANSI-styled strings into cell-based representation. Implements Drawable.
go
ss := uv.NewStyledString("Hello \x1b[1mWorld\x1b[0m")
ss.Draw(screen, area)将ANSI样式字符串转换为基于单元格的表示。实现了Drawable接口。
go
ss := uv.NewStyledString("Hello \x1b[1mWorld\x1b[0m")
ss.Draw(screen, area)Sub-Packages
子包
screen/ - Screen Helpers
screen/ - 屏幕辅助工具
Utility functions that work with any implementation.
Screengo
import "github.com/charmbracelet/ultraviolet/screen"
screen.Clear(scr) // clear entire screen
screen.ClearArea(scr, area) // clear region
screen.Fill(scr, &cell) // fill screen
screen.FillArea(scr, &cell, area) // fill region
screen.Clone(scr) // deep copy to Buffer
screen.CloneArea(scr, area) // deep copy region
// Drawing context with stateful style
ctx := screen.NewContext(scr)
ctx.SetForeground(ansi.Red)
ctx.SetBold(true)
ctx.DrawString("hello", x, y)
ctx.Printf("count: %d", n) // implements io.Writer适用于任何实现的实用函数。
Screengo
import "github.com/charmbracelet/ultraviolet/screen"
screen.Clear(scr) // 清空整个屏幕
screen.ClearArea(scr, area) // 清空指定区域
screen.Fill(scr, &cell) // 填充整个屏幕
screen.FillArea(scr, &cell, area) // 填充指定区域
screen.Clone(scr) // 深拷贝到Buffer
screen.CloneArea(scr, area) // 深拷贝指定区域
// 带状态样式的绘制上下文
ctx := screen.NewContext(scr)
ctx.SetForeground(ansi.Red)
ctx.SetBold(true)
ctx.DrawString("hello", x, y)
ctx.Printf("count: %d", n) // 实现了io.Writer接口layout/ - Constraint-Based Layout
layout/ - 基于约束的布局
Cassowary-based layout solver (ported from Ratatui). Splits areas into non-overlapping rectangles.
go
import "github.com/charmbracelet/ultraviolet/layout"
// Split area vertically into 3 parts
chunks := layout.New().
Direction(layout.Vertical).
Constraints(
layout.Len(3), // fixed 3 rows
layout.Fill(1), // fill remaining
layout.Len(1), // fixed 1 row
).
Split(area)Constraint types: , , , , , .
LenRatioPercentFillMinMax基于Cassowary的布局求解器(从Ratatui移植)。将区域划分为不重叠的矩形。
go
import "github.com/charmbracelet/ultraviolet/layout"
// 将区域垂直分割为3部分
chunks := layout.New().
Direction(layout.Vertical).
Constraints(
layout.Len(3), // 固定3行
layout.Fill(1), // 填充剩余空间
layout.Len(1), // 固定1行
).
Split(area)约束类型包括:、、、、、。
LenRatioPercentFillMinMaxEvents
事件
Events come from channel. Key types:
t.Events()| Event | Description |
|---|---|
| Terminal resized (width, height in cells) |
| Terminal resized (width, height in pixels) |
| Key pressed. Use |
| Key released (requires kitty keyboard protocol) |
| Mouse click with position and button |
| Mouse moved (requires mouse mode enabled) |
| Bracketed paste content |
Key matching uses human-readable strings: , , , , .
"ctrl+a""shift+enter""alt+tab""f1""space"事件来自通道。主要类型:
t.Events()| 事件 | 描述 |
|---|---|
| 终端窗口大小变更(单元格为单位的宽高) |
| 终端窗口大小变更(像素为单位的宽高) |
| 按键按下。使用 |
| 按键释放(需要kitty键盘协议) |
| 鼠标点击,包含位置和按钮信息 |
| 鼠标移动(需要启用鼠标模式) |
| 括号粘贴内容 |
按键匹配使用人类可读的字符串:、、、、。
"ctrl+a""shift+enter""alt+tab""f1""space"Geometry
几何
Uses and from stdlib:
image.Pointimage.Rectanglego
pos := uv.Pos(x, y) // == image.Point{X: x, Y: y}
rect := uv.Rect(x, y, width, height) // origin + size (NOT min/max)Note: takes width/height, not max coordinates. This differs from .
uv.Rect(x, y, w, h)image.Rect(x0, y0, x1, y1)使用标准库的和:
image.Pointimage.Rectanglego
pos := uv.Pos(x, y) // == image.Point{X: x, Y: y}
rect := uv.Rect(x, y, width, height) // 原点 + 尺寸(不是最小/最大坐标)注意:接受宽度/高度,而非最大坐标。这与不同。
uv.Rect(x, y, w, h)image.Rect(x0, y0, x1, y1)Style System
样式系统
Styles are value types with bitfield attributes:
go
style := uv.Style{
Fg: ansi.Red,
Bg: ansi.Black,
UnderlineColor: ansi.Blue,
Underline: uv.UnderlineCurly,
Attrs: uv.AttrBold | uv.AttrItalic,
}Attributes: , , , , , , .
AttrBoldAttrFaintAttrItalicAttrBlinkAttrReverseAttrConcealAttrStrikethroughUnderline styles: , , , , , .
UnderlineNoneUnderlineSingleUnderlineDoubleUnderlineCurlyUnderlineDottedUnderlineDashedStyle diffing is built in - the renderer computes minimal ANSI sequences to transition between styles.
样式是带位字段属性的值类型:
go
style := uv.Style{
Fg: ansi.Red,
Bg: ansi.Black,
UnderlineColor: ansi.Blue,
Underline: uv.UnderlineCurly,
Attrs: uv.AttrBold | uv.AttrItalic,
}属性包括:、、、、、、。
AttrBoldAttrFaintAttrItalicAttrBlinkAttrReverseAttrConcealAttrStrikethrough下划线样式包括:、、、、、。
UnderlineNoneUnderlineSingleUnderlineDoubleUnderlineCurlyUnderlineDottedUnderlineDashed内置样式对比功能——渲染器会计算切换样式所需的最少ANSI序列。
Rendering Pipeline
渲染流水线
The "Cursed Renderer" is a cell-based diffing engine inspired by ncurses:
- You write cells to the screen buffer via
SetCell - diffs current buffer against previous state
Render() - Renderer emits minimal ANSI escape sequences (cursor movement, style changes, text)
- writes the accumulated output to the terminal
Flush()
Optimizations include:
- Only touched lines are re-rendered
- Style diffs minimize SGR sequence length
- Cursor movement uses shortest path (absolute, relative, tabs, backspace)
- Supports synchronized updates (mode 2026) to prevent flicker
- Hash-based scroll detection for efficient content shifts
“Cursed Renderer”是受ncurses启发的基于单元格的差异引擎:
- 通过将单元格写入屏幕缓冲区
SetCell - 对比当前缓冲区与之前的状态
Render() - 渲染器生成最少的ANSI转义序列(光标移动、样式变更、文本)
- 将累积的输出写入终端
Flush()
优化点包括:
- 仅重新渲染被修改过的行
- 样式对比最小化SGR序列长度
- 光标移动使用最短路径(绝对、相对、制表符、退格)
- 支持同步更新(模式2026)以防止闪烁
- 基于哈希的滚动检测,实现高效内容偏移
Minimal Hello World
最简Hello World示例
go
package main
import (
"log"
uv "github.com/charmbracelet/ultraviolet"
"github.com/charmbracelet/ultraviolet/screen"
)
func main() {
t := uv.DefaultTerminal()
scr := t.Screen()
scr.EnterAltScreen()
if err := t.Start(); err != nil {
log.Fatal(err)
}
defer t.Stop()
ctx := screen.NewContext(scr)
for ev := range t.Events() {
switch ev := ev.(type) {
case uv.WindowSizeEvent:
scr.Resize(ev.Width, ev.Height)
case uv.KeyPressEvent:
if ev.MatchString("q", "ctrl+c") {
return
}
}
screen.Clear(scr)
ctx.DrawString("Hello, World!", 0, 0)
scr.Render()
scr.Flush()
}
}go
package main
import (
"log"
uv "github.com/charmbracelet/ultraviolet"
"github.com/charmbracelet/ultraviolet/screen"
)
func main() {
t := uv.DefaultTerminal()
scr := t.Screen()
scr.EnterAltScreen()
if err := t.Start(); err != nil {
log.Fatal(err)
}
defer t.Stop()
ctx := screen.NewContext(scr)
for ev := range t.Events() {
switch ev := ev.(type) {
case uv.WindowSizeEvent:
scr.Resize(ev.Width, ev.Height)
case uv.KeyPressEvent:
if ev.MatchString("q", "ctrl+c") {
return
}
}
screen.Clear(scr)
ctx.DrawString("Hello, World!", 0, 0)
scr.Render()
scr.Flush()
}
}Relationship to Bubble Tea v2
与Bubble Tea v2的关系
ultraviolet (primitives)
|
+-- Lip Gloss v2 (styling, composition)
|
+-- Bubble Tea v2 (framework: Elm architecture, state management, commands)
|
+-- Bubbles (components: text input, list, table, etc.)- Ultraviolet provides: cells, buffers, screen management, input decoding, rendering
- Bubble Tea v2 provides: ,
Program,Model,Update, commands, subscriptionsView - Lip Gloss v2 provides: , layout, borders, padding, composition
Style
If you are building a TUI application, start with Bubble Tea v2. Only reach for ultraviolet when Bubble Tea's abstractions get in your way.
ultraviolet (原语)
|
+-- Lip Gloss v2(样式、组合)
|
+-- Bubble Tea v2(框架:Elm架构、状态管理、命令)
|
+-- Bubbles(组件:文本输入、列表、表格等)- Ultraviolet提供:单元格、缓冲区、屏幕管理、输入解码、渲染
- Bubble Tea v2提供:、
Program、Model、Update、命令、订阅View - Lip Gloss v2提供:、布局、边框、内边距、组合
Style
如果你正在构建TUI应用,请从Bubble Tea v2开始。只有当Bubble Tea的抽象成为阻碍时,才考虑使用ultraviolet。
Checklist
检查清单
Before using ultraviolet directly, confirm:
- Bubble Tea v2 genuinely cannot solve your problem
- You need cell-level rendering control
- You accept API instability risk
- You understand the rendering pipeline (SetCell -> Render -> Flush)
- You handle WindowSizeEvent and call Resize yourself
- You manage terminal raw mode and cleanup (Start/Stop)
- You have read the examples in the directory
examples/
在直接使用ultraviolet之前,请确认:
- Bubble Tea v2确实无法解决你的问题
- 你需要单元格级的渲染控制
- 你接受API不稳定的风险
- 你理解渲染流水线(SetCell -> Render -> Flush)
- 你会处理WindowSizeEvent并自行调用Resize
- 你会管理终端原始模式和清理工作(Start/Stop)
- 你已阅读目录中的示例
examples/