fix(terminal): SGR/UTF8 mouse reporting in terminal panes (#1664)
* work * work * fix: selection mishandling * style(fmt): rustfmt * style(comments): remove outdated * style(clippy): make clippy happy * fix(mouse): off by one sgr/utf8 reporting * style(fmt): rustfmt * fix(mouse): correctly report drag event code * fix(input): support mouse middle click * style(fmt): rustfmt
This commit is contained in:
parent
b53e3807eb
commit
f4ad946497
13 changed files with 1117 additions and 210 deletions
|
|
@ -19,6 +19,19 @@ use zellij_utils::{
|
|||
termwiz::input::InputEvent,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
enum HeldMouseButton {
|
||||
Left,
|
||||
Right,
|
||||
Middle,
|
||||
}
|
||||
|
||||
impl Default for HeldMouseButton {
|
||||
fn default() -> Self {
|
||||
HeldMouseButton::Left
|
||||
}
|
||||
}
|
||||
|
||||
/// Handles the dispatching of [`Action`]s according to the current
|
||||
/// [`InputMode`], and keep tracks of the current [`InputMode`].
|
||||
struct InputHandler {
|
||||
|
|
@ -31,7 +44,7 @@ struct InputHandler {
|
|||
send_client_instructions: SenderWithContext<ClientInstruction>,
|
||||
should_exit: bool,
|
||||
receive_input_instructions: Receiver<(InputInstruction, ErrorContext)>,
|
||||
holding_mouse: bool,
|
||||
holding_mouse: Option<HeldMouseButton>,
|
||||
}
|
||||
|
||||
impl InputHandler {
|
||||
|
|
@ -54,7 +67,7 @@ impl InputHandler {
|
|||
send_client_instructions,
|
||||
should_exit: false,
|
||||
receive_input_instructions,
|
||||
holding_mouse: false,
|
||||
holding_mouse: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -161,30 +174,59 @@ impl InputHandler {
|
|||
self.dispatch_action(Action::ScrollDownAt(point), None);
|
||||
},
|
||||
MouseButton::Left => {
|
||||
if self.holding_mouse {
|
||||
self.dispatch_action(Action::MouseHold(point), None);
|
||||
if self.holding_mouse.is_some() {
|
||||
self.dispatch_action(Action::MouseHoldLeft(point), None);
|
||||
} else {
|
||||
self.dispatch_action(Action::LeftClick(point), None);
|
||||
}
|
||||
self.holding_mouse = true;
|
||||
self.holding_mouse = Some(HeldMouseButton::Left);
|
||||
},
|
||||
MouseButton::Right => {
|
||||
if self.holding_mouse {
|
||||
self.dispatch_action(Action::MouseHold(point), None);
|
||||
if self.holding_mouse.is_some() {
|
||||
self.dispatch_action(Action::MouseHoldRight(point), None);
|
||||
} else {
|
||||
self.dispatch_action(Action::RightClick(point), None);
|
||||
}
|
||||
self.holding_mouse = true;
|
||||
self.holding_mouse = Some(HeldMouseButton::Right);
|
||||
},
|
||||
MouseButton::Middle => {
|
||||
if self.holding_mouse.is_some() {
|
||||
self.dispatch_action(Action::MouseHoldMiddle(point), None);
|
||||
} else {
|
||||
self.dispatch_action(Action::MiddleClick(point), None);
|
||||
}
|
||||
self.holding_mouse = Some(HeldMouseButton::Middle);
|
||||
},
|
||||
_ => {},
|
||||
},
|
||||
MouseEvent::Release(point) => {
|
||||
self.dispatch_action(Action::MouseRelease(point), None);
|
||||
self.holding_mouse = false;
|
||||
let button_released = self.holding_mouse.unwrap_or_default();
|
||||
match button_released {
|
||||
HeldMouseButton::Left => {
|
||||
self.dispatch_action(Action::LeftMouseRelease(point), None)
|
||||
},
|
||||
HeldMouseButton::Right => {
|
||||
self.dispatch_action(Action::RightMouseRelease(point), None)
|
||||
},
|
||||
HeldMouseButton::Middle => {
|
||||
self.dispatch_action(Action::MiddleMouseRelease(point), None)
|
||||
},
|
||||
};
|
||||
self.holding_mouse = None;
|
||||
},
|
||||
MouseEvent::Hold(point) => {
|
||||
self.dispatch_action(Action::MouseHold(point), None);
|
||||
self.holding_mouse = true;
|
||||
let button_held = self.holding_mouse.unwrap_or_default();
|
||||
match button_held {
|
||||
HeldMouseButton::Left => {
|
||||
self.dispatch_action(Action::MouseHoldLeft(point), None)
|
||||
},
|
||||
HeldMouseButton::Right => {
|
||||
self.dispatch_action(Action::MouseHoldRight(point), None)
|
||||
},
|
||||
HeldMouseButton::Middle => {
|
||||
self.dispatch_action(Action::MouseHoldMiddle(point), None)
|
||||
},
|
||||
};
|
||||
self.holding_mouse = Some(button_held);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -299,6 +299,28 @@ macro_rules! dump_screen {
|
|||
}};
|
||||
}
|
||||
|
||||
fn utf8_mouse_coordinates(column: usize, line: isize) -> Vec<u8> {
|
||||
let mut coordinates = vec![];
|
||||
let mouse_pos_encode = |pos: usize| -> Vec<u8> {
|
||||
let pos = 32 + pos;
|
||||
let first = 0xC0 + pos / 64;
|
||||
let second = 0x80 + (pos & 63);
|
||||
vec![first as u8, second as u8]
|
||||
};
|
||||
|
||||
if column > 95 {
|
||||
coordinates.append(&mut mouse_pos_encode(column));
|
||||
} else {
|
||||
coordinates.push(32 + column as u8);
|
||||
}
|
||||
if line > 95 {
|
||||
coordinates.append(&mut mouse_pos_encode(line as usize));
|
||||
} else {
|
||||
coordinates.push(32 + line as u8);
|
||||
}
|
||||
coordinates
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Grid {
|
||||
pub(crate) lines_above: VecDeque<Row>,
|
||||
|
|
@ -340,11 +362,38 @@ pub struct Grid {
|
|||
pub link_handler: Rc<RefCell<LinkHandler>>,
|
||||
pub ring_bell: bool,
|
||||
scrollback_buffer_lines: usize,
|
||||
pub mouse_mode: bool,
|
||||
pub mouse_mode: MouseMode,
|
||||
pub mouse_tracking: MouseTracking,
|
||||
pub search_results: SearchResult,
|
||||
pub pending_clipboard_update: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum MouseMode {
|
||||
NoEncoding,
|
||||
Utf8,
|
||||
Sgr,
|
||||
}
|
||||
|
||||
impl Default for MouseMode {
|
||||
fn default() -> Self {
|
||||
MouseMode::NoEncoding
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum MouseTracking {
|
||||
Off,
|
||||
Normal,
|
||||
ButtonEventTracking,
|
||||
}
|
||||
|
||||
impl Default for MouseTracking {
|
||||
fn default() -> Self {
|
||||
MouseTracking::Off
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Grid {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
let mut buffer: Vec<Row> = self.viewport.clone();
|
||||
|
|
@ -444,7 +493,8 @@ impl Grid {
|
|||
link_handler,
|
||||
ring_bell: false,
|
||||
scrollback_buffer_lines: 0,
|
||||
mouse_mode: false,
|
||||
mouse_mode: MouseMode::default(),
|
||||
mouse_tracking: MouseTracking::default(),
|
||||
character_cell_size,
|
||||
search_results: Default::default(),
|
||||
sixel_grid,
|
||||
|
|
@ -1471,6 +1521,8 @@ impl Grid {
|
|||
self.scrollback_buffer_lines = 0;
|
||||
self.search_results = Default::default();
|
||||
self.sixel_scrolling = false;
|
||||
self.mouse_mode = MouseMode::NoEncoding;
|
||||
self.mouse_tracking = MouseTracking::Off;
|
||||
if let Some(images_to_reap) = self.sixel_grid.clear() {
|
||||
self.sixel_grid.reap_images(images_to_reap);
|
||||
}
|
||||
|
|
@ -1673,6 +1725,209 @@ impl Grid {
|
|||
}
|
||||
}
|
||||
}
|
||||
pub fn mouse_left_click_signal(&self, position: &Position, is_held: bool) -> Option<String> {
|
||||
let utf8_event = || -> Option<String> {
|
||||
let button_code = if is_held { b'@' } else { b' ' };
|
||||
let mut msg: Vec<u8> = vec![27, b'[', b'M', button_code];
|
||||
msg.append(&mut utf8_mouse_coordinates(
|
||||
position.column() + 1,
|
||||
position.line() + 1,
|
||||
));
|
||||
Some(String::from_utf8_lossy(&msg).into())
|
||||
};
|
||||
let sgr_event = || -> Option<String> {
|
||||
let button_code = if is_held { 32 } else { 0 };
|
||||
Some(format!(
|
||||
"\u{1b}[<{:?};{:?};{:?}M",
|
||||
button_code,
|
||||
position.column() + 1,
|
||||
position.line() + 1
|
||||
))
|
||||
};
|
||||
match (&self.mouse_mode, &self.mouse_tracking) {
|
||||
(_, MouseTracking::Off) => None,
|
||||
(MouseMode::NoEncoding | MouseMode::Utf8, MouseTracking::Normal) if !is_held => {
|
||||
utf8_event()
|
||||
},
|
||||
(MouseMode::NoEncoding | MouseMode::Utf8, MouseTracking::ButtonEventTracking) => {
|
||||
utf8_event()
|
||||
},
|
||||
(MouseMode::Sgr, MouseTracking::ButtonEventTracking) => sgr_event(),
|
||||
(MouseMode::Sgr, MouseTracking::Normal) if !is_held => sgr_event(),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
pub fn mouse_left_click_release_signal(&self, position: &Position) -> Option<String> {
|
||||
match (&self.mouse_mode, &self.mouse_tracking) {
|
||||
(_, MouseTracking::Off) => None,
|
||||
(MouseMode::NoEncoding | MouseMode::Utf8, _) => {
|
||||
let mut msg: Vec<u8> = vec![27, b'[', b'M', b'#'];
|
||||
msg.append(&mut utf8_mouse_coordinates(
|
||||
position.column() + 1,
|
||||
position.line() + 1,
|
||||
));
|
||||
Some(String::from_utf8_lossy(&msg).into())
|
||||
},
|
||||
(MouseMode::Sgr, _) => {
|
||||
let mouse_event = format!(
|
||||
"\u{1b}[<0;{:?};{:?}m",
|
||||
position.column() + 1,
|
||||
position.line() + 1
|
||||
);
|
||||
Some(mouse_event)
|
||||
},
|
||||
}
|
||||
}
|
||||
pub fn mouse_right_click_signal(&self, position: &Position, is_held: bool) -> Option<String> {
|
||||
let utf8_event = || -> Option<String> {
|
||||
let button_code = if is_held { b'B' } else { b'"' };
|
||||
let mut msg: Vec<u8> = vec![27, b'[', b'M', button_code];
|
||||
msg.append(&mut utf8_mouse_coordinates(
|
||||
position.column() + 1,
|
||||
position.line() + 1,
|
||||
));
|
||||
Some(String::from_utf8_lossy(&msg).into())
|
||||
};
|
||||
let sgr_event = || -> Option<String> {
|
||||
let button_code = if is_held { 34 } else { 2 };
|
||||
Some(format!(
|
||||
"\u{1b}[<{:?};{:?};{:?}M",
|
||||
button_code,
|
||||
position.column() + 1,
|
||||
position.line() + 1
|
||||
))
|
||||
};
|
||||
match (&self.mouse_mode, &self.mouse_tracking) {
|
||||
(_, MouseTracking::Off) => None,
|
||||
(MouseMode::NoEncoding | MouseMode::Utf8, MouseTracking::Normal) if !is_held => {
|
||||
utf8_event()
|
||||
},
|
||||
(MouseMode::NoEncoding | MouseMode::Utf8, MouseTracking::ButtonEventTracking) => {
|
||||
utf8_event()
|
||||
},
|
||||
(MouseMode::Sgr, MouseTracking::ButtonEventTracking) => sgr_event(),
|
||||
(MouseMode::Sgr, MouseTracking::Normal) if !is_held => sgr_event(),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
pub fn mouse_right_click_release_signal(&self, position: &Position) -> Option<String> {
|
||||
match (&self.mouse_mode, &self.mouse_tracking) {
|
||||
(_, MouseTracking::Off) => None,
|
||||
(MouseMode::NoEncoding | MouseMode::Utf8, _) => {
|
||||
let mut msg: Vec<u8> = vec![27, b'[', b'M', b'#'];
|
||||
msg.append(&mut utf8_mouse_coordinates(
|
||||
position.column() + 1,
|
||||
position.line() + 1,
|
||||
));
|
||||
Some(String::from_utf8_lossy(&msg).into())
|
||||
},
|
||||
(MouseMode::Sgr, _) => {
|
||||
let mouse_event = format!(
|
||||
"\u{1b}[<2;{:?};{:?}m",
|
||||
position.column() + 1,
|
||||
position.line() + 1
|
||||
);
|
||||
Some(mouse_event)
|
||||
},
|
||||
}
|
||||
}
|
||||
pub fn mouse_middle_click_signal(&self, position: &Position, is_held: bool) -> Option<String> {
|
||||
let utf8_event = || -> Option<String> {
|
||||
let button_code = if is_held { b'A' } else { b'!' };
|
||||
let mut msg: Vec<u8> = vec![27, b'[', b'M', button_code];
|
||||
msg.append(&mut utf8_mouse_coordinates(
|
||||
position.column() + 1,
|
||||
position.line() + 1,
|
||||
));
|
||||
Some(String::from_utf8_lossy(&msg).into())
|
||||
};
|
||||
let sgr_event = || -> Option<String> {
|
||||
let button_code = if is_held { 33 } else { 1 };
|
||||
Some(format!(
|
||||
"\u{1b}[<{:?};{:?};{:?}M",
|
||||
button_code,
|
||||
position.column() + 1,
|
||||
position.line() + 1
|
||||
))
|
||||
};
|
||||
match (&self.mouse_mode, &self.mouse_tracking) {
|
||||
(_, MouseTracking::Off) => None,
|
||||
(MouseMode::NoEncoding | MouseMode::Utf8, MouseTracking::Normal) if !is_held => {
|
||||
utf8_event()
|
||||
},
|
||||
(MouseMode::NoEncoding | MouseMode::Utf8, MouseTracking::ButtonEventTracking) => {
|
||||
utf8_event()
|
||||
},
|
||||
(MouseMode::Sgr, MouseTracking::ButtonEventTracking) => sgr_event(),
|
||||
(MouseMode::Sgr, MouseTracking::Normal) if !is_held => sgr_event(),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
pub fn mouse_middle_click_release_signal(&self, position: &Position) -> Option<String> {
|
||||
match (&self.mouse_mode, &self.mouse_tracking) {
|
||||
(_, MouseTracking::Off) => None,
|
||||
(MouseMode::NoEncoding | MouseMode::Utf8, _) => {
|
||||
let mut msg: Vec<u8> = vec![27, b'[', b'M', b'#'];
|
||||
msg.append(&mut utf8_mouse_coordinates(
|
||||
position.column() + 1,
|
||||
position.line() + 1,
|
||||
));
|
||||
Some(String::from_utf8_lossy(&msg).into())
|
||||
},
|
||||
(MouseMode::Sgr, _) => {
|
||||
// TODO: these don't add a +1 because it's done outside, we should change it to
|
||||
// happen here for consistency
|
||||
let mouse_event = format!(
|
||||
"\u{1b}[<1;{:?};{:?}m",
|
||||
position.column() + 1,
|
||||
position.line() + 1
|
||||
);
|
||||
Some(mouse_event)
|
||||
},
|
||||
}
|
||||
}
|
||||
pub fn mouse_scroll_up_signal(&self, position: &Position) -> Option<String> {
|
||||
match (&self.mouse_mode, &self.mouse_tracking) {
|
||||
(_, MouseTracking::Off) => None,
|
||||
(MouseMode::NoEncoding | MouseMode::Utf8, _) => {
|
||||
let mut msg: Vec<u8> = vec![27, b'[', b'M', b'`'];
|
||||
msg.append(&mut utf8_mouse_coordinates(
|
||||
position.column() + 1,
|
||||
position.line() + 1,
|
||||
));
|
||||
Some(String::from_utf8_lossy(&msg).into())
|
||||
},
|
||||
(MouseMode::Sgr, _) => {
|
||||
let mouse_event = format!(
|
||||
"\u{1b}[<64;{:?};{:?}M",
|
||||
position.column.0 + 1,
|
||||
position.line.0 + 1
|
||||
);
|
||||
Some(mouse_event)
|
||||
},
|
||||
}
|
||||
}
|
||||
pub fn mouse_scroll_down_signal(&self, position: &Position) -> Option<String> {
|
||||
match (&self.mouse_mode, &self.mouse_tracking) {
|
||||
(_, MouseTracking::Off) => None,
|
||||
(MouseMode::NoEncoding | MouseMode::Utf8, _) => {
|
||||
let mut msg: Vec<u8> = vec![27, b'[', b'M', b'a'];
|
||||
msg.append(&mut utf8_mouse_coordinates(
|
||||
position.column() + 1,
|
||||
position.line() + 1,
|
||||
));
|
||||
Some(String::from_utf8_lossy(&msg).into())
|
||||
},
|
||||
(MouseMode::Sgr, _) => {
|
||||
let mouse_event = format!(
|
||||
"\u{1b}[<65;{:?};{:?}M",
|
||||
position.column.0 + 1,
|
||||
position.line.0 + 1
|
||||
);
|
||||
Some(mouse_event)
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Perform for Grid {
|
||||
|
|
@ -2027,59 +2282,74 @@ impl Perform for Grid {
|
|||
_ => false,
|
||||
};
|
||||
if first_intermediate_is_questionmark {
|
||||
match params_iter.next().map(|param| param[0]) {
|
||||
Some(2004) => {
|
||||
self.bracketed_paste_mode = false;
|
||||
},
|
||||
Some(1049) => {
|
||||
if let Some(mut alternate_screen_state) = self.alternate_screen_state.take()
|
||||
{
|
||||
if let Some(image_ids_to_reap) = self.sixel_grid.clear() {
|
||||
// reap images before dropping the alternate_screen_state contents
|
||||
// - we can't implement a drop method for this because the store is
|
||||
// outside of the alternate_screen_state struct
|
||||
self.sixel_grid.reap_images(image_ids_to_reap);
|
||||
for param in params_iter.map(|param| param[0]) {
|
||||
match param {
|
||||
2004 => {
|
||||
self.bracketed_paste_mode = false;
|
||||
},
|
||||
1049 => {
|
||||
if let Some(mut alternate_screen_state) =
|
||||
self.alternate_screen_state.take()
|
||||
{
|
||||
if let Some(image_ids_to_reap) = self.sixel_grid.clear() {
|
||||
// reap images before dropping the alternate_screen_state contents
|
||||
// - we can't implement a drop method for this because the store is
|
||||
// outside of the alternate_screen_state struct
|
||||
self.sixel_grid.reap_images(image_ids_to_reap);
|
||||
}
|
||||
alternate_screen_state.apply_contents_to(
|
||||
&mut self.lines_above,
|
||||
&mut self.viewport,
|
||||
&mut self.cursor,
|
||||
&mut self.sixel_grid,
|
||||
);
|
||||
}
|
||||
alternate_screen_state.apply_contents_to(
|
||||
&mut self.lines_above,
|
||||
&mut self.viewport,
|
||||
&mut self.cursor,
|
||||
&mut self.sixel_grid,
|
||||
);
|
||||
}
|
||||
self.alternate_screen_state = None;
|
||||
self.clear_viewport_before_rendering = true;
|
||||
self.force_change_size(self.height, self.width); // the alternative_viewport might have been of a different size...
|
||||
self.mark_for_rerender();
|
||||
},
|
||||
Some(25) => {
|
||||
self.hide_cursor();
|
||||
self.mark_for_rerender();
|
||||
},
|
||||
Some(1) => {
|
||||
self.cursor_key_mode = false;
|
||||
},
|
||||
Some(3) => {
|
||||
// DECCOLM - only side effects
|
||||
self.scroll_region = None;
|
||||
self.clear_all(EMPTY_TERMINAL_CHARACTER);
|
||||
self.cursor.x = 0;
|
||||
self.cursor.y = 0;
|
||||
},
|
||||
Some(6) => {
|
||||
self.erasure_mode = false;
|
||||
},
|
||||
Some(7) => {
|
||||
self.disable_linewrap = true;
|
||||
},
|
||||
Some(80) => {
|
||||
self.sixel_scrolling = false;
|
||||
},
|
||||
Some(1006) => {
|
||||
self.mouse_mode = false;
|
||||
},
|
||||
_ => {},
|
||||
};
|
||||
self.alternate_screen_state = None;
|
||||
self.clear_viewport_before_rendering = true;
|
||||
self.force_change_size(self.height, self.width); // the alternative_viewport might have been of a different size...
|
||||
self.mark_for_rerender();
|
||||
},
|
||||
25 => {
|
||||
self.hide_cursor();
|
||||
self.mark_for_rerender();
|
||||
},
|
||||
1 => {
|
||||
self.cursor_key_mode = false;
|
||||
},
|
||||
3 => {
|
||||
// DECCOLM - only side effects
|
||||
self.scroll_region = None;
|
||||
self.clear_all(EMPTY_TERMINAL_CHARACTER);
|
||||
self.cursor.x = 0;
|
||||
self.cursor.y = 0;
|
||||
},
|
||||
6 => {
|
||||
self.erasure_mode = false;
|
||||
},
|
||||
7 => {
|
||||
self.disable_linewrap = true;
|
||||
},
|
||||
80 => {
|
||||
self.sixel_scrolling = false;
|
||||
},
|
||||
1000 => {
|
||||
self.mouse_tracking = MouseTracking::Off;
|
||||
},
|
||||
1002 => {
|
||||
self.mouse_tracking = MouseTracking::Off;
|
||||
},
|
||||
1003 => {
|
||||
// TBD: any-even mouse tracking
|
||||
},
|
||||
1005 => {
|
||||
self.mouse_mode = MouseMode::NoEncoding;
|
||||
},
|
||||
1006 => {
|
||||
self.mouse_mode = MouseMode::NoEncoding;
|
||||
},
|
||||
_ => {},
|
||||
};
|
||||
}
|
||||
} else if let Some(4) = params_iter.next().map(|param| param[0]) {
|
||||
self.insert_mode = false;
|
||||
}
|
||||
|
|
@ -2090,64 +2360,80 @@ impl Perform for Grid {
|
|||
_ => false,
|
||||
};
|
||||
if first_intermediate_is_questionmark {
|
||||
match params_iter.next().map(|param| param[0]) {
|
||||
Some(25) => {
|
||||
self.show_cursor();
|
||||
self.mark_for_rerender();
|
||||
},
|
||||
Some(2004) => {
|
||||
self.bracketed_paste_mode = true;
|
||||
},
|
||||
Some(1049) => {
|
||||
// enter alternate buffer
|
||||
let current_lines_above = std::mem::replace(
|
||||
&mut self.lines_above,
|
||||
VecDeque::with_capacity(*SCROLL_BUFFER_SIZE.get().unwrap()),
|
||||
);
|
||||
let current_viewport = std::mem::replace(
|
||||
&mut self.viewport,
|
||||
vec![Row::new(self.width).canonical()],
|
||||
);
|
||||
let current_cursor = std::mem::replace(&mut self.cursor, Cursor::new(0, 0));
|
||||
let sixel_image_store = self.sixel_grid.sixel_image_store.clone();
|
||||
let alternate_sixelgrid = std::mem::replace(
|
||||
&mut self.sixel_grid,
|
||||
SixelGrid::new(self.character_cell_size.clone(), sixel_image_store),
|
||||
);
|
||||
self.alternate_screen_state = Some(AlternateScreenState::new(
|
||||
current_lines_above,
|
||||
current_viewport,
|
||||
current_cursor,
|
||||
alternate_sixelgrid,
|
||||
));
|
||||
self.clear_viewport_before_rendering = true;
|
||||
self.scrollback_buffer_lines = self.recalculate_scrollback_buffer_count();
|
||||
self.output_buffer.update_all_lines(); // make sure the screen gets cleared in the next render
|
||||
},
|
||||
Some(1) => {
|
||||
self.cursor_key_mode = true;
|
||||
},
|
||||
Some(3) => {
|
||||
// DECCOLM - only side effects
|
||||
self.scroll_region = None;
|
||||
self.clear_all(EMPTY_TERMINAL_CHARACTER);
|
||||
self.cursor.x = 0;
|
||||
self.cursor.y = 0;
|
||||
},
|
||||
Some(6) => {
|
||||
self.erasure_mode = true;
|
||||
},
|
||||
Some(7) => {
|
||||
self.disable_linewrap = false;
|
||||
},
|
||||
Some(80) => {
|
||||
self.sixel_scrolling = true;
|
||||
},
|
||||
Some(1006) => {
|
||||
self.mouse_mode = true;
|
||||
},
|
||||
_ => {},
|
||||
};
|
||||
for param in params_iter.map(|param| param[0]) {
|
||||
match param {
|
||||
25 => {
|
||||
self.show_cursor();
|
||||
self.mark_for_rerender();
|
||||
},
|
||||
2004 => {
|
||||
self.bracketed_paste_mode = true;
|
||||
},
|
||||
1049 => {
|
||||
// enter alternate buffer
|
||||
let current_lines_above = std::mem::replace(
|
||||
&mut self.lines_above,
|
||||
VecDeque::with_capacity(*SCROLL_BUFFER_SIZE.get().unwrap()),
|
||||
);
|
||||
let current_viewport = std::mem::replace(
|
||||
&mut self.viewport,
|
||||
vec![Row::new(self.width).canonical()],
|
||||
);
|
||||
let current_cursor =
|
||||
std::mem::replace(&mut self.cursor, Cursor::new(0, 0));
|
||||
let sixel_image_store = self.sixel_grid.sixel_image_store.clone();
|
||||
let alternate_sixelgrid = std::mem::replace(
|
||||
&mut self.sixel_grid,
|
||||
SixelGrid::new(self.character_cell_size.clone(), sixel_image_store),
|
||||
);
|
||||
self.alternate_screen_state = Some(AlternateScreenState::new(
|
||||
current_lines_above,
|
||||
current_viewport,
|
||||
current_cursor,
|
||||
alternate_sixelgrid,
|
||||
));
|
||||
self.clear_viewport_before_rendering = true;
|
||||
self.scrollback_buffer_lines =
|
||||
self.recalculate_scrollback_buffer_count();
|
||||
self.output_buffer.update_all_lines(); // make sure the screen gets cleared in the next render
|
||||
},
|
||||
1 => {
|
||||
self.cursor_key_mode = true;
|
||||
},
|
||||
3 => {
|
||||
// DECCOLM - only side effects
|
||||
self.scroll_region = None;
|
||||
self.clear_all(EMPTY_TERMINAL_CHARACTER);
|
||||
self.cursor.x = 0;
|
||||
self.cursor.y = 0;
|
||||
},
|
||||
6 => {
|
||||
self.erasure_mode = true;
|
||||
},
|
||||
7 => {
|
||||
self.disable_linewrap = false;
|
||||
},
|
||||
80 => {
|
||||
self.sixel_scrolling = true;
|
||||
},
|
||||
1000 => {
|
||||
self.mouse_tracking = MouseTracking::Normal;
|
||||
},
|
||||
1002 => {
|
||||
self.mouse_tracking = MouseTracking::ButtonEventTracking;
|
||||
},
|
||||
1003 => {
|
||||
// TBD: any-even mouse tracking
|
||||
},
|
||||
1005 => {
|
||||
self.mouse_mode = MouseMode::Utf8;
|
||||
},
|
||||
1006 => {
|
||||
self.mouse_mode = MouseMode::Sgr;
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
} else if let Some(4) = params_iter.next().map(|param| param[0]) {
|
||||
self.insert_mode = true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -415,7 +415,4 @@ impl Pane for PluginPane {
|
|||
))
|
||||
.unwrap();
|
||||
}
|
||||
fn mouse_mode(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -566,10 +566,30 @@ impl Pane for TerminalPane {
|
|||
self.borderless
|
||||
}
|
||||
|
||||
fn mouse_mode(&self) -> bool {
|
||||
self.grid.mouse_mode
|
||||
fn mouse_left_click(&self, position: &Position, is_held: bool) -> Option<String> {
|
||||
self.grid.mouse_left_click_signal(position, is_held)
|
||||
}
|
||||
fn mouse_left_click_release(&self, position: &Position) -> Option<String> {
|
||||
self.grid.mouse_left_click_release_signal(position)
|
||||
}
|
||||
fn mouse_right_click(&self, position: &Position, is_held: bool) -> Option<String> {
|
||||
self.grid.mouse_right_click_signal(position, is_held)
|
||||
}
|
||||
fn mouse_right_click_release(&self, position: &Position) -> Option<String> {
|
||||
self.grid.mouse_right_click_release_signal(position)
|
||||
}
|
||||
fn mouse_middle_click(&self, position: &Position, is_held: bool) -> Option<String> {
|
||||
self.grid.mouse_middle_click_signal(position, is_held)
|
||||
}
|
||||
fn mouse_middle_click_release(&self, position: &Position) -> Option<String> {
|
||||
self.grid.mouse_middle_click_release_signal(position)
|
||||
}
|
||||
fn mouse_scroll_up(&self, position: &Position) -> Option<String> {
|
||||
self.grid.mouse_scroll_up_signal(position)
|
||||
}
|
||||
fn mouse_scroll_down(&self, position: &Position) -> Option<String> {
|
||||
self.grid.mouse_scroll_down_signal(position)
|
||||
}
|
||||
|
||||
fn get_line_number(&self) -> Option<usize> {
|
||||
// + 1 because the absolute position in the scrollback is 0 indexed and this should be 1 indexed
|
||||
Some(self.grid.absolute_position_in_scrollback() + 1)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use zellij_utils::errors::{ContextType, PtyWriteContext};
|
|||
|
||||
use crate::thread_bus::Bus;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub(crate) enum PtyWriteInstruction {
|
||||
Write(Vec<u8>, i32),
|
||||
Exit,
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ fn route_action(
|
|||
// this is a bit of a hack around the unfortunate architecture we use with plugins
|
||||
// this will change as soon as we refactor
|
||||
match action {
|
||||
Action::MouseHold(_) => {},
|
||||
Action::MouseHoldLeft(..) | Action::MouseHoldRight(..) => {},
|
||||
_ => {
|
||||
session
|
||||
.senders
|
||||
|
|
@ -376,17 +376,46 @@ fn route_action(
|
|||
.send_to_screen(ScreenInstruction::RightClick(point, client_id))
|
||||
.unwrap();
|
||||
},
|
||||
|
||||
Action::MouseRelease(point) => {
|
||||
Action::MiddleClick(point) => {
|
||||
session
|
||||
.senders
|
||||
.send_to_screen(ScreenInstruction::MouseRelease(point, client_id))
|
||||
.send_to_screen(ScreenInstruction::MiddleClick(point, client_id))
|
||||
.unwrap();
|
||||
},
|
||||
Action::MouseHold(point) => {
|
||||
Action::LeftMouseRelease(point) => {
|
||||
session
|
||||
.senders
|
||||
.send_to_screen(ScreenInstruction::MouseHold(point, client_id))
|
||||
.send_to_screen(ScreenInstruction::LeftMouseRelease(point, client_id))
|
||||
.unwrap();
|
||||
},
|
||||
Action::RightMouseRelease(point) => {
|
||||
session
|
||||
.senders
|
||||
.send_to_screen(ScreenInstruction::RightMouseRelease(point, client_id))
|
||||
.unwrap();
|
||||
},
|
||||
Action::MiddleMouseRelease(point) => {
|
||||
session
|
||||
.senders
|
||||
.send_to_screen(ScreenInstruction::MiddleMouseRelease(point, client_id))
|
||||
.unwrap();
|
||||
},
|
||||
Action::MouseHoldLeft(point) => {
|
||||
session
|
||||
.senders
|
||||
.send_to_screen(ScreenInstruction::MouseHoldLeft(point, client_id))
|
||||
.unwrap();
|
||||
},
|
||||
Action::MouseHoldRight(point) => {
|
||||
session
|
||||
.senders
|
||||
.send_to_screen(ScreenInstruction::MouseHoldRight(point, client_id))
|
||||
.unwrap();
|
||||
},
|
||||
Action::MouseHoldMiddle(point) => {
|
||||
session
|
||||
.senders
|
||||
.send_to_screen(ScreenInstruction::MouseHoldMiddle(point, client_id))
|
||||
.unwrap();
|
||||
},
|
||||
Action::Copy => {
|
||||
|
|
|
|||
|
|
@ -123,8 +123,13 @@ pub enum ScreenInstruction {
|
|||
ChangeMode(ModeInfo, ClientId),
|
||||
LeftClick(Position, ClientId),
|
||||
RightClick(Position, ClientId),
|
||||
MouseRelease(Position, ClientId),
|
||||
MouseHold(Position, ClientId),
|
||||
MiddleClick(Position, ClientId),
|
||||
LeftMouseRelease(Position, ClientId),
|
||||
RightMouseRelease(Position, ClientId),
|
||||
MiddleMouseRelease(Position, ClientId),
|
||||
MouseHoldLeft(Position, ClientId),
|
||||
MouseHoldRight(Position, ClientId),
|
||||
MouseHoldMiddle(Position, ClientId),
|
||||
Copy(ClientId),
|
||||
AddClient(ClientId),
|
||||
RemoveClient(ClientId),
|
||||
|
|
@ -222,8 +227,13 @@ impl From<&ScreenInstruction> for ScreenContext {
|
|||
ScreenInstruction::ScrollDownAt(..) => ScreenContext::ScrollDownAt,
|
||||
ScreenInstruction::LeftClick(..) => ScreenContext::LeftClick,
|
||||
ScreenInstruction::RightClick(..) => ScreenContext::RightClick,
|
||||
ScreenInstruction::MouseRelease(..) => ScreenContext::MouseRelease,
|
||||
ScreenInstruction::MouseHold(..) => ScreenContext::MouseHold,
|
||||
ScreenInstruction::MiddleClick(..) => ScreenContext::MiddleClick,
|
||||
ScreenInstruction::LeftMouseRelease(..) => ScreenContext::LeftMouseRelease,
|
||||
ScreenInstruction::RightMouseRelease(..) => ScreenContext::RightMouseRelease,
|
||||
ScreenInstruction::MiddleMouseRelease(..) => ScreenContext::MiddleMouseRelease,
|
||||
ScreenInstruction::MouseHoldLeft(..) => ScreenContext::MouseHoldLeft,
|
||||
ScreenInstruction::MouseHoldRight(..) => ScreenContext::MouseHoldRight,
|
||||
ScreenInstruction::MouseHoldMiddle(..) => ScreenContext::MouseHoldMiddle,
|
||||
ScreenInstruction::Copy(..) => ScreenContext::Copy,
|
||||
ScreenInstruction::ToggleTab(..) => ScreenContext::ToggleTab,
|
||||
ScreenInstruction::AddClient(..) => ScreenContext::AddClient,
|
||||
|
|
@ -1303,14 +1313,42 @@ pub(crate) fn screen_thread_main(
|
|||
screen.update_tabs();
|
||||
screen.render();
|
||||
},
|
||||
ScreenInstruction::MouseRelease(point, client_id) => {
|
||||
ScreenInstruction::MiddleClick(point, client_id) => {
|
||||
active_tab!(screen, client_id, |tab: &mut Tab| tab
|
||||
.handle_mouse_release(&point, client_id));
|
||||
.handle_middle_click(&point, client_id));
|
||||
screen.update_tabs();
|
||||
screen.render();
|
||||
},
|
||||
ScreenInstruction::MouseHold(point, client_id) => {
|
||||
ScreenInstruction::LeftMouseRelease(point, client_id) => {
|
||||
active_tab!(screen, client_id, |tab: &mut Tab| tab
|
||||
.handle_left_mouse_release(&point, client_id));
|
||||
screen.render();
|
||||
},
|
||||
ScreenInstruction::RightMouseRelease(point, client_id) => {
|
||||
active_tab!(screen, client_id, |tab: &mut Tab| tab
|
||||
.handle_right_mouse_release(&point, client_id));
|
||||
screen.render();
|
||||
},
|
||||
ScreenInstruction::MiddleMouseRelease(point, client_id) => {
|
||||
active_tab!(screen, client_id, |tab: &mut Tab| tab
|
||||
.handle_middle_mouse_release(&point, client_id));
|
||||
screen.render();
|
||||
},
|
||||
ScreenInstruction::MouseHoldLeft(point, client_id) => {
|
||||
active_tab!(screen, client_id, |tab: &mut Tab| {
|
||||
tab.handle_mouse_hold(&point, client_id);
|
||||
tab.handle_mouse_hold_left(&point, client_id);
|
||||
});
|
||||
screen.render();
|
||||
},
|
||||
ScreenInstruction::MouseHoldRight(point, client_id) => {
|
||||
active_tab!(screen, client_id, |tab: &mut Tab| {
|
||||
tab.handle_mouse_hold_right(&point, client_id);
|
||||
});
|
||||
screen.render();
|
||||
},
|
||||
ScreenInstruction::MouseHoldMiddle(point, client_id) => {
|
||||
active_tab!(screen, client_id, |tab: &mut Tab| {
|
||||
tab.handle_mouse_hold_middle(&point, client_id);
|
||||
});
|
||||
screen.render();
|
||||
},
|
||||
|
|
|
|||
|
|
@ -296,8 +296,32 @@ pub trait Pane {
|
|||
fn load_pane_name(&mut self);
|
||||
fn set_borderless(&mut self, borderless: bool);
|
||||
fn borderless(&self) -> bool;
|
||||
// TODO: this should probably be merged with the mouse_right_click
|
||||
fn handle_right_click(&mut self, _to: &Position, _client_id: ClientId) {}
|
||||
fn mouse_mode(&self) -> bool;
|
||||
fn mouse_left_click(&self, _position: &Position, _is_held: bool) -> Option<String> {
|
||||
None
|
||||
}
|
||||
fn mouse_left_click_release(&self, _position: &Position) -> Option<String> {
|
||||
None
|
||||
}
|
||||
fn mouse_right_click(&self, _position: &Position, _is_held: bool) -> Option<String> {
|
||||
None
|
||||
}
|
||||
fn mouse_right_click_release(&self, _position: &Position) -> Option<String> {
|
||||
None
|
||||
}
|
||||
fn mouse_middle_click(&self, _position: &Position, _is_held: bool) -> Option<String> {
|
||||
None
|
||||
}
|
||||
fn mouse_middle_click_release(&self, _position: &Position) -> Option<String> {
|
||||
None
|
||||
}
|
||||
fn mouse_scroll_up(&self, _position: &Position) -> Option<String> {
|
||||
None
|
||||
}
|
||||
fn mouse_scroll_down(&self, _position: &Position) -> Option<String> {
|
||||
None
|
||||
}
|
||||
fn get_line_number(&self) -> Option<usize> {
|
||||
None
|
||||
}
|
||||
|
|
@ -1668,13 +1692,8 @@ impl Tab {
|
|||
}
|
||||
pub fn scroll_terminal_up(&mut self, point: &Position, lines: usize, client_id: ClientId) {
|
||||
if let Some(pane) = self.get_pane_at(point, false) {
|
||||
if pane.mouse_mode() {
|
||||
let relative_position = pane.relative_position(point);
|
||||
let mouse_event = format!(
|
||||
"\u{1b}[<64;{:?};{:?}M",
|
||||
relative_position.column.0 + 1,
|
||||
relative_position.line.0 + 1
|
||||
);
|
||||
let relative_position = pane.relative_position(point);
|
||||
if let Some(mouse_event) = pane.mouse_scroll_up(&relative_position) {
|
||||
self.write_to_terminal_at(mouse_event.into_bytes(), point);
|
||||
} else {
|
||||
pane.scroll_up(lines, client_id);
|
||||
|
|
@ -1683,13 +1702,8 @@ impl Tab {
|
|||
}
|
||||
pub fn scroll_terminal_down(&mut self, point: &Position, lines: usize, client_id: ClientId) {
|
||||
if let Some(pane) = self.get_pane_at(point, false) {
|
||||
if pane.mouse_mode() {
|
||||
let relative_position = pane.relative_position(point);
|
||||
let mouse_event = format!(
|
||||
"\u{1b}[<65;{:?};{:?}M",
|
||||
relative_position.column.0 + 1,
|
||||
relative_position.line.0 + 1
|
||||
);
|
||||
let relative_position = pane.relative_position(point);
|
||||
if let Some(mouse_event) = pane.mouse_scroll_down(&relative_position) {
|
||||
self.write_to_terminal_at(mouse_event.into_bytes(), point);
|
||||
} else {
|
||||
pane.scroll_down(lines, client_id);
|
||||
|
|
@ -1755,18 +1769,11 @@ impl Tab {
|
|||
|
||||
if let Some(pane) = self.get_pane_at(position, false) {
|
||||
let relative_position = pane.relative_position(position);
|
||||
|
||||
if pane.mouse_mode() {
|
||||
if let Some(mouse_event) = pane.mouse_left_click(&relative_position, false) {
|
||||
if !pane.position_is_on_frame(position) {
|
||||
let mouse_event = format!(
|
||||
"\u{1b}[<0;{:?};{:?}M",
|
||||
relative_position.column() + 1,
|
||||
relative_position.line() + 1
|
||||
);
|
||||
self.write_to_active_terminal(mouse_event.into_bytes(), client_id);
|
||||
}
|
||||
} else {
|
||||
// TODO: rename this method, it is used to forward click events to plugin panes
|
||||
pane.start_selection(&relative_position, client_id);
|
||||
if let PaneId::Terminal(_) = pane.pid() {
|
||||
self.selecting_with_mouse = true;
|
||||
|
|
@ -1779,13 +1786,8 @@ impl Tab {
|
|||
|
||||
if let Some(pane) = self.get_pane_at(position, false) {
|
||||
let relative_position = pane.relative_position(position);
|
||||
if pane.mouse_mode() {
|
||||
if let Some(mouse_event) = pane.mouse_right_click(&relative_position, false) {
|
||||
if !pane.position_is_on_frame(position) {
|
||||
let mouse_event = format!(
|
||||
"\u{1b}[<2;{:?};{:?}M",
|
||||
relative_position.column() + 1,
|
||||
relative_position.line() + 1
|
||||
);
|
||||
self.write_to_active_terminal(mouse_event.into_bytes(), client_id);
|
||||
}
|
||||
} else {
|
||||
|
|
@ -1793,6 +1795,18 @@ impl Tab {
|
|||
}
|
||||
};
|
||||
}
|
||||
pub fn handle_middle_click(&mut self, position: &Position, client_id: ClientId) {
|
||||
self.focus_pane_at(position, client_id);
|
||||
|
||||
if let Some(pane) = self.get_pane_at(position, false) {
|
||||
let relative_position = pane.relative_position(position);
|
||||
if let Some(mouse_event) = pane.mouse_middle_click(&relative_position, false) {
|
||||
if !pane.position_is_on_frame(position) {
|
||||
self.write_to_active_terminal(mouse_event.into_bytes(), client_id);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
fn focus_pane_at(&mut self, point: &Position, client_id: ClientId) {
|
||||
if self.floating_panes.panes_are_visible() {
|
||||
if let Some(clicked_pane) = self.floating_panes.get_pane_id_at(point, true) {
|
||||
|
|
@ -1810,7 +1824,51 @@ impl Tab {
|
|||
}
|
||||
}
|
||||
}
|
||||
pub fn handle_mouse_release(&mut self, position: &Position, client_id: ClientId) {
|
||||
pub fn handle_right_mouse_release(&mut self, position: &Position, client_id: ClientId) {
|
||||
self.last_mouse_hold_position = None;
|
||||
let active_pane = self.get_active_pane_or_floating_pane_mut(client_id);
|
||||
if let Some(active_pane) = active_pane {
|
||||
let mut relative_position = active_pane.relative_position(position);
|
||||
relative_position.change_column(
|
||||
(relative_position.column())
|
||||
.max(0)
|
||||
.min(active_pane.get_content_columns()),
|
||||
);
|
||||
|
||||
relative_position.change_line(
|
||||
(relative_position.line())
|
||||
.max(0)
|
||||
.min(active_pane.get_content_rows() as isize),
|
||||
);
|
||||
|
||||
if let Some(mouse_event) = active_pane.mouse_right_click_release(&relative_position) {
|
||||
self.write_to_active_terminal(mouse_event.into_bytes(), client_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn handle_middle_mouse_release(&mut self, position: &Position, client_id: ClientId) {
|
||||
self.last_mouse_hold_position = None;
|
||||
let active_pane = self.get_active_pane_or_floating_pane_mut(client_id);
|
||||
if let Some(active_pane) = active_pane {
|
||||
let mut relative_position = active_pane.relative_position(position);
|
||||
relative_position.change_column(
|
||||
(relative_position.column())
|
||||
.max(0)
|
||||
.min(active_pane.get_content_columns()),
|
||||
);
|
||||
|
||||
relative_position.change_line(
|
||||
(relative_position.line())
|
||||
.max(0)
|
||||
.min(active_pane.get_content_rows() as isize),
|
||||
);
|
||||
|
||||
if let Some(mouse_event) = active_pane.mouse_middle_click_release(&relative_position) {
|
||||
self.write_to_active_terminal(mouse_event.into_bytes(), client_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn handle_left_mouse_release(&mut self, position: &Position, client_id: ClientId) {
|
||||
self.last_mouse_hold_position = None;
|
||||
|
||||
if self.floating_panes.panes_are_visible()
|
||||
|
|
@ -1826,20 +1884,23 @@ impl Tab {
|
|||
let active_pane = self.get_active_pane_or_floating_pane_mut(client_id);
|
||||
|
||||
if let Some(active_pane) = active_pane {
|
||||
let relative_position = active_pane.relative_position(position);
|
||||
if active_pane.mouse_mode() {
|
||||
// ensure that coordinates are valid
|
||||
let col = (relative_position.column() + 1)
|
||||
.max(1)
|
||||
.min(active_pane.get_content_columns());
|
||||
let mut relative_position = active_pane.relative_position(position);
|
||||
relative_position.change_column(
|
||||
(relative_position.column())
|
||||
.max(0)
|
||||
.min(active_pane.get_content_columns()),
|
||||
);
|
||||
|
||||
let line = (relative_position.line() + 1)
|
||||
.max(1)
|
||||
.min(active_pane.get_content_rows() as isize);
|
||||
let mouse_event = format!("\u{1b}[<0;{:?};{:?}m", col, line);
|
||||
relative_position.change_line(
|
||||
(relative_position.line())
|
||||
.max(0)
|
||||
.min(active_pane.get_content_rows() as isize),
|
||||
);
|
||||
|
||||
if let Some(mouse_event) = active_pane.mouse_left_click_release(&relative_position) {
|
||||
self.write_to_active_terminal(mouse_event.into_bytes(), client_id);
|
||||
} else {
|
||||
// TODO: rename this method, it is used to forward release events to plugin panes
|
||||
let relative_position = active_pane.relative_position(position);
|
||||
if let PaneId::Terminal(_) = active_pane.pid() {
|
||||
if selecting && copy_on_release {
|
||||
active_pane.end_selection(&relative_position, client_id);
|
||||
|
|
@ -1858,7 +1919,7 @@ impl Tab {
|
|||
}
|
||||
}
|
||||
}
|
||||
pub fn handle_mouse_hold(
|
||||
pub fn handle_mouse_hold_left(
|
||||
&mut self,
|
||||
position_on_screen: &Position,
|
||||
client_id: ClientId,
|
||||
|
|
@ -1889,20 +1950,24 @@ impl Tab {
|
|||
let active_pane = self.get_active_pane_or_floating_pane_mut(client_id);
|
||||
|
||||
if let Some(active_pane) = active_pane {
|
||||
let relative_position = active_pane.relative_position(position_on_screen);
|
||||
if active_pane.mouse_mode() && !is_repeated {
|
||||
let mut relative_position = active_pane.relative_position(position_on_screen);
|
||||
if !is_repeated {
|
||||
// ensure that coordinates are valid
|
||||
let col = (relative_position.column() + 1)
|
||||
.max(1)
|
||||
.min(active_pane.get_content_columns());
|
||||
relative_position.change_column(
|
||||
(relative_position.column())
|
||||
.max(0)
|
||||
.min(active_pane.get_content_columns()),
|
||||
);
|
||||
|
||||
let line = (relative_position.line() + 1)
|
||||
.max(1)
|
||||
.min(active_pane.get_content_rows() as isize);
|
||||
|
||||
let mouse_event = format!("\u{1b}[<32;{:?};{:?}M", col, line);
|
||||
self.write_to_active_terminal(mouse_event.into_bytes(), client_id);
|
||||
return true; // we need to re-render in this case so the selection disappears
|
||||
relative_position.change_line(
|
||||
(relative_position.line())
|
||||
.max(0)
|
||||
.min(active_pane.get_content_rows() as isize),
|
||||
);
|
||||
if let Some(mouse_event) = active_pane.mouse_left_click(&relative_position, true) {
|
||||
self.write_to_active_terminal(mouse_event.into_bytes(), client_id);
|
||||
return true; // we need to re-render in this case so the selection disappears
|
||||
}
|
||||
} else if selecting {
|
||||
active_pane.update_selection(&relative_position, client_id);
|
||||
return true; // we need to re-render in this case so the selection is updated
|
||||
|
|
@ -1910,6 +1975,87 @@ impl Tab {
|
|||
}
|
||||
false // we shouldn't even get here, but might as well not needlessly render if we do
|
||||
}
|
||||
pub fn handle_mouse_hold_right(
|
||||
&mut self,
|
||||
position_on_screen: &Position,
|
||||
client_id: ClientId,
|
||||
) -> bool {
|
||||
// return value indicates whether we should trigger a render
|
||||
// determine if event is repeated to enable smooth scrolling
|
||||
let is_repeated = if let Some(last_position) = self.last_mouse_hold_position {
|
||||
position_on_screen == &last_position
|
||||
} else {
|
||||
false
|
||||
};
|
||||
self.last_mouse_hold_position = Some(*position_on_screen);
|
||||
|
||||
let active_pane = self.get_active_pane_or_floating_pane_mut(client_id);
|
||||
|
||||
if let Some(active_pane) = active_pane {
|
||||
let mut relative_position = active_pane.relative_position(position_on_screen);
|
||||
if !is_repeated {
|
||||
relative_position.change_column(
|
||||
(relative_position.column())
|
||||
.max(0)
|
||||
.min(active_pane.get_content_columns()),
|
||||
);
|
||||
|
||||
relative_position.change_line(
|
||||
(relative_position.line())
|
||||
.max(0)
|
||||
.min(active_pane.get_content_rows() as isize),
|
||||
);
|
||||
if let Some(mouse_event) = active_pane.mouse_right_click(&relative_position, true) {
|
||||
self.write_to_active_terminal(mouse_event.into_bytes(), client_id);
|
||||
return true; // we need to re-render in this case so the selection disappears
|
||||
}
|
||||
}
|
||||
}
|
||||
false // we shouldn't even get here, but might as well not needlessly render if we do
|
||||
}
|
||||
pub fn handle_mouse_hold_middle(
|
||||
&mut self,
|
||||
position_on_screen: &Position,
|
||||
client_id: ClientId,
|
||||
) -> bool {
|
||||
println!("mouse hold middle");
|
||||
// return value indicates whether we should trigger a render
|
||||
// determine if event is repeated to enable smooth scrolling
|
||||
let is_repeated = if let Some(last_position) = self.last_mouse_hold_position {
|
||||
position_on_screen == &last_position
|
||||
} else {
|
||||
false
|
||||
};
|
||||
println!("is repeated: {:?}", is_repeated);
|
||||
self.last_mouse_hold_position = Some(*position_on_screen);
|
||||
|
||||
let active_pane = self.get_active_pane_or_floating_pane_mut(client_id);
|
||||
|
||||
if let Some(active_pane) = active_pane {
|
||||
println!("can have active pane");
|
||||
let mut relative_position = active_pane.relative_position(position_on_screen);
|
||||
if !is_repeated {
|
||||
relative_position.change_column(
|
||||
(relative_position.column())
|
||||
.max(0)
|
||||
.min(active_pane.get_content_columns()),
|
||||
);
|
||||
|
||||
relative_position.change_line(
|
||||
(relative_position.line())
|
||||
.max(0)
|
||||
.min(active_pane.get_content_rows() as isize),
|
||||
);
|
||||
if let Some(mouse_event) = active_pane.mouse_middle_click(&relative_position, true)
|
||||
{
|
||||
log::info!("can have mouse event: {:?}", mouse_event);
|
||||
self.write_to_active_terminal(mouse_event.into_bytes(), client_id);
|
||||
return true; // we need to re-render in this case so the selection disappears
|
||||
}
|
||||
}
|
||||
}
|
||||
false // we shouldn't even get here, but might as well not needlessly render if we do
|
||||
}
|
||||
|
||||
pub fn copy_selection(&self, client_id: ClientId) {
|
||||
let selected_text = self
|
||||
|
|
|
|||
|
|
@ -17,6 +17,9 @@ use zellij_utils::ipc::IpcReceiverWithContext;
|
|||
use zellij_utils::pane_size::{Size, SizeInPixels};
|
||||
use zellij_utils::position::Position;
|
||||
|
||||
use crate::pty_writer::PtyWriteInstruction;
|
||||
use zellij_utils::channels::{self, ChannelWithContext, SenderWithContext};
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::os::unix::io::RawFd;
|
||||
|
|
@ -150,6 +153,63 @@ fn create_new_tab(size: Size, default_mode: ModeInfo) -> Tab {
|
|||
tab
|
||||
}
|
||||
|
||||
fn create_new_tab_with_mock_pty_writer(
|
||||
size: Size,
|
||||
default_mode: ModeInfo,
|
||||
mock_pty_writer: SenderWithContext<PtyWriteInstruction>,
|
||||
) -> Tab {
|
||||
set_session_name("test".into());
|
||||
let index = 0;
|
||||
let position = 0;
|
||||
let name = String::new();
|
||||
let os_api = Box::new(FakeInputOutput {
|
||||
file_dumps: Arc::new(Mutex::new(HashMap::new())),
|
||||
});
|
||||
let mut senders = ThreadSenders::default().silently_fail_on_send();
|
||||
senders.replace_to_pty_writer(mock_pty_writer);
|
||||
let max_panes = None;
|
||||
let mode_info = default_mode;
|
||||
let style = Style::default();
|
||||
let draw_pane_frames = true;
|
||||
let client_id = 1;
|
||||
let session_is_mirrored = true;
|
||||
let mut connected_clients = HashSet::new();
|
||||
connected_clients.insert(client_id);
|
||||
let connected_clients = Rc::new(RefCell::new(connected_clients));
|
||||
let character_cell_info = Rc::new(RefCell::new(None));
|
||||
let terminal_emulator_colors = Rc::new(RefCell::new(Palette::default()));
|
||||
let copy_options = CopyOptions::default();
|
||||
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
|
||||
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
|
||||
let mut tab = Tab::new(
|
||||
index,
|
||||
position,
|
||||
name,
|
||||
size,
|
||||
character_cell_info,
|
||||
sixel_image_store,
|
||||
os_api,
|
||||
senders,
|
||||
max_panes,
|
||||
style,
|
||||
mode_info,
|
||||
draw_pane_frames,
|
||||
connected_clients,
|
||||
session_is_mirrored,
|
||||
client_id,
|
||||
copy_options,
|
||||
terminal_emulator_colors,
|
||||
terminal_emulator_color_codes,
|
||||
);
|
||||
tab.apply_layout(
|
||||
LayoutTemplate::default().try_into().unwrap(),
|
||||
vec![1],
|
||||
index,
|
||||
client_id,
|
||||
);
|
||||
tab
|
||||
}
|
||||
|
||||
fn create_new_tab_with_sixel_support(
|
||||
size: Size,
|
||||
sixel_image_store: Rc<RefCell<SixelImageStore>>,
|
||||
|
|
@ -846,7 +906,7 @@ fn move_floating_pane_focus_with_mouse() {
|
|||
tab.handle_pty_bytes(5, Vec::from("\u{1b}#8".as_bytes()));
|
||||
tab.handle_pty_bytes(6, Vec::from("\u{1b}#8".as_bytes()));
|
||||
tab.handle_left_click(&Position::new(9, 71), client_id);
|
||||
tab.handle_mouse_release(&Position::new(9, 71), client_id);
|
||||
tab.handle_left_mouse_release(&Position::new(9, 71), client_id);
|
||||
tab.render(&mut output, None);
|
||||
let (snapshot, cursor_coordinates) = take_snapshot_and_cursor_position(
|
||||
output.serialize().get(&client_id).unwrap(),
|
||||
|
|
@ -892,7 +952,7 @@ fn move_pane_focus_with_mouse_to_non_floating_pane() {
|
|||
tab.handle_pty_bytes(5, Vec::from("\u{1b}#8".as_bytes()));
|
||||
tab.handle_pty_bytes(6, Vec::from("\u{1b}#8".as_bytes()));
|
||||
tab.handle_left_click(&Position::new(4, 71), client_id);
|
||||
tab.handle_mouse_release(&Position::new(4, 71), client_id);
|
||||
tab.handle_left_mouse_release(&Position::new(4, 71), client_id);
|
||||
tab.render(&mut output, None);
|
||||
let (snapshot, cursor_coordinates) = take_snapshot_and_cursor_position(
|
||||
output.serialize().get(&client_id).unwrap(),
|
||||
|
|
@ -938,7 +998,7 @@ fn drag_pane_with_mouse() {
|
|||
tab.handle_pty_bytes(5, Vec::from("\u{1b}#8".as_bytes()));
|
||||
tab.handle_pty_bytes(6, Vec::from("\u{1b}#8".as_bytes()));
|
||||
tab.handle_left_click(&Position::new(5, 71), client_id);
|
||||
tab.handle_mouse_release(&Position::new(7, 75), client_id);
|
||||
tab.handle_left_mouse_release(&Position::new(7, 75), client_id);
|
||||
tab.render(&mut output, None);
|
||||
let (snapshot, cursor_coordinates) = take_snapshot_and_cursor_position(
|
||||
output.serialize().get(&client_id).unwrap(),
|
||||
|
|
@ -988,7 +1048,7 @@ fn mark_text_inside_floating_pane() {
|
|||
tab.selecting_with_mouse,
|
||||
"started selecting with mouse on click"
|
||||
);
|
||||
tab.handle_mouse_release(&Position::new(8, 50), client_id);
|
||||
tab.handle_left_mouse_release(&Position::new(8, 50), client_id);
|
||||
assert!(
|
||||
!tab.selecting_with_mouse,
|
||||
"stopped selecting with mouse on release"
|
||||
|
|
@ -1357,7 +1417,7 @@ fn move_floating_pane_with_sixel_image() {
|
|||
let fixture = read_fixture("sixel-image-500px.six");
|
||||
tab.handle_pty_bytes(2, fixture);
|
||||
tab.handle_left_click(&Position::new(5, 71), client_id);
|
||||
tab.handle_mouse_release(&Position::new(7, 75), client_id);
|
||||
tab.handle_left_mouse_release(&Position::new(7, 75), client_id);
|
||||
|
||||
tab.render(&mut output, None);
|
||||
let snapshot = take_snapshot_with_sixel(
|
||||
|
|
@ -1392,7 +1452,7 @@ fn floating_pane_above_sixel_image() {
|
|||
let fixture = read_fixture("sixel-image-500px.six");
|
||||
tab.handle_pty_bytes(1, fixture);
|
||||
tab.handle_left_click(&Position::new(5, 71), client_id);
|
||||
tab.handle_mouse_release(&Position::new(7, 75), client_id);
|
||||
tab.handle_left_mouse_release(&Position::new(7, 75), client_id);
|
||||
|
||||
tab.render(&mut output, None);
|
||||
let snapshot = take_snapshot_with_sixel(
|
||||
|
|
@ -1720,3 +1780,267 @@ fn enter_search_floating_pane() {
|
|||
);
|
||||
assert_snapshot!("search_floating_tab_highlight_fring", snapshot);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pane_in_sgr_button_event_tracking_mouse_mode() {
|
||||
let size = Size {
|
||||
cols: 121,
|
||||
rows: 20,
|
||||
};
|
||||
let client_id = 1;
|
||||
|
||||
let messages_to_pty_writer = Arc::new(Mutex::new(vec![]));
|
||||
let (to_pty_writer, pty_writer_receiver): ChannelWithContext<PtyWriteInstruction> =
|
||||
channels::unbounded();
|
||||
let to_pty_writer = SenderWithContext::new(to_pty_writer);
|
||||
let mut tab = create_new_tab_with_mock_pty_writer(size, ModeInfo::default(), to_pty_writer);
|
||||
|
||||
// TODO: note that this thread does not die when the test dies
|
||||
// it only dies once all the test process exits... not a biggy if we have only a handful of
|
||||
// these, but otherwise we might want to think of a better way to handle this
|
||||
let _pty_writer_thread = std::thread::Builder::new()
|
||||
.name("pty_writer".to_string())
|
||||
.spawn({
|
||||
// TODO: kill this thread
|
||||
let messages_to_pty_writer = messages_to_pty_writer.clone();
|
||||
move || loop {
|
||||
let (event, _err_ctx) = pty_writer_receiver
|
||||
.recv()
|
||||
.expect("failed to receive event on channel");
|
||||
if let PtyWriteInstruction::Write(msg, _) = event {
|
||||
messages_to_pty_writer
|
||||
.lock()
|
||||
.unwrap()
|
||||
.push(String::from_utf8_lossy(&msg).to_string());
|
||||
}
|
||||
}
|
||||
});
|
||||
let sgr_mouse_mode_any_button = String::from("\u{1b}[?1002;1006h"); // button event tracking (1002) with SGR encoding (1006)
|
||||
tab.handle_pty_bytes(1, sgr_mouse_mode_any_button.as_bytes().to_vec());
|
||||
tab.handle_left_click(&Position::new(5, 71), client_id);
|
||||
tab.handle_mouse_hold_left(&Position::new(9, 72), client_id);
|
||||
tab.handle_left_mouse_release(&Position::new(7, 75), client_id);
|
||||
tab.handle_right_click(&Position::new(5, 71), client_id);
|
||||
tab.handle_mouse_hold_right(&Position::new(9, 72), client_id);
|
||||
tab.handle_right_mouse_release(&Position::new(7, 75), client_id);
|
||||
tab.handle_middle_click(&Position::new(5, 71), client_id);
|
||||
tab.handle_mouse_hold_middle(&Position::new(9, 72), client_id);
|
||||
tab.handle_middle_mouse_release(&Position::new(7, 75), client_id);
|
||||
tab.scroll_terminal_up(&Position::new(5, 71), 1, client_id);
|
||||
tab.scroll_terminal_down(&Position::new(5, 71), 1, client_id);
|
||||
std::thread::sleep(std::time::Duration::from_millis(100)); // give time for messages to arrive
|
||||
assert_eq!(
|
||||
*messages_to_pty_writer.lock().unwrap(),
|
||||
vec![
|
||||
"\u{1b}[<0;71;5M".to_string(), // SGR left click
|
||||
"\u{1b}[<32;72;9M".to_string(), // SGR left click (hold)
|
||||
"\u{1b}[<0;75;7m".to_string(), // SGR left button release
|
||||
"\u{1b}[<2;71;5M".to_string(), // SGR right click
|
||||
"\u{1b}[<34;72;9M".to_string(), // SGR right click (hold)
|
||||
"\u{1b}[<2;75;7m".to_string(), // SGR right button release
|
||||
"\u{1b}[<1;71;5M".to_string(), // SGR middle click
|
||||
"\u{1b}[<33;72;9M".to_string(), // SGR middle click (hold)
|
||||
"\u{1b}[<1;75;7m".to_string(), // SGR middle button release
|
||||
"\u{1b}[<64;71;5M".to_string(), // SGR scroll up
|
||||
"\u{1b}[<65;71;5M".to_string(), // SGR scroll down
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pane_in_sgr_normal_event_tracking_mouse_mode() {
|
||||
let size = Size {
|
||||
cols: 121,
|
||||
rows: 20,
|
||||
};
|
||||
let client_id = 1;
|
||||
|
||||
let messages_to_pty_writer = Arc::new(Mutex::new(vec![]));
|
||||
let (to_pty_writer, pty_writer_receiver): ChannelWithContext<PtyWriteInstruction> =
|
||||
channels::unbounded();
|
||||
let to_pty_writer = SenderWithContext::new(to_pty_writer);
|
||||
let mut tab = create_new_tab_with_mock_pty_writer(size, ModeInfo::default(), to_pty_writer);
|
||||
|
||||
// TODO: note that this thread does not die when the test dies
|
||||
// it only dies once all the test process exits... not a biggy if we have only a handful of
|
||||
// these, but otherwise we might want to think of a better way to handle this
|
||||
let _pty_writer_thread = std::thread::Builder::new()
|
||||
.name("pty_writer".to_string())
|
||||
.spawn({
|
||||
// TODO: kill this thread
|
||||
let messages_to_pty_writer = messages_to_pty_writer.clone();
|
||||
move || loop {
|
||||
let (event, _err_ctx) = pty_writer_receiver
|
||||
.recv()
|
||||
.expect("failed to receive event on channel");
|
||||
if let PtyWriteInstruction::Write(msg, _) = event {
|
||||
messages_to_pty_writer
|
||||
.lock()
|
||||
.unwrap()
|
||||
.push(String::from_utf8_lossy(&msg).to_string());
|
||||
}
|
||||
}
|
||||
});
|
||||
let sgr_mouse_mode_any_button = String::from("\u{1b}[?1000;1006h"); // normal event tracking (1000) with sgr encoding (1006)
|
||||
tab.handle_pty_bytes(1, sgr_mouse_mode_any_button.as_bytes().to_vec());
|
||||
tab.handle_left_click(&Position::new(5, 71), client_id);
|
||||
tab.handle_mouse_hold_left(&Position::new(9, 72), client_id);
|
||||
tab.handle_left_mouse_release(&Position::new(7, 75), client_id);
|
||||
tab.handle_right_click(&Position::new(5, 71), client_id);
|
||||
tab.handle_mouse_hold_right(&Position::new(9, 72), client_id);
|
||||
tab.handle_right_mouse_release(&Position::new(7, 75), client_id);
|
||||
tab.handle_middle_click(&Position::new(5, 71), client_id);
|
||||
tab.handle_mouse_hold_middle(&Position::new(9, 72), client_id);
|
||||
tab.handle_middle_mouse_release(&Position::new(7, 75), client_id);
|
||||
tab.scroll_terminal_up(&Position::new(5, 71), 1, client_id);
|
||||
tab.scroll_terminal_down(&Position::new(5, 71), 1, client_id);
|
||||
std::thread::sleep(std::time::Duration::from_millis(100)); // give time for messages to arrive
|
||||
assert_eq!(
|
||||
*messages_to_pty_writer.lock().unwrap(),
|
||||
vec![
|
||||
"\u{1b}[<0;71;5M".to_string(), // SGR left click
|
||||
// no hold event here, as hold events are not reported in normal mode
|
||||
"\u{1b}[<0;75;7m".to_string(), // SGR left button release
|
||||
"\u{1b}[<2;71;5M".to_string(), // SGR right click
|
||||
// no hold event here, as hold events are not reported in normal mode
|
||||
"\u{1b}[<2;75;7m".to_string(), // SGR right button release
|
||||
"\u{1b}[<1;71;5M".to_string(), // SGR middle click
|
||||
// no hold event here, as hold events are not reported in normal mode
|
||||
"\u{1b}[<1;75;7m".to_string(), // SGR middle button release
|
||||
"\u{1b}[<64;71;5M".to_string(), // SGR scroll up
|
||||
"\u{1b}[<65;71;5M".to_string(), // SGR scroll down
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pane_in_utf8_button_event_tracking_mouse_mode() {
|
||||
let size = Size {
|
||||
cols: 121,
|
||||
rows: 20,
|
||||
};
|
||||
let client_id = 1;
|
||||
|
||||
let messages_to_pty_writer = Arc::new(Mutex::new(vec![]));
|
||||
let (to_pty_writer, pty_writer_receiver): ChannelWithContext<PtyWriteInstruction> =
|
||||
channels::unbounded();
|
||||
let to_pty_writer = SenderWithContext::new(to_pty_writer);
|
||||
let mut tab = create_new_tab_with_mock_pty_writer(size, ModeInfo::default(), to_pty_writer);
|
||||
|
||||
// TODO: note that this thread does not die when the test dies
|
||||
// it only dies once all the test process exits... not a biggy if we have only a handful of
|
||||
// these, but otherwise we might want to think of a better way to handle this
|
||||
let _pty_writer_thread = std::thread::Builder::new()
|
||||
.name("pty_writer".to_string())
|
||||
.spawn({
|
||||
// TODO: kill this thread
|
||||
let messages_to_pty_writer = messages_to_pty_writer.clone();
|
||||
move || loop {
|
||||
let (event, _err_ctx) = pty_writer_receiver
|
||||
.recv()
|
||||
.expect("failed to receive event on channel");
|
||||
if let PtyWriteInstruction::Write(msg, _) = event {
|
||||
messages_to_pty_writer
|
||||
.lock()
|
||||
.unwrap()
|
||||
.push(String::from_utf8_lossy(&msg).to_string());
|
||||
}
|
||||
}
|
||||
});
|
||||
let sgr_mouse_mode_any_button = String::from("\u{1b}[?1002;1005h"); // button event tracking (1002) with utf8 encoding (1005)
|
||||
tab.handle_pty_bytes(1, sgr_mouse_mode_any_button.as_bytes().to_vec());
|
||||
tab.handle_left_click(&Position::new(5, 71), client_id);
|
||||
tab.handle_mouse_hold_left(&Position::new(9, 72), client_id);
|
||||
tab.handle_left_mouse_release(&Position::new(7, 75), client_id);
|
||||
tab.handle_right_click(&Position::new(5, 71), client_id);
|
||||
tab.handle_mouse_hold_right(&Position::new(9, 72), client_id);
|
||||
tab.handle_right_mouse_release(&Position::new(7, 75), client_id);
|
||||
tab.handle_middle_click(&Position::new(5, 71), client_id);
|
||||
tab.handle_mouse_hold_middle(&Position::new(9, 72), client_id);
|
||||
tab.handle_middle_mouse_release(&Position::new(7, 75), client_id);
|
||||
tab.scroll_terminal_up(&Position::new(5, 71), 1, client_id);
|
||||
tab.scroll_terminal_down(&Position::new(5, 71), 1, client_id);
|
||||
std::thread::sleep(std::time::Duration::from_millis(100)); // give time for messages to arrive
|
||||
assert_eq!(
|
||||
*messages_to_pty_writer.lock().unwrap(),
|
||||
vec![
|
||||
"\u{1b}[M g%".to_string(), // utf8 left click
|
||||
"\u{1b}[M@h)".to_string(), // utf8 left click (hold)
|
||||
"\u{1b}[M#k'".to_string(), // utf8 left button release
|
||||
"\u{1b}[M\"g%".to_string(), // utf8 right click
|
||||
"\u{1b}[MBh)".to_string(), // utf8 right click (hold)
|
||||
"\u{1b}[M#k'".to_string(), // utf8 right button release
|
||||
"\u{1b}[M!g%".to_string(), // utf8 middle click
|
||||
"\u{1b}[MAh)".to_string(), // utf8 middle click (hold)
|
||||
"\u{1b}[M#k'".to_string(), // utf8 middle click release
|
||||
"\u{1b}[M`g%".to_string(), // utf8 scroll up
|
||||
"\u{1b}[Mag%".to_string(), // utf8 scroll down
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pane_in_utf8_normal_event_tracking_mouse_mode() {
|
||||
let size = Size {
|
||||
cols: 121,
|
||||
rows: 20,
|
||||
};
|
||||
let client_id = 1;
|
||||
|
||||
let messages_to_pty_writer = Arc::new(Mutex::new(vec![]));
|
||||
let (to_pty_writer, pty_writer_receiver): ChannelWithContext<PtyWriteInstruction> =
|
||||
channels::unbounded();
|
||||
let to_pty_writer = SenderWithContext::new(to_pty_writer);
|
||||
let mut tab = create_new_tab_with_mock_pty_writer(size, ModeInfo::default(), to_pty_writer);
|
||||
|
||||
// TODO: note that this thread does not die when the test dies
|
||||
// it only dies once all the test process exits... not a biggy if we have only a handful of
|
||||
// these, but otherwise we might want to think of a better way to handle this
|
||||
let _pty_writer_thread = std::thread::Builder::new()
|
||||
.name("pty_writer".to_string())
|
||||
.spawn({
|
||||
// TODO: kill this thread
|
||||
let messages_to_pty_writer = messages_to_pty_writer.clone();
|
||||
move || loop {
|
||||
let (event, _err_ctx) = pty_writer_receiver
|
||||
.recv()
|
||||
.expect("failed to receive event on channel");
|
||||
if let PtyWriteInstruction::Write(msg, _) = event {
|
||||
messages_to_pty_writer
|
||||
.lock()
|
||||
.unwrap()
|
||||
.push(String::from_utf8_lossy(&msg).to_string());
|
||||
}
|
||||
}
|
||||
});
|
||||
let sgr_mouse_mode_any_button = String::from("\u{1b}[?1000;1005h"); // normal event tracking (1000) with sgr encoding (1006)
|
||||
tab.handle_pty_bytes(1, sgr_mouse_mode_any_button.as_bytes().to_vec());
|
||||
tab.handle_left_click(&Position::new(5, 71), client_id);
|
||||
tab.handle_mouse_hold_left(&Position::new(9, 72), client_id);
|
||||
tab.handle_left_mouse_release(&Position::new(7, 75), client_id);
|
||||
tab.handle_right_click(&Position::new(5, 71), client_id);
|
||||
tab.handle_mouse_hold_right(&Position::new(9, 72), client_id);
|
||||
tab.handle_right_mouse_release(&Position::new(7, 75), client_id);
|
||||
tab.handle_middle_click(&Position::new(5, 71), client_id);
|
||||
tab.handle_mouse_hold_middle(&Position::new(9, 72), client_id);
|
||||
tab.handle_middle_mouse_release(&Position::new(7, 75), client_id);
|
||||
tab.scroll_terminal_up(&Position::new(5, 71), 1, client_id);
|
||||
tab.scroll_terminal_down(&Position::new(5, 71), 1, client_id);
|
||||
std::thread::sleep(std::time::Duration::from_millis(100)); // give time for messages to arrive
|
||||
assert_eq!(
|
||||
*messages_to_pty_writer.lock().unwrap(),
|
||||
vec![
|
||||
"\u{1b}[M g%".to_string(), // utf8 left click
|
||||
// no hold event here, as hold events are not reported in normal mode
|
||||
"\u{1b}[M#k'".to_string(), // utf8 left button release
|
||||
"\u{1b}[M\"g%".to_string(), // utf8 right click
|
||||
// no hold event here, as hold events are not reported in normal mode
|
||||
"\u{1b}[M#k'".to_string(), // utf8 right button release
|
||||
"\u{1b}[M!g%".to_string(), // utf8 middle click
|
||||
// no hold event here, as hold events are not reported in normal mode
|
||||
"\u{1b}[M#k'".to_string(), // utf8 middle click release
|
||||
"\u{1b}[M`g%".to_string(), // utf8 scroll up
|
||||
"\u{1b}[Mag%".to_string(), // utf8 scroll down
|
||||
]
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -105,6 +105,14 @@ impl ThreadSenders {
|
|||
self.should_silently_fail = true;
|
||||
self
|
||||
}
|
||||
#[allow(unused)]
|
||||
pub fn replace_to_pty_writer(
|
||||
&mut self,
|
||||
new_pty_writer: SenderWithContext<PtyWriteInstruction>,
|
||||
) {
|
||||
// this is mostly used for the tests, see struct
|
||||
self.to_pty_writer.replace(new_pty_writer);
|
||||
}
|
||||
}
|
||||
|
||||
/// A container for a receiver, OS input and the senders to a given thread
|
||||
|
|
|
|||
|
|
@ -295,8 +295,13 @@ pub enum ScreenContext {
|
|||
ChangeMode,
|
||||
LeftClick,
|
||||
RightClick,
|
||||
MouseRelease,
|
||||
MouseHold,
|
||||
MiddleClick,
|
||||
LeftMouseRelease,
|
||||
RightMouseRelease,
|
||||
MiddleMouseRelease,
|
||||
MouseHoldLeft,
|
||||
MouseHoldRight,
|
||||
MouseHoldMiddle,
|
||||
Copy,
|
||||
ToggleTab,
|
||||
AddClient,
|
||||
|
|
|
|||
|
|
@ -125,8 +125,13 @@ pub enum Action {
|
|||
Detach,
|
||||
LeftClick(Position),
|
||||
RightClick(Position),
|
||||
MouseRelease(Position),
|
||||
MouseHold(Position),
|
||||
MiddleClick(Position),
|
||||
LeftMouseRelease(Position),
|
||||
RightMouseRelease(Position),
|
||||
MiddleMouseRelease(Position),
|
||||
MouseHoldLeft(Position),
|
||||
MouseHoldRight(Position),
|
||||
MouseHoldMiddle(Position),
|
||||
Copy,
|
||||
/// Confirm a prompt
|
||||
Confirm,
|
||||
|
|
|
|||
|
|
@ -13,6 +13,13 @@ impl Position {
|
|||
column: Column(column as usize),
|
||||
}
|
||||
}
|
||||
pub fn change_line(&mut self, line: isize) {
|
||||
self.line = Line(line);
|
||||
}
|
||||
|
||||
pub fn change_column(&mut self, column: usize) {
|
||||
self.column = Column(column);
|
||||
}
|
||||
|
||||
pub fn relative_to(&self, line: usize, column: usize) -> Self {
|
||||
Self {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue