package terminal import "github.com/gdamore/tcell/v2" // Encode turns a key press into the bytes a terminal program expects to read. // // applicationCursor is the screen's DECCKM setting: a program that has asked // for application cursor keys expects ESC O A for the up arrow rather than // ESC [ A, and gets the arrow keys wrong if it is given the other form. // // A key with no terminal meaning encodes to nothing at all, which the caller // should treat as "nothing to send" rather than as an empty line. // // terminal.Encode(tcell.NewEventKey(tcell.KeyEnter, 0, tcell.ModNone), false) // // []byte{'\r'} func Encode(ev *tcell.EventKey, applicationCursor bool) []byte { if sequence, ok := encodeCursor(ev.Key(), applicationCursor); ok { return sequence } if sequence, ok := namedKeys[ev.Key()]; ok { return []byte(sequence) } if ev.Key() == tcell.KeyRune { return encodeRune(ev) } return encodeControl(ev) } // cursorKeys are the four arrows and the two keys that travel with them, whose // encoding depends on the mode the program asked for. var cursorKeys = map[tcell.Key]byte{ tcell.KeyUp: 'A', tcell.KeyDown: 'B', tcell.KeyRight: 'C', tcell.KeyLeft: 'D', tcell.KeyHome: 'H', tcell.KeyEnd: 'F', } // encodeCursor encodes an arrow, Home or End in whichever form the program is // expecting. func encodeCursor(key tcell.Key, applicationCursor bool) ([]byte, bool) { final, ok := cursorKeys[key] if !ok { return nil, false } introducer := byte('[') if applicationCursor { introducer = 'O' } return []byte{escape, introducer, final}, true } // namedKeys are the keys with a fixed sequence of their own. var namedKeys = map[tcell.Key]string{ tcell.KeyEnter: "\r", tcell.KeyTab: "\t", tcell.KeyEscape: "\x1b", tcell.KeyBackspace: "\x7f", tcell.KeyBackspace2: "\x7f", tcell.KeyBacktab: "\x1b[Z", tcell.KeyInsert: "\x1b[2~", tcell.KeyDelete: "\x1b[3~", tcell.KeyPgUp: "\x1b[5~", tcell.KeyPgDn: "\x1b[6~", tcell.KeyF1: "\x1bOP", tcell.KeyF2: "\x1bOQ", tcell.KeyF3: "\x1bOR", tcell.KeyF4: "\x1bOS", tcell.KeyF5: "\x1b[15~", tcell.KeyF6: "\x1b[17~", tcell.KeyF7: "\x1b[18~", tcell.KeyF8: "\x1b[19~", tcell.KeyF9: "\x1b[20~", tcell.KeyF10: "\x1b[21~", tcell.KeyF11: "\x1b[23~", tcell.KeyF12: "\x1b[24~", } // encodeRune encodes a printable character, with the escape prefix that stands // for Alt. func encodeRune(ev *tcell.EventKey) []byte { text := []byte(string(ev.Rune())) if ev.Modifiers()&tcell.ModAlt != 0 { return append([]byte{escape}, text...) } return text } // encodeControl encodes a Ctrl-key press. // // tcell does not report the control byte itself: reading 0x03 from the // terminal, it posts KeyCtrlSpace+3, and KeyCtrlSpace is 64. So the whole // range 64…95 stands for the control bytes 0…31, and turning a key back into // what a terminal expects means subtracting that offset again. // // Getting this wrong is silent: every Ctrl-key would simply send nothing. func encodeControl(ev *tcell.EventKey) []byte { key := ev.Key() if key < tcell.KeyCtrlSpace || key > tcell.KeyCtrlUnderscore { return nil } control := byte(key - tcell.KeyCtrlSpace) if ev.Modifiers()&tcell.ModAlt != 0 { return []byte{escape, control} } return []byte{control} }