1// SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
2//
3// SPDX-License-Identifier: AGPL-3.0-or-later
4
5// Package ui provides terminal output utilities and styles.
6package ui
7
8import (
9 "os"
10 "strings"
11
12 "github.com/mattn/go-isatty"
13)
14
15// ColorMode represents the color output mode.
16type ColorMode int
17
18// Color mode constants controlling terminal output styling.
19const (
20 ColorAuto ColorMode = iota // Detect from environment/TTY
21 ColorAlways // Force colors on
22 ColorNever // Force colors off
23)
24
25//nolint:gochecknoglobals // intentional global for output mode
26var colorMode = ColorAuto
27
28// SetColorMode sets the global color mode.
29func SetColorMode(mode ColorMode) {
30 colorMode = mode
31}
32
33// IsPlain returns true when output should be unstyled plain text.
34// Detection priority:
35// 1. Explicit ColorNever/ColorAlways mode
36// 2. NO_COLOR env var (non-empty = plain)
37// 3. FORCE_COLOR env var (non-empty = styled)
38// 4. TERM=dumb (plain)
39// 5. TTY detection (non-TTY = plain)
40func IsPlain() bool {
41 switch colorMode {
42 case ColorNever:
43 return true
44 case ColorAlways:
45 return false
46 case ColorAuto:
47 return detectPlain()
48 }
49
50 return detectPlain()
51}
52
53// IsInteractive returns true when running in an interactive terminal.
54// This checks TTY status regardless of color settings.
55func IsInteractive() bool {
56 return isatty.IsTerminal(os.Stdout.Fd()) || isatty.IsCygwinTerminal(os.Stdout.Fd())
57}
58
59func detectPlain() bool {
60 // NO_COLOR takes precedence (https://no-color.org/)
61 if noColor := os.Getenv("NO_COLOR"); noColor != "" {
62 return true
63 }
64
65 // FORCE_COLOR overrides TTY detection
66 if forceColor := os.Getenv("FORCE_COLOR"); forceColor != "" {
67 // FORCE_COLOR=0 or FORCE_COLOR=false means no color
68 lower := strings.ToLower(forceColor)
69 if lower == "0" || lower == "false" {
70 return true
71 }
72
73 return false
74 }
75
76 // TERM=dumb indicates minimal terminal
77 if term := os.Getenv("TERM"); term == "dumb" {
78 return true
79 }
80
81 // Fall back to TTY detection
82 return !IsInteractive()
83}