diff options
| author | Aram Drevekenin <aram@poor.dev> | 2025-04-29 20:52:17 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-04-29 20:52:17 +0200 |
| commit | 27c8986939a1642f3e4afda73e9717b7bd328824 (patch) | |
| tree | e80ee57aeede2578a5ce9fa6d0ab4a350ed9d838 /default-plugins/multiple-select/src/main.rs | |
| parent | chore: update development version (diff) | |
| download | sl0ppij-27c8986939a1642f3e4afda73e9717b7bd328824.tar.gz sl0ppij-27c8986939a1642f3e4afda73e9717b7bd328824.tar.xz sl0ppij-27c8986939a1642f3e4afda73e9717b7bd328824.zip | |
feat: multiple Select and Bulk Pane Actions (#4169)
* initial implementation with break panes to new tab
* break pane group left/right
* group embed/eject panes
* stack pane group on resize
* close pane group
* style(fmt): rustfmt
* fix tests
* group drag and ungroup with the mouse
* fix mouse hover for multiple clients
* fix for multiple clients
* multiple select plugin initial
* use real data in plugin
* adjust functionality
* fix some ux issues
* reflect group mouse group selections in plugin
* group/ungroup panes in Zellij
* highlight frames when marked by the plugin
* refactor: render function in plugin
* some ui responsiveness
* some more responsiveness and adjust hover text
* break out functionality
* stack functionality
* break panes left/right and close multiple panes
* fix(tab): only relayout the relevant layout when non-focused pane is closed
* status bar UI
* embed and float panes
* work
* fix some ui/ux issues
* refactor: move stuff around
* some responsiveness and fix search result browsing bug
* change plugin pane title
* differentiate group from focused pane
* add keyboard shortcut
* add ui to compact bar
* make boundary colors appear properly without pane frames
* get plugins to also display their frame color
* make hover shortcuts appear on command panes
* fix: do not render search string component if it's empty
* BeforeClose Event and unhighlight panes on exit
* some UI/UX fixes
* some fixes to the catppuccin-latte theme
* remove ungroup shortcut
* make some ui components opaque
* fix more opaque elements
* fix some issues with stacking pane order
* keyboard shortcuts for grouping
* config to opt out of advanced mouse actions
* make selected + focused frame color distinct
* group marking mode
* refactor: multiple-select plugin
* adjust stacking group behavior
* adjust flashing periods
* render common modifier in group controls
* add to compact bar
* adjust key hint wording
* add key to presets and default config
* some cleanups
* some refactoring
* fix tests
* fix plugin system tests
* tests: group/ungroup/hover
* test: BeforeClose plugin event
* new plugin assets
* style(fmt): rustfmt
* remove warnings
* tests: give plugin more time to load
Diffstat (limited to 'default-plugins/multiple-select/src/main.rs')
| -rw-r--r-- | default-plugins/multiple-select/src/main.rs | 212 |
1 files changed, 212 insertions, 0 deletions
diff --git a/default-plugins/multiple-select/src/main.rs b/default-plugins/multiple-select/src/main.rs new file mode 100644 index 00000000..773fd6fb --- /dev/null +++ b/default-plugins/multiple-select/src/main.rs @@ -0,0 +1,212 @@ +pub mod state; +pub mod ui; + +use state::{MarkedIndex, VisibilityAndFocus}; +use std::collections::BTreeMap; +use ui::PaneItem; +use zellij_tile::prelude::*; + +#[derive(Debug, Default)] +pub struct App { + own_plugin_id: Option<u32>, + own_client_id: Option<ClientId>, + own_tab_index: Option<usize>, + total_tabs_in_session: Option<usize>, + search_string: String, + previous_search_string: String, // used eg. for the new tab title when breaking panes + left_side_panes: Vec<PaneItem>, + right_side_panes: Vec<PaneItem>, + search_results: Option<Vec<PaneItem>>, + visibility_and_focus: VisibilityAndFocus, + marked_index: Option<MarkedIndex>, +} + +register_plugin!(App); + +impl ZellijPlugin for App { + fn load(&mut self, _configuration: BTreeMap<String, String>) { + subscribe(&[ + EventType::Key, + EventType::Mouse, + EventType::ModeUpdate, + EventType::RunCommandResult, + EventType::TabUpdate, + EventType::PaneUpdate, + EventType::FailedToWriteConfigToDisk, + EventType::ConfigWasWrittenToDisk, + EventType::BeforeClose, + ]); + let plugin_ids = get_plugin_ids(); + self.own_plugin_id = Some(plugin_ids.plugin_id); + self.own_client_id = Some(plugin_ids.client_id); + rename_plugin_pane(plugin_ids.plugin_id, "Multiple Select"); + } + fn update(&mut self, event: Event) -> bool { + let mut should_render = false; + match event { + Event::PaneUpdate(pane_manifest) => { + self.react_to_zellij_state_update(pane_manifest); + should_render = true; + }, + Event::Key(key) => { + match key.bare_key { + BareKey::Tab if key.has_no_modifiers() => { + self.visibility_and_focus.toggle_focus(); + self.marked_index = None; + self.update_highlighted_panes(); + should_render = true; + }, + BareKey::Char(character) + if key.has_no_modifiers() + && self.visibility_and_focus.left_side_is_focused() + && self.marked_index.is_none() => + { + self.search_string.push(character); + self.update_search_results(); + should_render = true; + }, + BareKey::Backspace + if key.has_no_modifiers() + && self.visibility_and_focus.left_side_is_focused() + && self.marked_index.is_none() => + { + self.search_string.pop(); + self.update_search_results(); + should_render = true; + }, + BareKey::Enter if key.has_no_modifiers() => { + if self.visibility_and_focus.left_side_is_focused() { + if let Some(marked_index) = self.marked_index.take() { + let keep_left_side_focused = false; + self.group_panes(marked_index, keep_left_side_focused); + } else { + match self.search_results.take() { + Some(search_results) => { + self.group_search_results(search_results) + }, + None => self.group_all_panes(), + } + self.handle_left_side_emptied(); + } + } + should_render = true; + }, + BareKey::Right + if key.has_no_modifiers() + && self.visibility_and_focus.left_side_is_focused() => + { + if let Some(marked_index) = self.marked_index.take() { + let keep_left_side_focused = true; + self.group_panes(marked_index, keep_left_side_focused); + should_render = true; + } + }, + BareKey::Left + if key.has_no_modifiers() + && self.visibility_and_focus.right_side_is_focused() => + { + if self.visibility_and_focus.right_side_is_focused() { + if let Some(marked_index) = self.marked_index.take() { + self.ungroup_panes(marked_index); + should_render = true; + } + } + }, + BareKey::Char('c') if key.has_modifiers(&[KeyModifier::Ctrl]) => { + if self.visibility_and_focus.right_side_is_focused() { + // this means we're in the selection panes part and we want to clear + // them + self.ungroup_all_panes(); + } else if self.visibility_and_focus.left_side_is_focused() { + if self.marked_index.is_some() { + self.marked_index = None; + self.update_highlighted_panes(); + } else { + self.ungroup_all_panes_and_close_self(); + } + } + should_render = true; + }, + BareKey::Down if key.has_no_modifiers() => { + self.move_marked_index_down(); + should_render = true; + }, + BareKey::Up if key.has_no_modifiers() => { + self.move_marked_index_up(); + should_render = true; + }, + BareKey::Char(' ') if key.has_no_modifiers() && self.marked_index.is_some() => { + self.mark_entry(); + should_render = true; + }, + BareKey::Char('b') + if key.has_no_modifiers() + && self.visibility_and_focus.right_side_is_focused() => + { + self.break_grouped_panes_to_new_tab(); + }, + BareKey::Char('s') + if key.has_no_modifiers() + && self.visibility_and_focus.right_side_is_focused() => + { + self.stack_grouped_panes(); + }, + BareKey::Char('f') + if key.has_no_modifiers() + && self.visibility_and_focus.right_side_is_focused() => + { + self.float_grouped_panes(); + }, + BareKey::Char('e') + if key.has_no_modifiers() + && self.visibility_and_focus.right_side_is_focused() => + { + self.embed_grouped_panes(); + }, + BareKey::Char('r') + if key.has_no_modifiers() + && self.visibility_and_focus.right_side_is_focused() => + { + self.break_grouped_panes_right(); + }, + BareKey::Char('l') + if key.has_no_modifiers() + && self.visibility_and_focus.right_side_is_focused() => + { + self.break_grouped_panes_left(); + }, + BareKey::Char('c') + if key.has_no_modifiers() + && self.visibility_and_focus.right_side_is_focused() => + { + self.close_grouped_panes(); + }, + _ => {}, + } + }, + Event::BeforeClose => { + self.unhighlight_all_panes(); + }, + _ => {}, + } + should_render + } + fn render(&mut self, rows: usize, cols: usize) { + self.render_close_shortcut(cols); + self.render_tab_shortcut(cols, rows); + match self.visibility_and_focus { + VisibilityAndFocus::OnlyLeftSideVisible => self.render_left_side(rows, cols, true), + VisibilityAndFocus::OnlyRightSideVisible => self.render_right_side(rows, cols, true), + VisibilityAndFocus::BothSidesVisibleLeftSideFocused => { + self.render_left_side(rows, cols, true); + self.render_right_side(rows, cols, false); + }, + VisibilityAndFocus::BothSidesVisibleRightSideFocused => { + self.render_left_side(rows, cols, false); + self.render_right_side(rows, cols, true); + }, + } + self.render_focus_boundary(rows, cols); + self.render_help_line(rows, cols); + } +} |
