1
1
mirror of https://github.com/wez/wezterm.git synced 2024-11-22 22:42:48 +03:00

Move default key assignments to gui layer, add more metadata

Create a list of command definitions to hold the default key
assignments.

That list has more metadata, such as a brief and longer human
readable description of the purpose, and allows for (in the
future) reasoning about the context where the command is valid,
as well as providing more information when rendering in the
launcher menu.

refs: https://github.com/wez/wezterm/issues/1485
This commit is contained in:
Wez Furlong 2022-04-09 16:20:59 -07:00
parent ae78c7d6c4
commit 1956054e6f
6 changed files with 896 additions and 447 deletions

View File

@ -1,12 +1,11 @@
use crate::de_notnan;
use crate::keys::KeyNoAction;
use crate::{de_notnan, ConfigHandle};
use luahelper::impl_lua_conversion;
use ordered_float::NotNan;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::convert::TryFrom;
use std::path::PathBuf;
use std::time::Duration;
use wezterm_input_types::{KeyCode, Modifiers};
use wezterm_term::input::MouseButton;
@ -27,6 +26,7 @@ bitflags::bitflags! {
const DOMAINS = 8;
const KEY_ASSIGNMENTS = 16;
const WORKSPACES = 32;
const COMMANDS = 64;
}
}
@ -57,6 +57,9 @@ impl ToString for LauncherFlags {
if self.contains(Self::WORKSPACES) {
s.push("WORKSPACES");
}
if self.contains(Self::COMMANDS) {
s.push("COMMANDS");
}
s.join("|")
}
}
@ -75,6 +78,7 @@ impl TryFrom<String> for LauncherFlags {
"DOMAINS" => flags |= Self::DOMAINS,
"KEY_ASSIGNMENTS" => flags |= Self::KEY_ASSIGNMENTS,
"WORKSPACES" => flags |= Self::WORKSPACES,
"COMMANDS" => flags |= Self::COMMANDS,
_ => {
return Err(format!("invalid LauncherFlags `{}` in `{}`", ele, s));
}
@ -373,446 +377,3 @@ pub struct KeyTables {
pub struct KeyTableEntry {
pub action: KeyAssignment,
}
pub struct InputMap {
pub keys: KeyTables,
pub mouse: HashMap<(MouseEventTrigger, Modifiers), KeyAssignment>,
leader: Option<(KeyCode, Modifiers, Duration)>,
}
impl InputMap {
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),
)
});
fn us_layout_shift(s: &str) -> String {
match s {
"0" => ")".to_string(),
"5" => "%".to_string(),
"[" => "{".to_string(),
"]" => "}".to_string(),
"=" => "+".to_string(),
"-" => "_".to_string(),
"'" => "\"".to_string(),
s if s.len() == 1 => s.to_ascii_uppercase(),
s => s.to_string(),
}
}
let ctrl_shift = Modifiers::CTRL | Modifiers::SHIFT;
macro_rules! k {
($([$mod:expr, $code:literal, $action:expr]),* $(,)?) => {
$(
let mut items = vec![];
// Blech. Depending on the OS, a shifted key combination
// such as CTRL-SHIFT-L may present as either:
// CTRL+SHIFT + mapped lowercase l
// CTRL+SHIFT + mapped uppercase l
// CTRL + mapped uppercase l
//
// This logic synthesizes the different combinations so
// that it isn't such a headache to maintain the mapping
// and prevents missing cases.
//
// Note that the mapped form of these things assumes
// US layout for some of the special shifted/punctuation cases.
// It's not perfect.
//
// The synthesis here requires that the defaults in
// the keymap below use the lowercase form of single characters!
let key = crate::DeferredKeyCode::try_from($code)
.unwrap()
.resolve(config.key_map_preference).clone();
let ukey = crate::DeferredKeyCode::try_from(us_layout_shift($code))
.unwrap()
.resolve(config.key_map_preference).clone();
items.push((key.clone(), $mod));
if $mod == Modifiers::SUPER {
// We want each SUPER/CMD version of the keys to also have
// CTRL+SHIFT version(s) for environments where SUPER/CMD
// is reserved for the window manager.
// This bit synthesizes those.
items.push((key.clone(), ctrl_shift));
if ukey != key {
items.push((ukey.clone(), ctrl_shift));
items.push((ukey.clone(), Modifiers::CTRL));
}
} else if $mod.contains(Modifiers::SHIFT) && ukey != key {
items.push((ukey.clone(), $mod));
items.push((ukey.clone(), $mod - Modifiers::SHIFT));
}
log::trace!("{:?} {:?} -> {:?}", $code, $mod, items);
for key in items {
keys.default.entry(key).or_insert(KeyTableEntry {
action: $action.clone()
});
}
)*
};
}
macro_rules! m {
($([$mod:expr, $code:expr, $action:expr]),* $(,)?) => {
$(
mouse.entry(($code, $mod)).or_insert($action);
)*
};
}
use KeyAssignment::*;
if !config.disable_default_key_bindings {
// Apply the default bindings; if the user has already mapped
// a given entry then that will take precedence.
k!(
// Clipboard
[
Modifiers::SHIFT,
"Insert",
PasteFrom(ClipboardPasteSource::PrimarySelection)
],
[
Modifiers::CTRL,
"Insert",
CopyTo(ClipboardCopyDestination::PrimarySelection)
],
[
Modifiers::SUPER,
"c",
CopyTo(ClipboardCopyDestination::Clipboard)
],
[
Modifiers::SUPER,
"v",
PasteFrom(ClipboardPasteSource::Clipboard)
],
[
Modifiers::NONE,
"Copy",
CopyTo(ClipboardCopyDestination::Clipboard)
],
[
Modifiers::NONE,
"Paste",
PasteFrom(ClipboardPasteSource::Clipboard)
],
// Window management
[Modifiers::ALT, "Return", ToggleFullScreen],
[Modifiers::SUPER, "m", Hide],
[Modifiers::SUPER, "n", SpawnWindow],
[
Modifiers::SUPER,
"k",
ClearScrollback(ScrollbackEraseMode::ScrollbackOnly)
],
[
Modifiers::SUPER,
"f",
Search(Pattern::CaseSensitiveString("".into()))
],
[ctrl_shift, "l", ShowDebugOverlay],
[ctrl_shift, "Space", QuickSelect],
// Font size manipulation
[Modifiers::SUPER, "-", DecreaseFontSize],
[Modifiers::SUPER, "0", ResetFontSize],
[Modifiers::SUPER, "=", IncreaseFontSize],
// Font size, CTRL variant.
[Modifiers::CTRL, "-", DecreaseFontSize],
[Modifiers::CTRL, "0", ResetFontSize],
[Modifiers::CTRL, "=", IncreaseFontSize],
// Tab navigation and management
[
Modifiers::SUPER,
"t",
SpawnTab(SpawnTabDomain::CurrentPaneDomain)
],
[Modifiers::SUPER, "1", ActivateTab(0)],
[Modifiers::SUPER, "2", ActivateTab(1)],
[Modifiers::SUPER, "3", ActivateTab(2)],
[Modifiers::SUPER, "4", ActivateTab(3)],
[Modifiers::SUPER, "5", ActivateTab(4)],
[Modifiers::SUPER, "6", ActivateTab(5)],
[Modifiers::SUPER, "7", ActivateTab(6)],
[Modifiers::SUPER, "8", ActivateTab(7)],
[Modifiers::SUPER, "9", ActivateTab(-1)],
[Modifiers::SUPER, "w", CloseCurrentTab { confirm: true }],
[
Modifiers::SUPER | Modifiers::SHIFT,
"[",
ActivateTabRelative(-1)
],
[ctrl_shift, "Tab", ActivateTabRelative(-1)],
[Modifiers::CTRL, "PageUp", ActivateTabRelative(-1)],
[
Modifiers::SUPER | Modifiers::SHIFT,
"]",
ActivateTabRelative(1)
],
[Modifiers::CTRL, "Tab", ActivateTabRelative(1)],
[Modifiers::CTRL, "PageDown", ActivateTabRelative(1)],
[Modifiers::SUPER, "r", ReloadConfiguration],
[ctrl_shift, "PageUp", MoveTabRelative(-1)],
[ctrl_shift, "PageDown", MoveTabRelative(1)],
[
Modifiers::SHIFT,
"PageUp",
ScrollByPage(NotNan::new(-1.0).unwrap())
],
[
Modifiers::SHIFT,
"PageDown",
ScrollByPage(NotNan::new(1.0).unwrap())
],
[ctrl_shift, "x", ActivateCopyMode],
[
Modifiers::CTRL | Modifiers::ALT | Modifiers::SHIFT,
"'",
SplitVertical(SpawnCommand {
domain: SpawnTabDomain::CurrentPaneDomain,
..Default::default()
})
],
[
Modifiers::CTRL | Modifiers::ALT | Modifiers::SHIFT,
"5",
SplitHorizontal(SpawnCommand {
domain: SpawnTabDomain::CurrentPaneDomain,
..Default::default()
})
],
[
Modifiers::CTRL | Modifiers::ALT | Modifiers::SHIFT,
"LeftArrow",
AdjustPaneSize(PaneDirection::Left, 1)
],
[
Modifiers::CTRL | Modifiers::ALT | Modifiers::SHIFT,
"RightArrow",
AdjustPaneSize(PaneDirection::Right, 1)
],
[
Modifiers::CTRL | Modifiers::ALT | Modifiers::SHIFT,
"UpArrow",
AdjustPaneSize(PaneDirection::Up, 1)
],
[
Modifiers::CTRL | Modifiers::ALT | Modifiers::SHIFT,
"DownArrow",
AdjustPaneSize(PaneDirection::Down, 1)
],
[
ctrl_shift,
"LeftArrow",
ActivatePaneDirection(PaneDirection::Left)
],
[
ctrl_shift,
"RightArrow",
ActivatePaneDirection(PaneDirection::Right)
],
[
ctrl_shift,
"UpArrow",
ActivatePaneDirection(PaneDirection::Up)
],
[
ctrl_shift,
"DownArrow",
ActivatePaneDirection(PaneDirection::Down)
],
[ctrl_shift, "z", TogglePaneZoomState],
);
#[cfg(target_os = "macos")]
k!(
[Modifiers::SUPER, "h", HideApplication],
[Modifiers::SUPER, "q", QuitApplication],
);
}
if !config.disable_default_mouse_bindings {
m!(
[
Modifiers::NONE,
MouseEventTrigger::Down {
streak: 3,
button: MouseButton::Left
},
SelectTextAtMouseCursor(SelectionMode::Line)
],
[
Modifiers::NONE,
MouseEventTrigger::Down {
streak: 2,
button: MouseButton::Left
},
SelectTextAtMouseCursor(SelectionMode::Word)
],
[
Modifiers::NONE,
MouseEventTrigger::Down {
streak: 1,
button: MouseButton::Left
},
SelectTextAtMouseCursor(SelectionMode::Cell)
],
[
Modifiers::SHIFT,
MouseEventTrigger::Down {
streak: 1,
button: MouseButton::Left
},
ExtendSelectionToMouseCursor(None)
],
[
Modifiers::SHIFT,
MouseEventTrigger::Up {
streak: 1,
button: MouseButton::Left
},
CompleteSelectionOrOpenLinkAtMouseCursor(
ClipboardCopyDestination::PrimarySelection
)
],
[
Modifiers::NONE,
MouseEventTrigger::Up {
streak: 1,
button: MouseButton::Left
},
CompleteSelectionOrOpenLinkAtMouseCursor(
ClipboardCopyDestination::PrimarySelection
)
],
[
Modifiers::NONE,
MouseEventTrigger::Up {
streak: 2,
button: MouseButton::Left
},
CompleteSelection(ClipboardCopyDestination::PrimarySelection)
],
[
Modifiers::NONE,
MouseEventTrigger::Up {
streak: 3,
button: MouseButton::Left
},
CompleteSelection(ClipboardCopyDestination::PrimarySelection)
],
[
Modifiers::NONE,
MouseEventTrigger::Drag {
streak: 1,
button: MouseButton::Left
},
ExtendSelectionToMouseCursor(Some(SelectionMode::Cell))
],
[
Modifiers::NONE,
MouseEventTrigger::Drag {
streak: 2,
button: MouseButton::Left
},
ExtendSelectionToMouseCursor(Some(SelectionMode::Word))
],
[
Modifiers::NONE,
MouseEventTrigger::Drag {
streak: 3,
button: MouseButton::Left
},
ExtendSelectionToMouseCursor(Some(SelectionMode::Line))
],
[
Modifiers::NONE,
MouseEventTrigger::Down {
streak: 1,
button: MouseButton::Middle
},
PasteFrom(ClipboardPasteSource::PrimarySelection)
],
[
Modifiers::SUPER,
MouseEventTrigger::Drag {
streak: 1,
button: MouseButton::Left,
},
StartWindowDrag
],
[
ctrl_shift,
MouseEventTrigger::Drag {
streak: 1,
button: MouseButton::Left,
},
StartWindowDrag
],
);
}
keys.default
.retain(|_, v| v.action != KeyAssignment::DisableDefaultAssignment);
mouse.retain(|_, v| *v != KeyAssignment::DisableDefaultAssignment);
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 {
return Some(timeout.clone());
}
}
None
}
fn remove_positional_alt(mods: Modifiers) -> Modifiers {
mods - (Modifiers::LEFT_ALT | Modifiers::RIGHT_ALT)
}
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(Self::remove_positional_alt(mods)))
.cloned()
}
pub fn lookup_mouse(&self, event: MouseEventTrigger, mods: Modifiers) -> Option<KeyAssignment> {
self.mouse
.get(&(event, Self::remove_positional_alt(mods)))
.cloned()
}
}

