1
1
mirror of https://github.com/wez/wezterm.git synced 2024-09-20 03:09:06 +03:00

wezterm: move most click related mouse events to InputMap

The click/movement related events are now "table driven" with
defaults contained in the InputMap object.

This gives the the potential to be configured from the config
file, but there is not glue in the config layer to enable
this yet.

This also does not include mouse wheel events.
This commit is contained in:
Wez Furlong 2020-05-21 18:51:20 -07:00
parent b18e66968d
commit b67682ddc9
4 changed files with 201 additions and 158 deletions

View File

@ -35,6 +35,7 @@ impl Selection {
self.start = Some(start);
}
#[allow(dead_code)]
pub fn is_empty(&self) -> bool {
self.range.is_none()
}

View File

@ -12,7 +12,9 @@ use crate::frontend::gui::overlay::{launcher, start_overlay, tab_navigator};
use crate::frontend::gui::scrollbar::*;
use crate::frontend::gui::selection::*;
use crate::frontend::gui::tabbar::{TabBarItem, TabBarState};
use crate::keyassignment::{KeyAssignment, KeyMap, SpawnCommand, SpawnTabDomain};
use crate::keyassignment::{
InputMap, KeyAssignment, MouseEventTrigger, SpawnCommand, SpawnTabDomain,
};
use crate::mux::domain::{DomainId, DomainState};
use crate::mux::renderable::{Renderable, RenderableDimensions, StableCursorPosition};
use crate::mux::tab::{Tab, TabId};
@ -147,7 +149,7 @@ pub struct TermWindow {
mux_window_id: MuxWindowId,
render_metrics: RenderMetrics,
render_state: RenderState,
keys: KeyMap,
input_map: InputMap,
show_tab_bar: bool,
show_scroll_bar: bool,
tab_bar: TabBarState,
@ -379,7 +381,7 @@ impl WindowCallbacks for TermWindow {
// user-defined key binding then we execute it and stop there.
if let Some(key) = &key.raw_key {
if let Key::Code(key) = self.win_key_code_to_termwiz_key_code(&key) {
if let Some(assignment) = self.keys.lookup(key, modifiers) {
if let Some(assignment) = self.input_map.lookup_key(key, modifiers) {
self.perform_key_assignment(&tab, &assignment).ok();
return true;
}
@ -399,7 +401,7 @@ impl WindowCallbacks for TermWindow {
let key = self.win_key_code_to_termwiz_key_code(&key.key);
match key {
Key::Code(key) => {
if let Some(assignment) = self.keys.lookup(key, modifiers) {
if let Some(assignment) = self.input_map.lookup_key(key, modifiers) {
self.perform_key_assignment(&tab, &assignment).ok();
true
} else if tab.key_down(key, modifiers).is_ok() {
@ -568,7 +570,7 @@ impl TermWindow {
dimensions,
terminal_size,
render_state,
keys: KeyMap::new(),
input_map: InputMap::new(),
show_tab_bar,
show_scroll_bar: config.enable_scroll_bar,
tab_bar: TabBarState::default(),
@ -874,7 +876,7 @@ impl TermWindow {
self.show_scroll_bar = config.enable_scroll_bar;
self.shape_cache.borrow_mut().clear();
self.keys = KeyMap::new();
self.input_map = InputMap::new();
let dimensions = self.dimensions;
let cell_dims = self.current_cell_dimensions();
self.apply_scale_change(&dimensions, self.fonts.get_font_scale());
@ -1440,13 +1442,32 @@ impl TermWindow {
// Ensure that we spawn the `open` call outside of the context
// of our window loop; on Windows it can cause a panic due to
// triggering our WndProc recursively.
let link = self.current_highlight.as_ref().unwrap().clone();
promise::spawn::spawn(async move {
log::error!("clicking {}", link.uri());
if let Err(err) = open::that(link.uri()) {
log::error!("failed to open {}: {:?}", link.uri(), err);
}
});
if let Some(link) = self.current_highlight.as_ref().cloned() {
promise::spawn::spawn(async move {
log::error!("clicking {}", link.uri());
if let Err(err) = open::that(link.uri()) {
log::error!("failed to open {}: {:?}", link.uri(), err);
}
});
}
}
CompleteSelectionOrOpenLinkAtMouseCursor => {
let text = self.selection_text(&tab);
if !text.is_empty() {
let window = self.window.as_ref().unwrap();
window.set_clipboard(text);
window.invalidate();
} else {
return self.perform_key_assignment(tab, &KeyAssignment::OpenLinkAtMouseCursor);
}
}
CompleteSelection => {
let text = self.selection_text(&tab);
if !text.is_empty() {
let window = self.window.as_ref().unwrap();
window.set_clipboard(text);
window.invalidate();
}
}
};
Ok(())
@ -2769,6 +2790,8 @@ impl TermWindow {
self.selection(tab.tab_id()).range = Some(selection_range);
}
}
self.window.as_ref().unwrap().invalidate();
}
fn select_text_at_mouse_cursor(&mut self, mode: SelectionMode, tab: &Rc<dyn Tab>) {
@ -2793,6 +2816,8 @@ impl TermWindow {
.begin(SelectionCoordinate { x, y });
}
}
self.window.as_ref().unwrap().invalidate();
}
fn mouse_event_terminal(
@ -2844,12 +2869,6 @@ impl TermWindow {
MouseCursor::Text
}));
enum MouseEventTrigger {
Down { streak: usize, button: TMB },
Drag { streak: usize, button: TMB },
Up { streak: usize, button: TMB },
}
let event_trigger_type = match &event.kind {
WMEK::Press(press) => {
let press = mouse_press_to_tmb(press);
@ -2878,10 +2897,14 @@ impl TermWindow {
WMEK::Move => {
if let Some(LastMouseClick { streak, button, .. }) = self.last_mouse_click.as_ref()
{
Some(MouseEventTrigger::Drag {
streak: *streak,
button: *button,
})
if Some(*button) == self.current_mouse_button.as_ref().map(mouse_press_to_tmb) {
Some(MouseEventTrigger::Drag {
streak: *streak,
button: *button,
})
} else {
None
}
} else {
None
}
@ -2889,138 +2912,29 @@ impl TermWindow {
WMEK::VertWheel(_) | WMEK::HorzWheel(_) => None,
};
if !tab.is_mouse_grabbed() || event.modifiers == Modifiers::SHIFT {
let ignore_grab_modifier = Modifiers::SHIFT;
if !tab.is_mouse_grabbed() || event.modifiers.contains(ignore_grab_modifier) {
let event_trigger_type = match event_trigger_type {
Some(ett) => ett,
None => return,
};
match event_trigger_type {
MouseEventTrigger::Down {
streak: 3,
button: TMB::Left,
} => {
self.perform_key_assignment(
&tab,
&KeyAssignment::SelectTextAtMouseCursor(SelectionMode::Line),
)
.ok();
context.invalidate();
}
MouseEventTrigger::Down {
streak: 2,
button: TMB::Left,
} => {
self.perform_key_assignment(
&tab,
&KeyAssignment::SelectTextAtMouseCursor(SelectionMode::Word),
)
.ok();
context.invalidate();
}
MouseEventTrigger::Down {
streak: 1,
button: TMB::Left,
} => {
// If the mouse is grabbed, do not use Shfit+Left to
// extend a selection, since otherwise it's hard to
// clear a selection.
if tab.is_mouse_grabbed()
|| !event.modifiers.contains(Modifiers::SHIFT)
|| self.selection(tab.tab_id()).is_empty()
{
// Initiate a selection
self.perform_key_assignment(
&tab,
&KeyAssignment::SelectTextAtMouseCursor(SelectionMode::Cell),
)
.ok();
} else {
// Extend selection
self.perform_key_assignment(
&tab,
&KeyAssignment::ExtendSelectionToMouseCursor(Some(SelectionMode::Cell)),
)
.ok();
}
context.invalidate();
}
let mut modifiers = window_mods_to_termwiz_mods(event.modifiers);
MouseEventTrigger::Up {
streak: 1,
button: TMB::Left,
} => {
let text = self.selection_text(&tab);
if text.is_empty() && self.current_highlight.is_some() {
self.perform_key_assignment(&tab, &KeyAssignment::OpenLinkAtMouseCursor)
.ok();
} else if !text.is_empty() {
self.window.as_ref().unwrap().set_clipboard(text);
context.invalidate();
}
}
MouseEventTrigger::Up {
streak,
button: TMB::Left,
} if streak > 1 => {
let text = self.selection_text(&tab);
self.window.as_ref().unwrap().set_clipboard(text);
context.invalidate();
}
MouseEventTrigger::Drag {
streak: 1,
button: TMB::Left,
} => {
if let Some(MousePress::Left) = self.current_mouse_button {
self.perform_key_assignment(
&tab,
&KeyAssignment::ExtendSelectionToMouseCursor(Some(SelectionMode::Cell)),
)
.ok();
context.invalidate();
}
}
MouseEventTrigger::Drag {
streak: 2,
button: TMB::Left,
} => {
if let Some(MousePress::Left) = self.current_mouse_button {
self.perform_key_assignment(
&tab,
&KeyAssignment::ExtendSelectionToMouseCursor(Some(SelectionMode::Word)),
)
.ok();
context.invalidate();
}
}
MouseEventTrigger::Drag {
streak: 3,
button: TMB::Left,
} => {
if let Some(MousePress::Left) = self.current_mouse_button {
self.perform_key_assignment(
&tab,
&KeyAssignment::ExtendSelectionToMouseCursor(Some(SelectionMode::Line)),
)
.ok();
context.invalidate();
}
}
MouseEventTrigger::Down {
streak: 1,
button: TMB::Middle,
} => {
self.perform_key_assignment(&tab, &KeyAssignment::Paste)
.ok();
return;
}
_ => {}
// Since we use shift to force assessing the mouse bindings, pretend
// that shift is not one of the mods when the mouse is grabbed.
if tab.is_mouse_grabbed() {
modifiers -= window_mods_to_termwiz_mods(ignore_grab_modifier);
}
if let Some(action) = self
.input_map
.lookup_mouse(event_trigger_type.clone(), modifiers)
{
self.perform_key_assignment(&tab, &action).ok();
}
return;
}

View File

@ -4,8 +4,23 @@ use crate::mux::domain::DomainId;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::PathBuf;
use term::input::MouseButton;
use term::{KeyCode, KeyModifiers};
/// A mouse event that can trigger an action
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, Hash)]
pub enum MouseEventTrigger {
/// Mouse button is pressed. streak is how many times in a row
/// it was pressed.
Down { streak: usize, button: MouseButton },
/// Mouse button is held down while the cursor is moving. streak is how many times in a row
/// it was pressed, with the last of those being held to form the drag.
Drag { streak: usize, button: MouseButton },
/// Mouse button is being released. streak is how many times
/// in a row it was pressed and released.
Up { streak: usize, button: MouseButton },
}
/// When spawning a tab, specify which domain should be used to
/// host/spawn that tab.
#[derive(Debug, Clone, Deserialize, Serialize)]
@ -86,21 +101,35 @@ pub enum KeyAssignment {
SelectTextAtMouseCursor(SelectionMode),
ExtendSelectionToMouseCursor(Option<SelectionMode>),
OpenLinkAtMouseCursor,
CompleteSelection,
CompleteSelectionOrOpenLinkAtMouseCursor,
}
impl_lua_conversion!(KeyAssignment);
pub struct KeyMap(HashMap<(KeyCode, KeyModifiers), KeyAssignment>);
pub struct InputMap {
keys: HashMap<(KeyCode, KeyModifiers), KeyAssignment>,
mouse: HashMap<(MouseEventTrigger, KeyModifiers), KeyAssignment>,
}
impl KeyMap {
impl InputMap {
pub fn new() -> Self {
let mut map = configuration()
let mut mouse = HashMap::new();
let mut keys = configuration()
.key_bindings()
.expect("keys section of config to be valid");
macro_rules! k {
($([$mod:expr, $code:expr, $action:expr]),* $(,)?) => {
$(
keys.entry(($code, $mod)).or_insert($action);
)*
};
};
macro_rules! m {
($([$mod:expr, $code:expr, $action:expr]),* $(,)?) => {
$(
map.entry(($code, $mod)).or_insert($action);
mouse.entry(($code, $mod)).or_insert($action);
)*
};
};
@ -111,7 +140,7 @@ impl KeyMap {
// Apply the default bindings; if the user has already mapped
// a given entry then that will take precedence.
m!(
k!(
// Clipboard
[KeyModifiers::SHIFT, KeyCode::Insert, Paste],
[KeyModifiers::SUPER, KeyCode::Char('c'), Copy],
@ -199,14 +228,113 @@ impl KeyMap {
);
#[cfg(target_os = "macos")]
m!([KeyModifiers::SUPER, KeyCode::Char('h'), HideApplication],);
k!([KeyModifiers::SUPER, KeyCode::Char('h'), HideApplication],);
Self(map)
m!(
[
KeyModifiers::NONE,
MouseEventTrigger::Down {
streak: 3,
button: MouseButton::Left
},
SelectTextAtMouseCursor(SelectionMode::Line)
],
[
KeyModifiers::NONE,
MouseEventTrigger::Down {
streak: 2,
button: MouseButton::Left
},
SelectTextAtMouseCursor(SelectionMode::Word)
],
[
KeyModifiers::NONE,
MouseEventTrigger::Down {
streak: 1,
button: MouseButton::Left
},
SelectTextAtMouseCursor(SelectionMode::Cell)
],
[
KeyModifiers::SHIFT,
MouseEventTrigger::Down {
streak: 1,
button: MouseButton::Left
},
ExtendSelectionToMouseCursor(None)
],
[
KeyModifiers::NONE,
MouseEventTrigger::Up {
streak: 1,
button: MouseButton::Left
},
CompleteSelectionOrOpenLinkAtMouseCursor
],
[
KeyModifiers::NONE,
MouseEventTrigger::Up {
streak: 2,
button: MouseButton::Left
},
CompleteSelection
],
[
KeyModifiers::NONE,
MouseEventTrigger::Up {
streak: 3,
button: MouseButton::Left
},
CompleteSelection
],
[
KeyModifiers::NONE,
MouseEventTrigger::Drag {
streak: 1,
button: MouseButton::Left
},
ExtendSelectionToMouseCursor(Some(SelectionMode::Cell))
],
[
KeyModifiers::NONE,
MouseEventTrigger::Drag {
streak: 2,
button: MouseButton::Left
},
ExtendSelectionToMouseCursor(Some(SelectionMode::Word))
],
[
KeyModifiers::NONE,
MouseEventTrigger::Drag {
streak: 3,
button: MouseButton::Left
},
ExtendSelectionToMouseCursor(Some(SelectionMode::Line))
],
[
KeyModifiers::NONE,
MouseEventTrigger::Down {
streak: 1,
button: MouseButton::Middle
},
Paste
],
);
Self { keys, mouse }
}
pub fn lookup(&self, key: KeyCode, mods: KeyModifiers) -> Option<KeyAssignment> {
self.0
pub fn lookup_key(&self, key: KeyCode, mods: KeyModifiers) -> Option<KeyAssignment> {
self.keys
.get(&(key.normalize_shift_to_upper_case(mods), mods))
.cloned()
}
pub fn lookup_mouse(
&self,
event: MouseEventTrigger,
mods: KeyModifiers,
) -> Option<KeyAssignment> {
self.mouse.get(&(event, mods)).cloned()
}
}

View File

@ -8,7 +8,7 @@ use std::time::{Duration, Instant};
pub use termwiz::input::KeyCode;
pub use termwiz::input::Modifiers as KeyModifiers;
#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize, Serialize, Hash)]
pub enum MouseButton {
Left,
Middle,