service

package
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 4, 2025 License: MIT Imports: 5 Imported by: 0

Documentation

Overview

Package service provides domain services for terminal capabilities detection.

Index

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:

  1. NO_COLOR → Disable all
  2. FORCE_COLOR → User override
  3. Platform-specific detection
  4. COLORTERM → Explicit color
  5. TERM_PROGRAM → Known terminals
  6. TERM → Parsing
  7. 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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL