Skip to content

Commit

Permalink
feat(keys): add support for shift/ctrl + arrow keys (#292)
Browse files Browse the repository at this point in the history
* feat(keys): add support for shift/ctrl+arrow keys

* chore(keys): use sequences for shift/ctrl arrow keys

* feat(keys): support ctrl+shift+arrow keys

* chore(keys): use sequences for alt+arrows

* feat(keys): add support for arrow key combinations with alt modifiers

* fix(keys): remove an extreaneous check

* feat(keys): add support for urxvt arrow keys with modfiers

* feat(keys): add support for arrow keys in DECCKM mode

* docs(keys): expand on comment about ctrl+backtick

* chore(keys): migrate various bindings to sequences

* Minor comment improvements to the input parser

Co-authored-by: Bwahharharrr <yitang@tutanota.com>
  • Loading branch information
meowgorithm and Bwahharharrr committed May 10, 2022
1 parent 14e58aa commit 48cdc1e
Showing 1 changed file with 138 additions and 65 deletions.
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

0 comments on commit 48cdc1e

Please sign in to comment.