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,
|
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
|
/// Handles the dispatching of [`Action`]s according to the current
|
||||||
/// [`InputMode`], and keep tracks of the current [`InputMode`].
|
/// [`InputMode`], and keep tracks of the current [`InputMode`].
|
||||||
struct InputHandler {
|
struct InputHandler {
|
||||||
|
|
@ -31,7 +44,7 @@ struct InputHandler {
|
||||||
send_client_instructions: SenderWithContext<ClientInstruction>,
|
send_client_instructions: SenderWithContext<ClientInstruction>,
|
||||||
should_exit: bool,
|
should_exit: bool,
|
||||||
receive_input_instructions: Receiver<(InputInstruction, ErrorContext)>,
|
receive_input_instructions: Receiver<(InputInstruction, ErrorContext)>,
|
||||||
holding_mouse: bool,
|
holding_mouse: Option<HeldMouseButton>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InputHandler {
|
impl InputHandler {
|
||||||
|
|
@ -54,7 +67,7 @@ impl InputHandler {
|
||||||
send_client_instructions,
|
send_client_instructions,
|
||||||
should_exit: false,
|
should_exit: false,
|
||||||
receive_input_instructions,
|
receive_input_instructions,
|
||||||
holding_mouse: false,
|
holding_mouse: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -161,30 +174,59 @@ impl InputHandler {
|
||||||
self.dispatch_action(Action::ScrollDownAt(point), None);
|
self.dispatch_action(Action::ScrollDownAt(point), None);
|
||||||
},
|
},
|
||||||
MouseButton::Left => {
|
MouseButton::Left => {
|
||||||
if self.holding_mouse {
|
if self.holding_mouse.is_some() {
|
||||||
self.dispatch_action(Action::MouseHold(point), None);
|
self.dispatch_action(Action::MouseHoldLeft(point), None);
|
||||||
} else {
|
} else {
|
||||||
self.dispatch_action(Action::LeftClick(point), None);
|
self.dispatch_action(Action::LeftClick(point), None);
|
||||||
}
|
}
|
||||||
self.holding_mouse = true;
|
self.holding_mouse = Some(HeldMouseButton::Left);
|
||||||
},
|
},
|
||||||
MouseButton::Right => {
|
MouseButton::Right => {
|
||||||
if self.holding_mouse {
|
if self.holding_mouse.is_some() {
|
||||||
self.dispatch_action(Action::MouseHold(point), None);
|
self.dispatch_action(Action::MouseHoldRight(point), None);
|
||||||
} else {
|
} else {
|
||||||
self.dispatch_action(Action::RightClick(point), None);
|
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) => {
|
MouseEvent::Release(point) => {
|
||||||
self.dispatch_action(Action::MouseRelease(point), None);
|
let button_released = self.holding_mouse.unwrap_or_default();
|
||||||
self.holding_mouse = false;
|
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) => {
|
MouseEvent::Hold(point) => {
|
||||||
self.dispatch_action(Action::MouseHold(point), None);
|
let button_held = self.holding_mouse.unwrap_or_default();
|
||||||
self.holding_mouse = true;
|
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)]
|
#[derive(Clone)]
|
||||||
pub struct Grid {
|
pub struct Grid {
|
||||||
pub(crate) lines_above: VecDeque<Row>,
|
pub(crate) lines_above: VecDeque<Row>,
|
||||||
|
|
@ -340,11 +362,38 @@ pub struct Grid {
|
||||||
pub link_handler: Rc<RefCell<LinkHandler>>,
|
pub link_handler: Rc<RefCell<LinkHandler>>,
|
||||||
pub ring_bell: bool,
|
pub ring_bell: bool,
|
||||||
scrollback_buffer_lines: usize,
|
scrollback_buffer_lines: usize,
|
||||||
pub mouse_mode: bool,
|
pub mouse_mode: MouseMode,
|
||||||
|
pub mouse_tracking: MouseTracking,
|
||||||
pub search_results: SearchResult,
|
pub search_results: SearchResult,
|
||||||
pub pending_clipboard_update: Option<String>,
|
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 {
|
impl Debug for Grid {
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||||
let mut buffer: Vec<Row> = self.viewport.clone();
|
let mut buffer: Vec<Row> = self.viewport.clone();
|
||||||
|
|
@ -444,7 +493,8 @@ impl Grid {
|
||||||
link_handler,
|
link_handler,
|
||||||
ring_bell: false,
|
ring_bell: false,
|
||||||
scrollback_buffer_lines: 0,
|
scrollback_buffer_lines: 0,
|
||||||
mouse_mode: false,
|
mouse_mode: MouseMode::default(),
|
||||||
|
mouse_tracking: MouseTracking::default(),
|
||||||
character_cell_size,
|
character_cell_size,
|
||||||
search_results: Default::default(),
|
search_results: Default::default(),
|
||||||
sixel_grid,
|
sixel_grid,
|
||||||
|
|
@ -1471,6 +1521,8 @@ impl Grid {
|
||||||
self.scrollback_buffer_lines = 0;
|
self.scrollback_buffer_lines = 0;
|
||||||
self.search_results = Default::default();
|
self.search_results = Default::default();
|
||||||
self.sixel_scrolling = false;
|
self.sixel_scrolling = false;
|
||||||
|
self.mouse_mode = MouseMode::NoEncoding;
|
||||||
|
self.mouse_tracking = MouseTracking::Off;
|
||||||
if let Some(images_to_reap) = self.sixel_grid.clear() {
|
if let Some(images_to_reap) = self.sixel_grid.clear() {
|
||||||
self.sixel_grid.reap_images(images_to_reap);
|
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 {
|
impl Perform for Grid {
|
||||||
|
|
@ -2027,12 +2282,14 @@ impl Perform for Grid {
|
||||||
_ => false,
|
_ => false,
|
||||||
};
|
};
|
||||||
if first_intermediate_is_questionmark {
|
if first_intermediate_is_questionmark {
|
||||||
match params_iter.next().map(|param| param[0]) {
|
for param in params_iter.map(|param| param[0]) {
|
||||||
Some(2004) => {
|
match param {
|
||||||
|
2004 => {
|
||||||
self.bracketed_paste_mode = false;
|
self.bracketed_paste_mode = false;
|
||||||
},
|
},
|
||||||
Some(1049) => {
|
1049 => {
|
||||||
if let Some(mut alternate_screen_state) = self.alternate_screen_state.take()
|
if let Some(mut alternate_screen_state) =
|
||||||
|
self.alternate_screen_state.take()
|
||||||
{
|
{
|
||||||
if let Some(image_ids_to_reap) = self.sixel_grid.clear() {
|
if let Some(image_ids_to_reap) = self.sixel_grid.clear() {
|
||||||
// reap images before dropping the alternate_screen_state contents
|
// reap images before dropping the alternate_screen_state contents
|
||||||
|
|
@ -2052,34 +2309,47 @@ impl Perform for Grid {
|
||||||
self.force_change_size(self.height, self.width); // the alternative_viewport might have been of a different size...
|
self.force_change_size(self.height, self.width); // the alternative_viewport might have been of a different size...
|
||||||
self.mark_for_rerender();
|
self.mark_for_rerender();
|
||||||
},
|
},
|
||||||
Some(25) => {
|
25 => {
|
||||||
self.hide_cursor();
|
self.hide_cursor();
|
||||||
self.mark_for_rerender();
|
self.mark_for_rerender();
|
||||||
},
|
},
|
||||||
Some(1) => {
|
1 => {
|
||||||
self.cursor_key_mode = false;
|
self.cursor_key_mode = false;
|
||||||
},
|
},
|
||||||
Some(3) => {
|
3 => {
|
||||||
// DECCOLM - only side effects
|
// DECCOLM - only side effects
|
||||||
self.scroll_region = None;
|
self.scroll_region = None;
|
||||||
self.clear_all(EMPTY_TERMINAL_CHARACTER);
|
self.clear_all(EMPTY_TERMINAL_CHARACTER);
|
||||||
self.cursor.x = 0;
|
self.cursor.x = 0;
|
||||||
self.cursor.y = 0;
|
self.cursor.y = 0;
|
||||||
},
|
},
|
||||||
Some(6) => {
|
6 => {
|
||||||
self.erasure_mode = false;
|
self.erasure_mode = false;
|
||||||
},
|
},
|
||||||
Some(7) => {
|
7 => {
|
||||||
self.disable_linewrap = true;
|
self.disable_linewrap = true;
|
||||||
},
|
},
|
||||||
Some(80) => {
|
80 => {
|
||||||
self.sixel_scrolling = false;
|
self.sixel_scrolling = false;
|
||||||
},
|
},
|
||||||
Some(1006) => {
|
1000 => {
|
||||||
self.mouse_mode = false;
|
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]) {
|
} else if let Some(4) = params_iter.next().map(|param| param[0]) {
|
||||||
self.insert_mode = false;
|
self.insert_mode = false;
|
||||||
}
|
}
|
||||||
|
|
@ -2090,15 +2360,16 @@ impl Perform for Grid {
|
||||||
_ => false,
|
_ => false,
|
||||||
};
|
};
|
||||||
if first_intermediate_is_questionmark {
|
if first_intermediate_is_questionmark {
|
||||||
match params_iter.next().map(|param| param[0]) {
|
for param in params_iter.map(|param| param[0]) {
|
||||||
Some(25) => {
|
match param {
|
||||||
|
25 => {
|
||||||
self.show_cursor();
|
self.show_cursor();
|
||||||
self.mark_for_rerender();
|
self.mark_for_rerender();
|
||||||
},
|
},
|
||||||
Some(2004) => {
|
2004 => {
|
||||||
self.bracketed_paste_mode = true;
|
self.bracketed_paste_mode = true;
|
||||||
},
|
},
|
||||||
Some(1049) => {
|
1049 => {
|
||||||
// enter alternate buffer
|
// enter alternate buffer
|
||||||
let current_lines_above = std::mem::replace(
|
let current_lines_above = std::mem::replace(
|
||||||
&mut self.lines_above,
|
&mut self.lines_above,
|
||||||
|
|
@ -2108,7 +2379,8 @@ impl Perform for Grid {
|
||||||
&mut self.viewport,
|
&mut self.viewport,
|
||||||
vec![Row::new(self.width).canonical()],
|
vec![Row::new(self.width).canonical()],
|
||||||
);
|
);
|
||||||
let current_cursor = std::mem::replace(&mut self.cursor, Cursor::new(0, 0));
|
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 sixel_image_store = self.sixel_grid.sixel_image_store.clone();
|
||||||
let alternate_sixelgrid = std::mem::replace(
|
let alternate_sixelgrid = std::mem::replace(
|
||||||
&mut self.sixel_grid,
|
&mut self.sixel_grid,
|
||||||
|
|
@ -2121,33 +2393,47 @@ impl Perform for Grid {
|
||||||
alternate_sixelgrid,
|
alternate_sixelgrid,
|
||||||
));
|
));
|
||||||
self.clear_viewport_before_rendering = true;
|
self.clear_viewport_before_rendering = true;
|
||||||
self.scrollback_buffer_lines = self.recalculate_scrollback_buffer_count();
|
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
|
self.output_buffer.update_all_lines(); // make sure the screen gets cleared in the next render
|
||||||
},
|
},
|
||||||
Some(1) => {
|
1 => {
|
||||||
self.cursor_key_mode = true;
|
self.cursor_key_mode = true;
|
||||||
},
|
},
|
||||||
Some(3) => {
|
3 => {
|
||||||
// DECCOLM - only side effects
|
// DECCOLM - only side effects
|
||||||
self.scroll_region = None;
|
self.scroll_region = None;
|
||||||
self.clear_all(EMPTY_TERMINAL_CHARACTER);
|
self.clear_all(EMPTY_TERMINAL_CHARACTER);
|
||||||
self.cursor.x = 0;
|
self.cursor.x = 0;
|
||||||
self.cursor.y = 0;
|
self.cursor.y = 0;
|
||||||
},
|
},
|
||||||
Some(6) => {
|
6 => {
|
||||||
self.erasure_mode = true;
|
self.erasure_mode = true;
|
||||||
},
|
},
|
||||||
Some(7) => {
|
7 => {
|
||||||
self.disable_linewrap = false;
|
self.disable_linewrap = false;
|
||||||
},
|
},
|
||||||
Some(80) => {
|
80 => {
|
||||||
self.sixel_scrolling = true;
|
self.sixel_scrolling = true;
|
||||||
},
|
},
|
||||||
Some(1006) => {
|
1000 => {
|
||||||
self.mouse_mode = true;
|
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]) {
|
} else if let Some(4) = params_iter.next().map(|param| param[0]) {
|
||||||
self.insert_mode = true;
|
self.insert_mode = true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -415,7 +415,4 @@ impl Pane for PluginPane {
|
||||||
))
|
))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
fn mouse_mode(&self) -> bool {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -566,10 +566,30 @@ impl Pane for TerminalPane {
|
||||||
self.borderless
|
self.borderless
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mouse_mode(&self) -> bool {
|
fn mouse_left_click(&self, position: &Position, is_held: bool) -> Option<String> {
|
||||||
self.grid.mouse_mode
|
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> {
|
fn get_line_number(&self) -> Option<usize> {
|
||||||
// + 1 because the absolute position in the scrollback is 0 indexed and this should be 1 indexed
|
// + 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)
|
Some(self.grid.absolute_position_in_scrollback() + 1)
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ use zellij_utils::errors::{ContextType, PtyWriteContext};
|
||||||
|
|
||||||
use crate::thread_bus::Bus;
|
use crate::thread_bus::Bus;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||||
pub(crate) enum PtyWriteInstruction {
|
pub(crate) enum PtyWriteInstruction {
|
||||||
Write(Vec<u8>, i32),
|
Write(Vec<u8>, i32),
|
||||||
Exit,
|
Exit,
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ fn route_action(
|
||||||
// this is a bit of a hack around the unfortunate architecture we use with plugins
|
// this is a bit of a hack around the unfortunate architecture we use with plugins
|
||||||
// this will change as soon as we refactor
|
// this will change as soon as we refactor
|
||||||
match action {
|
match action {
|
||||||
Action::MouseHold(_) => {},
|
Action::MouseHoldLeft(..) | Action::MouseHoldRight(..) => {},
|
||||||
_ => {
|
_ => {
|
||||||
session
|
session
|
||||||
.senders
|
.senders
|
||||||
|
|
@ -376,17 +376,46 @@ fn route_action(
|
||||||
.send_to_screen(ScreenInstruction::RightClick(point, client_id))
|
.send_to_screen(ScreenInstruction::RightClick(point, client_id))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
},
|
},
|
||||||
|
Action::MiddleClick(point) => {
|
||||||
Action::MouseRelease(point) => {
|
|
||||||
session
|
session
|
||||||
.senders
|
.senders
|
||||||
.send_to_screen(ScreenInstruction::MouseRelease(point, client_id))
|
.send_to_screen(ScreenInstruction::MiddleClick(point, client_id))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
},
|
},
|
||||||
Action::MouseHold(point) => {
|
Action::LeftMouseRelease(point) => {
|
||||||
session
|
session
|
||||||
.senders
|
.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();
|
.unwrap();
|
||||||
},
|
},
|
||||||
Action::Copy => {
|
Action::Copy => {
|
||||||
|
|
|
||||||
|
|
@ -123,8 +123,13 @@ pub enum ScreenInstruction {
|
||||||
ChangeMode(ModeInfo, ClientId),
|
ChangeMode(ModeInfo, ClientId),
|
||||||
LeftClick(Position, ClientId),
|
LeftClick(Position, ClientId),
|
||||||
RightClick(Position, ClientId),
|
RightClick(Position, ClientId),
|
||||||
MouseRelease(Position, ClientId),
|
MiddleClick(Position, ClientId),
|
||||||
MouseHold(Position, ClientId),
|
LeftMouseRelease(Position, ClientId),
|
||||||
|
RightMouseRelease(Position, ClientId),
|
||||||
|
MiddleMouseRelease(Position, ClientId),
|
||||||
|
MouseHoldLeft(Position, ClientId),
|
||||||
|
MouseHoldRight(Position, ClientId),
|
||||||
|
MouseHoldMiddle(Position, ClientId),
|
||||||
Copy(ClientId),
|
Copy(ClientId),
|
||||||
AddClient(ClientId),
|
AddClient(ClientId),
|
||||||
RemoveClient(ClientId),
|
RemoveClient(ClientId),
|
||||||
|
|
@ -222,8 +227,13 @@ impl From<&ScreenInstruction> for ScreenContext {
|
||||||
ScreenInstruction::ScrollDownAt(..) => ScreenContext::ScrollDownAt,
|
ScreenInstruction::ScrollDownAt(..) => ScreenContext::ScrollDownAt,
|
||||||
ScreenInstruction::LeftClick(..) => ScreenContext::LeftClick,
|
ScreenInstruction::LeftClick(..) => ScreenContext::LeftClick,
|
||||||
ScreenInstruction::RightClick(..) => ScreenContext::RightClick,
|
ScreenInstruction::RightClick(..) => ScreenContext::RightClick,
|
||||||
ScreenInstruction::MouseRelease(..) => ScreenContext::MouseRelease,
|
ScreenInstruction::MiddleClick(..) => ScreenContext::MiddleClick,
|
||||||
ScreenInstruction::MouseHold(..) => ScreenContext::MouseHold,
|
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::Copy(..) => ScreenContext::Copy,
|
||||||
ScreenInstruction::ToggleTab(..) => ScreenContext::ToggleTab,
|
ScreenInstruction::ToggleTab(..) => ScreenContext::ToggleTab,
|
||||||
ScreenInstruction::AddClient(..) => ScreenContext::AddClient,
|
ScreenInstruction::AddClient(..) => ScreenContext::AddClient,
|
||||||
|
|
@ -1303,14 +1313,42 @@ pub(crate) fn screen_thread_main(
|
||||||
screen.update_tabs();
|
screen.update_tabs();
|
||||||
screen.render();
|
screen.render();
|
||||||
},
|
},
|
||||||
ScreenInstruction::MouseRelease(point, client_id) => {
|
ScreenInstruction::MiddleClick(point, client_id) => {
|
||||||
active_tab!(screen, client_id, |tab: &mut Tab| tab
|
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();
|
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| {
|
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();
|
screen.render();
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -296,8 +296,32 @@ pub trait Pane {
|
||||||
fn load_pane_name(&mut self);
|
fn load_pane_name(&mut self);
|
||||||
fn set_borderless(&mut self, borderless: bool);
|
fn set_borderless(&mut self, borderless: bool);
|
||||||
fn borderless(&self) -> 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 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> {
|
fn get_line_number(&self) -> Option<usize> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
@ -1668,13 +1692,8 @@ impl Tab {
|
||||||
}
|
}
|
||||||
pub fn scroll_terminal_up(&mut self, point: &Position, lines: usize, client_id: ClientId) {
|
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 let Some(pane) = self.get_pane_at(point, false) {
|
||||||
if pane.mouse_mode() {
|
|
||||||
let relative_position = pane.relative_position(point);
|
let relative_position = pane.relative_position(point);
|
||||||
let mouse_event = format!(
|
if let Some(mouse_event) = pane.mouse_scroll_up(&relative_position) {
|
||||||
"\u{1b}[<64;{:?};{:?}M",
|
|
||||||
relative_position.column.0 + 1,
|
|
||||||
relative_position.line.0 + 1
|
|
||||||
);
|
|
||||||
self.write_to_terminal_at(mouse_event.into_bytes(), point);
|
self.write_to_terminal_at(mouse_event.into_bytes(), point);
|
||||||
} else {
|
} else {
|
||||||
pane.scroll_up(lines, client_id);
|
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) {
|
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 let Some(pane) = self.get_pane_at(point, false) {
|
||||||
if pane.mouse_mode() {
|
|
||||||
let relative_position = pane.relative_position(point);
|
let relative_position = pane.relative_position(point);
|
||||||
let mouse_event = format!(
|
if let Some(mouse_event) = pane.mouse_scroll_down(&relative_position) {
|
||||||
"\u{1b}[<65;{:?};{:?}M",
|
|
||||||
relative_position.column.0 + 1,
|
|
||||||
relative_position.line.0 + 1
|
|
||||||
);
|
|
||||||
self.write_to_terminal_at(mouse_event.into_bytes(), point);
|
self.write_to_terminal_at(mouse_event.into_bytes(), point);
|
||||||
} else {
|
} else {
|
||||||
pane.scroll_down(lines, client_id);
|
pane.scroll_down(lines, client_id);
|
||||||
|
|
@ -1755,18 +1769,11 @@ impl Tab {
|
||||||
|
|
||||||
if let Some(pane) = self.get_pane_at(position, false) {
|
if let Some(pane) = self.get_pane_at(position, false) {
|
||||||
let relative_position = pane.relative_position(position);
|
let relative_position = pane.relative_position(position);
|
||||||
|
if let Some(mouse_event) = pane.mouse_left_click(&relative_position, false) {
|
||||||
if pane.mouse_mode() {
|
|
||||||
if !pane.position_is_on_frame(position) {
|
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);
|
self.write_to_active_terminal(mouse_event.into_bytes(), client_id);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// TODO: rename this method, it is used to forward click events to plugin panes
|
|
||||||
pane.start_selection(&relative_position, client_id);
|
pane.start_selection(&relative_position, client_id);
|
||||||
if let PaneId::Terminal(_) = pane.pid() {
|
if let PaneId::Terminal(_) = pane.pid() {
|
||||||
self.selecting_with_mouse = true;
|
self.selecting_with_mouse = true;
|
||||||
|
|
@ -1779,13 +1786,8 @@ impl Tab {
|
||||||
|
|
||||||
if let Some(pane) = self.get_pane_at(position, false) {
|
if let Some(pane) = self.get_pane_at(position, false) {
|
||||||
let relative_position = pane.relative_position(position);
|
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) {
|
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);
|
self.write_to_active_terminal(mouse_event.into_bytes(), client_id);
|
||||||
}
|
}
|
||||||
} else {
|
} 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) {
|
fn focus_pane_at(&mut self, point: &Position, client_id: ClientId) {
|
||||||
if self.floating_panes.panes_are_visible() {
|
if self.floating_panes.panes_are_visible() {
|
||||||
if let Some(clicked_pane) = self.floating_panes.get_pane_id_at(point, true) {
|
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;
|
self.last_mouse_hold_position = None;
|
||||||
|
|
||||||
if self.floating_panes.panes_are_visible()
|
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);
|
let active_pane = self.get_active_pane_or_floating_pane_mut(client_id);
|
||||||
|
|
||||||
if let Some(active_pane) = active_pane {
|
if let Some(active_pane) = active_pane {
|
||||||
let relative_position = active_pane.relative_position(position);
|
let mut relative_position = active_pane.relative_position(position);
|
||||||
if active_pane.mouse_mode() {
|
relative_position.change_column(
|
||||||
// ensure that coordinates are valid
|
(relative_position.column())
|
||||||
let col = (relative_position.column() + 1)
|
.max(0)
|
||||||
.max(1)
|
.min(active_pane.get_content_columns()),
|
||||||
.min(active_pane.get_content_columns());
|
);
|
||||||
|
|
||||||
let line = (relative_position.line() + 1)
|
relative_position.change_line(
|
||||||
.max(1)
|
(relative_position.line())
|
||||||
.min(active_pane.get_content_rows() as isize);
|
.max(0)
|
||||||
let mouse_event = format!("\u{1b}[<0;{:?};{:?}m", col, line);
|
.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);
|
self.write_to_active_terminal(mouse_event.into_bytes(), client_id);
|
||||||
} else {
|
} 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 let PaneId::Terminal(_) = active_pane.pid() {
|
||||||
if selecting && copy_on_release {
|
if selecting && copy_on_release {
|
||||||
active_pane.end_selection(&relative_position, client_id);
|
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,
|
&mut self,
|
||||||
position_on_screen: &Position,
|
position_on_screen: &Position,
|
||||||
client_id: ClientId,
|
client_id: ClientId,
|
||||||
|
|
@ -1889,20 +1950,24 @@ impl Tab {
|
||||||
let active_pane = self.get_active_pane_or_floating_pane_mut(client_id);
|
let active_pane = self.get_active_pane_or_floating_pane_mut(client_id);
|
||||||
|
|
||||||
if let Some(active_pane) = active_pane {
|
if let Some(active_pane) = active_pane {
|
||||||
let relative_position = active_pane.relative_position(position_on_screen);
|
let mut relative_position = active_pane.relative_position(position_on_screen);
|
||||||
if active_pane.mouse_mode() && !is_repeated {
|
if !is_repeated {
|
||||||
// ensure that coordinates are valid
|
// ensure that coordinates are valid
|
||||||
let col = (relative_position.column() + 1)
|
relative_position.change_column(
|
||||||
.max(1)
|
(relative_position.column())
|
||||||
.min(active_pane.get_content_columns());
|
.max(0)
|
||||||
|
.min(active_pane.get_content_columns()),
|
||||||
|
);
|
||||||
|
|
||||||
let line = (relative_position.line() + 1)
|
relative_position.change_line(
|
||||||
.max(1)
|
(relative_position.line())
|
||||||
.min(active_pane.get_content_rows() as isize);
|
.max(0)
|
||||||
|
.min(active_pane.get_content_rows() as isize),
|
||||||
let mouse_event = format!("\u{1b}[<32;{:?};{:?}M", col, line);
|
);
|
||||||
|
if let Some(mouse_event) = active_pane.mouse_left_click(&relative_position, true) {
|
||||||
self.write_to_active_terminal(mouse_event.into_bytes(), client_id);
|
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
|
return true; // we need to re-render in this case so the selection disappears
|
||||||
|
}
|
||||||
} else if selecting {
|
} else if selecting {
|
||||||
active_pane.update_selection(&relative_position, client_id);
|
active_pane.update_selection(&relative_position, client_id);
|
||||||
return true; // we need to re-render in this case so the selection is updated
|
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
|
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) {
|
pub fn copy_selection(&self, client_id: ClientId) {
|
||||||
let selected_text = self
|
let selected_text = self
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,9 @@ use zellij_utils::ipc::IpcReceiverWithContext;
|
||||||
use zellij_utils::pane_size::{Size, SizeInPixels};
|
use zellij_utils::pane_size::{Size, SizeInPixels};
|
||||||
use zellij_utils::position::Position;
|
use zellij_utils::position::Position;
|
||||||
|
|
||||||
|
use crate::pty_writer::PtyWriteInstruction;
|
||||||
|
use zellij_utils::channels::{self, ChannelWithContext, SenderWithContext};
|
||||||
|
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::os::unix::io::RawFd;
|
use std::os::unix::io::RawFd;
|
||||||
|
|
@ -150,6 +153,63 @@ fn create_new_tab(size: Size, default_mode: ModeInfo) -> Tab {
|
||||||
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(
|
fn create_new_tab_with_sixel_support(
|
||||||
size: Size,
|
size: Size,
|
||||||
sixel_image_store: Rc<RefCell<SixelImageStore>>,
|
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(5, Vec::from("\u{1b}#8".as_bytes()));
|
||||||
tab.handle_pty_bytes(6, 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_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);
|
tab.render(&mut output, None);
|
||||||
let (snapshot, cursor_coordinates) = take_snapshot_and_cursor_position(
|
let (snapshot, cursor_coordinates) = take_snapshot_and_cursor_position(
|
||||||
output.serialize().get(&client_id).unwrap(),
|
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(5, Vec::from("\u{1b}#8".as_bytes()));
|
||||||
tab.handle_pty_bytes(6, 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_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);
|
tab.render(&mut output, None);
|
||||||
let (snapshot, cursor_coordinates) = take_snapshot_and_cursor_position(
|
let (snapshot, cursor_coordinates) = take_snapshot_and_cursor_position(
|
||||||
output.serialize().get(&client_id).unwrap(),
|
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(5, Vec::from("\u{1b}#8".as_bytes()));
|
||||||
tab.handle_pty_bytes(6, 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_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);
|
tab.render(&mut output, None);
|
||||||
let (snapshot, cursor_coordinates) = take_snapshot_and_cursor_position(
|
let (snapshot, cursor_coordinates) = take_snapshot_and_cursor_position(
|
||||||
output.serialize().get(&client_id).unwrap(),
|
output.serialize().get(&client_id).unwrap(),
|
||||||
|
|
@ -988,7 +1048,7 @@ fn mark_text_inside_floating_pane() {
|
||||||
tab.selecting_with_mouse,
|
tab.selecting_with_mouse,
|
||||||
"started selecting with mouse on click"
|
"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!(
|
assert!(
|
||||||
!tab.selecting_with_mouse,
|
!tab.selecting_with_mouse,
|
||||||
"stopped selecting with mouse on release"
|
"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");
|
let fixture = read_fixture("sixel-image-500px.six");
|
||||||
tab.handle_pty_bytes(2, fixture);
|
tab.handle_pty_bytes(2, fixture);
|
||||||
tab.handle_left_click(&Position::new(5, 71), client_id);
|
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);
|
tab.render(&mut output, None);
|
||||||
let snapshot = take_snapshot_with_sixel(
|
let snapshot = take_snapshot_with_sixel(
|
||||||
|
|
@ -1392,7 +1452,7 @@ fn floating_pane_above_sixel_image() {
|
||||||
let fixture = read_fixture("sixel-image-500px.six");
|
let fixture = read_fixture("sixel-image-500px.six");
|
||||||
tab.handle_pty_bytes(1, fixture);
|
tab.handle_pty_bytes(1, fixture);
|
||||||
tab.handle_left_click(&Position::new(5, 71), client_id);
|
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);
|
tab.render(&mut output, None);
|
||||||
let snapshot = take_snapshot_with_sixel(
|
let snapshot = take_snapshot_with_sixel(
|
||||||
|
|
@ -1720,3 +1780,267 @@ fn enter_search_floating_pane() {
|
||||||
);
|
);
|
||||||
assert_snapshot!("search_floating_tab_highlight_fring", snapshot);
|
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.should_silently_fail = true;
|
||||||
self
|
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
|
/// A container for a receiver, OS input and the senders to a given thread
|
||||||
|
|
|
||||||
|
|
@ -295,8 +295,13 @@ pub enum ScreenContext {
|
||||||
ChangeMode,
|
ChangeMode,
|
||||||
LeftClick,
|
LeftClick,
|
||||||
RightClick,
|
RightClick,
|
||||||
MouseRelease,
|
MiddleClick,
|
||||||
MouseHold,
|
LeftMouseRelease,
|
||||||
|
RightMouseRelease,
|
||||||
|
MiddleMouseRelease,
|
||||||
|
MouseHoldLeft,
|
||||||
|
MouseHoldRight,
|
||||||
|
MouseHoldMiddle,
|
||||||
Copy,
|
Copy,
|
||||||
ToggleTab,
|
ToggleTab,
|
||||||
AddClient,
|
AddClient,
|
||||||
|
|
|
||||||
|
|
@ -125,8 +125,13 @@ pub enum Action {
|
||||||
Detach,
|
Detach,
|
||||||
LeftClick(Position),
|
LeftClick(Position),
|
||||||
RightClick(Position),
|
RightClick(Position),
|
||||||
MouseRelease(Position),
|
MiddleClick(Position),
|
||||||
MouseHold(Position),
|
LeftMouseRelease(Position),
|
||||||
|
RightMouseRelease(Position),
|
||||||
|
MiddleMouseRelease(Position),
|
||||||
|
MouseHoldLeft(Position),
|
||||||
|
MouseHoldRight(Position),
|
||||||
|
MouseHoldMiddle(Position),
|
||||||
Copy,
|
Copy,
|
||||||
/// Confirm a prompt
|
/// Confirm a prompt
|
||||||
Confirm,
|
Confirm,
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,13 @@ impl Position {
|
||||||
column: Column(column as usize),
|
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 {
|
pub fn relative_to(&self, line: usize, column: usize) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue