Add terminal pkg
This commit is contained in:
@@ -0,0 +1,276 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user