wip
This commit is contained in:
parent
cb8b7b03cc
commit
fa3abe789c
8 changed files with 190 additions and 324 deletions
|
@ -19,6 +19,10 @@ static KEYMAP_LINUX: LazyLock<KeymapFile> = LazyLock::new(|| {
|
||||||
load_keymap("keymaps/default-linux.json").expect("Failed to load Linux keymap")
|
load_keymap("keymaps/default-linux.json").expect("Failed to load Linux keymap")
|
||||||
});
|
});
|
||||||
|
|
||||||
|
static KEYMAP_WINDOWS: LazyLock<KeymapFile> = LazyLock::new(|| {
|
||||||
|
load_keymap("keymaps/default-windows.json").expect("Failed to load Windows keymap")
|
||||||
|
});
|
||||||
|
|
||||||
static ALL_ACTIONS: LazyLock<Vec<ActionDef>> = LazyLock::new(dump_all_gpui_actions);
|
static ALL_ACTIONS: LazyLock<Vec<ActionDef>> = LazyLock::new(dump_all_gpui_actions);
|
||||||
|
|
||||||
const FRONT_MATTER_COMMENT: &str = "<!-- ZED_META {} -->";
|
const FRONT_MATTER_COMMENT: &str = "<!-- ZED_META {} -->";
|
||||||
|
@ -216,6 +220,7 @@ fn find_binding(os: &str, action: &str) -> Option<String> {
|
||||||
let keymap = match os {
|
let keymap = match os {
|
||||||
"macos" => &KEYMAP_MACOS,
|
"macos" => &KEYMAP_MACOS,
|
||||||
"linux" | "freebsd" => &KEYMAP_LINUX,
|
"linux" | "freebsd" => &KEYMAP_LINUX,
|
||||||
|
"windows" => &KEYMAP_WINDOWS,
|
||||||
_ => unreachable!("Not a valid OS: {}", os),
|
_ => unreachable!("Not a valid OS: {}", os),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -3,8 +3,8 @@ use std::rc::Rc;
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
Action, AsKeystroke, InvalidKeystrokeError, KeyBindingContextPredicate, KeybindingKeystroke,
|
Action, AsKeystroke, DummyKeyboardMapper, InvalidKeystrokeError, KeyBindingContextPredicate,
|
||||||
Keystroke, SharedString,
|
KeybindingKeystroke, Keystroke, PlatformKeyboardMapper, SharedString,
|
||||||
};
|
};
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
|
||||||
|
@ -35,7 +35,16 @@ impl KeyBinding {
|
||||||
pub fn new<A: Action>(keystrokes: &str, action: A, context: Option<&str>) -> Self {
|
pub fn new<A: Action>(keystrokes: &str, action: A, context: Option<&str>) -> Self {
|
||||||
let context_predicate =
|
let context_predicate =
|
||||||
context.map(|context| KeyBindingContextPredicate::parse(context).unwrap().into());
|
context.map(|context| KeyBindingContextPredicate::parse(context).unwrap().into());
|
||||||
Self::load(keystrokes, Box::new(action), context_predicate, None, None).unwrap()
|
Self::load(
|
||||||
|
keystrokes,
|
||||||
|
Box::new(action),
|
||||||
|
context_predicate,
|
||||||
|
false,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
&DummyKeyboardMapper,
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Load a keybinding from the given raw data.
|
/// Load a keybinding from the given raw data.
|
||||||
|
@ -43,12 +52,14 @@ impl KeyBinding {
|
||||||
keystrokes: &str,
|
keystrokes: &str,
|
||||||
action: Box<dyn Action>,
|
action: Box<dyn Action>,
|
||||||
context_predicate: Option<Rc<KeyBindingContextPredicate>>,
|
context_predicate: Option<Rc<KeyBindingContextPredicate>>,
|
||||||
|
use_key_equivalents: bool,
|
||||||
key_equivalents: Option<&HashMap<char, char>>,
|
key_equivalents: Option<&HashMap<char, char>>,
|
||||||
action_input: Option<SharedString>,
|
action_input: Option<SharedString>,
|
||||||
|
keyboard_mapper: &dyn PlatformKeyboardMapper,
|
||||||
) -> std::result::Result<Self, InvalidKeystrokeError> {
|
) -> std::result::Result<Self, InvalidKeystrokeError> {
|
||||||
let mut keystrokes: SmallVec<[Keystroke; 2]> = keystrokes
|
let mut keystrokes: SmallVec<[Keystroke; 2]> = keystrokes
|
||||||
.split_whitespace()
|
.split_whitespace()
|
||||||
.map(|source| Keystroke::parse(source).map(|keystroke| keystroke.into_shifted()))
|
.map(Keystroke::parse)
|
||||||
.collect::<std::result::Result<_, _>>()?;
|
.collect::<std::result::Result<_, _>>()?;
|
||||||
|
|
||||||
if let Some(equivalents) = key_equivalents {
|
if let Some(equivalents) = key_equivalents {
|
||||||
|
@ -63,7 +74,9 @@ impl KeyBinding {
|
||||||
|
|
||||||
let keystrokes = keystrokes
|
let keystrokes = keystrokes
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(KeybindingKeystroke::new)
|
.map(|keystroke| {
|
||||||
|
KeybindingKeystroke::new(keystroke, use_key_equivalents, keyboard_mapper)
|
||||||
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
|
|
|
@ -11,13 +11,22 @@ pub trait PlatformKeyboardLayout {
|
||||||
/// A trait for platform-specific keyboard mappings
|
/// A trait for platform-specific keyboard mappings
|
||||||
pub trait PlatformKeyboardMapper {
|
pub trait PlatformKeyboardMapper {
|
||||||
/// Map a key equivalent to its platform-specific representation
|
/// Map a key equivalent to its platform-specific representation
|
||||||
fn map_key_equivalent(&self, keystroke: Keystroke) -> KeybindingKeystroke;
|
fn map_key_equivalent(
|
||||||
|
&self,
|
||||||
|
keystroke: Keystroke,
|
||||||
|
use_key_equivalents: bool,
|
||||||
|
) -> KeybindingKeystroke;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) struct DummyKeyboardMapper;
|
/// A dummy implementation of the platform keyboard mapper
|
||||||
|
pub struct DummyKeyboardMapper;
|
||||||
|
|
||||||
impl PlatformKeyboardMapper for DummyKeyboardMapper {
|
impl PlatformKeyboardMapper for DummyKeyboardMapper {
|
||||||
fn map_key_equivalent(&self, keystroke: Keystroke) -> KeybindingKeystroke {
|
fn map_key_equivalent(
|
||||||
KeybindingKeystroke::new(keystroke)
|
&self,
|
||||||
|
keystroke: Keystroke,
|
||||||
|
_use_key_equivalents: bool,
|
||||||
|
) -> KeybindingKeystroke {
|
||||||
|
KeybindingKeystroke::from_keystroke(keystroke)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,8 @@ use std::{
|
||||||
fmt::{Display, Write},
|
fmt::{Display, Write},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::PlatformKeyboardMapper;
|
||||||
|
|
||||||
/// TODO:
|
/// TODO:
|
||||||
pub trait AsKeystroke {
|
pub trait AsKeystroke {
|
||||||
/// TODO:
|
/// TODO:
|
||||||
|
@ -281,30 +283,23 @@ impl Keystroke {
|
||||||
}
|
}
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// TODO:
|
|
||||||
pub fn into_shifted(self) -> Self {
|
|
||||||
let Keystroke {
|
|
||||||
modifiers,
|
|
||||||
key,
|
|
||||||
key_char,
|
|
||||||
} = self;
|
|
||||||
let (key, modifiers) = into_shifted_key(key, modifiers);
|
|
||||||
Self {
|
|
||||||
key,
|
|
||||||
modifiers,
|
|
||||||
key_char,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl KeybindingKeystroke {
|
impl KeybindingKeystroke {
|
||||||
/// Create a new keybinding keystroke from the given keystroke
|
/// Create a new keybinding keystroke from the given keystroke
|
||||||
pub fn new(inner: Keystroke) -> Self {
|
pub fn new(
|
||||||
let key = inner.key.clone();
|
inner: Keystroke,
|
||||||
let modifiers = inner.modifiers;
|
use_key_equivalents: bool,
|
||||||
|
keyboard_mapper: &dyn PlatformKeyboardMapper,
|
||||||
|
) -> Self {
|
||||||
|
keyboard_mapper.map_key_equivalent(inner, use_key_equivalents)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn from_keystroke(keystroke: Keystroke) -> Self {
|
||||||
|
let key = keystroke.key.clone();
|
||||||
|
let modifiers = keystroke.modifiers;
|
||||||
KeybindingKeystroke {
|
KeybindingKeystroke {
|
||||||
inner,
|
inner: keystroke,
|
||||||
modifiers,
|
modifiers,
|
||||||
key,
|
key,
|
||||||
}
|
}
|
||||||
|
@ -608,191 +603,6 @@ impl AsKeystroke for KeybindingKeystroke {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_unshifted_key(key: &str, modifiers: &Modifiers) -> (String, Modifiers) {
|
|
||||||
let mut modifiers = modifiers.clone();
|
|
||||||
match key {
|
|
||||||
"~" => {
|
|
||||||
modifiers.shift = true;
|
|
||||||
("`".to_string(), modifiers)
|
|
||||||
}
|
|
||||||
"!" => {
|
|
||||||
modifiers.shift = true;
|
|
||||||
("1".to_string(), modifiers)
|
|
||||||
}
|
|
||||||
"@" => {
|
|
||||||
modifiers.shift = true;
|
|
||||||
("2".to_string(), modifiers)
|
|
||||||
}
|
|
||||||
"#" => {
|
|
||||||
modifiers.shift = true;
|
|
||||||
("3".to_string(), modifiers)
|
|
||||||
}
|
|
||||||
"$" => {
|
|
||||||
modifiers.shift = true;
|
|
||||||
("4".to_string(), modifiers)
|
|
||||||
}
|
|
||||||
"%" => {
|
|
||||||
modifiers.shift = true;
|
|
||||||
("5".to_string(), modifiers)
|
|
||||||
}
|
|
||||||
"^" => {
|
|
||||||
modifiers.shift = true;
|
|
||||||
("6".to_string(), modifiers)
|
|
||||||
}
|
|
||||||
"&" => {
|
|
||||||
modifiers.shift = true;
|
|
||||||
("7".to_string(), modifiers)
|
|
||||||
}
|
|
||||||
"*" => {
|
|
||||||
modifiers.shift = true;
|
|
||||||
("8".to_string(), modifiers)
|
|
||||||
}
|
|
||||||
"(" => {
|
|
||||||
modifiers.shift = true;
|
|
||||||
("9".to_string(), modifiers)
|
|
||||||
}
|
|
||||||
")" => {
|
|
||||||
modifiers.shift = true;
|
|
||||||
("0".to_string(), modifiers)
|
|
||||||
}
|
|
||||||
"_" => {
|
|
||||||
modifiers.shift = true;
|
|
||||||
("-".to_string(), modifiers)
|
|
||||||
}
|
|
||||||
"+" => {
|
|
||||||
modifiers.shift = true;
|
|
||||||
("=".to_string(), modifiers)
|
|
||||||
}
|
|
||||||
"{" => {
|
|
||||||
modifiers.shift = true;
|
|
||||||
("[".to_string(), modifiers)
|
|
||||||
}
|
|
||||||
"}" => {
|
|
||||||
modifiers.shift = true;
|
|
||||||
("]".to_string(), modifiers)
|
|
||||||
}
|
|
||||||
"|" => {
|
|
||||||
modifiers.shift = true;
|
|
||||||
("\\".to_string(), modifiers)
|
|
||||||
}
|
|
||||||
":" => {
|
|
||||||
modifiers.shift = true;
|
|
||||||
(";".to_string(), modifiers)
|
|
||||||
}
|
|
||||||
"\"" => {
|
|
||||||
modifiers.shift = true;
|
|
||||||
("'".to_string(), modifiers)
|
|
||||||
}
|
|
||||||
"<" => {
|
|
||||||
modifiers.shift = true;
|
|
||||||
(",".to_string(), modifiers)
|
|
||||||
}
|
|
||||||
">" => {
|
|
||||||
modifiers.shift = true;
|
|
||||||
(">".to_string(), modifiers)
|
|
||||||
}
|
|
||||||
"?" => {
|
|
||||||
modifiers.shift = true;
|
|
||||||
("/".to_string(), modifiers)
|
|
||||||
}
|
|
||||||
_ => (key.to_string(), modifiers),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn into_shifted_key(key: String, mut modifiers: Modifiers) -> (String, Modifiers) {
|
|
||||||
if !modifiers.shift {
|
|
||||||
(key, modifiers)
|
|
||||||
} else {
|
|
||||||
match key.as_str() {
|
|
||||||
"`" => {
|
|
||||||
modifiers.shift = false;
|
|
||||||
("~".to_string(), modifiers)
|
|
||||||
}
|
|
||||||
"1" => {
|
|
||||||
modifiers.shift = false;
|
|
||||||
("!".to_string(), modifiers)
|
|
||||||
}
|
|
||||||
"2" => {
|
|
||||||
modifiers.shift = false;
|
|
||||||
("@".to_string(), modifiers)
|
|
||||||
}
|
|
||||||
"3" => {
|
|
||||||
modifiers.shift = false;
|
|
||||||
("#".to_string(), modifiers)
|
|
||||||
}
|
|
||||||
"4" => {
|
|
||||||
modifiers.shift = false;
|
|
||||||
("$".to_string(), modifiers)
|
|
||||||
}
|
|
||||||
"5" => {
|
|
||||||
modifiers.shift = false;
|
|
||||||
("%".to_string(), modifiers)
|
|
||||||
}
|
|
||||||
"6" => {
|
|
||||||
modifiers.shift = false;
|
|
||||||
("^".to_string(), modifiers)
|
|
||||||
}
|
|
||||||
"7" => {
|
|
||||||
modifiers.shift = false;
|
|
||||||
("&".to_string(), modifiers)
|
|
||||||
}
|
|
||||||
"8" => {
|
|
||||||
modifiers.shift = false;
|
|
||||||
("*".to_string(), modifiers)
|
|
||||||
}
|
|
||||||
"9" => {
|
|
||||||
modifiers.shift = false;
|
|
||||||
("(".to_string(), modifiers)
|
|
||||||
}
|
|
||||||
"0" => {
|
|
||||||
modifiers.shift = false;
|
|
||||||
(")".to_string(), modifiers)
|
|
||||||
}
|
|
||||||
"-" => {
|
|
||||||
modifiers.shift = false;
|
|
||||||
("_".to_string(), modifiers)
|
|
||||||
}
|
|
||||||
"=" => {
|
|
||||||
modifiers.shift = false;
|
|
||||||
("+".to_string(), modifiers)
|
|
||||||
}
|
|
||||||
"[" => {
|
|
||||||
modifiers.shift = false;
|
|
||||||
("{".to_string(), modifiers)
|
|
||||||
}
|
|
||||||
"]" => {
|
|
||||||
modifiers.shift = false;
|
|
||||||
("}".to_string(), modifiers)
|
|
||||||
}
|
|
||||||
"\\" => {
|
|
||||||
modifiers.shift = false;
|
|
||||||
("|".to_string(), modifiers)
|
|
||||||
}
|
|
||||||
";" => {
|
|
||||||
modifiers.shift = false;
|
|
||||||
(":".to_string(), modifiers)
|
|
||||||
}
|
|
||||||
"'" => {
|
|
||||||
modifiers.shift = false;
|
|
||||||
("\"".to_string(), modifiers)
|
|
||||||
}
|
|
||||||
"," => {
|
|
||||||
modifiers.shift = false;
|
|
||||||
("<".to_string(), modifiers)
|
|
||||||
}
|
|
||||||
"." => {
|
|
||||||
modifiers.shift = false;
|
|
||||||
(">".to_string(), modifiers)
|
|
||||||
}
|
|
||||||
"/" => {
|
|
||||||
modifiers.shift = false;
|
|
||||||
("?".to_string(), modifiers)
|
|
||||||
}
|
|
||||||
_ => (key, modifiers),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn display_modifiers(modifiers: &Modifiers, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn display_modifiers(modifiers: &Modifiers, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
if modifiers.control {
|
if modifiers.control {
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
|
@ -858,45 +668,3 @@ fn display_key(key: &str, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
};
|
};
|
||||||
f.write_char(key)
|
f.write_char(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use crate::{KeybindingKeystroke, Keystroke, Modifiers};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_parsing_keystroke_on_windows() {
|
|
||||||
// On windows, users prefer to use "ctrl-shift-key", so here we support
|
|
||||||
// both "ctrl-$" and "ctrl-shift-4"
|
|
||||||
let source = "ctrl-$";
|
|
||||||
let keystroke = Keystroke::parse(source).unwrap();
|
|
||||||
assert_eq!(keystroke.modifiers, Modifiers::control());
|
|
||||||
assert_eq!(keystroke.key, "$");
|
|
||||||
assert_eq!(keystroke.key_char, None);
|
|
||||||
|
|
||||||
let keystroke_display = KeybindingKeystroke::new(keystroke.clone());
|
|
||||||
assert_eq!(keystroke_display.inner, keystroke);
|
|
||||||
assert_eq!(keystroke_display.key, "4");
|
|
||||||
assert_eq!(keystroke_display.modifiers, Modifiers::control_shift());
|
|
||||||
|
|
||||||
let source = "ctrl-shift-4";
|
|
||||||
let keystroke = Keystroke::parse(source).unwrap();
|
|
||||||
assert_eq!(keystroke.modifiers, Modifiers::control_shift());
|
|
||||||
assert_eq!(keystroke.key, "4");
|
|
||||||
assert_eq!(keystroke.key_char, None);
|
|
||||||
|
|
||||||
let keystroke = keystroke.into_shifted();
|
|
||||||
assert_eq!(keystroke.modifiers, Modifiers::control());
|
|
||||||
assert_eq!(keystroke.key, "$");
|
|
||||||
let keystroke_display = KeybindingKeystroke::new(keystroke.clone());
|
|
||||||
assert_eq!(
|
|
||||||
keystroke_display.inner,
|
|
||||||
Keystroke {
|
|
||||||
modifiers: Modifiers::control(),
|
|
||||||
key: "$".to_string(),
|
|
||||||
key_char: None
|
|
||||||
}
|
|
||||||
);
|
|
||||||
assert_eq!(keystroke_display.key, "4");
|
|
||||||
assert_eq!(keystroke_display.modifiers, Modifiers::control_shift());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use collections::HashMap;
|
||||||
use windows::Win32::UI::{
|
use windows::Win32::UI::{
|
||||||
Input::KeyboardAndMouse::{
|
Input::KeyboardAndMouse::{
|
||||||
GetKeyboardLayoutNameW, MAPVK_VK_TO_CHAR, MAPVK_VK_TO_VSC, MapVirtualKeyW, ToUnicode,
|
GetKeyboardLayoutNameW, MAPVK_VK_TO_CHAR, MAPVK_VK_TO_VSC, MapVirtualKeyW, ToUnicode,
|
||||||
|
@ -19,7 +20,11 @@ pub(crate) struct WindowsKeyboardLayout {
|
||||||
name: String,
|
name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) struct WindowsKeyboardMapper;
|
pub(crate) struct WindowsKeyboardMapper {
|
||||||
|
key_to_vkey: HashMap<String, (u16, bool)>,
|
||||||
|
vkey_to_key: HashMap<u16, String>,
|
||||||
|
vkey_to_shifted: HashMap<u16, String>,
|
||||||
|
}
|
||||||
|
|
||||||
impl PlatformKeyboardLayout for WindowsKeyboardLayout {
|
impl PlatformKeyboardLayout for WindowsKeyboardLayout {
|
||||||
fn id(&self) -> &str {
|
fn id(&self) -> &str {
|
||||||
|
@ -32,9 +37,14 @@ impl PlatformKeyboardLayout for WindowsKeyboardLayout {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PlatformKeyboardMapper for WindowsKeyboardMapper {
|
impl PlatformKeyboardMapper for WindowsKeyboardMapper {
|
||||||
fn map_key_equivalent(&self, mut keystroke: Keystroke) -> KeybindingKeystroke {
|
fn map_key_equivalent(
|
||||||
let Some((vkey, shifted_key)) = key_needs_processing(&keystroke.key) else {
|
&self,
|
||||||
return KeybindingKeystroke::new(keystroke);
|
mut keystroke: Keystroke,
|
||||||
|
use_key_equivalents: bool,
|
||||||
|
) -> KeybindingKeystroke {
|
||||||
|
let Some((vkey, shifted_key)) = self.get_vkey_from_key(&keystroke.key, use_key_equivalents)
|
||||||
|
else {
|
||||||
|
return KeybindingKeystroke::from_keystroke(keystroke);
|
||||||
};
|
};
|
||||||
if shifted_key && keystroke.modifiers.shift {
|
if shifted_key && keystroke.modifiers.shift {
|
||||||
log::warn!(
|
log::warn!(
|
||||||
|
@ -46,31 +56,22 @@ impl PlatformKeyboardMapper for WindowsKeyboardMapper {
|
||||||
let shift = shifted_key || keystroke.modifiers.shift;
|
let shift = shifted_key || keystroke.modifiers.shift;
|
||||||
keystroke.modifiers.shift = false;
|
keystroke.modifiers.shift = false;
|
||||||
|
|
||||||
let Some(key) = get_key_from_vkey(vkey) else {
|
let Some(key) = self.vkey_to_key.get(&vkey).cloned() else {
|
||||||
log::error!(
|
log::error!(
|
||||||
"Failed to map key equivalent '{:?}' to a valid key",
|
"Failed to map key equivalent '{:?}' to a valid key",
|
||||||
keystroke
|
keystroke
|
||||||
);
|
);
|
||||||
return KeybindingKeystroke::new(keystroke);
|
return KeybindingKeystroke::from_keystroke(keystroke);
|
||||||
};
|
};
|
||||||
|
|
||||||
keystroke.key = if shift {
|
keystroke.key = if shift {
|
||||||
let scan_code = unsafe { MapVirtualKeyW(vkey.0 as u32, MAPVK_VK_TO_VSC) };
|
let Some(shifted_key) = self.vkey_to_shifted.get(&vkey).cloned() else {
|
||||||
if scan_code == 0 {
|
|
||||||
log::error!(
|
|
||||||
"Failed to map keystroke {:?} with virtual key '{:?}' to a scan code",
|
|
||||||
keystroke,
|
|
||||||
vkey
|
|
||||||
);
|
|
||||||
return KeybindingKeystroke::new(keystroke);
|
|
||||||
}
|
|
||||||
let Some(shifted_key) = get_shifted_key(vkey, scan_code) else {
|
|
||||||
log::error!(
|
log::error!(
|
||||||
"Failed to map keystroke {:?} with virtual key '{:?}' to a shifted key",
|
"Failed to map keystroke {:?} with virtual key '{:?}' to a shifted key",
|
||||||
keystroke,
|
keystroke,
|
||||||
vkey
|
vkey
|
||||||
);
|
);
|
||||||
return KeybindingKeystroke::new(keystroke);
|
return KeybindingKeystroke::from_keystroke(keystroke);
|
||||||
};
|
};
|
||||||
shifted_key
|
shifted_key
|
||||||
} else {
|
} else {
|
||||||
|
@ -113,7 +114,36 @@ impl WindowsKeyboardLayout {
|
||||||
|
|
||||||
impl WindowsKeyboardMapper {
|
impl WindowsKeyboardMapper {
|
||||||
pub(crate) fn new() -> Self {
|
pub(crate) fn new() -> Self {
|
||||||
Self
|
let mut key_to_vkey = HashMap::default();
|
||||||
|
let mut vkey_to_key = HashMap::default();
|
||||||
|
let mut vkey_to_shifted = HashMap::default();
|
||||||
|
for vkey in CANDIDATE_VKEYS {
|
||||||
|
if let Some(key) = get_key_from_vkey(*vkey) {
|
||||||
|
key_to_vkey.insert(key.clone(), (vkey.0, false));
|
||||||
|
vkey_to_key.insert(vkey.0, key);
|
||||||
|
}
|
||||||
|
let scan_code = unsafe { MapVirtualKeyW(vkey.0 as u32, MAPVK_VK_TO_VSC) };
|
||||||
|
if scan_code == 0 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if let Some(shifted_key) = get_shifted_key(*vkey, scan_code) {
|
||||||
|
key_to_vkey.insert(shifted_key.clone(), (vkey.0, true));
|
||||||
|
vkey_to_shifted.insert(vkey.0, shifted_key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Self {
|
||||||
|
key_to_vkey,
|
||||||
|
vkey_to_key,
|
||||||
|
vkey_to_shifted,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_vkey_from_key(&self, key: &str, use_key_equivalents: bool) -> Option<(u16, bool)> {
|
||||||
|
if use_key_equivalents {
|
||||||
|
key_needs_processing(key)
|
||||||
|
} else {
|
||||||
|
self.key_to_vkey.get(key).cloned()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -210,54 +240,81 @@ pub(crate) fn generate_key_char(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn key_needs_processing(key: &str) -> Option<(VIRTUAL_KEY, bool)> {
|
fn key_needs_processing(key: &str) -> Option<(u16, bool)> {
|
||||||
match key {
|
match key {
|
||||||
"`" => Some((VK_OEM_3, false)),
|
"`" => Some((VK_OEM_3.0, false)),
|
||||||
"~" => Some((VK_OEM_3, true)),
|
"~" => Some((VK_OEM_3.0, true)),
|
||||||
"1" => Some((VK_1, false)),
|
"1" => Some((VK_1.0, false)),
|
||||||
"!" => Some((VK_1, true)),
|
"!" => Some((VK_1.0, true)),
|
||||||
"2" => Some((VK_2, false)),
|
"2" => Some((VK_2.0, false)),
|
||||||
"@" => Some((VK_2, true)),
|
"@" => Some((VK_2.0, true)),
|
||||||
"3" => Some((VK_3, false)),
|
"3" => Some((VK_3.0, false)),
|
||||||
"#" => Some((VK_3, true)),
|
"#" => Some((VK_3.0, true)),
|
||||||
"4" => Some((VK_4, false)),
|
"4" => Some((VK_4.0, false)),
|
||||||
"$" => Some((VK_4, true)),
|
"$" => Some((VK_4.0, true)),
|
||||||
"5" => Some((VK_5, false)),
|
"5" => Some((VK_5.0, false)),
|
||||||
"%" => Some((VK_5, true)),
|
"%" => Some((VK_5.0, true)),
|
||||||
"6" => Some((VK_6, false)),
|
"6" => Some((VK_6.0, false)),
|
||||||
"^" => Some((VK_6, true)),
|
"^" => Some((VK_6.0, true)),
|
||||||
"7" => Some((VK_7, false)),
|
"7" => Some((VK_7.0, false)),
|
||||||
"&" => Some((VK_7, true)),
|
"&" => Some((VK_7.0, true)),
|
||||||
"8" => Some((VK_8, false)),
|
"8" => Some((VK_8.0, false)),
|
||||||
"*" => Some((VK_8, true)),
|
"*" => Some((VK_8.0, true)),
|
||||||
"9" => Some((VK_9, false)),
|
"9" => Some((VK_9.0, false)),
|
||||||
"(" => Some((VK_9, true)),
|
"(" => Some((VK_9.0, true)),
|
||||||
"0" => Some((VK_0, false)),
|
"0" => Some((VK_0.0, false)),
|
||||||
")" => Some((VK_0, true)),
|
")" => Some((VK_0.0, true)),
|
||||||
"-" => Some((VK_OEM_MINUS, false)),
|
"-" => Some((VK_OEM_MINUS.0, false)),
|
||||||
"_" => Some((VK_OEM_MINUS, true)),
|
"_" => Some((VK_OEM_MINUS.0, true)),
|
||||||
"=" => Some((VK_OEM_PLUS, false)),
|
"=" => Some((VK_OEM_PLUS.0, false)),
|
||||||
"+" => Some((VK_OEM_PLUS, true)),
|
"+" => Some((VK_OEM_PLUS.0, true)),
|
||||||
"[" => Some((VK_OEM_4, false)),
|
"[" => Some((VK_OEM_4.0, false)),
|
||||||
"{" => Some((VK_OEM_4, true)),
|
"{" => Some((VK_OEM_4.0, true)),
|
||||||
"]" => Some((VK_OEM_6, false)),
|
"]" => Some((VK_OEM_6.0, false)),
|
||||||
"}" => Some((VK_OEM_6, true)),
|
"}" => Some((VK_OEM_6.0, true)),
|
||||||
"\\" => Some((VK_OEM_5, false)),
|
"\\" => Some((VK_OEM_5.0, false)),
|
||||||
"|" => Some((VK_OEM_5, true)),
|
"|" => Some((VK_OEM_5.0, true)),
|
||||||
";" => Some((VK_OEM_1, false)),
|
";" => Some((VK_OEM_1.0, false)),
|
||||||
":" => Some((VK_OEM_1, true)),
|
":" => Some((VK_OEM_1.0, true)),
|
||||||
"'" => Some((VK_OEM_7, false)),
|
"'" => Some((VK_OEM_7.0, false)),
|
||||||
"\"" => Some((VK_OEM_7, true)),
|
"\"" => Some((VK_OEM_7.0, true)),
|
||||||
"," => Some((VK_OEM_COMMA, false)),
|
"," => Some((VK_OEM_COMMA.0, false)),
|
||||||
"<" => Some((VK_OEM_COMMA, true)),
|
"<" => Some((VK_OEM_COMMA.0, true)),
|
||||||
"." => Some((VK_OEM_PERIOD, false)),
|
"." => Some((VK_OEM_PERIOD.0, false)),
|
||||||
">" => Some((VK_OEM_PERIOD, true)),
|
">" => Some((VK_OEM_PERIOD.0, true)),
|
||||||
"/" => Some((VK_OEM_2, false)),
|
"/" => Some((VK_OEM_2.0, false)),
|
||||||
"?" => Some((VK_OEM_2, true)),
|
"?" => Some((VK_OEM_2.0, true)),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const CANDIDATE_VKEYS: &[VIRTUAL_KEY] = &[
|
||||||
|
VK_OEM_3,
|
||||||
|
VK_OEM_MINUS,
|
||||||
|
VK_OEM_PLUS,
|
||||||
|
VK_OEM_4,
|
||||||
|
VK_OEM_5,
|
||||||
|
VK_OEM_6,
|
||||||
|
VK_OEM_1,
|
||||||
|
VK_OEM_7,
|
||||||
|
VK_OEM_COMMA,
|
||||||
|
VK_OEM_PERIOD,
|
||||||
|
VK_OEM_2,
|
||||||
|
VK_OEM_102,
|
||||||
|
VK_OEM_8,
|
||||||
|
VK_ABNT_C1,
|
||||||
|
VK_0,
|
||||||
|
VK_1,
|
||||||
|
VK_2,
|
||||||
|
VK_3,
|
||||||
|
VK_4,
|
||||||
|
VK_5,
|
||||||
|
VK_6,
|
||||||
|
VK_7,
|
||||||
|
VK_8,
|
||||||
|
VK_9,
|
||||||
|
];
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::{Keystroke, Modifiers, PlatformKeyboardMapper, WindowsKeyboardMapper};
|
use crate::{Keystroke, Modifiers, PlatformKeyboardMapper, WindowsKeyboardMapper};
|
||||||
|
@ -272,7 +329,7 @@ mod tests {
|
||||||
key: "a".to_string(),
|
key: "a".to_string(),
|
||||||
key_char: None,
|
key_char: None,
|
||||||
};
|
};
|
||||||
let mapped = mapper.map_key_equivalent(keystroke.clone());
|
let mapped = mapper.map_key_equivalent(keystroke.clone(), true);
|
||||||
assert_eq!(mapped.inner, keystroke);
|
assert_eq!(mapped.inner, keystroke);
|
||||||
assert_eq!(mapped.key, "a");
|
assert_eq!(mapped.key, "a");
|
||||||
assert_eq!(mapped.modifiers, Modifiers::control());
|
assert_eq!(mapped.modifiers, Modifiers::control());
|
||||||
|
@ -283,7 +340,7 @@ mod tests {
|
||||||
key: "$".to_string(),
|
key: "$".to_string(),
|
||||||
key_char: None,
|
key_char: None,
|
||||||
};
|
};
|
||||||
let mapped = mapper.map_key_equivalent(keystroke.clone());
|
let mapped = mapper.map_key_equivalent(keystroke.clone(), true);
|
||||||
assert_eq!(mapped.inner, keystroke);
|
assert_eq!(mapped.inner, keystroke);
|
||||||
assert_eq!(mapped.key, "4");
|
assert_eq!(mapped.key, "4");
|
||||||
assert_eq!(mapped.modifiers, Modifiers::control_shift());
|
assert_eq!(mapped.modifiers, Modifiers::control_shift());
|
||||||
|
@ -294,7 +351,7 @@ mod tests {
|
||||||
key: "$".to_string(),
|
key: "$".to_string(),
|
||||||
key_char: None,
|
key_char: None,
|
||||||
};
|
};
|
||||||
let mapped = mapper.map_key_equivalent(keystroke.clone());
|
let mapped = mapper.map_key_equivalent(keystroke.clone(), true);
|
||||||
assert_eq!(mapped.inner.modifiers, Modifiers::control());
|
assert_eq!(mapped.inner.modifiers, Modifiers::control());
|
||||||
assert_eq!(mapped.key, "4");
|
assert_eq!(mapped.key, "4");
|
||||||
assert_eq!(mapped.modifiers, Modifiers::control_shift());
|
assert_eq!(mapped.modifiers, Modifiers::control_shift());
|
||||||
|
@ -305,7 +362,7 @@ mod tests {
|
||||||
key: "4".to_string(),
|
key: "4".to_string(),
|
||||||
key_char: None,
|
key_char: None,
|
||||||
};
|
};
|
||||||
let mapped = mapper.map_key_equivalent(keystroke.clone());
|
let mapped = mapper.map_key_equivalent(keystroke.clone(), true);
|
||||||
assert_eq!(mapped.inner.modifiers, Modifiers::control());
|
assert_eq!(mapped.inner.modifiers, Modifiers::control());
|
||||||
assert_eq!(mapped.inner.key, "$");
|
assert_eq!(mapped.inner.key, "$");
|
||||||
assert_eq!(mapped.key, "4");
|
assert_eq!(mapped.key, "4");
|
||||||
|
|
|
@ -279,6 +279,7 @@ impl KeymapFile {
|
||||||
keystrokes,
|
keystrokes,
|
||||||
action,
|
action,
|
||||||
context_predicate.clone(),
|
context_predicate.clone(),
|
||||||
|
*use_key_equivalents,
|
||||||
key_equivalents,
|
key_equivalents,
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
|
@ -337,6 +338,7 @@ impl KeymapFile {
|
||||||
keystrokes: &str,
|
keystrokes: &str,
|
||||||
action: &KeymapAction,
|
action: &KeymapAction,
|
||||||
context: Option<Rc<KeyBindingContextPredicate>>,
|
context: Option<Rc<KeyBindingContextPredicate>>,
|
||||||
|
use_key_equivalents: bool,
|
||||||
key_equivalents: Option<&HashMap<char, char>>,
|
key_equivalents: Option<&HashMap<char, char>>,
|
||||||
cx: &App,
|
cx: &App,
|
||||||
) -> std::result::Result<KeyBinding, String> {
|
) -> std::result::Result<KeyBinding, String> {
|
||||||
|
@ -405,8 +407,10 @@ impl KeymapFile {
|
||||||
keystrokes,
|
keystrokes,
|
||||||
action,
|
action,
|
||||||
context,
|
context,
|
||||||
|
use_key_equivalents,
|
||||||
key_equivalents,
|
key_equivalents,
|
||||||
action_input_string.map(SharedString::from),
|
action_input_string.map(SharedString::from),
|
||||||
|
cx.keyboard_mapper(),
|
||||||
) {
|
) {
|
||||||
Ok(key_binding) => key_binding,
|
Ok(key_binding) => key_binding,
|
||||||
Err(InvalidKeystrokeError { keystroke }) => {
|
Err(InvalidKeystrokeError { keystroke }) => {
|
||||||
|
@ -1021,7 +1025,7 @@ impl From<KeybindSource> for KeyBindingMetaIndex {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use gpui::{KeybindingKeystroke, Keystroke};
|
use gpui::{DummyKeyboardMapper, KeybindingKeystroke, Keystroke};
|
||||||
use unindent::Unindent;
|
use unindent::Unindent;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
@ -1059,7 +1063,13 @@ mod tests {
|
||||||
fn parse_keystrokes(keystrokes: &str) -> Vec<KeybindingKeystroke> {
|
fn parse_keystrokes(keystrokes: &str) -> Vec<KeybindingKeystroke> {
|
||||||
keystrokes
|
keystrokes
|
||||||
.split(' ')
|
.split(' ')
|
||||||
.map(|s| KeybindingKeystroke::new(Keystroke::parse(s).expect("Keystrokes valid")))
|
.map(|s| {
|
||||||
|
KeybindingKeystroke::new(
|
||||||
|
Keystroke::parse(s).expect("Keystrokes valid"),
|
||||||
|
false,
|
||||||
|
&DummyKeyboardMapper,
|
||||||
|
)
|
||||||
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -89,9 +89,12 @@ pub fn default_settings() -> Cow<'static, str> {
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
pub const DEFAULT_KEYMAP_PATH: &str = "keymaps/default-macos.json";
|
pub const DEFAULT_KEYMAP_PATH: &str = "keymaps/default-macos.json";
|
||||||
|
|
||||||
#[cfg(not(target_os = "macos"))]
|
#[cfg(target_os = "linux")]
|
||||||
pub const DEFAULT_KEYMAP_PATH: &str = "keymaps/default-linux.json";
|
pub const DEFAULT_KEYMAP_PATH: &str = "keymaps/default-linux.json";
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
pub const DEFAULT_KEYMAP_PATH: &str = "keymaps/default-windows.json";
|
||||||
|
|
||||||
pub fn default_keymap() -> Cow<'static, str> {
|
pub fn default_keymap() -> Cow<'static, str> {
|
||||||
asset_str::<SettingsAssets>(DEFAULT_KEYMAP_PATH)
|
asset_str::<SettingsAssets>(DEFAULT_KEYMAP_PATH)
|
||||||
}
|
}
|
||||||
|
|
|
@ -301,7 +301,8 @@ impl KeystrokeInput {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut keystroke = KeybindingKeystroke::new(keystroke.clone());
|
let mut keystroke =
|
||||||
|
KeybindingKeystroke::new(keystroke.clone(), false, cx.keyboard_mapper());
|
||||||
if let Some(last) = self.keystrokes.last()
|
if let Some(last) = self.keystrokes.last()
|
||||||
&& last.key.is_empty()
|
&& last.key.is_empty()
|
||||||
&& (!self.search || self.previous_modifiers.modified())
|
&& (!self.search || self.previous_modifiers.modified())
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue