summaryrefslogtreecommitdiff
path: root/src/tui/light.go
diff options
context:
space:
mode:
authorMassimo Mund <masmu@users.noreply.github.com>2025-09-05 07:56:51 +0200
committerGitHub <noreply@github.com>2025-09-05 14:56:51 +0900
commit9ed971cc90c9d65def3c52178578e43f29b68f9f (patch)
tree3a25368d44d6091c83e21623b313886bdfa1700b /src/tui/light.go
parent129cb230781d0258cd837486e436ba9ad471bc89 (diff)
downloadfzf-9ed971cc90c9d65def3c52178578e43f29b68f9f.tar.gz
Add keybindings for CTRL, ALT, SHIFT + UP, DOWN, RIGHT, LEFT, HOME, END, BACKSPACE, DELETE & more (#3996)
* Added tests for `LightRenderer` * Added common SHIFT, ALT and ALT+SHIFT key sequences * Added common CTRL key sequences * Added common CTRL+ALT, CTRL+SHIFT, CTRL+ALT+SHIFT key sequences * Added proper xterm META modifier handling according to https://github.com/joejulian/xterm/blob/defc6dd5684a12dc8e56cb6973ef973e7a32caa3/input.c#L357-L375 * Fix `ctrl-backspace` and `ctrl-alt-backspace` * Fix broken tcell tests on windows by swallowing Resize events * Added tests for FullscreenRenderer * Removed own fork of tcell and updated tcell to 2.9.0 tcell 2.9.0 is needed for `Ctrl-Alt-*` and `Ctrl-Alt-Shift-*` shortcuts in Windows * Replace conditional checks with switch statements to improve readability * Replace long conditionals with constant slices to improve readability * Bind `ctrl-bspace` (`ctrl-h`) to `backward-delete-char` by default Since we now distinguish between Backspace and Ctrl-Backspace, Ctrl-Backspace should trigger the same action as Backspace by default. In that way nothing changes for the user but you can bind other actions to Ctrl-Backspace when desired.
Diffstat (limited to 'src/tui/light.go')
-rw-r--r--src/tui/light.go282
1 files changed, 255 insertions, 27 deletions
diff --git a/src/tui/light.go b/src/tui/light.go
index 54abbe5c..ec93e67e 100644
--- a/src/tui/light.go
+++ b/src/tui/light.go
@@ -335,6 +335,8 @@ func (r *LightRenderer) GetChar() Event {
return Event{CtrlQ, 0, nil}
case 127:
return Event{Backspace, 0, nil}
+ case 8:
+ return Event{CtrlBackspace, 0, nil}
case 0:
return Event{CtrlSpace, 0, nil}
case 28:
@@ -381,6 +383,9 @@ func (r *LightRenderer) escSequence(sz *int) Event {
}
*sz = 2
+ if r.buffer[1] == 8 {
+ return Event{CtrlAltBackspace, 0, nil}
+ }
if r.buffer[1] >= 1 && r.buffer[1] <= 'z'-'a'+1 {
return CtrlAltKey(rune(r.buffer[1] + 'a' - 1))
}
@@ -473,22 +478,136 @@ func (r *LightRenderer) escSequence(sz *int) Event {
if r.buffer[3] == '~' {
return Event{Delete, 0, nil}
}
+ if len(r.buffer) == 7 && r.buffer[6] == '~' && r.buffer[4] == '1' {
+ switch r.buffer[5] {
+ case '0':
+ return Event{AltShiftDelete, 0, nil}
+ case '1':
+ return Event{AltDelete, 0, nil}
+ case '2':
+ return Event{AltShiftDelete, 0, nil}
+ case '3':
+ return Event{CtrlAltDelete, 0, nil}
+ case '4':
+ return Event{CtrlAltShiftDelete, 0, nil}
+ case '5':
+ return Event{CtrlAltDelete, 0, nil}
+ case '6':
+ return Event{CtrlAltShiftDelete, 0, nil}
+ }
+ }
if len(r.buffer) == 6 && r.buffer[5] == '~' {
*sz = 6
switch r.buffer[4] {
- case '5':
- return Event{CtrlDelete, 0, nil}
case '2':
return Event{ShiftDelete, 0, nil}
+ case '3':
+ return Event{AltDelete, 0, nil}
+ case '4':
+ return Event{AltShiftDelete, 0, nil}
+ case '5':
+ return Event{CtrlDelete, 0, nil}
+ case '6':
+ return Event{CtrlShiftDelete, 0, nil}
+ case '7':
+ return Event{CtrlAltDelete, 0, nil}
+ case '8':
+ return Event{CtrlAltShiftDelete, 0, nil}
+ case '9':
+ return Event{AltDelete, 0, nil}
}
}
return Event{Invalid, 0, nil}
case '4':
return Event{End, 0, nil}
case '5':
- return Event{PageUp, 0, nil}
+ if r.buffer[3] == '~' {
+ return Event{PageUp, 0, nil}
+ }
+ if len(r.buffer) == 7 && r.buffer[6] == '~' && r.buffer[4] == '1' {
+ switch r.buffer[5] {
+ case '0':
+ return Event{AltShiftPageUp, 0, nil}
+ case '1':
+ return Event{AltPageUp, 0, nil}
+ case '2':
+ return Event{AltShiftPageUp, 0, nil}
+ case '3':
+ return Event{CtrlAltPageUp, 0, nil}
+ case '4':
+ return Event{CtrlAltShiftPageUp, 0, nil}
+ case '5':
+ return Event{CtrlAltPageUp, 0, nil}
+ case '6':
+ return Event{CtrlAltShiftPageUp, 0, nil}
+ }
+ }
+ if len(r.buffer) == 6 && r.buffer[5] == '~' {
+ *sz = 6
+ switch r.buffer[4] {
+ case '2':
+ return Event{ShiftPageUp, 0, nil}
+ case '3':
+ return Event{AltPageUp, 0, nil}
+ case '4':
+ return Event{AltShiftPageUp, 0, nil}
+ case '5':
+ return Event{CtrlPageUp, 0, nil}
+ case '6':
+ return Event{CtrlShiftPageUp, 0, nil}
+ case '7':
+ return Event{CtrlAltPageUp, 0, nil}
+ case '8':
+ return Event{CtrlAltShiftPageUp, 0, nil}
+ case '9':
+ return Event{AltPageUp, 0, nil}
+ }
+ }
+ return Event{Invalid, 0, nil}
case '6':
- return Event{PageDown, 0, nil}
+ if r.buffer[3] == '~' {
+ return Event{PageDown, 0, nil}
+ }
+ if len(r.buffer) == 7 && r.buffer[6] == '~' && r.buffer[4] == '1' {
+ switch r.buffer[5] {
+ case '0':
+ return Event{AltShiftPageDown, 0, nil}
+ case '1':
+ return Event{AltPageDown, 0, nil}
+ case '2':
+ return Event{AltShiftPageDown, 0, nil}
+ case '3':
+ return Event{CtrlAltPageDown, 0, nil}
+ case '4':
+ return Event{CtrlAltShiftPageDown, 0, nil}
+ case '5':
+ return Event{CtrlAltPageDown, 0, nil}
+ case '6':
+ return Event{CtrlAltShiftPageDown, 0, nil}
+ }
+ }
+ if len(r.buffer) == 6 && r.buffer[5] == '~' {
+ *sz = 6
+ switch r.buffer[4] {
+ case '2':
+ return Event{ShiftPageDown, 0, nil}
+ case '3':
+ return Event{AltPageDown, 0, nil}
+ case '4':
+ return Event{AltShiftPageDown, 0, nil}
+ case '5':
+ return Event{CtrlPageDown, 0, nil}
+ case '6':
+ return Event{CtrlShiftPageDown, 0, nil}
+ case '7':
+ return Event{CtrlAltPageDown, 0, nil}
+ case '8':
+ return Event{CtrlAltShiftPageDown, 0, nil}
+ case '9':
+ return Event{AltPageDown, 0, nil}
+ }
+ }
+ return Event{Invalid, 0, nil}
case '7':
return Event{Home, 0, nil}
case '8':
@@ -526,63 +645,172 @@ func (r *LightRenderer) escSequence(sz *int) Event {
}
*sz = 6
switch r.buffer[4] {
- case '1', '2', '3', '4', '5':
+ case '1', '2', '3', '4', '5', '6', '7', '8', '9':
// Kitty iTerm2 WezTerm
// SHIFT-ARROW "\e[1;2D"
// ALT-SHIFT-ARROW "\e[1;4D" "\e[1;10D" "\e[1;4D"
// CTRL-SHIFT-ARROW "\e[1;6D" N/A
// CMD-SHIFT-ARROW "\e[1;10D" N/A N/A ("\e[1;2D")
- alt := r.buffer[4] == '3'
+ ctrl := bytes.IndexByte([]byte{'5', '6', '7', '8'}, r.buffer[4]) >= 0
+ alt := bytes.IndexByte([]byte{'3', '4', '7', '8'}, r.buffer[4]) >= 0
+ shift := bytes.IndexByte([]byte{'2', '4', '6', '8'}, r.buffer[4]) >= 0
char := r.buffer[5]
- altShift := false
- if r.buffer[4] == '1' && r.buffer[5] == '0' {
- altShift = true
- if len(r.buffer) < 7 {
- return Event{Invalid, 0, nil}
- }
- *sz = 7
- char = r.buffer[6]
- } else if r.buffer[4] == '4' {
- altShift = true
+ if r.buffer[4] == '9' {
+ ctrl = false
+ alt = true
+ shift = false
if len(r.buffer) < 6 {
return Event{Invalid, 0, nil}
}
*sz = 6
char = r.buffer[5]
+ } else if r.buffer[4] == '1' && bytes.IndexByte([]byte{'0', '1', '2', '3', '4', '5', '6'}, r.buffer[5]) >= 0 {
+ ctrl = bytes.IndexByte([]byte{'3', '4', '5', '6'}, r.buffer[5]) >= 0
+ alt = true
+ shift = bytes.IndexByte([]byte{'0', '2', '4', '6'}, r.buffer[5]) >= 0
+ if len(r.buffer) < 7 {
+ return Event{Invalid, 0, nil}
+ }
+ *sz = 7
+ char = r.buffer[6]
}
+ ctrlShift := ctrl && shift
+ ctrlAlt := ctrl && alt
+ altShift := alt && shift
+ ctrlAltShift := ctrl && alt && shift
switch char {
case 'A':
- if alt {
- return Event{AltUp, 0, nil}
+ if ctrlAltShift {
+ return Event{CtrlAltShiftUp, 0, nil}
+ }
+ if ctrlAlt {
+ return Event{CtrlAltUp, 0, nil}
+ }
+ if ctrlShift {
+ return Event{CtrlShiftUp, 0, nil}
}
if altShift {
return Event{AltShiftUp, 0, nil}
}
- return Event{ShiftUp, 0, nil}
- case 'B':
+ if ctrl {
+ return Event{CtrlUp, 0, nil}
+ }
if alt {
- return Event{AltDown, 0, nil}
+ return Event{AltUp, 0, nil}
+ }
+ if shift {
+ return Event{ShiftUp, 0, nil}
+ }
+ case 'B':
+ if ctrlAltShift {
+ return Event{CtrlAltShiftDown, 0, nil}
+ }
+ if ctrlAlt {
+ return Event{CtrlAltDown, 0, nil}
+ }
+ if ctrlShift {
+ return Event{CtrlShiftDown, 0, nil}
}
if altShift {
return Event{AltShiftDown, 0, nil}
}
- return Event{ShiftDown, 0, nil}
- case 'C':
+ if ctrl {
+ return Event{CtrlDown, 0, nil}
+ }
if alt {
- return Event{AltRight, 0, nil}
+ return Event{AltDown, 0, nil}
+ }
+ if shift {
+ return Event{ShiftDown, 0, nil}
+ }
+ case 'C':
+ if ctrlAltShift {
+ return Event{CtrlAltShiftRight, 0, nil}
+ }
+ if ctrlAlt {
+ return Event{CtrlAltRight, 0, nil}
+ }
+ if ctrlShift {
+ return Event{CtrlShiftRight, 0, nil}
}
if altShift {
return Event{AltShiftRight, 0, nil}
}
- return Event{ShiftRight, 0, nil}
+ if ctrl {
+ return Event{CtrlRight, 0, nil}
+ }
+ if shift {
+ return Event{ShiftRight, 0, nil}
+ }
+ if alt {
+ return Event{AltRight, 0, nil}
+ }
case 'D':
+ if ctrlAltShift {
+ return Event{CtrlAltShiftLeft, 0, nil}
+ }
+ if ctrlAlt {
+ return Event{CtrlAltLeft, 0, nil}
+ }
+ if ctrlShift {
+ return Event{CtrlShiftLeft, 0, nil}
+ }
+ if altShift {
+ return Event{AltShiftLeft, 0, nil}
+ }
+ if ctrl {
+ return Event{CtrlLeft, 0, nil}
+ }
if alt {
return Event{AltLeft, 0, nil}
}
+ if shift {
+ return Event{ShiftLeft, 0, nil}
+ }
+ case 'H':
+ if ctrlAltShift {
+ return Event{CtrlAltShiftHome, 0, nil}
+ }
+ if ctrlAlt {
+ return Event{CtrlAltHome, 0, nil}
+ }
+ if ctrlShift {
+ return Event{CtrlShiftHome, 0, nil}
+ }
if altShift {
- return Event{AltShiftLeft, 0, nil}
+ return Event{AltShiftHome, 0, nil}
+ }
+ if ctrl {
+ return Event{CtrlHome, 0, nil}
+ }
+ if alt {
+ return Event{AltHome, 0, nil}
+ }
+ if shift {
+ return Event{ShiftHome, 0, nil}
+ }
+ case 'F':
+ if ctrlAltShift {
+ return Event{CtrlAltShiftEnd, 0, nil}
+ }
+ if ctrlAlt {
+ return Event{CtrlAltEnd, 0, nil}
+ }
+ if ctrlShift {
+ return Event{CtrlShiftEnd, 0, nil}
+ }
+ if altShift {
+ return Event{AltShiftEnd, 0, nil}
+ }
+ if ctrl {
+ return Event{CtrlEnd, 0, nil}
+ }
+ if alt {
+ return Event{AltEnd, 0, nil}
+ }
+ if shift {
+ return Event{ShiftEnd, 0, nil}
}
- return Event{ShiftLeft, 0, nil}
}
} // r.buffer[4]
} // r.buffer[3]