Centralize Key Binding Logic and Refactor (Reopen)#5356
Centralize Key Binding Logic and Refactor (Reopen)#5356jacob314 merged 10 commits intogoogle-gemini:mainfrom
Conversation
There was a problem hiding this comment.
Summary of Changes
Hello @Lee-WonJun, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!
This pull request reopens previous work to centralize the application's key binding logic. It introduces a dedicated module for defining key combination matchers and refactors both the main App.tsx and InputPrompt.tsx components to consume these centralized definitions. The primary goal is to enhance code maintainability and consistency, while also preparing the codebase for future user-customizable keybindings.
Highlights
- Centralized Key Binding Logic: Introduced a new
keyBindings.tsfile that centralizes the definitions of various key combinations (e.g., Ctrl+C, Ctrl+D, arrow keys, etc.) into reusable matcher functions. This aims to improve consistency and maintainability across the application's key handling. - Refactoring of App.tsx: The main
App.tsxcomponent has been refactored to remove its direct dependency on Ink'suseInputhook andInkKeyType. Instead, it now utilizes the customuseKeypresshook and the newly centralizedkeyMatchersto handle global key events, streamlining its keypress logic. - Refactoring of InputPrompt.tsx: The
InputPrompt.tsxcomponent has also been updated to leverage the newkeyMatchersfor its extensive key handling logic, replacing numerous inline conditional checks with calls to the centralized matcher functions. This promotes a consistent pattern for key event processing. - Foundation for User-Configurable Key Mappings: This centralization effort lays the groundwork for future features, specifically enabling support for user-configurable key mappings, as noted in the PR description and linked issue #1950.
Using Gemini Code Assist
The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.
Invoking Gemini
You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands.
| Feature | Command | Description |
|---|---|---|
| Code Review | /gemini review |
Performs a code review for the current pull request in its current state. |
| Pull Request Summary | /gemini summary |
Provides a summary of the current pull request in its current state. |
| Comment | @gemini-code-assist | Responds in comments when explicitly tagged, both in issue comments and review comments. |
| Help | /gemini help |
Displays a list of available commands. |
Customization
To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.
Limitations & Feedback
Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments or fill out our survey to provide feedback.
You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.
Footnotes
-
Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution. ↩
There was a problem hiding this comment.
Code Review
This pull request does a great job of centralizing the key binding logic into the new keyBindings.ts file. This significantly improves the readability and maintainability of the key handling logic in both App.tsx and InputPrompt.tsx.
However, there is a critical issue introduced by replacing Ink's useInput hook with the custom useKeypress hook in App.tsx. This change bypasses Ink's focus management system, causing key press handlers in both App.tsx and InputPrompt.tsx to be active at the same time. This leads to conflicting behavior, for instance with Ctrl+C, which now both clears the input and attempts to quit the application. I've left a comment with a suggested fix for this.
|
@Lee-WonJun thanks for resurrecting this! Can you address the comment from the menu box about the overlapping keys? @jacob314 this one looks good to me. I want to double check with you. |
|
Does my understanding of the Gemini review seem correct?
I'm not fully familiar with how priority or exclusivity works between them, but if my assumption is correct, it seems that both hooks were already running concurrently even before. When I test it with the stable version of Gemini or the 33.mp4(Please note that I may be running into an XY problem due to my limited understanding of Ink or frontend behavior.) |
packages/cli/src/ui/keyBindings.ts
Outdated
There was a problem hiding this comment.
This is a great step in the right direction. However lets tweak this so it can be more data driven and less imperative code .
That means create an enum of all the Commands to assign keyboard shortcuts to
and for each one provide a list of matching keys where each key is
{
key | String,
ctrl | boolean,
shift | boolean,
alt | boolean,
command | boolean,
}
I would expect Gemini CLI should be able to one short refactor from this imperative code to that data driven code. Please let me know if you have any questions.
The reason that is important is it will make this code then scale to allowing users to provide their own keybinding mappings.
jacob314
left a comment
There was a problem hiding this comment.
I'll approve this to land once the changes requested are made and there is some tests for the new keyBindings functionality.
There was a problem hiding this comment.
note that text-buffer.ts also has a large number of key bindings that should be moved to use the new system over time.
8dbbb09 to
4fbf92f
Compare
|
@jacob314 thanks for your feedback and suggestions! I've clarified my thought process below based on your comments. Reason for Using Lambda FunctionsThe main reason I've implemented this using lambda functions is that, I'm not aware of the detailed plans for all the shortcuts (or key binding) that Ideal Architecture and Staged ImplementationI'm also a big fan of data-driven DSLs (Domain-Specific Languages), and the ideal architecture I had in mind is as follows: However, this PR follows a staged approach rather than implementing the entire feature at once. In this first stage (Stage 1), I've focused on consolidating the interfaces related to shortcuts. I've excluded Thoughts on DSL DesignUsers will need to be able to override shortcuts via {
"keymap": {
"command": ["ctrl+A+B", "ctrl+A+ALT"]
}
}The system can't interpret this string directly, so it needs to be converted into an internal data structure. For example, to support complex key combinations with AND/OR logic, we could design the DSL with a recursive ADT (Abstract Data Type) structure. This would be very fluent and expressive, but it also makes the DSL structure itself more complex to write. Command: [
{
op: AND,
keys: ['A', 'CTRL', { op: OR, keys: ['B', 'ALT'] }] // more combination
}
]Conversely, if we only allow simple shortcuts, a structure like the one you suggested would also be great: Command: [
{ key: 'A', clt: true },
{ key: 'a', clt: true }
]However, since I don't know the full scope of user configurations (Case-sensitivity, multi input , and so on) that Role of the
|
937b9a2 to
f31ed08
Compare
|
@jacob314 I've made some code changes to support a more data-driven config. I’d love to hear your thoughts on whether this is heading in a positive direction.
|
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request does an excellent job of centralizing the key binding logic into a new data-driven configuration. The refactoring in App.tsx and InputPrompt.tsx to use the new keyMatchers greatly improves code clarity and maintainability, and paves the way for user-configurable keybindings. The addition of comprehensive tests for the new key binding and matching logic is also a great addition.
I've found one issue related to how key bindings for Ctrl+Shift combinations are defined, which appears to be based on a misunderstanding of how readline reports key presses. The fix is straightforward and will make the new configuration more correct and less confusing.
There was a problem hiding this comment.
The key bindings for QUIT and EXIT include entries for uppercase characters (e.g., { key: 'C', ctrl: true }). This seems to be based on a misunderstanding of how key presses are reported. The useKeypress hook, relying on Node.js's readline, typically reports key names in lowercase, using the shift flag to indicate an uppercase character (e.g., Ctrl+Shift+C would be { name: 'c', ctrl: true, shift: true }).
The existing binding { key: 'c', ctrl: true } already correctly handles both Ctrl+C and Ctrl+Shift+C because the shift modifier is not specified in the binding, so it's ignored during matching.
The bindings for uppercase keys are redundant and misleading. They should be removed for clarity and correctness.
[Command.QUIT]: [{ key: 'c', ctrl: true }],
// Original: key.ctrl && (key.name === 'd' || key.name === 'D')
[Command.EXIT]: [{ key: 'd', ctrl: true }],There was a problem hiding this comment.
generally agree with this. suspect previous code we had was confused but it would be good to verify this.
There was a problem hiding this comment.
At least in my environment, it works after removing the uppercase binding. If case sensitivity is needed, we could probably use the sequence instead.
|
Please let me know when you are ready for me to review again. Can't wait to have custom keyboard shortcuts. |
d9bf574 to
e92d210
Compare
|
@jacob314 I’ve resolved the conflicting parts. |
a7d1596 to
e1063d2
Compare
e1063d2 to
4a0abd1
Compare
|
By changing the hook in |
jacob314
left a comment
There was a problem hiding this comment.
Minor comments. Attempting to approve so you can merge to unblock.
There was a problem hiding this comment.
generally agree with this. suspect previous code we had was confused but it would be good to verify this.
There was a problem hiding this comment.
this should be a separate command
COMPLETION_UP
that matches both of these keys
that way users who are annoyed that the up arrow navigates through completion history can change it without breaking other things coupled to NAVIGATION_UP
Co-authored-by: Lee-WonJun <[email protected]>
…on command (#5884) Co-authored-by: Jacob Richman <[email protected]>
…on command (#5884) Co-authored-by: Jacob Richman <[email protected]>
…ate binding and add complete navigation command (google-gemini#5884)
Co-authored-by: Lee-WonJun <[email protected]>
…lete navigation command (google-gemini#5884) Co-authored-by: Jacob Richman <[email protected]>
Co-authored-by: Lee-WonJun <[email protected]>
…lete navigation command (google-gemini#5884) Co-authored-by: Jacob Richman <[email protected]>
Co-authored-by: Lee-WonJun <[email protected]>
…lete navigation command (google-gemini#5884) Co-authored-by: Jacob Richman <[email protected]>
TLDR
This PR reopens the work from the #1969 previously closed PR. For implementation details, please refer to the previous discussion.
App.tsx Now Uses the Key Interface
InputPrompt.ts,App.tsxhas been updated to use theKeyinterface by replacing the hook.App.tsxwas intentional, then maybekeyBindingsneeds to be revised to accommodate it—or perhaps it should leverage the same type + mapper structure used here. But honestly, that approach doesn't feel very clean to me. I think it's better to follow a consistent pattern across the app.Context on Centralization Effort
Reviewer Test Plan
Confirm that all previous key bindings still work as expected.
Testing Matrix
Linked issues / bugs
#1950 #1969
Thanks for the helpful feedback, @mattKorwel