Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(keys): add support for shift/ctrl + arrow keys #292

Merged
merged 11 commits into from May 10, 2022
203 changes: 138 additions & 65 deletions key.go
Expand Up @@ -199,6 +199,18 @@ const (
KeyPgDown
KeyDelete
KeySpace
KeyCtrlUp
KeyCtrlDown
KeyCtrlRight
KeyCtrlLeft
KeyShiftUp
KeyShiftDown
KeyShiftRight
KeyShiftLeft
KeyCtrlShiftUp
KeyCtrlShiftDown
KeyCtrlShiftLeft
KeyCtrlShiftRight
KeyF1
KeyF2
KeyF3
Expand All @@ -224,7 +236,7 @@ const (
// Mappings for control keys and other special keys to friendly consts.
var keyNames = map[KeyType]string{
// Control keys.
keyNUL: "ctrl+@", // also ctrl+`
keyNUL: "ctrl+@", // also ctrl+` (that's ctrl+backtick)
keySOH: "ctrl+a",
keySTX: "ctrl+b",
keyETX: "ctrl+c",
Expand Down Expand Up @@ -259,46 +271,127 @@ var keyNames = map[KeyType]string{
keyDEL: "backspace",

// Other keys.
KeyRunes: "runes",
KeyUp: "up",
KeyDown: "down",
KeyRight: "right",
KeySpace: " ", // for backwards compatibility
KeyLeft: "left",
KeyShiftTab: "shift+tab",
KeyHome: "home",
KeyEnd: "end",
KeyPgUp: "pgup",
KeyPgDown: "pgdown",
KeyDelete: "delete",
KeyF1: "f1",
KeyF2: "f2",
KeyF3: "f3",
KeyF4: "f4",
KeyF5: "f5",
KeyF6: "f6",
KeyF7: "f7",
KeyF8: "f8",
KeyF9: "f9",
KeyF10: "f10",
KeyF11: "f11",
KeyF12: "f12",
KeyF13: "f13",
KeyF14: "f14",
KeyF15: "f15",
KeyF16: "f16",
KeyF17: "f17",
KeyF18: "f18",
KeyF19: "f19",
KeyF20: "f20",
KeyRunes: "runes",
KeyUp: "up",
KeyDown: "down",
KeyRight: "right",
KeySpace: " ", // for backwards compatibility
KeyLeft: "left",
KeyShiftTab: "shift+tab",
KeyHome: "home",
KeyEnd: "end",
KeyPgUp: "pgup",
KeyPgDown: "pgdown",
KeyDelete: "delete",
KeyCtrlUp: "ctrl+up",
KeyCtrlDown: "ctrl+down",
KeyCtrlRight: "ctrl+right",
KeyCtrlLeft: "ctrl+left",
KeyShiftUp: "shift+up",
KeyShiftDown: "shift+down",
KeyShiftRight: "shift+right",
KeyShiftLeft: "shift+left",
KeyCtrlShiftUp: "ctrl+shift+up",
KeyCtrlShiftDown: "ctrl+shift+down",
KeyCtrlShiftLeft: "ctrl+shift+left",
KeyCtrlShiftRight: "ctrl+shift+right",
KeyF1: "f1",
KeyF2: "f2",
KeyF3: "f3",
KeyF4: "f4",
KeyF5: "f5",
KeyF6: "f6",
KeyF7: "f7",
KeyF8: "f8",
KeyF9: "f9",
KeyF10: "f10",
KeyF11: "f11",
KeyF12: "f12",
KeyF13: "f13",
KeyF14: "f14",
KeyF15: "f15",
KeyF16: "f16",
KeyF17: "f17",
KeyF18: "f18",
KeyF19: "f19",
KeyF20: "f20",
}

// Sequence mappings.
var sequences = map[string]Key{
"\x1b[A": {Type: KeyUp},
"\x1b[B": {Type: KeyDown},
"\x1b[C": {Type: KeyRight},
"\x1b[D": {Type: KeyLeft},
// Arrow keys
"\x1b[A": {Type: KeyUp},
"\x1b[B": {Type: KeyDown},
"\x1b[C": {Type: KeyRight},
"\x1b[D": {Type: KeyLeft},
"\x1b[1;2A": {Type: KeyShiftUp},
"\x1b[1;2B": {Type: KeyShiftDown},
"\x1b[1;2C": {Type: KeyShiftRight},
"\x1b[1;2D": {Type: KeyShiftLeft},
"\x1b[OA": {Type: KeyShiftUp}, // DECCKM
"\x1b[OB": {Type: KeyShiftDown}, // DECCKM
"\x1b[OC": {Type: KeyShiftRight}, // DECCKM
"\x1b[OD": {Type: KeyShiftLeft}, // DECCKM
"\x1b[a": {Type: KeyShiftUp}, // urxvt
"\x1b[b": {Type: KeyShiftDown}, // urxvt
"\x1b[c": {Type: KeyShiftRight}, // urxvt
"\x1b[d": {Type: KeyShiftLeft}, // urxvt
"\x1b[1;3A": {Type: KeyUp, Alt: true},
"\x1b[1;3B": {Type: KeyDown, Alt: true},
"\x1b[1;3C": {Type: KeyRight, Alt: true},
"\x1b[1;3D": {Type: KeyLeft, Alt: true},
"\x1b\x1b[A": {Type: KeyUp, Alt: true}, // urxvt
"\x1b\x1b[B": {Type: KeyDown, Alt: true}, // urxvt
"\x1b\x1b[C": {Type: KeyRight, Alt: true}, // urxvt
"\x1b\x1b[D": {Type: KeyLeft, Alt: true}, // urxvt
"\x1b[1;4A": {Type: KeyShiftUp, Alt: true},
"\x1b[1;4B": {Type: KeyShiftDown, Alt: true},
"\x1b[1;4C": {Type: KeyShiftRight, Alt: true},
"\x1b[1;4D": {Type: KeyShiftLeft, Alt: true},
"\x1b\x1b[a": {Type: KeyShiftUp, Alt: true}, // urxvt
"\x1b\x1b[b": {Type: KeyShiftDown, Alt: true}, // urxvt
"\x1b\x1b[c": {Type: KeyShiftRight, Alt: true}, // urxvt
"\x1b\x1b[d": {Type: KeyShiftLeft, Alt: true}, // urxvt
"\x1b[1;5A": {Type: KeyCtrlUp},
"\x1b[1;5B": {Type: KeyCtrlDown},
"\x1b[1;5C": {Type: KeyCtrlRight},
"\x1b[1;5D": {Type: KeyCtrlLeft},
"\x1b[Oa": {Type: KeyCtrlUp, Alt: true}, // urxvt
"\x1b[Ob": {Type: KeyCtrlDown, Alt: true}, // urxvt
"\x1b[Oc": {Type: KeyCtrlRight, Alt: true}, // urxvt
"\x1b[Od": {Type: KeyCtrlLeft, Alt: true}, // urxvt
"\x1b[1;6A": {Type: KeyCtrlShiftUp},
"\x1b[1;6B": {Type: KeyCtrlShiftDown},
"\x1b[1;6C": {Type: KeyCtrlShiftRight},
"\x1b[1;6D": {Type: KeyCtrlShiftLeft},
"\x1b[1;7A": {Type: KeyCtrlUp, Alt: true},
"\x1b[1;7B": {Type: KeyCtrlDown, Alt: true},
"\x1b[1;7C": {Type: KeyCtrlRight, Alt: true},
"\x1b[1;7D": {Type: KeyCtrlLeft, Alt: true},
"\x1b[1;8A": {Type: KeyCtrlShiftUp, Alt: true},
"\x1b[1;8B": {Type: KeyCtrlShiftDown, Alt: true},
"\x1b[1;8C": {Type: KeyCtrlShiftRight, Alt: true},
"\x1b[1;8D": {Type: KeyCtrlShiftLeft, Alt: true},

// Miscellaneous keys
"\x1b[Z": {Type: KeyShiftTab},
"\x1b[3~": {Type: KeyDelete},
"\x1b[3;3~": {Type: KeyDelete, Alt: true},
"\x1b[1~": {Type: KeyHome},
"\x1b[1;3H~": {Type: KeyHome, Alt: true},
"\x1b[4~": {Type: KeyEnd},
"\x1b[1;3F~": {Type: KeyEnd, Alt: true},
"\x1b[5~": {Type: KeyPgUp},
"\x1b[5;3~": {Type: KeyPgUp, Alt: true},
"\x1b[6~": {Type: KeyPgDown},
"\x1b[6;3~": {Type: KeyPgDown, Alt: true},
"\x1b[7~": {Type: KeyHome}, // urxvt
"\x1b[8~": {Type: KeyEnd}, // urxvt
"\x1b\x1b[3~": {Type: KeyDelete, Alt: true}, // urxvt
"\x1b\x1b[5~": {Type: KeyPgUp, Alt: true}, // urxvt
"\x1b\x1b[6~": {Type: KeyPgDown, Alt: true}, // urxvt
"\x1b\x1b[7~": {Type: KeyHome, Alt: true}, // urxvt
"\x1b\x1b[8~": {Type: KeyEnd, Alt: true}, // urxvt

// Function keys, X11
"\x1bOP": {Type: KeyF1}, // vt100
Expand Down Expand Up @@ -367,28 +460,8 @@ var sequences = map[string]Key{

// Hex code mappings.
var hexes = map[string]Key{
"1b5b5a": {Type: KeyShiftTab},
"1b5b337e": {Type: KeyDelete},
"1b0d": {Type: KeyEnter, Alt: true},
"1b7f": {Type: KeyBackspace, Alt: true},
"1b5b48": {Type: KeyHome},
"1b5b377e": {Type: KeyHome}, // urxvt
"1b5b313b3348": {Type: KeyHome, Alt: true},
"1b1b5b377e": {Type: KeyHome, Alt: true}, // urxvt
"1b5b46": {Type: KeyEnd},
"1b5b387e": {Type: KeyEnd}, // urxvt
"1b5b313b3346": {Type: KeyEnd, Alt: true},
"1b1b5b387e": {Type: KeyEnd, Alt: true}, // urxvt
"1b5b357e": {Type: KeyPgUp},
"1b5b353b337e": {Type: KeyPgUp, Alt: true},
"1b1b5b357e": {Type: KeyPgUp, Alt: true}, // urxvt
"1b5b367e": {Type: KeyPgDown},
"1b5b363b337e": {Type: KeyPgDown, Alt: true},
"1b1b5b367e": {Type: KeyPgDown, Alt: true}, // urxvt
"1b5b313b3341": {Type: KeyUp, Alt: true},
"1b5b313b3342": {Type: KeyDown, Alt: true},
"1b5b313b3343": {Type: KeyRight, Alt: true},
"1b5b313b3344": {Type: KeyLeft, Alt: true},
"1b0d": {Type: KeyEnter, Alt: true},
"1b7f": {Type: KeyBackspace, Alt: true},

// Powershell
"1b4f41": {Type: KeyUp, Alt: false},
Expand All @@ -408,7 +481,7 @@ func readInputs(input io.Reader) ([]Msg, error) {
return nil, err
}

// See if it's a mouse event. For now we're parsing X10-type mouse events
// Check if it's a mouse event. For now we're parsing X10-type mouse events
// only.
mouseEvent, err := parseX10MouseEvents(buf[:numBytes])
if err == nil {
Expand All @@ -419,23 +492,23 @@ func readInputs(input io.Reader) ([]Msg, error) {
return m, nil
}

// Is it a special sequence, like an arrow key?
// Is it a sequence, like an arrow key?
if k, ok := sequences[string(buf[:numBytes])]; ok {
return []Msg{
KeyMsg(k),
}, nil
}

// Some of these need special handling
// Some of these need special handling.
hex := fmt.Sprintf("%x", buf[:numBytes])
if k, ok := hexes[hex]; ok {
return []Msg{
KeyMsg(k),
}, nil
}

// Is the alt key pressed? The buffer will be prefixed with an escape
// sequence if so.
// Is the alt key pressed? If so, the buffer will be prefixed with an
// escape.
if numBytes > 1 && buf[0] == 0x1b {
// Now remove the initial escape sequence and re-process to get the
// character being pressed in combination with alt.
Expand Down Expand Up @@ -483,7 +556,7 @@ func readInputs(input io.Reader) ([]Msg, error) {

// If it's a space, override the type with KeySpace (but still include the
// rune).
if len(runes) == 1 && runes[0] == ' ' {
if runes[0] == ' ' {
return []Msg{
KeyMsg(Key{Type: KeySpace, Runes: runes}),
}, nil
Expand Down