Repository: alacritty/alacritty (language: rust)

Issue:
Alt + 4 wrongly sends escape sequence inside Alacritty+Neovim (Swedish/Nordic kb layout!)
OS: macOS Ventura 13.6.1
Version: Current commit: https://github.com/alacritty/alacritty/tree/a10fb8adc0857633be428d673ab8d36d2a90193a (but same issues since kitty key protocol)
Keyboard layout: Swedish/Nordic

option_as_alt = "None"

Key events:

Same output for command line inside Alacritty, Inside Neovim, Inside Neovim inside Tmux:
```
[122.479315289s] [INFO ] [alacritty] winit event: WindowEvent { window_id: WindowId(WindowId(140634396691760)), event: KeyboardInput { device_id: DeviceId(DeviceId), event: KeyEvent { physical_key: Code(AltLeft), logical_key: Named(Alt), text: None, location: Left, state: Pressed, repeat: false, platform_specific: KeyEventExtra { text_with_all_modifiers: None, key_without_modifiers: Named(Alt) } }, is_synthetic: false } }
[122.483869688s] [INFO ] [alacritty] winit event: WindowEvent { window_id: WindowId(WindowId(140634396691760)), event: KeyboardInput { device_id: DeviceId(DeviceId), event: KeyEvent { physical_key: Code(Digit4), logical_key: Character("$"), text: Some("$"), location: Standard, state: Pressed, repeat: false, platform_specific: KeyEventExtra { text_with_all_modifiers: Some("$"), key_without_modifiers: Character("4") } }, is_synthetic: false } }
[122.550189254s] [INFO ] [alacritty] winit event: WindowEvent { window_id: WindowId(WindowId(140634396691760)), event: KeyboardInput { device_id: DeviceId(DeviceId), event: KeyEvent { physical_key: Code(Digit4), logical_key: Character("$"), text: None, location: Standard, state: Released, repeat: false, platform_specific: KeyEventExtra { text_with_all_modifiers: Some("$"), key_without_modifiers: Character("4") } }, is_synthetic: false } }
[122.554484575s] [INFO ] [alacritty] winit event: WindowEvent { window_id: WindowId(WindowId(140634396691760)), event: KeyboardInput { device_id: DeviceId(DeviceId), event: KeyEvent { physical_key: Code(AltLeft), logical_key: Named(Alt), text: None, location: Left, state: Released, repeat: false, platform_specific: KeyEventExtra { text_with_all_modifiers: None, key_without_modifiers: Named(Alt) } }, is_synthetic: false } }
```

_Alt + 4_ inside Alacritty command line outputs **$**

_Alt + 4_ inside Alacritty+Neovim from Insert mode it switches to Normal mode and then sends the keycode which result in my caret ending up at the end of the line instead of inserting a **$**

_Alt + 4_ inside Alacritty+Tmux+Neovim correctly inserts **$** inside Neovim


I had a similar issue when Alacritty switched from "alt_send_esc" to "option_as_alt", "option_as_alt" = "None" has been working correctly ever since the following issue was filed:
https://github.com/alacritty/alacritty/issues/6637#issuecomment-1420837553

Back then i had to comment out a section that appended an escape sequence/bytes to make it work, after that issue a fix was implemented and it has worked natively ever since then.


But now something has changed (i think it changed after switching to the kitty keyboard protocol?), it is as if the option "None" doesn't work anymore, it seems to have no effect, but i have not tested it out properly, instead; this time i simply commented out code again to get it to work as i expect it to:

https://github.com/alacritty/alacritty/blob/master/alacritty_terminal/src/term/mod.rs#L94-L95

After commenting out/removing:

```
let disambiguate_esc_codes = value.contains(KeyboardModes::DISAMBIGUATE_ESC_CODES);
mode.set(TermMode::DISAMBIGUATE_ESC_CODES, disambiguate_esc_codes);
```

Once again Alacritty works as expected, always inserting a **$** no matter if i am just using Alacritty cmdline, Alacritty + Neovim, Alacritty + Tmux + Neovim

I have only used it for 1 hour so far but so far there has been no negative issues for me without those lines, i am sure there is some unforseen issues i will run into in the future but for now, Alacritty works as expected

So, in latest commit, it seems like **option_as_alt = "None"** isn't respected, or something else is going on?

And it is weird that it works in Alacritty+Tmux+Neovim but not Alacritty+Neovim, i don't know how Tmux is handling events, is it stripping out the escape sequence?

Anyway, with my "fix" i can use Alacritty properly again but it not a nice solution


