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