input.go

 1package uv
 2
 3import (
 4	"context"
 5
 6	"github.com/charmbracelet/x/term"
 7	"golang.org/x/sync/errgroup"
 8)
 9
10// Size represents the size of the terminal window.
11type Size struct {
12	Width  int
13	Height int
14}
15
16// Bounds returns the bounds corresponding to the size.
17func (s Size) Bounds() Rectangle {
18	return Rect(0, 0, s.Width, s.Height)
19}
20
21// InputReceiver is an interface for receiving input events from an input source.
22type InputReceiver interface {
23	// ReceiveEvents read input events and channel them to the given event
24	// channel. The listener stops when either the context is done or an error
25	// occurs. Caller is responsible for closing the channels.
26	ReceiveEvents(ctx context.Context, events chan<- Event) error
27}
28
29// InputManager manages input events from multiple input sources. It listens
30// for input events from the registered input sources and combines them into a
31// single event channel. It also handles errors from the input sources and
32// sends them to the error channel.
33type InputManager struct {
34	receivers []InputReceiver
35}
36
37// NewInputManager creates a new InputManager with the input receivers.
38func NewInputManager(receivers ...InputReceiver) *InputManager {
39	im := &InputManager{
40		receivers: receivers,
41	}
42	return im
43}
44
45// RegisterReceiver registers a new input receiver with the input manager.
46func (im *InputManager) RegisterReceiver(r InputReceiver) {
47	im.receivers = append(im.receivers, r)
48}
49
50// ReceiveEvents starts receiving events from the registered input
51// receivers. It sends the events to the given event and error channels.
52func (im *InputManager) ReceiveEvents(ctx context.Context, events chan<- Event) error {
53	errg, ctx := errgroup.WithContext(ctx)
54	for _, r := range im.receivers {
55		errg.Go(func() error {
56			return r.ReceiveEvents(ctx, events)
57		})
58	}
59
60	// Wait for all receivers to finish
61	return errg.Wait()
62}
63
64// InitialSizeReceiver query the initial size of the terminal window and sends
65// it to the given event channel.
66type InitialSizeReceiver struct {
67	File term.File
68}
69
70// ReceiveEvents queries the initial size of the terminal window and sends it
71// to the given event channel. It stops when event is sent, the context is done
72// or an error occurs.
73func (r *InitialSizeReceiver) ReceiveEvents(ctx context.Context, events chan<- Event) error {
74	if r.File == nil {
75		return nil // No file set, nothing to do.
76	}
77
78	w, h, err := term.GetSize(r.File.Fd())
79	if err != nil {
80		return err
81	}
82
83	select {
84	case <-ctx.Done():
85		return ctx.Err()
86	case events <- WindowSizeEvent{Width: w, Height: h}:
87		return nil
88	}
89}