2020-05-05 22:50:23 +00:00
|
|
|
//! Handle input from glutin.
|
2016-06-23 16:48:31 +00:00
|
|
|
//!
|
2020-05-05 22:50:23 +00:00
|
|
|
//! Certain key combinations should send some escape sequence back to the PTY.
|
2016-06-23 16:48:31 +00:00
|
|
|
//! 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.
|
2020-06-06 18:49:14 +00:00
|
|
|
|
2017-02-02 06:13:08 +00:00
|
|
|
use std::borrow::Cow;
|
2020-06-18 01:02:56 +00:00
|
|
|
use std::cmp::{max, min, Ordering};
|
2019-10-05 00:29:26 +00:00
|
|
|
use std::marker::PhantomData;
|
2020-06-18 01:02:56 +00:00
|
|
|
use std::time::{Duration, Instant};
|
2017-01-02 02:28:49 +00:00
|
|
|
|
2020-02-07 13:44:11 +00:00
|
|
|
use log::{debug, trace, warn};
|
|
|
|
|
2020-06-18 01:02:56 +00:00
|
|
|
use glutin::dpi::PhysicalPosition;
|
2019-10-05 00:29:26 +00:00
|
|
|
use glutin::event::{
|
|
|
|
ElementState, KeyboardInput, ModifiersState, MouseButton, MouseScrollDelta, TouchPhase,
|
2019-03-19 19:14:17 +00:00
|
|
|
};
|
2020-02-07 13:44:11 +00:00
|
|
|
use glutin::event_loop::EventLoopWindowTarget;
|
|
|
|
#[cfg(target_os = "macos")]
|
|
|
|
use glutin::platform::macos::EventLoopWindowTargetExtMacOS;
|
2019-10-05 00:29:26 +00:00
|
|
|
use glutin::window::CursorIcon;
|
|
|
|
|
|
|
|
use alacritty_terminal::ansi::{ClearMode, Handler};
|
2020-06-18 01:02:56 +00:00
|
|
|
use alacritty_terminal::event::EventListener;
|
2019-10-05 00:29:26 +00:00
|
|
|
use alacritty_terminal::grid::Scroll;
|
|
|
|
use alacritty_terminal::index::{Column, Line, Point, Side};
|
|
|
|
use alacritty_terminal::message_bar::{self, Message};
|
2020-03-18 02:35:08 +00:00
|
|
|
use alacritty_terminal::selection::SelectionType;
|
2019-10-05 00:29:26 +00:00
|
|
|
use alacritty_terminal::term::mode::TermMode;
|
2020-06-06 21:33:20 +00:00
|
|
|
use alacritty_terminal::term::{ClipboardType, SizeInfo, Term};
|
2019-10-05 00:29:26 +00:00
|
|
|
use alacritty_terminal::util::start_daemon;
|
2020-03-18 02:35:08 +00:00
|
|
|
use alacritty_terminal::vi_mode::ViMotion;
|
2019-10-05 00:29:26 +00:00
|
|
|
|
2020-06-06 21:33:20 +00:00
|
|
|
use crate::clipboard::Clipboard;
|
2020-03-18 02:35:08 +00:00
|
|
|
use crate::config::{Action, Binding, Config, Key, ViAction};
|
2020-06-18 01:02:56 +00:00
|
|
|
use crate::event::{ClickState, Event, Mouse};
|
|
|
|
use crate::scheduler::{Scheduler, TimerId};
|
2019-11-03 20:59:28 +00:00
|
|
|
use crate::url::{Url, Urls};
|
2019-10-05 00:29:26 +00:00
|
|
|
use crate::window::Window;
|
2018-07-21 17:17:41 +00:00
|
|
|
|
2020-05-05 22:50:23 +00:00
|
|
|
/// Font size change interval.
|
2019-11-03 19:02:26 +00:00
|
|
|
pub const FONT_SIZE_STEP: f32 = 0.5;
|
|
|
|
|
2020-06-18 01:02:56 +00:00
|
|
|
/// Interval for mouse scrolling during selection outside of the boundaries.
|
|
|
|
const SELECTION_SCROLLING_INTERVAL: Duration = Duration::from_millis(15);
|
|
|
|
|
|
|
|
/// Minimum number of pixels at the bottom/top where selection scrolling is performed.
|
|
|
|
const MIN_SELECTION_SCROLLING_HEIGHT: f64 = 5.;
|
|
|
|
|
|
|
|
/// Number of pixels for increasing the selection scrolling speed factor by one.
|
|
|
|
const SELECTION_SCROLLING_STEP: f64 = 20.;
|
|
|
|
|
2016-06-23 16:48:31 +00:00
|
|
|
/// Processes input from glutin.
|
|
|
|
///
|
|
|
|
/// An escape sequence may be emitted in case specific keys or key combinations
|
|
|
|
/// are activated.
|
2019-11-03 20:59:28 +00:00
|
|
|
pub struct Processor<'a, T: EventListener, A: ActionContext<T>> {
|
2017-02-02 06:13:08 +00:00
|
|
|
pub ctx: A,
|
2019-11-03 20:59:28 +00:00
|
|
|
pub highlighted_url: &'a Option<Url>,
|
2019-10-05 00:29:26 +00:00
|
|
|
_phantom: PhantomData<T>,
|
2016-11-24 04:25:37 +00:00
|
|
|
}
|
|
|
|
|
2019-10-05 00:29:26 +00:00
|
|
|
pub trait ActionContext<T: EventListener> {
|
2020-03-18 02:35:08 +00:00
|
|
|
fn write_to_pty<B: Into<Cow<'static, [u8]>>>(&mut self, data: B);
|
2017-02-02 06:13:08 +00:00
|
|
|
fn size_info(&self) -> SizeInfo;
|
2020-03-18 02:35:08 +00:00
|
|
|
fn copy_selection(&mut self, ty: ClipboardType);
|
|
|
|
fn start_selection(&mut self, ty: SelectionType, point: Point, side: Side);
|
|
|
|
fn toggle_selection(&mut self, ty: SelectionType, point: Point, side: Side);
|
2017-06-16 04:43:28 +00:00
|
|
|
fn update_selection(&mut self, point: Point, side: Side);
|
2020-03-18 02:35:08 +00:00
|
|
|
fn clear_selection(&mut self);
|
2018-10-22 19:39:26 +00:00
|
|
|
fn selection_is_empty(&self) -> bool;
|
2017-02-02 06:13:08 +00:00
|
|
|
fn mouse_mut(&mut self) -> &mut Mouse;
|
2018-09-27 12:12:49 +00:00
|
|
|
fn mouse(&self) -> &Mouse;
|
2017-01-15 01:53:48 +00:00
|
|
|
fn mouse_coords(&self) -> Option<Point>;
|
2017-07-20 17:50:50 +00:00
|
|
|
fn received_count(&mut self) -> &mut usize;
|
|
|
|
fn suppress_chars(&mut self) -> &mut bool;
|
2019-11-11 21:05:24 +00:00
|
|
|
fn modifiers(&mut self) -> &mut ModifiersState;
|
2018-03-11 12:01:06 +00:00
|
|
|
fn scroll(&mut self, scroll: Scroll);
|
2019-10-05 00:29:26 +00:00
|
|
|
fn window(&self) -> &Window;
|
|
|
|
fn window_mut(&mut self) -> &mut Window;
|
|
|
|
fn terminal(&self) -> &Term<T>;
|
|
|
|
fn terminal_mut(&mut self) -> &mut Term<T>;
|
2019-01-05 20:47:12 +00:00
|
|
|
fn spawn_new_instance(&mut self);
|
2019-10-05 00:29:26 +00:00
|
|
|
fn change_font_size(&mut self, delta: f32);
|
|
|
|
fn reset_font_size(&mut self);
|
|
|
|
fn pop_message(&mut self);
|
|
|
|
fn message(&self) -> Option<&Message>;
|
2019-11-03 19:02:26 +00:00
|
|
|
fn config(&self) -> &Config;
|
2020-02-07 13:44:11 +00:00
|
|
|
fn event_loop(&self) -> &EventLoopWindowTarget<Event>;
|
2020-03-18 02:35:08 +00:00
|
|
|
fn urls(&self) -> &Urls;
|
|
|
|
fn launch_url(&self, url: Url);
|
|
|
|
fn mouse_mode(&self) -> bool;
|
2020-06-18 01:02:56 +00:00
|
|
|
fn clipboard_mut(&mut self) -> &mut Clipboard;
|
|
|
|
fn scheduler_mut(&mut self) -> &mut Scheduler;
|
2016-07-02 15:32:28 +00:00
|
|
|
}
|
|
|
|
|
2019-10-05 00:29:26 +00:00
|
|
|
trait Execute<T: EventListener> {
|
2020-01-10 00:44:41 +00:00
|
|
|
fn execute<A: ActionContext<T>>(&self, ctx: &mut A);
|
2016-12-27 00:00:27 +00:00
|
|
|
}
|
2016-11-15 17:38:50 +00:00
|
|
|
|
2019-10-05 00:29:26 +00:00
|
|
|
impl<T, U: EventListener> Execute<U> for Binding<T> {
|
2020-05-05 22:50:23 +00:00
|
|
|
/// Execute the action associate with this binding.
|
2016-11-15 17:38:50 +00:00
|
|
|
#[inline]
|
2020-01-10 00:44:41 +00:00
|
|
|
fn execute<A: ActionContext<U>>(&self, ctx: &mut A) {
|
|
|
|
self.action.execute(ctx)
|
2016-11-15 17:38:50 +00:00
|
|
|
}
|
2016-06-23 16:48:31 +00:00
|
|
|
}
|
|
|
|
|
2020-03-18 02:35:08 +00:00
|
|
|
impl Action {
|
|
|
|
fn toggle_selection<T, A>(ctx: &mut A, ty: SelectionType)
|
|
|
|
where
|
|
|
|
T: EventListener,
|
|
|
|
A: ActionContext<T>,
|
|
|
|
{
|
|
|
|
let cursor_point = ctx.terminal().vi_mode_cursor.point;
|
|
|
|
ctx.toggle_selection(ty, cursor_point, Side::Left);
|
|
|
|
|
2020-05-05 22:50:23 +00:00
|
|
|
// Make sure initial selection is not empty.
|
2020-05-30 20:45:44 +00:00
|
|
|
if let Some(selection) = &mut ctx.terminal_mut().selection {
|
2020-03-18 02:35:08 +00:00
|
|
|
selection.include_all();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-10-05 00:29:26 +00:00
|
|
|
impl<T: EventListener> Execute<T> for Action {
|
2016-11-15 17:38:50 +00:00
|
|
|
#[inline]
|
2020-01-10 00:44:41 +00:00
|
|
|
fn execute<A: ActionContext<T>>(&self, ctx: &mut A) {
|
2016-11-15 17:38:50 +00:00
|
|
|
match *self {
|
2016-12-26 23:33:27 +00:00
|
|
|
Action::Esc(ref s) => {
|
2020-05-06 21:58:43 +00:00
|
|
|
if ctx.config().ui_config.mouse.hide_when_typing {
|
|
|
|
ctx.window_mut().set_mouse_visible(false);
|
|
|
|
}
|
|
|
|
|
2019-10-29 16:56:48 +00:00
|
|
|
ctx.clear_selection();
|
2018-03-23 00:01:55 +00:00
|
|
|
ctx.scroll(Scroll::Bottom);
|
2017-02-02 06:13:08 +00:00
|
|
|
ctx.write_to_pty(s.clone().into_bytes())
|
2016-12-26 23:33:27 +00:00
|
|
|
},
|
2020-03-23 23:46:33 +00:00
|
|
|
Action::Copy => ctx.copy_selection(ClipboardType::Clipboard),
|
|
|
|
#[cfg(not(any(target_os = "macos", windows)))]
|
|
|
|
Action::CopySelection => ctx.copy_selection(ClipboardType::Selection),
|
2017-01-20 05:55:38 +00:00
|
|
|
Action::Paste => {
|
2020-06-18 01:02:56 +00:00
|
|
|
let text = ctx.clipboard_mut().load(ClipboardType::Clipboard);
|
2019-10-05 00:29:26 +00:00
|
|
|
paste(ctx, &text);
|
2017-01-20 05:55:38 +00:00
|
|
|
},
|
2016-12-26 23:33:27 +00:00
|
|
|
Action::PasteSelection => {
|
2020-06-18 01:02:56 +00:00
|
|
|
let text = ctx.clipboard_mut().load(ClipboardType::Selection);
|
2020-01-10 00:44:41 +00:00
|
|
|
paste(ctx, &text);
|
2016-11-15 17:38:50 +00:00
|
|
|
},
|
2020-06-04 22:10:31 +00:00
|
|
|
Action::Command(ref program) => {
|
|
|
|
let args = program.args();
|
|
|
|
let program = program.program();
|
2019-01-07 00:06:57 +00:00
|
|
|
trace!("Running command {} with args {:?}", program, args);
|
2018-10-16 17:02:52 +00:00
|
|
|
|
2018-12-06 21:38:34 +00:00
|
|
|
match start_daemon(program, args) {
|
2019-04-30 13:30:46 +00:00
|
|
|
Ok(_) => debug!("Spawned new proc"),
|
|
|
|
Err(err) => warn!("Couldn't run command {}", err),
|
2017-05-25 16:19:00 +00:00
|
|
|
}
|
|
|
|
},
|
2020-03-18 02:35:08 +00:00
|
|
|
Action::ClearSelection => ctx.clear_selection(),
|
|
|
|
Action::ToggleViMode => ctx.terminal_mut().toggle_vi_mode(),
|
|
|
|
Action::ViAction(ViAction::ToggleNormalSelection) => {
|
|
|
|
Self::toggle_selection(ctx, SelectionType::Simple)
|
|
|
|
},
|
|
|
|
Action::ViAction(ViAction::ToggleLineSelection) => {
|
|
|
|
Self::toggle_selection(ctx, SelectionType::Lines)
|
|
|
|
},
|
|
|
|
Action::ViAction(ViAction::ToggleBlockSelection) => {
|
|
|
|
Self::toggle_selection(ctx, SelectionType::Block)
|
|
|
|
},
|
|
|
|
Action::ViAction(ViAction::ToggleSemanticSelection) => {
|
|
|
|
Self::toggle_selection(ctx, SelectionType::Semantic)
|
|
|
|
},
|
|
|
|
Action::ViAction(ViAction::Open) => {
|
|
|
|
ctx.mouse_mut().block_url_launcher = false;
|
|
|
|
if let Some(url) = ctx.urls().find_at(ctx.terminal().vi_mode_cursor.point) {
|
|
|
|
ctx.launch_url(url);
|
|
|
|
}
|
|
|
|
},
|
2020-05-06 21:58:43 +00:00
|
|
|
Action::ViMotion(motion) => {
|
|
|
|
if ctx.config().ui_config.mouse.hide_when_typing {
|
|
|
|
ctx.window_mut().set_mouse_visible(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx.terminal_mut().vi_motion(motion)
|
|
|
|
},
|
2019-10-05 00:29:26 +00:00
|
|
|
Action::ToggleFullscreen => ctx.window_mut().toggle_fullscreen(),
|
2019-04-23 19:05:47 +00:00
|
|
|
#[cfg(target_os = "macos")]
|
2019-10-05 00:29:26 +00:00
|
|
|
Action::ToggleSimpleFullscreen => ctx.window_mut().toggle_simple_fullscreen(),
|
2020-02-07 13:44:11 +00:00
|
|
|
#[cfg(target_os = "macos")]
|
|
|
|
Action::Hide => ctx.event_loop().hide_application(),
|
|
|
|
#[cfg(not(target_os = "macos"))]
|
2019-10-05 00:29:26 +00:00
|
|
|
Action::Hide => ctx.window().set_visible(false),
|
2020-01-05 01:54:14 +00:00
|
|
|
Action::Minimize => ctx.window().set_minimized(true),
|
2019-10-05 00:29:26 +00:00
|
|
|
Action::Quit => ctx.terminal_mut().exit(),
|
|
|
|
Action::IncreaseFontSize => ctx.change_font_size(FONT_SIZE_STEP),
|
|
|
|
Action::DecreaseFontSize => ctx.change_font_size(FONT_SIZE_STEP * -1.),
|
|
|
|
Action::ResetFontSize => ctx.reset_font_size(),
|
2020-03-18 02:35:08 +00:00
|
|
|
Action::ScrollPageUp => {
|
2020-05-05 22:50:23 +00:00
|
|
|
// Move vi mode cursor.
|
2020-03-18 02:35:08 +00:00
|
|
|
let term = ctx.terminal_mut();
|
|
|
|
let scroll_lines = term.grid().num_lines().0 as isize;
|
|
|
|
term.vi_mode_cursor = term.vi_mode_cursor.scroll(term, scroll_lines);
|
|
|
|
|
|
|
|
ctx.scroll(Scroll::PageUp);
|
|
|
|
},
|
|
|
|
Action::ScrollPageDown => {
|
2020-05-05 22:50:23 +00:00
|
|
|
// Move vi mode cursor.
|
2020-03-18 02:35:08 +00:00
|
|
|
let term = ctx.terminal_mut();
|
|
|
|
let scroll_lines = -(term.grid().num_lines().0 as isize);
|
|
|
|
term.vi_mode_cursor = term.vi_mode_cursor.scroll(term, scroll_lines);
|
|
|
|
|
|
|
|
ctx.scroll(Scroll::PageDown);
|
|
|
|
},
|
|
|
|
Action::ScrollHalfPageUp => {
|
2020-05-05 22:50:23 +00:00
|
|
|
// Move vi mode cursor.
|
2020-03-18 02:35:08 +00:00
|
|
|
let term = ctx.terminal_mut();
|
|
|
|
let scroll_lines = term.grid().num_lines().0 as isize / 2;
|
|
|
|
term.vi_mode_cursor = term.vi_mode_cursor.scroll(term, scroll_lines);
|
|
|
|
|
|
|
|
ctx.scroll(Scroll::Lines(scroll_lines));
|
|
|
|
},
|
|
|
|
Action::ScrollHalfPageDown => {
|
2020-05-05 22:50:23 +00:00
|
|
|
// Move vi mode cursor.
|
2020-03-18 02:35:08 +00:00
|
|
|
let term = ctx.terminal_mut();
|
|
|
|
let scroll_lines = -(term.grid().num_lines().0 as isize / 2);
|
|
|
|
term.vi_mode_cursor = term.vi_mode_cursor.scroll(term, scroll_lines);
|
|
|
|
|
|
|
|
ctx.scroll(Scroll::Lines(scroll_lines));
|
|
|
|
},
|
|
|
|
Action::ScrollLineUp => {
|
2020-05-05 22:50:23 +00:00
|
|
|
// Move vi mode cursor.
|
2020-03-18 02:35:08 +00:00
|
|
|
let term = ctx.terminal();
|
|
|
|
if term.grid().display_offset() != term.grid().history_size()
|
|
|
|
&& term.vi_mode_cursor.point.line + 1 != term.grid().num_lines()
|
|
|
|
{
|
|
|
|
ctx.terminal_mut().vi_mode_cursor.point.line += 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx.scroll(Scroll::Lines(1));
|
|
|
|
},
|
|
|
|
Action::ScrollLineDown => {
|
2020-05-05 22:50:23 +00:00
|
|
|
// Move vi mode cursor.
|
2020-03-18 02:35:08 +00:00
|
|
|
if ctx.terminal().grid().display_offset() != 0
|
|
|
|
&& ctx.terminal().vi_mode_cursor.point.line.0 != 0
|
|
|
|
{
|
|
|
|
ctx.terminal_mut().vi_mode_cursor.point.line -= 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx.scroll(Scroll::Lines(-1));
|
|
|
|
},
|
|
|
|
Action::ScrollToTop => {
|
|
|
|
ctx.scroll(Scroll::Top);
|
|
|
|
|
2020-05-05 22:50:23 +00:00
|
|
|
// Move vi mode cursor.
|
2020-03-18 02:35:08 +00:00
|
|
|
ctx.terminal_mut().vi_mode_cursor.point.line = Line(0);
|
|
|
|
ctx.terminal_mut().vi_motion(ViMotion::FirstOccupied);
|
|
|
|
},
|
|
|
|
Action::ScrollToBottom => {
|
|
|
|
ctx.scroll(Scroll::Bottom);
|
|
|
|
|
2020-05-05 22:50:23 +00:00
|
|
|
// Move vi mode cursor.
|
2020-03-18 02:35:08 +00:00
|
|
|
let term = ctx.terminal_mut();
|
|
|
|
term.vi_mode_cursor.point.line = term.grid().num_lines() - 1;
|
2020-05-16 00:16:21 +00:00
|
|
|
|
|
|
|
// Move to beginning twice, to always jump across linewraps.
|
|
|
|
term.vi_motion(ViMotion::FirstOccupied);
|
2020-03-18 02:35:08 +00:00
|
|
|
term.vi_motion(ViMotion::FirstOccupied);
|
|
|
|
},
|
2019-10-05 00:29:26 +00:00
|
|
|
Action::ClearHistory => ctx.terminal_mut().clear_screen(ClearMode::Saved),
|
|
|
|
Action::ClearLogNotice => ctx.pop_message(),
|
|
|
|
Action::SpawnNewInstance => ctx.spawn_new_instance(),
|
2019-09-28 00:37:22 +00:00
|
|
|
Action::ReceiveChar | Action::None => (),
|
2016-11-15 17:38:50 +00:00
|
|
|
}
|
|
|
|
}
|
2018-11-01 19:35:37 +00:00
|
|
|
}
|
|
|
|
|
2019-10-05 00:29:26 +00:00
|
|
|
fn paste<T: EventListener, A: ActionContext<T>>(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());
|
2016-11-15 17:38:50 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-11-03 20:59:28 +00:00
|
|
|
#[derive(Debug, Clone, PartialEq)]
|
2019-08-24 23:18:51 +00:00
|
|
|
pub enum MouseState {
|
2019-07-28 17:04:26 +00:00
|
|
|
Url(Url),
|
|
|
|
MessageBar,
|
|
|
|
MessageBarButton,
|
2019-08-24 23:18:51 +00:00
|
|
|
Mouse,
|
|
|
|
Text,
|
2019-07-28 17:04:26 +00:00
|
|
|
}
|
|
|
|
|
2019-10-05 00:29:26 +00:00
|
|
|
impl From<MouseState> for CursorIcon {
|
|
|
|
fn from(mouse_state: MouseState) -> CursorIcon {
|
|
|
|
match mouse_state {
|
|
|
|
MouseState::Url(_) | MouseState::MessageBarButton => CursorIcon::Hand,
|
|
|
|
MouseState::Text => CursorIcon::Text,
|
|
|
|
_ => CursorIcon::Default,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-11-03 20:59:28 +00:00
|
|
|
impl<'a, T: EventListener, A: ActionContext<T>> Processor<'a, T, A> {
|
2020-03-18 02:35:08 +00:00
|
|
|
pub fn new(ctx: A, highlighted_url: &'a Option<Url>) -> Self {
|
|
|
|
Self { ctx, highlighted_url, _phantom: Default::default() }
|
2019-10-05 00:29:26 +00:00
|
|
|
}
|
|
|
|
|
2016-11-24 04:25:37 +00:00
|
|
|
#[inline]
|
2020-06-18 01:02:56 +00:00
|
|
|
pub fn mouse_moved(&mut self, position: PhysicalPosition<f64>) {
|
2019-11-03 20:59:28 +00:00
|
|
|
let size_info = self.ctx.size_info();
|
|
|
|
|
2020-06-18 01:02:56 +00:00
|
|
|
let (x, y) = position.into();
|
|
|
|
|
|
|
|
let lmb_pressed = self.ctx.mouse().left_button_state == ElementState::Pressed;
|
|
|
|
if !self.ctx.selection_is_empty() && lmb_pressed {
|
|
|
|
self.update_selection_scrolling(y);
|
|
|
|
}
|
|
|
|
|
|
|
|
let x = min(max(x, 0), size_info.width as i32 - 1) as usize;
|
|
|
|
let y = min(max(y, 0), size_info.height as i32 - 1) as usize;
|
|
|
|
|
2017-02-02 06:13:08 +00:00
|
|
|
self.ctx.mouse_mut().x = x;
|
|
|
|
self.ctx.mouse_mut().y = y;
|
2016-12-12 06:02:03 +00:00
|
|
|
|
2019-11-03 20:59:28 +00:00
|
|
|
let inside_grid = size_info.contains_point(x, y);
|
2018-03-13 18:00:14 +00:00
|
|
|
let point = size_info.pixels_to_coords(x, y);
|
2018-10-22 19:39:26 +00:00
|
|
|
let cell_side = self.get_mouse_side();
|
2018-03-13 18:00:14 +00:00
|
|
|
|
2019-04-28 22:36:02 +00:00
|
|
|
let cell_changed =
|
2019-11-03 20:59:28 +00:00
|
|
|
point.line != self.ctx.mouse().line || point.col != self.ctx.mouse().column;
|
2019-07-08 18:13:55 +00:00
|
|
|
|
2020-05-05 22:50:23 +00:00
|
|
|
// If the mouse hasn't changed cells, do nothing.
|
2019-11-03 20:59:28 +00:00
|
|
|
if !cell_changed
|
|
|
|
&& self.ctx.mouse().cell_side == cell_side
|
|
|
|
&& self.ctx.mouse().inside_grid == inside_grid
|
|
|
|
{
|
2019-07-08 18:13:55 +00:00
|
|
|
return;
|
|
|
|
}
|
2018-10-22 19:39:26 +00:00
|
|
|
|
2019-11-03 20:59:28 +00:00
|
|
|
self.ctx.mouse_mut().inside_grid = inside_grid;
|
|
|
|
self.ctx.mouse_mut().cell_side = cell_side;
|
|
|
|
self.ctx.mouse_mut().line = point.line;
|
|
|
|
self.ctx.mouse_mut().column = point.col;
|
|
|
|
|
2020-05-05 22:50:23 +00:00
|
|
|
// Don't launch URLs if mouse has moved.
|
2019-04-28 22:36:02 +00:00
|
|
|
self.ctx.mouse_mut().block_url_launcher = true;
|
|
|
|
|
2020-05-05 22:50:23 +00:00
|
|
|
// Update mouse state and check for URL change.
|
2020-01-05 01:00:50 +00:00
|
|
|
let mouse_state = self.mouse_state();
|
2019-11-03 20:59:28 +00:00
|
|
|
self.update_url_state(&mouse_state);
|
|
|
|
self.ctx.window_mut().set_mouse_cursor(mouse_state.into());
|
2019-03-19 19:14:17 +00:00
|
|
|
|
2019-11-03 20:59:28 +00:00
|
|
|
let last_term_line = self.ctx.terminal().grid().num_lines() - 1;
|
2020-06-18 01:02:56 +00:00
|
|
|
if lmb_pressed && (self.ctx.modifiers().shift() || !self.ctx.mouse_mode()) {
|
2020-05-05 22:50:23 +00:00
|
|
|
// Treat motion over message bar like motion over the last line.
|
2019-11-03 20:59:28 +00:00
|
|
|
let line = min(point.line, last_term_line);
|
|
|
|
|
2020-05-05 22:50:23 +00:00
|
|
|
// Move vi mode cursor to mouse cursor position.
|
2020-03-18 02:35:08 +00:00
|
|
|
if self.ctx.terminal().mode().contains(TermMode::VI) {
|
|
|
|
self.ctx.terminal_mut().vi_mode_cursor.point = point;
|
|
|
|
}
|
|
|
|
|
2019-11-03 20:59:28 +00:00
|
|
|
self.ctx.update_selection(Point { line, col: point.col }, cell_side);
|
|
|
|
} else if inside_grid
|
2019-04-28 22:36:02 +00:00
|
|
|
&& cell_changed
|
2019-11-03 20:59:28 +00:00
|
|
|
&& point.line <= last_term_line
|
|
|
|
&& self.ctx.terminal().mode().intersects(TermMode::MOUSE_MOTION | TermMode::MOUSE_DRAG)
|
2018-03-13 18:00:14 +00:00
|
|
|
{
|
2020-06-18 01:02:56 +00:00
|
|
|
if lmb_pressed {
|
2020-01-05 01:00:50 +00:00
|
|
|
self.mouse_report(32, ElementState::Pressed);
|
2018-09-27 12:12:49 +00:00
|
|
|
} else if self.ctx.mouse().middle_button_state == ElementState::Pressed {
|
2020-01-05 01:00:50 +00:00
|
|
|
self.mouse_report(33, ElementState::Pressed);
|
2018-09-27 12:12:49 +00:00
|
|
|
} else if self.ctx.mouse().right_button_state == ElementState::Pressed {
|
2020-01-05 01:00:50 +00:00
|
|
|
self.mouse_report(34, ElementState::Pressed);
|
2019-02-07 22:36:45 +00:00
|
|
|
} else if self.ctx.terminal().mode().contains(TermMode::MOUSE_MOTION) {
|
2020-01-05 01:00:50 +00:00
|
|
|
self.mouse_report(35, ElementState::Pressed);
|
2016-12-22 18:43:06 +00:00
|
|
|
}
|
2016-12-12 06:02:03 +00:00
|
|
|
}
|
2016-11-24 04:25:37 +00:00
|
|
|
}
|
|
|
|
|
2018-10-22 19:39:26 +00:00
|
|
|
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;
|
|
|
|
|
2019-02-07 22:36:45 +00:00
|
|
|
let additional_padding =
|
|
|
|
(size_info.width - size_info.padding_x * 2.) % size_info.cell_width;
|
2018-10-22 19:39:26 +00:00
|
|
|
let end_of_grid = size_info.width - size_info.padding_x - additional_padding;
|
|
|
|
|
|
|
|
if cell_x > half_cell_width
|
2020-05-05 22:50:23 +00:00
|
|
|
// Edge case when mouse leaves the window.
|
2018-10-22 19:39:26 +00:00
|
|
|
|| x as f32 >= end_of_grid
|
|
|
|
{
|
|
|
|
Side::Right
|
|
|
|
} else {
|
|
|
|
Side::Left
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-11-11 21:05:24 +00:00
|
|
|
fn normal_mouse_report(&mut self, button: u8) {
|
2018-09-27 12:12:49 +00:00
|
|
|
let (line, column) = (self.ctx.mouse().line, self.ctx.mouse().column);
|
2019-11-04 19:41:13 +00:00
|
|
|
let utf8 = self.ctx.terminal().mode().contains(TermMode::UTF8_MOUSE);
|
2016-12-22 18:43:06 +00:00
|
|
|
|
2019-11-04 19:41:13 +00:00
|
|
|
let max_point = if utf8 { 2015 } else { 223 };
|
|
|
|
|
|
|
|
if line >= Line(max_point) || column >= Column(max_point) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
let mut msg = vec![b'\x1b', b'[', b'M', 32 + button];
|
|
|
|
|
|
|
|
let mouse_pos_encode = |pos: usize| -> Vec<u8> {
|
|
|
|
let pos = 32 + 1 + pos;
|
|
|
|
let first = 0xC0 + pos / 64;
|
|
|
|
let second = 0x80 + (pos & 63);
|
|
|
|
vec![first as u8, second as u8]
|
|
|
|
};
|
|
|
|
|
|
|
|
if utf8 && column >= Column(95) {
|
|
|
|
msg.append(&mut mouse_pos_encode(column.0));
|
|
|
|
} else {
|
|
|
|
msg.push(32 + 1 + column.0 as u8);
|
|
|
|
}
|
|
|
|
|
|
|
|
if utf8 && line >= Line(95) {
|
|
|
|
msg.append(&mut mouse_pos_encode(line.0));
|
|
|
|
} else {
|
|
|
|
msg.push(32 + 1 + line.0 as u8);
|
2016-11-15 17:38:50 +00:00
|
|
|
}
|
2019-11-04 19:41:13 +00:00
|
|
|
|
|
|
|
self.ctx.write_to_pty(msg);
|
2016-06-23 16:48:31 +00:00
|
|
|
}
|
|
|
|
|
2019-11-11 21:05:24 +00:00
|
|
|
fn sgr_mouse_report(&mut self, button: u8, state: ElementState) {
|
2018-09-27 12:12:49 +00:00
|
|
|
let (line, column) = (self.ctx.mouse().line, self.ctx.mouse().column);
|
2018-01-06 19:39:33 +00:00
|
|
|
let c = match state {
|
|
|
|
ElementState::Pressed => 'M',
|
|
|
|
ElementState::Released => 'm',
|
|
|
|
};
|
2017-01-02 02:28:49 +00:00
|
|
|
|
|
|
|
let msg = format!("\x1b[<{};{};{}{}", button, column + 1, line + 1, c);
|
2017-02-02 06:13:08 +00:00
|
|
|
self.ctx.write_to_pty(msg.into_bytes());
|
2017-01-02 02:28:49 +00:00
|
|
|
}
|
|
|
|
|
2020-01-05 01:00:50 +00:00
|
|
|
fn mouse_report(&mut self, button: u8, state: ElementState) {
|
2020-05-05 22:50:23 +00:00
|
|
|
// Calculate modifiers value.
|
2018-03-08 21:50:04 +00:00
|
|
|
let mut mods = 0;
|
2020-01-05 01:00:50 +00:00
|
|
|
let modifiers = self.ctx.modifiers();
|
|
|
|
if modifiers.shift() {
|
2018-03-08 21:50:04 +00:00
|
|
|
mods += 4;
|
|
|
|
}
|
2020-01-05 01:00:50 +00:00
|
|
|
if modifiers.alt() {
|
2018-03-08 21:50:04 +00:00
|
|
|
mods += 8;
|
|
|
|
}
|
2020-01-05 01:00:50 +00:00
|
|
|
if modifiers.ctrl() {
|
2018-03-08 21:50:04 +00:00
|
|
|
mods += 16;
|
|
|
|
}
|
|
|
|
|
2020-05-05 22:50:23 +00:00
|
|
|
// Report mouse events.
|
2019-02-07 22:36:45 +00:00
|
|
|
if self.ctx.terminal().mode().contains(TermMode::SGR_MOUSE) {
|
2018-03-08 21:50:04 +00:00
|
|
|
self.sgr_mouse_report(button + mods, state);
|
2018-01-26 22:37:48 +00:00
|
|
|
} else if let ElementState::Released = state {
|
2018-03-08 21:50:04 +00:00
|
|
|
self.normal_mouse_report(3 + mods);
|
2017-01-02 02:28:49 +00:00
|
|
|
} else {
|
2018-03-08 21:50:04 +00:00
|
|
|
self.normal_mouse_report(button + mods);
|
2017-01-02 02:28:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-10 18:23:50 +00:00
|
|
|
fn on_mouse_press(&mut self, button: MouseButton) {
|
2020-05-05 22:50:23 +00:00
|
|
|
// Handle mouse mode.
|
2020-04-10 18:23:50 +00:00
|
|
|
if !self.ctx.modifiers().shift() && self.ctx.mouse_mode() {
|
|
|
|
self.ctx.mouse_mut().click_state = ClickState::None;
|
|
|
|
|
|
|
|
let code = match button {
|
|
|
|
MouseButton::Left => 0,
|
|
|
|
MouseButton::Middle => 1,
|
|
|
|
MouseButton::Right => 2,
|
2020-05-05 22:50:23 +00:00
|
|
|
// Can't properly report more than three buttons..
|
2020-04-10 18:23:50 +00:00
|
|
|
MouseButton::Other(_) => return,
|
|
|
|
};
|
2016-12-22 18:43:06 +00:00
|
|
|
|
2020-04-10 18:23:50 +00:00
|
|
|
self.mouse_report(code, ElementState::Pressed);
|
|
|
|
} else {
|
2020-06-23 09:57:15 +00:00
|
|
|
// Calculate time since the last click to handle double/triple clicks.
|
|
|
|
let now = Instant::now();
|
|
|
|
let elapsed = now - self.ctx.mouse().last_click_timestamp;
|
|
|
|
self.ctx.mouse_mut().last_click_timestamp = now;
|
|
|
|
|
|
|
|
// Update multi-click state.
|
|
|
|
let mouse_config = &self.ctx.config().ui_config.mouse;
|
|
|
|
self.ctx.mouse_mut().click_state = match self.ctx.mouse().click_state {
|
|
|
|
// Reset click state if button has changed.
|
|
|
|
_ if button != self.ctx.mouse().last_click_button => {
|
|
|
|
self.ctx.mouse_mut().last_click_button = button;
|
|
|
|
ClickState::Click
|
|
|
|
},
|
|
|
|
ClickState::Click if elapsed < mouse_config.double_click.threshold => {
|
|
|
|
ClickState::DoubleClick
|
|
|
|
},
|
|
|
|
ClickState::DoubleClick if elapsed < mouse_config.triple_click.threshold => {
|
|
|
|
ClickState::TripleClick
|
|
|
|
},
|
|
|
|
_ => ClickState::Click,
|
|
|
|
};
|
|
|
|
|
|
|
|
// Load mouse point, treating message bar and padding as the closest cell.
|
|
|
|
let mouse = self.ctx.mouse();
|
|
|
|
let mut point = self.ctx.size_info().pixels_to_coords(mouse.x, mouse.y);
|
|
|
|
point.line = min(point.line, self.ctx.terminal().grid().num_lines() - 1);
|
|
|
|
|
|
|
|
match button {
|
|
|
|
MouseButton::Left => self.on_left_click(point),
|
|
|
|
MouseButton::Right => self.on_right_click(point),
|
|
|
|
// Do nothing when using buttons other than LMB.
|
|
|
|
_ => self.ctx.mouse_mut().click_state = ClickState::None,
|
|
|
|
}
|
2017-01-15 01:53:48 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-23 09:57:15 +00:00
|
|
|
/// Handle selection expansion on right click.
|
|
|
|
fn on_right_click(&mut self, point: Point) {
|
|
|
|
match self.ctx.mouse().click_state {
|
|
|
|
ClickState::Click => {
|
|
|
|
let selection_type = if self.ctx.modifiers().ctrl() {
|
|
|
|
SelectionType::Block
|
|
|
|
} else {
|
|
|
|
SelectionType::Simple
|
|
|
|
};
|
2017-01-15 01:53:48 +00:00
|
|
|
|
2020-06-23 09:57:15 +00:00
|
|
|
self.expand_selection(point, selection_type);
|
|
|
|
},
|
|
|
|
ClickState::DoubleClick => self.expand_selection(point, SelectionType::Semantic),
|
|
|
|
ClickState::TripleClick => self.expand_selection(point, SelectionType::Lines),
|
|
|
|
ClickState::None => (),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Expand existing selection.
|
|
|
|
fn expand_selection(&mut self, point: Point, selection_type: SelectionType) {
|
|
|
|
let cell_side = self.ctx.mouse().cell_side;
|
|
|
|
|
|
|
|
let selection = match &mut self.ctx.terminal_mut().selection {
|
|
|
|
Some(selection) => selection,
|
|
|
|
None => return,
|
|
|
|
};
|
|
|
|
|
|
|
|
selection.ty = selection_type;
|
|
|
|
self.ctx.update_selection(point, cell_side);
|
|
|
|
}
|
2019-11-03 20:59:28 +00:00
|
|
|
|
2020-06-23 09:57:15 +00:00
|
|
|
/// Handle left click selection and vi mode cursor movement.
|
|
|
|
fn on_left_click(&mut self, point: Point) {
|
2020-04-10 18:23:50 +00:00
|
|
|
let side = self.ctx.mouse().cell_side;
|
|
|
|
|
2020-06-23 09:57:15 +00:00
|
|
|
match self.ctx.mouse().click_state {
|
|
|
|
ClickState::Click => {
|
2020-05-05 22:50:23 +00:00
|
|
|
// Don't launch URLs if this click cleared the selection.
|
2020-04-10 18:23:50 +00:00
|
|
|
self.ctx.mouse_mut().block_url_launcher = !self.ctx.selection_is_empty();
|
2020-03-19 12:39:00 +00:00
|
|
|
|
2020-04-10 18:23:50 +00:00
|
|
|
self.ctx.clear_selection();
|
2020-03-18 02:35:08 +00:00
|
|
|
|
2020-05-05 22:50:23 +00:00
|
|
|
// Start new empty selection.
|
2020-04-10 18:23:50 +00:00
|
|
|
if self.ctx.modifiers().ctrl() {
|
|
|
|
self.ctx.start_selection(SelectionType::Block, point, side);
|
|
|
|
} else {
|
|
|
|
self.ctx.start_selection(SelectionType::Simple, point, side);
|
2017-01-15 01:53:48 +00:00
|
|
|
}
|
2019-03-30 16:48:36 +00:00
|
|
|
},
|
2020-06-23 09:57:15 +00:00
|
|
|
ClickState::DoubleClick => {
|
|
|
|
self.ctx.mouse_mut().block_url_launcher = true;
|
|
|
|
self.ctx.start_selection(SelectionType::Semantic, point, side);
|
|
|
|
},
|
|
|
|
ClickState::TripleClick => {
|
|
|
|
self.ctx.mouse_mut().block_url_launcher = true;
|
|
|
|
self.ctx.start_selection(SelectionType::Lines, point, side);
|
|
|
|
},
|
|
|
|
ClickState::None => (),
|
2017-01-15 01:53:48 +00:00
|
|
|
};
|
2020-04-10 18:23:50 +00:00
|
|
|
|
2020-05-05 22:50:23 +00:00
|
|
|
// Move vi mode cursor to mouse position.
|
2020-04-10 18:23:50 +00:00
|
|
|
if self.ctx.terminal().mode().contains(TermMode::VI) {
|
2020-05-05 22:50:23 +00:00
|
|
|
// Update Vi mode cursor position on click.
|
2020-04-10 18:23:50 +00:00
|
|
|
self.ctx.terminal_mut().vi_mode_cursor.point = point;
|
|
|
|
}
|
2016-11-24 04:25:37 +00:00
|
|
|
}
|
|
|
|
|
2020-01-05 01:00:50 +00:00
|
|
|
fn on_mouse_release(&mut self, button: MouseButton) {
|
2020-03-18 02:35:08 +00:00
|
|
|
if !self.ctx.modifiers().shift() && self.ctx.mouse_mode() {
|
2019-02-07 22:36:45 +00:00
|
|
|
let code = match button {
|
|
|
|
MouseButton::Left => 0,
|
|
|
|
MouseButton::Middle => 1,
|
|
|
|
MouseButton::Right => 2,
|
2018-01-26 21:28:43 +00:00
|
|
|
// Can't properly report more than three buttons.
|
2019-02-07 22:36:45 +00:00
|
|
|
MouseButton::Other(_) => return,
|
2018-01-26 21:28:43 +00:00
|
|
|
};
|
2020-01-05 01:00:50 +00:00
|
|
|
self.mouse_report(code, ElementState::Released);
|
2016-12-22 18:43:06 +00:00
|
|
|
return;
|
2020-01-05 01:00:50 +00:00
|
|
|
} else if let (MouseButton::Left, MouseState::Url(url)) = (button, self.mouse_state()) {
|
2020-03-18 02:35:08 +00:00
|
|
|
self.ctx.launch_url(url);
|
2016-12-22 18:43:06 +00:00
|
|
|
}
|
2016-12-30 01:39:30 +00:00
|
|
|
|
2020-06-18 01:02:56 +00:00
|
|
|
self.ctx.scheduler_mut().unschedule(TimerId::SelectionScrolling);
|
2019-02-07 22:36:45 +00:00
|
|
|
self.copy_selection();
|
2016-11-24 04:25:37 +00:00
|
|
|
}
|
|
|
|
|
2020-01-05 01:00:50 +00:00
|
|
|
pub fn mouse_wheel_input(&mut self, delta: MouseScrollDelta, phase: TouchPhase) {
|
2016-11-25 03:50:34 +00:00
|
|
|
match delta {
|
2016-12-12 04:00:00 +00:00
|
|
|
MouseScrollDelta::LineDelta(_columns, lines) => {
|
2019-01-27 21:06:27 +00:00
|
|
|
let new_scroll_px = lines * self.ctx.size_info().cell_height;
|
2020-03-12 22:49:46 +00:00
|
|
|
self.scroll_terminal(f64::from(new_scroll_px));
|
2016-11-25 03:50:34 +00:00
|
|
|
},
|
2018-11-10 16:08:48 +00:00
|
|
|
MouseScrollDelta::PixelDelta(lpos) => {
|
2016-11-25 03:50:34 +00:00
|
|
|
match phase {
|
|
|
|
TouchPhase::Started => {
|
2020-05-05 22:50:23 +00:00
|
|
|
// Reset offset to zero.
|
2020-03-02 05:32:18 +00:00
|
|
|
self.ctx.mouse_mut().scroll_px = 0.;
|
2016-11-25 03:50:34 +00:00
|
|
|
},
|
|
|
|
TouchPhase::Moved => {
|
2020-03-02 05:32:18 +00:00
|
|
|
self.scroll_terminal(lpos.y);
|
2016-11-25 03:50:34 +00:00
|
|
|
},
|
|
|
|
_ => (),
|
|
|
|
}
|
2019-03-30 16:48:36 +00:00
|
|
|
},
|
2016-11-25 03:50:34 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-02 05:32:18 +00:00
|
|
|
fn scroll_terminal(&mut self, new_scroll_px: f64) {
|
2020-03-12 22:49:46 +00:00
|
|
|
let height = f64::from(self.ctx.size_info().cell_height);
|
2018-03-09 22:02:45 +00:00
|
|
|
|
2020-03-18 02:35:08 +00:00
|
|
|
if self.ctx.mouse_mode() {
|
2019-01-27 21:06:27 +00:00
|
|
|
self.ctx.mouse_mut().scroll_px += new_scroll_px;
|
|
|
|
|
2020-03-02 05:32:18 +00:00
|
|
|
let code = if new_scroll_px > 0. { 64 } else { 65 };
|
|
|
|
let lines = (self.ctx.mouse().scroll_px / height).abs() as i32;
|
2019-01-27 21:06:27 +00:00
|
|
|
|
|
|
|
for _ in 0..lines {
|
2020-01-05 01:00:50 +00:00
|
|
|
self.mouse_report(code, ElementState::Pressed);
|
2019-01-27 21:06:27 +00:00
|
|
|
}
|
2019-11-03 20:59:28 +00:00
|
|
|
} else if self
|
|
|
|
.ctx
|
|
|
|
.terminal()
|
|
|
|
.mode()
|
|
|
|
.contains(TermMode::ALT_SCREEN | TermMode::ALTERNATE_SCROLL)
|
2020-01-05 01:00:50 +00:00
|
|
|
&& !self.ctx.modifiers().shift()
|
2019-11-03 20:59:28 +00:00
|
|
|
{
|
2020-03-02 05:32:18 +00:00
|
|
|
let multiplier = f64::from(
|
2019-11-03 19:02:26 +00:00
|
|
|
self.ctx
|
|
|
|
.config()
|
2019-10-15 19:13:58 +00:00
|
|
|
.scrolling
|
|
|
|
.faux_multiplier()
|
2019-11-03 19:02:26 +00:00
|
|
|
.unwrap_or_else(|| self.ctx.config().scrolling.multiplier()),
|
2019-10-15 19:13:58 +00:00
|
|
|
);
|
|
|
|
self.ctx.mouse_mut().scroll_px += new_scroll_px * multiplier;
|
2019-01-27 21:06:27 +00:00
|
|
|
|
2020-03-02 05:32:18 +00:00
|
|
|
let cmd = if new_scroll_px > 0. { b'A' } else { b'B' };
|
|
|
|
let lines = (self.ctx.mouse().scroll_px / height).abs() as i32;
|
2019-01-27 21:06:27 +00:00
|
|
|
|
|
|
|
let mut content = Vec::with_capacity(lines as usize * 3);
|
|
|
|
for _ in 0..lines {
|
2018-03-09 10:17:47 +00:00
|
|
|
content.push(0x1b);
|
|
|
|
content.push(b'O');
|
|
|
|
content.push(cmd);
|
|
|
|
}
|
|
|
|
self.ctx.write_to_pty(content);
|
2018-02-16 02:35:49 +00:00
|
|
|
} else {
|
2020-03-02 05:32:18 +00:00
|
|
|
let multiplier = f64::from(self.ctx.config().scrolling.multiplier());
|
2019-01-27 21:06:27 +00:00
|
|
|
self.ctx.mouse_mut().scroll_px += new_scroll_px * multiplier;
|
|
|
|
|
|
|
|
let lines = self.ctx.mouse().scroll_px / height;
|
|
|
|
|
2020-05-05 22:50:23 +00:00
|
|
|
// Store absolute position of vi mode cursor.
|
2020-03-18 02:35:08 +00:00
|
|
|
let term = self.ctx.terminal();
|
|
|
|
let absolute = term.visible_to_buffer(term.vi_mode_cursor.point);
|
|
|
|
|
2019-01-27 21:06:27 +00:00
|
|
|
self.ctx.scroll(Scroll::Lines(lines as isize));
|
2020-03-18 02:35:08 +00:00
|
|
|
|
2020-05-05 22:50:23 +00:00
|
|
|
// Try to restore vi mode cursor position, to keep it above its previous content.
|
2020-03-18 02:35:08 +00:00
|
|
|
let term = self.ctx.terminal_mut();
|
|
|
|
term.vi_mode_cursor.point = term.grid().clamp_buffer_to_visible(absolute);
|
|
|
|
term.vi_mode_cursor.point.col = absolute.col;
|
|
|
|
|
2020-05-05 22:50:23 +00:00
|
|
|
// Update selection.
|
2020-03-19 00:30:30 +00:00
|
|
|
if term.mode().contains(TermMode::VI) {
|
|
|
|
let point = term.vi_mode_cursor.point;
|
|
|
|
if !self.ctx.selection_is_empty() {
|
|
|
|
self.ctx.update_selection(point, Side::Right);
|
|
|
|
}
|
2020-03-18 02:35:08 +00:00
|
|
|
}
|
2017-12-25 21:15:41 +00:00
|
|
|
}
|
2019-01-27 21:06:27 +00:00
|
|
|
|
|
|
|
self.ctx.mouse_mut().scroll_px %= height;
|
2017-12-25 21:15:41 +00:00
|
|
|
}
|
|
|
|
|
2017-05-29 16:51:49 +00:00
|
|
|
pub fn on_focus_change(&mut self, is_focused: bool) {
|
2019-02-07 22:36:45 +00:00
|
|
|
if self.ctx.terminal().mode().contains(TermMode::FOCUS_IN_OUT) {
|
2019-03-30 16:48:36 +00:00
|
|
|
let chr = if is_focused { "I" } else { "O" };
|
2017-05-29 16:51:49 +00:00
|
|
|
|
|
|
|
let msg = format!("\x1b[{}", chr);
|
|
|
|
self.ctx.write_to_pty(msg.into_bytes());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-05 01:00:50 +00:00
|
|
|
pub fn mouse_input(&mut self, state: ElementState, button: MouseButton) {
|
2019-01-03 01:56:28 +00:00
|
|
|
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,
|
|
|
|
_ => (),
|
2016-11-24 04:25:37 +00:00
|
|
|
}
|
|
|
|
|
2020-05-05 22:50:23 +00:00
|
|
|
// Skip normal mouse events if the message bar has been clicked.
|
2019-11-03 20:59:28 +00:00
|
|
|
if self.message_close_at_cursor() && state == ElementState::Pressed {
|
|
|
|
self.ctx.clear_selection();
|
|
|
|
self.ctx.pop_message();
|
|
|
|
|
2020-05-05 22:50:23 +00:00
|
|
|
// Reset cursor when message bar height changed or all messages are gone.
|
2019-11-03 20:59:28 +00:00
|
|
|
let size = self.ctx.size_info();
|
|
|
|
let current_lines = (size.lines() - self.ctx.terminal().grid().num_lines()).0;
|
|
|
|
let new_lines = self.ctx.message().map(|m| m.text(&size).len()).unwrap_or(0);
|
|
|
|
|
|
|
|
let new_icon = match current_lines.cmp(&new_lines) {
|
|
|
|
Ordering::Less => CursorIcon::Default,
|
|
|
|
Ordering::Equal => CursorIcon::Hand,
|
2019-11-03 23:14:23 +00:00
|
|
|
Ordering::Greater => {
|
2020-03-18 02:35:08 +00:00
|
|
|
if self.ctx.mouse_mode() {
|
2019-11-03 23:14:23 +00:00
|
|
|
CursorIcon::Default
|
|
|
|
} else {
|
|
|
|
CursorIcon::Text
|
|
|
|
}
|
2019-11-03 20:59:28 +00:00
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
self.ctx.window_mut().set_mouse_cursor(new_icon);
|
2019-02-07 22:36:45 +00:00
|
|
|
} else {
|
|
|
|
match state {
|
|
|
|
ElementState::Pressed => {
|
2020-01-05 01:00:50 +00:00
|
|
|
self.process_mouse_bindings(button);
|
|
|
|
self.on_mouse_press(button);
|
2019-02-07 22:36:45 +00:00
|
|
|
},
|
2020-01-05 01:00:50 +00:00
|
|
|
ElementState::Released => self.on_mouse_release(button),
|
2019-02-07 22:36:45 +00:00
|
|
|
}
|
2016-09-27 05:22:28 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-28 00:37:22 +00:00
|
|
|
/// Process key input.
|
2019-11-11 21:05:24 +00:00
|
|
|
pub fn key_input(&mut self, input: KeyboardInput) {
|
2018-10-16 18:46:26 +00:00
|
|
|
match input.state {
|
|
|
|
ElementState::Pressed => {
|
2017-07-20 17:50:50 +00:00
|
|
|
*self.ctx.received_count() = 0;
|
2019-09-28 00:37:22 +00:00
|
|
|
self.process_key_bindings(input);
|
2017-07-20 17:50:50 +00:00
|
|
|
},
|
2018-10-16 18:46:26 +00:00
|
|
|
ElementState::Released => *self.ctx.suppress_chars() = false,
|
2017-07-20 17:50:50 +00:00
|
|
|
}
|
|
|
|
}
|
2016-06-23 16:48:31 +00:00
|
|
|
|
2019-11-11 21:05:24 +00:00
|
|
|
/// Modifier state change.
|
|
|
|
pub fn modifiers_input(&mut self, modifiers: ModifiersState) {
|
|
|
|
*self.ctx.modifiers() = modifiers;
|
|
|
|
|
2020-05-05 22:50:23 +00:00
|
|
|
// Update mouse state and check for URL change.
|
2020-01-05 01:00:50 +00:00
|
|
|
let mouse_state = self.mouse_state();
|
2019-11-11 21:05:24 +00:00
|
|
|
self.update_url_state(&mouse_state);
|
|
|
|
self.ctx.window_mut().set_mouse_cursor(mouse_state.into());
|
|
|
|
}
|
|
|
|
|
2019-09-28 00:37:22 +00:00
|
|
|
/// Process a received character.
|
2017-07-20 17:50:50 +00:00
|
|
|
pub fn received_char(&mut self, c: char) {
|
2020-03-18 02:35:08 +00:00
|
|
|
if *self.ctx.suppress_chars() || self.ctx.terminal().mode().contains(TermMode::VI) {
|
2019-02-16 20:23:23 +00:00
|
|
|
return;
|
|
|
|
}
|
2017-01-07 06:04:51 +00:00
|
|
|
|
2020-05-06 21:58:43 +00:00
|
|
|
if self.ctx.config().ui_config.mouse.hide_when_typing {
|
|
|
|
self.ctx.window_mut().set_mouse_visible(false);
|
|
|
|
}
|
|
|
|
|
2019-02-16 20:23:23 +00:00
|
|
|
self.ctx.scroll(Scroll::Bottom);
|
|
|
|
self.ctx.clear_selection();
|
2018-12-20 00:33:42 +00:00
|
|
|
|
2019-02-16 20:23:23 +00:00
|
|
|
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[..]);
|
|
|
|
}
|
2017-07-20 17:50:50 +00:00
|
|
|
|
2019-11-03 19:02:26 +00:00
|
|
|
if self.ctx.config().alt_send_esc()
|
2019-02-16 20:23:23 +00:00
|
|
|
&& *self.ctx.received_count() == 0
|
2020-01-05 01:00:50 +00:00
|
|
|
&& self.ctx.modifiers().alt()
|
2019-02-16 20:23:23 +00:00
|
|
|
&& utf8_len == 1
|
|
|
|
{
|
|
|
|
bytes.insert(0, b'\x1b');
|
2016-06-23 16:48:31 +00:00
|
|
|
}
|
2019-02-16 20:23:23 +00:00
|
|
|
|
|
|
|
self.ctx.write_to_pty(bytes);
|
|
|
|
|
|
|
|
*self.ctx.received_count() += 1;
|
2016-06-23 16:48:31 +00:00
|
|
|
}
|
|
|
|
|
2019-11-11 21:05:24 +00:00
|
|
|
/// Reset mouse cursor based on modifier and terminal state.
|
|
|
|
#[inline]
|
|
|
|
pub fn reset_mouse_cursor(&mut self) {
|
2020-01-05 01:00:50 +00:00
|
|
|
let mouse_state = self.mouse_state();
|
2019-11-11 21:05:24 +00:00
|
|
|
self.ctx.window_mut().set_mouse_cursor(mouse_state.into());
|
|
|
|
}
|
|
|
|
|
2019-09-28 00:37:22 +00:00
|
|
|
/// Attempt to find a binding and execute its action.
|
2016-11-15 17:38:50 +00:00
|
|
|
///
|
|
|
|
/// The provided mode, mods, and key must match what is allowed by a binding
|
|
|
|
/// for its action to be executed.
|
2019-09-28 00:37:22 +00:00
|
|
|
fn process_key_bindings(&mut self, input: KeyboardInput) {
|
2020-01-10 00:44:41 +00:00
|
|
|
let mods = *self.ctx.modifiers();
|
2019-10-05 00:29:26 +00:00
|
|
|
let mut suppress_chars = None;
|
2019-09-28 00:37:22 +00:00
|
|
|
|
2019-11-03 19:02:26 +00:00
|
|
|
for i in 0..self.ctx.config().ui_config.key_bindings.len() {
|
|
|
|
let binding = &self.ctx.config().ui_config.key_bindings[i];
|
|
|
|
|
2019-10-05 00:29:26 +00:00
|
|
|
let key = match (binding.trigger, input.virtual_keycode) {
|
|
|
|
(Key::Scancode(_), _) => Key::Scancode(input.scancode),
|
2020-01-06 22:06:09 +00:00
|
|
|
(_, Some(key)) => Key::Keycode(key),
|
2019-10-05 00:29:26 +00:00
|
|
|
_ => continue,
|
|
|
|
};
|
|
|
|
|
2020-01-10 00:44:41 +00:00
|
|
|
if binding.is_triggered_by(*self.ctx.terminal().mode(), mods, &key) {
|
2020-05-05 22:50:23 +00:00
|
|
|
// Binding was triggered; run the action.
|
2019-11-03 19:02:26 +00:00
|
|
|
let binding = binding.clone();
|
2020-01-10 00:44:41 +00:00
|
|
|
binding.execute(&mut self.ctx);
|
2016-11-15 17:38:50 +00:00
|
|
|
|
2020-05-05 22:50:23 +00:00
|
|
|
// Don't suppress when there has been a `ReceiveChar` action.
|
2019-10-05 00:29:26 +00:00
|
|
|
*suppress_chars.get_or_insert(true) &= binding.action != Action::ReceiveChar;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-05 22:50:23 +00:00
|
|
|
// Don't suppress char if no bindings were triggered.
|
2019-10-05 00:29:26 +00:00
|
|
|
*self.ctx.suppress_chars() = suppress_chars.unwrap_or(false);
|
2016-11-15 17:38:50 +00:00
|
|
|
}
|
|
|
|
|
2019-09-28 00:37:22 +00:00
|
|
|
/// Attempt to find a binding and execute its action.
|
2016-11-15 17:38:50 +00:00
|
|
|
///
|
|
|
|
/// The provided mode, mods, and key must match what is allowed by a binding
|
|
|
|
/// for its action to be executed.
|
2020-01-05 01:00:50 +00:00
|
|
|
fn process_mouse_bindings(&mut self, button: MouseButton) {
|
2020-01-10 00:44:41 +00:00
|
|
|
let mods = *self.ctx.modifiers();
|
|
|
|
let mode = *self.ctx.terminal().mode();
|
2020-03-18 02:35:08 +00:00
|
|
|
let mouse_mode = self.ctx.mouse_mode();
|
2020-01-10 00:44:41 +00:00
|
|
|
|
2019-11-03 19:02:26 +00:00
|
|
|
for i in 0..self.ctx.config().ui_config.mouse_bindings.len() {
|
2020-01-10 00:44:41 +00:00
|
|
|
let mut binding = self.ctx.config().ui_config.mouse_bindings[i].clone();
|
2019-11-03 19:02:26 +00:00
|
|
|
|
2020-05-05 22:50:23 +00:00
|
|
|
// Require shift for all modifiers when mouse mode is active.
|
2020-01-10 00:44:41 +00:00
|
|
|
if mouse_mode {
|
|
|
|
binding.mods |= ModifiersState::SHIFT;
|
|
|
|
}
|
2019-11-03 19:02:26 +00:00
|
|
|
|
2020-01-10 00:44:41 +00:00
|
|
|
if binding.is_triggered_by(mode, mods, &button) {
|
|
|
|
binding.execute(&mut self.ctx);
|
2016-06-23 16:48:31 +00:00
|
|
|
}
|
|
|
|
}
|
2016-11-15 17:38:50 +00:00
|
|
|
}
|
2019-02-07 22:36:45 +00:00
|
|
|
|
2019-11-03 20:59:28 +00:00
|
|
|
/// Check if the cursor is hovering above the message bar.
|
|
|
|
fn message_at_cursor(&mut self) -> bool {
|
|
|
|
self.ctx.mouse().line >= self.ctx.terminal().grid().num_lines()
|
2019-02-07 22:36:45 +00:00
|
|
|
}
|
|
|
|
|
2020-05-05 22:50:23 +00:00
|
|
|
/// Whether the point is over the message bar's close button.
|
2019-11-03 20:59:28 +00:00
|
|
|
fn message_close_at_cursor(&self) -> bool {
|
|
|
|
let mouse = self.ctx.mouse();
|
|
|
|
mouse.inside_grid
|
|
|
|
&& mouse.column + message_bar::CLOSE_BUTTON_TEXT.len() >= self.ctx.size_info().cols()
|
|
|
|
&& mouse.line == self.ctx.terminal().grid().num_lines()
|
2019-02-07 22:36:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Copy text selection.
|
|
|
|
fn copy_selection(&mut self) {
|
2019-11-03 19:02:26 +00:00
|
|
|
if self.ctx.config().selection.save_to_clipboard {
|
2019-06-09 11:46:31 +00:00
|
|
|
self.ctx.copy_selection(ClipboardType::Clipboard);
|
2019-02-07 22:36:45 +00:00
|
|
|
}
|
2019-06-09 11:46:31 +00:00
|
|
|
self.ctx.copy_selection(ClipboardType::Selection);
|
2019-02-07 22:36:45 +00:00
|
|
|
}
|
2019-08-24 23:18:51 +00:00
|
|
|
|
2019-11-03 20:59:28 +00:00
|
|
|
/// Trigger redraw when URL highlight changed.
|
2019-08-24 23:18:51 +00:00
|
|
|
#[inline]
|
2019-11-03 20:59:28 +00:00
|
|
|
fn update_url_state(&mut self, mouse_state: &MouseState) {
|
|
|
|
if let MouseState::Url(url) = mouse_state {
|
|
|
|
if Some(url) != self.highlighted_url.as_ref() {
|
|
|
|
self.ctx.terminal_mut().dirty = true;
|
|
|
|
}
|
|
|
|
} else if self.highlighted_url.is_some() {
|
|
|
|
self.ctx.terminal_mut().dirty = true;
|
|
|
|
}
|
2019-10-05 00:29:26 +00:00
|
|
|
}
|
2019-08-24 23:18:51 +00:00
|
|
|
|
2019-11-11 21:05:24 +00:00
|
|
|
/// Location of the mouse cursor.
|
2020-01-05 01:00:50 +00:00
|
|
|
fn mouse_state(&mut self) -> MouseState {
|
2020-05-05 22:50:23 +00:00
|
|
|
// Check message bar before URL to ignore URLs in the message bar.
|
2019-11-11 21:05:24 +00:00
|
|
|
if self.message_close_at_cursor() {
|
|
|
|
return MouseState::MessageBarButton;
|
|
|
|
} else if self.message_at_cursor() {
|
|
|
|
return MouseState::MessageBar;
|
|
|
|
}
|
|
|
|
|
2020-03-18 02:35:08 +00:00
|
|
|
let mouse_mode = self.ctx.mouse_mode();
|
|
|
|
|
2020-05-05 22:50:23 +00:00
|
|
|
// Check for URL at mouse cursor.
|
2020-01-05 01:00:50 +00:00
|
|
|
let mods = *self.ctx.modifiers();
|
2020-03-18 02:35:08 +00:00
|
|
|
let highlighted_url = self.ctx.urls().highlighted(
|
|
|
|
self.ctx.config(),
|
|
|
|
self.ctx.mouse(),
|
|
|
|
mods,
|
|
|
|
mouse_mode,
|
|
|
|
!self.ctx.selection_is_empty(),
|
|
|
|
);
|
2019-11-11 21:05:24 +00:00
|
|
|
|
|
|
|
if let Some(url) = highlighted_url {
|
|
|
|
return MouseState::Url(url);
|
|
|
|
}
|
|
|
|
|
2020-05-05 22:50:23 +00:00
|
|
|
// Check mouse mode if location is not special.
|
2020-03-18 02:35:08 +00:00
|
|
|
if !self.ctx.modifiers().shift() && mouse_mode {
|
2019-11-11 21:05:24 +00:00
|
|
|
MouseState::Mouse
|
|
|
|
} else {
|
|
|
|
MouseState::Text
|
|
|
|
}
|
2019-08-24 23:18:51 +00:00
|
|
|
}
|
2020-06-18 01:02:56 +00:00
|
|
|
|
|
|
|
/// Handle automatic scrolling when selecting above/below the window.
|
|
|
|
fn update_selection_scrolling(&mut self, mouse_y: i32) {
|
|
|
|
let size_info = self.ctx.size_info();
|
|
|
|
let scheduler = self.ctx.scheduler_mut();
|
|
|
|
|
|
|
|
// Scale constants by DPI.
|
|
|
|
let min_height = (MIN_SELECTION_SCROLLING_HEIGHT * size_info.dpr) as i32;
|
|
|
|
let step = (SELECTION_SCROLLING_STEP * size_info.dpr) as i32;
|
|
|
|
|
|
|
|
// Compute the height of the scrolling areas.
|
|
|
|
let end_top = max(min_height, size_info.padding_y as i32);
|
|
|
|
let height_bottom = max(min_height, size_info.padding_bottom() as i32);
|
|
|
|
let start_bottom = size_info.height as i32 - height_bottom;
|
|
|
|
|
|
|
|
// Get distance from closest window boundary.
|
|
|
|
let delta = if mouse_y < end_top {
|
|
|
|
end_top - mouse_y + step
|
|
|
|
} else if mouse_y >= start_bottom {
|
|
|
|
start_bottom - mouse_y - step
|
|
|
|
} else {
|
|
|
|
scheduler.unschedule(TimerId::SelectionScrolling);
|
|
|
|
return;
|
|
|
|
};
|
|
|
|
|
|
|
|
// Scale number of lines scrolled based on distance to boundary.
|
|
|
|
let delta = delta as isize / step as isize;
|
|
|
|
let event = Event::Scroll(Scroll::Lines(delta));
|
|
|
|
|
|
|
|
// Schedule event.
|
|
|
|
match scheduler.get_mut(TimerId::SelectionScrolling) {
|
|
|
|
Some(timer) => timer.event = event.into(),
|
|
|
|
None => {
|
|
|
|
scheduler.schedule(
|
|
|
|
event.into(),
|
|
|
|
SELECTION_SCROLLING_INTERVAL,
|
|
|
|
true,
|
|
|
|
TimerId::SelectionScrolling,
|
|
|
|
);
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
2016-06-23 16:48:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
2020-06-18 01:02:56 +00:00
|
|
|
use super::*;
|
2019-10-05 00:29:26 +00:00
|
|
|
|
2020-06-18 01:02:56 +00:00
|
|
|
use glutin::event::{Event as GlutinEvent, VirtualKeyCode, WindowEvent};
|
2016-09-27 15:27:39 +00:00
|
|
|
|
2020-06-18 01:02:56 +00:00
|
|
|
use alacritty_terminal::event::Event as TerminalEvent;
|
|
|
|
use alacritty_terminal::message_bar::MessageBuffer;
|
|
|
|
use alacritty_terminal::selection::Selection;
|
2017-01-15 01:53:48 +00:00
|
|
|
|
2020-06-18 01:02:56 +00:00
|
|
|
use crate::config::ClickHandler;
|
2016-06-23 16:48:31 +00:00
|
|
|
|
2016-11-15 17:38:50 +00:00
|
|
|
const KEY: VirtualKeyCode = VirtualKeyCode::Key0;
|
|
|
|
|
2019-10-05 00:29:26 +00:00
|
|
|
struct MockEventProxy;
|
2019-07-15 21:32:48 +00:00
|
|
|
|
2019-10-05 00:29:26 +00:00
|
|
|
impl EventListener for MockEventProxy {
|
|
|
|
fn send_event(&self, _event: TerminalEvent) {}
|
2019-07-15 21:32:48 +00:00
|
|
|
}
|
|
|
|
|
2019-10-05 00:29:26 +00:00
|
|
|
struct ActionContext<'a, T> {
|
|
|
|
pub terminal: &'a mut Term<T>,
|
2017-06-17 17:29:59 +00:00
|
|
|
pub selection: &'a mut Option<Selection>,
|
2017-01-15 01:53:48 +00:00
|
|
|
pub size_info: &'a SizeInfo,
|
|
|
|
pub mouse: &'a mut Mouse,
|
2020-06-06 21:33:20 +00:00
|
|
|
pub clipboard: &'a mut Clipboard,
|
2019-10-05 00:29:26 +00:00
|
|
|
pub message_buffer: &'a mut MessageBuffer,
|
2017-07-20 17:50:50 +00:00
|
|
|
pub received_count: usize,
|
|
|
|
pub suppress_chars: bool,
|
2019-11-11 21:05:24 +00:00
|
|
|
pub modifiers: ModifiersState,
|
2019-11-03 19:02:26 +00:00
|
|
|
config: &'a Config,
|
2017-01-15 01:53:48 +00:00
|
|
|
}
|
|
|
|
|
2019-10-05 00:29:26 +00:00
|
|
|
impl<'a, T: EventListener> super::ActionContext<T> for ActionContext<'a, T> {
|
2019-01-05 20:47:12 +00:00
|
|
|
fn write_to_pty<B: Into<Cow<'static, [u8]>>>(&mut self, _val: B) {}
|
2019-03-30 16:48:36 +00:00
|
|
|
|
2019-01-05 20:47:12 +00:00
|
|
|
fn update_selection(&mut self, _point: Point, _side: Side) {}
|
2019-03-30 16:48:36 +00:00
|
|
|
|
2020-04-10 18:23:50 +00:00
|
|
|
fn start_selection(&mut self, _ty: SelectionType, _point: Point, _side: Side) {}
|
2019-03-30 16:48:36 +00:00
|
|
|
|
2020-03-18 02:35:08 +00:00
|
|
|
fn toggle_selection(&mut self, _ty: SelectionType, _point: Point, _side: Side) {}
|
2019-06-20 15:56:09 +00:00
|
|
|
|
2019-04-28 20:21:39 +00:00
|
|
|
fn copy_selection(&mut self, _: ClipboardType) {}
|
2019-03-30 16:48:36 +00:00
|
|
|
|
2019-01-05 20:47:12 +00:00
|
|
|
fn clear_selection(&mut self) {}
|
2019-03-30 16:48:36 +00:00
|
|
|
|
2019-01-05 20:47:12 +00:00
|
|
|
fn spawn_new_instance(&mut self) {}
|
2017-01-15 01:53:48 +00:00
|
|
|
|
2019-10-05 00:29:26 +00:00
|
|
|
fn change_font_size(&mut self, _delta: f32) {}
|
2019-04-23 19:05:47 +00:00
|
|
|
|
2019-10-05 00:29:26 +00:00
|
|
|
fn reset_font_size(&mut self) {}
|
2019-04-23 19:05:47 +00:00
|
|
|
|
2019-10-05 00:29:26 +00:00
|
|
|
fn terminal(&self) -> &Term<T> {
|
2019-02-07 22:36:45 +00:00
|
|
|
&self.terminal
|
|
|
|
}
|
|
|
|
|
2019-10-05 00:29:26 +00:00
|
|
|
fn terminal_mut(&mut self) -> &mut Term<T> {
|
2019-02-07 22:36:45 +00:00
|
|
|
&mut self.terminal
|
2017-01-15 01:53:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn size_info(&self) -> SizeInfo {
|
|
|
|
*self.size_info
|
|
|
|
}
|
|
|
|
|
2018-10-22 19:39:26 +00:00
|
|
|
fn selection_is_empty(&self) -> bool {
|
|
|
|
true
|
|
|
|
}
|
|
|
|
|
2018-03-25 19:09:18 +00:00
|
|
|
fn scroll(&mut self, scroll: Scroll) {
|
|
|
|
self.terminal.scroll_display(scroll);
|
|
|
|
}
|
|
|
|
|
2017-01-15 01:53:48 +00:00
|
|
|
fn mouse_coords(&self) -> Option<Point> {
|
2019-10-05 00:29:26 +00:00
|
|
|
let x = self.mouse.x as usize;
|
|
|
|
let y = self.mouse.y as usize;
|
|
|
|
|
2019-11-03 20:59:28 +00:00
|
|
|
if self.size_info.contains_point(x, y) {
|
2019-10-05 00:29:26 +00:00
|
|
|
Some(self.size_info.pixels_to_coords(x, y))
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
2017-01-15 01:53:48 +00:00
|
|
|
}
|
|
|
|
|
2020-03-18 02:35:08 +00:00
|
|
|
fn mouse_mode(&self) -> bool {
|
|
|
|
false
|
|
|
|
}
|
|
|
|
|
2017-01-15 01:53:48 +00:00
|
|
|
#[inline]
|
|
|
|
fn mouse_mut(&mut self) -> &mut Mouse {
|
|
|
|
self.mouse
|
|
|
|
}
|
2018-09-27 12:12:49 +00:00
|
|
|
|
|
|
|
#[inline]
|
|
|
|
fn mouse(&self) -> &Mouse {
|
|
|
|
self.mouse
|
|
|
|
}
|
|
|
|
|
2017-07-20 17:50:50 +00:00
|
|
|
fn received_count(&mut self) -> &mut usize {
|
|
|
|
&mut self.received_count
|
|
|
|
}
|
2019-01-05 20:47:12 +00:00
|
|
|
|
2017-07-20 17:50:50 +00:00
|
|
|
fn suppress_chars(&mut self) -> &mut bool {
|
|
|
|
&mut self.suppress_chars
|
|
|
|
}
|
2019-01-05 20:47:12 +00:00
|
|
|
|
2019-11-11 21:05:24 +00:00
|
|
|
fn modifiers(&mut self) -> &mut ModifiersState {
|
2019-08-24 23:18:51 +00:00
|
|
|
&mut self.modifiers
|
2017-07-20 17:50:50 +00:00
|
|
|
}
|
2019-10-05 00:29:26 +00:00
|
|
|
|
|
|
|
fn window(&self) -> &Window {
|
|
|
|
unimplemented!();
|
|
|
|
}
|
|
|
|
|
|
|
|
fn window_mut(&mut self) -> &mut Window {
|
|
|
|
unimplemented!();
|
|
|
|
}
|
|
|
|
|
|
|
|
fn pop_message(&mut self) {
|
|
|
|
self.message_buffer.pop();
|
|
|
|
}
|
|
|
|
|
|
|
|
fn message(&self) -> Option<&Message> {
|
|
|
|
self.message_buffer.message()
|
|
|
|
}
|
2019-11-03 19:02:26 +00:00
|
|
|
|
|
|
|
fn config(&self) -> &Config {
|
|
|
|
self.config
|
|
|
|
}
|
2020-02-07 13:44:11 +00:00
|
|
|
|
2020-06-18 01:02:56 +00:00
|
|
|
fn clipboard_mut(&mut self) -> &mut Clipboard {
|
|
|
|
self.clipboard
|
|
|
|
}
|
|
|
|
|
|
|
|
fn event_loop(&self) -> &EventLoopWindowTarget<Event> {
|
2020-02-07 13:44:11 +00:00
|
|
|
unimplemented!();
|
|
|
|
}
|
2020-03-18 02:35:08 +00:00
|
|
|
|
|
|
|
fn urls(&self) -> &Urls {
|
|
|
|
unimplemented!();
|
|
|
|
}
|
|
|
|
|
2020-06-18 01:02:56 +00:00
|
|
|
fn launch_url(&self, _: Url) {
|
|
|
|
unimplemented!();
|
2020-06-06 21:33:20 +00:00
|
|
|
}
|
|
|
|
|
2020-06-23 09:57:15 +00:00
|
|
|
fn scheduler_mut(&mut self) -> &mut Scheduler {
|
2020-03-18 02:35:08 +00:00
|
|
|
unimplemented!();
|
|
|
|
}
|
2017-01-15 01:53:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
macro_rules! test_clickstate {
|
|
|
|
{
|
|
|
|
name: $name:ident,
|
|
|
|
initial_state: $initial_state:expr,
|
2019-01-08 07:37:58 +00:00
|
|
|
initial_button: $initial_button:expr,
|
2017-01-15 01:53:48 +00:00
|
|
|
input: $input:expr,
|
2020-04-10 18:23:50 +00:00
|
|
|
end_state: $end_state:expr,
|
2017-01-15 01:53:48 +00:00
|
|
|
} => {
|
|
|
|
#[test]
|
|
|
|
fn $name() {
|
2019-10-05 00:29:26 +00:00
|
|
|
let mut cfg = Config::default();
|
|
|
|
cfg.ui_config.mouse = crate::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(),
|
|
|
|
};
|
|
|
|
|
2017-01-15 01:53:48 +00:00
|
|
|
let size = SizeInfo {
|
|
|
|
width: 21.0,
|
|
|
|
height: 51.0,
|
|
|
|
cell_width: 3.0,
|
|
|
|
cell_height: 3.0,
|
2020-03-02 05:32:18 +00:00
|
|
|
padding_x: 0.,
|
|
|
|
padding_y: 0.,
|
2018-11-10 16:08:48 +00:00
|
|
|
dpr: 1.0,
|
2017-01-15 01:53:48 +00:00
|
|
|
};
|
|
|
|
|
2020-06-06 21:33:20 +00:00
|
|
|
let mut clipboard = Clipboard::new_nop();
|
|
|
|
|
|
|
|
let mut terminal = Term::new(&cfg, &size, MockEventProxy);
|
2017-01-15 01:53:48 +00:00
|
|
|
|
|
|
|
let mut mouse = Mouse::default();
|
|
|
|
mouse.click_state = $initial_state;
|
|
|
|
|
2017-06-17 17:29:59 +00:00
|
|
|
let mut selection = None;
|
|
|
|
|
2019-10-05 00:29:26 +00:00
|
|
|
let mut message_buffer = MessageBuffer::new();
|
|
|
|
|
2017-01-15 01:53:48 +00:00
|
|
|
let context = ActionContext {
|
|
|
|
terminal: &mut terminal,
|
2017-06-17 17:29:59 +00:00
|
|
|
selection: &mut selection,
|
2017-01-15 01:53:48 +00:00
|
|
|
mouse: &mut mouse,
|
|
|
|
size_info: &size,
|
2020-06-06 21:33:20 +00:00
|
|
|
clipboard: &mut clipboard,
|
2017-07-20 17:50:50 +00:00
|
|
|
received_count: 0,
|
|
|
|
suppress_chars: false,
|
2019-11-11 21:05:24 +00:00
|
|
|
modifiers: Default::default(),
|
2019-10-05 00:29:26 +00:00
|
|
|
message_buffer: &mut message_buffer,
|
2019-11-03 19:02:26 +00:00
|
|
|
config: &cfg,
|
2017-01-15 01:53:48 +00:00
|
|
|
};
|
|
|
|
|
2020-03-18 02:35:08 +00:00
|
|
|
let mut processor = Processor::new(context, &None);
|
2017-01-15 01:53:48 +00:00
|
|
|
|
2020-06-18 01:02:56 +00:00
|
|
|
let event: GlutinEvent::<'_, TerminalEvent> = $input;
|
|
|
|
if let GlutinEvent::WindowEvent {
|
2019-11-03 20:59:28 +00:00
|
|
|
event: WindowEvent::MouseInput {
|
|
|
|
state,
|
|
|
|
button,
|
|
|
|
..
|
|
|
|
},
|
|
|
|
..
|
2020-01-10 01:51:37 +00:00
|
|
|
} = event
|
2019-11-03 20:59:28 +00:00
|
|
|
{
|
2020-01-05 01:00:50 +00:00
|
|
|
processor.mouse_input(state, button);
|
2017-01-15 01:53:48 +00:00
|
|
|
};
|
|
|
|
|
2020-04-10 18:23:50 +00:00
|
|
|
assert_eq!(processor.ctx.mouse.click_state, $end_state);
|
2017-01-15 01:53:48 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-06-23 16:48:31 +00:00
|
|
|
macro_rules! test_process_binding {
|
|
|
|
{
|
|
|
|
name: $name:ident,
|
|
|
|
binding: $binding:expr,
|
2016-12-27 00:00:27 +00:00
|
|
|
triggers: $triggers:expr,
|
2016-06-23 16:48:31 +00:00
|
|
|
mode: $mode:expr,
|
2020-01-05 01:00:50 +00:00
|
|
|
mods: $mods:expr,
|
2016-06-23 16:48:31 +00:00
|
|
|
} => {
|
|
|
|
#[test]
|
|
|
|
fn $name() {
|
2016-12-27 00:00:27 +00:00
|
|
|
if $triggers {
|
2020-01-10 00:44:41 +00:00
|
|
|
assert!($binding.is_triggered_by($mode, $mods, &KEY));
|
2016-12-27 00:00:27 +00:00
|
|
|
} else {
|
2020-01-10 00:44:41 +00:00
|
|
|
assert!(!$binding.is_triggered_by($mode, $mods, &KEY));
|
2016-12-27 00:00:27 +00:00
|
|
|
}
|
2016-06-23 16:48:31 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-01-15 01:53:48 +00:00
|
|
|
test_clickstate! {
|
|
|
|
name: single_click,
|
|
|
|
initial_state: ClickState::None,
|
2019-01-08 07:37:58 +00:00
|
|
|
initial_button: MouseButton::Other(0),
|
2020-06-18 01:02:56 +00:00
|
|
|
input: GlutinEvent::WindowEvent {
|
2017-07-20 17:50:50 +00:00
|
|
|
event: WindowEvent::MouseInput {
|
|
|
|
state: ElementState::Pressed,
|
|
|
|
button: MouseButton::Left,
|
2020-04-10 18:23:50 +00:00
|
|
|
device_id: unsafe { std::mem::transmute_copy(&0) },
|
2017-12-29 23:49:20 +00:00
|
|
|
modifiers: ModifiersState::default(),
|
2017-07-20 17:50:50 +00:00
|
|
|
},
|
2020-04-10 18:23:50 +00:00
|
|
|
window_id: unsafe { std::mem::transmute_copy(&0) },
|
2017-07-20 17:50:50 +00:00
|
|
|
},
|
2017-01-15 01:53:48 +00:00
|
|
|
end_state: ClickState::Click,
|
2020-04-10 18:23:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
test_clickstate! {
|
|
|
|
name: single_right_click,
|
|
|
|
initial_state: ClickState::None,
|
|
|
|
initial_button: MouseButton::Other(0),
|
2020-06-18 01:02:56 +00:00
|
|
|
input: GlutinEvent::WindowEvent {
|
2020-04-10 18:23:50 +00:00
|
|
|
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) },
|
|
|
|
},
|
2020-06-23 09:57:15 +00:00
|
|
|
end_state: ClickState::Click,
|
2020-04-10 18:23:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
test_clickstate! {
|
|
|
|
name: single_middle_click,
|
|
|
|
initial_state: ClickState::None,
|
|
|
|
initial_button: MouseButton::Other(0),
|
2020-06-18 01:02:56 +00:00
|
|
|
input: GlutinEvent::WindowEvent {
|
2020-04-10 18:23:50 +00:00
|
|
|
event: WindowEvent::MouseInput {
|
|
|
|
state: ElementState::Pressed,
|
|
|
|
button: MouseButton::Middle,
|
|
|
|
device_id: unsafe { std::mem::transmute_copy(&0) },
|
|
|
|
modifiers: ModifiersState::default(),
|
|
|
|
},
|
|
|
|
window_id: unsafe { std::mem::transmute_copy(&0) },
|
|
|
|
},
|
|
|
|
end_state: ClickState::None,
|
2017-01-15 01:53:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
test_clickstate! {
|
|
|
|
name: double_click,
|
|
|
|
initial_state: ClickState::Click,
|
2019-01-08 07:37:58 +00:00
|
|
|
initial_button: MouseButton::Left,
|
2020-06-18 01:02:56 +00:00
|
|
|
input: GlutinEvent::WindowEvent {
|
2017-07-20 17:50:50 +00:00
|
|
|
event: WindowEvent::MouseInput {
|
|
|
|
state: ElementState::Pressed,
|
|
|
|
button: MouseButton::Left,
|
2020-04-10 18:23:50 +00:00
|
|
|
device_id: unsafe { std::mem::transmute_copy(&0) },
|
2017-12-29 23:49:20 +00:00
|
|
|
modifiers: ModifiersState::default(),
|
2017-07-20 17:50:50 +00:00
|
|
|
},
|
2020-04-10 18:23:50 +00:00
|
|
|
window_id: unsafe { std::mem::transmute_copy(&0) },
|
2017-07-20 17:50:50 +00:00
|
|
|
},
|
2017-01-15 01:53:48 +00:00
|
|
|
end_state: ClickState::DoubleClick,
|
|
|
|
}
|
|
|
|
|
|
|
|
test_clickstate! {
|
|
|
|
name: triple_click,
|
|
|
|
initial_state: ClickState::DoubleClick,
|
2019-01-08 07:37:58 +00:00
|
|
|
initial_button: MouseButton::Left,
|
2020-06-18 01:02:56 +00:00
|
|
|
input: GlutinEvent::WindowEvent {
|
2017-07-20 17:50:50 +00:00
|
|
|
event: WindowEvent::MouseInput {
|
|
|
|
state: ElementState::Pressed,
|
|
|
|
button: MouseButton::Left,
|
2020-04-10 18:23:50 +00:00
|
|
|
device_id: unsafe { std::mem::transmute_copy(&0) },
|
2017-12-29 23:49:20 +00:00
|
|
|
modifiers: ModifiersState::default(),
|
2017-07-20 17:50:50 +00:00
|
|
|
},
|
2020-04-10 18:23:50 +00:00
|
|
|
window_id: unsafe { std::mem::transmute_copy(&0) },
|
2017-07-20 17:50:50 +00:00
|
|
|
},
|
2017-01-15 01:53:48 +00:00
|
|
|
end_state: ClickState::TripleClick,
|
|
|
|
}
|
|
|
|
|
2019-01-08 07:37:58 +00:00
|
|
|
test_clickstate! {
|
|
|
|
name: multi_click_separate_buttons,
|
|
|
|
initial_state: ClickState::DoubleClick,
|
|
|
|
initial_button: MouseButton::Left,
|
2020-06-18 01:02:56 +00:00
|
|
|
input: GlutinEvent::WindowEvent {
|
2019-01-08 07:37:58 +00:00
|
|
|
event: WindowEvent::MouseInput {
|
|
|
|
state: ElementState::Pressed,
|
|
|
|
button: MouseButton::Right,
|
2020-04-10 18:23:50 +00:00
|
|
|
device_id: unsafe { std::mem::transmute_copy(&0) },
|
2019-01-08 07:37:58 +00:00
|
|
|
modifiers: ModifiersState::default(),
|
|
|
|
},
|
2020-04-10 18:23:50 +00:00
|
|
|
window_id: unsafe { std::mem::transmute_copy(&0) },
|
2019-01-08 07:37:58 +00:00
|
|
|
},
|
2020-06-23 09:57:15 +00:00
|
|
|
end_state: ClickState::Click,
|
2019-01-08 07:37:58 +00:00
|
|
|
}
|
|
|
|
|
2016-06-23 16:48:31 +00:00
|
|
|
test_process_binding! {
|
|
|
|
name: process_binding_nomode_shiftmod_require_shift,
|
2020-01-05 01:00:50 +00:00
|
|
|
binding: Binding { trigger: KEY, mods: ModifiersState::SHIFT, action: Action::from("\x1b[1;2D"), mode: TermMode::NONE, notmode: TermMode::NONE },
|
2016-12-27 00:00:27 +00:00
|
|
|
triggers: true,
|
2018-03-08 21:50:04 +00:00
|
|
|
mode: TermMode::NONE,
|
2020-01-05 01:00:50 +00:00
|
|
|
mods: ModifiersState::SHIFT,
|
2016-06-23 16:48:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
test_process_binding! {
|
|
|
|
name: process_binding_nomode_nomod_require_shift,
|
2020-01-05 01:00:50 +00:00
|
|
|
binding: Binding { trigger: KEY, mods: ModifiersState::SHIFT, action: Action::from("\x1b[1;2D"), mode: TermMode::NONE, notmode: TermMode::NONE },
|
2016-12-27 00:00:27 +00:00
|
|
|
triggers: false,
|
2018-03-08 21:50:04 +00:00
|
|
|
mode: TermMode::NONE,
|
2020-01-05 01:00:50 +00:00
|
|
|
mods: ModifiersState::empty(),
|
2016-06-23 16:48:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
test_process_binding! {
|
|
|
|
name: process_binding_nomode_controlmod,
|
2020-01-05 01:00:50 +00:00
|
|
|
binding: Binding { trigger: KEY, mods: ModifiersState::CTRL, action: Action::from("\x1b[1;5D"), mode: TermMode::NONE, notmode: TermMode::NONE },
|
2016-12-27 00:00:27 +00:00
|
|
|
triggers: true,
|
2018-03-08 21:50:04 +00:00
|
|
|
mode: TermMode::NONE,
|
2020-01-05 01:00:50 +00:00
|
|
|
mods: ModifiersState::CTRL,
|
2016-06-23 16:48:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
test_process_binding! {
|
|
|
|
name: process_binding_nomode_nomod_require_not_appcursor,
|
2020-01-05 01:00:50 +00:00
|
|
|
binding: Binding { trigger: KEY, mods: ModifiersState::empty(), action: Action::from("\x1b[D"), mode: TermMode::NONE, notmode: TermMode::APP_CURSOR },
|
2016-12-27 00:00:27 +00:00
|
|
|
triggers: true,
|
2018-03-08 21:50:04 +00:00
|
|
|
mode: TermMode::NONE,
|
2020-01-05 01:00:50 +00:00
|
|
|
mods: ModifiersState::empty(),
|
2016-06-23 16:48:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
test_process_binding! {
|
|
|
|
name: process_binding_appcursormode_nomod_require_appcursor,
|
2020-01-05 01:00:50 +00:00
|
|
|
binding: Binding { trigger: KEY, mods: ModifiersState::empty(), action: Action::from("\x1bOD"), mode: TermMode::APP_CURSOR, notmode: TermMode::NONE },
|
2016-12-27 00:00:27 +00:00
|
|
|
triggers: true,
|
2018-03-08 21:50:04 +00:00
|
|
|
mode: TermMode::APP_CURSOR,
|
2020-01-05 01:00:50 +00:00
|
|
|
mods: ModifiersState::empty(),
|
2016-06-23 16:48:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
test_process_binding! {
|
|
|
|
name: process_binding_nomode_nomod_require_appcursor,
|
2020-01-05 01:00:50 +00:00
|
|
|
binding: Binding { trigger: KEY, mods: ModifiersState::empty(), action: Action::from("\x1bOD"), mode: TermMode::APP_CURSOR, notmode: TermMode::NONE },
|
2016-12-27 00:00:27 +00:00
|
|
|
triggers: false,
|
2018-03-08 21:50:04 +00:00
|
|
|
mode: TermMode::NONE,
|
2020-01-05 01:00:50 +00:00
|
|
|
mods: ModifiersState::empty(),
|
2016-06-23 16:48:31 +00:00
|
|
|
}
|
2016-07-30 01:27:45 +00:00
|
|
|
|
|
|
|
test_process_binding! {
|
|
|
|
name: process_binding_appcursormode_appkeypadmode_nomod_require_appcursor,
|
2020-01-05 01:00:50 +00:00
|
|
|
binding: Binding { trigger: KEY, mods: ModifiersState::empty(), action: Action::from("\x1bOD"), mode: TermMode::APP_CURSOR, notmode: TermMode::NONE },
|
2016-12-27 00:00:27 +00:00
|
|
|
triggers: true,
|
2018-03-08 21:50:04 +00:00
|
|
|
mode: TermMode::APP_CURSOR | TermMode::APP_KEYPAD,
|
2020-01-05 01:00:50 +00:00
|
|
|
mods: ModifiersState::empty(),
|
2016-07-30 01:27:45 +00:00
|
|
|
}
|
2016-09-27 15:27:39 +00:00
|
|
|
|
|
|
|
test_process_binding! {
|
|
|
|
name: process_binding_fail_with_extra_mods,
|
2020-01-05 01:00:50 +00:00
|
|
|
binding: Binding { trigger: KEY, mods: ModifiersState::LOGO, action: Action::from("arst"), mode: TermMode::NONE, notmode: TermMode::NONE },
|
2016-12-27 00:00:27 +00:00
|
|
|
triggers: false,
|
2018-03-08 21:50:04 +00:00
|
|
|
mode: TermMode::NONE,
|
2020-01-05 01:00:50 +00:00
|
|
|
mods: ModifiersState::ALT | ModifiersState::LOGO,
|
2016-09-27 15:27:39 +00:00
|
|
|
}
|
2016-06-23 16:48:31 +00:00
|
|
|
}
|