Skip to content

Commit

Permalink
feat(plugin): Add mouse events for plugins (#629)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
qepasa authored Oct 12, 2021
1 parent a6453f1 commit 0710594
Show file tree
Hide file tree
Showing 8 changed files with 229 additions and 68 deletions.
86 changes: 49 additions & 37 deletions default-plugins/strider/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,27 @@
mod state;

use colored::*;
use state::{FsEntry, State};
use std::{cmp::min, fs::read_dir, path::Path};
use state::{refresh_directory, FsEntry, State};
use std::{cmp::min, time::Instant};
use zellij_tile::prelude::*;

const ROOT: &str = "/host";

register_plugin!(State);

impl ZellijPlugin for State {
fn load(&mut self) {
refresh_directory(self);
subscribe(&[EventType::KeyPress]);
subscribe(&[EventType::KeyPress, EventType::Mouse]);
}

fn update(&mut self, event: Event) {
if let Event::KeyPress(key) = event {
match key {
let prev_event = if self.ev_history.len() == 2 {
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') => {
*self.selected_mut() = self.selected().saturating_sub(1);
}
Expand All @@ -26,13 +30,8 @@ impl ZellijPlugin for State {
*self.selected_mut() = min(self.files.len() - 1, next);
}
Key::Right | Key::Char('\n') | Key::Char('l') if !self.files.is_empty() => {
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()),
}
self.traverse_dir_or_open_file();
self.ev_history.clear();
}
Key::Left | Key::Char('h') => {
if self.path.components().count() > 2 {
Expand All @@ -44,25 +43,59 @@ impl ZellijPlugin for State {
refresh_directory(self);
}
}

Key::Char('.') => {
self.toggle_hidden_files();
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) {
for i in 0..rows {
// If the key was pressed, set selected so that we can see the cursor
if self.selected() < self.scroll() {
*self.scroll_mut() = self.selected();
}
if self.selected() - self.scroll() + 2 > rows {
*self.scroll_mut() = self.selected() + 2 - rows;
}

let i = self.scroll() + i;
if let Some(entry) = self.files.get(i) {
let mut path = entry.as_line(cols).normal();
Expand All @@ -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();
}
40 changes: 39 additions & 1 deletion default-plugins/strider/src/state.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
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)]
pub struct State {
pub path: PathBuf,
pub files: Vec<FsEntry>,
pub cursor_hist: HashMap<PathBuf, (usize, usize)>,
pub hide_hidden_files: bool,
pub ev_history: VecDeque<(Event, Instant)>, // stores last event, can be expanded in future
}

impl State {
Expand All @@ -25,6 +33,15 @@ impl State {
pub fn toggle_hidden_files(&mut self) {
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)]
Expand Down Expand Up @@ -61,3 +78,24 @@ impl FsEntry {
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();
}
49 changes: 45 additions & 4 deletions default-plugins/tab-bar/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
mod line;
mod tab;

use std::cmp::{max, min};
use std::convert::TryInto;

use zellij_tile::prelude::*;

use crate::line::tab_line;
Expand All @@ -15,7 +18,10 @@ pub struct LinePart {
#[derive(Default)]
struct State {
tabs: Vec<TabInfo>,
active_tab_idx: usize,
mode_info: ModeInfo,
mouse_click_pos: usize,
should_render: bool,
}

static ARROW_SEPARATOR: &str = "";
Expand All @@ -25,13 +31,34 @@ register_plugin!(State);
impl ZellijPlugin for State {
fn load(&mut self) {
set_selectable(false);
subscribe(&[EventType::TabUpdate, EventType::ModeUpdate]);
subscribe(&[
EventType::TabUpdate,
EventType::ModeUpdate,
EventType::Mouse,
]);
}

fn update(&mut self, event: Event) {
match event {
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
}
}
Expand Down Expand Up @@ -70,8 +97,21 @@ impl ZellijPlugin for State {
self.mode_info.capabilities,
);
let mut s = String::new();
for bar_part in tab_line {
s = format!("{}{}", s, bar_part.part);
let mut len_cnt = 0;
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 {
PaletteColor::Rgb((r, g, b)) => {
Expand All @@ -81,5 +121,6 @@ impl ZellijPlugin for State {
println!("{}\u{1b}[48;5;{}m\u{1b}[0K", s, color);
}
}
self.should_render = false;
}
}
51 changes: 44 additions & 7 deletions zellij-server/src/panes/plugin_pane.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ use crate::tab::Pane;
use crate::ui::pane_boundaries_frame::PaneFrame;
use crate::wasm_vm::PluginInstruction;
use zellij_utils::pane_size::Offset;
use zellij_utils::position::Position;
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::{
channels::SenderWithContext,
pane_size::{Dimension, PaneGeom},
Expand Down Expand Up @@ -254,14 +255,50 @@ impl Pane for PluginPane {
self.geom.y -= count;
self.should_render = true;
}
fn scroll_up(&mut self, _count: usize) {
//unimplemented!()
}
fn scroll_down(&mut self, _count: usize) {
//unimplemented!()
fn scroll_up(&mut self, count: usize) {
self.send_plugin_instructions
.send(PluginInstruction::Update(
Some(self.pid),
Event::Mouse(Mouse::ScrollUp(count)),
))
.unwrap();
}
fn scroll_down(&mut self, count: usize) {
self.send_plugin_instructions
.send(PluginInstruction::Update(
Some(self.pid),
Event::Mouse(Mouse::ScrollDown(count)),
))
.unwrap();
}
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 {
false
Expand Down
Loading

0 comments on commit 0710594

Please sign in to comment.