652
wezterm-gui/src/commands.rs Normal file
View File

@ -0,0 +1,652 @@
use config::keyassignment::*;
use config::{ConfigHandle, DeferredKeyCode};
use ordered_float::NotNan;
use std::borrow::Cow;
use std::convert::TryFrom;
use window::{KeyCode, Modifiers};
use KeyAssignment::*;
type ExpandFn = fn(&mut Expander);
/// Describes an argument/parameter/context that is required
/// in order for the command to have meaning.
/// The intent is for this to be used when filtering the items
/// that should be shown in eg: a context menu.
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum ArgType {
/// Operates on the active pane
ActivePane,
/// Operates on the active tab
ActiveTab,
/// Operates on the active window
ActiveWindow,
}
/// A helper function used to synthesize key binding permutations.
/// If the input is a character on a US ANSI keyboard layout, returns
/// the the typical character that is produced when holding down
/// the shift key and pressing the original key.
/// This doesn't produce an exhaustive list because there are only
/// a handful of default assignments in the command DEFS below.
fn us_layout_shift(s: &str) -> String {
match s {
"0" => ")".to_string(),
"5" => "%".to_string(),
"[" => "{".to_string(),
"]" => "}".to_string(),
"=" => "+".to_string(),
"-" => "_".to_string(),
"'" => "\"".to_string(),
s if s.len() == 1 => s.to_ascii_uppercase(),
s => s.to_string(),
}
}
/// `CommandDef` defines a command in the UI.
pub struct CommandDef {
/// Brief description
pub brief: &'static str,
/// A longer, more detailed, description
pub doc: &'static str,
/// A function that can produce 0 or more ExpandedCommand's.
/// The intent is that we can use this to dynamically populate
/// a list of commands for a given context.
pub exp: ExpandFn,
/// The key assignments associated with this command.
pub keys: &'static [(Modifiers, &'static str)],
/// The argument types/context in which this command is valid.
pub args: &'static [ArgType],
}
impl std::fmt::Debug for CommandDef {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
fmt.debug_struct("CommandDef")
.field("brief", &self.brief)
.field("doc", &self.doc)
.field("keys", &self.keys)
.field("args", &self.args)
.finish()
}
}
impl CommandDef {
/// Blech. Depending on the OS, a shifted key combination
/// such as CTRL-SHIFT-L may present as either:
/// CTRL+SHIFT + mapped lowercase l
/// CTRL+SHIFT + mapped uppercase l
/// CTRL + mapped uppercase l
///
/// This logic synthesizes the different combinations so
/// that it isn't such a headache to maintain the mapping
/// and prevents missing cases.
///
/// Note that the mapped form of these things assumes
/// US layout for some of the special shifted/punctuation cases.
/// It's not perfect.
///
/// The synthesis here requires that the defaults in
/// the keymap below use the lowercase form of single characters!
fn permute_keys(&self, config: &ConfigHandle) -> Vec<(Modifiers, KeyCode)> {
let mut keys = vec![];
for &(mods, label) in self.keys {
let key = DeferredKeyCode::try_from(label)
.unwrap()
.resolve(config.key_map_preference)
.clone();
let ukey = DeferredKeyCode::try_from(us_layout_shift(label))
.unwrap()
.resolve(config.key_map_preference)
.clone();
keys.push((mods, key.clone()));
if mods == Modifiers::SUPER {
// We want each SUPER/CMD version of the keys to also have
// CTRL+SHIFT version(s) for environments where SUPER/CMD
// is reserved for the window manager.
// This bit synthesizes those.
keys.push((Modifiers::CTRL | Modifiers::SHIFT, key.clone()));
if ukey != key {
keys.push((Modifiers::CTRL | Modifiers::SHIFT, ukey.clone()));
keys.push((Modifiers::CTRL, ukey.clone()));
}
} else if mods.contains(Modifiers::SHIFT) && ukey != key {
keys.push((mods, ukey.clone()));
keys.push((mods - Modifiers::SHIFT, ukey.clone()));
}
}
keys
}
/// Produces the list of default key assignments and actions.
/// Used by the InputMap.
pub fn default_key_assignments(
config: &ConfigHandle,
) -> Vec<(Modifiers, KeyCode, KeyAssignment)> {
let mut result = vec![];
for cmd in Self::expanded_commands(config) {
for (mods, code) in cmd.keys {
result.push((mods, code.clone(), cmd.action.clone()));
}
}
result
}
/// Produces the complete set of expanded commands.
pub fn expanded_commands(config: &ConfigHandle) -> Vec<ExpandedCommand> {
let mut result = vec![];
for def in DEFS {
let expander = Expander::new(def, config);
result.append(&mut expander.expand());
}
result
}
}
#[derive(Debug)]
pub struct ExpandedCommand {
pub brief: Cow<'static, str>,
pub doc: Cow<'static, str>,
pub action: KeyAssignment,
pub keys: Vec<(Modifiers, KeyCode)>,
}
#[derive(Debug)]
pub struct Expander {
template: &'static CommandDef,
commands: Vec<ExpandedCommand>,
config: ConfigHandle,
}
impl Expander {
pub fn push(&mut self, action: KeyAssignment) {
let expanded = ExpandedCommand {
brief: self.template.brief.into(),
doc: self.template.doc.into(),
keys: self.template.permute_keys(&self.config),
action,
};
self.commands.push(expanded);
}
pub fn new(template: &'static CommandDef, config: &ConfigHandle) -> Self {
Self {
template,
commands: vec![],
config: config.clone(),
}
}
pub fn expand(mut self) -> Vec<ExpandedCommand> {
(self.template.exp)(&mut self);
self.commands
}
}
static DEFS: &[CommandDef] = &[
CommandDef {
brief: "Paste primary selection",
doc: "Pastes text from the primary selection",
exp: |exp| exp.push(PasteFrom(ClipboardPasteSource::PrimarySelection)),
keys: &[(Modifiers::SHIFT, "Insert")],
args: &[ArgType::ActivePane],
},
CommandDef {
brief: "Copy to primary selection",
doc: "Copies text from the primary selection",
exp: |exp| {
exp.push(CopyTo(ClipboardCopyDestination::PrimarySelection));
},
keys: &[(Modifiers::CTRL, "Insert")],
args: &[ArgType::ActivePane],
},
CommandDef {
brief: "Copy to clipboard",
doc: "Copies text to the clipboard",
exp: |exp| exp.push(CopyTo(ClipboardCopyDestination::Clipboard)),
keys: &[(Modifiers::SUPER, "c"), (Modifiers::NONE, "Copy")],
args: &[ArgType::ActivePane],
},
CommandDef {
brief: "Paste from clipboard",
doc: "Pastes text from the clipboard",
exp: |exp| exp.push(PasteFrom(ClipboardPasteSource::Clipboard)),
keys: &[(Modifiers::SUPER, "v"), (Modifiers::NONE, "Paste")],
args: &[ArgType::ActivePane],
},
CommandDef {
brief: "Toggle full screen mode",
doc: "Switch between normal and full screen mode",
exp: |exp| {
exp.push(ToggleFullScreen);
},
keys: &[(Modifiers::ALT, "Return")],
args: &[ArgType::ActiveWindow],
},
CommandDef {
brief: "Hide/Minimize Window",
doc: "Hides/Mimimizes the current window",
exp: |exp| {
exp.push(Hide);
},
keys: &[(Modifiers::SUPER, "m")],
args: &[ArgType::ActiveWindow],
},
#[cfg(target_os = "macos")]
CommandDef {
brief: "Hide Application (macOS only)",
doc: "Hides all of the windows of the application. \
This is macOS specific.",
exp: |exp| {
exp.push(HideApplication);
},
keys: &[(Modifiers::SUPER, "h")],
args: &[],
},
#[cfg(target_os = "macos")]
CommandDef {
brief: "Quit WezTerm (macOS only)",
doc: "Quits WezTerm",
exp: |exp| {
exp.push(HideApplication);
},
keys: &[(Modifiers::SUPER, "h")],
args: &[],
},
CommandDef {
brief: "New Window",
doc: "Launches the default program into a new window",
exp: |exp| {
exp.push(SpawnWindow);
},
keys: &[(Modifiers::SUPER, "n")],
args: &[],
},
CommandDef {
brief: "Clear scrollback",
doc: "Clears any text that has scrolled out of the \
viewport of the current pane",
exp: |exp| {
exp.push(ClearScrollback(ScrollbackEraseMode::ScrollbackOnly));
},
keys: &[(Modifiers::SUPER, "k")],
args: &[ArgType::ActivePane],
},
CommandDef {
brief: "Search pane output",
doc: "Enters the search mode UI for the current pane",
exp: |exp| {
exp.push(Search(Pattern::CaseSensitiveString("".into())));
},
keys: &[(Modifiers::SUPER, "f")],
args: &[ArgType::ActivePane],
},
CommandDef {
brief: "Show debug overlay",
doc: "Activates the debug overlay and Lua REPL",
exp: |exp| {
exp.push(ShowDebugOverlay);
},
keys: &[(Modifiers::CTRL.union(Modifiers::SHIFT), "l")],
args: &[ArgType::ActiveWindow],
},
CommandDef {
brief: "Enter QuickSelect mode",
doc: "Activates the quick selection UI for the current pane",
exp: |exp| {
exp.push(QuickSelect);
},
keys: &[(Modifiers::CTRL.union(Modifiers::SHIFT), "Space")],
args: &[ArgType::ActivePane],
},
CommandDef {
brief: "Decrease font size",
doc: "Scales the font size smaller by 10%",
exp: |exp| {
exp.push(DecreaseFontSize);
},
keys: &[(Modifiers::SUPER, "-"), (Modifiers::CTRL, "-")],
args: &[ArgType::ActiveWindow],
},
CommandDef {
brief: "Reset font size",
doc: "Restores the font size to match your configuration file",
exp: |exp| {
exp.push(ResetFontSize);
},
keys: &[(Modifiers::SUPER, "0"), (Modifiers::CTRL, "0")],
args: &[ArgType::ActiveWindow],
},
CommandDef {
brief: "Increase font size",
doc: "Scales the font size larger by 10%",
exp: |exp| {
exp.push(IncreaseFontSize);
},
keys: &[(Modifiers::SUPER, "="), (Modifiers::CTRL, "=")],
args: &[ArgType::ActiveWindow],
},
CommandDef {
brief: "New Tab",
doc: "Create a new tab in the same domain as the current pane",
exp: |exp| {
exp.push(SpawnTab(SpawnTabDomain::CurrentPaneDomain));
},
keys: &[(Modifiers::SUPER, "t")],
args: &[ArgType::ActiveWindow],
},
CommandDef {
brief: "Activate 1st Tab",
doc: "Activates the left-most tab",
exp: |exp| {
exp.push(ActivateTab(0));
},
keys: &[(Modifiers::SUPER, "1")],
args: &[ArgType::ActiveWindow],
},
CommandDef {
brief: "Activate 2nd Tab",
doc: "Activates the 2nd tab from the left",
exp: |exp| {
exp.push(ActivateTab(1));
},
keys: &[(Modifiers::SUPER, "2")],
args: &[ArgType::ActiveWindow],
},
CommandDef {
brief: "Activate 3rd Tab",
doc: "Activates the 3rd tab from the left",
exp: |exp| {
exp.push(ActivateTab(2));
},
keys: &[(Modifiers::SUPER, "3")],
args: &[ArgType::ActiveWindow],
},
CommandDef {
brief: "Activate 4th Tab",
doc: "Activates the 4th tab from the left",
exp: |exp| {
exp.push(ActivateTab(3));
},
keys: &[(Modifiers::SUPER, "4")],
args: &[ArgType::ActiveWindow],
},
CommandDef {
brief: "Activate 5th Tab",
doc: "Activates the 5th tab from the left",
exp: |exp| {
exp.push(ActivateTab(4));
},
keys: &[(Modifiers::SUPER, "5")],
args: &[ArgType::ActiveWindow],
},
CommandDef {
brief: "Activate 6th Tab",
doc: "Activates the 6th tab from the left",
exp: |exp| {
exp.push(ActivateTab(5));
},
keys: &[(Modifiers::SUPER, "6")],
args: &[ArgType::ActiveWindow],
},
CommandDef {
brief: "Activate 7th Tab",
doc: "Activates the 7th tab from the left",
exp: |exp| {
exp.push(ActivateTab(6));
},
keys: &[(Modifiers::SUPER, "7")],
args: &[ArgType::ActiveWindow],
},
CommandDef {
brief: "Activate 8th Tab",
doc: "Activates the 8th tab from the left",
exp: |exp| {
exp.push(ActivateTab(7));
},
keys: &[(Modifiers::SUPER, "8")],
args: &[ArgType::ActiveWindow],
},
CommandDef {
brief: "Activate right-most tab",
doc: "Activates the tab on the far right",
exp: |exp| {
exp.push(ActivateTab(-1));
},
keys: &[(Modifiers::SUPER, "9")],
args: &[ArgType::ActiveWindow],
},
CommandDef {
brief: "Close current tab",
doc: "Closes the current tab, terminating all the \
processes that are running in its panes.",
exp: |exp| {
exp.push(CloseCurrentTab { confirm: true });
},
keys: &[(Modifiers::SUPER, "w")],
args: &[ArgType::ActiveTab],
},
CommandDef {
brief: "Activate the tab to the left",
doc: "Activates the tab to the left. If this is the left-most \
tab then cycles around and activates the right-most tab",
exp: |exp| {
exp.push(ActivateTabRelative(-1));
},
keys: &[
(Modifiers::SUPER.union(Modifiers::SHIFT), "["),
(Modifiers::CTRL.union(Modifiers::SHIFT), "Tab"),
(Modifiers::CTRL, "PageUp"),
],
args: &[ArgType::ActiveWindow],
},
CommandDef {
brief: "Activate the tab to the right",
doc: "Activates the tab to the right. If this is the right-most \
tab then cycles around and activates the left-most tab",
exp: |exp| {
exp.push(ActivateTabRelative(1));
},
keys: &[
(Modifiers::SUPER.union(Modifiers::SHIFT), "]"),
(Modifiers::CTRL, "Tab"),
(Modifiers::CTRL, "PageDown"),
],
args: &[ArgType::ActiveWindow],
},
CommandDef {
brief: "Reload configuration",
doc: "Reloads the configuration file",
exp: |exp| {
exp.push(ReloadConfiguration);
},
keys: &[(Modifiers::SUPER, "r")],
args: &[],
},
CommandDef {
brief: "Move tab one place to the left",
doc: "Rearranges the tabs so that the current tab moves \
one place to the left",
exp: |exp| {
exp.push(MoveTabRelative(-1));
},
keys: &[(Modifiers::SUPER.union(Modifiers::SHIFT), "PageUp")],
args: &[ArgType::ActiveTab],
},
CommandDef {
brief: "Move tab one place to the right",
doc: "Rearranges the tabs so that the current tab moves \
one place to the right",
exp: |exp| {
exp.push(MoveTabRelative(1));
},
keys: &[(Modifiers::SUPER.union(Modifiers::SHIFT), "PageDown")],
args: &[ArgType::ActiveTab],
},
CommandDef {
brief: "Scroll Up One Page",
doc: "Scrolls the viewport up by 1 page",
exp: |exp| exp.push(ScrollByPage(NotNan::new(-1.0).unwrap())),
keys: &[(Modifiers::SHIFT, "PageUp")],
args: &[ArgType::ActivePane],
},
CommandDef {
brief: "Scroll Down One Page",
doc: "Scrolls the viewport down by 1 page",
exp: |exp| exp.push(ScrollByPage(NotNan::new(1.0).unwrap())),
keys: &[(Modifiers::SHIFT, "PageUp")],
args: &[ArgType::ActivePane],
},
CommandDef {
brief: "Activate Copy Mode",
doc: "Enter mouse-less copy mode to select text using only \
the keyboard",
exp: |exp| {
exp.push(ActivateCopyMode);
},
keys: &[(Modifiers::SUPER.union(Modifiers::SHIFT), "x")],
args: &[ArgType::ActivePane],
},
CommandDef {
brief: "Split Vertically (Top/Bottom)",
doc: "Split the current pane vertically into two panes, by spawning \
the default program into the bottom half",
exp: |exp| {
exp.push(SplitVertical(SpawnCommand {
domain: SpawnTabDomain::CurrentPaneDomain,
..Default::default()
}));
},
keys: &[(
Modifiers::CTRL
.union(Modifiers::ALT)
.union(Modifiers::SHIFT),
"'",
)],
args: &[ArgType::ActivePane],
},
CommandDef {
brief: "Split Horizontally (Left/Right)",
doc: "Split the current pane horizontally into two panes, by spawning \
the default program into the right hand side",
exp: |exp| {
exp.push(SplitHorizontal(SpawnCommand {
domain: SpawnTabDomain::CurrentPaneDomain,
..Default::default()
}));
},
keys: &[(
Modifiers::CTRL
.union(Modifiers::ALT)
.union(Modifiers::SHIFT),
"5",
)],
args: &[ArgType::ActivePane],
},
CommandDef {
brief: "Adjust Pane Size to the Left",
doc: "Adjusts the closest split divider to the left",
exp: |exp| {
exp.push(AdjustPaneSize(PaneDirection::Left, 1));
},
keys: &[(
Modifiers::CTRL
.union(Modifiers::ALT)
.union(Modifiers::SHIFT),
"LeftArrow",
)],
args: &[ArgType::ActivePane],
},
CommandDef {
brief: "Adjust Pane Size to the Right",
doc: "Adjusts the closest split divider to the right",
exp: |exp| {
exp.push(AdjustPaneSize(PaneDirection::Right, 1));
},
keys: &[(
Modifiers::CTRL
.union(Modifiers::ALT)
.union(Modifiers::SHIFT),
"RightArrow",
)],
args: &[ArgType::ActivePane],
},
CommandDef {
brief: "Adjust Pane Size Upwards",
doc: "Adjusts the closest split divider towards the top",
exp: |exp| {
exp.push(AdjustPaneSize(PaneDirection::Up, 1));
},
keys: &[(
Modifiers::CTRL
.union(Modifiers::ALT)
.union(Modifiers::SHIFT),
"UpArrow",
)],
args: &[ArgType::ActivePane],
},
CommandDef {
brief: "Adjust Pane Size Downwards",
doc: "Adjusts the closest split divider towards the bottom",
exp: |exp| {
exp.push(AdjustPaneSize(PaneDirection::Down, 1));
},
keys: &[(
Modifiers::CTRL
.union(Modifiers::ALT)
.union(Modifiers::SHIFT),
"DownArrow",
)],
args: &[ArgType::ActivePane],
},
CommandDef {
brief: "Activate Pane Left",
doc: "Activates the pane to the left of the current pane",
exp: |exp| {
exp.push(ActivatePaneDirection(PaneDirection::Left));
},
keys: &[(Modifiers::CTRL.union(Modifiers::SHIFT), "LeftArrow")],
args: &[ArgType::ActivePane],
},
CommandDef {
brief: "Activate Pane Right",
doc: "Activates the pane to the right of the current pane",
exp: |exp| {
exp.push(ActivatePaneDirection(PaneDirection::Right));
},
keys: &[(Modifiers::CTRL.union(Modifiers::SHIFT), "RightArrow")],
args: &[ArgType::ActivePane],
},
CommandDef {
brief: "Activate Pane Up",
doc: "Activates the pane to the top of the current pane",
exp: |exp| {
exp.push(ActivatePaneDirection(PaneDirection::Up));
},
keys: &[(Modifiers::CTRL.union(Modifiers::SHIFT), "UpArrow")],
args: &[ArgType::ActivePane],
},
CommandDef {
brief: "Activate Pane Down",
doc: "Activates the pane to the bottom of the current pane",
exp: |exp| {
exp.push(ActivatePaneDirection(PaneDirection::Down));
},
keys: &[(Modifiers::CTRL.union(Modifiers::SHIFT), "DownArrow")],
args: &[ArgType::ActivePane],
},
CommandDef {
brief: "Toggle Pane Zoom",
doc: "Toggles the zoom state for the current pane",
exp: |exp| {
exp.push(TogglePaneZoomState);
},
keys: &[(Modifiers::CTRL.union(Modifiers::SHIFT), "z")],
args: &[ArgType::ActivePane],
},
];

222
wezterm-gui/src/inputmap.rs Normal file
View File

@ -0,0 +1,222 @@
use crate::commands::CommandDef;
use config::keyassignment::{
ClipboardCopyDestination, ClipboardPasteSource, KeyAssignment, KeyTableEntry, KeyTables,
MouseEventTrigger, SelectionMode,
};
use config::ConfigHandle;
use std::collections::HashMap;
use std::time::Duration;
use wezterm_term::input::MouseButton;
use window::{KeyCode, Modifiers};
pub struct InputMap {
pub keys: KeyTables,
pub mouse: HashMap<(MouseEventTrigger, Modifiers), KeyAssignment>,
leader: Option<(KeyCode, Modifiers, Duration)>,
}
impl InputMap {
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!(
[
Modifiers::NONE,
MouseEventTrigger::Down {
streak: 3,
button: MouseButton::Left
},
SelectTextAtMouseCursor(SelectionMode::Line)
],
[
Modifiers::NONE,
MouseEventTrigger::Down {
streak: 2,
button: MouseButton::Left
},
SelectTextAtMouseCursor(SelectionMode::Word)
],
[
Modifiers::NONE,
MouseEventTrigger::Down {
streak: 1,
button: MouseButton::Left
},
SelectTextAtMouseCursor(SelectionMode::Cell)
],
[
Modifiers::SHIFT,
MouseEventTrigger::Down {
streak: 1,
button: MouseButton::Left
},
ExtendSelectionToMouseCursor(None)
],
[
Modifiers::SHIFT,
MouseEventTrigger::Up {
streak: 1,
button: MouseButton::Left
},
CompleteSelectionOrOpenLinkAtMouseCursor(
ClipboardCopyDestination::PrimarySelection
)
],
[
Modifiers::NONE,
MouseEventTrigger::Up {
streak: 1,
button: MouseButton::Left
},
CompleteSelectionOrOpenLinkAtMouseCursor(
ClipboardCopyDestination::PrimarySelection
)
],
[
Modifiers::NONE,
MouseEventTrigger::Up {
streak: 2,
button: MouseButton::Left
},
CompleteSelection(ClipboardCopyDestination::PrimarySelection)
],
[
Modifiers::NONE,
MouseEventTrigger::Up {
streak: 3,
button: MouseButton::Left
},
CompleteSelection(ClipboardCopyDestination::PrimarySelection)
],
[
Modifiers::NONE,
MouseEventTrigger::Drag {
streak: 1,
button: MouseButton::Left
},
ExtendSelectionToMouseCursor(Some(SelectionMode::Cell))
],
[
Modifiers::NONE,
MouseEventTrigger::Drag {
streak: 2,
button: MouseButton::Left
},
ExtendSelectionToMouseCursor(Some(SelectionMode::Word))
],
[
Modifiers::NONE,
MouseEventTrigger::Drag {
streak: 3,
button: MouseButton::Left
},
ExtendSelectionToMouseCursor(Some(SelectionMode::Line))
],
[
Modifiers::NONE,
MouseEventTrigger::Down {
streak: 1,
button: MouseButton::Middle
},
PasteFrom(ClipboardPasteSource::PrimarySelection)
],
[
Modifiers::SUPER,
MouseEventTrigger::Drag {
streak: 1,
button: MouseButton::Left,
},
StartWindowDrag
],
[
ctrl_shift,
MouseEventTrigger::Drag {
streak: 1,
button: MouseButton::Left,
},
StartWindowDrag
],
);
}
keys.default
.retain(|_, v| v.action != KeyAssignment::DisableDefaultAssignment);
mouse.retain(|_, v| *v != KeyAssignment::DisableDefaultAssignment);
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 {
return Some(timeout.clone());
}
}
None
}
fn remove_positional_alt(mods: Modifiers) -> Modifiers {
mods - (Modifiers::LEFT_ALT | Modifiers::RIGHT_ALT)
}
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(Self::remove_positional_alt(mods)))
.cloned()
}
pub fn lookup_mouse(&self, event: MouseEventTrigger, mods: Modifiers) -> Option<KeyAssignment> {
self.mouse
.get(&(event, Self::remove_positional_alt(mods)))
.cloned()
}
}

