T44: round 1 fixes: under kitty's flags the legacy bytes a key falls back to keep gpui's character, so Option-Shift-[ with only events set sends ESC { again; a key that is down is known by its key code on macOS and its virtual key on Windows, so its release goes out, as a record where its press did, though Shift came up first; shot posts a press or a release alone with down: and up:
This commit is contained in:
8 files changed
+90
-47
No files matched your search
+40
-14
@@ -92,37 +92,43 @@ impl TerminalView {
|
||||
if event.prefer_character_input {
|
||||
return;
|
||||
}
|
||||
self.held.insert(event.keystroke.key.clone());
|
||||
let facts = self.facts(&event.keystroke);
|
||||
let action = if event.is_held || facts.repeat {
|
||||
Action::Repeat
|
||||
} else {
|
||||
Action::Press
|
||||
};
|
||||
if self.send(&event.keystroke, action, facts, cx) {
|
||||
let went = self.send(&event.keystroke, action, facts, false, cx);
|
||||
self.held
|
||||
.insert(held(&event.keystroke, facts), went == Some(true));
|
||||
if went.is_some() {
|
||||
cx.stop_propagation();
|
||||
}
|
||||
}
|
||||
|
||||
/// A release goes out only for a press this terminal saw: none for one a binding took.
|
||||
pub(super) fn key_up(&mut self, event: &KeyUpEvent, _: &mut Window, cx: &mut Context<Self>) {
|
||||
if !self.held.remove(&event.keystroke.key) {
|
||||
return;
|
||||
}
|
||||
let facts = self.facts(&event.keystroke);
|
||||
if self.send(&event.keystroke, Action::Release, facts, cx) {
|
||||
let Some(record) = self.held.remove(&held(&event.keystroke, facts)) else {
|
||||
return;
|
||||
};
|
||||
if self
|
||||
.send(&event.keystroke, Action::Release, facts, record, cx)
|
||||
.is_some()
|
||||
{
|
||||
cx.stop_propagation();
|
||||
}
|
||||
}
|
||||
|
||||
/// Sends what `keystroke` means to the program; false when it is text the input method types.
|
||||
/// Sends what `keystroke` means to the program: none when it is text the input method types, else whether it went as Windows' record; `record` says that of its press.
|
||||
fn send(
|
||||
&mut self,
|
||||
keystroke: &Keystroke,
|
||||
action: Action,
|
||||
facts: Facts,
|
||||
record: bool,
|
||||
cx: &mut Context<Self>,
|
||||
) -> bool {
|
||||
) -> Option<bool> {
|
||||
let modifiers = keystroke.modifiers;
|
||||
let alt = modifiers.alt && Self::alt_is_meta(facts, cx);
|
||||
let text = match &keystroke.key_char {
|
||||
@@ -150,12 +156,15 @@ impl TerminalView {
|
||||
};
|
||||
// As in Windows Terminal: kitty's flags first, then Windows' records; text stays text.
|
||||
#[cfg(windows)]
|
||||
if self.win32 && kitty == Kitty::default() && !keys::types(&key) {
|
||||
if self.win32
|
||||
&& kitty == Kitty::default()
|
||||
&& (record || action != Action::Release && !keys::types(&key))
|
||||
{
|
||||
match super::win32::record(keystroke, action) {
|
||||
Some(bytes) => {
|
||||
log::trace!("terminal {}: {key:?} sends {bytes:?}", self.pty.pid);
|
||||
self.input(bytes, cx);
|
||||
return true;
|
||||
return Some(true);
|
||||
}
|
||||
None => log::debug!(
|
||||
"terminal {}: no virtual key for {:?}, so it goes as bytes",
|
||||
@@ -164,12 +173,12 @@ impl TerminalView {
|
||||
),
|
||||
}
|
||||
}
|
||||
let Some(bytes) = keys::encode(&key, mode.contains(TermMode::APP_CURSOR), kitty) else {
|
||||
return false;
|
||||
};
|
||||
#[cfg(not(windows))]
|
||||
debug_assert!(!record, "only Windows sends records");
|
||||
let bytes = keys::encode(&key, mode.contains(TermMode::APP_CURSOR), kitty)?;
|
||||
log::trace!("terminal {}: {key:?} sends {bytes:?}", self.pty.pid);
|
||||
self.input(bytes, cx);
|
||||
true
|
||||
Some(false)
|
||||
}
|
||||
|
||||
/// AppKit's facts of the key; logged and none when unreadable.
|
||||
@@ -182,6 +191,7 @@ impl TerminalView {
|
||||
right_option: false,
|
||||
repeat: false,
|
||||
unshifted: None,
|
||||
code: None,
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -197,6 +207,7 @@ impl TerminalView {
|
||||
Facts {
|
||||
repeat: false,
|
||||
unshifted,
|
||||
code: super::win32::virtual_key(&keystroke.key).map(|(key, _)| key),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -233,6 +244,21 @@ type Facts = super::mac::KeyFacts;
|
||||
struct Facts {
|
||||
repeat: bool,
|
||||
unshifted: Option<char>,
|
||||
/// The key's virtual key, which Shift does not change; none off Windows.
|
||||
code: Option<u16>,
|
||||
}
|
||||
|
||||
/// A key while it is down: its code where the platform gives one, else gpui's name, which Shift may change.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub(super) enum Held {
|
||||
Code(u16),
|
||||
Name(String),
|
||||
}
|
||||
|
||||
fn held(keystroke: &Keystroke, facts: Facts) -> Held {
|
||||
facts
|
||||
.code
|
||||
.map_or_else(|| Held::Name(keystroke.key.clone()), Held::Code)
|
||||
}
|
||||
|
||||
impl EntityInputHandler for TerminalView {
|
||||
|
||||
+14
-23
@@ -49,21 +49,7 @@ pub fn encode(key: &Key, app_cursor: bool, kitty: Kitty) -> Option<Vec<u8>> {
|
||||
}
|
||||
match functional(key.name) {
|
||||
Some((number, end)) => named(key, number, end, app_cursor, kitty),
|
||||
None => match key.unshifted {
|
||||
// kitty names the key itself, with Shift, and what Shift made of it as its alternate.
|
||||
Some(own) => {
|
||||
let made = printable(key.name)?;
|
||||
let mut name = [0; 4];
|
||||
let key = Key {
|
||||
name: own.encode_utf8(&mut name),
|
||||
shift: true,
|
||||
unshifted: None,
|
||||
..*key
|
||||
};
|
||||
typed(&key, own, Some(made), kitty)
|
||||
}
|
||||
None => typed(key, printable(key.name)?, None, kitty),
|
||||
},
|
||||
None => typed(key, printable(key.name)?, kitty),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,23 +167,28 @@ fn named(key: &Key, number: u32, end: char, app_cursor: bool, kitty: Kitty) -> O
|
||||
Some(sequence.into_bytes())
|
||||
}
|
||||
|
||||
/// A printable key: text, then legacy bytes, then CSI u once a flag has more to say; `made` is what Shift made of it.
|
||||
fn typed(key: &Key, ch: char, made: Option<char>, kitty: Kitty) -> Option<Vec<u8>> {
|
||||
/// A printable key: text, then legacy bytes, then CSI u once a flag has more to say.
|
||||
fn typed(key: &Key, ch: char, kitty: Kitty) -> Option<Vec<u8>> {
|
||||
let typing = types(key);
|
||||
if typing && !kitty.all && key.action != Action::Release {
|
||||
return None;
|
||||
}
|
||||
// gpui names what Shift made of a key and drops Shift; CSI u names the key, with Shift.
|
||||
let (own, shift, made) = match key.unshifted {
|
||||
Some(own) => (own, true, Some(ch).filter(|&made| made != own)),
|
||||
None => (ch, key.shift, None),
|
||||
};
|
||||
let shifted = key
|
||||
.text
|
||||
.chars()
|
||||
.next()
|
||||
.filter(|&typed| typing && typed != ch && !typed.is_control())
|
||||
.or(made.filter(|&made| made != ch))
|
||||
.or_else(|| (key.shift && ch.is_ascii_lowercase()).then(|| ch.to_ascii_uppercase()))
|
||||
.filter(|_| key.shift && kitty.alternates);
|
||||
.filter(|&typed| typing && typed != own && !typed.is_control())
|
||||
.or(made)
|
||||
.or_else(|| (shift && own.is_ascii_lowercase()).then(|| own.to_ascii_uppercase()))
|
||||
.filter(|_| shift && kitty.alternates);
|
||||
let event = event(key, kitty);
|
||||
let text = typing && kitty.text && key.action != Action::Release;
|
||||
let mods = code(key);
|
||||
let mods = code(&Key { shift, ..*key });
|
||||
if shifted.is_none() && event.is_none() && !text {
|
||||
if mods == 1 && !kitty.all {
|
||||
return None;
|
||||
@@ -210,7 +201,7 @@ fn typed(key: &Key, ch: char, made: Option<char>, kitty: Kitty) -> Option<Vec<u8
|
||||
return Some(bytes);
|
||||
}
|
||||
}
|
||||
let mut out = format!("\x1b[{}", ch.to_ascii_lowercase() as u32);
|
||||
let mut out = format!("\x1b[{}", own.to_ascii_lowercase() as u32);
|
||||
if let Some(shifted) = shifted {
|
||||
out.push_str(&format!(":{}", shifted as u32));
|
||||
}
|
||||
|
||||
@@ -341,6 +341,20 @@ fn kitty_reports_the_key_shift_turned_into_a_symbol() {
|
||||
Some("\x1b[123;5u"),
|
||||
"legacy keeps fixterms' shifted character"
|
||||
);
|
||||
let events = Kitty {
|
||||
events: true,
|
||||
..Kitty::default()
|
||||
};
|
||||
assert_eq!(
|
||||
kitty(made("alt+{", "", Action::Press), events).as_deref(),
|
||||
Some("\x1b{"),
|
||||
"a flag that adds no field keeps the legacy bytes gpui's name gives"
|
||||
);
|
||||
let bang = Key {
|
||||
unshifted: Some('1'),
|
||||
..key("alt+!", "")
|
||||
};
|
||||
assert_eq!(kitty(bang, events).as_deref(), Some("\x1b!"));
|
||||
let same = Key {
|
||||
unshifted: Some('7'),
|
||||
..key("ctrl+7", "")
|
||||
|
||||
@@ -58,6 +58,8 @@ pub struct KeyFacts {
|
||||
pub repeat: bool,
|
||||
/// The key's own character, where gpui named the one Shift made and dropped Shift.
|
||||
pub unshifted: Option<char>,
|
||||
/// AppKit's key code, which Shift does not change.
|
||||
pub code: Option<u16>,
|
||||
}
|
||||
|
||||
/// The facts of the key event AppKit is handling now, which gpui named `name`, with `shift` or not.
|
||||
@@ -89,6 +91,7 @@ pub fn key_facts(name: &str, shift: bool) -> anyhow::Result<KeyFacts> {
|
||||
right_option: flags & RIGHT_OPTION != 0,
|
||||
repeat,
|
||||
unshifted,
|
||||
code: Some(code),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
+5
-5
@@ -1,4 +1,4 @@
|
||||
use std::{collections::HashSet, path::PathBuf, process::ExitStatus, rc::Rc, time::Duration};
|
||||
use std::{collections::HashMap, path::PathBuf, process::ExitStatus, rc::Rc, time::Duration};
|
||||
|
||||
use alacritty_terminal::{
|
||||
event::Event,
|
||||
@@ -21,7 +21,7 @@ use super::{
|
||||
find::Finder,
|
||||
frame::frame,
|
||||
ink::Ink,
|
||||
input::{self, Find, NextPrompt, PreviousPrompt},
|
||||
input::{self, Find, Held, NextPrompt, PreviousPrompt},
|
||||
marks::Mark,
|
||||
mouse::MouseMode,
|
||||
paint::{self, Look},
|
||||
@@ -70,8 +70,8 @@ pub struct TerminalView {
|
||||
/// Keys go as Windows' records, as ConPTY asks.
|
||||
#[cfg(windows)]
|
||||
pub(super) win32: bool,
|
||||
/// Keys whose press this terminal saw, so their releases may go out.
|
||||
pub(super) held: HashSet<String>,
|
||||
/// Keys whose press this terminal saw, so their releases may go out; true where it went as Windows' record.
|
||||
pub(super) held: HashMap<Held, bool>,
|
||||
pub(super) finder: Option<Finder>,
|
||||
pub(super) size: (usize, usize),
|
||||
/// How many folders the program has reported.
|
||||
@@ -135,7 +135,7 @@ impl TerminalView {
|
||||
mouse: MouseMode::default(),
|
||||
#[cfg(windows)]
|
||||
win32: false,
|
||||
held: HashSet::new(),
|
||||
held: HashMap::new(),
|
||||
finder: None,
|
||||
size,
|
||||
reports: 0,
|
||||
|
||||
+1
-1
@@ -72,7 +72,7 @@ pub fn unshifted(made: &str) -> Option<char> {
|
||||
}
|
||||
|
||||
/// The virtual key gpui's `name` stands for, and whether it sits in the enhanced block.
|
||||
fn virtual_key(name: &str) -> Option<(u16, bool)> {
|
||||
pub fn virtual_key(name: &str) -> Option<(u16, bool)> {
|
||||
let named = match name {
|
||||
"enter" => (VK_RETURN, false),
|
||||
"tab" => (VK_TAB, false),
|
||||
|
||||
Reference in new issue
Block a user