charm-ultraviolet

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Ultraviolet (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
uv
(import path:
github.com/charmbracelet/ultraviolet
), with helper sub-packages
screen/
and
layout/
.
该库位于单个扁平化Go包
uv
(导入路径:
github.com/charmbracelet/ultraviolet
),包含辅助子包
screen/
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:
  • EmptyCell
    - a cell with
    " "
    , width 1, no style
  • Zero-width cells (
    Width == 0
    ) are placeholders for wide characters
最基础的单元。一个终端单元格对应一个字符簇。
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
Lines []Line
where
Line []Cell
.
go
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 copy
Buffer implements
Drawable
, so you can
buf.Draw(screen, area)
to composite buffers onto screens.
单元格组成的二维网格,组织为
Lines []Line
,其中
Line []Cell
go
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实现了
Drawable
接口,因此你可以调用
buf.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:
Buffer
,
ScreenBuffer
,
Window
,
TerminalScreen
.
所有可绘制对象的核心抽象目标。
go
type Screen interface {
    Bounds() Rectangle
    CellAt(x, y int) *Cell
    SetCell(x, y int, c *Cell)
    WidthMethod() WidthMethod
}
实现类包括:
Buffer
ScreenBuffer
Window
TerminalScreen

Drawable (Interface)

Drawable(可绘制接口)

Anything that can render itself onto a Screen.
go
type Drawable interface {
    Draw(scr Screen, area Rectangle)
}
Implemented by:
Buffer
,
Window
,
StyledString
, and your own components.
任何可以渲染到Screen上的对象。
go
type Drawable interface {
    Draw(scr Screen, area Rectangle)
}
实现类包括:
Buffer
Window
StyledString
,以及你自己的组件。

Window

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
MoveTo
,
MoveBy
,
Resize
,
Clone
.
一个矩形区域,可以拥有自己的缓冲区或共享父级缓冲区(视图)。
go
// 根窗口(拥有自己的缓冲区)
root := uv.NewScreen(80, 24)

// 拥有独立缓冲区的子窗口
child := root.NewWindow(x, y, width, height)

// 父级缓冲区的视图(共享内存)
view := root.NewView(x, y, width, height)
Window支持
MoveTo
MoveBy
Resize
Clone
方法。

Terminal

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
Screen
implementation.
go
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
适用于任何
Screen
实现的实用函数。
go
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:
Len
,
Ratio
,
Percent
,
Fill
,
Min
,
Max
.
基于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)
约束类型包括:
Len
Ratio
Percent
Fill
Min
Max

Events

事件

Events come from
t.Events()
channel. Key types:
EventDescription
WindowSizeEvent
Terminal resized (width, height in cells)
PixelSizeEvent
Terminal resized (width, height in pixels)
KeyPressEvent
Key pressed. Use
ev.MatchString("ctrl+c", "q")
KeyReleaseEvent
Key released (requires kitty keyboard protocol)
MouseClickEvent
Mouse click with position and button
MouseMotionEvent
Mouse moved (requires mouse mode enabled)
PasteEvent
Bracketed paste content
Key matching uses human-readable strings:
"ctrl+a"
,
"shift+enter"
,
"alt+tab"
,
"f1"
,
"space"
.
事件来自
t.Events()
通道。主要类型:
事件描述
WindowSizeEvent
终端窗口大小变更(单元格为单位的宽高)
PixelSizeEvent
终端窗口大小变更(像素为单位的宽高)
KeyPressEvent
按键按下。使用
ev.MatchString("ctrl+c", "q")
匹配
KeyReleaseEvent
按键释放(需要kitty键盘协议)
MouseClickEvent
鼠标点击,包含位置和按钮信息
MouseMotionEvent
鼠标移动(需要启用鼠标模式)
PasteEvent
括号粘贴内容
按键匹配使用人类可读的字符串:
"ctrl+a"
"shift+enter"
"alt+tab"
"f1"
"space"

Geometry

几何

Uses
image.Point
and
image.Rectangle
from stdlib:
go
pos := uv.Pos(x, y)                    // == image.Point{X: x, Y: y}
rect := uv.Rect(x, y, width, height)   // origin + size (NOT min/max)
Note:
uv.Rect(x, y, w, h)
takes width/height, not max coordinates. This differs from
image.Rect(x0, y0, x1, y1)
.
使用标准库的
image.Point
image.Rectangle
go
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:
AttrBold
,
AttrFaint
,
AttrItalic
,
AttrBlink
,
AttrReverse
,
AttrConceal
,
AttrStrikethrough
.
Underline styles:
UnderlineNone
,
UnderlineSingle
,
UnderlineDouble
,
UnderlineCurly
,
UnderlineDotted
,
UnderlineDashed
.
Style 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,
}
属性包括:
AttrBold
AttrFaint
AttrItalic
AttrBlink
AttrReverse
AttrConceal
AttrStrikethrough
下划线样式包括:
UnderlineNone
UnderlineSingle
UnderlineDouble
UnderlineCurly
UnderlineDotted
UnderlineDashed
内置样式对比功能——渲染器会计算切换样式所需的最少ANSI序列。

Rendering Pipeline

渲染流水线

The "Cursed Renderer" is a cell-based diffing engine inspired by ncurses:
  1. You write cells to the screen buffer via
    SetCell
  2. Render()
    diffs current buffer against previous state
  3. Renderer emits minimal ANSI escape sequences (cursor movement, style changes, text)
  4. Flush()
    writes the accumulated output to the terminal
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启发的基于单元格的差异引擎:
  1. 通过
    SetCell
    将单元格写入屏幕缓冲区
  2. Render()
    对比当前缓冲区与之前的状态
  3. 渲染器生成最少的ANSI转义序列(光标移动、样式变更、文本)
  4. 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
    ,
    View
    , commands, subscriptions
  • Lip Gloss v2 provides:
    Style
    , layout, borders, padding, composition
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
    examples/
    directory
在直接使用ultraviolet之前,请确认:
  • Bubble Tea v2确实无法解决你的问题
  • 你需要单元格级的渲染控制
  • 你接受API不稳定的风险
  • 你理解渲染流水线(SetCell -> Render -> Flush)
  • 你会处理WindowSizeEvent并自行调用Resize
  • 你会管理终端原始模式和清理工作(Start/Stop)
  • 你已阅读
    examples/
    目录中的示例