aboutsummaryrefslogtreecommitdiff
path: root/alacritty_terminal/src/input.rs
diff options
context:
space:
mode:
Diffstat (limited to 'alacritty_terminal/src/input.rs')
-rw-r--r--alacritty_terminal/src/input.rs1538
1 files changed, 0 insertions, 1538 deletions
diff --git a/alacritty_terminal/src/input.rs b/alacritty_terminal/src/input.rs
deleted file mode 100644
index e268bf62..00000000
--- a/alacritty_terminal/src/input.rs
+++ /dev/null
@@ -1,1538 +0,0 @@
-// Copyright 2016 Joe Wilm, The Alacritty Project Contributors
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-//! Handle input from glutin
-//!
-//! Certain key combinations should send some escape sequence back to the pty.
-//! In order to figure that out, state about which modifier keys are pressed
-//! needs to be tracked. Additionally, we need a bit of a state machine to
-//! determine what to do when a non-modifier key is pressed.
-use crate::url::Url;
-use std::borrow::Cow;
-use std::mem;
-use std::time::Instant;
-
-use glutin::{
- ElementState, KeyboardInput, ModifiersState, MouseButton, MouseCursor, MouseScrollDelta,
- TouchPhase, VirtualKeyCode,
-};
-
-use crate::ansi::{ClearMode, Handler};
-use crate::clipboard::ClipboardType;
-use crate::config::{self, Key};
-use crate::event::{ClickState, Mouse};
-use crate::grid::Scroll;
-use crate::index::{Column, Line, Point, Side};
-use crate::message_bar::{self, Message};
-use crate::term::mode::TermMode;
-use crate::term::{SizeInfo, Term};
-use crate::util::start_daemon;
-
-pub const FONT_SIZE_STEP: f32 = 0.5;
-
-/// Processes input from glutin.
-///
-/// An escape sequence may be emitted in case specific keys or key combinations
-/// are activated.
-pub struct Processor<'a, A: 'a> {
- pub key_bindings: &'a [KeyBinding],
- pub mouse_bindings: &'a [MouseBinding],
- pub mouse_config: &'a config::Mouse,
- pub scrolling_config: &'a config::Scrolling,
- pub ctx: A,
- pub save_to_clipboard: bool,
- pub alt_send_esc: bool,
-}
-
-pub trait ActionContext {
- fn write_to_pty<B: Into<Cow<'static, [u8]>>>(&mut self, _: B);
- fn size_info(&self) -> SizeInfo;
- fn copy_selection(&mut self, _: ClipboardType);
- fn clear_selection(&mut self);
- fn update_selection(&mut self, point: Point, side: Side);
- fn simple_selection(&mut self, point: Point, side: Side);
- fn block_selection(&mut self, point: Point, side: Side);
- fn semantic_selection(&mut self, point: Point);
- fn line_selection(&mut self, point: Point);
- fn selection_is_empty(&self) -> bool;
- fn mouse_mut(&mut self) -> &mut Mouse;
- fn mouse(&self) -> &Mouse;
- fn mouse_coords(&self) -> Option<Point>;
- fn received_count(&mut self) -> &mut usize;
- fn suppress_chars(&mut self) -> &mut bool;
- fn modifiers(&mut self) -> &mut Modifiers;
- fn scroll(&mut self, scroll: Scroll);
- fn hide_window(&mut self);
- fn terminal(&self) -> &Term;
- fn terminal_mut(&mut self) -> &mut Term;
- fn spawn_new_instance(&mut self);
- fn toggle_fullscreen(&mut self);
- #[cfg(target_os = "macos")]
- fn toggle_simple_fullscreen(&mut self);
-}
-
-#[derive(Debug, Default, Copy, Clone)]
-pub struct Modifiers {
- mods: ModifiersState,
- lshift: bool,
- rshift: bool,
-}
-
-impl Modifiers {
- pub fn update(&mut self, input: KeyboardInput) {
- match input.virtual_keycode {
- Some(VirtualKeyCode::LShift) => self.lshift = input.state == ElementState::Pressed,
- Some(VirtualKeyCode::RShift) => self.rshift = input.state == ElementState::Pressed,
- _ => (),
- }
-
- self.mods = input.modifiers;
- }
-
- pub fn shift(self) -> bool {
- self.lshift || self.rshift
- }
-
- pub fn ctrl(self) -> bool {
- self.mods.ctrl
- }
-
- pub fn logo(self) -> bool {
- self.mods.logo
- }
-
- pub fn alt(self) -> bool {
- self.mods.alt
- }
-}
-
-impl From<&mut Modifiers> for ModifiersState {
- fn from(mods: &mut Modifiers) -> ModifiersState {
- ModifiersState { shift: mods.shift(), ..mods.mods }
- }
-}
-
-/// Describes a state and action to take in that state
-///
-/// This is the shared component of `MouseBinding` and `KeyBinding`
-#[derive(Debug, Clone, PartialEq, Eq)]
-pub struct Binding<T> {
- /// Modifier keys required to activate binding
- pub mods: ModifiersState,
-
- /// String to send to pty if mods and mode match
- pub action: Action,
-
- /// Terminal mode required to activate binding
- pub mode: TermMode,
-
- /// excluded terminal modes where the binding won't be activated
- pub notmode: TermMode,
-
- /// This property is used as part of the trigger detection code.
- ///
- /// For example, this might be a key like "G", or a mouse button.
- pub trigger: T,
-}
-
-/// Bindings that are triggered by a keyboard key
-pub type KeyBinding = Binding<Key>;
-
-/// Bindings that are triggered by a mouse button
-pub type MouseBinding = Binding<MouseButton>;
-
-impl Default for KeyBinding {
- fn default() -> KeyBinding {
- KeyBinding {
- mods: Default::default(),
- action: Action::Esc(String::new()),
- mode: TermMode::NONE,
- notmode: TermMode::NONE,
- trigger: Key::A,
- }
- }
-}
-
-impl Default for MouseBinding {
- fn default() -> MouseBinding {
- MouseBinding {
- mods: Default::default(),
- action: Action::Esc(String::new()),
- mode: TermMode::NONE,
- notmode: TermMode::NONE,
- trigger: MouseButton::Left,
- }
- }
-}
-
-impl<T: Eq> Binding<T> {
- #[inline]
- fn is_triggered_by(
- &self,
- mode: TermMode,
- mods: ModifiersState,
- input: &T,
- relaxed: bool,
- ) -> bool {
- // Check input first since bindings are stored in one big list. This is
- // the most likely item to fail so prioritizing it here allows more
- // checks to be short circuited.
- self.trigger == *input
- && mode.contains(self.mode)
- && !mode.intersects(self.notmode)
- && self.mods_match(mods, relaxed)
- }
-
- #[inline]
- pub fn triggers_match(&self, binding: &Binding<T>) -> bool {
- // Check the binding's key and modifiers
- if self.trigger != binding.trigger || self.mods != binding.mods {
- return false;
- }
-
- // Completely empty modes match all modes
- if (self.mode.is_empty() && self.notmode.is_empty())
- || (binding.mode.is_empty() && binding.notmode.is_empty())
- {
- return true;
- }
-
- // Check for intersection (equality is required since empty does not intersect itself)
- (self.mode == binding.mode || self.mode.intersects(binding.mode))
- && (self.notmode == binding.notmode || self.notmode.intersects(binding.notmode))
- }
-}
-
-impl<T> Binding<T> {
- /// Execute the action associate with this binding
- #[inline]
- fn execute<A: ActionContext>(&self, ctx: &mut A, mouse_mode: bool) {
- self.action.execute(ctx, mouse_mode)
- }
-
- /// Check that two mods descriptions for equivalence
- #[inline]
- fn mods_match(&self, mods: ModifiersState, relaxed: bool) -> bool {
- if relaxed {
- self.mods.relaxed_eq(mods)
- } else {
- self.mods == mods
- }
- }
-}
-
-#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
-pub enum Action {
- /// Write an escape sequence.
- #[serde(skip)]
- Esc(String),
-
- /// Paste contents of system clipboard.
- Paste,
-
- /// Store current selection into clipboard.
- Copy,
-
- /// Paste contents of selection buffer.
- PasteSelection,
-
- /// Increase font size.
- IncreaseFontSize,
-
- /// Decrease font size.
- DecreaseFontSize,
-
- /// Reset font size to the config value.
- ResetFontSize,
-
- /// Scroll exactly one page up.
- ScrollPageUp,
-
- /// Scroll exactly one page down.
- ScrollPageDown,
-
- /// Scroll one line up.
- ScrollLineUp,
-
- /// Scroll one line down.
- ScrollLineDown,
-
- /// Scroll all the way to the top.
- ScrollToTop,
-
- /// Scroll all the way to the bottom.
- ScrollToBottom,
-
- /// Clear the display buffer(s) to remove history.
- ClearHistory,
-
- /// Run given command.
- #[serde(skip)]
- Command(String, Vec<String>),
-
- /// Hide the Alacritty window.
- Hide,
-
- /// Quit Alacritty.
- Quit,
-
- /// Clear warning and error notices.
- ClearLogNotice,
-
- /// Spawn a new instance of Alacritty.
- SpawnNewInstance,
-
- /// Toggle fullscreen.
- ToggleFullscreen,
-
- /// Toggle simple fullscreen on macos.
- #[cfg(target_os = "macos")]
- ToggleSimpleFullscreen,
-
- /// Allow receiving char input.
- ReceiveChar,
-
- /// No action.
- None,
-}
-
-impl Default for Action {
- fn default() -> Action {
- Action::None
- }
-}
-
-impl Action {
- #[inline]
- fn execute<A: ActionContext>(&self, ctx: &mut A, mouse_mode: bool) {
- match *self {
- Action::Esc(ref s) => {
- ctx.scroll(Scroll::Bottom);
- ctx.write_to_pty(s.clone().into_bytes())
- },
- Action::Copy => {
- ctx.copy_selection(ClipboardType::Clipboard);
- },
- Action::Paste => {
- let text = ctx.terminal_mut().clipboard().load(ClipboardType::Clipboard);
- self.paste(ctx, &text);
- },
- Action::PasteSelection => {
- // Only paste if mouse events are not captured by an application
- if !mouse_mode {
- let text = ctx.terminal_mut().clipboard().load(ClipboardType::Selection);
- self.paste(ctx, &text);
- }
- },
- Action::Command(ref program, ref args) => {
- trace!("Running command {} with args {:?}", program, args);
-
- match start_daemon(program, args) {
- Ok(_) => debug!("Spawned new proc"),
- Err(err) => warn!("Couldn't run command {}", err),
- }
- },
- Action::ToggleFullscreen => {
- ctx.toggle_fullscreen();
- },
- #[cfg(target_os = "macos")]
- Action::ToggleSimpleFullscreen => {
- ctx.toggle_simple_fullscreen();
- },
- Action::Hide => {
- ctx.hide_window();
- },
- Action::Quit => {
- ctx.terminal_mut().exit();
- },
- Action::IncreaseFontSize => {
- ctx.terminal_mut().change_font_size(FONT_SIZE_STEP);
- },
- Action::DecreaseFontSize => {
- ctx.terminal_mut().change_font_size(-FONT_SIZE_STEP);
- },
- Action::ResetFontSize => {
- ctx.terminal_mut().reset_font_size();
- },
- Action::ScrollPageUp => {
- ctx.scroll(Scroll::PageUp);
- },
- Action::ScrollPageDown => {
- ctx.scroll(Scroll::PageDown);
- },
- Action::ScrollLineUp => {
- ctx.scroll(Scroll::Lines(1));
- },
- Action::ScrollLineDown => {
- ctx.scroll(Scroll::Lines(-1));
- },
- Action::ScrollToTop => {
- ctx.scroll(Scroll::Top);
- },
- Action::ScrollToBottom => {
- ctx.scroll(Scroll::Bottom);
- },
- Action::ClearHistory => {
- ctx.terminal_mut().clear_screen(ClearMode::Saved);
- },
- Action::ClearLogNotice => {
- ctx.terminal_mut().message_buffer_mut().pop();
- },
- Action::SpawnNewInstance => {
- ctx.spawn_new_instance();
- },
- Action::ReceiveChar | Action::None => (),
- }
- }
-
- fn paste<A: ActionContext>(&self, ctx: &mut A, contents: &str) {
- if ctx.terminal().mode().contains(TermMode::BRACKETED_PASTE) {
- ctx.write_to_pty(&b"\x1b[200~"[..]);
- ctx.write_to_pty(contents.replace("\x1b", "").into_bytes());
- ctx.write_to_pty(&b"\x1b[201~"[..]);
- } else {
- // In non-bracketed (ie: normal) mode, terminal applications cannot distinguish
- // pasted data from keystrokes.
- // In theory, we should construct the keystrokes needed to produce the data we are
- // pasting... since that's neither practical nor sensible (and probably an impossible
- // task to solve in a general way), we'll just replace line breaks (windows and unix
- // style) with a single carriage return (\r, which is what the Enter key produces).
- ctx.write_to_pty(contents.replace("\r\n", "\r").replace("\n", "\r").into_bytes());
- }
- }
-}
-
-trait RelaxedEq<T: ?Sized = Self> {
- fn relaxed_eq(&self, other: T) -> bool;
-}
-
-impl RelaxedEq for ModifiersState {
- // Make sure that modifiers in the config are always present,
- // but ignore surplus modifiers.
- fn relaxed_eq(&self, other: Self) -> bool {
- (!self.logo || other.logo)
- && (!self.alt || other.alt)
- && (!self.ctrl || other.ctrl)
- && (!self.shift || other.shift)
- }
-}
-
-impl From<&'static str> for Action {
- fn from(s: &'static str) -> Action {
- Action::Esc(s.into())
- }
-}
-
-#[derive(Debug, Copy, Clone, PartialEq)]
-pub enum MouseState {
- Url(Url),
- MessageBar,
- MessageBarButton,
- Mouse,
- Text,
-}
-
-impl<'a, A: ActionContext + 'a> Processor<'a, A> {
- fn mouse_state(&mut self, point: Point, mods: ModifiersState) -> MouseState {
- let mouse_mode =
- TermMode::MOUSE_MOTION | TermMode::MOUSE_DRAG | TermMode::MOUSE_REPORT_CLICK;
-
- // Check message bar before URL to ignore URLs in the message bar
- if let Some(message) = self.message_at_point(Some(point)) {
- if self.message_close_at_point(point, message) {
- return MouseState::MessageBarButton;
- } else {
- return MouseState::MessageBar;
- }
- }
-
- // Check for URL at point with required modifiers held
- if self.mouse_config.url.mods().relaxed_eq(mods)
- && (!self.ctx.terminal().mode().intersects(mouse_mode) || mods.shift)
- && self.mouse_config.url.launcher.is_some()
- && self.ctx.selection_is_empty()
- && self.ctx.mouse().left_button_state != ElementState::Pressed
- {
- let buffer_point = self.ctx.terminal().visible_to_buffer(point);
- if let Some(url) =
- self.ctx.terminal().urls().drain(..).find(|url| url.contains(buffer_point))
- {
- return MouseState::Url(url);
- }
- }
-
- if self.ctx.terminal().mode().intersects(mouse_mode) && !self.ctx.modifiers().shift() {
- MouseState::Mouse
- } else {
- MouseState::Text
- }
- }
-
- #[inline]
- pub fn mouse_moved(&mut self, x: usize, y: usize, modifiers: ModifiersState) {
- self.ctx.mouse_mut().x = x;
- self.ctx.mouse_mut().y = y;
-
- let size_info = self.ctx.size_info();
- let point = size_info.pixels_to_coords(x, y);
-
- let cell_side = self.get_mouse_side();
- let prev_side = mem::replace(&mut self.ctx.mouse_mut().cell_side, cell_side);
- let prev_line = mem::replace(&mut self.ctx.mouse_mut().line, point.line);
- let prev_col = mem::replace(&mut self.ctx.mouse_mut().column, point.col);
-
- let motion_mode = TermMode::MOUSE_MOTION | TermMode::MOUSE_DRAG;
- let report_mode = TermMode::MOUSE_REPORT_CLICK | motion_mode;
-
- let cell_changed =
- prev_line != self.ctx.mouse().line || prev_col != self.ctx.mouse().column;
-
- // If the mouse hasn't changed cells, do nothing
- if !cell_changed && prev_side == cell_side {
- return;
- }
-
- // Don't launch URLs if mouse has moved
- self.ctx.mouse_mut().block_url_launcher = true;
-
- let mouse_state = self.mouse_state(point, modifiers);
- self.update_mouse_cursor(mouse_state);
- match mouse_state {
- MouseState::Url(url) => {
- let url_bounds = url.linear_bounds(self.ctx.terminal());
- self.ctx.terminal_mut().set_url_highlight(url_bounds);
- },
- MouseState::MessageBar | MouseState::MessageBarButton => {
- self.ctx.terminal_mut().reset_url_highlight();
- return;
- },
- _ => self.ctx.terminal_mut().reset_url_highlight(),
- }
-
- if self.ctx.mouse().left_button_state == ElementState::Pressed
- && (modifiers.shift || !self.ctx.terminal().mode().intersects(report_mode))
- {
- self.ctx.update_selection(Point { line: point.line, col: point.col }, cell_side);
- } else if self.ctx.terminal().mode().intersects(motion_mode)
- && size_info.contains_point(x, y, false)
- && cell_changed
- {
- if self.ctx.mouse().left_button_state == ElementState::Pressed {
- self.mouse_report(32, ElementState::Pressed, modifiers);
- } else if self.ctx.mouse().middle_button_state == ElementState::Pressed {
- self.mouse_report(33, ElementState::Pressed, modifiers);
- } else if self.ctx.mouse().right_button_state == ElementState::Pressed {
- self.mouse_report(34, ElementState::Pressed, modifiers);
- } else if self.ctx.terminal().mode().contains(TermMode::MOUSE_MOTION) {
- self.mouse_report(35, ElementState::Pressed, modifiers);
- }
- }
- }
-
- fn get_mouse_side(&self) -> Side {
- let size_info = self.ctx.size_info();
- let x = self.ctx.mouse().x;
-
- let cell_x = x.saturating_sub(size_info.padding_x as usize) % size_info.cell_width as usize;
- let half_cell_width = (size_info.cell_width / 2.0) as usize;
-
- let additional_padding =
- (size_info.width - size_info.padding_x * 2.) % size_info.cell_width;
- let end_of_grid = size_info.width - size_info.padding_x - additional_padding;
-
- if cell_x > half_cell_width
- // Edge case when mouse leaves the window
- || x as f32 >= end_of_grid
- {
- Side::Right
- } else {
- Side::Left
- }
- }
-
- pub fn normal_mouse_report(&mut self, button: u8) {
- let (line, column) = (self.ctx.mouse().line, self.ctx.mouse().column);
-
- if line < Line(223) && column < Column(223) {
- let msg = vec![
- b'\x1b',
- b'[',
- b'M',
- 32 + button,
- 32 + 1 + column.0 as u8,
- 32 + 1 + line.0 as u8,
- ];
-
- self.ctx.write_to_pty(msg);
- }
- }
-
- pub fn sgr_mouse_report(&mut self, button: u8, state: ElementState) {
- let (line, column) = (self.ctx.mouse().line, self.ctx.mouse().column);
- let c = match state {
- ElementState::Pressed => 'M',
- ElementState::Released => 'm',
- };
-
- let msg = format!("\x1b[<{};{};{}{}", button, column + 1, line + 1, c);
- self.ctx.write_to_pty(msg.into_bytes());
- }
-
- pub fn mouse_report(&mut self, button: u8, state: ElementState, modifiers: ModifiersState) {
- // Calculate modifiers value
- let mut mods = 0;
- if modifiers.shift {
- mods += 4;
- }
- if modifiers.alt {
- mods += 8;
- }
- if modifiers.ctrl {
- mods += 16;
- }
-
- // Report mouse events
- if self.ctx.terminal().mode().contains(TermMode::SGR_MOUSE) {
- self.sgr_mouse_report(button + mods, state);
- } else if let ElementState::Released = state {
- self.normal_mouse_report(3 + mods);
- } else {
- self.normal_mouse_report(button + mods);
- }
- }
-
- pub fn on_mouse_double_click(&mut self, button: MouseButton, point: Option<Point>) {
- if let (Some(point), true) = (point, button == MouseButton::Left) {
- self.ctx.semantic_selection(point);
- }
- }
-
- pub fn on_mouse_triple_click(&mut self, button: MouseButton, point: Option<Point>) {
- if let (Some(point), true) = (point, button == MouseButton::Left) {
- self.ctx.line_selection(point);
- }
- }
-
- pub fn on_mouse_press(
- &mut self,
- button: MouseButton,
- modifiers: ModifiersState,
- point: Option<Point>,
- ) {
- let now = Instant::now();
- let elapsed = self.ctx.mouse().last_click_timestamp.elapsed();
- self.ctx.mouse_mut().last_click_timestamp = now;
-
- let button_changed = self.ctx.mouse().last_button != button;
-
- self.ctx.mouse_mut().click_state = match self.ctx.mouse().click_state {
- ClickState::Click
- if !button_changed && elapsed < self.mouse_config.double_click.threshold =>
- {
- self.ctx.mouse_mut().block_url_launcher = true;
- self.on_mouse_double_click(button, point);
- ClickState::DoubleClick
- }
- ClickState::DoubleClick
- if !button_changed && elapsed < self.mouse_config.triple_click.threshold =>
- {
- self.ctx.mouse_mut().block_url_launcher = true;
- self.on_mouse_triple_click(button, point);
- ClickState::TripleClick
- }
- _ => {
- // Don't launch URLs if this click cleared the selection
- self.ctx.mouse_mut().block_url_launcher = !self.ctx.selection_is_empty();
-
- self.ctx.clear_selection();
-
- // Start new empty selection
- let side = self.ctx.mouse().cell_side;
- if let Some(point) = point {
- if modifiers.ctrl {
- self.ctx.block_selection(point, side);
- } else {
- self.ctx.simple_selection(point, side);
- }
- }
-
- let report_modes =
- TermMode::MOUSE_REPORT_CLICK | TermMode::MOUSE_DRAG | TermMode::MOUSE_MOTION;
- if !modifiers.shift && self.ctx.terminal().mode().intersects(report_modes) {
- let code = match button {
- MouseButton::Left => 0,
- MouseButton::Middle => 1,
- MouseButton::Right => 2,
- // Can't properly report more than three buttons.
- MouseButton::Other(_) => return,
- };
- self.mouse_report(code, ElementState::Pressed, modifiers);
- return;
- }
-
- ClickState::Click
- },
- };
- }
-
- pub fn on_mouse_release(
- &mut self,
- button: MouseButton,
- modifiers: ModifiersState,
- point: Option<Point>,
- ) {
- let report_modes =
- TermMode::MOUSE_REPORT_CLICK | TermMode::MOUSE_DRAG | TermMode::MOUSE_MOTION;
- if !modifiers.shift && self.ctx.terminal().mode().intersects(report_modes) {
- let code = match button {
- MouseButton::Left => 0,
- MouseButton::Middle => 1,
- MouseButton::Right => 2,
- // Can't properly report more than three buttons.
- MouseButton::Other(_) => return,
- };
- self.mouse_report(code, ElementState::Released, modifiers);
- return;
- } else if let (Some(point), true) = (point, button == MouseButton::Left) {
- let mouse_state = self.mouse_state(point, modifiers);
- self.update_mouse_cursor(mouse_state);
- if let MouseState::Url(url) = mouse_state {
- let url_bounds = url.linear_bounds(self.ctx.terminal());
- self.ctx.terminal_mut().set_url_highlight(url_bounds);
- self.launch_url(url);
- }
- }
-
- self.copy_selection();
- }
-
- /// Spawn URL launcher when clicking on URLs.
- fn launch_url(&self, url: Url) {
- if self.ctx.mouse().block_url_launcher {
- return;
- }
-
- if let Some(ref launcher) = self.mouse_config.url.launcher {
- let mut args = launcher.args().to_vec();
- args.push(self.ctx.terminal().url_to_string(url));
-
- match start_daemon(launcher.program(), &args) {
- Ok(_) => debug!("Launched {} with args {:?}", launcher.program(), args),
- Err(_) => warn!("Unable to launch {} with args {:?}", launcher.program(), args),
- }
- }
- }
-
- pub fn on_mouse_wheel(
- &mut self,
- delta: MouseScrollDelta,
- phase: TouchPhase,
- modifiers: ModifiersState,
- ) {
- match delta {
- MouseScrollDelta::LineDelta(_columns, lines) => {
- let new_scroll_px = lines * self.ctx.size_info().cell_height;
- self.scroll_terminal(modifiers, new_scroll_px as i32);
- },
- MouseScrollDelta::PixelDelta(lpos) => {
- match phase {
- TouchPhase::Started => {
- // Reset offset to zero
- self.ctx.mouse_mut().scroll_px = 0;
- },
- TouchPhase::Moved => {
- self.scroll_terminal(modifiers, lpos.y as i32);
- },
- _ => (),
- }
- },
- }
- }
-
- fn scroll_terminal(&mut self, modifiers: ModifiersState, new_scroll_px: i32) {
- let mouse_modes =
- TermMode::MOUSE_REPORT_CLICK | TermMode::MOUSE_DRAG | TermMode::MOUSE_MOTION;
- let height = self.ctx.size_info().cell_height as i32;
-
- // Make sure the new and deprecated setting are both allowed
- let faux_multiplier = self.scrolling_config.faux_multiplier() as usize;
-
- if self.ctx.terminal().mode().intersects(mouse_modes) {
- self.ctx.mouse_mut().scroll_px += new_scroll_px;
-
- let code = if new_scroll_px > 0 { 64 } else { 65 };
- let lines = (self.ctx.mouse().scroll_px / height).abs();
-
- for _ in 0..lines {
- self.mouse_report(code, ElementState::Pressed, modifiers);
- }
- } else if self.ctx.terminal().mode().contains(TermMode::ALT_SCREEN)
- && faux_multiplier > 0
- && !modifiers.shift
- {
- self.ctx.mouse_mut().scroll_px += new_scroll_px * faux_multiplier as i32;
-
- let cmd = if new_scroll_px > 0 { b'A' } else { b'B' };
- let lines = (self.ctx.mouse().scroll_px / height).abs();
-
- let mut content = Vec::with_capacity(lines as usize * 3);
- for _ in 0..lines {
- content.push(0x1b);
- content.push(b'O');
- content.push(cmd);
- }
- self.ctx.write_to_pty(content);
- } else {
- let multiplier = i32::from(self.scrolling_config.multiplier());
- self.ctx.mouse_mut().scroll_px += new_scroll_px * multiplier;
-
- let lines = self.ctx.mouse().scroll_px / height;
-
- self.ctx.scroll(Scroll::Lines(lines as isize));
- }
-
- self.ctx.mouse_mut().scroll_px %= height;
- }
-
- pub fn on_focus_change(&mut self, is_focused: bool) {
- if self.ctx.terminal().mode().contains(TermMode::FOCUS_IN_OUT) {
- let chr = if is_focused { "I" } else { "O" };
-
- let msg = format!("\x1b[{}", chr);
- self.ctx.write_to_pty(msg.into_bytes());
- }
- }
-
- pub fn mouse_input(
- &mut self,
- state: ElementState,
- button: MouseButton,
- modifiers: ModifiersState,
- ) {
- match button {
- MouseButton::Left => self.ctx.mouse_mut().left_button_state = state,
- MouseButton::Middle => self.ctx.mouse_mut().middle_button_state = state,
- MouseButton::Right => self.ctx.mouse_mut().right_button_state = state,
- _ => (),
- }
-
- let point = self.ctx.mouse_coords();
-
- // Skip normal mouse events if the message bar has been clicked
- if let Some(message) = self.message_at_point(point) {
- // Message should never be `Some` if point is `None`
- debug_assert!(point.is_some());
- self.on_message_bar_click(state, point.unwrap(), message, modifiers);
- } else {
- match state {
- ElementState::Pressed => {
- self.process_mouse_bindings(modifiers, button);
- self.on_mouse_press(button, modifiers, point);
- },
- ElementState::Released => self.on_mouse_release(button, modifiers, point),
- }
- }
-
- self.ctx.mouse_mut().last_button = button;
- }
-
- /// Process key input.
- pub fn process_key(&mut self, input: KeyboardInput) {
- self.ctx.modifiers().update(input);
-
- // Update mouse cursor for temporarily disabling mouse mode
- if input.virtual_keycode == Some(VirtualKeyCode::LShift)
- || input.virtual_keycode == Some(VirtualKeyCode::RShift)
- {
- if let Some(point) = self.ctx.mouse_coords() {
- let mods = self.ctx.modifiers().into();
- let mouse_state = self.mouse_state(point, mods);
- self.update_mouse_cursor(mouse_state);
- }
- }
-
- match input.state {
- ElementState::Pressed => {
- *self.ctx.received_count() = 0;
- self.process_key_bindings(input);
- },
- ElementState::Released => *self.ctx.suppress_chars() = false,
- }
- }
-
- /// Process a received character.
- pub fn received_char(&mut self, c: char) {
- if *self.ctx.suppress_chars() {
- return;
- }
-
- self.ctx.scroll(Scroll::Bottom);
- self.ctx.clear_selection();
-
- let utf8_len = c.len_utf8();
- let mut bytes = Vec::with_capacity(utf8_len);
- unsafe {
- bytes.set_len(utf8_len);
- c.encode_utf8(&mut bytes[..]);
- }
-
- if self.alt_send_esc
- && *self.ctx.received_count() == 0
- && self.ctx.modifiers().alt()
- && utf8_len == 1
- {
- bytes.insert(0, b'\x1b');
- }
-
- self.ctx.write_to_pty(bytes);
-
- *self.ctx.received_count() += 1;
- }
-
- /// Attempt to find a binding and execute its action.
- ///
- /// The provided mode, mods, and key must match what is allowed by a binding
- /// for its action to be executed.
- fn process_key_bindings(&mut self, input: KeyboardInput) {
- let mode = *self.ctx.terminal().mode();
-
- *self.ctx.suppress_chars() = self
- .key_bindings
- .iter()
- .filter(|binding| {
- let key = match (binding.trigger, input.virtual_keycode) {
- (Key::Scancode(_), _) => Key::Scancode(input.scancode),
- (_, Some(key)) => Key::from_glutin_input(key),
- _ => return false,
- };
-
- binding.is_triggered_by(mode, input.modifiers, &key, false)
- })
- .fold(None, |suppress_chars, binding| {
- // Binding was triggered; run the action
- binding.execute(&mut self.ctx, false);
-
- // Don't suppress when there has been a `ReceiveChar` action
- Some(suppress_chars.unwrap_or(true) && binding.action != Action::ReceiveChar)
- })
- // Don't suppress char if no bindings were triggered
- .unwrap_or(false);
- }
-
- /// Attempt to find a binding and execute its action.
- ///
- /// The provided mode, mods, and key must match what is allowed by a binding
- /// for its action to be executed.
- fn process_mouse_bindings(&mut self, mods: ModifiersState, button: MouseButton) {
- for binding in self.mouse_bindings {
- if binding.is_triggered_by(*self.ctx.terminal().mode(), mods, &button, true) {
- // binding was triggered; run the action
- let mouse_mode = !mods.shift
- && self.ctx.terminal().mode().intersects(
- TermMode::MOUSE_REPORT_CLICK
- | TermMode::MOUSE_DRAG
- | TermMode::MOUSE_MOTION,
- );
- binding.execute(&mut self.ctx, mouse_mode);
- }
- }
- }
-
- /// Return the message bar's message if there is some at the specified point
- fn message_at_point(&mut self, point: Option<Point>) -> Option<Message> {
- if let (Some(point), Some(message)) =
- (point, self.ctx.terminal_mut().message_buffer_mut().message())
- {
- let size = self.ctx.size_info();
- if point.line.0 >= size.lines().saturating_sub(message.text(&size).len()) {
- return Some(message);
- }
- }
-
- None
- }
-
- /// Whether the point is over the message bar's close button
- fn message_close_at_point(&self, point: Point, message: Message) -> bool {
- let size = self.ctx.size_info();
- point.col + message_bar::CLOSE_BUTTON_TEXT.len() >= size.cols()
- && point.line == size.lines() - message.text(&size).len()
- }
-
- /// Handle clicks on the message bar.
- fn on_message_bar_click(
- &mut self,
- button_state: ElementState,
- point: Point,
- message: Message,
- mods: ModifiersState,
- ) {
- match button_state {
- ElementState::Released => self.copy_selection(),
- ElementState::Pressed => {
- if self.message_close_at_point(point, message) {
- let mouse_state = self.mouse_state(point, mods);
- self.update_mouse_cursor(mouse_state);
- self.ctx.terminal_mut().message_buffer_mut().pop();
- }
-
- self.ctx.clear_selection();
- },
- }
- }
-
- /// Copy text selection.
- fn copy_selection(&mut self) {
- if self.save_to_clipboard {
- self.ctx.copy_selection(ClipboardType::Clipboard);
- }
- self.ctx.copy_selection(ClipboardType::Selection);
- }
-
- #[inline]
- fn update_mouse_cursor(&mut self, mouse_state: MouseState) {
- let mouse_cursor = match mouse_state {
- MouseState::Url(_) | MouseState::MessageBarButton => MouseCursor::Hand,
- MouseState::Text => MouseCursor::Text,
- _ => MouseCursor::Default,
- };
-
- self.ctx.terminal_mut().set_mouse_cursor(mouse_cursor);
- }
-}
-
-#[cfg(test)]
-mod tests {
- use std::borrow::Cow;
- use std::time::Duration;
-
- use glutin::{ElementState, Event, ModifiersState, MouseButton, VirtualKeyCode, WindowEvent};
-
- use crate::clipboard::{Clipboard, ClipboardType};
- use crate::config::{self, ClickHandler, Config};
- use crate::event::{ClickState, Mouse, WindowChanges};
- use crate::grid::Scroll;
- use crate::index::{Point, Side};
- use crate::message_bar::MessageBuffer;
- use crate::selection::Selection;
- use crate::term::{SizeInfo, Term, TermMode};
-
- use super::{Action, Binding, Modifiers, Processor};
-
- const KEY: VirtualKeyCode = VirtualKeyCode::Key0;
-
- type MockBinding = Binding<usize>;
-
- impl Default for MockBinding {
- fn default() -> Self {
- Self {
- mods: Default::default(),
- action: Default::default(),
- mode: TermMode::empty(),
- notmode: TermMode::empty(),
- trigger: Default::default(),
- }
- }
- }
-
- #[test]
- fn binding_matches_itself() {
- let binding = MockBinding::default();
- let identical_binding = MockBinding::default();
-
- assert!(binding.triggers_match(&identical_binding));
- assert!(identical_binding.triggers_match(&binding));
- }
-
- #[test]
- fn binding_matches_different_action() {
- let binding = MockBinding::default();
- let mut different_action = MockBinding::default();
- different_action.action = Action::ClearHistory;
-
- assert!(binding.triggers_match(&different_action));
- assert!(different_action.triggers_match(&binding));
- }
-
- #[test]
- fn mods_binding_requires_strict_match() {
- let mut superset_mods = MockBinding::default();
- superset_mods.mods = ModifiersState { alt: true, logo: true, ctrl: true, shift: true };
- let mut subset_mods = MockBinding::default();
- subset_mods.mods = ModifiersState { alt: true, logo: false, ctrl: false, shift: false };
-
- assert!(!superset_mods.triggers_match(&subset_mods));
- assert!(!subset_mods.triggers_match(&superset_mods));
- }
-
- #[test]
- fn binding_matches_identical_mode() {
- let mut b1 = MockBinding::default();
- b1.mode = TermMode::ALT_SCREEN;
- let mut b2 = MockBinding::default();
- b2.mode = TermMode::ALT_SCREEN;
-
- assert!(b1.triggers_match(&b2));
- }
-
- #[test]
- fn binding_without_mode_matches_any_mode() {
- let b1 = MockBinding::default();
- let mut b2 = MockBinding::default();
- b2.mode = TermMode::APP_KEYPAD;
- b2.notmode = TermMode::ALT_SCREEN;
-
- assert!(b1.triggers_match(&b2));
- }
-
- #[test]
- fn binding_with_mode_matches_empty_mode() {
- let mut b1 = MockBinding::default();
- b1.mode = TermMode::APP_KEYPAD;
- b1.notmode = TermMode::ALT_SCREEN;
- let b2 = MockBinding::default();
-
- assert!(b1.triggers_match(&b2));
- }
-
- #[test]
- fn binding_matches_superset_mode() {
- let mut b1 = MockBinding::default();
- b1.mode = TermMode::APP_KEYPAD;
- let mut b2 = MockBinding::default();
- b2.mode = TermMode::ALT_SCREEN | TermMode::APP_KEYPAD;
-
- assert!(b1.triggers_match(&b2));
- }
-
- #[test]
- fn binding_matches_subset_mode() {
- let mut b1 = MockBinding::default();
- b1.mode = TermMode::ALT_SCREEN | TermMode::APP_KEYPAD;
- let mut b2 = MockBinding::default();
- b2.mode = TermMode::APP_KEYPAD;
-
- assert!(b1.triggers_match(&b2));
- }
-
- #[test]
- fn binding_matches_partial_intersection() {
- let mut b1 = MockBinding::default();
- b1.mode = TermMode::ALT_SCREEN | TermMode::APP_KEYPAD;
- let mut b2 = MockBinding::default();
- b2.mode = TermMode::APP_KEYPAD | TermMode::APP_CURSOR;
-
- assert!(b1.triggers_match(&b2));
- }
-
- #[test]
- fn binding_mismatches_notmode() {
- let mut b1 = MockBinding::default();
- b1.mode = TermMode::ALT_SCREEN;
- let mut b2 = MockBinding::default();
- b2.notmode = TermMode::ALT_SCREEN;
-
- assert!(!b1.triggers_match(&b2));
- }
-
- #[test]
- fn binding_mismatches_unrelated() {
- let mut b1 = MockBinding::default();
- b1.mode = TermMode::ALT_SCREEN;
- let mut b2 = MockBinding::default();
- b2.mode = TermMode::APP_KEYPAD;
-
- assert!(!b1.triggers_match(&b2));
- }
-
- #[test]
- fn binding_trigger_input() {
- let mut binding = MockBinding::default();
- binding.trigger = 13;
-
- let mods = binding.mods;
- let mode = binding.mode;
-
- assert!(binding.is_triggered_by(mode, mods, &13, true));
- assert!(!binding.is_triggered_by(mode, mods, &32, true));
- }
-
- #[test]
- fn binding_trigger_mods() {
- let mut binding = MockBinding::default();
- binding.mods = ModifiersState { alt: true, logo: true, ctrl: false, shift: false };
-
- let superset_mods = ModifiersState { alt: true, logo: true, ctrl: true, shift: true };
- let subset_mods = ModifiersState { alt: false, logo: false, ctrl: false, shift: false };
-
- let t = binding.trigger;
- let mode = binding.mode;
-
- assert!(binding.is_triggered_by(mode, binding.mods, &t, true));
- assert!(binding.is_triggered_by(mode, binding.mods, &t, false));
-
- assert!(binding.is_triggered_by(mode, superset_mods, &t, true));
- assert!(!binding.is_triggered_by(mode, superset_mods, &t, false));
-
- assert!(!binding.is_triggered_by(mode, subset_mods, &t, true));
- assert!(!binding.is_triggered_by(mode, subset_mods, &t, false));
- }
-
- #[test]
- fn binding_trigger_modes() {
- let mut binding = MockBinding::default();
- binding.mode = TermMode::ALT_SCREEN;
-
- let t = binding.trigger;
- let mods = binding.mods;
-
- assert!(!binding.is_triggered_by(TermMode::INSERT, mods, &t, true));
- assert!(binding.is_triggered_by(TermMode::ALT_SCREEN, mods, &t, true));
- assert!(binding.is_triggered_by(TermMode::ALT_SCREEN | TermMode::INSERT, mods, &t, true));
- }
-
- #[test]
- fn binding_trigger_notmodes() {
- let mut binding = MockBinding::default();
- binding.notmode = TermMode::ALT_SCREEN;
-
- let t = binding.trigger;
- let mods = binding.mods;
-
- assert!(binding.is_triggered_by(TermMode::INSERT, mods, &t, true));
- assert!(!binding.is_triggered_by(TermMode::ALT_SCREEN, mods, &t, true));
- assert!(!binding.is_triggered_by(TermMode::ALT_SCREEN | TermMode::INSERT, mods, &t, true));
- }
-
- #[derive(PartialEq)]
- enum MultiClick {
- DoubleClick,
- TripleClick,
- None,
- }
-
- struct ActionContext<'a> {
- pub terminal: &'a mut Term,
- pub selection: &'a mut Option<Selection>,
- pub size_info: &'a SizeInfo,
- pub mouse: &'a mut Mouse,
- pub last_action: MultiClick,
- pub received_count: usize,
- pub suppress_chars: bool,
- pub modifiers: Modifiers,
- pub window_changes: &'a mut WindowChanges,
- }
-
- impl<'a> super::ActionContext for ActionContext<'a> {
- fn write_to_pty<B: Into<Cow<'static, [u8]>>>(&mut self, _val: B) {}
-
- fn update_selection(&mut self, _point: Point, _side: Side) {}
-
- fn simple_selection(&mut self, _point: Point, _side: Side) {}
-
- fn block_selection(&mut self, _point: Point, _side: Side) {}
-
- fn copy_selection(&mut self, _: ClipboardType) {}
-
- fn clear_selection(&mut self) {}
-
- fn hide_window(&mut self) {}
-
- fn spawn_new_instance(&mut self) {}
-
- fn toggle_fullscreen(&mut self) {}
-
- #[cfg(target_os = "macos")]
- fn toggle_simple_fullscreen(&mut self) {}
-
- fn terminal(&self) -> &Term {
- &self.terminal
- }
-
- fn terminal_mut(&mut self) -> &mut Term {
- &mut self.terminal
- }
-
- fn size_info(&self) -> SizeInfo {
- *self.size_info
- }
-
- fn semantic_selection(&mut self, _point: Point) {
- // set something that we can check for here
- self.last_action = MultiClick::DoubleClick;
- }
-
- fn line_selection(&mut self, _point: Point) {
- self.last_action = MultiClick::TripleClick;
- }
-
- fn selection_is_empty(&self) -> bool {
- true
- }
-
- fn scroll(&mut self, scroll: Scroll) {
- self.terminal.scroll_display(scroll);
- }
-
- fn mouse_coords(&self) -> Option<Point> {
- self.terminal.pixels_to_coords(self.mouse.x as usize, self.mouse.y as usize)
- }
-
- #[inline]
- fn mouse_mut(&mut self) -> &mut Mouse {
- self.mouse
- }
-
- #[inline]
- fn mouse(&self) -> &Mouse {
- self.mouse
- }
-
- fn received_count(&mut self) -> &mut usize {
- &mut self.received_count
- }
-
- fn suppress_chars(&mut self) -> &mut bool {
- &mut self.suppress_chars
- }
-
- fn modifiers(&mut self) -> &mut Modifiers {
- &mut self.modifiers
- }
- }
-
- macro_rules! test_clickstate {
- {
- name: $name:ident,
- initial_state: $initial_state:expr,
- initial_button: $initial_button:expr,
- input: $input:expr,
- end_state: $end_state:pat,
- last_action: $last_action:expr
- } => {
- #[test]
- fn $name() {
- let config = Config::default();
- let size = SizeInfo {
- width: 21.0,
- height: 51.0,
- cell_width: 3.0,
- cell_height: 3.0,
- padding_x: 0.0,
- padding_y: 0.0,
- dpr: 1.0,
- };
-
- let mut terminal = Term::new(&config, size, MessageBuffer::new(), Clipboard::new_nop());
-
- let mut mouse = Mouse::default();
- mouse.click_state = $initial_state;
- mouse.last_button = $initial_button;
-
- let mut selection = None;
-
- let context = ActionContext {
- terminal: &mut terminal,
- selection: &mut selection,
- mouse: &mut mouse,
- size_info: &size,
- last_action: MultiClick::None,
- received_count: 0,
- suppress_chars: false,
- modifiers: Default::default(),
- window_changes: &mut WindowChanges::default(),
- };
-
- let mut processor = Processor {
- ctx: context,
- mouse_config: &config::Mouse {
- double_click: ClickHandler {
- threshold: Duration::from_millis(1000),
- },
- triple_click: ClickHandler {
- threshold: Duration::from_millis(1000),
- },
- hide_when_typing: false,
- url: Default::default(),
- },
- scrolling_config: &config::Scrolling::default(),
- key_bindings: &config.key_bindings[..],
- mouse_bindings: &config.mouse_bindings[..],
- save_to_clipboard: config.selection.save_to_clipboard,
- alt_send_esc: config.alt_send_esc(),
- };
-
- if let Event::WindowEvent { event: WindowEvent::MouseInput { state, button, modifiers, .. }, .. } = $input {
- processor.mouse_input(state, button, modifiers);
- };
-
- assert!(match processor.ctx.mouse.click_state {
- $end_state => processor.ctx.last_action == $last_action,
- _ => false
- });
- }
- }
- }
-
- macro_rules! test_process_binding {
- {
- name: $name:ident,
- binding: $binding:expr,
- triggers: $triggers:expr,
- mode: $mode:expr,
- mods: $mods:expr
- } => {
- #[test]
- fn $name() {
- if $triggers {
- assert!($binding.is_triggered_by($mode, $mods, &KEY, false));
- } else {
- assert!(!$binding.is_triggered_by($mode, $mods, &KEY, false));
- }
- }
- }
- }
-
- test_clickstate! {
- name: single_click,
- initial_state: ClickState::None,
- initial_button: MouseButton::Other(0),
- input: Event::WindowEvent {
- event: WindowEvent::MouseInput {
- state: ElementState::Pressed,
- button: MouseButton::Left,
- device_id: unsafe { ::std::mem::transmute_copy(&0) },
- modifiers: ModifiersState::default(),
- },
- window_id: unsafe { ::std::mem::transmute_copy(&0) },
- },
- end_state: ClickState::Click,
- last_action: MultiClick::None
- }
-
- test_clickstate! {
- name: double_click,
- initial_state: ClickState::Click,
- initial_button: MouseButton::Left,
- input: Event::WindowEvent {
- event: WindowEvent::MouseInput {
- state: ElementState::Pressed,
- button: MouseButton::Left,
- device_id: unsafe { ::std::mem::transmute_copy(&0) },
- modifiers: ModifiersState::default(),
- },
- window_id: unsafe { ::std::mem::transmute_copy(&0) },
- },
- end_state: ClickState::DoubleClick,
- last_action: MultiClick::DoubleClick
- }
-
- test_clickstate! {
- name: triple_click,
- initial_state: ClickState::DoubleClick,
- initial_button: MouseButton::Left,
- input: Event::WindowEvent {
- event: WindowEvent::MouseInput {
- state: ElementState::Pressed,
- button: MouseButton::Left,
- device_id: unsafe { ::std::mem::transmute_copy(&0) },
- modifiers: ModifiersState::default(),
- },
- window_id: unsafe { ::std::mem::transmute_copy(&0) },
- },
- end_state: ClickState::TripleClick,
- last_action: MultiClick::TripleClick
- }
-
- test_clickstate! {
- name: multi_click_separate_buttons,
- initial_state: ClickState::DoubleClick,
- initial_button: MouseButton::Left,
- input: Event::WindowEvent {
- event: WindowEvent::MouseInput {
- state: ElementState::Pressed,
- button: MouseButton::Right,
- device_id: unsafe { ::std::mem::transmute_copy(&0) },
- modifiers: ModifiersState::default(),
- },
- window_id: unsafe { ::std::mem::transmute_copy(&0) },
- },
- end_state: ClickState::Click,
- last_action: MultiClick::None
- }
-
- test_process_binding! {
- name: process_binding_nomode_shiftmod_require_shift,
- binding: Binding { trigger: KEY, mods: ModifiersState { shift: true, ctrl: false, alt: false, logo: false }, action: Action::from("\x1b[1;2D"), mode: TermMode::NONE, notmode: TermMode::NONE },
- triggers: true,
- mode: TermMode::NONE,
- mods: ModifiersState { shift: true, ctrl: false, alt: false, logo: false }
- }
-
- test_process_binding! {
- name: process_binding_nomode_nomod_require_shift,
- binding: Binding { trigger: KEY, mods: ModifiersState { shift: true, ctrl: false, alt: false, logo: false }, action: Action::from("\x1b[1;2D"), mode: TermMode::NONE, notmode: TermMode::NONE },
- triggers: false,
- mode: TermMode::NONE,
- mods: ModifiersState { shift: false, ctrl: false, alt: false, logo: false }
- }
-
- test_process_binding! {
- name: process_binding_nomode_controlmod,
- binding: Binding { trigger: KEY, mods: ModifiersState { ctrl: true, shift: false, alt: false, logo: false }, action: Action::from("\x1b[1;5D"), mode: TermMode::NONE, notmode: TermMode::NONE },
- triggers: true,
- mode: TermMode::NONE,
- mods: ModifiersState { ctrl: true, shift: false, alt: false, logo: false }
- }
-
- test_process_binding! {
- name: process_binding_nomode_nomod_require_not_appcursor,
- binding: Binding { trigger: KEY, mods: ModifiersState { shift: false, ctrl: false, alt: false, logo: false }, action: Action::from("\x1b[D"), mode: TermMode::NONE, notmode: TermMode::APP_CURSOR },
- triggers: true,
- mode: TermMode::NONE,
- mods: ModifiersState { shift: false, ctrl: false, alt: false, logo: false }
- }
-
- test_process_binding! {
- name: process_binding_appcursormode_nomod_require_appcursor,
- binding: Binding { trigger: KEY, mods: ModifiersState { shift: false, ctrl: false, alt: false, logo: false }, action: Action::from("\x1bOD"), mode: TermMode::APP_CURSOR, notmode: TermMode::NONE },
- triggers: true,
- mode: TermMode::APP_CURSOR,
- mods: ModifiersState { shift: false, ctrl: false, alt: false, logo: false }
- }
-
- test_process_binding! {
- name: process_binding_nomode_nomod_require_appcursor,
- binding: Binding { trigger: KEY, mods: ModifiersState { shift: false, ctrl: false, alt: false, logo: false }, action: Action::from("\x1bOD"), mode: TermMode::APP_CURSOR, notmode: TermMode::NONE },
- triggers: false,
- mode: TermMode::NONE,
- mods: ModifiersState { shift: false, ctrl: false, alt: false, logo: false }
- }
-
- test_process_binding! {
- name: process_binding_appcursormode_appkeypadmode_nomod_require_appcursor,
- binding: Binding { trigger: KEY, mods: ModifiersState { shift: false, ctrl: false, alt: false, logo: false }, action: Action::from("\x1bOD"), mode: TermMode::APP_CURSOR, notmode: TermMode::NONE },
- triggers: true,
- mode: TermMode::APP_CURSOR | TermMode::APP_KEYPAD,
- mods: ModifiersState { shift: false, ctrl: false, alt: false, logo: false }
- }
-
- test_process_binding! {
- name: process_binding_fail_with_extra_mods,
- binding: Binding { trigger: KEY, mods: ModifiersState { shift: false, ctrl: false, alt: false, logo: true }, action: Action::from("arst"), mode: TermMode::NONE, notmode: TermMode::NONE },
- triggers: false,
- mode: TermMode::NONE,
- mods: ModifiersState { shift: false, ctrl: false, alt: true, logo: true }
- }
-}