* work * almost done with command protobuffers * done translating command data structures * mid transferring of every command to protobuff command * transferred plugin_command.rs, now moving on to shim.rs * plugin command working with protobufs * protobuffers in update * protobuf event tests * various TODOs and comments * fix zellij-tile * clean up prost deps * remove version mismatch error * fix panic * some cleanups * clean up event protobuffers * clean up command protobuffers * clean up various protobufs * refactor protobufs * update comments * some transformation fixes * use protobufs for workers * style(fmt): rustfmt * style(fmt): rustfmt * chore(build): add protoc * chore(build): authenticate protoc
293 lines
12 KiB
Rust
293 lines
12 KiB
Rust
mod search;
|
|
mod state;
|
|
|
|
use colored::*;
|
|
use search::{FileContentsWorker, FileNameWorker, MessageToSearch, ResultsOfSearch};
|
|
use serde::{Deserialize, Serialize};
|
|
use serde_json;
|
|
use state::{refresh_directory, FsEntry, State};
|
|
use std::collections::BTreeMap;
|
|
use std::{cmp::min, time::Instant};
|
|
use zellij_tile::prelude::*;
|
|
|
|
register_plugin!(State);
|
|
register_worker!(FileNameWorker, file_name_search_worker, FILE_NAME_WORKER);
|
|
register_worker!(
|
|
FileContentsWorker,
|
|
file_contents_search_worker,
|
|
FILE_CONTENTS_WORKER
|
|
);
|
|
|
|
impl ZellijPlugin for State {
|
|
fn load(&mut self, configuration: BTreeMap<String, String>) {
|
|
refresh_directory(self);
|
|
self.search_state.loading = true;
|
|
subscribe(&[
|
|
EventType::Key,
|
|
EventType::Mouse,
|
|
EventType::CustomMessage,
|
|
EventType::Timer,
|
|
EventType::FileSystemCreate,
|
|
EventType::FileSystemUpdate,
|
|
EventType::FileSystemDelete,
|
|
]);
|
|
post_message_to(PluginMessage {
|
|
worker_name: Some("file_name_search".into()),
|
|
name: serde_json::to_string(&MessageToSearch::ScanFolder).unwrap(),
|
|
payload: "".into(),
|
|
});
|
|
post_message_to(PluginMessage {
|
|
worker_name: Some("file_contents_search".into()),
|
|
name: serde_json::to_string(&MessageToSearch::ScanFolder).unwrap(),
|
|
payload: "".into(),
|
|
});
|
|
self.search_state.loading = true;
|
|
set_timeout(0.5); // for displaying loading animation
|
|
}
|
|
|
|
fn update(&mut self, event: Event) -> bool {
|
|
let mut should_render = false;
|
|
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::Timer(_elapsed) => {
|
|
if self.search_state.loading {
|
|
set_timeout(0.5);
|
|
self.search_state.progress_animation();
|
|
should_render = true;
|
|
}
|
|
},
|
|
Event::CustomMessage(message, payload) => match serde_json::from_str(&message) {
|
|
Ok(MessageToPlugin::UpdateFileNameSearchResults) => {
|
|
if let Ok(results_of_search) = serde_json::from_str::<ResultsOfSearch>(&payload)
|
|
{
|
|
self.search_state
|
|
.update_file_name_search_results(results_of_search);
|
|
should_render = true;
|
|
}
|
|
},
|
|
Ok(MessageToPlugin::UpdateFileContentsSearchResults) => {
|
|
if let Ok(results_of_search) = serde_json::from_str::<ResultsOfSearch>(&payload)
|
|
{
|
|
self.search_state
|
|
.update_file_contents_search_results(results_of_search);
|
|
should_render = true;
|
|
}
|
|
},
|
|
Ok(MessageToPlugin::DoneScanningFolder) => {
|
|
self.search_state.loading = false;
|
|
should_render = true;
|
|
},
|
|
Err(e) => eprintln!("Failed to deserialize custom message: {:?}", e),
|
|
},
|
|
Event::Key(key) => match key {
|
|
Key::Esc if self.typing_search_term() => {
|
|
if self.search_state.search_term.is_empty() {
|
|
self.stop_typing_search_term();
|
|
} else {
|
|
self.search_state.handle_key(key);
|
|
}
|
|
should_render = true;
|
|
},
|
|
_ if self.typing_search_term() => {
|
|
self.search_state.handle_key(key);
|
|
should_render = true;
|
|
},
|
|
Key::Char('/') => {
|
|
self.start_typing_search_term();
|
|
should_render = true;
|
|
},
|
|
Key::Esc => {
|
|
self.stop_typing_search_term();
|
|
hide_self();
|
|
should_render = true;
|
|
},
|
|
Key::Up | Key::Char('k') => {
|
|
let currently_selected = self.selected();
|
|
*self.selected_mut() = self.selected().saturating_sub(1);
|
|
if currently_selected != self.selected() {
|
|
should_render = true;
|
|
}
|
|
},
|
|
Key::Down | Key::Char('j') => {
|
|
let currently_selected = self.selected();
|
|
let next = self.selected().saturating_add(1);
|
|
*self.selected_mut() = min(self.files.len().saturating_sub(1), next);
|
|
if currently_selected != self.selected() {
|
|
should_render = true;
|
|
}
|
|
},
|
|
Key::Right | Key::Char('\n') | Key::Char('l') if !self.files.is_empty() => {
|
|
self.traverse_dir_or_open_file();
|
|
self.ev_history.clear();
|
|
should_render = true;
|
|
},
|
|
Key::Left | Key::Char('h') => {
|
|
if self.path.components().count() > 2 {
|
|
// don't descend into /host
|
|
// the reason this is a hard-coded number (2) and not "== ROOT"
|
|
// or some such is that there are certain cases in which self.path
|
|
// is empty and this will work then too
|
|
should_render = true;
|
|
self.path.pop();
|
|
refresh_directory(self);
|
|
}
|
|
},
|
|
Key::Char('.') => {
|
|
should_render = true;
|
|
self.toggle_hidden_files();
|
|
refresh_directory(self);
|
|
},
|
|
|
|
_ => (),
|
|
},
|
|
Event::Mouse(mouse_event) => match mouse_event {
|
|
Mouse::ScrollDown(_) => {
|
|
let currently_selected = self.selected();
|
|
let next = self.selected().saturating_add(1);
|
|
*self.selected_mut() = min(self.files.len().saturating_sub(1), next);
|
|
if currently_selected != self.selected() {
|
|
should_render = true;
|
|
}
|
|
},
|
|
Mouse::ScrollUp(_) => {
|
|
let currently_selected = self.selected();
|
|
*self.selected_mut() = self.selected().saturating_sub(1);
|
|
if currently_selected != self.selected() {
|
|
should_render = true;
|
|
}
|
|
},
|
|
Mouse::Release(line, _) => {
|
|
if line < 0 {
|
|
return should_render;
|
|
}
|
|
let mut should_select = true;
|
|
if let Some((Event::Mouse(Mouse::Release(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;
|
|
should_render = true;
|
|
}
|
|
}
|
|
if should_select && self.scroll() + (line as usize) < self.files.len() {
|
|
let currently_selected = self.selected();
|
|
*self.selected_mut() = self.scroll() + (line as usize);
|
|
if currently_selected != self.selected() {
|
|
should_render = true;
|
|
}
|
|
}
|
|
},
|
|
_ => {},
|
|
},
|
|
Event::FileSystemCreate(paths) => {
|
|
let paths: Vec<String> = paths
|
|
.iter()
|
|
.map(|p| p.to_string_lossy().to_string())
|
|
.collect();
|
|
post_message_to(PluginMessage {
|
|
worker_name: Some("file_name_search".into()),
|
|
name: serde_json::to_string(&MessageToSearch::FileSystemCreate).unwrap(),
|
|
payload: serde_json::to_string(&paths).unwrap(),
|
|
});
|
|
post_message_to(PluginMessage {
|
|
worker_name: Some("file_contents_search".into()),
|
|
name: serde_json::to_string(&MessageToSearch::FileSystemCreate).unwrap(),
|
|
payload: serde_json::to_string(&paths).unwrap(),
|
|
});
|
|
},
|
|
Event::FileSystemUpdate(paths) => {
|
|
let paths: Vec<String> = paths
|
|
.iter()
|
|
.map(|p| p.to_string_lossy().to_string())
|
|
.collect();
|
|
post_message_to(PluginMessage {
|
|
worker_name: Some("file_name_search".into()),
|
|
name: serde_json::to_string(&MessageToSearch::FileSystemUpdate).unwrap(),
|
|
payload: serde_json::to_string(&paths).unwrap(),
|
|
});
|
|
post_message_to(PluginMessage {
|
|
worker_name: Some("file_contents_search".into()),
|
|
name: serde_json::to_string(&MessageToSearch::FileSystemUpdate).unwrap(),
|
|
payload: serde_json::to_string(&paths).unwrap(),
|
|
});
|
|
},
|
|
Event::FileSystemDelete(paths) => {
|
|
let paths: Vec<String> = paths
|
|
.iter()
|
|
.map(|p| p.to_string_lossy().to_string())
|
|
.collect();
|
|
post_message_to(PluginMessage {
|
|
worker_name: Some("file_name_search".into()),
|
|
name: serde_json::to_string(&MessageToSearch::FileSystemDelete).unwrap(),
|
|
payload: serde_json::to_string(&paths).unwrap(),
|
|
});
|
|
post_message_to(PluginMessage {
|
|
worker_name: Some("file_contents_search".into()),
|
|
name: serde_json::to_string(&MessageToSearch::FileSystemDelete).unwrap(),
|
|
payload: serde_json::to_string(&paths).unwrap(),
|
|
});
|
|
},
|
|
_ => {
|
|
dbg!("Unknown event {:?}", event);
|
|
},
|
|
};
|
|
should_render
|
|
}
|
|
|
|
fn render(&mut self, rows: usize, cols: usize) {
|
|
if self.typing_search_term() {
|
|
self.search_state.change_size(rows, cols);
|
|
print!("{}", self.search_state);
|
|
return;
|
|
}
|
|
|
|
for i in 0..rows {
|
|
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 is_last_row = i == rows.saturating_sub(1);
|
|
let i = self.scroll() + i;
|
|
if let Some(entry) = self.files.get(i) {
|
|
let mut path = entry.as_line(cols).normal();
|
|
|
|
if let FsEntry::Dir(..) = entry {
|
|
path = path.dimmed().bold();
|
|
}
|
|
|
|
if i == self.selected() {
|
|
if is_last_row {
|
|
print!("{}", path.clone().reversed());
|
|
} else {
|
|
println!("{}", path.clone().reversed());
|
|
}
|
|
} else {
|
|
if is_last_row {
|
|
print!("{}", path);
|
|
} else {
|
|
println!("{}", path);
|
|
}
|
|
}
|
|
} else if !is_last_row {
|
|
println!();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
pub enum MessageToPlugin {
|
|
UpdateFileNameSearchResults,
|
|
UpdateFileContentsSearchResults,
|
|
DoneScanningFolder,
|
|
}
|