View File

@ -26,10 +26,12 @@ use wezterm_toast_notification::*;
mod cache;
mod colorease;
mod commands;
mod customglyph;
mod download;
mod frontend;
mod glyphcache;
mod inputmap;
mod markdown;
mod overlay;
mod quad;

View File

@ -5,10 +5,11 @@
//! be rendered as a popup/context menu if the system supports it; at the
//! time of writing our window layer doesn't provide an API for context
//! menus.
use crate::inputmap::InputMap;
use crate::termwindow::TermWindowNotif;
use anyhow::anyhow;
use config::configuration;
use config::keyassignment::{InputMap, KeyAssignment, SpawnCommand, SpawnTabDomain};
use config::keyassignment::{KeyAssignment, SpawnCommand, SpawnTabDomain};
use config::lua::truncate_right;
use fuzzy_matcher::skim::SkimMatcherV2;
use fuzzy_matcher::FuzzyMatcher;
@ -332,6 +333,16 @@ impl LauncherState {
key_entries.sort_by(|a, b| a.label.cmp(&b.label));
self.entries.append(&mut key_entries);
}
if args.flags.contains(LauncherFlags::COMMANDS) {
let commands = crate::commands::CommandDef::expanded_commands(&config);
for cmd in commands {
self.entries.push(Entry {
label: format!("{}. {}", cmd.brief, cmd.doc),
kind: EntryKind::KeyAssignment(cmd.action),
});
}
}
}
fn render(&mut self, term: &mut TermWizTerminal) -> termwiz::Result<()> {

View File

@ -5,6 +5,7 @@ use crate::cache::LruCache;
use crate::colorease::ColorEase;
use crate::frontend::front_end;
use crate::glium::texture::SrgbTexture2d;
use crate::inputmap::InputMap;
use crate::overlay::{
confirm_close_pane, confirm_close_tab, confirm_close_window, confirm_quit_program, launcher,
start_overlay, start_overlay_pane, CopyOverlay, LauncherArgs, LauncherFlags,
@ -21,7 +22,7 @@ use ::wezterm_term::input::{ClickPosition, MouseButton as TMB};
use ::window::*;
use anyhow::{anyhow, ensure, Context};
use config::keyassignment::{
ClipboardCopyDestination, ClipboardPasteSource, InputMap, KeyAssignment, QuickSelectArguments,
ClipboardCopyDestination, ClipboardPasteSource, KeyAssignment, QuickSelectArguments,
SpawnCommand,
};
use config::{