fix(tui): prevent AltGr from swallowing @/#/$/!/%/ characters in composer#2867
Conversation
…oser On Windows, AltGr is delivered as Ctrl+Alt by crossterm. European keyboard layouts (French AZERTY, German QWERTZ, etc.) use AltGr to type characters like @ (AltGr+0), # (AltGr+3), etc. The sidebar-focus shortcuts for Alt+@/Alt+!/Alt+#/Alt+$/Alt+%) were matching on "contains ALT" alone, swallowing these AltGr-typed characters instead of inserting them into the composer. Exclude the Ctrl modifier from these sidebar-focus shortcut guards so AltGr-typed glyphs fall through to the catch-all and are inserted as text. This is consistent with the has_ctrl_or_alt / is_altgr philosophy in key_hint.rs, which already treats Ctrl+Alt as AltGr to preserve European keyboard input. Closes Hmbown#2863
|
Thanks @ousamabenyounes for taking the time to contribute. This repository is currently observing a maintainer-managed contribution gate in dry-run mode, so this pull request is staying open. When enforcement is enabled, pull requests from contributors who are not listed in Please read |
There was a problem hiding this comment.
Code Review
This pull request updates the event loop in crates/tui/src/tui/ui.rs to ensure that sidebar focus shortcuts using the Alt key are not triggered when the Control key is also pressed. This change prevents AltGr-typed characters on European keyboards (which emit Ctrl+Alt on Windows) from being swallowed. The review feedback points out a minor typo in the added explanatory comment, suggesting a correction to list all the sidebar focus shortcuts accurately.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| continue; | ||
| } | ||
| KeyCode::Char('!') if key.modifiers.contains(KeyModifiers::ALT) => { | ||
| // Sidebar focus via Alt+! / Alt+@ / Alt+# / Alt+$ / Alt+%) |
| // Sidebar focus via Alt+! / Alt+@ / Alt+# / Alt+$ / Alt+%) | ||
| // AltGr on European keyboards emits Ctrl+Alt on Windows, so | ||
| // exclude Ctrl to avoid swallowing AltGr-typed characters | ||
| // like @ (AltGr+0 on French AZERTY) and # (AltGr+3). This | ||
| // matches the has_ctrl_or_alt / is_altgr philosophy in | ||
| // key_hint.rs: treat Ctrl+Alt as AltGr, not a shortcut. | ||
| KeyCode::Char('!') if key.modifiers.contains(KeyModifiers::ALT) && !key.modifiers.contains(KeyModifiers::CONTROL) => { |
There was a problem hiding this comment.
The comment says this fix "matches the
has_ctrl_or_alt / is_altgr philosophy in key_hint.rs", but the actual guard uses a raw !key.modifiers.contains(KeyModifiers::CONTROL) check instead of calling is_altgr. is_altgr is #[cfg(windows)]-gated and always returns false on Linux/macOS, so calling it here would preserve the (undocumented) ability for Ctrl+Alt+@ to fire the sidebar shortcut on non-Windows hosts. Using a raw CONTROL check unconditionally suppresses that combo on every platform. That may be the intended tradeoff — the PR description is explicit about trading Ctrl+Alt+char bindings — but then the comment overstates the alignment with is_altgr and may mislead future maintainers.
| // Sidebar focus via Alt+! / Alt+@ / Alt+# / Alt+$ / Alt+%) | |
| // AltGr on European keyboards emits Ctrl+Alt on Windows, so | |
| // exclude Ctrl to avoid swallowing AltGr-typed characters | |
| // like @ (AltGr+0 on French AZERTY) and # (AltGr+3). This | |
| // matches the has_ctrl_or_alt / is_altgr philosophy in | |
| // key_hint.rs: treat Ctrl+Alt as AltGr, not a shortcut. | |
| KeyCode::Char('!') if key.modifiers.contains(KeyModifiers::ALT) && !key.modifiers.contains(KeyModifiers::CONTROL) => { | |
| // Sidebar focus via Alt+! / Alt+@ / Alt+# / Alt+$ / Alt+%) | |
| // AltGr on European keyboards emits Ctrl+Alt on Windows, so | |
| // exclude Ctrl to avoid swallowing AltGr-typed characters | |
| // like @ (AltGr+0 on French AZERTY) and # (AltGr+3). | |
| // Note: this guard is platform-unconditional (unlike | |
| // is_altgr in key_hint.rs), intentionally trading the ability | |
| // to bind Ctrl+Alt+<char> shortcuts on any platform. | |
| KeyCode::Char('!') if key.modifiers.contains(KeyModifiers::ALT) && !key.modifiers.contains(KeyModifiers::CONTROL) => { |
|
Want your agent to iterate on Greptile's feedback? Try greploops. |
Problem
On Windows,
AltGris delivered asCtrl+Altby crossterm. European keyboard layouts use AltGr to type common characters:@=AltGr+0,#=AltGr+3@=AltGr+Q#=AltGr+3The sidebar-focus shortcuts (
Alt+@,Alt+!,Alt+#,Alt+$,Alt+%),Alt+)) were matching onkey.modifiers.contains(KeyModifiers::ALT)alone — so when a European user pressed AltGr to type@, the TUI interpreted it as theAlt+@sidebar shortcut and focused the sidebar instead of inserting the character.Closes #2863.
Fix
Added
&& !key.modifiers.contains(KeyModifiers::CONTROL)to five sidebar-focus shortcut guards (!,@,#,$/%,)). When AltGr producesCtrl+Alt+char, the shortcut no longer fires and the character falls through to theKeyCode::Char(c)catch-all, which inserts it as text.This is consistent with the existing codebase philosophy in
key_hint.rs, wherehas_ctrl_or_alt()andis_altgr()already treatCtrl+Altas AltGr on Windows — trading the rare ability to bindCtrl+Alt+<char>shortcuts for not swallowing European keyboard input.Testing
Alt+@(without Ctrl) still focuses the Tasks sidebar on all platforms.Ctrl+Alt+@(AltGr+0 on French AZERTY) now inserts@as text instead of triggering the sidebar shortcut.Alt+0/Ctrl+Alt+0shortcut is untouched —apply_alt_0_shortcutalready handles Ctrl internally, andAltGr+0on French AZERTY produces@(not0), so it doesn't conflict.Greptile Summary
This PR fixes a bug where European keyboard users on Windows could not type
@,#,$,!, or)in the composer because AltGr (delivered asCtrl+Altby crossterm) was incorrectly matching the sidebar-focus shortcuts. The fix adds&& !key.modifiers.contains(KeyModifiers::CONTROL)to five match guards so thatCtrl+Alt+<char>events fall through to theKeyCode::Char(c)catch-all and are inserted as text.!,@,#,$/%,)): adding!CONTROLto each guard prevents AltGr-produced characters from triggering focus changes on Windows while leaving plainAlt+<char>shortcuts fully intact.KeyCode::Char(c) => { app.insert_char(c); }at the end of the match has no modifier guard, so the rejectedCtrl+Alt+@event is correctly handed off to character insertion.Alt+0andAlt+1/2/3/4already inspect or delegateKeyModifiers::CONTROLinternally and are not touched by this change.Confidence Score: 5/5
Safe to merge — the change is a minimal, targeted guard addition with no regressions on the happy path.
The five changed guards each gain one additional condition that rejects events carrying both ALT and CONTROL. The downstream catch-all has no modifier guard, so rejected events are correctly routed to character insertion. Adjacent shortcuts already handle or delegate CONTROL internally and are untouched. The tradeoff that Ctrl+Alt+char no longer fires sidebar shortcuts on any platform is explicitly documented in both the PR description and the inline comment.
No files require special attention.
Important Files Changed
Flowchart
%%{init: {'theme': 'neutral'}}%% flowchart TD A[Key Event received] --> B{Char is one of ! @ # $ percent close-paren} B -- no --> C[Other key handlers] B -- yes --> D{ALT modifier present?} D -- no --> E[Char catch-all: app.insert_char] D -- yes --> F{CONTROL modifier also present? Ctrl+Alt = AltGr on Windows} F -- yes --> E F -- no --> G[set_sidebar_focus and update status_message]Reviews (2): Last reviewed commit: "style(tui): format AltGr sidebar shortcu..." | Re-trigger Greptile