diff options
author | Aram Drevekenin <aram@poor.dev> | 2024-03-18 09:19:58 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-03-18 09:19:58 +0100 |
commit | ee16a4b8c347a81878389fd4f90144c2c6357f41 (patch) | |
tree | bf8675d57499b6278e8c89b84c8141969cc2ce3f | |
parent | 12daac3b5445e4281cf5c1810be0ebdb257085c1 (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
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; |