Fix various mouse mode + vi mode interactions
This commit fixes some issues introduced by 1a8cd172e520e493bacc9c6a2ae6f80de086eaa3: 1. Vi cursor not moving properly on double/triple click 2. URL not launching via mouse click in vi mode + mouse mode 3. Ability to select in mouse mode with double/triple click regardless of shift modifier
This commit is contained in:
parent
2fc5120327
commit
f14d24542c
|
@ -364,7 +364,8 @@ impl Display {
|
||||||
let size_info = self.size_info;
|
let size_info = self.size_info;
|
||||||
|
|
||||||
let selection = !terminal.selection().as_ref().map(Selection::is_empty).unwrap_or(true);
|
let selection = !terminal.selection().as_ref().map(Selection::is_empty).unwrap_or(true);
|
||||||
let mouse_mode = terminal.mode().intersects(TermMode::MOUSE_MODE);
|
let mouse_mode = terminal.mode().intersects(TermMode::MOUSE_MODE)
|
||||||
|
&& !terminal.mode().contains(TermMode::VI);
|
||||||
|
|
||||||
let vi_mode_cursor = if terminal.mode().contains(TermMode::VI) {
|
let vi_mode_cursor = if terminal.mode().contains(TermMode::VI) {
|
||||||
Some(terminal.vi_mode_cursor)
|
Some(terminal.vi_mode_cursor)
|
||||||
|
|
|
@ -12,7 +12,7 @@ use std::sync::Arc;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
|
||||||
use glutin::dpi::PhysicalSize;
|
use glutin::dpi::PhysicalSize;
|
||||||
use glutin::event::{ElementState, Event as GlutinEvent, ModifiersState, MouseButton, WindowEvent};
|
use glutin::event::{ElementState, Event as GlutinEvent, ModifiersState, WindowEvent};
|
||||||
use glutin::event_loop::{ControlFlow, EventLoop, EventLoopProxy, EventLoopWindowTarget};
|
use glutin::event_loop::{ControlFlow, EventLoop, EventLoopProxy, EventLoopWindowTarget};
|
||||||
use glutin::platform::desktop::EventLoopExtDesktop;
|
use glutin::platform::desktop::EventLoopExtDesktop;
|
||||||
#[cfg(not(any(target_os = "macos", windows)))]
|
#[cfg(not(any(target_os = "macos", windows)))]
|
||||||
|
@ -295,7 +295,7 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext<T> for ActionCon
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Eq, PartialEq)]
|
||||||
pub enum ClickState {
|
pub enum ClickState {
|
||||||
None,
|
None,
|
||||||
Click,
|
Click,
|
||||||
|
@ -319,7 +319,6 @@ pub struct Mouse {
|
||||||
pub cell_side: Side,
|
pub cell_side: Side,
|
||||||
pub lines_scrolled: f32,
|
pub lines_scrolled: f32,
|
||||||
pub block_url_launcher: bool,
|
pub block_url_launcher: bool,
|
||||||
pub last_button: MouseButton,
|
|
||||||
pub inside_grid: bool,
|
pub inside_grid: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -339,7 +338,6 @@ impl Default for Mouse {
|
||||||
cell_side: Side::Left,
|
cell_side: Side::Left,
|
||||||
lines_scrolled: 0.,
|
lines_scrolled: 0.,
|
||||||
block_url_launcher: false,
|
block_url_launcher: false,
|
||||||
last_button: MouseButton::Other(0),
|
|
||||||
inside_grid: false,
|
inside_grid: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -455,89 +455,79 @@ impl<'a, T: EventListener, A: ActionContext<T>> Processor<'a, T, A> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_mouse_double_click(&mut self, button: MouseButton, point: Point) {
|
|
||||||
if button == MouseButton::Left {
|
|
||||||
let side = self.ctx.mouse().cell_side;
|
|
||||||
self.ctx.start_selection(SelectionType::Semantic, point, side);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn on_mouse_triple_click(&mut self, button: MouseButton, point: Point) {
|
|
||||||
if button == MouseButton::Left {
|
|
||||||
let side = self.ctx.mouse().cell_side;
|
|
||||||
self.ctx.start_selection(SelectionType::Lines, point, side);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn on_mouse_press(&mut self, button: MouseButton) {
|
fn on_mouse_press(&mut self, button: MouseButton) {
|
||||||
let now = Instant::now();
|
// Handle mouse mode
|
||||||
let elapsed = self.ctx.mouse().last_click_timestamp.elapsed();
|
if !self.ctx.modifiers().shift() && self.ctx.mouse_mode() {
|
||||||
self.ctx.mouse_mut().last_click_timestamp = now;
|
self.ctx.mouse_mut().click_state = ClickState::None;
|
||||||
|
|
||||||
let button_changed = self.ctx.mouse().last_button != button;
|
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);
|
||||||
|
} else if button == MouseButton::Left {
|
||||||
|
self.on_left_click();
|
||||||
|
} else {
|
||||||
|
// Do nothing when using buttons other than LMB
|
||||||
|
self.ctx.mouse_mut().click_state = ClickState::None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handle left click selection and vi mode cursor movement.
|
||||||
|
fn on_left_click(&mut self) {
|
||||||
|
// Calculate time since the last click to handle double/triple clicks in normal mode
|
||||||
|
let now = Instant::now();
|
||||||
|
let elapsed = now - self.ctx.mouse().last_click_timestamp;
|
||||||
|
self.ctx.mouse_mut().last_click_timestamp = now;
|
||||||
|
|
||||||
// Load mouse point, treating message bar and padding as closest cell
|
// Load mouse point, treating message bar and padding as closest cell
|
||||||
let mouse = self.ctx.mouse();
|
let mouse = self.ctx.mouse();
|
||||||
let mut point = self.ctx.size_info().pixels_to_coords(mouse.x, mouse.y);
|
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);
|
point.line = min(point.line, self.ctx.terminal().grid().num_lines() - 1);
|
||||||
|
|
||||||
|
let side = self.ctx.mouse().cell_side;
|
||||||
|
|
||||||
self.ctx.mouse_mut().click_state = match self.ctx.mouse().click_state {
|
self.ctx.mouse_mut().click_state = match self.ctx.mouse().click_state {
|
||||||
ClickState::Click
|
ClickState::Click
|
||||||
if !button_changed
|
if elapsed < self.ctx.config().ui_config.mouse.double_click.threshold =>
|
||||||
&& elapsed < self.ctx.config().ui_config.mouse.double_click.threshold =>
|
|
||||||
{
|
{
|
||||||
self.ctx.mouse_mut().block_url_launcher = true;
|
self.ctx.mouse_mut().block_url_launcher = true;
|
||||||
self.on_mouse_double_click(button, point);
|
self.ctx.start_selection(SelectionType::Semantic, point, side);
|
||||||
ClickState::DoubleClick
|
ClickState::DoubleClick
|
||||||
}
|
}
|
||||||
ClickState::DoubleClick
|
ClickState::DoubleClick
|
||||||
if !button_changed
|
if elapsed < self.ctx.config().ui_config.mouse.triple_click.threshold =>
|
||||||
&& elapsed < self.ctx.config().ui_config.mouse.triple_click.threshold =>
|
|
||||||
{
|
{
|
||||||
self.ctx.mouse_mut().block_url_launcher = true;
|
self.ctx.mouse_mut().block_url_launcher = true;
|
||||||
self.on_mouse_triple_click(button, point);
|
self.ctx.start_selection(SelectionType::Lines, point, side);
|
||||||
ClickState::TripleClick
|
ClickState::TripleClick
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
if button == MouseButton::Left
|
// Don't launch URLs if this click cleared the selection
|
||||||
&& (self.ctx.modifiers().shift()
|
self.ctx.mouse_mut().block_url_launcher = !self.ctx.selection_is_empty();
|
||||||
|| !self.ctx.terminal().mode().intersects(TermMode::MOUSE_MODE))
|
|
||||||
{
|
|
||||||
// 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();
|
self.ctx.clear_selection();
|
||||||
|
|
||||||
// Start new empty selection
|
// Start new empty selection
|
||||||
let side = self.ctx.mouse().cell_side;
|
if self.ctx.modifiers().ctrl() {
|
||||||
if self.ctx.modifiers().ctrl() {
|
self.ctx.start_selection(SelectionType::Block, point, side);
|
||||||
self.ctx.start_selection(SelectionType::Block, point, side);
|
} else {
|
||||||
} else {
|
self.ctx.start_selection(SelectionType::Simple, point, side);
|
||||||
self.ctx.start_selection(SelectionType::Simple, point, side);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Move vi mode cursor to mouse position
|
|
||||||
if self.ctx.terminal().mode().contains(TermMode::VI) {
|
|
||||||
// Update vi mode cursor position on click
|
|
||||||
self.ctx.terminal_mut().vi_mode_cursor.point = point;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !self.ctx.modifiers().shift() && self.ctx.mouse_mode() {
|
|
||||||
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);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ClickState::Click
|
ClickState::Click
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Move vi mode cursor to mouse position
|
||||||
|
if self.ctx.terminal().mode().contains(TermMode::VI) {
|
||||||
|
// Update Vi mode cursor position on click
|
||||||
|
self.ctx.terminal_mut().vi_mode_cursor.point = point;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_mouse_release(&mut self, button: MouseButton) {
|
fn on_mouse_release(&mut self, button: MouseButton) {
|
||||||
|
@ -695,8 +685,6 @@ impl<'a, T: EventListener, A: ActionContext<T>> Processor<'a, T, A> {
|
||||||
ElementState::Released => self.on_mouse_release(button),
|
ElementState::Released => self.on_mouse_release(button),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.ctx.mouse_mut().last_button = button;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Process key input.
|
/// Process key input.
|
||||||
|
@ -910,20 +898,12 @@ mod tests {
|
||||||
fn send_event(&self, _event: TerminalEvent) {}
|
fn send_event(&self, _event: TerminalEvent) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
enum MultiClick {
|
|
||||||
DoubleClick,
|
|
||||||
TripleClick,
|
|
||||||
None,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ActionContext<'a, T> {
|
struct ActionContext<'a, T> {
|
||||||
pub terminal: &'a mut Term<T>,
|
pub terminal: &'a mut Term<T>,
|
||||||
pub selection: &'a mut Option<Selection>,
|
pub selection: &'a mut Option<Selection>,
|
||||||
pub size_info: &'a SizeInfo,
|
pub size_info: &'a SizeInfo,
|
||||||
pub mouse: &'a mut Mouse,
|
pub mouse: &'a mut Mouse,
|
||||||
pub message_buffer: &'a mut MessageBuffer,
|
pub message_buffer: &'a mut MessageBuffer,
|
||||||
pub last_action: MultiClick,
|
|
||||||
pub received_count: usize,
|
pub received_count: usize,
|
||||||
pub suppress_chars: bool,
|
pub suppress_chars: bool,
|
||||||
pub modifiers: ModifiersState,
|
pub modifiers: ModifiersState,
|
||||||
|
@ -935,13 +915,7 @@ mod tests {
|
||||||
|
|
||||||
fn update_selection(&mut self, _point: Point, _side: Side) {}
|
fn update_selection(&mut self, _point: Point, _side: Side) {}
|
||||||
|
|
||||||
fn start_selection(&mut self, ty: SelectionType, _point: Point, _side: Side) {
|
fn start_selection(&mut self, _ty: SelectionType, _point: Point, _side: Side) {}
|
||||||
match ty {
|
|
||||||
SelectionType::Semantic => self.last_action = MultiClick::DoubleClick,
|
|
||||||
SelectionType::Lines => self.last_action = MultiClick::TripleClick,
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn toggle_selection(&mut self, _ty: SelectionType, _point: Point, _side: Side) {}
|
fn toggle_selection(&mut self, _ty: SelectionType, _point: Point, _side: Side) {}
|
||||||
|
|
||||||
|
@ -1051,8 +1025,7 @@ mod tests {
|
||||||
initial_state: $initial_state:expr,
|
initial_state: $initial_state:expr,
|
||||||
initial_button: $initial_button:expr,
|
initial_button: $initial_button:expr,
|
||||||
input: $input:expr,
|
input: $input:expr,
|
||||||
end_state: $end_state:pat,
|
end_state: $end_state:expr,
|
||||||
last_action: $last_action:expr
|
|
||||||
} => {
|
} => {
|
||||||
#[test]
|
#[test]
|
||||||
fn $name() {
|
fn $name() {
|
||||||
|
@ -1082,7 +1055,6 @@ mod tests {
|
||||||
|
|
||||||
let mut mouse = Mouse::default();
|
let mut mouse = Mouse::default();
|
||||||
mouse.click_state = $initial_state;
|
mouse.click_state = $initial_state;
|
||||||
mouse.last_button = $initial_button;
|
|
||||||
|
|
||||||
let mut selection = None;
|
let mut selection = None;
|
||||||
|
|
||||||
|
@ -1093,7 +1065,6 @@ mod tests {
|
||||||
selection: &mut selection,
|
selection: &mut selection,
|
||||||
mouse: &mut mouse,
|
mouse: &mut mouse,
|
||||||
size_info: &size,
|
size_info: &size,
|
||||||
last_action: MultiClick::None,
|
|
||||||
received_count: 0,
|
received_count: 0,
|
||||||
suppress_chars: false,
|
suppress_chars: false,
|
||||||
modifiers: Default::default(),
|
modifiers: Default::default(),
|
||||||
|
@ -1116,10 +1087,7 @@ mod tests {
|
||||||
processor.mouse_input(state, button);
|
processor.mouse_input(state, button);
|
||||||
};
|
};
|
||||||
|
|
||||||
assert!(match processor.ctx.mouse.click_state {
|
assert_eq!(processor.ctx.mouse.click_state, $end_state);
|
||||||
$end_state => processor.ctx.last_action == $last_action,
|
|
||||||
_ => false
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1151,13 +1119,44 @@ mod tests {
|
||||||
event: WindowEvent::MouseInput {
|
event: WindowEvent::MouseInput {
|
||||||
state: ElementState::Pressed,
|
state: ElementState::Pressed,
|
||||||
button: MouseButton::Left,
|
button: MouseButton::Left,
|
||||||
device_id: unsafe { ::std::mem::transmute_copy(&0) },
|
device_id: unsafe { std::mem::transmute_copy(&0) },
|
||||||
modifiers: ModifiersState::default(),
|
modifiers: ModifiersState::default(),
|
||||||
},
|
},
|
||||||
window_id: unsafe { ::std::mem::transmute_copy(&0) },
|
window_id: unsafe { std::mem::transmute_copy(&0) },
|
||||||
},
|
},
|
||||||
end_state: ClickState::Click,
|
end_state: ClickState::Click,
|
||||||
last_action: MultiClick::None
|
}
|
||||||
|
|
||||||
|
test_clickstate! {
|
||||||
|
name: single_right_click,
|
||||||
|
initial_state: ClickState::None,
|
||||||
|
initial_button: MouseButton::Other(0),
|
||||||
|
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::None,
|
||||||
|
}
|
||||||
|
|
||||||
|
test_clickstate! {
|
||||||
|
name: single_middle_click,
|
||||||
|
initial_state: ClickState::None,
|
||||||
|
initial_button: MouseButton::Other(0),
|
||||||
|
input: Event::WindowEvent {
|
||||||
|
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,
|
||||||
}
|
}
|
||||||
|
|
||||||
test_clickstate! {
|
test_clickstate! {
|
||||||
|
@ -1168,13 +1167,12 @@ mod tests {
|
||||||
event: WindowEvent::MouseInput {
|
event: WindowEvent::MouseInput {
|
||||||
state: ElementState::Pressed,
|
state: ElementState::Pressed,
|
||||||
button: MouseButton::Left,
|
button: MouseButton::Left,
|
||||||
device_id: unsafe { ::std::mem::transmute_copy(&0) },
|
device_id: unsafe { std::mem::transmute_copy(&0) },
|
||||||
modifiers: ModifiersState::default(),
|
modifiers: ModifiersState::default(),
|
||||||
},
|
},
|
||||||
window_id: unsafe { ::std::mem::transmute_copy(&0) },
|
window_id: unsafe { std::mem::transmute_copy(&0) },
|
||||||
},
|
},
|
||||||
end_state: ClickState::DoubleClick,
|
end_state: ClickState::DoubleClick,
|
||||||
last_action: MultiClick::DoubleClick
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test_clickstate! {
|
test_clickstate! {
|
||||||
|
@ -1185,13 +1183,12 @@ mod tests {
|
||||||
event: WindowEvent::MouseInput {
|
event: WindowEvent::MouseInput {
|
||||||
state: ElementState::Pressed,
|
state: ElementState::Pressed,
|
||||||
button: MouseButton::Left,
|
button: MouseButton::Left,
|
||||||
device_id: unsafe { ::std::mem::transmute_copy(&0) },
|
device_id: unsafe { std::mem::transmute_copy(&0) },
|
||||||
modifiers: ModifiersState::default(),
|
modifiers: ModifiersState::default(),
|
||||||
},
|
},
|
||||||
window_id: unsafe { ::std::mem::transmute_copy(&0) },
|
window_id: unsafe { std::mem::transmute_copy(&0) },
|
||||||
},
|
},
|
||||||
end_state: ClickState::TripleClick,
|
end_state: ClickState::TripleClick,
|
||||||
last_action: MultiClick::TripleClick
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test_clickstate! {
|
test_clickstate! {
|
||||||
|
@ -1202,13 +1199,12 @@ mod tests {
|
||||||
event: WindowEvent::MouseInput {
|
event: WindowEvent::MouseInput {
|
||||||
state: ElementState::Pressed,
|
state: ElementState::Pressed,
|
||||||
button: MouseButton::Right,
|
button: MouseButton::Right,
|
||||||
device_id: unsafe { ::std::mem::transmute_copy(&0) },
|
device_id: unsafe { std::mem::transmute_copy(&0) },
|
||||||
modifiers: ModifiersState::default(),
|
modifiers: ModifiersState::default(),
|
||||||
},
|
},
|
||||||
window_id: unsafe { ::std::mem::transmute_copy(&0) },
|
window_id: unsafe { std::mem::transmute_copy(&0) },
|
||||||
},
|
},
|
||||||
end_state: ClickState::Click,
|
end_state: ClickState::None,
|
||||||
last_action: MultiClick::None
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test_process_binding! {
|
test_process_binding! {
|
||||||
|
|
Loading…
Reference in New Issue