diff --git a/terminal/runereader_posix.go b/terminal/runereader_posix.go index a7e38425..99cbf434 100644 --- a/terminal/runereader_posix.go +++ b/terminal/runereader_posix.go @@ -16,6 +16,11 @@ import ( "unsafe" ) +const ( + normalKeypad = '[' + applicationKeypad = 'O' +) + type runeReaderState struct { term syscall.Termios reader *bufio.Reader @@ -60,52 +65,62 @@ func (rr *RuneReader) RestoreTermMode() error { return nil } +// ReadRune Parse escape sequences such as ESC [ A for arrow keys. +// See https://vt100.net/docs/vt102-ug/appendixc.html func (rr *RuneReader) ReadRune() (rune, int, error) { r, size, err := rr.state.reader.ReadRune() if err != nil { return r, size, err } - // parse ^[ sequences to look for arrow keys - if r == '\033' { - if rr.state.reader.Buffered() == 0 { - // no more characters so must be `Esc` key - return KeyEscape, 1, nil - } - r, size, err = rr.state.reader.ReadRune() - if err != nil { - return r, size, err - } - if r != '[' { - return r, size, fmt.Errorf("Unexpected Escape Sequence: %q", []rune{'\033', r}) - } - r, size, err = rr.state.reader.ReadRune() - if err != nil { - return r, size, err - } - switch r { - case 'D': - return KeyArrowLeft, 1, nil - case 'C': - return KeyArrowRight, 1, nil - case 'A': - return KeyArrowUp, 1, nil - case 'B': - return KeyArrowDown, 1, nil - case 'H': // Home button - return SpecialKeyHome, 1, nil - case 'F': // End button - return SpecialKeyEnd, 1, nil - case '3': // Delete Button + if r != KeyEscape { + return r, size, err + } + + if rr.state.reader.Buffered() == 0 { + // no more characters so must be `Esc` key + return KeyEscape, 1, nil + } + + r, size, err = rr.state.reader.ReadRune() + if err != nil { + return r, size, err + } + + // ESC O ... or ESC [ ...? + if r != normalKeypad && r != applicationKeypad { + return r, size, fmt.Errorf("unexpected escape sequence from terminal: %q", []rune{KeyEscape, r}) + } + + keypad := r + + r, size, err = rr.state.reader.ReadRune() + if err != nil { + return r, size, err + } + + switch r { + case 'A': // ESC [ A or ESC O A + return KeyArrowUp, 1, nil + case 'B': // ESC [ B or ESC O B + return KeyArrowDown, 1, nil + case 'C': // ESC [ C or ESC O C + return KeyArrowRight, 1, nil + case 'D': // ESC [ D or ESC O D + return KeyArrowLeft, 1, nil + case 'F': // ESC [ F or ESC O F + return SpecialKeyEnd, 1, nil + case 'H': // ESC [ H or ESC O H + return SpecialKeyHome, 1, nil + case '3': // ESC [ 3 + if keypad == normalKeypad { // discard the following '~' key from buffer - rr.state.reader.Discard(1) + _, _ = rr.state.reader.Discard(1) return SpecialKeyDelete, 1, nil - default: - // discard the following '~' key from buffer - rr.state.reader.Discard(1) - return IgnoreKey, 1, nil } - return r, size, fmt.Errorf("Unknown Escape Sequence: %q", []rune{'\033', '[', r}) } - return r, size, err + + // discard the following '~' key from buffer + _, _ = rr.state.reader.Discard(1) + return IgnoreKey, 1, nil }