src

Go monorepo.
Log | Files | Refs

commit 54c91681380332187e0265bd5e492be58afcbd3d
parent 87783226a7ea14f6e1a228621210abef4fc36873
Author: dwrz <dwrz@dwrz.net>
Date:   Mon, 19 Dec 2022 20:10:37 +0000

Refactor editor input to terminal input

Diffstat:
Dpkg/editor/command/command.go | 20--------------------
Mpkg/editor/editor.go | 2+-
Mpkg/editor/input.go | 74++++++++++++++++++++++++++++++++++++++++----------------------------------
Dpkg/editor/input/input.go | 220-------------------------------------------------------------------------------
Mpkg/editor/run.go | 14++++++++------
Apkg/terminal/input/input.go | 217+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Apkg/terminal/input/key.go | 151++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dpkg/terminal/key.go | 137-------------------------------------------------------------------------------
8 files changed, 417 insertions(+), 418 deletions(-)

diff --git a/pkg/editor/command/command.go b/pkg/editor/command/command.go @@ -1,20 +0,0 @@ -package command - -type Command int - -const ( - Backspace Command = iota - CursorDown - CursorLeft - CursorRight - CursorUp - Delete - End - Home - Insert - Open - PageDown - PageUp - Quit - Save -) diff --git a/pkg/editor/editor.go b/pkg/editor/editor.go @@ -7,10 +7,10 @@ import ( "code.dwrz.net/src/pkg/editor/buffer" "code.dwrz.net/src/pkg/editor/canvas" - "code.dwrz.net/src/pkg/editor/input" "code.dwrz.net/src/pkg/editor/message" "code.dwrz.net/src/pkg/log" "code.dwrz.net/src/pkg/terminal" + "code.dwrz.net/src/pkg/terminal/input" ) type Editor struct { diff --git a/pkg/editor/input.go b/pkg/editor/input.go @@ -3,74 +3,80 @@ package editor import ( "fmt" - "code.dwrz.net/src/pkg/editor/command" - "code.dwrz.net/src/pkg/editor/input" "code.dwrz.net/src/pkg/editor/message" + "code.dwrz.net/src/pkg/terminal/input" ) -func (e *Editor) bufferInput(input *input.Event) error { +func (e *Editor) bufferInput(event *input.Event) error { size, err := e.terminal.Size() if err != nil { return fmt.Errorf("failed to get terminal size: %w", err) } - switch input.Command { - case command.Backspace: - e.active.Backspace() + if event.Rune != input.Null { + switch event.Rune { + case input.Delete: + e.active.Backspace() + + case 's' & input.Control: + // Get the filename. + if err := e.active.Save(); err != nil { + go func() { + e.messages <- message.New(fmt.Sprintf( + "failed to save: %v", err, + )) + }() + } + go func() { + e.messages <- message.New("saved file") + }() + + default: + e.active.Insert(event.Rune) + } + + return nil + } - case command.CursorDown: + switch event.Key { + case input.Down: e.active.CursorDown() - case command.CursorLeft: + case input.Left: e.active.CursorLeft() - case command.CursorRight: + case input.Right: e.active.CursorRight() - case command.CursorUp: + case input.Up: e.active.CursorUp() - case command.Insert: - e.active.Insert(input.Rune) + case input.Insert: - case command.End: + case input.End: e.active.CursorEnd() - case command.Home: + case input.Home: e.active.CursorHome() - case command.PageDown: + case input.PageDown: e.active.PageDown(int(size.Rows)) - case command.PageUp: + case input.PageUp: e.active.PageUp(int(size.Rows)) - case command.Save: - // Get the filename. - if err := e.active.Save(); err != nil { - go func() { - e.messages <- message.New(fmt.Sprintf( - "failed to save: %v", err, - )) - }() - } - go func() { - e.messages <- message.New("saved file") - }() - default: - e.log.Debug.Printf("unrecognized input: %#v", input) + e.log.Debug.Printf("unrecognized input: %#v", event) } return nil } -func (e *Editor) promptInput(input *input.Event) error { - switch input.Command { - case command.Backspace: +func (e *Editor) promptInput(event *input.Event) error { + switch event.Key { default: - e.log.Debug.Printf("unrecognized input: %#v", input) + e.log.Debug.Printf("unrecognized input: %#v", event) } // If a newline was received, take the input. diff --git a/pkg/editor/input/input.go b/pkg/editor/input/input.go @@ -1,220 +0,0 @@ -package input - -import ( - "bufio" - "fmt" - "os" - "unicode/utf8" - - "code.dwrz.net/src/pkg/editor/command" - "code.dwrz.net/src/pkg/log" - "code.dwrz.net/src/pkg/terminal" -) - -type Event struct { - Command command.Command - Rune rune -} - -type Reader struct { - buf *bufio.Reader - events chan *Event - log *log.Logger -} - -type Parameters struct { - Chan chan *Event - In *os.File - Log *log.Logger -} - -func New(p Parameters) *Reader { - return &Reader{ - buf: bufio.NewReader(p.In), - events: p.Chan, - log: p.Log, - } -} - -// 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() error { - for { - r, size, err := i.buf.ReadRune() - if err != nil { - return fmt.Errorf("failed to read: %w", err) - } - i.log.Debug.Printf( - "read rune %s %v (%d)", - string(r), []byte(string(r)), size, - ) - switch r { - case utf8.RuneError: - i.log.Error.Printf( - "rune error: %s (%d)", string(r), size, - ) - - // Handle escape sequences. - case terminal.Escape: - if err := i.parseEscapeSequence(); err != nil { - return fmt.Errorf("failed to read: %w", err) - } - - case terminal.Delete: - i.events <- &Event{Command: command.Backspace} - - case 'q' & terminal.Control: - i.events <- &Event{Command: command.Quit} - - case 's' & terminal.Control: - i.events <- &Event{Command: command.Save} - - default: - i.events <- &Event{Command: command.Insert, Rune: r} - } - } -} - -func (i *Reader) parseEscapeSequence() 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' { - i.events <- &Event{Command: command.Insert, Rune: r1} - 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': - i.events <- &Event{Command: command.CursorUp} - return nil - case 'B': - i.events <- &Event{Command: command.CursorDown} - return nil - case 'C': - i.events <- &Event{Command: command.CursorRight} - return nil - case 'D': - i.events <- &Event{Command: command.CursorLeft} - 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. - i.events <- &Event{Command: command.Insert, Rune: r1} - i.events <- &Event{Command: command.Insert, Rune: r2} - i.events <- &Event{Command: command.Insert, Rune: r3} - 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 == '~': - i.events <- &Event{Command: command.Home} - return nil - case r2 == '2' && r3 == '~': - i.events <- &Event{Command: command.Insert} - return nil - case r2 == '3' && r3 == '~': - i.events <- &Event{Command: command.Delete} - return nil - case r2 == '4' && r3 == '~': - i.events <- &Event{Command: command.End} - return nil - case r2 == '5' && r3 == '~': - i.events <- &Event{Command: command.PageUp} - return nil - case r2 == '6' && r3 == '~': - i.events <- &Event{Command: command.PageDown} - return nil - case r2 == '7' && r3 == '~': - i.events <- &Event{Command: command.Home} - return nil - case r2 == '8' && r3 == '~': - i.events <- &Event{Command: command.End} - return nil - case r2 == '9' && r3 == '~': - i.events <- &Event{Command: command.End} - 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. - i.events <- &Event{Command: command.Insert, Rune: r1} - i.events <- &Event{Command: command.Insert, Rune: r2} - i.events <- &Event{Command: command.Insert, Rune: r3} - i.events <- &Event{Command: command.Insert, Rune: r4} - - return nil -} diff --git a/pkg/editor/run.go b/pkg/editor/run.go @@ -5,11 +5,13 @@ import ( "fmt" "code.dwrz.net/src/pkg/build" - "code.dwrz.net/src/pkg/editor/command" + "code.dwrz.net/src/pkg/terminal/input" ) func (e *Editor) Run(ctx context.Context, files []string) error { - e.terminal.SetRaw() + if err := e.terminal.SetRaw(); err != nil { + return fmt.Errorf("failed to set raw mode: %w", err) + } e.canvas.Reset() // Log build info. @@ -23,7 +25,7 @@ func (e *Editor) Run(ctx context.Context, files []string) error { // Start reading user input. go func() { - if err := e.reader.Run(); err != nil { + if err := e.reader.Run(ctx); err != nil { e.errs <- err } }() @@ -47,11 +49,11 @@ func (e *Editor) Run(ctx context.Context, files []string) error { return fmt.Errorf("failed to render: %w", err) } - case input := <-e.input: - if input.Command == command.Quit { + case event := <-e.input: + if event.Rune == 'q'&input.Control { return nil } - if err := e.bufferInput(input); err != nil { + if err := e.bufferInput(event); err != nil { return fmt.Errorf( "failed to process input: %w", err, ) diff --git a/pkg/terminal/input/input.go b/pkg/terminal/input/input.go @@ -0,0 +1,217 @@ +package input + +import ( + "bufio" + "context" + "fmt" + "os" + "unicode/utf8" + + "code.dwrz.net/src/pkg/log" +) + +type Event struct { + Key Key + Rune rune +} + +type Reader struct { + buf *bufio.Reader + events chan *Event + log *log.Logger +} + +type Parameters struct { + Chan chan *Event + In *os.File + Log *log.Logger +} + +func New(p Parameters) *Reader { + return &Reader{ + buf: bufio.NewReader(p.In), + events: p.Chan, + log: p.Log, + } +} + +// 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.Debug.Printf( + "read rune %s %v (%d)", + string(r), []byte(string(r)), size, + ) + switch r { + case utf8.RuneError: + i.log.Error.Printf( + "rune error: %s (%d)", string(r), size, + ) + + // Handle escape sequences. + case Escape: + if err := i.parseEscapeSequence(); err != nil { + return fmt.Errorf( + "failed to read: %w", err, + ) + } + + default: + i.events <- &Event{Rune: r} + } + } + } +} + +func (i *Reader) parseEscapeSequence() 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' { + i.events <- &Event{Rune: r1} + 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': + i.events <- &Event{Key: Up} + return nil + case 'B': + i.events <- &Event{Key: Down} + return nil + case 'C': + i.events <- &Event{Key: Right} + return nil + case 'D': + i.events <- &Event{Key: Left} + 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. + i.events <- &Event{Rune: r1} + i.events <- &Event{Rune: r2} + i.events <- &Event{Rune: r3} + 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 == '~': + i.events <- &Event{Key: Home} + return nil + case r2 == '2' && r3 == '~': + i.events <- &Event{Key: Insert} + return nil + case r2 == '3' && r3 == '~': + i.events <- &Event{Rune: Delete} + return nil + case r2 == '4' && r3 == '~': + i.events <- &Event{Key: End} + return nil + case r2 == '5' && r3 == '~': + i.events <- &Event{Key: PageUp} + return nil + case r2 == '6' && r3 == '~': + i.events <- &Event{Key: PageDown} + return nil + case r2 == '7' && r3 == '~': + i.events <- &Event{Key: Home} + return nil + case r2 == '8' && r3 == '~': + i.events <- &Event{Key: End} + return nil + case r2 == '9' && r3 == '~': + i.events <- &Event{Key: End} + 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. + i.events <- &Event{Rune: r1} + i.events <- &Event{Rune: r2} + i.events <- &Event{Rune: r3} + i.events <- &Event{Rune: r4} + + return nil +} diff --git a/pkg/terminal/input/key.go b/pkg/terminal/input/key.go @@ -0,0 +1,151 @@ +package input + +type Key uint8 + +const ( + Down Key = iota + 1 + Left + Right + Up + End + Home + Insert + PageDown + PageUp +) + +const Control rune = 0x1f + +const ( + // Control Characters + Null rune = 0 + StartOfHeading rune = 1 + StartOfText rune = 2 + EndOfText rune = 3 + EndOfTransmission rune = 4 + Enquiry rune = 5 + Acknowledgment rune = 6 + Bell rune = 7 + Backspace rune = 8 + HorizontalTab rune = 9 + LineFeed rune = 10 + VerticalTab rune = 11 + FormFeed rune = 12 + CarriageReturn rune = 13 + ShiftOut rune = 14 + ShiftIn rune = 15 + DataLineEscape rune = 16 + DeviceControl1 rune = 17 + DeviceControl2 rune = 18 + DeviceControl3 rune = 19 + DeviceControl4 rune = 20 + NegativeAcknowledgment rune = 21 + SynchronousIdle rune = 22 + EndOfTransmitBlock rune = 23 + Cancel rune = 24 + EndOfMedium rune = 25 + Substitute rune = 26 + Escape rune = 27 + FileSeparator rune = 28 + GroupSeparator rune = 29 + RecordSeparator rune = 30 + UnitSeparator rune = 31 + + // Printable Characters + Space rune = 32 + ExclamationMark rune = 33 + DoubleQuote rune = 34 + Number rune = 35 + Dollar rune = 36 + Percentage rune = 37 + Ampersand rune = 38 + SingleQuote rune = 39 + LeftParenthesis rune = 40 + RightParenthesis rune = 41 + Asterisk rune = 42 + Plus rune = 43 + Comma rune = 44 + Hyphen rune = 45 + Period rune = 46 + ForwardSlash rune = 47 + Zero rune = 48 + One rune = 49 + Two rune = 50 + Three rune = 51 + Four rune = 52 + Five rune = 53 + Six rune = 54 + Seven rune = 55 + Eight rune = 56 + Nine rune = 57 + Colon rune = 58 + Semicolon rune = 59 + LessThan rune = 60 + Equals rune = 61 + GreaterThan rune = 62 + QuestionMark rune = 63 + At rune = 64 + UpperA rune = 65 + UpperB rune = 66 + UpperC rune = 67 + UpperD rune = 68 + UpperE rune = 69 + UpperF rune = 70 + UpperG rune = 71 + UpperH rune = 72 + UpperI rune = 73 + UpperJ rune = 74 + UpperK rune = 75 + UpperL rune = 76 + UpperM rune = 77 + UpperN rune = 78 + UpperO rune = 79 + UpperP rune = 80 + UpperQ rune = 81 + UpperR rune = 82 + UpperS rune = 83 + UpperT rune = 84 + UpperU rune = 85 + UpperV rune = 86 + UpperW rune = 87 + UpperX rune = 88 + UpperY rune = 89 + UpperZ rune = 90 + LeftBracket rune = 91 + Backslash rune = 92 + RightBracket rune = 93 + Caret rune = 94 + Underscore rune = 95 + Grave rune = 96 + LowerA rune = 97 + LowerB rune = 98 + LowerC rune = 99 + LowerD rune = 100 + LowerE rune = 101 + LowerF rune = 102 + LowerG rune = 103 + LowerH rune = 104 + LowerI rune = 105 + LowerJ rune = 106 + LowerK rune = 107 + LowerL rune = 108 + LowerM rune = 109 + LowerN rune = 110 + LowerO rune = 111 + LowerP rune = 112 + LowerQ rune = 113 + LowerR rune = 114 + LowerS rune = 115 + LowerT rune = 116 + LowerU rune = 117 + LowerV rune = 118 + LowerW rune = 119 + LowerX rune = 120 + LowerY rune = 121 + LowerZ rune = 122 + LeftBrace rune = 123 + VerticalBar rune = 124 + RightBrace rune = 125 + Tilde rune = 126 + Delete rune = 127 +) diff --git a/pkg/terminal/key.go b/pkg/terminal/key.go @@ -1,137 +0,0 @@ -package terminal - -const ( - // Control Characters - Null rune = 0 - StartOfHeading rune = 1 - StartOfText rune = 2 - EndOfText rune = 3 - EndOfTransmission rune = 4 - Enquiry rune = 5 - Acknowledgment rune = 6 - Bell rune = 7 - Backspace rune = 8 - HorizontalTab rune = 9 - LineFeed rune = 10 - VerticalTab rune = 11 - FormFeed rune = 12 - CarriageReturn rune = 13 - ShiftOut rune = 14 - ShiftIn rune = 15 - DataLineEscape rune = 16 - DeviceControl1 rune = 17 - DeviceControl2 rune = 18 - DeviceControl3 rune = 19 - DeviceControl4 rune = 20 - NegativeAcknowledgment rune = 21 - SynchronousIdle rune = 22 - EndOfTransmitBlock rune = 23 - Cancel rune = 24 - EndOfMedium rune = 25 - Substitute rune = 26 - Escape rune = 27 - FileSeparator rune = 28 - GroupSeparator rune = 29 - RecordSeparator rune = 30 - UnitSeparator rune = 31 - - // Printable Characters - Space rune = 32 - ExclamationMark rune = 33 - DoubleQuote rune = 34 - Number rune = 35 - Dollar rune = 36 - Percentage rune = 37 - Ampersand rune = 38 - SingleQuote rune = 39 - LeftParenthesis rune = 40 - RightParenthesis rune = 41 - Asterisk rune = 42 - Plus rune = 43 - Comma rune = 44 - Hyphen rune = 45 - Period rune = 46 - ForwardSlash rune = 47 - Zero rune = 48 - One rune = 49 - Two rune = 50 - Three rune = 51 - Four rune = 52 - Five rune = 53 - Six rune = 54 - Seven rune = 55 - Eight rune = 56 - Nine rune = 57 - Colon rune = 58 - Semicolon rune = 59 - LessThan rune = 60 - Equals rune = 61 - GreaterThan rune = 62 - QuestionMark rune = 63 - At rune = 64 - UpperA rune = 65 - UpperB rune = 66 - UpperC rune = 67 - UpperD rune = 68 - UpperE rune = 69 - UpperF rune = 70 - UpperG rune = 71 - UpperH rune = 72 - UpperI rune = 73 - UpperJ rune = 74 - UpperK rune = 75 - UpperL rune = 76 - UpperM rune = 77 - UpperN rune = 78 - UpperO rune = 79 - UpperP rune = 80 - UpperQ rune = 81 - UpperR rune = 82 - UpperS rune = 83 - UpperT rune = 84 - UpperU rune = 85 - UpperV rune = 86 - UpperW rune = 87 - UpperX rune = 88 - UpperY rune = 89 - UpperZ rune = 90 - LeftBracket rune = 91 - Backslash rune = 92 - RightBracket rune = 93 - Caret rune = 94 - Underscore rune = 95 - Grave rune = 96 - LowerA rune = 97 - LowerB rune = 98 - LowerC rune = 99 - LowerD rune = 100 - LowerE rune = 101 - LowerF rune = 102 - LowerG rune = 103 - LowerH rune = 104 - LowerI rune = 105 - LowerJ rune = 106 - LowerK rune = 107 - LowerL rune = 108 - LowerM rune = 109 - LowerN rune = 110 - LowerO rune = 111 - LowerP rune = 112 - LowerQ rune = 113 - LowerR rune = 114 - LowerS rune = 115 - LowerT rune = 116 - LowerU rune = 117 - LowerV rune = 118 - LowerW rune = 119 - LowerX rune = 120 - LowerY rune = 121 - LowerZ rune = 122 - LeftBrace rune = 123 - VerticalBar rune = 124 - RightBrace rune = 125 - Tilde rune = 126 - Delete rune = 127 -) - -const Control = 0x1f