okami.mom/ultraviolet

git clone https://git.nkp.pet/okami.mom/ultraviolet

Ayman Bagabasfeat: add cross-platform console I/O interface (#78)6ea2266

upstream
5.5 KiB219 linesraw
1package uv
2
3import (
4	"io"
5	"os"
6	"runtime"
7
8	"github.com/charmbracelet/x/term"
9)
10
11var isWindows = runtime.GOOS == "windows"
12
13// File is an interface that represents a file with a file descriptor.
14//
15// This is typically an [os.File] like [os.Stdin] and [os.Stdout].
16type File interface {
17	io.ReadWriteCloser
18	term.File
19
20	// Name returns the name of the file.
21	Name() string
22}
23
24// Winsize represents the size of a terminal in cells and pixels.
25//
26// This is the same as [unix.Winsize], but defined here for cross-platform compatibility.
27type Winsize struct {
28	Row    uint16
29	Col    uint16
30	Xpixel uint16
31	Ypixel uint16
32}
33
34// Console represents a cross-platform console I/O interface.
35type Console interface {
36	io.ReadWriteCloser
37
38	// Environ returns the console's environment variables.
39	Environ() []string
40
41	// Getenv retrieves the value of the environment variable named by the key.
42	Getenv(key string) string
43
44	// LookupEnv retrieves the value of the environment variable named by the key
45	// and a boolean indicating whether the variable is present.
46	LookupEnv(key string) (string, bool)
47
48	// Reader returns the input reader of the console.
49	Reader() io.Reader
50
51	// Writer returns the output writer of the console.
52	Writer() io.Writer
53
54	// MakeRaw puts the console input side into raw mode.
55	MakeRaw() (state *term.State, err error)
56
57	// Restore restores the console to its previous state.
58	Restore() error
59
60	// GetSize returns the current size of the console.
61	GetSize() (width, height int, err error)
62
63	// GetWinsize returns the current size of the console in cells and pixels.
64	GetWinsize() (*Winsize, error)
65}
66
67// TTY represents a Unix TTY device. It implements the [Console] interface.
68type TTY struct {
69	*console
70}
71
72var _ Console = (*TTY)(nil)
73
74// WinCon represents a Windows Console. It implements the [Console] interface.
75type WinCon struct {
76	*console
77}
78
79var _ Console = (*WinCon)(nil)
80
81// Console is a cross-platform console I/O.
82type console struct {
83	input       File
84	inputState  *term.State
85	output      File
86	outputState *term.State
87	environ     Environ
88}
89
90// DefaultConsole returns a new default console instance that uses standard I/O
91// [os.Stdin], [os.Stdout], and [os.Environ].
92//
93// To use [os.Stderr] as the output, you can create a new console with
94// [NewConsole] and pass [os.Stderr] as the output parameter.
95func DefaultConsole() Console {
96	return NewConsole(os.Stdin, os.Stdout, os.Environ())
97}
98
99// ControllingConsole returns a new console instance that uses the current
100// controlling terminal's input and output file descriptors.
101func ControllingConsole() (Console, error) {
102	inTty, outTty, err := OpenTTY()
103	if err != nil {
104		return nil, err
105	}
106	return NewConsole(inTty, outTty, os.Environ()), nil
107}
108
109// NewConsole creates a new [Console] with the given input, output, and
110// environment variables.
111//
112// You can use [OpenTTY] to open the current controlling console files and pass
113// them to this function. Use [ControllingConsole] for a convenience function
114// that does this for you.
115//
116// Use this to create a new terminal for PTY processes by passing the PTY slave
117// file as the input and output and any environment variables the process
118// needs.
119func NewConsole(input, output File, environ []string) Console {
120	if input == nil {
121		input = os.Stdin
122	}
123	if output == nil {
124		output = os.Stdout
125	}
126	if environ == nil {
127		environ = os.Environ()
128	}
129	return newConsole(input, output, environ)
130}
131
132// Environ returns the console's environment variables.
133func (t *console) Environ() []string {
134	return t.environ
135}
136
137// Writer returns the output writer of the console.
138func (t *console) Writer() io.Writer {
139	return t.output
140}
141
142// Reader returns the input reader of the console.
143func (t *console) Reader() io.Reader {
144	return t.input
145}
146
147// Write writes data to the console's output.
148func (t *console) Write(p []byte) (n int, err error) {
149	return t.output.Write(p)
150}
151
152// Read reads data from the console's input.
153func (t *console) Read(p []byte) (n int, err error) {
154	return t.input.Read(p)
155}
156
157// Getenv retrieves the value of the environment variable named by the key.
158func (t *console) Getenv(key string) string {
159	return t.environ.Getenv(key)
160}
161
162// LookupEnv retrieves the value of the environment variable named by the key
163// and a boolean indicating whether the variable is present.
164func (t *console) LookupEnv(key string) (string, bool) {
165	return t.environ.LookupEnv(key)
166}
167
168// MakeRaw puts the console input side into raw mode.
169func (t *console) MakeRaw() (state *term.State, err error) {
170	inState, outState, err := makeRaw(t.input, t.output)
171	if err != nil {
172		return nil, err
173	}
174	t.inputState = inState
175	t.outputState = outState
176	if inState != nil {
177		return inState, nil
178	}
179	return outState, nil
180}
181
182// Restore restores the console to its previous state.
183func (t *console) Restore() error {
184	if t.inputState != nil {
185		if err := term.Restore(t.input.Fd(), t.inputState); err != nil {
186			return err
187		}
188		t.inputState = nil
189	}
190	if t.outputState != nil {
191		if err := term.Restore(t.output.Fd(), t.outputState); err != nil {
192			return err
193		}
194		t.outputState = nil
195	}
196	return nil
197}
198
199// Close restores the console to its previous state and releases resources.
200func (t *console) Close() error {
201	if err := t.Restore(); err != nil {
202		return err
203	}
204	return nil
205}
206
207// GetSize returns the current size of the console.
208func (t *console) GetSize() (width, height int, err error) {
209	return getSize(t.input, t.output)
210}
211
212// GetWinsize returns the current size of the console in cells and pixels.
213func (t *console) GetWinsize() (*Winsize, error) {
214	ws, err := getWinsize(t.input, t.output)
215	if err != nil {
216		return nil, err
217	}
218	return &ws, nil
219}