Show text cursor when pressing shift in mouse mode

Fixes #2550.
This commit is contained in:
John Sullivan 2019-08-24 16:18:51 -07:00 committed by Christian Duerr
parent 629ea247cd
commit ad0365219f
5 changed files with 112 additions and 65 deletions

View File

@ -40,6 +40,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- URL parser incorrectly handling Markdown URLs and angled brackets
- Intermediate bytes of CSI sequences not checked
- Wayland clipboard integration
- Use text mouse cursor when mouse mode is temporarily disabled with shift
## 0.3.3

View File

@ -52,11 +52,7 @@ fn parse_rgb_color(color: &[u8]) -> Option<Rgb> {
Some((255 * value / max) as u8)
};
Some(Rgb {
r: scale(colors[0])?,
g: scale(colors[1])?,
b: scale(colors[2])?,
})
Some(Rgb { r: scale(colors[0])?, g: scale(colors[1])?, b: scale(colors[2])? })
}
// Parse colors in `#r(rrr)g(ggg)b(bbb)` format
@ -1449,8 +1445,8 @@ pub mod C1 {
#[cfg(test)]
mod tests {
use super::{
parse_number, xparse_color, Attr, CharsetIndex, Color, Handler, Processor,
StandardCharset, TermInfo,
parse_number, xparse_color, Attr, CharsetIndex, Color, Handler, Processor, StandardCharset,
TermInfo,
};
use crate::index::{Column, Line};
use crate::term::color::Rgb;

View File

@ -7,7 +7,7 @@ use std::sync::mpsc;
use std::time::Instant;
use glutin::dpi::PhysicalSize;
use glutin::{self, ElementState, Event, ModifiersState, MouseButton};
use glutin::{self, ElementState, Event, MouseButton};
use parking_lot::MutexGuard;
use crate::clipboard::ClipboardType;
@ -15,7 +15,7 @@ use crate::config::{self, Config, StartupMode};
use crate::display::OnResize;
use crate::grid::Scroll;
use crate::index::{Column, Line, Point, Side};
use crate::input::{self, KeyBinding, MouseBinding};
use crate::input::{self, KeyBinding, Modifiers, MouseBinding};
use crate::selection::Selection;
use crate::sync::FairMutex;
use crate::term::{SizeInfo, Term};
@ -39,7 +39,7 @@ pub struct ActionContext<'a, N> {
pub mouse: &'a mut Mouse,
pub received_count: &'a mut usize,
pub suppress_chars: &'a mut bool,
pub last_modifiers: &'a mut ModifiersState,
pub modifiers: &'a mut Modifiers,
pub window_changes: &'a mut WindowChanges,
}
@ -141,8 +141,8 @@ impl<'a, N: Notify + 'a> input::ActionContext for ActionContext<'a, N> {
}
#[inline]
fn last_modifiers(&mut self) -> &mut ModifiersState {
&mut self.last_modifiers
fn modifiers(&mut self) -> &mut Modifiers {
&mut self.modifiers
}
#[inline]
@ -287,7 +287,7 @@ pub struct Processor<N> {
hide_mouse: bool,
received_count: usize,
suppress_chars: bool,
last_modifiers: ModifiersState,
modifiers: Modifiers,
pending_events: Vec<Event>,
window_changes: WindowChanges,
save_to_clipboard: bool,
@ -331,7 +331,7 @@ impl<N: Notify> Processor<N> {
hide_mouse: false,
received_count: 0,
suppress_chars: false,
last_modifiers: Default::default(),
modifiers: Default::default(),
pending_events: Vec::with_capacity(4),
window_changes: Default::default(),
save_to_clipboard: config.selection.save_to_clipboard,
@ -406,11 +406,9 @@ impl<N: Notify> Processor<N> {
*window_is_focused = is_focused;
if is_focused {
processor.ctx.terminal.dirty = true;
processor.ctx.terminal.next_is_urgent = Some(false);
processor.ctx.terminal.dirty = true;
} else {
processor.ctx.terminal.reset_url_highlight();
processor.ctx.terminal.reset_mouse_cursor();
processor.ctx.terminal.dirty = true;
*hide_mouse = false;
}
@ -426,6 +424,7 @@ impl<N: Notify> Processor<N> {
processor.ctx.size_info.dpr = new_dpr;
processor.ctx.terminal.dirty = true;
},
CursorLeft { .. } => processor.ctx.terminal.reset_url_highlight(),
_ => (),
}
},
@ -478,7 +477,7 @@ impl<N: Notify> Processor<N> {
size_info: &mut self.size_info,
received_count: &mut self.received_count,
suppress_chars: &mut self.suppress_chars,
last_modifiers: &mut self.last_modifiers,
modifiers: &mut self.modifiers,
window_changes: &mut self.window_changes,
};

View File

@ -25,7 +25,7 @@ use std::time::Instant;
use glutin::{
ElementState, KeyboardInput, ModifiersState, MouseButton, MouseCursor, MouseScrollDelta,
TouchPhase,
TouchPhase, VirtualKeyCode,
};
use crate::ansi::{ClearMode, Handler};
@ -73,7 +73,7 @@ pub trait ActionContext {
fn mouse_coords(&self) -> Option<Point>;
fn received_count(&mut self) -> &mut usize;
fn suppress_chars(&mut self) -> &mut bool;
fn last_modifiers(&mut self) -> &mut ModifiersState;
fn modifiers(&mut self) -> &mut Modifiers;
fn scroll(&mut self, scroll: Scroll);
fn hide_window(&mut self);
fn terminal(&self) -> &Term;
@ -84,6 +84,47 @@ pub trait ActionContext {
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<Modifiers> for ModifiersState {
fn from(mods: 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`
@ -381,41 +422,48 @@ impl From<&'static str> for Action {
}
}
enum MousePosition {
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum MouseState {
Url(Url),
MessageBar,
MessageBarButton,
Terminal,
Mouse,
Text,
}
impl<'a, A: ActionContext + 'a> Processor<'a, A> {
fn mouse_position(&mut self, point: Point, modifiers: ModifiersState) -> MousePosition {
fn mouse_state(&mut self, point: Point) -> 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 MousePosition::MessageBarButton;
return MouseState::MessageBarButton;
} else {
return MousePosition::MessageBar;
return MouseState::MessageBar;
}
}
// Check for URL at point with required modifiers held
if self.mouse_config.url.mods().relaxed_eq(modifiers)
&& (!self.ctx.terminal().mode().intersects(mouse_mode) || modifiers.shift)
let mods = *self.ctx.modifiers();
if self.mouse_config.url.mods().relaxed_eq(mods.into())
&& (!self.ctx.terminal().mode().intersects(mouse_mode) || mods.shift())
&& self.mouse_config.url.launcher.is_some()
{
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 MousePosition::Url(url);
return MouseState::Url(url);
}
}
MousePosition::Terminal
if self.ctx.terminal().mode().intersects(mouse_mode) && !self.ctx.modifiers().shift() {
MouseState::Mouse
} else {
MouseState::Text
}
}
#[inline]
@ -445,27 +493,18 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> {
// Don't launch URLs if mouse has moved
self.ctx.mouse_mut().block_url_launcher = true;
match self.mouse_position(point, modifiers) {
MousePosition::Url(url) => {
let mouse_state = self.mouse_state(point);
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);
self.ctx.terminal_mut().set_mouse_cursor(MouseCursor::Hand);
self.ctx.terminal_mut().dirty = true;
},
MousePosition::MessageBar => {
MouseState::MessageBar | MouseState::MessageBarButton => {
self.ctx.terminal_mut().reset_url_highlight();
self.ctx.terminal_mut().set_mouse_cursor(MouseCursor::Default);
return;
},
MousePosition::MessageBarButton => {
self.ctx.terminal_mut().reset_url_highlight();
self.ctx.terminal_mut().set_mouse_cursor(MouseCursor::Hand);
return;
},
MousePosition::Terminal => {
self.ctx.terminal_mut().reset_url_highlight();
self.ctx.terminal_mut().reset_mouse_cursor();
},
_ => self.ctx.terminal_mut().reset_url_highlight(),
}
if self.ctx.mouse().left_button_state == ElementState::Pressed
@ -798,9 +837,20 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> {
/// 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 mouse_state = self.mouse_state(point);
self.update_mouse_cursor(mouse_state);
}
}
match input.state {
ElementState::Pressed => {
*self.ctx.last_modifiers() = input.modifiers;
*self.ctx.received_count() = 0;
*self.ctx.suppress_chars() = false;
@ -830,7 +880,7 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> {
if self.alt_send_esc
&& *self.ctx.received_count() == 0
&& self.ctx.last_modifiers().alt
&& self.ctx.modifiers().alt()
&& utf8_len == 1
{
bytes.insert(0, b'\x1b');
@ -934,8 +984,9 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> {
ElementState::Released => self.copy_selection(),
ElementState::Pressed => {
if self.message_close_at_point(point, message) {
let mouse_state = self.mouse_state(point);
self.update_mouse_cursor(mouse_state);
self.ctx.terminal_mut().message_buffer_mut().pop();
self.ctx.terminal_mut().reset_mouse_cursor();
}
self.ctx.clear_selection();
@ -950,6 +1001,17 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> {
}
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)]
@ -968,7 +1030,7 @@ mod tests {
use crate::selection::Selection;
use crate::term::{SizeInfo, Term, TermMode};
use super::{Action, Binding, Processor};
use super::{Action, Binding, Modifiers, Processor};
const KEY: VirtualKeyCode = VirtualKeyCode::Key0;
@ -1112,7 +1174,7 @@ mod tests {
pub last_action: MultiClick,
pub received_count: usize,
pub suppress_chars: bool,
pub last_modifiers: ModifiersState,
pub modifiers: Modifiers,
pub window_changes: &'a mut WindowChanges,
}
@ -1189,8 +1251,8 @@ mod tests {
&mut self.suppress_chars
}
fn last_modifiers(&mut self) -> &mut ModifiersState {
&mut self.last_modifiers
fn modifiers(&mut self) -> &mut Modifiers {
&mut self.modifiers
}
}
@ -1232,7 +1294,7 @@ mod tests {
last_action: MultiClick::None,
received_count: 0,
suppress_chars: false,
last_modifiers: ModifiersState::default(),
modifiers: Default::default(),
window_changes: &mut WindowChanges::default(),
};

View File

@ -841,9 +841,9 @@ impl Term {
#[inline]
pub fn scroll_display(&mut self, scroll: Scroll) {
self.set_mouse_cursor(MouseCursor::Text);
self.grid.scroll_display(scroll);
self.reset_url_highlight();
self.reset_mouse_cursor();
self.dirty = true;
}
@ -1296,18 +1296,7 @@ impl Term {
#[inline]
pub fn set_url_highlight(&mut self, hl: RangeInclusive<index::Linear>) {
self.grid.url_highlight = Some(hl);
}
#[inline]
pub fn reset_mouse_cursor(&mut self) {
let mouse_mode =
TermMode::MOUSE_MOTION | TermMode::MOUSE_DRAG | TermMode::MOUSE_REPORT_CLICK;
let mouse_cursor = if self.mode().intersects(mouse_mode) {
MouseCursor::Default
} else {
MouseCursor::Text
};
self.set_mouse_cursor(mouse_cursor);
self.dirty = true;
}
#[inline]