go-runewidth
Computes the terminal cell width of runes and strings, handling CJK, emoji, and combining characters correctly.
Repository Health
Technical Analysis
go-runewidth is a small, dependency-light Go library that answers a deceptively hard question: how many terminal columns does this string actually occupy? A plain rune count is wrong the moment a string contains East Asian wide characters, emoji, zero-width combining marks, or grapheme clusters made of several runes that render as one glyph. go-runewidth implements the Unicode East Asian Width property (UTR #11) plus practical heuristics for ambiguous-width and emoji runes, and exposes both single-rune (RuneWidth) and full-string (StringWidth) width calculations.
Beyond raw measurement, it ships the string-manipulation primitives that terminal UIs actually need: Truncate, TruncateLeft, and TruncatePrefix cut a string to fit a target cell width without splitting a grapheme cluster in half, Wrap line-wraps text by visual width rather than byte or rune count, and FillLeft/FillRight pad strings for column alignment. A Condition struct lets callers override the East-Asian-width and strict-emoji-neutral flags per call site (instead of relying on the auto-detected locale), and an optional CreateLUT() precomputes a full lookup table for hot paths that call width functions at high frequency.
What You Get
StringWidth/RuneWidthfunctions that return true terminal cell width per Unicode TR#11, not a naive rune or byte count- Grapheme-cluster-aware
Truncate,TruncateLeft, andTruncatePrefixthat never split a combined character in half Wrap,FillLeft, andFillRighthelpers for building aligned, column-based terminal output- A
Conditiontype for per-call-site control of East-Asian-width and emoji-neutral behavior, independent of the detected locale - Automatic locale detection (
IsEastAsian) via POSIX locale env vars or the Windows console output codepage - An optional
CreateLUT()precomputed lookup table for width-sensitive hot paths, plus a lazily-built internal LUT so importing the package costs no memory unless width functions are actually called
Common Use Cases
- Terminal UI / TUI toolkits computing box-border and column widths so layouts stay aligned when text contains CJK or emoji
- CLI tools rendering formatted tables or progress bars over mixed-width Unicode content
- Truncating long strings (filenames, commit messages, chat text) to fit a fixed terminal width without corrupting a multi-rune glyph
- Wrapping paragraph text in a terminal app by actual visual width instead of rune count
Under The Hood
Architecture
The package is a flat, single-package library: runewidth.go holds the primary API and orchestration logic, runewidth_table.go holds generated static Unicode interval tables (private, nonprint, combining, doublewidth, ambiguous, emoji, narrow, neutral) produced by script/generate.go’s go:generate directive pulling data from unicode.org, and runewidth_posix.go/runewidth_windows.go/runewidth_appengine.go/runewidth_js.go provide build-tag-gated platform implementations of IsEastAsian() (POSIX locale-env parsing vs. a Windows GetConsoleOutputCP syscall vs. no-op stubs). The core data flow merges the static interval tables into zerowidth/widewidth/eastAsianWidth tables lazily via sync.Once, consulted through binary search (inTable/inWidthTable) or a fully materialized two-plane byte lookup table (strictWidthLUT) that fills its low region eagerly at init and its high region lazily behind an atomic, acquire-semantics limit check — a documented concurrency invariant (see the comments around issue #104) that keeps plain package import cheap. Condition is the central strategy object encapsulating the EastAsianWidth/StrictEmojiNeutral flags and an optional precomputed combinedLut, with every package-level convenience function delegating to a shared DefaultCondition. Grapheme-aware operations delegate cluster segmentation to the external uax29/v2 dependency rather than iterating runes directly, so that dependency’s segmentation boundaries flow straight through to every width computation.
Tech Stack
Pure Go, module targets Go 1.23, with a single external dependency (github.com/clipperhouse/uax29/v2) providing UAX #29 grapheme-cluster segmentation. Table generation is handled by a go generate-invoked script that fetches interval data from unicode.org and regenerates runewidth_table.go; there is no other build tooling. CI (GitHub Actions) runs go generate ./... followed by git diff --cached --exit-code to guarantee committed tables are never stale, then go test -cover and go test -bench, across a 3x3 matrix of Go 1.24/1.25/1.26 on Windows, macOS, and Linux runners, with coverage uploaded to Codecov.
Code Quality
Testing is substantial for a library this size: table-driven tests verify SHA-256 checksums of every generated Unicode interval table to catch silent drift, cross-check the fast lookup-table path against the slow binary-search path for correctness, and exercise StringWidth/Truncate/Wrap across ASCII, CJK, and emoji inputs, plus locale-parsing edge cases on POSIX. Only the standard testing package is used — no external assertion library. Naming is idiomatic Go throughout, and comments are unusually precise about concurrency invariants (documenting exactly which regions of shared state may be read concurrently with a lazy build). There is no linter configuration beyond the generated-file diff check in CI, and error handling is minimal by design since width computation never fails.
API Design
The public surface favors progressive disclosure: top-level functions (RuneWidth, StringWidth, Truncate, Wrap, FillLeft, FillRight) work with zero configuration for the common case — a single import and one function call — while advanced use cases construct a Condition to override locale detection per call site, or call CreateLUT() to trade memory for speed on hot paths, without any API redesign. Naming follows a consistent Verb+Modifier pattern (TruncateLeft, TruncatePrefix, FillLeft, FillRight), every exported symbol carries a doc comment, and deprecated fields (ZeroWidthJoiner) are marked with a clear migration note. The README itself is minimal (a single usage line), so onboarding relies on godoc rather than prose documentation.
Used by 5 apps in this directory
Dolt
Databases · Data Engineering · Developer Tools
The SQL database you can branch, merge, diff, and clone — Git for your data, MySQL-compatible and ready for multi-agent AI workflows.
Hatchet
AI Development · Developer Tools · Automation
A Postgres-backed orchestration engine for background tasks, AI agents, and durable workflows that replaces Redis queues and multi-datastore durable execution platforms with a single self-hostable service.
Ollama
AI Development · Developer Tools
Run Llama, Gemma, DeepSeek, and other open LLMs on your own machine with one command and an OpenAI-compatible API.
tau
Devops
Open-source, Git-native platform-as-a-service for building, deploying, and scaling fullstack apps on your own infrastructure with no DevOps required.
Tusk
Developer Tools · AI Code Assistants · Devops
Record live API traffic and replay it as deterministic, sandboxed tests, plus AI code review and unit test generation, all from one Go CLI built by YC W2024's Use-Tusk.