1
1
mirror of https://github.com/wez/wezterm.git synced 2024-12-24 05:42:03 +03:00
wezterm/wezterm-gui/src/inputmap.rs
Wez Furlong c0fff4e843 Add wezterm.gui.default_key_tables()
This provides a means for more easily extending the default key
tables without forcing the user to recreate the entire config
for themselves.

wezterm.gui.default_keys is also added by this, but it is likely
not as useful.
2022-09-07 10:21:54 -07:00

657 lines
23 KiB
Rust

use crate::commands::CommandDef;
use config::keyassignment::{
ClipboardCopyDestination, ClipboardPasteSource, KeyAssignment, KeyTableEntry, KeyTables,
MouseEventTrigger, SelectionMode,
};
use config::{ConfigHandle, MouseEventAltScreen, MouseEventTriggerMods};
use std::collections::{BTreeMap, HashMap};
use std::time::Duration;
use wezterm_dynamic::{ToDynamic, Value};
use wezterm_term::input::MouseButton;
use window::{KeyCode, Modifiers};
pub struct InputMap {
pub keys: KeyTables,
pub mouse: HashMap<(MouseEventTrigger, MouseEventTriggerMods), KeyAssignment>,
leader: Option<(KeyCode, Modifiers, Duration)>,
}
impl InputMap {
pub fn default_input_map() -> Self {
let config = ConfigHandle::default_config();
Self::new(&config)
}
pub fn new(config: &ConfigHandle) -> Self {
let mut mouse = config.mouse_bindings();
let mut keys = config.key_bindings();
let leader = config.leader.as_ref().map(|leader| {
(
leader.key.key.resolve(config.key_map_preference).clone(),
leader.key.mods,
Duration::from_millis(leader.timeout_milliseconds),
)
});
let ctrl_shift = Modifiers::CTRL | Modifiers::SHIFT;
macro_rules! m {
($([$mod:expr, $code:expr, $action:expr]),* $(,)?) => {
$(
mouse.entry(($code, $mod)).or_insert($action);
)*
};
}
use KeyAssignment::*;
if !config.disable_default_key_bindings {
for (mods, code, action) in CommandDef::default_key_assignments(config) {
keys.default
.entry((code, mods))
.or_insert(KeyTableEntry { action });
}
}
if !config.disable_default_mouse_bindings {
m!(
[
MouseEventTriggerMods {
mods: Modifiers::NONE,
mouse_reporting: false,
alt_screen: MouseEventAltScreen::False,
},
MouseEventTrigger::Down {
streak: 1,
button: MouseButton::WheelUp(1),
},
ScrollByCurrentEventWheelDelta
],
[
MouseEventTriggerMods {
mods: Modifiers::NONE,
mouse_reporting: false,
alt_screen: MouseEventAltScreen::False,
},
MouseEventTrigger::Down {
streak: 1,
button: MouseButton::WheelDown(1),
},
ScrollByCurrentEventWheelDelta
],
[
MouseEventTriggerMods {
mods: Modifiers::NONE,
mouse_reporting: false,
alt_screen: MouseEventAltScreen::Any,
},
MouseEventTrigger::Down {
streak: 3,
button: MouseButton::Left
},
SelectTextAtMouseCursor(SelectionMode::Line)
],
[
MouseEventTriggerMods {
mods: Modifiers::NONE,
mouse_reporting: false,
alt_screen: MouseEventAltScreen::Any,
},
MouseEventTrigger::Down {
streak: 2,
button: MouseButton::Left
},
SelectTextAtMouseCursor(SelectionMode::Word)
],
[
MouseEventTriggerMods {
mods: Modifiers::NONE,
mouse_reporting: false,
alt_screen: MouseEventAltScreen::Any,
},
MouseEventTrigger::Down {
streak: 1,
button: MouseButton::Left
},
SelectTextAtMouseCursor(SelectionMode::Cell)
],
[
MouseEventTriggerMods {
mods: Modifiers::ALT,
mouse_reporting: false,
alt_screen: MouseEventAltScreen::Any,
},
MouseEventTrigger::Down {
streak: 1,
button: MouseButton::Left
},
SelectTextAtMouseCursor(SelectionMode::Block)
],
[
MouseEventTriggerMods {
mods: Modifiers::SHIFT,
mouse_reporting: false,
alt_screen: MouseEventAltScreen::Any,
},
MouseEventTrigger::Down {
streak: 1,
button: MouseButton::Left
},
ExtendSelectionToMouseCursor(SelectionMode::Cell)
],
[
MouseEventTriggerMods {
mods: Modifiers::SHIFT,
mouse_reporting: false,
alt_screen: MouseEventAltScreen::Any,
},
MouseEventTrigger::Up {
streak: 1,
button: MouseButton::Left
},
CompleteSelectionOrOpenLinkAtMouseCursor(
ClipboardCopyDestination::PrimarySelection
)
],
[
MouseEventTriggerMods {
mods: Modifiers::NONE,
mouse_reporting: false,
alt_screen: MouseEventAltScreen::Any,
},
MouseEventTrigger::Up {
streak: 1,
button: MouseButton::Left
},
CompleteSelectionOrOpenLinkAtMouseCursor(
ClipboardCopyDestination::PrimarySelection
)
],
[
MouseEventTriggerMods {
mods: Modifiers::ALT,
mouse_reporting: false,
alt_screen: MouseEventAltScreen::Any,
},
MouseEventTrigger::Up {
streak: 1,
button: MouseButton::Left
},
CompleteSelection(ClipboardCopyDestination::PrimarySelection)
],
[
MouseEventTriggerMods {
mods: Modifiers::ALT | Modifiers::SHIFT,
mouse_reporting: false,
alt_screen: MouseEventAltScreen::Any,
},
MouseEventTrigger::Down {
streak: 1,
button: MouseButton::Left
},
ExtendSelectionToMouseCursor(SelectionMode::Block)
],
[
MouseEventTriggerMods {
mods: Modifiers::ALT | Modifiers::SHIFT,
mouse_reporting: false,
alt_screen: MouseEventAltScreen::Any,
},
MouseEventTrigger::Up {
streak: 1,
button: MouseButton::Left
},
CompleteSelectionOrOpenLinkAtMouseCursor(
ClipboardCopyDestination::PrimarySelection
)
],
[
MouseEventTriggerMods {
mods: Modifiers::NONE,
mouse_reporting: false,
alt_screen: MouseEventAltScreen::Any,
},
MouseEventTrigger::Up {
streak: 2,
button: MouseButton::Left
},
CompleteSelection(ClipboardCopyDestination::PrimarySelection)
],
[
MouseEventTriggerMods {
mods: Modifiers::NONE,
mouse_reporting: false,
alt_screen: MouseEventAltScreen::Any,
},
MouseEventTrigger::Up {
streak: 3,
button: MouseButton::Left
},
CompleteSelection(ClipboardCopyDestination::PrimarySelection)
],
[
MouseEventTriggerMods {
mods: Modifiers::NONE,
mouse_reporting: false,
alt_screen: MouseEventAltScreen::Any,
},
MouseEventTrigger::Drag {
streak: 1,
button: MouseButton::Left
},
ExtendSelectionToMouseCursor(SelectionMode::Cell)
],
[
MouseEventTriggerMods {
mods: Modifiers::ALT,
mouse_reporting: false,
alt_screen: MouseEventAltScreen::Any,
},
MouseEventTrigger::Drag {
streak: 1,
button: MouseButton::Left
},
ExtendSelectionToMouseCursor(SelectionMode::Block)
],
[
MouseEventTriggerMods {
mods: Modifiers::NONE,
mouse_reporting: false,
alt_screen: MouseEventAltScreen::Any,
},
MouseEventTrigger::Drag {
streak: 2,
button: MouseButton::Left
},
ExtendSelectionToMouseCursor(SelectionMode::Word)
],
[
MouseEventTriggerMods {
mods: Modifiers::NONE,
mouse_reporting: false,
alt_screen: MouseEventAltScreen::Any,
},
MouseEventTrigger::Drag {
streak: 3,
button: MouseButton::Left
},
ExtendSelectionToMouseCursor(SelectionMode::Line)
],
[
MouseEventTriggerMods {
mods: Modifiers::NONE,
mouse_reporting: false,
alt_screen: MouseEventAltScreen::Any,
},
MouseEventTrigger::Down {
streak: 1,
button: MouseButton::Middle
},
PasteFrom(ClipboardPasteSource::PrimarySelection)
],
[
MouseEventTriggerMods {
mods: Modifiers::SUPER,
mouse_reporting: false,
alt_screen: MouseEventAltScreen::Any,
},
MouseEventTrigger::Drag {
streak: 1,
button: MouseButton::Left,
},
StartWindowDrag
],
[
MouseEventTriggerMods {
mods: ctrl_shift,
mouse_reporting: false,
alt_screen: MouseEventAltScreen::Any,
},
MouseEventTrigger::Drag {
streak: 1,
button: MouseButton::Left,
},
StartWindowDrag
],
);
}
keys.default
.retain(|_, v| v.action != KeyAssignment::DisableDefaultAssignment);
mouse.retain(|_, v| *v != KeyAssignment::DisableDefaultAssignment);
// Expand MouseEventAltScreen::Any to individual True/False entries
let mut expanded_mouse = vec![];
for ((code, mods), v) in &mouse {
if mods.alt_screen == MouseEventAltScreen::Any {
let mods_true = MouseEventTriggerMods {
alt_screen: MouseEventAltScreen::True,
..*mods
};
let mods_false = MouseEventTriggerMods {
alt_screen: MouseEventAltScreen::False,
..*mods
};
expanded_mouse.push((code.clone(), mods_true, v.clone()));
expanded_mouse.push((code.clone(), mods_false, v.clone()));
}
}
// Eliminate ::Any
mouse.retain(|(_, mods), _| mods.alt_screen != MouseEventAltScreen::Any);
for (code, mods, v) in expanded_mouse {
mouse.insert((code, mods), v);
}
keys.by_name
.entry("copy_mode".to_string())
.or_insert_with(crate::overlay::copy::copy_key_table);
keys.by_name
.entry("search_mode".to_string())
.or_insert_with(crate::overlay::copy::search_key_table);
Self {
keys,
leader,
mouse,
}
}
pub fn is_leader(&self, key: &KeyCode, mods: Modifiers) -> Option<std::time::Duration> {
if let Some((leader_key, leader_mods, timeout)) = self.leader.as_ref() {
if *leader_key == *key && *leader_mods == mods.remove_positional_mods() {
return Some(timeout.clone());
}
}
None
}
pub fn has_table(&self, name: &str) -> bool {
self.keys.by_name.contains_key(name)
}
pub fn lookup_key(
&self,
key: &KeyCode,
mods: Modifiers,
table_name: Option<&str>,
) -> Option<KeyTableEntry> {
let table = match table_name {
Some(name) => self.keys.by_name.get(name)?,
None => &self.keys.default,
};
table
.get(&key.normalize_shift(mods.remove_positional_mods()))
.cloned()
}
pub fn lookup_mouse(
&self,
event: MouseEventTrigger,
mut mods: MouseEventTriggerMods,
) -> Option<KeyAssignment> {
mods.mods = mods.mods.remove_positional_mods();
self.mouse.get(&(event, mods)).cloned()
}
pub fn dump_config(&self, key_table: Option<&str>) {
println!("local wezterm = require 'wezterm'");
println!("local act = wezterm.action");
println!();
println!("return {{");
if key_table.is_none() {
println!(" keys = {{");
show_key_table_as_lua(&self.keys.default, 4);
println!(" }},");
println!();
}
let mut table_names = self.keys.by_name.keys().collect::<Vec<_>>();
table_names.sort();
println!(" key_tables = {{");
for name in table_names {
if let Some(wanted_table) = key_table {
if name != wanted_table {
continue;
}
}
if let Some(table) = self.keys.by_name.get(name) {
println!(" {name} = {{");
show_key_table_as_lua(table, 6);
println!(" }},");
println!();
}
}
println!(" }}");
println!("}}");
}
pub fn show_keys(&self) {
if let Some((key, mods, duration)) = &self.leader {
println!("Leader: {key:?} {mods:?} {duration:?}");
}
section_header("Default key table");
show_key_table(&self.keys.default);
println!();
let mut table_names = self.keys.by_name.keys().collect::<Vec<_>>();
table_names.sort();
for name in table_names {
if let Some(table) = self.keys.by_name.get(name) {
section_header(&format!("Key Table: {name}"));
show_key_table(table);
println!();
}
}
self.show_mouse();
}
fn show_mouse(&self) {
for (label, alt_screen, mouse_reporting) in [
("Mouse", MouseEventAltScreen::False, false),
("Mouse: alt_screen", MouseEventAltScreen::True, false),
("Mouse: mouse_reporting", MouseEventAltScreen::False, true),
(
"Mouse: mouse_reporting + alt_screen",
MouseEventAltScreen::True,
true,
),
] {
let ordered = self
.mouse
.iter()
.filter(|((_, m), _)| {
m.alt_screen == alt_screen && m.mouse_reporting == mouse_reporting
})
.collect::<BTreeMap<_, _>>();
if ordered.is_empty() {
continue;
}
section_header(label);
let mut trigger_width = 0;
let mut mod_width = 0;
for (trigger, mods) in ordered.keys() {
mod_width = mod_width.max(format!("{:?}", mods.mods).len());
trigger_width = trigger_width.max(format!("{trigger:?}").len());
}
for ((trigger, mods), action) in ordered {
let mods = if mods.mods == Modifiers::NONE {
String::new()
} else {
format!("{:?}", mods.mods)
};
let trigger = format!("{trigger:?}");
println!("\t{mods:mod_width$} {trigger:trigger_width$} -> {action:?}");
}
println!();
}
}
}
fn section_header(title: &str) {
let dash = "-".repeat(title.len());
println!("{title}");
println!("{dash}");
println!();
}
fn human_key(key: &KeyCode) -> String {
match key {
KeyCode::Char('\x1b') => "Escape".to_string(),
KeyCode::Char('\x7f') => "Escape".to_string(),
KeyCode::Char('\x08') => "Backspace".to_string(),
KeyCode::Char('\r') => "Enter".to_string(),
KeyCode::Char(' ') => "Space".to_string(),
KeyCode::Char('\t') => "Tab".to_string(),
KeyCode::Char(c) if c.is_ascii_control() => c.escape_debug().to_string(),
KeyCode::Char(c) => c.to_string(),
KeyCode::Function(n) => format!("F{n}"),
KeyCode::Numpad(n) => format!("Numpad{n}"),
KeyCode::Physical(phys) => format!("{} (Physical)", phys.to_string()),
_ => format!("{key:?}"),
}
}
fn lua_key_code(key: &KeyCode) -> String {
match key {
KeyCode::Char('\x1b') => "Escape".to_string(),
KeyCode::Char('\x7f') => "Escape".to_string(),
KeyCode::Char('\x08') => "Backspace".to_string(),
KeyCode::Char('\r') => "Enter".to_string(),
KeyCode::Char(' ') => "Space".to_string(),
KeyCode::Char('\t') => "Tab".to_string(),
KeyCode::Char(c) if c.is_ascii_control() => c.escape_debug().to_string(),
KeyCode::Char(c) => c.to_string(),
KeyCode::Function(n) => format!("F{n}"),
KeyCode::Numpad(n) => format!("Numpad{n}"),
KeyCode::Physical(phys) => format!("phys:{}", phys.to_string()),
_ => format!("{key:?}"),
}
}
fn luaify(value: Value, is_top: bool) -> String {
match value {
Value::String(s) if is_top => format!("act.{s}"),
Value::String(s) => quote_lua_string(&s),
Value::Bool(true) => "true".to_string(),
Value::Bool(false) => "false".to_string(),
Value::Null => "nil".to_string(),
Value::U64(u) => u.to_string(),
Value::F64(u) => u.to_string(),
Value::I64(u) => u.to_string(),
Value::Array(a) => {
format!("wat {a:?}")
}
Value::Object(o) if is_top => {
for (k, v) in o {
let k = match k {
Value::String(s) => s,
_ => unreachable!(),
};
let arg = match v {
Value::String(_) => format!(" {}", luaify(v, false)),
Value::Array(a) => {
let b: Vec<String> = a.into_iter().map(|v| luaify(v, false)).collect();
format!("{{ {} }}", b.join(", "))
}
Value::I64(i) => format!("({i})"),
Value::U64(i) => format!("({i})"),
Value::F64(i) => format!("({i})"),
_ => luaify(v, false),
};
return format!("act.{k}{arg}");
}
unreachable!()
}
Value::Object(o) => {
let mut fields = vec![];
for (k, v) in o {
let k = match k {
Value::String(s) => s,
_ => unreachable!(),
};
let arg = match v {
Value::Null => continue,
Value::String(_) => format!(" {}", luaify(v, false)),
Value::Array(a) => {
let b: Vec<String> = a.into_iter().map(|v| luaify(v, false)).collect();
format!("{{ {} }}", b.join(", "))
}
Value::I64(i) => format!("({i})"),
Value::U64(i) => format!("({i})"),
Value::F64(i) => format!("({i})"),
Value::Object(o) if o.is_empty() => continue,
_ => luaify(v, false),
};
fields.push(format!("{k} = {arg}"));
}
format!("{{ {} }}", fields.join(", "))
}
}
}
fn quote_lua_string(s: &str) -> String {
if s.contains('\'') && !s.contains('"') {
format!("\"{s}\"")
} else if s.contains('\'') {
format!("\"{}\"", s.escape_debug())
} else {
format!("'{s}'")
}
}
fn lua_key(key: &KeyCode, mods: Modifiers, action: &KeyAssignment) -> String {
let dyn_action = action.to_dynamic();
// println!(" -- {dyn_action:?}");
let action = luaify(dyn_action, true);
let key = lua_key_code(key);
let key = quote_lua_string(&key);
let mods = format!("{mods:?}").replace(" ", "");
format!("{{ key = {key}, mods = '{mods}', action = {action} }}")
}
fn show_key_table(table: &config::keyassignment::KeyTable) {
let ordered = table.iter().collect::<BTreeMap<_, _>>();
let mut key_width = 0;
let mut mod_width = 0;
for (key, mods) in ordered.keys() {
mod_width = mod_width.max(format!("{mods:?}").len());
key_width = key_width.max(human_key(key).len());
}
for ((key, mods), entry) in ordered {
let action = &entry.action;
let mods = if *mods == Modifiers::NONE {
String::new()
} else {
format!("{mods:?}")
};
let key = human_key(key);
println!("\t{mods:mod_width$} {key:key_width$} -> {action:?}");
}
}
fn show_key_table_as_lua(table: &config::keyassignment::KeyTable, indent: usize) {
let ordered = table.iter().collect::<BTreeMap<_, _>>();
let pad = " ".repeat(indent);
for ((key, mods), entry) in ordered {
let action = &entry.action;
println!("{pad}{},", lua_key(key, *mods, action));
}
}