1package lipgloss
2
3import (
4 "fmt"
5 "image/color"
6 "io"
7 "strings"
8 "time"
9
10 "github.com/charmbracelet/x/ansi"
11 "github.com/muesli/cancelreader"
12)
13
14// queryBackgroundColor queries the terminal for the background color.
15// If the terminal does not support querying the background color, nil is
16// returned.
17//
18// Note: you will need to set the input to raw mode before calling this
19// function.
20//
21// state, _ := term.MakeRaw(in.Fd())
22// defer term.Restore(in.Fd(), state)
23//
24// copied from x/term@v0.1.3.
25func queryBackgroundColor(in io.Reader, out io.Writer) (c color.Color, err error) {
26 err = queryTerminal(in, out, defaultQueryTimeout,
27 func(seq string, pa *ansi.Parser) bool {
28 switch {
29 case ansi.HasOscPrefix(seq):
30 switch pa.Command() {
31 case 11: // OSC 11
32 parts := strings.Split(string(pa.Data()), ";")
33 if len(parts) != 2 {
34 break // invalid, but we still need to parse the next sequence
35 }
36 c = ansi.XParseColor(parts[1])
37 }
38 case ansi.HasCsiPrefix(seq):
39 switch pa.Command() {
40 case ansi.Command('?', 0, 'c'): // DA1
41 return false
42 }
43 }
44 return true
45 }, ansi.RequestBackgroundColor+ansi.RequestPrimaryDeviceAttributes)
46 return
47}
48
49const defaultQueryTimeout = time.Second * 2
50
51// queryTerminalFilter is a function that filters input events using a type
52// switch. If false is returned, the QueryTerminal function will stop reading
53// input.
54type queryTerminalFilter func(seq string, pa *ansi.Parser) bool
55
56// queryTerminal queries the terminal for support of various features and
57// returns a list of response events.
58// Most of the time, you will need to set stdin to raw mode before calling this
59// function.
60// Note: This function will block until the terminal responds or the timeout
61// is reached.
62// copied from x/term@v0.1.3.
63func queryTerminal(
64 in io.Reader,
65 out io.Writer,
66 timeout time.Duration,
67 filter queryTerminalFilter,
68 query string,
69) error {
70 rd, err := cancelreader.NewReader(in)
71 if err != nil {
72 return fmt.Errorf("could not create cancel reader: %w", err)
73 }
74
75 defer rd.Close() //nolint: errcheck
76
77 done := make(chan struct{}, 1)
78 defer close(done)
79 go func() {
80 select {
81 case <-done:
82 case <-time.After(timeout):
83 rd.Cancel()
84 }
85 }()
86
87 if _, err := io.WriteString(out, query); err != nil {
88 return fmt.Errorf("could not write query: %w", err)
89 }
90
91 pa := ansi.GetParser()
92 defer ansi.PutParser(pa)
93
94 var buf [256]byte // 256 bytes should be enough for most responses
95 for {
96 n, err := rd.Read(buf[:])
97 if err != nil {
98 return fmt.Errorf("could not read from input: %w", err)
99 }
100
101 var state byte
102 p := buf[:]
103 for n > 0 {
104 seq, _, read, newState := ansi.DecodeSequence(p[:n], state, pa)
105 if !filter(string(seq), pa) {
106 return nil
107 }
108
109 state = newState
110 n -= read
111 p = p[read:]
112 }
113 }
114}