summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAram Drevekenin <aram@poor.dev>2024-03-18 09:19:58 +0100
committerGitHub <noreply@github.com>2024-03-18 09:19:58 +0100
commitee16a4b8c347a81878389fd4f90144c2c6357f41 (patch)
treebf8675d57499b6278e8c89b84c8141969cc2ce3f
parent12daac3b5445e4281cf5c1810be0ebdb257085c1 (diff)
feat(plugins): session manager cwd and new filepicker (#3200)
* prototype * folder selection ui in session manager * overhaul strider * scan folder host command * get strider to work from the cli and some cli pipe fixes * some ux improvements to strider * improve strider's ui * make strider ui responsive * make session-manager new ui parts responsive * fix tests * style(fmt): rustfmt
-rw-r--r--Cargo.lock5
-rw-r--r--default-plugins/fixture-plugin-for-tests/src/main.rs1
-rw-r--r--default-plugins/session-manager/Cargo.toml1
-rw-r--r--default-plugins/session-manager/src/main.rs56
-rw-r--r--default-plugins/session-manager/src/new_session_info.rs5
-rw-r--r--default-plugins/session-manager/src/ui/components.rs178
-rw-r--r--default-plugins/strider/.cargo/config.toml2
-rw-r--r--default-plugins/strider/src/file_list_view.rs191
-rw-r--r--default-plugins/strider/src/main.rs224
-rw-r--r--default-plugins/strider/src/search_view.rs134
-rw-r--r--default-plugins/strider/src/shared.rs156
-rw-r--r--default-plugins/strider/src/state.rs243
-rw-r--r--src/main.rs2
-rw-r--r--zellij-client/src/cli_client.rs52
-rw-r--r--zellij-client/src/os_input_output.rs17
-rw-r--r--zellij-server/src/plugins/mod.rs16
-rw-r--r--zellij-server/src/plugins/wasm_bridge.rs14
-rw-r--r--zellij-server/src/plugins/watch_filesystem.rs33
-rw-r--r--zellij-server/src/plugins/zellij_exports.rs81
-rw-r--r--zellij-tile/src/shim.rs18
-rw-r--r--zellij-utils/assets/prost/api.event.rs17
-rw-r--r--zellij-utils/assets/prost/api.plugin_command.rs10
-rw-r--r--zellij-utils/assets/prost/api.plugin_ids.rs2
-rw-r--r--zellij-utils/src/data.rs33
-rw-r--r--zellij-utils/src/errors.rs1
-rw-r--r--zellij-utils/src/input/actions.rs18
-rw-r--r--zellij-utils/src/plugin_api/event.proto9
-rw-r--r--zellij-utils/src/plugin_api/event.rs126
-rw-r--r--zellij-utils/src/plugin_api/plugin_command.proto3
-rw-r--r--zellij-utils/src/plugin_api/plugin_command.rs22
-rw-r--r--zellij-utils/src/plugin_api/plugin_ids.proto1
-rw-r--r--zellij-utils/src/plugin_api/plugin_ids.rs3
32 files changed, 1390 insertions, 284 deletions
diff --git a/Cargo.lock b/Cargo.lock
index b109ca64f..279ee4c08 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3338,6 +3338,7 @@ dependencies = [
"fuzzy-matcher",
"humantime",
"unicode-width",
+ "uuid",
"zellij-tile",
]
@@ -4191,9 +4192,9 @@ checksum = "936e4b492acfd135421d8dca4b1aa80a7bfc26e702ef3af710e0752684df5372"
[[package]]
name = "uuid"
-version = "1.4.1"
+version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "79daa5ed5740825c40b389c5e50312b9c86df53fccd33f281df655642b43869d"
+checksum = "f00cc9702ca12d3c81455259621e676d0f7251cec66a21e98fe2e9a37db93b2a"
dependencies = [
"getrandom 0.2.10",
"serde",
diff --git a/default-plugins/fixture-plugin-for-tests/src/main.rs b/default-plugins/fixture-plugin-for-tests/src/main.rs
index cd4fb45a1..c241281a1 100644
--- a/default-plugins/fixture-plugin-for-tests/src/main.rs
+++ b/default-plugins/fixture-plugin-for-tests/src/main.rs
@@ -67,6 +67,7 @@ impl ZellijPlugin for State {
EventType::FileSystemUpdate,
EventType::FileSystemDelete,
]);
+ watch_filesystem();
}
fn update(&mut self, event: Event) -> bool {
diff --git a/default-plugins/session-manager/Cargo.toml b/default-plugins/session-manager/Cargo.toml
index 97100ed69..deb8cec55 100644
--- a/default-plugins/session-manager/Cargo.toml
+++ b/default-plugins/session-manager/Cargo.toml
@@ -11,3 +11,4 @@ chrono = "0.4.0"
fuzzy-matcher = "0.3.7"
unicode-width = "0.1.10"
humantime = "2.1.0"
+uuid = { version = "1.7.0", features = ["v4"] }
diff --git a/default-plugins/session-manager/src/main.rs b/default-plugins/session-manager/src/main.rs
index 8040f79e0..7015144fd 100644
--- a/default-plugins/session-manager/src/main.rs
+++ b/default-plugins/session-manager/src/main.rs
@@ -2,9 +2,9 @@ mod new_session_info;
mod resurrectable_sessions;
mod session_list;
mod ui;
-use zellij_tile::prelude::*;
-
use std::collections::BTreeMap;
+use uuid::Uuid;
+use zellij_tile::prelude::*;
use new_session_info::NewSessionInfo;
use ui::{
@@ -45,6 +45,7 @@ struct State {
colors: Colors,
is_welcome_screen: bool,
show_kill_all_sessions_warning: bool,
+ request_ids: Vec<String>,
}
register_plugin!(State);
@@ -66,6 +67,28 @@ impl ZellijPlugin for State {
]);
}
+ fn pipe(&mut self, pipe_message: PipeMessage) -> bool {
+ if pipe_message.name == "filepicker_result" {
+ match (pipe_message.payload, pipe_message.args.get("request_id")) {
+ (Some(payload), Some(request_id)) => {
+ match self.request_ids.iter().position(|p| p == request_id) {
+ Some(request_id_position) => {
+ self.request_ids.remove(request_id_position);
+ let new_session_folder = std::path::PathBuf::from(payload);
+ self.new_session_info.new_session_folder = Some(new_session_folder);
+ },
+ None => {
+ eprintln!("request id not found");
+ },
+ }
+ },
+ _ => {},
+ }
+ true
+ } else {
+ false
+ }
+ }
fn update(&mut self, event: Event) -> bool {
let mut should_render = false;
match event {
@@ -109,7 +132,7 @@ impl ZellijPlugin for State {
render_new_session_block(
&self.new_session_info,
self.colors,
- height,
+ height.saturating_sub(2),
width,
x,
y + 2,
@@ -184,12 +207,33 @@ impl State {
} else if let Key::Ctrl('w') = key {
self.active_screen = ActiveScreen::NewSession;
should_render = true;
- } else if let Key::Ctrl('c') = key {
- self.new_session_info.handle_key(key);
- should_render = true;
} else if let Key::BackTab = key {
self.toggle_active_screen();
should_render = true;
+ } else if let Key::Ctrl('f') = key {
+ let request_id = Uuid::new_v4();
+ let mut config = BTreeMap::new();
+ let mut args = BTreeMap::new();
+ self.request_ids.push(request_id.to_string());
+ // we insert this into the config so that a new plugin will be opened (the plugin's
+ // uniqueness is determined by its name/url as well as its config)
+ config.insert("request_id".to_owned(), request_id.to_string());
+ // we also insert this into the args so that the plugin will have an easier access to
+ // it
+ args.insert("request_id".to_owned(), request_id.to_string());
+ pipe_message_to_plugin(
+ MessageToPlugin::new("filepicker")
+ .with_plugin_url("filepicker")
+ .with_plugin_config(config)
+ .new_plugin_instance_should_have_pane_title(
+ "Select folder for the new session...",
+ )
+ .with_args(args),
+ );
+ should_render = true;
+ } else if let Key::Ctrl('c') = key {
+ self.new_session_info.new_session_folder = None;
+ should_render = true;
} else if let Key::Esc = key {
self.new_session_info.handle_key(key);
should_render = true;
diff --git a/default-plugins/session-manager/src/new_session_info.rs b/default-plugins/session-manager/src/new_session_info.rs
index 294f4fbc8..02e539668 100644
--- a/default-plugins/session-manager/src/new_session_info.rs
+++ b/default-plugins/session-manager/src/new_session_info.rs
@@ -1,6 +1,7 @@
use fuzzy_matcher::skim::SkimMatcherV2;
use fuzzy_matcher::FuzzyMatcher;
use std::cmp::Ordering;
+use std::path::PathBuf;
use zellij_tile::prelude::*;
#[derive(Default)]
@@ -8,6 +9,7 @@ pub struct NewSessionInfo {
name: String,
layout_list: LayoutList,
entering_new_session_info: EnteringState,
+ pub new_session_folder: Option<PathBuf>,
}
#[derive(Eq, PartialEq)]
@@ -104,7 +106,8 @@ impl NewSessionInfo {
if new_session_name != current_session_name.as_ref().map(|s| s.as_str()) {
match new_session_layout {
Some(new_session_layout) => {
- switch_session_with_layout(new_session_name, new_session_layout, None)
+ let cwd = self.new_session_folder.as_ref().map(|c| PathBuf::from(c));
+ switch_session_with_layout(new_session_name, new_session_layout, cwd)
},
None => {
switch_session(new_session_name);
diff --git a/default-plugins/session-manager/src/ui/components.rs b/default-plugins/session-manager/src/ui/components.rs
index d3c4afe80..d44fddf44 100644
--- a/default-plugins/session-manager/src/ui/components.rs
+++ b/default-plugins/session-manager/src/ui/components.rs
@@ -1,3 +1,4 @@
+use std::path::PathBuf;
use unicode_width::UnicodeWidthChar;
use unicode_width::UnicodeWidthStr;
use zellij_tile::prelude::*;
@@ -519,9 +520,6 @@ pub fn render_screen_toggle(active_screen: ActiveScreen, x: usize, y: usize, max
let key_indication_len = key_indication_text.chars().count() + 1;
let first_ribbon_length = new_session_text.chars().count() + 4;
let second_ribbon_length = running_sessions_text.chars().count() + 4;
- let third_ribbon_length = exited_sessions_text.chars().count() + 4;
- let total_len =
- key_indication_len + first_ribbon_length + second_ribbon_length + third_ribbon_length;
let key_indication_x = x;
let first_ribbon_x = key_indication_x + key_indication_len;
let second_ribbon_x = first_ribbon_x + first_ribbon_length;
@@ -552,6 +550,140 @@ pub fn render_screen_toggle(active_screen: ActiveScreen, x: usize, y: usize, max
print_ribbon_with_coordinates(exited_sessions_text, third_ribbon_x, y, None, None);
}
+fn render_new_session_folder_prompt(
+ new_session_info: &NewSessionInfo,
+ colors: Colors,
+ x: usize,
+ y: usize,
+ max_cols: usize,
+) {
+ match new_session_info.new_session_folder.as_ref() {
+ Some(new_session_folder) => {
+ let folder_prompt = "New session folder:";
+ let short_folder_prompt = "Folder:";
+ let new_session_path = new_session_folder.clone();
+ let new_session_folder = new_session_folder.display().to_string();
+ let change_folder_shortcut_text = "<Ctrl f>";
+ let change_folder_shortcut = colors.magenta(&change_folder_shortcut_text);
+ let to_change = "to change";
+ let reset_folder_shortcut_text = "<Ctrl c>";
+ let reset_folder_shortcut = colors.magenta(reset_folder_shortcut_text);
+ let to_reset = "to reset";
+ if max_cols
+ >= folder_prompt.width()
+ + new_session_folder.width()
+ + change_folder_shortcut_text.width()
+ + to_change.width()
+ + reset_folder_shortcut_text.width()
+ + to_reset.width()
+ + 8
+ {
+ print!(
+ "\u{1b}[m{}{} {} ({} {}, {} {})",
+ format!("\u{1b}[{};{}H", y + 1, x + 1),
+ colors.green(folder_prompt),
+ colors.orange(&new_session_folder),
+ change_folder_shortcut,
+ to_change,
+ reset_folder_shortcut,
+ to_reset,
+ );
+ } else if max_cols
+ >= short_folder_prompt.width()
+ + new_session_folder.width()
+ + change_folder_shortcut_text.width()
+ + to_change.width()
+ + reset_folder_shortcut_text.width()
+ + to_reset.width()
+ + 8
+ {
+ print!(
+ "\u{1b}[m{}{} {} ({} {}, {} {})",
+ format!("\u{1b}[{};{}H", y + 1, x + 1),
+ colors.green(short_folder_prompt),
+ colors.orange(&new_session_folder),
+ change_folder_shortcut,
+ to_change,
+ reset_folder_shortcut,
+ to_reset,
+ );
+ } else if max_cols
+ >= short_folder_prompt.width()
+ + new_session_folder.width()
+ + change_folder_shortcut_text.width()
+ + reset_folder_shortcut_text.width()
+ + 5
+ {
+ print!(
+ "\u{1b}[m{}{} {} ({}/{})",
+ format!("\u{1b}[{};{}H", y + 1, x + 1),
+ colors.green(short_folder_prompt),
+ colors.orange(&new_session_folder),
+ change_folder_shortcut,
+ reset_folder_shortcut,
+ );
+ } else {
+ let total_len = short_folder_prompt.width()
+ + change_folder_shortcut_text.width()
+ + reset_folder_shortcut_text.width()
+ + 5;
+ let max_path_len = max_cols.saturating_sub(total_len);
+ let truncated_path = truncate_path(
+ new_session_path,
+ new_session_folder.width().saturating_sub(max_path_len),
+ );
+ print!(
+ "\u{1b}[m{}{} {} ({}/{})",
+ format!("\u{1b}[{};{}H", y + 1, x + 1),
+ colors.green(short_folder_prompt),
+ colors.orange(&truncated_path),
+ change_folder_shortcut,
+ reset_folder_shortcut,
+ );
+ }
+ },
+ None => {
+ let folder_prompt = "New session folder:";
+ let short_folder_prompt = "Folder:";
+ let change_folder_shortcut_text = "<Ctrl f>";
+ let change_folder_shortcut = colors.magenta(change_folder_shortcut_text);
+ let to_set = "to set";
+
+ if max_cols
+ >= folder_prompt.width() + change_folder_shortcut_text.width() + to_set.width() + 4
+ {
+ print!(
+ "\u{1b}[m{}{} ({} {})",
+ format!("\u{1b}[{};{}H", y + 1, x + 1),
+ colors.green(folder_prompt),
+ change_folder_shortcut,
+ to_set,
+ );
+ } else if max_cols
+ >= short_folder_prompt.width()
+ + change_folder_shortcut_text.width()
+ + to_set.width()
+ + 4
+ {
+ print!(
+ "\u{1b}[m{}{} ({} {})",
+ format!("\u{1b}[{};{}H", y + 1, x + 1),
+ colors.green(short_folder_prompt),
+ change_folder_shortcut,
+ to_set,
+ );
+ } else {
+ print!(
+ "\u{1b}[m{}{} {}",
+ format!("\u{1b}[{};{}H", y + 1, x + 1),
+ colors.green(short_folder_prompt),
+ change_folder_shortcut,
+ );
+ }
+ },
+ }
+}
+
pub fn render_new_session_block(
new_session_info: &NewSessionInfo,
colors: Colors,
@@ -615,6 +747,7 @@ pub fn render_new_session_block(
}
render_layout_selection_list(
new_session_info,
+ colors,
max_rows_of_new_session_block.saturating_sub(1),
max_cols_of_new_session_block,
x,
@@ -625,6 +758,7 @@ pub fn render_new_session_block(
pub fn render_layout_selection_list(
new_session_info: &NewSessionInfo,
+ colors: Colors,
max_rows_of_new_session_block: usize,
max_cols_of_new_session_block: usize,
x: usize,
@@ -658,7 +792,7 @@ pub fn render_layout_selection_list(
let layout_name = layout_info.name();
let layout_name_len = layout_name.width();
let is_builtin = layout_info.is_builtin();
- if i > max_rows_of_new_session_block {
+ if i > max_rows_of_new_session_block.saturating_sub(1) {
break;
} else {
let mut layout_cell = if is_builtin {
@@ -682,7 +816,15 @@ pub fn render_layout_selection_list(
table = table.add_styled_row(vec![arrow_cell, layout_cell]);
}
}
- print_table_with_coordinates(table, x, y + 3, None, None);
+ let table_y = y + 3;
+ print_table_with_coordinates(table, x, table_y, None, None);
+ render_new_session_folder_prompt(
+ new_session_info,
+ colors,
+ x,
+ (y + max_rows_of_new_session_block).saturating_sub(3),
+ max_cols_of_new_session_block,
+ );
}
pub fn render_error(error_text: &str, rows: usize, columns: usize, x: usize, y: usize) {
@@ -733,10 +875,6 @@ pub fn render_controls_line(
}
},
ActiveScreen::AttachToSession => {
- let arrows = colors.magenta("<←↓↑→>");
- let navigate = colors.bold("Navigate");
- let enter = colors.magenta("<ENTER>");
- let select = colors.bold("Attach");
let rename = colors.magenta("<Ctrl r>");
let rename_text = colors.bold("Rename");
let disconnect = colors.magenta("<Ctrl x>");
@@ -817,3 +955,25 @@ impl Colors {
self.color(&self.palette.magenta, text)
}
}
+
+fn truncate_path(path: PathBuf, mut char_count_to_remove: usize) -> String {
+ let mut truncated = String::new();
+ let component_count = path.iter().count();
+ for (i, component) in path.iter().enumerate() {
+ let mut component_str = component.to_string_lossy().to_string();
+ if char_count_to_remove > 0 {
+ truncated.push(component_str.remove(0));
+ if i != 0 && i + 1 != component_count {
+ truncated.push('/');
+ }
+ char_count_to_remove =
+ char_count_to_remove.saturating_sub(component_str.width().saturating_sub(1));
+ } else {
+ truncated.push_str(&component_str);
+ if i != 0 && i + 1 != component_count {
+ truncated.push('/');
+ }
+ }
+ }
+ truncated
+}
diff --git a/default-plugins/strider/.cargo/config.toml b/default-plugins/strider/.cargo/config.toml
index bc255e30b..6b77899cb 100644
--- a/default-plugins/strider/.cargo/config.toml
+++ b/default-plugins/strider/.cargo/config.toml
@@ -1,2 +1,2 @@
[build]
-target = "wasm32-wasi" \ No newline at end of file
+target = "wasm32-wasi"
diff --git a/default-plugins/strider/src/file_list_view.rs b/default-plugins/strider/src/file_list_view.rs
new file mode 100644
index 000000000..e16c66b3f
--- /dev/null
+++ b/default-plugins/strider/src/file_list_view.rs
@@ -0,0 +1,191 @@
+use crate::shared::{calculate_list_bounds, render_list_tip};
+use crate::state::{refresh_directory, ROOT};
+use pretty_bytes::converter::convert as pretty_bytes;
+use std::collections::HashMap;
+use std::path::PathBuf;
+use unicode_width::UnicodeWidthStr;
+use zellij_tile::prelude::*;
+
+#[derive(Debug, Clone)]
+pub struct FileListView {
+ pub path: PathBuf,
+ pub path_is_dir: bool,
+ pub files: Vec<FsEntry>,
+ pub cursor_hist: HashMap<PathBuf, usize>,
+}
+
+impl Default for FileListView {
+ fn default() -> Self {
+ FileListView {
+ path_is_dir: true,
+ path: Default::default(),
+ files: Default::default(),
+ cursor_hist: Default::default(),
+ }
+ }
+}
+
+impl FileListView {
+ pub fn descend_to_previous_path(&mut self) {
+ self.path.pop();
+ self.path_is_dir = true;
+ self.files.clear();
+ self.reset_selected();
+ refresh_directory(&self.path);
+ }
+ pub fn descend_to_root_path(&mut self) {
+ self.path.clear();
+ self.path_is_dir = true;