Add keybindings for CTRL, ALT, SHIFT + UP, DOWN, RIGHT, LEFT, HOME, END, BACKSPACE, DELETE & more#3996
Conversation
|
Thanks, I'll review the code when I get some time. A few things.
|
|
No worries, take your time.
Yep, I checked
True. Do you write the man page by hand? I was hoping it might be generated by some docstring, although I didn't find one when I searched for it.
SGTM. I use
I tried to stick as close to your implementation as possible and saw this |
d7d08bc to
aad5e49
Compare
|
I made the mentioned changes and the remaining exciting question is if the following block ( case tcell.KeyBackspace2:
if ctrlAlt {
return Event{CtrlAltBackspace, 0, nil}
}
if ctrl {
return Event{CtrlBackspace, 0, nil}
}
if alt {
return Event{AltBackspace, 0, nil}
}
return Event{Backspace, 0, nil}My educated guess is that it is actually not and has to be covered in the following block: case tcell.KeyCtrlH:
switch ev.Rune() {
case 0:
if ctrl {
return Event{Backspace, 0, nil}
}
case rune(tcell.KeyCtrlH):
switch {
case ctrl:
return keyfn('h')
case alt:
return Event{AltBackspace, 0, nil}
case none, shift:
return Event{Backspace, 0, nil}
}
} |
You can run |
|
Thanks for the hint! But I am trying to get my hands on a windows machine as well because it all depends on what the OS is really gonna send. I will report back after that. |
aad5e49 to
d90cff2
Compare
d90cff2 to
e948d0f
Compare
|
is this ready for merging? |
|
Nope, not yet. The Linux implementation is done. Windows implementation potentially as well, but requires a patch in tcell to make the following shortcuts work:
That's why this PR currently points to my own fork of You can build this PR yourself. That should work. |
|
Does this need to wait for full tcell support of all modifiers in windows, or could it just be annotated (like kitty, iterm2 are) that it's not supported yet? (context: I was hoping to be able to use shift-pageup/down for the preview panel). |
|
Would you be fine splitting off the part depending on |
|
I would not mind not adding windows support at all. But that probably would demolish the chances of getting this merged, right @junegunn? I am gonna try to get things rolling again in tcell/pull/749. |
|
It's okay as long as we document the limitation. I assume you're a Linux user, so you might not be able to answer this, but I'm wondering how I can test this on macOS. |
Yes.
Do you mean how to try out the keybindings? Build the PR, add some bindings a la and see if they work. Note that macOS might intercept certain keystrokes (like Windows does for |
tcell 2.9.0 is needed for `Ctrl-Alt-*` and `Ctrl-Alt-Shift-*` shortcuts in Windows
|
Good news. tcell 2.9.0 fixed the windows issue with the broken shortcuts for Since we are now also having full windows support, I added the remaining tests and updated tcell to 2.9.0. This can be tested via the following "one-liner": ./target/fzf-linux_amd64 \
--bind 'up:change-prompt(up)' \
--bind 'down:change-prompt(down)' \
--bind 'right:change-prompt(right)' \
--bind 'left:change-prompt(left)' \
--bind 'home:change-prompt(home)' \
--bind 'end:change-prompt(end)' \
--bind 'delete:change-prompt(delete)' \
--bind 'page-up:change-prompt(pageup)' \
--bind 'page-down:change-prompt(pagedown)' \
\
--bind 'ctrl-up:change-prompt(ctrl-up)' \
--bind 'ctrl-down:change-prompt(ctrl-down)' \
--bind 'ctrl-right:change-prompt(ctrl-right)' \
--bind 'ctrl-left:change-prompt(ctrl-left)' \
--bind 'ctrl-home:change-prompt(ctrl-home)' \
--bind 'ctrl-end:change-prompt(ctrl-end)' \
--bind 'ctrl-delete:change-prompt(ctrl-delete)' \
--bind 'ctrl-page-up:change-prompt(ctrl-pageup)' \
--bind 'ctrl-page-down:change-prompt(ctrl-pagedown)' \
\
--bind 'shift-up:change-prompt(shift-up)' \
--bind 'shift-down:change-prompt(shift-down)' \
--bind 'shift-right:change-prompt(shift-right)' \
--bind 'shift-left:change-prompt(shift-left)' \
--bind 'shift-home:change-prompt(shift-home)' \
--bind 'shift-end:change-prompt(shift-end)' \
--bind 'shift-delete:change-prompt(shift-delete)' \
--bind 'shift-page-up:change-prompt(shift-pageup)' \
--bind 'shift-page-down:change-prompt(shift-pagedown)' \
\
--bind 'alt-up:change-prompt(alt-up)' \
--bind 'alt-down:change-prompt(alt-down)' \
--bind 'alt-right:change-prompt(alt-right)' \
--bind 'alt-left:change-prompt(alt-left)' \
--bind 'alt-home:change-prompt(alt-home)' \
--bind 'alt-end:change-prompt(alt-end)' \
--bind 'alt-delete:change-prompt(alt-delete)' \
--bind 'alt-page-up:change-prompt(alt-pageup)' \
--bind 'alt-page-down:change-prompt(alt-pagedown)' \
\
--bind 'ctrl-shift-up:change-prompt(ctrl-shift-up)' \
--bind 'ctrl-shift-down:change-prompt(ctrl-shift-down)' \
--bind 'ctrl-shift-right:change-prompt(ctrl-shift-right)' \
--bind 'ctrl-shift-left:change-prompt(ctrl-shift-left)' \
--bind 'ctrl-shift-home:change-prompt(ctrl-shift-home)' \
--bind 'ctrl-shift-end:change-prompt(ctrl-shift-end)' \
--bind 'ctrl-shift-delete:change-prompt(ctrl-shift-delete)' \
--bind 'ctrl-shift-page-up:change-prompt(ctrl-shift-pageup)' \
--bind 'ctrl-shift-page-down:change-prompt(ctrl-shift-pagedown)' \
\
--bind 'ctrl-alt-up:change-prompt(ctrl-alt-up)' \
--bind 'ctrl-alt-down:change-prompt(ctrl-alt-down)' \
--bind 'ctrl-alt-right:change-prompt(ctrl-alt-right)' \
--bind 'ctrl-alt-left:change-prompt(ctrl-alt-left)' \
--bind 'ctrl-alt-home:change-prompt(ctrl-alt-home)' \
--bind 'ctrl-alt-end:change-prompt(ctrl-alt-end)' \
--bind 'ctrl-alt-delete:change-prompt(ctrl-alt-delete)' \
--bind 'ctrl-alt-page-up:change-prompt(ctrl-alt-pageup)' \
--bind 'ctrl-alt-page-down:change-prompt(ctrl-alt-pagedown)' \
\
--bind 'shift-alt-up:change-prompt(shift-alt-up)' \
--bind 'shift-alt-down:change-prompt(shift-alt-down)' \
--bind 'shift-alt-right:change-prompt(shift-alt-right)' \
--bind 'shift-alt-left:change-prompt(shift-alt-left)' \
--bind 'shift-alt-home:change-prompt(shift-alt-home)' \
--bind 'shift-alt-end:change-prompt(shift-alt-end)' \
--bind 'shift-alt-delete:change-prompt(shift-alt-delete)' \
--bind 'shift-alt-page-up:change-prompt(shift-alt-pageup)' \
--bind 'shift-alt-page-down:change-prompt(shift-alt-pagedown)' \
\
--bind 'ctrl-alt-shift-up:change-prompt(ctrl-alt-shift-up)' \
--bind 'ctrl-alt-shift-down:change-prompt(ctrl-alt-shift-down)' \
--bind 'ctrl-alt-shift-right:change-prompt(ctrl-alt-shift-right)' \
--bind 'ctrl-alt-shift-left:change-prompt(ctrl-alt-shift-left)' \
--bind 'ctrl-alt-shift-home:change-prompt(ctrl-alt-shift-home)' \
--bind 'ctrl-alt-shift-end:change-prompt(ctrl-alt-shift-end)' \
--bind 'ctrl-alt-shift-delete:change-prompt(ctrl-alt-shift-delete)' \
--bind 'ctrl-alt-shift-page-up:change-prompt(ctrl-alt-shift-pageup)' \
--bind 'ctrl-alt-shift-page-down:change-prompt(ctrl-alt-shift-pagedown)' \
\
--bind 'backspace:change-prompt(backspace)' \
--bind 'ctrl-backspace:change-prompt(ctrl-backspace)' \
--bind 'alt-backspace:change-prompt(alt-backspace)' \
--bind 'ctrl-alt-backspace:change-prompt(ctrl-alt-backspace)' \
@junegunn |
|
Passed the tests! I'll review the code when I have some time. Thanks! |
|
Thanks, I tested your patch using One thing I noticed after enabling For example, pressing CMD-a leaves
Do we want to support this super key as well? (This was actually my first time hearing about it.) Maybe another time? |
Thank you.
Sounds like kittys own enhanced keyboard protocol.
No, I'm afraid not. That would go far beyond the scope of this PR. xterm is the de facto standard in terminal emulation. We now have everything what is possible with pure xterm to ensure that it works everywhere. So there should be no exceptions. As long as something implements xterm, it just works. And they all implement xterm first, other stuff maybe if at all. Furthermore, the Super key in Linux (and the Windows key in Windows) is assigned to many GUI actions by default. This makes it a poor modifier, as the operating system would often intercept the key combination anyway before it could reach the terminal window. Not sure how the situation is like on Macs in this regard. |
Agreed. Thanks for the detailed explanation.
On a related note, when I enable |
Have you tried it without clear_all_shortcuts? My expectation is that most of them work and some don't because Kitty has bound internal actions to them. Kitty has a set of default keyboard shortcuts to perform certain actions. For example, |
There was a problem hiding this comment.
Pull Request Overview
This PR implements comprehensive key binding support for all possible combinations of modifier keys (CTRL, ALT, SHIFT) with directional keys (Up/Down/Left/Right), navigation keys (Home/End), editing keys (Backspace/Delete), and page navigation keys (PageUp/PageDown).
- Adds over 50 new key event constants for modifier key combinations
- Implements detection and handling logic for these key combinations in both tcell and light renderers
- Expands test coverage with comprehensive test cases for the new key bindings
Reviewed Changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| src/tui/tui.go | Adds new EventType constants for all modifier key combinations |
| src/tui/tcell.go | Implements key detection logic for tcell renderer |
| src/tui/light.go | Implements escape sequence parsing for light renderer |
| src/tui/tcell_test.go | Adds comprehensive test cases for tcell key detection |
| src/tui/light_test.go | Adds new test file with extensive light renderer key binding tests |
| src/tui/eventtype_string.go | Updates auto-generated string representation for new event types |
| src/options.go | Adds string-to-event mapping for new key combinations |
| src/terminal.go | Maps ctrl-backspace to backward delete char action |
| man/man1/fzf.1 | Documents all new key binding options |
| go.mod | Updates tcell dependency version |
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
|
Oops, tcell 2.9 requires Go 1.23, but fzf is still using 1.20 to support Windows 7. We'll eventully drop support for Windows 7 and move on.
It's been 5 years, so this might be the good time for that. But let me first release a patch version for the pending bug fixes with Go 1.20 for the Windows 7 users, then we'll move on. |
I would just like to briefly note that, theoretically, you can stay on tcell 2.8.x, which means you “only” lose the key combinations
Cool. I am looking forward to 0.66.0 😃 Is there anything left for me to do? |
|
Everything's fine. I just want to wait a bit to see if the community has any feedback on that release. |
|
Merged, thanks for the great work! |
|
Thank you very much. And thank you for fzf and your ongoing commitment. |
This MR contains the following updates: | Package | Update | Change | |---|---|---| | [junegunn/fzf](https://github.com/junegunn/fzf) | minor | `v0.65.2` -> `v0.66.0` | MR created with the help of [el-capitano/tools/renovate-bot](https://gitlab.com/el-capitano/tools/renovate-bot). **Proposed changes to behavior should be submitted there as MRs.** --- ### Release Notes <details> <summary>junegunn/fzf (junegunn/fzf)</summary> ### [`v0.66.0`](https://github.com/junegunn/fzf/releases/tag/v0.66.0): 0.66.0 [Compare Source](junegunn/fzf@v0.65.2...v0.66.0) ##### Quick summary This version introduces many new features centered around the new "raw" mode. | Type | Class | Name | Description | | :---------- | :--------- | :------------------ | :------------------------------------------------- | | New | Option | `--raw` | Enable raw mode by default | | New | Option | `--gutter CHAR` | Set the gutter column character | | New | Option | `--gutter-raw CHAR` | Set the gutter column character in raw mode | | Enhancement | Option | `--listen SOCKET` | Added support for Unix domain sockets | | New | Action | `toggle-raw` | Toggle raw mode | | New | Action | `enable-raw` | Enable raw mode | | New | Action | `disable-raw` | Disable raw mode | | New | Action | `up-match` | Move up to the matching item | | New | Action | `down-match` | Move down to the matching item | | New | Action | `best` | Move to the matching item with the best score | | New | Color Name | `nomatch` | Color for non-matching items in raw mode | | New | Color Attr | `strip` | Remove original colors | | New | Env Var | `FZF_RAW` | Matching status in raw mode (0, 1, or undefined) | | New | Env Var | `FZF_DIRECTION` | `up` or `down` depending on the layout | | New | Env Var | `FZF_SOCK` | Path to the Unix domain socket fzf is listening on | | Enhancement | Key | `CTRL-N` | `down` -> `down-match` | | Enhancement | Key | `CTRL-P` | `up` -> `up-match` | | Enhancement | Shell | `CTRL-R` binding | Toggle raw mode with `ALT-R` | | Enhancement | Shell | `CTRL-R` binding | Opt-out with an empty `FZF_CTRL_R_COMMAND` | ##### 1. Introducing "raw" mode  This version introduces a new "raw" mode (named so because it shows the list "unfiltered"). In raw mode, non-matching items stay in their original positions, but appear dimmed. This allows you see surrounding items of a match and better understand the context of it. You can enable raw mode by default with `--raw`, but it's often more useful when toggled dynamically with the `toggle-raw` action. ```sh tree | fzf --reverse --bind alt-r:toggle-raw ``` While non-matching items are displayed in a dimmed color, they are treated just like matching items, so you can place the cursor on them and perform any action. If you prefer to navigate only through matching items, use the `down-match` and `up-match` actions, which are from now on bound to `CTRL-N` and `CTRL-P` respectively, and also to `ALT-DOWN` and `ALT-UP`. | Key | Action | With `--history` | | :--------- | :----------- | :--------------- | | `down` | `down` | | | `up` | `up` | | | `ctrl-j` | `down` | | | `ctrl-k` | `up` | | | `ctrl-n` | `down-match` | `next-history` | | `ctrl-p` | `up-match` | `prev-history` | | `alt-down` | `down-match` | | | `alt-up` | `up-match` | | > \[!NOTE] > `CTRL-N` and `CTRL-P` are bound to `next-history` and `prev-history` when `--history` option is enabled, so in that case, you'll need to manually bind them, or use `ALT-DOWN` and `ALT-UP` instead. > \[!TIP] > `up-match` and `down-match` are equivalent to `up` and `down` when not in raw mode, so you can safely bind them to `up` and `arrow` keys if you prefer. > > ```sh > fzf --bind up:up-match,down:down-match > ``` ##### Customizing the behavior In raw mode, the input list is presented in its original order, unfiltered, and your cursor will not move to the matching item automatically. Here are ways to customize the behavior. ```sh ### When the result list is updated, move the cursor to the item with the best score ### (assuming sorting is not disabled) fzf --raw --bind result:best ### Move to the first matching item in the original list ### - $FZF_RAW is set to 0 when raw mode is enabled and the current item is a non-match ### - $FZF_DIRECTION is set to either 'up' or 'down' depending on the layout direction fzf --raw --bind 'result:first+transform:[[ $FZF_RAW = 0 ]] && echo $FZF_DIRECTION-match' ``` ##### Customizing the look ##### Gutter To make the mode visually distinct, the gutter column is rendered in a dashed line using `▖` character. But you can customize it with the `--gutter-raw CHAR` option. ```sh ### Use a thinner gutter instead of the default dashed line fzf --bind alt-r:toggle-raw --gutter-raw ▎ ``` ##### Color and style of non-matching items Non-matching items are displayed in a dimmed color by default, but you can change it with the `--color nomatch:...` option. ```sh fzf --raw --color nomatch:red fzf --raw --color nomatch:red:dim fzf --raw --color nomatch:red:dim:strikethrough fzf --raw --color nomatch:red:dim:strikethrough:italic ``` For colored input, dimming alone may not be enough, and you may prefer to remove colors entirely. For that case, a new special style attribute `strip` has been added. ```sh fd --color always | fzf --ansi --raw --color nomatch:dim:strip:strikethrough ``` ##### Conditional actions for raw mode You may want to perform different actions depending on whether the current item is a match or not. For that, fzf now exports `$FZF_RAW` environment variable. It's: - Undefined if raw mode is disabled - `1` if the current item is a match - `0` otherwise ```sh ### Do not allow selecting non-matching items fzf --raw --bind 'enter:transform:[[ ${FZF_RAW-1} = 1 ]] && echo accept || echo bell' ``` ##### Leveraging raw mode in shell integration The `CTRL-R` binding (command history) now lets you toggle raw mode with `ALT-R`. ##### 2. Style changes The screenshot on the right shows the updated gutter style:  This version includes a few minor updates to fzf's classic visual style: - The gutter column is now narrower, rendered with the left-half block character (`▌`). - Markers no longer use background colors. - The `--color base16` theme (alias: `16`) has been updated for better compatibility with both dark and light themes. ##### 3. `--listen` now supports Unix domain sockets If an argument to `--listen` ends with `.sock`, fzf will listen on a Unix domain socket at the specified path. ```sh fzf --listen /tmp/fzf.sock --no-tmux ### GET curl --unix-socket /tmp/fzf.sock http ### POST curl --unix-socket /tmp/fzf.sock http -d up ``` Note that any existing file at the given path will be removed before creating the socket, so avoid using an important file path. ##### 4. Added options ##### `--gutter CHAR` The gutter column can now be customized using `--gutter CHAR` and styled with `--color gutter:...`. Examples: ```sh ### Right-aligned gutter fzf --gutter '▐' ### Even thinner gutter fzf --gutter '▎' ### Yellow checker pattern fzf --gutter '▚' --color gutter:yellow ### Classic style fzf --gutter ' ' --color gutter:reverse ``` ##### `--gutter-raw CHAR` As noted above, the `--gutter-raw CHAR` option was also added for customizing the gutter column in raw mode. ##### 5. Added actions The following actions were introduced to support working with raw mode: | Action | Description | | :------------ | :------------------------------------------------------------------------------------------ | | `toggle-raw` | Toggle raw mode | | `enable-raw` | Enable raw mode | | `disable-raw` | Disable raw mode | | `up-match` | Move up to the matching item; identical to `up` if raw mode is disabled | | `down-match` | Move down to the matching item; identical to `down` if raw mode is disabled | | `best` | Move to the matching item with the best score; identical to `first` if raw mode is disabled | ##### 6. Added environment variables ##### `$FZF_DIRECTION` `$FZF_DIRECTION` is now exported to child processes, indicating the list direction of the current layout: - `up` for the default layout - `down` for `reverse` or `reverse-list` This simplifies writing transform actions involving layout-dependent actions like `{up,down}-match`, `{up,down}-selected`, and `toggle+{up,down}`. ```sh fzf --raw --bind 'result:first+transform:[[ $FZF_RAW = 0 ]] && echo $FZF_DIRECTION-match' ``` ##### `$FZF_SOCK` When fzf is listening on a Unix domain socket using `--listen`, the path to the socket is exported as `$FZF_SOCK`, analogous to `$FZF_PORT` for TCP sockets. ##### `$FZF_RAW` As described above, `$FZF_RAW` is now exported to child processes in raw mode, indicating whether the current item is a match (`1`) or not (`0`). It is not defined when not in raw mode. ##### `$FZF_CTRL_R_COMMAND` You can opt-out `CTRL-R` binding from the shell integration by setting `FZF_CTRL_R_COMMAND` to an empty string. Setting it to any other value is not supported and will result in a warning. ```sh ### Disable the CTRL-R binding from the shell integration FZF_CTRL_R_COMMAND= eval "$(fzf --bash)" ``` ##### 7. Added key support for `--bind` Pull request [#​3996](junegunn/fzf#3996) added support for many additional keys for `--bind` option, such as `ctrl-backspace`. ##### 8. Breaking changes ##### Hiding the gutter column In the previous versions, the recommended way to hide the gutter column was to set `--color gutter:-1`. That's because the gutter column was just a space character, reversed. But now that it's using a visible character (`▌`), applying the default color is no longer enough to hide it. Instead, you can set it to a space character. ```sh ### Hide the gutter column fzf --gutter ' ' ### Classic style fzf --gutter ' ' --color gutter:reverse ``` ##### `--color` option In the previous versions, some elements had default style attributes applied and you would have to explicitly unset them with `regular` attribute if you wanted to reset them. This is no longer needed now, as the default style attributes are applied only when you do not specify any color or style for that element. ```sh ### No 'dim', just red and italic. fzf --ghost 'Type to search' --color ghost:red:italic ``` ##### Compatibility changes Starting with this release, fzf is built with Go 1.23. Support for some old OS versions has been dropped. See <https://go.dev/wiki/MinimumRequirements>. </details> --- ### Configuration 📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever MR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this MR and you won't be reminded about this update again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this MR, check this box --- This MR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MS4xNDQuNCIsInVwZGF0ZWRJblZlciI6IjQxLjE0Ni4wIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJSZW5vdmF0ZSBCb3QiXX0=-->

This PR implements some missing key bindings.
In particular all possible combinations of
CTRLALTSHIFTALT+SHIFTCTRL+ALTCTRL+SHIFTCTRL+ALT+SHIFTin conjunction with
Up,Down,Left,Right,Home,End,Backspace,Delete,PageUp,PageDown.I added tests to the
LightRendererbeforehand to make sure I wasn't breaking anything, and expanded them when I added new key bindings.So you can actually do stuff like:
This PR fixes #1747.