yoda: protocole clavier kitty, Shift+Entree pour Claude Code et Modjo

Claude Code (>= 2.1.269) n'active Shift+Entree que si le terminal repond
a CSI ? u. alacritty_terminal sait tenir la pile des modes et repondre,
mais seulement avec kitty_keyboard, et n'encode aucune touche : Shift+
Entree partait en \r, identique a Entree.

- term::Config : kitty_keyboard active.
- kitty_keyboard : encodeur du frontal (spec kitty, decoupe de l'encodeur
  d'Alacritty), utilise seulement quand l'application au premier plan a
  pousse le drapeau 1 ou 8 ; sinon l'encodage legacy reste seul. Gere
  desambiguisation, types d'evenements (repetition, relachement),
  touches alternatives, toutes les touches, texte associe, pave
  numerique, F1-F24, touches de modification. Shift+Entree -> CSI 13;2u.
- terminal_box : raccourcis de l'appli toujours prioritaires ;
  Shift+PageUp/PageDown/Home/End et Echap sur selection gardent leur
  comportement local ; relachements envoyes si le drapeau 2 est pose.
- Legacy : Shift+Entree envoie LF (comme Ctrl+J) au lieu de CR. rustyline
  le lit comme Ctrl+J, la touche de saut de ligne de Modjo/Goose ; pour
  un shell c'est toujours Entree.

Connu, non corrige ici : alacritty_terminal 0.25.1 push_keyboard_mode vide
title_stack au lieu de la pile des modes au-dela de 4096 empilements
(panique du fil PTY de l'onglet seulement, panic=unwind).

10 tests, dont la reponse a CSI ? u et l'application de CSI > 5 u.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Lionel DARNIS 2026-10-01 13:32:23 +02:00
parent 2414aaece3
commit 52b37cc2f8
3 changed files with 808 additions and 2 deletions

View file

@ -47,7 +47,7 @@ use std::{
};
use crate::{
Action, Terminal, TerminalScroll,
Action, Terminal, TerminalScroll, kitty_keyboard,
menu::MenuState,
terminal::{BlockFill, Metadata},
};
@ -940,6 +940,7 @@ where
let is_app_cursor = terminal.term.lock().mode().contains(TermMode::APP_CURSOR);
let is_mouse_mode = terminal.term.lock().mode().intersects(TermMode::MOUSE_MODE);
let term_mode = *terminal.term.lock().mode();
match event {
Event::Window(event) => match event {
cosmic::iced::window::Event::Focused => {
@ -979,6 +980,101 @@ where
}
_ => {}
},
// [yoda] Protocole clavier kitty, quand l'application au premier plan
// l'a demandé (voir `kitty_keyboard`) ; sinon encodage legacy plus bas.
Event::Keyboard(KeyEvent::KeyPressed {
key,
modified_key,
physical_key,
location,
modifiers,
text,
repeat,
}) if state.is_focused && kitty_keyboard::active(term_mode) => {
for key_bind in self.key_binds.keys() {
if key_bind.matches(*modifiers, key, Some(physical_key)) {
shell.capture_event();
return;
}
}
// Comportements locaux conservés, comme en legacy : Shift+PageUp…
// fait défiler l'historique, Échap annule une sélection.
let local_scroll = match modified_key {
Key::Named(Named::PageUp) if modifiers.shift() => Some(TerminalScroll::PageUp),
Key::Named(Named::PageDown) if modifiers.shift() => {
Some(TerminalScroll::PageDown)
}
Key::Named(Named::Home) if modifiers.shift() => Some(TerminalScroll::Top),
Key::Named(Named::End) if modifiers.shift() => Some(TerminalScroll::Bottom),
_ => None,
};
if let Some(scroll) = local_scroll {
terminal.scroll(scroll);
shell.capture_event();
return;
}
if *modified_key == Key::Named(Named::Escape) {
let had_selection = terminal.term.lock().selection.take().is_some();
if had_selection {
terminal.update();
shell.capture_event();
return;
}
}
let input = kitty_keyboard::KeyInput {
key,
modified_key,
location: *location,
modifiers: *modifiers,
text: text.as_deref(),
action: if *repeat {
kitty_keyboard::KeyAction::Repeat
} else {
kitty_keyboard::KeyAction::Press
},
};
let bytes = kitty_keyboard::encode(&input, term_mode);
if !bytes.is_empty() {
// Une touche de modification seule (« toutes les touches »)
// ne ramène pas la vue en bas.
let modifier_only = matches!(
modified_key,
Key::Named(Named::Shift | Named::Control | Named::Alt | Named::Super)
);
if modifier_only {
terminal.input_no_scroll(bytes);
} else {
terminal.input_scroll(bytes);
}
shell.capture_event();
}
}
Event::Keyboard(KeyEvent::KeyReleased {
key,
modified_key,
location,
modifiers,
..
}) if state.is_focused
&& kitty_keyboard::active(term_mode)
&& term_mode.contains(TermMode::REPORT_EVENT_TYPES) =>
{
let input = kitty_keyboard::KeyInput {
key,
modified_key,
location: *location,
modifiers: *modifiers,
text: None,
action: kitty_keyboard::KeyAction::Release,
};
let bytes = kitty_keyboard::encode(&input, term_mode);
if !bytes.is_empty() {
terminal.input_no_scroll(bytes);
shell.capture_event();
}
}
Event::Keyboard(KeyEvent::KeyPressed {
key: Key::Named(named),
physical_key,
@ -1094,7 +1190,15 @@ where
shell.capture_event();
}
Named::Enter => {
terminal.input_scroll(format!("{}{}", alt_prefix, "\x0D").into_bytes());
// [yoda] Shift+Entrée envoie LF (`\n`, comme Ctrl+J) : saut de
// ligne dans les éditeurs qui ne parlent pas le protocole kitty
// (rustyline de Modjo/Goose) ; pour un shell, c'est Entrée.
let code = if modifiers.shift() && !modifiers.control() {
"\x0A"
} else {
"\x0D"
};
terminal.input_scroll(format!("{alt_prefix}{code}").into_bytes());
shell.capture_event();
}
Named::Escape => {