feat(plugin): Add mouse events for plugins (#629)
* feat(plugin): Add mouse events for plugins * Add double click support in strider * Add support for mouse clicks in tab-bar and fix bug in strider with selecting past the list of files and random double click action * continue working on mouse support for tab bar * finish tab change * fix fmt and fix bug in strider double-click * fix clippy * cleanup dbgs and logs * fix clippy * noop change to rerun e2e tests * Rebase and fix mouse click behavior in tab-bar and strider after rebase * fix fmt * remove dbgs and and comment in tab-line/main.rs * cargo fmt * Code review suggestions * rebase fix * fix clippy * fix mouse selection for tabs in tab-bar
This commit is contained in:
parent
a6453f111e
commit
0710594588
8 changed files with 228 additions and 67 deletions
|
|
@ -1,23 +1,27 @@
|
||||||
mod state;
|
mod state;
|
||||||
|
|
||||||
use colored::*;
|
use colored::*;
|
||||||
use state::{FsEntry, State};
|
use state::{refresh_directory, FsEntry, State};
|
||||||
use std::{cmp::min, fs::read_dir, path::Path};
|
use std::{cmp::min, time::Instant};
|
||||||
use zellij_tile::prelude::*;
|
use zellij_tile::prelude::*;
|
||||||
|
|
||||||
const ROOT: &str = "/host";
|
|
||||||
|
|
||||||
register_plugin!(State);
|
register_plugin!(State);
|
||||||
|
|
||||||
impl ZellijPlugin for State {
|
impl ZellijPlugin for State {
|
||||||
fn load(&mut self) {
|
fn load(&mut self) {
|
||||||
refresh_directory(self);
|
refresh_directory(self);
|
||||||
subscribe(&[EventType::KeyPress]);
|
subscribe(&[EventType::KeyPress, EventType::Mouse]);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(&mut self, event: Event) {
|
fn update(&mut self, event: Event) {
|
||||||
if let Event::KeyPress(key) = event {
|
let prev_event = if self.ev_history.len() == 2 {
|
||||||
match key {
|
self.ev_history.pop_front()
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
self.ev_history.push_back((event.clone(), Instant::now()));
|
||||||
|
match event {
|
||||||
|
Event::KeyPress(key) => match key {
|
||||||
Key::Up | Key::Char('k') => {
|
Key::Up | Key::Char('k') => {
|
||||||
*self.selected_mut() = self.selected().saturating_sub(1);
|
*self.selected_mut() = self.selected().saturating_sub(1);
|
||||||
}
|
}
|
||||||
|
|
@ -26,13 +30,8 @@ impl ZellijPlugin for State {
|
||||||
*self.selected_mut() = min(self.files.len() - 1, next);
|
*self.selected_mut() = min(self.files.len() - 1, next);
|
||||||
}
|
}
|
||||||
Key::Right | Key::Char('\n') | Key::Char('l') if !self.files.is_empty() => {
|
Key::Right | Key::Char('\n') | Key::Char('l') if !self.files.is_empty() => {
|
||||||
match self.files[self.selected()].clone() {
|
self.traverse_dir_or_open_file();
|
||||||
FsEntry::Dir(p, _) => {
|
self.ev_history.clear();
|
||||||
self.path = p;
|
|
||||||
refresh_directory(self);
|
|
||||||
}
|
|
||||||
FsEntry::File(p, _) => open_file(p.strip_prefix(ROOT).unwrap()),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Key::Left | Key::Char('h') => {
|
Key::Left | Key::Char('h') => {
|
||||||
if self.path.components().count() > 2 {
|
if self.path.components().count() > 2 {
|
||||||
|
|
@ -44,25 +43,59 @@ impl ZellijPlugin for State {
|
||||||
refresh_directory(self);
|
refresh_directory(self);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Key::Char('.') => {
|
Key::Char('.') => {
|
||||||
self.toggle_hidden_files();
|
self.toggle_hidden_files();
|
||||||
refresh_directory(self);
|
refresh_directory(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
_ => (),
|
_ => (),
|
||||||
};
|
},
|
||||||
|
Event::Mouse(mouse_event) => match mouse_event {
|
||||||
|
Mouse::ScrollDown(_) => {
|
||||||
|
let next = self.selected().saturating_add(1);
|
||||||
|
*self.selected_mut() = min(self.files.len().saturating_sub(1), next);
|
||||||
|
}
|
||||||
|
Mouse::ScrollUp(_) => {
|
||||||
|
*self.selected_mut() = self.selected().saturating_sub(1);
|
||||||
|
}
|
||||||
|
Mouse::MouseRelease(Some((line, _))) => {
|
||||||
|
if line < 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let mut should_select = true;
|
||||||
|
if let Some((Event::Mouse(Mouse::MouseRelease(Some((prev_line, _)))), t)) =
|
||||||
|
prev_event
|
||||||
|
{
|
||||||
|
if prev_line == line
|
||||||
|
&& Instant::now().saturating_duration_since(t).as_millis() < 400
|
||||||
|
{
|
||||||
|
self.traverse_dir_or_open_file();
|
||||||
|
self.ev_history.clear();
|
||||||
|
should_select = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if should_select && self.scroll() + (line as usize) < self.files.len() {
|
||||||
|
*self.selected_mut() = self.scroll() + (line as usize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
dbg!("Unknown event {:?}", event);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render(&mut self, rows: usize, cols: usize) {
|
fn render(&mut self, rows: usize, cols: usize) {
|
||||||
for i in 0..rows {
|
for i in 0..rows {
|
||||||
|
// If the key was pressed, set selected so that we can see the cursor
|
||||||
if self.selected() < self.scroll() {
|
if self.selected() < self.scroll() {
|
||||||
*self.scroll_mut() = self.selected();
|
*self.scroll_mut() = self.selected();
|
||||||
}
|
}
|
||||||
if self.selected() - self.scroll() + 2 > rows {
|
if self.selected() - self.scroll() + 2 > rows {
|
||||||
*self.scroll_mut() = self.selected() + 2 - rows;
|
*self.scroll_mut() = self.selected() + 2 - rows;
|
||||||
}
|
}
|
||||||
|
|
||||||
let i = self.scroll() + i;
|
let i = self.scroll() + i;
|
||||||
if let Some(entry) = self.files.get(i) {
|
if let Some(entry) = self.files.get(i) {
|
||||||
let mut path = entry.as_line(cols).normal();
|
let mut path = entry.as_line(cols).normal();
|
||||||
|
|
@ -82,24 +115,3 @@ impl ZellijPlugin for State {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn refresh_directory(state: &mut State) {
|
|
||||||
state.files = read_dir(Path::new(ROOT).join(&state.path))
|
|
||||||
.unwrap()
|
|
||||||
.filter_map(|res| {
|
|
||||||
res.and_then(|d| {
|
|
||||||
if d.metadata()?.is_dir() {
|
|
||||||
let children = read_dir(d.path())?.count();
|
|
||||||
Ok(FsEntry::Dir(d.path(), children))
|
|
||||||
} else {
|
|
||||||
let size = d.metadata()?.len();
|
|
||||||
Ok(FsEntry::File(d.path(), size))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.ok()
|
|
||||||
.filter(|d| !d.is_hidden_file() || !state.hide_hidden_files)
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
state.files.sort_unstable();
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,20 @@
|
||||||
use pretty_bytes::converter as pb;
|
use pretty_bytes::converter as pb;
|
||||||
use std::{collections::HashMap, path::PathBuf};
|
use std::{
|
||||||
|
collections::{HashMap, VecDeque},
|
||||||
|
fs::read_dir,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
time::Instant,
|
||||||
|
};
|
||||||
|
use zellij_tile::prelude::*;
|
||||||
|
|
||||||
|
const ROOT: &str = "/host";
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct State {
|
pub struct State {
|
||||||
pub path: PathBuf,
|
pub path: PathBuf,
|
||||||
pub files: Vec<FsEntry>,
|
pub files: Vec<FsEntry>,
|
||||||
pub cursor_hist: HashMap<PathBuf, (usize, usize)>,
|
pub cursor_hist: HashMap<PathBuf, (usize, usize)>,
|
||||||
pub hide_hidden_files: bool,
|
pub hide_hidden_files: bool,
|
||||||
|
pub ev_history: VecDeque<(Event, Instant)>, // stores last event, can be expanded in future
|
||||||
}
|
}
|
||||||
|
|
||||||
impl State {
|
impl State {
|
||||||
|
|
@ -25,6 +33,15 @@ impl State {
|
||||||
pub fn toggle_hidden_files(&mut self) {
|
pub fn toggle_hidden_files(&mut self) {
|
||||||
self.hide_hidden_files = !self.hide_hidden_files;
|
self.hide_hidden_files = !self.hide_hidden_files;
|
||||||
}
|
}
|
||||||
|
pub fn traverse_dir_or_open_file(&mut self) {
|
||||||
|
match self.files[self.selected()].clone() {
|
||||||
|
FsEntry::Dir(p, _) => {
|
||||||
|
self.path = p;
|
||||||
|
refresh_directory(self);
|
||||||
|
}
|
||||||
|
FsEntry::File(p, _) => open_file(p.strip_prefix(ROOT).unwrap()),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone)]
|
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone)]
|
||||||
|
|
@ -61,3 +78,24 @@ impl FsEntry {
|
||||||
self.name().starts_with('.')
|
self.name().starts_with('.')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn refresh_directory(state: &mut State) {
|
||||||
|
state.files = read_dir(Path::new(ROOT).join(&state.path))
|
||||||
|
.unwrap()
|
||||||
|
.filter_map(|res| {
|
||||||
|
res.and_then(|d| {
|
||||||
|
if d.metadata()?.is_dir() {
|
||||||
|
let children = read_dir(d.path())?.count();
|
||||||
|
Ok(FsEntry::Dir(d.path(), children))
|
||||||
|
} else {
|
||||||
|
let size = d.metadata()?.len();
|
||||||
|
Ok(FsEntry::File(d.path(), size))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.ok()
|
||||||
|
.filter(|d| !d.is_hidden_file() || !state.hide_hidden_files)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
state.files.sort_unstable();
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
mod line;
|
mod line;
|
||||||
mod tab;
|
mod tab;
|
||||||
|
|
||||||
|
use std::cmp::{max, min};
|
||||||
|
use std::convert::TryInto;
|
||||||
|
|
||||||
use zellij_tile::prelude::*;
|
use zellij_tile::prelude::*;
|
||||||
|
|
||||||
use crate::line::tab_line;
|
use crate::line::tab_line;
|
||||||
|
|
@ -15,7 +18,10 @@ pub struct LinePart {
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct State {
|
struct State {
|
||||||
tabs: Vec<TabInfo>,
|
tabs: Vec<TabInfo>,
|
||||||
|
active_tab_idx: usize,
|
||||||
mode_info: ModeInfo,
|
mode_info: ModeInfo,
|
||||||
|
mouse_click_pos: usize,
|
||||||
|
should_render: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
static ARROW_SEPARATOR: &str = "";
|
static ARROW_SEPARATOR: &str = "";
|
||||||
|
|
@ -25,13 +31,34 @@ register_plugin!(State);
|
||||||
impl ZellijPlugin for State {
|
impl ZellijPlugin for State {
|
||||||
fn load(&mut self) {
|
fn load(&mut self) {
|
||||||
set_selectable(false);
|
set_selectable(false);
|
||||||
subscribe(&[EventType::TabUpdate, EventType::ModeUpdate]);
|
subscribe(&[
|
||||||
|
EventType::TabUpdate,
|
||||||
|
EventType::ModeUpdate,
|
||||||
|
EventType::Mouse,
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(&mut self, event: Event) {
|
fn update(&mut self, event: Event) {
|
||||||
match event {
|
match event {
|
||||||
Event::ModeUpdate(mode_info) => self.mode_info = mode_info,
|
Event::ModeUpdate(mode_info) => self.mode_info = mode_info,
|
||||||
Event::TabUpdate(tabs) => self.tabs = tabs,
|
Event::TabUpdate(tabs) => {
|
||||||
|
// tabs are indexed starting from 1 so we need to add 1
|
||||||
|
self.active_tab_idx = (&tabs).iter().position(|t| t.active).unwrap() + 1;
|
||||||
|
self.tabs = tabs;
|
||||||
|
}
|
||||||
|
Event::Mouse(me) => match me {
|
||||||
|
Mouse::LeftClick(_, col) => {
|
||||||
|
self.mouse_click_pos = col;
|
||||||
|
self.should_render = true;
|
||||||
|
}
|
||||||
|
Mouse::ScrollUp(_) => {
|
||||||
|
switch_tab_to(min(self.active_tab_idx + 1, self.tabs.len()) as u32);
|
||||||
|
}
|
||||||
|
Mouse::ScrollDown(_) => {
|
||||||
|
switch_tab_to(max(self.active_tab_idx.saturating_sub(1), 1) as u32);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
},
|
||||||
_ => unimplemented!(), // FIXME: This should be unreachable, but this could be cleaner
|
_ => unimplemented!(), // FIXME: This should be unreachable, but this could be cleaner
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -70,8 +97,21 @@ impl ZellijPlugin for State {
|
||||||
self.mode_info.capabilities,
|
self.mode_info.capabilities,
|
||||||
);
|
);
|
||||||
let mut s = String::new();
|
let mut s = String::new();
|
||||||
for bar_part in tab_line {
|
let mut len_cnt = 0;
|
||||||
s = format!("{}{}", s, bar_part.part);
|
dbg!(&tab_line);
|
||||||
|
for (idx, bar_part) in tab_line.iter().enumerate() {
|
||||||
|
s = format!("{}{}", s, &bar_part.part);
|
||||||
|
|
||||||
|
if self.should_render
|
||||||
|
&& self.mouse_click_pos > len_cnt
|
||||||
|
&& self.mouse_click_pos <= len_cnt + bar_part.len
|
||||||
|
&& idx > 2
|
||||||
|
{
|
||||||
|
// First three elements of tab_line are "Zellij", session name and empty thing, hence the idx > 2 condition.
|
||||||
|
// Tabs are indexed starting from 1, therefore we need subtract 2 below.
|
||||||
|
switch_tab_to(TryInto::<u32>::try_into(idx).unwrap() - 2);
|
||||||
|
}
|
||||||
|
len_cnt += bar_part.len;
|
||||||
}
|
}
|
||||||
match self.mode_info.palette.cyan {
|
match self.mode_info.palette.cyan {
|
||||||
PaletteColor::Rgb((r, g, b)) => {
|
PaletteColor::Rgb((r, g, b)) => {
|
||||||
|
|
@ -81,5 +121,6 @@ impl ZellijPlugin for State {
|
||||||
println!("{}\u{1b}[48;5;{}m\u{1b}[0K", s, color);
|
println!("{}\u{1b}[48;5;{}m\u{1b}[0K", s, color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
self.should_render = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,9 @@ use crate::tab::Pane;
|
||||||
use crate::ui::pane_boundaries_frame::PaneFrame;
|
use crate::ui::pane_boundaries_frame::PaneFrame;
|
||||||
use crate::wasm_vm::PluginInstruction;
|
use crate::wasm_vm::PluginInstruction;
|
||||||
use zellij_utils::pane_size::Offset;
|
use zellij_utils::pane_size::Offset;
|
||||||
|
use zellij_utils::position::Position;
|
||||||
use zellij_utils::shared::ansi_len;
|
use zellij_utils::shared::ansi_len;
|
||||||
use zellij_utils::zellij_tile::prelude::PaletteColor;
|
use zellij_utils::zellij_tile::prelude::{Event, Mouse, PaletteColor};
|
||||||
use zellij_utils::{
|
use zellij_utils::{
|
||||||
channels::SenderWithContext,
|
channels::SenderWithContext,
|
||||||
pane_size::{Dimension, PaneGeom},
|
pane_size::{Dimension, PaneGeom},
|
||||||
|
|
@ -254,14 +255,50 @@ impl Pane for PluginPane {
|
||||||
self.geom.y -= count;
|
self.geom.y -= count;
|
||||||
self.should_render = true;
|
self.should_render = true;
|
||||||
}
|
}
|
||||||
fn scroll_up(&mut self, _count: usize) {
|
fn scroll_up(&mut self, count: usize) {
|
||||||
//unimplemented!()
|
self.send_plugin_instructions
|
||||||
|
.send(PluginInstruction::Update(
|
||||||
|
Some(self.pid),
|
||||||
|
Event::Mouse(Mouse::ScrollUp(count)),
|
||||||
|
))
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
fn scroll_down(&mut self, _count: usize) {
|
fn scroll_down(&mut self, count: usize) {
|
||||||
//unimplemented!()
|
self.send_plugin_instructions
|
||||||
|
.send(PluginInstruction::Update(
|
||||||
|
Some(self.pid),
|
||||||
|
Event::Mouse(Mouse::ScrollDown(count)),
|
||||||
|
))
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
fn clear_scroll(&mut self) {
|
fn clear_scroll(&mut self) {
|
||||||
//unimplemented!()
|
unimplemented!();
|
||||||
|
}
|
||||||
|
fn start_selection(&mut self, start: &Position) {
|
||||||
|
self.send_plugin_instructions
|
||||||
|
.send(PluginInstruction::Update(
|
||||||
|
Some(self.pid),
|
||||||
|
Event::Mouse(Mouse::LeftClick(start.line.0, start.column.0)),
|
||||||
|
))
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
fn update_selection(&mut self, position: &Position) {
|
||||||
|
self.send_plugin_instructions
|
||||||
|
.send(PluginInstruction::Update(
|
||||||
|
Some(self.pid),
|
||||||
|
Event::Mouse(Mouse::MouseHold(position.line.0, position.column.0)),
|
||||||
|
))
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
fn end_selection(&mut self, end: Option<&Position>) {
|
||||||
|
self.send_plugin_instructions
|
||||||
|
.send(PluginInstruction::Update(
|
||||||
|
Some(self.pid),
|
||||||
|
Event::Mouse(Mouse::MouseRelease(
|
||||||
|
end.map(|Position { line, column }| (line.0, column.0)),
|
||||||
|
)),
|
||||||
|
))
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
fn is_scrolled(&self) -> bool {
|
fn is_scrolled(&self) -> bool {
|
||||||
false
|
false
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,8 @@
|
||||||
//! `Tab`s holds multiple panes. It tracks their coordinates (x/y) and size,
|
//! `Tab`s holds multiple panes. It tracks their coordinates (x/y) and size,
|
||||||
//! as well as how they should be resized
|
//! as well as how they should be resized
|
||||||
|
|
||||||
|
use zellij_utils::{position::Position, serde, zellij_tile};
|
||||||
|
|
||||||
use crate::ui::pane_resizer::PaneResizer;
|
use crate::ui::pane_resizer::PaneResizer;
|
||||||
use crate::{
|
use crate::{
|
||||||
os_input_output::ServerOsApi,
|
os_input_output::ServerOsApi,
|
||||||
|
|
@ -19,16 +22,12 @@ use std::{
|
||||||
collections::{BTreeMap, HashMap, HashSet},
|
collections::{BTreeMap, HashMap, HashSet},
|
||||||
};
|
};
|
||||||
use zellij_tile::data::{Event, InputMode, ModeInfo, Palette, PaletteColor};
|
use zellij_tile::data::{Event, InputMode, ModeInfo, Palette, PaletteColor};
|
||||||
use zellij_utils::input::layout::Direction;
|
|
||||||
use zellij_utils::pane_size::{Offset, Size, Viewport};
|
|
||||||
use zellij_utils::{
|
use zellij_utils::{
|
||||||
input::{
|
input::{
|
||||||
layout::{Layout, Run},
|
layout::{Direction, Layout, Run},
|
||||||
parse_keys,
|
parse_keys,
|
||||||
},
|
},
|
||||||
pane_size::{Dimension, PaneGeom},
|
pane_size::{Dimension, Offset, PaneGeom, Size, Viewport},
|
||||||
position::Position,
|
|
||||||
serde, zellij_tile,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const CURSOR_HEIGHT_WIDTH_RATIO: usize = 4; // this is not accurate and kind of a magic number, TODO: look into this
|
const CURSOR_HEIGHT_WIDTH_RATIO: usize = 4; // this is not accurate and kind of a magic number, TODO: look into this
|
||||||
|
|
@ -2322,12 +2321,12 @@ impl Tab {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn scroll_terminal_up(&mut self, point: &Position, lines: usize) {
|
pub fn scroll_terminal_up(&mut self, point: &Position, lines: usize) {
|
||||||
if let Some(pane) = self.get_pane_at(point) {
|
if let Some(pane) = self.get_pane_at(point, false) {
|
||||||
pane.scroll_up(lines);
|
pane.scroll_up(lines);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn scroll_terminal_down(&mut self, point: &Position, lines: usize) {
|
pub fn scroll_terminal_down(&mut self, point: &Position, lines: usize) {
|
||||||
if let Some(pane) = self.get_pane_at(point) {
|
if let Some(pane) = self.get_pane_at(point, false) {
|
||||||
pane.scroll_down(lines);
|
pane.scroll_down(lines);
|
||||||
if !pane.is_scrolled() {
|
if !pane.is_scrolled() {
|
||||||
if let PaneId::Terminal(pid) = pane.pid() {
|
if let PaneId::Terminal(pid) = pane.pid() {
|
||||||
|
|
@ -2336,32 +2335,42 @@ impl Tab {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn get_pane_at(&mut self, point: &Position) -> Option<&mut Box<dyn Pane>> {
|
fn get_pane_at(
|
||||||
if let Some(pane_id) = self.get_pane_id_at(point) {
|
&mut self,
|
||||||
|
point: &Position,
|
||||||
|
search_selectable: bool,
|
||||||
|
) -> Option<&mut Box<dyn Pane>> {
|
||||||
|
if let Some(pane_id) = self.get_pane_id_at(point, search_selectable) {
|
||||||
self.panes.get_mut(&pane_id)
|
self.panes.get_mut(&pane_id)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn get_pane_id_at(&self, point: &Position) -> Option<PaneId> {
|
|
||||||
|
fn get_pane_id_at(&self, point: &Position, search_selectable: bool) -> Option<PaneId> {
|
||||||
if self.fullscreen_is_active {
|
if self.fullscreen_is_active {
|
||||||
return self.get_active_pane_id();
|
return self.get_active_pane_id();
|
||||||
}
|
}
|
||||||
|
if search_selectable {
|
||||||
self.get_selectable_panes()
|
self.get_selectable_panes()
|
||||||
.find(|(_, p)| p.contains(point))
|
.find(|(_, p)| p.contains(point))
|
||||||
.map(|(&id, _)| id)
|
.map(|(&id, _)| id)
|
||||||
|
} else {
|
||||||
|
self.get_panes()
|
||||||
|
.find(|(_, p)| p.contains(point))
|
||||||
|
.map(|(&id, _)| id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
pub fn handle_left_click(&mut self, position: &Position) {
|
pub fn handle_left_click(&mut self, position: &Position) {
|
||||||
self.focus_pane_at(position);
|
self.focus_pane_at(position);
|
||||||
|
|
||||||
if let Some(pane) = self.get_pane_at(position) {
|
if let Some(pane) = self.get_pane_at(position, false) {
|
||||||
let relative_position = pane.relative_position(position);
|
let relative_position = pane.relative_position(position);
|
||||||
pane.start_selection(&relative_position);
|
pane.start_selection(&relative_position);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
fn focus_pane_at(&mut self, point: &Position) {
|
fn focus_pane_at(&mut self, point: &Position) {
|
||||||
if let Some(clicked_pane) = self.get_pane_id_at(point) {
|
if let Some(clicked_pane) = self.get_pane_id_at(point, true) {
|
||||||
self.active_terminal = Some(clicked_pane);
|
self.active_terminal = Some(clicked_pane);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2369,7 +2378,7 @@ impl Tab {
|
||||||
let active_pane_id = self.get_active_pane_id();
|
let active_pane_id = self.get_active_pane_id();
|
||||||
// on release, get the selected text from the active pane, and reset it's selection
|
// on release, get the selected text from the active pane, and reset it's selection
|
||||||
let mut selected_text = None;
|
let mut selected_text = None;
|
||||||
if active_pane_id != self.get_pane_id_at(position) {
|
if active_pane_id != self.get_pane_id_at(position, true) {
|
||||||
if let Some(active_pane_id) = active_pane_id {
|
if let Some(active_pane_id) = active_pane_id {
|
||||||
if let Some(active_pane) = self.panes.get_mut(&active_pane_id) {
|
if let Some(active_pane) = self.panes.get_mut(&active_pane_id) {
|
||||||
active_pane.end_selection(None);
|
active_pane.end_selection(None);
|
||||||
|
|
@ -2377,7 +2386,7 @@ impl Tab {
|
||||||
active_pane.reset_selection();
|
active_pane.reset_selection();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if let Some(pane) = self.get_pane_at(position) {
|
} else if let Some(pane) = self.get_pane_at(position, true) {
|
||||||
let relative_position = pane.relative_position(position);
|
let relative_position = pane.relative_position(position);
|
||||||
pane.end_selection(Some(&relative_position));
|
pane.end_selection(Some(&relative_position));
|
||||||
selected_text = pane.get_selected_text();
|
selected_text = pane.get_selected_text();
|
||||||
|
|
|
||||||
|
|
@ -241,6 +241,7 @@ pub(crate) fn zellij_exports(store: &Store, plugin_env: &PluginEnv) -> ImportObj
|
||||||
host_set_selectable,
|
host_set_selectable,
|
||||||
host_get_plugin_ids,
|
host_get_plugin_ids,
|
||||||
host_open_file,
|
host_open_file,
|
||||||
|
host_switch_tab_to,
|
||||||
host_set_timeout,
|
host_set_timeout,
|
||||||
host_exec_cmd,
|
host_exec_cmd,
|
||||||
}
|
}
|
||||||
|
|
@ -298,6 +299,13 @@ fn host_open_file(plugin_env: &PluginEnv) {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn host_switch_tab_to(plugin_env: &PluginEnv, tab_idx: u32) {
|
||||||
|
plugin_env
|
||||||
|
.senders
|
||||||
|
.send_to_screen(ScreenInstruction::GoToTab(tab_idx))
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
fn host_set_timeout(plugin_env: &PluginEnv, secs: f64) {
|
fn host_set_timeout(plugin_env: &PluginEnv, secs: f64) {
|
||||||
// There is a fancy, high-performance way to do this with zero additional threads:
|
// There is a fancy, high-performance way to do this with zero additional threads:
|
||||||
// If the plugin thread keeps a BinaryHeap of timer structs, it can manage multiple and easily `.peek()` at the
|
// If the plugin thread keeps a BinaryHeap of timer structs, it can manage multiple and easily `.peek()` at the
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,16 @@ pub enum Key {
|
||||||
Esc,
|
Esc,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
|
|
||||||
|
pub enum Mouse {
|
||||||
|
ScrollUp(usize), // number of lines
|
||||||
|
ScrollDown(usize), // number of lines
|
||||||
|
LeftClick(isize, usize), // line and column
|
||||||
|
MouseHold(isize, usize), // line and column
|
||||||
|
MouseRelease(Option<(isize, usize)>), // line and column
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, EnumDiscriminants, ToString, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, EnumDiscriminants, ToString, Serialize, Deserialize)]
|
||||||
#[strum_discriminants(derive(EnumString, Hash, Serialize, Deserialize))]
|
#[strum_discriminants(derive(EnumString, Hash, Serialize, Deserialize))]
|
||||||
#[strum_discriminants(name(EventType))]
|
#[strum_discriminants(name(EventType))]
|
||||||
|
|
@ -33,6 +43,7 @@ pub enum Event {
|
||||||
ModeUpdate(ModeInfo),
|
ModeUpdate(ModeInfo),
|
||||||
TabUpdate(Vec<TabInfo>),
|
TabUpdate(Vec<TabInfo>),
|
||||||
KeyPress(Key),
|
KeyPress(Key),
|
||||||
|
Mouse(Mouse),
|
||||||
Timer(f64),
|
Timer(f64),
|
||||||
CopyToClipboard,
|
CopyToClipboard,
|
||||||
InputReceived,
|
InputReceived,
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,10 @@ pub fn open_file(path: &Path) {
|
||||||
unsafe { host_open_file() };
|
unsafe { host_open_file() };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn switch_tab_to(tab_idx: u32) {
|
||||||
|
unsafe { host_switch_tab_to(tab_idx) };
|
||||||
|
}
|
||||||
|
|
||||||
pub fn set_timeout(secs: f64) {
|
pub fn set_timeout(secs: f64) {
|
||||||
unsafe { host_set_timeout(secs) };
|
unsafe { host_set_timeout(secs) };
|
||||||
}
|
}
|
||||||
|
|
@ -63,6 +67,7 @@ extern "C" {
|
||||||
fn host_set_selectable(selectable: i32);
|
fn host_set_selectable(selectable: i32);
|
||||||
fn host_get_plugin_ids();
|
fn host_get_plugin_ids();
|
||||||
fn host_open_file();
|
fn host_open_file();
|
||||||
|
fn host_switch_tab_to(tab_idx: u32);
|
||||||
fn host_set_timeout(secs: f64);
|
fn host_set_timeout(secs: f64);
|
||||||
fn host_exec_cmd();
|
fn host_exec_cmd();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue