Documentation
¶
Overview ¶
Package service provides domain services for terminal capabilities detection.
Index ¶
- type CapabilitiesDetector
- type EnvironmentProvider
- type UnicodeService
- func (us *UnicodeService) ClusterWidth(cluster string) int
- func (us *UnicodeService) ClusterWidthWithConfig(cluster string, config value.UnicodeConfig) int
- func (us *UnicodeService) GraphemeClusters(s string) []string
- func (us *UnicodeService) StringWidth(s string) int
- func (us *UnicodeService) StringWidthWithConfig(s string, config value.UnicodeConfig) int
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type CapabilitiesDetector ¶
type CapabilitiesDetector struct {
// contains filtered or unexported fields
}
CapabilitiesDetector is a domain service that detects terminal capabilities. Based on tui-research-analyst recommendations.
Detection Priority:
- NO_COLOR → Disable all
- FORCE_COLOR → User override
- Platform-specific detection
- COLORTERM → Explicit color
- TERM_PROGRAM → Known terminals
- TERM → Parsing
- Conservative defaults
func NewCapabilitiesDetector ¶
func NewCapabilitiesDetector(env EnvironmentProvider) *CapabilitiesDetector
NewCapabilitiesDetector creates detector with environment provider.
func (*CapabilitiesDetector) Detect ¶
func (cd *CapabilitiesDetector) Detect() *value.Capabilities
Detect analyzes environment and returns capabilities.
type EnvironmentProvider ¶
type EnvironmentProvider interface {
// Get returns environment variable value (empty string if not set).
Get(key string) string
// Platform returns the operating system ("linux", "darwin", "windows", etc).
Platform() string
}
EnvironmentProvider is a port (interface) for reading environment variables. This keeps the domain layer pure by abstracting infrastructure concerns.
Implementation lives in infrastructure/platform/environment.go.
type UnicodeService ¶
type UnicodeService struct{}
UnicodeService provides Unicode text analysis for correct width calculation. This is a domain service because width calculation is core business logic needed by Cell value object and ALL Phoenix libraries.
This service fixes Charm's lipgloss#562 bug by correctly calculating visual width of grapheme clusters including emoji, CJK, and combining chars.
func NewUnicodeService ¶
func NewUnicodeService() *UnicodeService
NewUnicodeService creates a new Unicode service instance.
func (*UnicodeService) ClusterWidth ¶
func (us *UnicodeService) ClusterWidth(cluster string) int
ClusterWidth calculates the visual width of a single grapheme cluster. Returns:
- 0 for zero-width/combining characters
- 1 for ASCII and most characters
- 2 for emoji, CJK characters
A grapheme cluster is a user-perceived character that may consist of multiple runes:
- Simple: "a" (1 rune) → width 1
- Emoji: "👋" (1 rune) → width 2
- Emoji + modifier: "👋🏻" (2 runes) → width 2 (use base emoji width)
- ZWJ sequence: "👨👩👧👦" (7 runes) → width 2 (use first emoji width)
- Combining: "é" (2 runes: e + combining acute) → width 1
For multi-rune clusters, we use the width of the FIRST (base) character only, because modifiers, ZWJ, and combining marks don't add visual width.
Example:
ClusterWidth("a") // 1
ClusterWidth("👋") // 2
ClusterWidth("👋🏻") // 2 (emoji with modifier - uses 👋 width)
ClusterWidth("👨👩👧👦") // 2 (ZWJ sequence - uses 👨 width)
ClusterWidth("中") // 2 (CJK)
ClusterWidth("é") // 1 (e + combining acute - uses e width)
ClusterWidth("\u0301") // 0 (combining acute accent alone)
func (*UnicodeService) ClusterWidthWithConfig ¶
func (us *UnicodeService) ClusterWidthWithConfig(cluster string, config value.UnicodeConfig) int
ClusterWidthWithConfig calculates the width of a grapheme cluster with custom configuration. This is the locale-aware version of ClusterWidth().
Example:
// English locale
config := value.NewUnicodeConfig()
width := us.ClusterWidthWithConfig("±", config) // 1
// Japanese locale
config := value.NewUnicodeConfig().WithEastAsianWide()
width := us.ClusterWidthWithConfig("±", config) // 2
func (*UnicodeService) GraphemeClusters ¶
func (us *UnicodeService) GraphemeClusters(s string) []string
GraphemeClusters splits a string into grapheme clusters. A grapheme cluster is a user-perceived character:
- "a" -> ["a"]
- "👋🏻" -> ["👋🏻"] (emoji + modifier = 1 cluster)
- "é" -> ["é"] (base + combining = 1 cluster)
- "👨👩👧👦" -> ["👨👩👧👦"] (family emoji with ZWJ = 1 cluster)
Example:
GraphemeClusters("Hello") // ["H", "e", "l", "l", "o"]
GraphemeClusters("👋🏻") // ["👋🏻"]
GraphemeClusters("Café") // ["C", "a", "f", "é"]
func (*UnicodeService) StringWidth ¶
func (us *UnicodeService) StringWidth(s string) int
StringWidth calculates the visual width of a string in terminal columns. Correctly handles:
- ASCII: 1 column each
- Emoji: 2 columns (including modifiers, ZWJ sequences)
- CJK characters: 2 columns
- Zero-width characters: 0 columns
- Combining characters: 0 columns
Performance optimization: Uses uniwidth library (9-23x faster than go-runewidth) with tiered lookup: O(1) for 90-95% of cases (ASCII, CJK, emoji), O(log n) for rare chars. Falls back to uniseg grapheme clustering only for truly complex Unicode (5-10% of cases).
Example:
StringWidth("Hello") // 5
StringWidth("👋") // 2
StringWidth("👋🏻") // 2 (emoji + modifier = 1 cluster, 2 columns)
StringWidth("こんにちは") // 10 (5 CJK chars * 2 columns)
StringWidth("Café") // 4 (C + a + f + é)
func (*UnicodeService) StringWidthWithConfig ¶
func (us *UnicodeService) StringWidthWithConfig(s string, config value.UnicodeConfig) int
StringWidthWithConfig calculates the visual width of a string with custom Unicode configuration. This allows locale-specific width calculation, particularly for East Asian Ambiguous characters.
East Asian Ambiguous characters (±, ½, °, ×, §, etc.) have different widths in different locales: - Narrow (width 1): Default for English and neutral locales - Wide (width 2): For East Asian locales (Japanese, Chinese, Korean)
Example:
// English locale (default)
config := value.NewUnicodeConfig()
width := us.StringWidthWithConfig("±", config) // 1
// Japanese locale
config := value.NewUnicodeConfig().WithEastAsianWide()
width := us.StringWidthWithConfig("±", config) // 2
For most use cases, use StringWidth() which uses neutral locale defaults. Use StringWidthWithConfig() when you need locale-specific rendering.