Files
2026-10-25 02:51:02 +00:00

277 lines
5.6 KiB
Go

package input
import (
"bufio"
"context"
"fmt"
"log/slog"
"os"
"unicode/utf8"
)
type Event struct {
Key Key
Rune rune
}
type Reader struct {
buf *bufio.Reader
events chan *Event
log *slog.Logger
}
type Parameters struct {
Chan chan *Event
In *os.File
Log *slog.Logger
}
func New(p Parameters) *Reader {
return &Reader{
buf: bufio.NewReader(p.In),
events: p.Chan,
log: p.Log,
}
}
// send delivers e, or gives up when ctx is done.
func (i *Reader) send(ctx context.Context, e *Event) error {
select {
case <-ctx.Done():
return ctx.Err()
case i.events <- e:
return nil
}
}
// TODO: reading one rune at a time is slow, especially when pasting large
// quantities of text into the active buffer. It would be nice to take more
// input at once, while still being able to handle escape sequences without
// too many edge cases.
func (i *Reader) Run(ctx context.Context) error {
for {
select {
case <-ctx.Done():
return nil
default:
r, size, err := i.buf.ReadRune()
if err != nil {
return fmt.Errorf("failed to read: %w", err)
}
i.log.DebugContext(
ctx,
"read rune",
slog.String("rune", string(r)),
slog.Any("bytes", []byte(string(r))),
slog.Int("size", size),
)
switch r {
case utf8.RuneError:
i.log.ErrorContext(
ctx,
"rune error",
slog.String("rune", string(r)),
slog.Int("size", size),
)
// Handle escape sequences.
case Escape:
if err := i.parseEscapeSequence(ctx); err != nil {
return fmt.Errorf(
"failed to read: %w", err,
)
}
default:
if err := i.send(ctx, &Event{Rune: r}); err != nil {
return err
}
}
}
}
}
func (i *Reader) parseEscapeSequence(ctx context.Context) error {
r1, _, err := i.buf.ReadRune()
if err != nil {
return fmt.Errorf("failed to read: %w", err)
}
// Ignore invalid escape sequences.
if r1 != '[' && r1 != 'O' {
if err := i.send(ctx, &Event{Rune: r1}); err != nil {
return err
}
return nil
}
// We've received an input of Esc + [ or Esc + O.
// Determine the escape sequence.
r2, _, err := i.buf.ReadRune()
if err != nil {
return fmt.Errorf("failed to read: %w", err)
}
// Check letter escape sequences.
switch r2 {
case 'A':
if err := i.send(ctx, &Event{Key: Up}); err != nil {
return err
}
return nil
case 'B':
if err := i.send(ctx, &Event{Key: Down}); err != nil {
return err
}
return nil
case 'C':
if err := i.send(ctx, &Event{Key: Right}); err != nil {
return err
}
return nil
case 'D':
if err := i.send(ctx, &Event{Key: Left}); err != nil {
return err
}
return nil
case 'O':
r3, _, err := i.buf.ReadRune()
if err != nil {
return fmt.Errorf("failed to read: %w", err)
}
switch r3 {
case 'P': // F1
return nil
case 'Q': // F2
return nil
case 'R': // F3
return nil
case 'S': // F4
return nil
default:
// No match.
if err := i.send(ctx, &Event{Rune: r1}); err != nil {
return err
}
if err := i.send(ctx, &Event{Rune: r2}); err != nil {
return err
}
if err := i.send(ctx, &Event{Rune: r3}); err != nil {
return err
}
return nil
}
}
// Check for single digit numerical escape sequences.
r3, _, err := i.buf.ReadRune()
if err != nil {
return fmt.Errorf("failed to read: %w", err)
}
switch {
case r2 == '1' && r3 == '~':
if err := i.send(ctx, &Event{Key: Home}); err != nil {
return err
}
return nil
case r2 == '2' && r3 == '~':
if err := i.send(ctx, &Event{Key: Insert}); err != nil {
return err
}
return nil
case r2 == '3' && r3 == '~':
if err := i.send(ctx, &Event{Rune: Delete}); err != nil {
return err
}
return nil
case r2 == '4' && r3 == '~':
if err := i.send(ctx, &Event{Key: End}); err != nil {
return err
}
return nil
case r2 == '5' && r3 == '~':
if err := i.send(ctx, &Event{Key: PageUp}); err != nil {
return err
}
return nil
case r2 == '6' && r3 == '~':
if err := i.send(ctx, &Event{Key: PageDown}); err != nil {
return err
}
return nil
case r2 == '7' && r3 == '~':
if err := i.send(ctx, &Event{Key: Home}); err != nil {
return err
}
return nil
case r2 == '8' && r3 == '~':
if err := i.send(ctx, &Event{Key: End}); err != nil {
return err
}
return nil
case r2 == '9' && r3 == '~':
if err := i.send(ctx, &Event{Key: End}); err != nil {
return err
}
return nil
}
// Check for double digit numerical escape sequences.
r4, _, err := i.buf.ReadRune()
if err != nil {
return fmt.Errorf("failed to read: %w", err)
}
switch {
case r2 == '1' && r3 == '0' && r4 == '~':
return nil
case r2 == '1' && r3 == '1' && r4 == '~':
return nil
case r2 == '1' && r3 == '2' && r4 == '~':
return nil
case r2 == '1' && r3 == '3' && r4 == '~':
return nil
case r2 == '1' && r3 == '4' && r4 == '~':
return nil
case r2 == '1' && r3 == '4' && r4 == '~':
return nil
case r2 == '1' && r3 == '6' && r4 == '~':
return nil
case r2 == '1' && r3 == '7' && r4 == '~':
return nil
case r2 == '1' && r3 == '8' && r4 == '~':
return nil
case r2 == '1' && r3 == '9' && r4 == '~':
return nil
case r2 == '2' && r3 == '0' && r4 == '~':
return nil
case r2 == '2' && r3 == '1' && r4 == '~':
return nil
case r2 == '2' && r3 == '2' && r4 == '~':
return nil
case r2 == '2' && r3 == '3' && r4 == '~':
return nil
case r2 == '2' && r3 == '4' && r4 == '~':
return nil
case r4 == '~':
return nil
}
// No match.
if err := i.send(ctx, &Event{Rune: r1}); err != nil {
return err
}
if err := i.send(ctx, &Event{Rune: r2}); err != nil {
return err
}
if err := i.send(ctx, &Event{Rune: r3}); err != nil {
return err
}
if err := i.send(ctx, &Event{Rune: r4}); err != nil {
return err
}
return nil
}