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 }