Discussion / hints:
Could you check kitty as well? For me it works the same way as you describe alacritty works, so I'd assume you won't be able to use it as well?

It all looks like neovim bug, if it matters, but I guess the thing is that your `Alt` is not really an `Alt`, but rather compose in such case?

Fix patch:
diff --git a/alacritty/src/input/keyboard.rs b/alacritty/src/input/keyboard.rs
index 9db67f4255c..160600a1b9d 100644
--- a/alacritty/src/input/keyboard.rs
+++ b/alacritty/src/input/keyboard.rs
@@ -78,13 +78,16 @@ impl<T: EventListener, A: ActionContext<T>> Processor<T, A> {
             return;
         }
 
+        // Mask `Alt` modifier from input when we won't send esc.
+        let mods = if self.alt_send_esc(&key, text) { mods } else { mods & !ModifiersState::ALT };
+
         let build_key_sequence = Self::should_build_sequence(&key, text, mode, mods);
 
         let bytes = if build_key_sequence {
             build_sequence(key, mods, mode)
         } else {
             let mut bytes = Vec::with_capacity(text.len() + 1);
-            if self.alt_send_esc() && text.len() == 1 {
+            if mods.alt_key() {
                 bytes.push(b'\x1b');
             }
 
@@ -99,6 +102,34 @@ impl<T: EventListener, A: ActionContext<T>> Processor<T, A> {
         }
     }
 
+    fn alt_send_esc(&mut self, key: &KeyEvent, text: &str) -> bool {
+        #[cfg(not(target_os = "macos"))]
+        let alt_send_esc = self.ctx.modifiers().state().alt_key();
+
+        #[cfg(target_os = "macos")]
+        let alt_send_esc = {
+            let option_as_alt = self.ctx.config().window.option_as_alt();
+            self.ctx.modifiers().state().alt_key()
+                && (option_as_alt == OptionAsAlt::Both
+                    || (option_as_alt == OptionAsAlt::OnlyLeft
+                        && self.ctx.modifiers().lalt_state() == ModifiersKeyState::Pressed)
+                    || (option_as_alt == OptionAsAlt::OnlyRight
+                        && self.ctx.modifiers().ralt_state() == ModifiersKeyState::Pressed))
+        };
+
+        match key.logical_key {
+            Key::Named(named) => {
+                if named.to_text().is_some() {
+                    alt_send_esc
+                } else {
+                    // Treat `Alt` as modifier for named keys without text, like ArrowUp.
+                    self.ctx.modifiers().state().alt_key()
+                }
+            },
+            _ => text.len() == 1 && alt_send_esc,
+        }
+    }
+
     /// Check whether we should try to build escape sequence for the [`KeyEvent`].
     fn should_build_sequence(
         key: &KeyEvent,
@@ -123,23 +154,6 @@ impl<T: EventListener, A: ActionContext<T>> Processor<T, A> {
         }
     }
 
-    /// Whether we should send `ESC` due to `Alt` being pressed.
-    #[cfg(not(target_os = "macos"))]
-    fn alt_send_esc(&mut self) -> bool {
-        self.ctx.modifiers().state().alt_key()
-    }
-
-    #[cfg(target_os = "macos")]
-    fn alt_send_esc(&mut self) -> bool {
-        let option_as_alt = self.ctx.config().window.option_as_alt();
-        self.ctx.modifiers().state().alt_key()
-            && (option_as_alt == OptionAsAlt::Both
-                || (option_as_alt == OptionAsAlt::OnlyLeft
-                    && self.ctx.modifiers().lalt_state() == ModifiersKeyState::Pressed)
-                || (option_as_alt == OptionAsAlt::OnlyRight
-                    && self.ctx.modifiers().ralt_state() == ModifiersKeyState::Pressed))
-    }
-
     /// Attempt to find a binding and execute its action.
     ///
     /// The provided mode, mods, and key must match what is allowed by a binding
@@ -190,6 +204,10 @@ impl<T: EventListener, A: ActionContext<T>> Processor<T, A> {
             return;
         }
 
+        // Mask `Alt` modifier from input when we won't send esc.
+        let text = key.text_with_all_modifiers().unwrap_or_default();
+        let mods = if self.alt_send_esc(&key, text) { mods } else { mods & !ModifiersState::ALT };
+
         let bytes: Cow<'static, [u8]> = match key.logical_key.as_ref() {
             // NOTE: Echo the key back on release to follow kitty/foot behavior. When
             // KEYBOARD_REPORT_ALL_KEYS_AS_ESC is used, we build proper escapes for


Test patch: