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
This commit is contained in:
Aram Drevekenin 2025-04-29 20:52:17 +02:00 committed by GitHub
parent 905892439f
commit 27c8986939
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
111 changed files with 7853 additions and 958 deletions

8
Cargo.lock generated
View file

@ -2231,6 +2231,14 @@ version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a"
[[package]]
name = "multiple-select"
version = "0.1.0"
dependencies = [
"fuzzy-matcher",
"zellij-tile",
]
[[package]]
name = "names"
version = "0.14.0"

View file

@ -47,6 +47,7 @@ members = [
"default-plugins/configuration",
"default-plugins/plugin-manager",
"default-plugins/about",
"default-plugins/multiple-select",
"zellij-client",
"zellij-server",
"zellij-utils",

View file

@ -2,6 +2,7 @@ use ansi_term::ANSIStrings;
use unicode_width::UnicodeWidthStr;
use crate::{LinePart, ARROW_SEPARATOR};
use zellij_tile::prelude::actions::Action;
use zellij_tile::prelude::*;
use zellij_tile_utils::style;
@ -251,6 +252,8 @@ pub fn tab_line(
mode: InputMode,
active_swap_layout_name: &Option<String>,
is_swap_layout_dirty: bool,
mode_info: &ModeInfo,
grouped_pane_count: Option<usize>,
) -> Vec<LinePart> {
let mut tabs_after_active = all_tabs.split_off(active_tab_index);
let mut tabs_before_active = all_tabs;
@ -286,15 +289,21 @@ pub fn tab_line(
if current_title_len < cols {
let mut remaining_space = cols - current_title_len;
let remaining_bg = palette.text_unselected.background;
if let Some(swap_layout_status) = swap_layout_status(
remaining_space,
active_swap_layout_name,
is_swap_layout_dirty,
mode,
&palette,
tab_separator(capabilities),
) {
remaining_space -= swap_layout_status.len;
let right_side_component = match grouped_pane_count {
Some(grouped_pane_count) => {
render_group_controls(mode_info, grouped_pane_count, remaining_space)
},
None => swap_layout_status(
remaining_space,
active_swap_layout_name,
is_swap_layout_dirty,
mode,
&palette,
tab_separator(capabilities),
),
};
if let Some(right_side_component) = right_side_component {
remaining_space -= right_side_component.len;
let mut buffer = String::new();
for _ in 0..remaining_space {
buffer.push_str(&style!(remaining_bg, remaining_bg).paint(" ").to_string());
@ -304,7 +313,7 @@ pub fn tab_line(
len: remaining_space,
tab_index: None,
});
prefix.push(swap_layout_status);
prefix.push(right_side_component);
}
}
@ -373,3 +382,274 @@ fn swap_layout_status(
None => None,
}
}
fn render_group_controls(
help: &ModeInfo,
grouped_pane_count: usize,
max_len: usize,
) -> Option<LinePart> {
let currently_marking_group = help.currently_marking_pane_group.unwrap_or(false);
let keymap = help.get_mode_keybinds();
let (common_modifiers, multiple_select_key, pane_group_toggle_key, group_mark_toggle_key) = {
let multiple_select_key = multiple_select_key(&keymap);
let pane_group_toggle_key = single_action_key(&keymap, &[Action::TogglePaneInGroup]);
let group_mark_toggle_key = single_action_key(&keymap, &[Action::ToggleGroupMarking]);
let common_modifiers = get_common_modifiers(
vec![
multiple_select_key.iter().next(),
pane_group_toggle_key.iter().next(),
group_mark_toggle_key.iter().next(),
]
.into_iter()
.filter_map(|k| k)
.collect(),
);
let multiple_select_key: Vec<KeyWithModifier> = multiple_select_key
.iter()
.map(|k| k.strip_common_modifiers(&common_modifiers))
.collect();
let pane_group_toggle_key: Vec<KeyWithModifier> = pane_group_toggle_key
.iter()
.map(|k| k.strip_common_modifiers(&common_modifiers))
.collect();
let group_mark_toggle_key: Vec<KeyWithModifier> = group_mark_toggle_key
.iter()
.map(|k| k.strip_common_modifiers(&common_modifiers))
.collect();
(
common_modifiers,
multiple_select_key,
pane_group_toggle_key,
group_mark_toggle_key,
)
};
let multiple_select_key = multiple_select_key
.iter()
.next()
.map(|key| format!("{}", key))
.unwrap_or("UNBOUND".to_owned());
let pane_group_toggle_key = pane_group_toggle_key
.iter()
.next()
.map(|key| format!("{}", key))
.unwrap_or("UNBOUND".to_owned());
let group_mark_toggle_key = group_mark_toggle_key
.iter()
.next()
.map(|key| format!("{}", key))
.unwrap_or("UNBOUND".to_owned());
let background = help.style.colors.text_unselected.background;
let foreground = help.style.colors.text_unselected.base;
let superkey_prefix_style = style!(foreground, background).bold();
let common_modifier_text = if common_modifiers.is_empty() {
"".to_owned()
} else {
format!(
"{} + ",
common_modifiers
.iter()
.map(|c| c.to_string())
.collect::<Vec<_>>()
.join("-")
)
};
// full
let full_selected_panes_text = if common_modifier_text.is_empty() {
format!("{} SELECTED PANES", grouped_pane_count)
} else {
format!("{} SELECTED PANES |", grouped_pane_count)
};
let full_group_actions_text = format!("<{}> Group Actions", &multiple_select_key);
let full_toggle_group_text = format!("<{}> Toggle Group", &pane_group_toggle_key);
let full_group_mark_toggle_text = format!("<{}> Follow Focus", &group_mark_toggle_key);
let ribbon_paddings_len = 12;
let full_controls_line_len = full_selected_panes_text.chars().count()
+ 1
+ common_modifier_text.chars().count()
+ full_group_actions_text.chars().count()
+ full_toggle_group_text.chars().count()
+ full_group_mark_toggle_text.chars().count()
+ ribbon_paddings_len
+ 1; // 1 for the end padding
// medium
let medium_selected_panes_text = if common_modifier_text.is_empty() {
format!("{} SELECTED", grouped_pane_count)
} else {
format!("{} SELECTED |", grouped_pane_count)
};
let medium_group_actions_text = format!("<{}> Actions", &multiple_select_key);
let medium_toggle_group_text = format!("<{}> Toggle", &pane_group_toggle_key);
let medium_group_mark_toggle_text = format!("<{}> Follow", &group_mark_toggle_key);
let ribbon_paddings_len = 12;
let medium_controls_line_len = medium_selected_panes_text.chars().count()
+ 1
+ common_modifier_text.chars().count()
+ medium_group_actions_text.chars().count()
+ medium_toggle_group_text.chars().count()
+ medium_group_mark_toggle_text.chars().count()
+ ribbon_paddings_len
+ 1; // 1 for the end padding
// short
let short_selected_panes_text = if common_modifier_text.is_empty() {
format!("{} SELECTED", grouped_pane_count)
} else {
format!("{} SELECTED |", grouped_pane_count)
};
let short_group_actions_text = format!("<{}>", &multiple_select_key);
let short_toggle_group_text = format!("<{}>", &pane_group_toggle_key);
let short_group_mark_toggle_text = format!("<{}>", &group_mark_toggle_key);
let color_emphasis_range_end = if common_modifier_text.is_empty() {
0
} else {
2
};
let ribbon_paddings_len = 12;
let short_controls_line_len = short_selected_panes_text.chars().count()
+ 1
+ common_modifier_text.chars().count()
+ short_group_actions_text.chars().count()
+ short_toggle_group_text.chars().count()
+ short_group_mark_toggle_text.chars().count()
+ ribbon_paddings_len
+ 1; // 1 for the end padding
let (
selected_panes_text,
group_actions_text,
toggle_group_text,
group_mark_toggle_text,
controls_line_len,
) = if max_len >= full_controls_line_len {
(
full_selected_panes_text,
full_group_actions_text,
full_toggle_group_text,
full_group_mark_toggle_text,
full_controls_line_len,
)
} else if max_len >= medium_controls_line_len {
(
medium_selected_panes_text,
medium_group_actions_text,
medium_toggle_group_text,
medium_group_mark_toggle_text,
medium_controls_line_len,
)
} else if max_len >= short_controls_line_len {
(
short_selected_panes_text,
short_group_actions_text,
short_toggle_group_text,
short_group_mark_toggle_text,
short_controls_line_len,
)
} else {
return None;
};
let selected_panes = serialize_text(
&Text::new(&selected_panes_text)
.color_range(
3,
..selected_panes_text
.chars()
.count()
.saturating_sub(color_emphasis_range_end),
)
.opaque(),
);
let group_actions_ribbon = serialize_ribbon(
&Text::new(&group_actions_text).color_range(0, 1..=multiple_select_key.chars().count()),
);
let toggle_group_ribbon = serialize_ribbon(
&Text::new(&toggle_group_text).color_range(0, 1..=pane_group_toggle_key.chars().count()),
);
let mut group_mark_toggle_ribbon = Text::new(&group_mark_toggle_text)
.color_range(0, 1..=group_mark_toggle_key.chars().count());
if currently_marking_group {
group_mark_toggle_ribbon = group_mark_toggle_ribbon.selected();
}
let group_mark_toggle_ribbon = serialize_ribbon(&group_mark_toggle_ribbon);
let controls_line = if common_modifiers.is_empty() {
format!(
"{} {}{}{}",
selected_panes, group_actions_ribbon, toggle_group_ribbon, group_mark_toggle_ribbon
)
} else {
let common_modifier =
serialize_text(&Text::new(&common_modifier_text).color_range(0, ..).opaque());
format!(
"{} {}{}{}{}",
selected_panes,
common_modifier,
group_actions_ribbon,
toggle_group_ribbon,
group_mark_toggle_ribbon
)
};
let remaining_space = max_len.saturating_sub(controls_line_len);
let mut padding = String::new();
let mut padding_len = 0;
for _ in 0..remaining_space {
padding.push_str(&ANSIStrings(&[superkey_prefix_style.paint(" ")]).to_string());
padding_len += 1;
}
Some(LinePart {
part: format!("{}{}", padding, controls_line),
len: controls_line_len + padding_len,
tab_index: None,
})
}
fn multiple_select_key(keymap: &[(KeyWithModifier, Vec<Action>)]) -> Vec<KeyWithModifier> {
let mut matching = keymap.iter().find_map(|(key, acvec)| {
let has_match = acvec
.iter()
.find(|a| a.launches_plugin("zellij:multiple-select")) // TODO: make this an alias
.is_some();
if has_match {
Some(key.clone())
} else {
None
}
});
if let Some(matching) = matching.take() {
vec![matching]
} else {
vec![]
}
}
fn single_action_key(
keymap: &[(KeyWithModifier, Vec<Action>)],
action: &[Action],
) -> Vec<KeyWithModifier> {
let mut matching = keymap.iter().find_map(|(key, acvec)| {
if acvec.iter().next() == action.iter().next() {
Some(key.clone())
} else {
None
}
});
if let Some(matching) = matching.take() {
vec![matching]
} else {
vec![]
}
}
fn get_common_modifiers(mut keyvec: Vec<&KeyWithModifier>) -> Vec<KeyModifier> {
if keyvec.is_empty() {
return vec![];
}
let mut common_modifiers = keyvec.pop().unwrap().key_modifiers.clone();
for key in keyvec {
common_modifiers = common_modifiers
.intersection(&key.key_modifiers)
.cloned()
.collect();
}
common_modifiers.into_iter().collect()
}

View file

@ -26,6 +26,8 @@ struct State {
tab_line: Vec<LinePart>,
text_copy_destination: Option<CopyDestination>,
display_system_clipboard_failure: bool,
own_client_id: Option<ClientId>,
grouped_panes_count: Option<usize>,
}
static ARROW_SEPARATOR: &str = "";
@ -42,7 +44,9 @@ impl ZellijPlugin for State {
EventType::CopyToClipboard,
EventType::InputReceived,
EventType::SystemClipboardFailure,
EventType::PaneUpdate,
]);
self.own_client_id = Some(get_plugin_ids().client_id);
}
fn update(&mut self, event: Event) -> bool {
@ -108,6 +112,28 @@ impl ZellijPlugin for State {
self.text_copy_destination = None;
self.display_system_clipboard_failure = false;
},
Event::PaneUpdate(pane_manifest) => {
if let Some(own_client_id) = self.own_client_id {
let mut grouped_panes_count = 0;
for (_tab_index, pane_infos) in pane_manifest.panes {
for pane_info in pane_infos {
let is_in_pane_group =
pane_info.index_in_pane_group.get(&own_client_id).is_some();
if is_in_pane_group {
grouped_panes_count += 1;
}
}
}
if Some(grouped_panes_count) != self.grouped_panes_count {
if grouped_panes_count == 0 {
self.grouped_panes_count = None;
} else {
self.grouped_panes_count = Some(grouped_panes_count);
}
should_render = true;
}
}
},
_ => {
eprintln!("Got unrecognized event: {:?}", event);
},
@ -181,6 +207,8 @@ impl ZellijPlugin for State {
self.mode_info.mode,
&active_swap_layout_name,
is_swap_layout_dirty,
&self.mode_info,
self.grouped_panes_count,
);
let output = self
.tab_line

View file

@ -168,6 +168,14 @@ keybinds clear-defaults=true {{
bind "{secondary_modifier} -" {{ Resize "Decrease"; }}
bind "{secondary_modifier} [" {{ PreviousSwapLayout; }}
bind "{secondary_modifier} ]" {{ NextSwapLayout; }}
bind "{secondary_modifier} m" {{
LaunchOrFocusPlugin "zellij:multiple-select" {{
floating true
move_to_focused_tab true
}}
}}
bind "{secondary_modifier} p" {{ TogglePaneInGroup; }}
bind "{secondary_modifier} Shift p" {{ ToggleGroupMarking; }}
}}
shared_except "locked" "renametab" "renamepane" {{
bind "Enter" {{ SwitchToMode "Locked"; }}
@ -392,6 +400,14 @@ keybinds clear-defaults=true {{
bind "{secondary_modifier} -" {{ Resize "Decrease"; }}
bind "{secondary_modifier} [" {{ PreviousSwapLayout; }}
bind "{secondary_modifier} ]" {{ NextSwapLayout; }}
bind "{secondary_modifier} m" {{
LaunchOrFocusPlugin "zellij:multiple-select" {{
floating true
move_to_focused_tab true
}}
}}
bind "{secondary_modifier} p" {{ TogglePaneInGroup; }}
bind "{secondary_modifier} Shift p" {{ ToggleGroupMarking; }}
}}
shared_except "normal" "locked" {{
bind "Enter" "Esc" {{ SwitchToMode "Normal"; }}
@ -595,6 +611,14 @@ keybinds clear-defaults=true {{
bind "{secondary_modifier} -" {{ Resize "Decrease"; }}
bind "{secondary_modifier} [" {{ PreviousSwapLayout; }}
bind "{secondary_modifier} ]" {{ NextSwapLayout; }}
bind "{secondary_modifier} m" {{
LaunchOrFocusPlugin "zellij:multiple-select" {{
floating true
move_to_focused_tab true
}}
}}
bind "{secondary_modifier} p" {{ TogglePaneInGroup; }}
bind "{secondary_modifier} Shift p" {{ ToggleGroupMarking; }}
}}
shared_except "normal" "locked" {{
bind "Enter" "Esc" {{ SwitchToMode "Normal"; }}
@ -1158,6 +1182,14 @@ keybinds clear-defaults=true {{
bind "{secondary_modifier} -" {{ Resize "Decrease"; }}
bind "{secondary_modifier} [" {{ PreviousSwapLayout; }}
bind "{secondary_modifier} ]" {{ NextSwapLayout; }}
bind "{secondary_modifier} m" {{
LaunchOrFocusPlugin "zellij:multiple-select" {{
floating true
move_to_focused_tab true
}}
}}
bind "{secondary_modifier} p" {{ TogglePaneInGroup; }}
bind "{secondary_modifier} Shift p" {{ ToggleGroupMarking; }}
}}
shared_except "normal" "locked" {{
bind "Enter" "Esc" {{ SwitchToMode "Normal"; }}

View file

@ -67,6 +67,7 @@ impl ZellijPlugin for State {
EventType::FileSystemCreate,
EventType::FileSystemUpdate,
EventType::FileSystemDelete,
EventType::BeforeClose,
]);
watch_filesystem();
}
@ -535,6 +536,10 @@ impl ZellijPlugin for State {
self.received_payload = Some(payload.clone());
}
},
Event::BeforeClose => {
// this is just to assert something to make sure this event was triggered
highlight_and_unhighlight_panes(vec![PaneId::Terminal(1)], vec![PaneId::Plugin(1)]);
},
Event::SystemClipboardFailure => {
// this is just to trigger the worker message
post_message_to(PluginMessage {

View file

@ -0,0 +1,2 @@
[build]
target = "wasm32-wasip1"

View file

@ -0,0 +1 @@
/target

View file

@ -0,0 +1,10 @@
[package]
name = "multiple-select"
version = "0.1.0"
authors = ["Aram Drevekenin <aram@poor.dev>"]
edition = "2021"
license = "MIT"
[dependencies]
zellij-tile = { path = "../../zellij-tile" }
fuzzy-matcher = "0.3.7"

View file

@ -0,0 +1 @@
../../LICENSE.md

View file

@ -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);
}
}

View file

@ -0,0 +1,634 @@
use crate::{ui::PaneItem, App};
use std::collections::{BTreeMap, BTreeSet, HashSet};
use fuzzy_matcher::skim::SkimMatcherV2;
use fuzzy_matcher::FuzzyMatcher;
use zellij_tile::prelude::*;
#[derive(Debug, Default)]
pub struct MarkedIndex {
pub main_index: usize,
pub additional_indices: HashSet<usize>,
}
impl MarkedIndex {
pub fn new(main_index: usize) -> Self {
MarkedIndex {
main_index,
additional_indices: HashSet::new(),
}
}
}
impl MarkedIndex {
pub fn toggle_additional_mark(&mut self) {
if self.additional_indices.contains(&self.main_index) {
self.additional_indices.retain(|a| a != &self.main_index);
} else {
self.additional_indices.insert(self.main_index);
}
}
}
#[derive(Debug)]
pub enum VisibilityAndFocus {
OnlyLeftSideVisible,
OnlyRightSideVisible,
BothSidesVisibleLeftSideFocused,
BothSidesVisibleRightSideFocused,
}
impl Default for VisibilityAndFocus {
fn default() -> Self {
VisibilityAndFocus::OnlyLeftSideVisible
}
}
impl VisibilityAndFocus {
pub fn only_left_side_is_focused(&self) -> bool {
match self {
VisibilityAndFocus::OnlyLeftSideVisible => true,
_ => false,
}
}
pub fn left_side_is_focused(&self) -> bool {
match self {
VisibilityAndFocus::OnlyLeftSideVisible
| VisibilityAndFocus::BothSidesVisibleLeftSideFocused => true,
_ => false,
}
}
pub fn right_side_is_focused(&self) -> bool {
match self {
VisibilityAndFocus::OnlyRightSideVisible
| VisibilityAndFocus::BothSidesVisibleRightSideFocused => true,
_ => false,
}
}
pub fn hide_left_side(&mut self) {
*self = VisibilityAndFocus::OnlyRightSideVisible
}
pub fn hide_right_side(&mut self) {
*self = VisibilityAndFocus::OnlyLeftSideVisible
}
pub fn focus_right_side(&mut self) {
*self = VisibilityAndFocus::BothSidesVisibleRightSideFocused
}
pub fn toggle_focus(&mut self) {
match self {
VisibilityAndFocus::BothSidesVisibleLeftSideFocused => {
*self = VisibilityAndFocus::BothSidesVisibleRightSideFocused
},
VisibilityAndFocus::BothSidesVisibleRightSideFocused => {
*self = VisibilityAndFocus::BothSidesVisibleLeftSideFocused
},
VisibilityAndFocus::OnlyLeftSideVisible => {
*self = VisibilityAndFocus::BothSidesVisibleRightSideFocused
},
VisibilityAndFocus::OnlyRightSideVisible => {
*self = VisibilityAndFocus::BothSidesVisibleLeftSideFocused
},
}
}
pub fn show_both_sides(&mut self) {
match self {
VisibilityAndFocus::OnlyLeftSideVisible => {
*self = VisibilityAndFocus::BothSidesVisibleLeftSideFocused
},
VisibilityAndFocus::OnlyRightSideVisible => {
*self = VisibilityAndFocus::BothSidesVisibleRightSideFocused
},
VisibilityAndFocus::BothSidesVisibleLeftSideFocused
| VisibilityAndFocus::BothSidesVisibleRightSideFocused => {
// no-op
},
}
}
}
impl App {
pub fn react_to_zellij_state_update(&mut self, pane_manifest: PaneManifest) {
let is_first_update = self.right_side_panes.is_empty() && self.left_side_panes.is_empty();
let panes_on_the_left_before = self.left_side_panes.len();
let panes_on_the_right_before = self.right_side_panes.len();
self.update_tab_info(&pane_manifest);
self.update_panes(pane_manifest);
if is_first_update && !self.right_side_panes.is_empty() {
// in this case, the plugin was started with an existing group
// most likely, the user wants to perform operations just on this group, so we
// only show the group, giving the option to add more panes
self.visibility_and_focus.hide_left_side();
}
let pane_count_changed = (panes_on_the_left_before != self.left_side_panes.len())
|| (panes_on_the_right_before != self.right_side_panes.len());
if !is_first_update && pane_count_changed {
let has_panes_on_the_right = !self.right_side_panes.is_empty();
let has_panes_on_the_left = !self.left_side_panes.is_empty();
if has_panes_on_the_right && has_panes_on_the_left {
self.visibility_and_focus.show_both_sides();
} else if has_panes_on_the_right {
self.visibility_and_focus.hide_left_side();
} else if has_panes_on_the_left {
self.visibility_and_focus.hide_right_side();
}
}
}
pub fn update_panes(&mut self, pane_manifest: PaneManifest) {
let mut all_panes = BTreeMap::new();
for (_tab_index, pane_infos) in pane_manifest.panes {
for pane_info in pane_infos {
if pane_info.is_selectable {
if pane_info.is_plugin {
all_panes.insert(PaneId::Plugin(pane_info.id), pane_info);
} else {
all_panes.insert(PaneId::Terminal(pane_info.id), pane_info);
}
}
}
}
self.left_side_panes
.retain(|p| all_panes.contains_key(&p.id));
self.right_side_panes
.retain(|p| all_panes.contains_key(&p.id));
let mut new_selected_panes: BTreeMap<usize, PaneItem> = BTreeMap::new(); // usize -> index_in_pane_group
for (pane_id, pane) in all_panes.into_iter() {
let is_known = self
.left_side_panes
.iter()
.find(|p| p.id == pane_id)
.is_some()
|| self
.right_side_panes
.iter()
.find(|p| p.id == pane_id)
.is_some();
let index_in_pane_group = self
.own_client_id
.and_then(|own_client_id| pane.index_in_pane_group.get(&own_client_id));
let is_grouped_for_own_client_id = index_in_pane_group.is_some();
if !is_known {
if is_grouped_for_own_client_id {
if let Some(index_in_pane_group) = index_in_pane_group {
// we do this rather than adding them directly to right_side_panes so that
// we can make sure they're in the same order as the group is so that
// things like stacking order will do the right thing
new_selected_panes.insert(
*index_in_pane_group,
PaneItem {
text: pane.title,
id: pane_id,
color_indices: vec![],
},
);
}
} else {
self.left_side_panes.push(PaneItem {
text: pane.title,
id: pane_id,
color_indices: vec![],
});
}
} else {
if is_grouped_for_own_client_id {
if let Some(position) =
self.left_side_panes.iter().position(|p| p.id == pane_id)
{
// pane was added to a pane group outside the plugin (eg. with mouse selection)
let mut pane = self.left_side_panes.remove(position);
pane.clear();
self.right_side_panes.push(pane);
}
} else {
if let Some(position) =
self.right_side_panes.iter().position(|p| p.id == pane_id)
{
// pane was removed from a pane group outside the plugin (eg. with mouse selection)
let mut pane = self.right_side_panes.remove(position);
pane.clear();
self.left_side_panes.push(pane);
}
}
}
}
for (_index_in_pane_group, pane_item) in new_selected_panes.into_iter() {
self.right_side_panes.push(pane_item);
}
}
pub fn update_tab_info(&mut self, pane_manifest: &PaneManifest) {
for (tab_index, pane_infos) in &pane_manifest.panes {
for pane_info in pane_infos {
if pane_info.is_plugin && Some(pane_info.id) == self.own_plugin_id {
self.own_tab_index = Some(*tab_index);
}
}
}
self.total_tabs_in_session = Some(pane_manifest.panes.keys().count());
}
pub fn update_search_results(&mut self) {
let mut matches = vec![];
let matcher = SkimMatcherV2::default().use_cache(true);
for pane_item in &self.left_side_panes {
if let Some((score, indices)) =
matcher.fuzzy_indices(&pane_item.text, &self.search_string)
{
let mut pane_item = pane_item.clone();
pane_item.color_indices = indices;
matches.push((score, pane_item));
}
}
matches.sort_by(|(a_score, _a), (b_score, _b)| b_score.cmp(&a_score));
if self.search_string.is_empty() {
self.search_results = None;
} else {
self.search_results = Some(
matches
.into_iter()
.map(|(_s, pane_item)| pane_item)
.collect(),
);
}
}
pub fn group_panes_in_zellij(&mut self, pane_ids: Vec<PaneId>) {
group_and_ungroup_panes(pane_ids, vec![]);
}
pub fn ungroup_panes_in_zellij(&mut self, pane_ids: Vec<PaneId>) {
group_and_ungroup_panes(vec![], pane_ids);
}
pub fn update_highlighted_panes(&self) {
let mut pane_ids_to_highlight = vec![];
let mut pane_ids_to_unhighlight = vec![];
if let Some(marked_index) = &self.marked_index {
if self.visibility_and_focus.left_side_is_focused() {
if let Some(main_index_pane_id) = self
.search_results
.as_ref()
.and_then(|s| s.get(marked_index.main_index))
.or_else(|| self.left_side_panes.get(marked_index.main_index))
.map(|p| p.id)
{
pane_ids_to_highlight.push(main_index_pane_id);
}
for index in &marked_index.additional_indices {
if let Some(pane_id) = self
.search_results
.as_ref()
.and_then(|s| s.get(*index))
.or_else(|| self.left_side_panes.get(*index))
.map(|p| p.id)
{
pane_ids_to_highlight.push(pane_id);
}
}
} else {
if let Some(main_index_pane_id) = self
.right_side_panes
.get(marked_index.main_index)
.map(|p| p.id)
{
pane_ids_to_highlight.push(main_index_pane_id);
}
for index in &marked_index.additional_indices {
if let Some(pane_id) = self.right_side_panes.get(*index).map(|p| p.id) {
pane_ids_to_highlight.push(pane_id);
}
}
}
}
for pane in &self.left_side_panes {
if !pane_ids_to_highlight.contains(&pane.id) {
pane_ids_to_unhighlight.push(pane.id);
}
}
for pane in &self.right_side_panes {
if !pane_ids_to_highlight.contains(&pane.id) {
pane_ids_to_unhighlight.push(pane.id);
}
}
highlight_and_unhighlight_panes(pane_ids_to_highlight, pane_ids_to_unhighlight);
}
pub fn unhighlight_all_panes(&mut self) {
let mut pane_ids_to_unhighlight = HashSet::new();
for pane_item in &self.left_side_panes {
pane_ids_to_unhighlight.insert(pane_item.id);
}
for pane_item in &self.right_side_panes {
pane_ids_to_unhighlight.insert(pane_item.id);
}
highlight_and_unhighlight_panes(vec![], pane_ids_to_unhighlight.into_iter().collect());
}
pub fn ungroup_all_panes(&mut self) {
let mut unselected_panes = vec![];
for pane_item in self.right_side_panes.iter_mut() {
pane_item.clear();
unselected_panes.push(pane_item.id);
}
self.left_side_panes.append(&mut self.right_side_panes);
self.ungroup_panes_in_zellij(unselected_panes);
self.visibility_and_focus.hide_right_side();
self.marked_index = None;
}
pub fn ungroup_all_panes_and_close_self(&mut self) {
let mut pane_ids_to_ungroup = HashSet::new();
for pane_item in &self.left_side_panes {
pane_ids_to_ungroup.insert(pane_item.id);
}
for pane_item in &self.right_side_panes {
pane_ids_to_ungroup.insert(pane_item.id);
}
group_and_ungroup_panes(vec![], pane_ids_to_ungroup.into_iter().collect());
close_self();
}
pub fn group_panes(&mut self, mut marked_index: MarkedIndex, keep_left_side_focused: bool) {
let mut all_selected_indices: BTreeSet<usize> =
marked_index.additional_indices.drain().collect();
all_selected_indices.insert(marked_index.main_index);
// reverse so that the indices will remain consistent while
// removing
let mut selected_panes = vec![];
for index in all_selected_indices.iter().rev() {
let index = self
.search_results
.as_mut()
.and_then(|search_results| {
if search_results.len() > *index {
Some(search_results.remove(*index))
} else {
None
}
})
.and_then(|selected_search_result| {
self.left_side_panes
.iter()
.position(|p| p.id == selected_search_result.id)
})
.unwrap_or(*index);
if self.left_side_panes.len() > index {
let selected_pane = self.left_side_panes.remove(index);
selected_panes.push(selected_pane);
}
}
let pane_ids_to_make_selected: Vec<PaneId> = selected_panes.iter().map(|p| p.id).collect();
self.right_side_panes
.append(&mut selected_panes.into_iter().rev().collect());
let displayed_list_len = match self.search_results.as_ref() {
Some(search_results) => search_results.len(),
None => self.left_side_panes.len(),
};
if displayed_list_len == 0 {
self.handle_left_side_emptied();
} else if keep_left_side_focused {
if marked_index.main_index > displayed_list_len.saturating_sub(1) {
self.marked_index = Some(MarkedIndex::new(displayed_list_len.saturating_sub(1)));
} else {
self.marked_index = Some(marked_index);
}
self.visibility_and_focus.show_both_sides();
} else {
self.visibility_and_focus.focus_right_side();
}
self.group_panes_in_zellij(pane_ids_to_make_selected);
self.update_highlighted_panes();
}
pub fn ungroup_panes(&mut self, mut marked_index: MarkedIndex) {
let mut all_selected_indices: BTreeSet<usize> =
marked_index.additional_indices.drain().collect();
all_selected_indices.insert(marked_index.main_index);
// reverse so that the indices will remain consistent while
// removing
let mut selected_panes = vec![];
for index in all_selected_indices.iter().rev() {
if self.right_side_panes.len() > *index {
let mut selected_pane = self.right_side_panes.remove(*index);
selected_pane.clear();
selected_panes.push(selected_pane);
}
}
self.ungroup_panes_in_zellij(selected_panes.iter().map(|p| p.id).collect());
self.left_side_panes
.append(&mut selected_panes.into_iter().rev().collect());
if self.right_side_panes.is_empty() {
self.marked_index = None;
self.visibility_and_focus.hide_right_side();
} else if marked_index.main_index > self.right_side_panes.len().saturating_sub(1) {
self.marked_index = Some(MarkedIndex::new(
self.right_side_panes.len().saturating_sub(1),
));
self.visibility_and_focus.show_both_sides();
} else {
self.marked_index = Some(marked_index);
self.visibility_and_focus.show_both_sides();
}
self.update_highlighted_panes();
}
pub fn group_search_results(&mut self, search_results: Vec<PaneItem>) {
let mut pane_ids_to_make_selected = vec![];
for search_result in search_results {
let pane_id = search_result.id;
pane_ids_to_make_selected.push(pane_id);
self.left_side_panes.retain(|p| p.id != pane_id);
self.right_side_panes.push(search_result);
}
self.group_panes_in_zellij(pane_ids_to_make_selected);
}
pub fn group_all_panes(&mut self) {
let pane_ids_to_make_selected: Vec<PaneId> =
self.left_side_panes.iter().map(|p| p.id).collect();
self.right_side_panes.append(&mut self.left_side_panes);
self.group_panes_in_zellij(pane_ids_to_make_selected);
}
pub fn handle_left_side_emptied(&mut self) {
self.visibility_and_focus.hide_left_side();
self.previous_search_string = self.search_string.drain(..).collect();
self.marked_index = None;
self.search_results = None;
self.update_highlighted_panes();
}
pub fn move_marked_index_down(&mut self) {
match self.marked_index.as_mut() {
Some(marked_index) => {
let is_searching = self.search_results.is_some();
let search_result_count =
self.search_results.as_ref().map(|s| s.len()).unwrap_or(0);
if self.visibility_and_focus.left_side_is_focused()
&& is_searching
&& marked_index.main_index == search_result_count.saturating_sub(1)
{
marked_index.main_index = 0;
} else if self.visibility_and_focus.left_side_is_focused()
&& !is_searching
&& marked_index.main_index == self.left_side_panes.len().saturating_sub(1)
{
marked_index.main_index = 0;
} else if self.visibility_and_focus.right_side_is_focused()
&& marked_index.main_index == self.right_side_panes.len().saturating_sub(1)
{
marked_index.main_index = 0;
} else {
marked_index.main_index += 1
}
},
None => {
if self.visibility_and_focus.left_side_is_focused() {
let is_searching = self.search_results.is_some();
let has_search_results = self
.search_results
.as_ref()
.map(|s| !s.is_empty())
.unwrap_or(false);
if is_searching && has_search_results {
self.marked_index = Some(MarkedIndex::new(0));
} else if !is_searching && !self.left_side_panes.is_empty() {
self.marked_index = Some(MarkedIndex::new(0));
}
} else if self.visibility_and_focus.right_side_is_focused()
&& !self.right_side_panes.is_empty()
{
self.marked_index = Some(MarkedIndex::new(0));
}
},
}
self.update_highlighted_panes();
}
pub fn move_marked_index_up(&mut self) {
match self.marked_index.as_mut() {
Some(marked_index) => {
if self.visibility_and_focus.left_side_is_focused() && marked_index.main_index == 0
{
if let Some(search_result_count) = self.search_results.as_ref().map(|s| s.len())
{
marked_index.main_index = search_result_count.saturating_sub(1);
} else {
marked_index.main_index = self.left_side_panes.len().saturating_sub(1);
}
} else if self.visibility_and_focus.right_side_is_focused()
&& marked_index.main_index == 0
{
marked_index.main_index = self.right_side_panes.len().saturating_sub(1);
} else {
marked_index.main_index = marked_index.main_index.saturating_sub(1);
}
},
None => {
if self.visibility_and_focus.left_side_is_focused() {
let is_searching = self.search_results.is_some();
let has_search_results = self
.search_results
.as_ref()
.map(|s| !s.is_empty())
.unwrap_or(false);
if is_searching && has_search_results {
let search_results_count =
self.search_results.as_ref().map(|s| s.len()).unwrap_or(0);
self.marked_index =
Some(MarkedIndex::new(search_results_count.saturating_sub(1)));
} else if !is_searching && !self.left_side_panes.is_empty() {
self.marked_index = Some(MarkedIndex::new(
self.left_side_panes.len().saturating_sub(1),
));
}
} else if self.visibility_and_focus.right_side_is_focused()
&& !self.right_side_panes.is_empty()
{
self.marked_index = Some(MarkedIndex::new(
self.right_side_panes.len().saturating_sub(1),
));
}
},
}
self.update_highlighted_panes();
}
pub fn mark_entry(&mut self) {
if let Some(marked_index) = self.marked_index.as_mut() {
marked_index.toggle_additional_mark();
self.update_highlighted_panes();
}
}
pub fn break_grouped_panes_to_new_tab(&mut self) {
let pane_ids_to_break_to_new_tab: Vec<PaneId> =
self.right_side_panes.drain(..).map(|p| p.id).collect();
let title_for_new_tab = if !self.previous_search_string.is_empty() {
Some(self.previous_search_string.clone())
} else {
None
};
break_panes_to_new_tab(&pane_ids_to_break_to_new_tab, title_for_new_tab, true);
self.ungroup_panes_in_zellij(pane_ids_to_break_to_new_tab);
close_self();
}
pub fn stack_grouped_panes(&mut self) {
let pane_ids_to_stack: Vec<PaneId> =
self.right_side_panes.drain(..).map(|p| p.id).collect();
stack_panes(pane_ids_to_stack.clone());
self.ungroup_panes_in_zellij(pane_ids_to_stack);
close_self();
}
pub fn float_grouped_panes(&mut self) {
let pane_ids_to_float: Vec<PaneId> =
self.right_side_panes.drain(..).map(|p| p.id).collect();
float_multiple_panes(pane_ids_to_float.clone());
self.ungroup_panes_in_zellij(pane_ids_to_float);
close_self();
}
pub fn embed_grouped_panes(&mut self) {
let pane_ids_to_embed: Vec<PaneId> =
self.right_side_panes.drain(..).map(|p| p.id).collect();
embed_multiple_panes(pane_ids_to_embed.clone());
self.ungroup_panes_in_zellij(pane_ids_to_embed);
close_self();
}
pub fn break_grouped_panes_right(&mut self) {
if let Some(own_tab_index) = self.own_tab_index {
if Some(own_tab_index + 1) < self.total_tabs_in_session {
let pane_ids_to_break_right: Vec<PaneId> =
self.right_side_panes.drain(..).map(|p| p.id).collect();
break_panes_to_tab_with_index(&pane_ids_to_break_right, own_tab_index + 1, true);
} else {
let pane_ids_to_break_to_new_tab: Vec<PaneId> =
self.right_side_panes.drain(..).map(|p| p.id).collect();
let title_for_new_tab = if !self.previous_search_string.is_empty() {
Some(self.previous_search_string.clone())
} else {
None
};
break_panes_to_new_tab(&pane_ids_to_break_to_new_tab, title_for_new_tab, true);
}
close_self();
}
}
pub fn break_grouped_panes_left(&mut self) {
if let Some(own_tab_index) = self.own_tab_index {
if own_tab_index > 0 {
let pane_ids_to_break_left: Vec<PaneId> =
self.right_side_panes.drain(..).map(|p| p.id).collect();
break_panes_to_tab_with_index(
&pane_ids_to_break_left,
own_tab_index.saturating_sub(1),
true,
);
} else {
let pane_ids_to_break_to_new_tab: Vec<PaneId> =
self.right_side_panes.drain(..).map(|p| p.id).collect();
let title_for_new_tab = if !self.previous_search_string.is_empty() {
Some(self.previous_search_string.clone())
} else {
None
};
break_panes_to_new_tab(&pane_ids_to_break_to_new_tab, title_for_new_tab, true);
}
close_self();
}
}
pub fn close_grouped_panes(&mut self) {
let pane_ids_to_close: Vec<PaneId> =
self.right_side_panes.drain(..).map(|p| p.id).collect();
close_multiple_panes(pane_ids_to_close);
close_self();
}
}

View file

@ -0,0 +1,873 @@
use crate::{App, VisibilityAndFocus};
use zellij_tile::prelude::*;
const TOP_LEFT_CORNER_CHARACTER: &'static str = "";
const TOP_RIGHT_CORNER_CHARACTER: &'static str = "";
const BOTTOM_LEFT_CORNER_CHARACTER: &'static str = "";
const BOTTOM_RIGHT_CORNER_CHARACTER: &'static str = "";
const BOUNDARY_CHARACTER: &'static str = "";
const HORIZONTAL_BOUNDARY_CHARACTER: &'static str = "";
#[derive(Debug, Clone)]
pub struct PaneItem {
pub text: String,
pub id: PaneId,
pub color_indices: Vec<usize>,
}
impl PaneItem {
pub fn clear(&mut self) {
self.color_indices.clear();
}
pub fn render(&self, max_width_for_item: usize) -> NestedListItem {
let pane_item_text_len = self.text.chars().count();
if pane_item_text_len <= max_width_for_item {
NestedListItem::new(&self.text)
.color_range(0, ..)
.color_indices(3, self.color_indices.iter().copied().collect())
} else {
let length_of_each_half = max_width_for_item.saturating_sub(3) / 2;
let first_half: String = self.text.chars().take(length_of_each_half).collect();
let second_half: String = self
.text
.chars()
.rev()
.take(length_of_each_half)
.collect::<Vec<_>>()
.iter()
.rev()
.collect();
let second_half_start_index = pane_item_text_len.saturating_sub(length_of_each_half);
let adjusted_indices: Vec<usize> = self
.color_indices
.iter()
.filter_map(|i| {
if i < &length_of_each_half {
Some(*i)
} else if i >= &second_half_start_index {
Some(i.saturating_sub(second_half_start_index) + length_of_each_half + 3)
//3 for the bulletin
} else {
None
}
})
.collect();
NestedListItem::new(format!("{}...{}", first_half, second_half))
.color_range(0, ..)
.color_indices(3, adjusted_indices)
}
}
}
// rendering code
impl App {
pub fn render_close_shortcut(&self, cols: usize) {
let should_render_close_shortcut =
self.visibility_and_focus.left_side_is_focused() && self.marked_index.is_none();
if should_render_close_shortcut {
let x_coordinates_right_padding =
if self.visibility_and_focus.only_left_side_is_focused() {
5
} else {
1
};
let ctrl_c_shortcut_text = "<Ctrl c> - Close";
let ctrl_c_shortcut = Text::new(ctrl_c_shortcut_text).color_range(3, ..=7);
print_text_with_coordinates(
ctrl_c_shortcut,
cols.saturating_sub(ctrl_c_shortcut_text.chars().count())
.saturating_sub(x_coordinates_right_padding),
0,
None,
None,
);
}
}
pub fn render_tab_shortcut(&self, cols: usize, rows: usize) {
match self.visibility_and_focus {
VisibilityAndFocus::BothSidesVisibleRightSideFocused => {
let side_width = self.calculate_side_width(cols);
let tab_shortcut = Text::new("<TAB> - select more panes").color_range(3, ..=4);
print_text_with_coordinates(
tab_shortcut,
side_width + 6,
rows.saturating_sub(2),
None,
None,
);
},
VisibilityAndFocus::BothSidesVisibleLeftSideFocused => {
let side_width = self.calculate_side_width(cols);
let tab_shortcut_text = "<TAB> - browse selected panes";
let tab_shortcut = Text::new(tab_shortcut_text).color_range(3, ..=4);
print_text_with_coordinates(
tab_shortcut,
side_width.saturating_sub(tab_shortcut_text.chars().count() + 1),
rows.saturating_sub(2),
None,
None,
);
},
VisibilityAndFocus::OnlyRightSideVisible => {
let tab_shortcut = Text::new("<TAB> - select more panes").color_range(3, ..=4);
print_text_with_coordinates(tab_shortcut, 4, rows.saturating_sub(2), None, None);
},
VisibilityAndFocus::OnlyLeftSideVisible => {
// not visible
},
};
}
pub fn render_left_side(&self, rows: usize, cols: usize, is_focused: bool) {
let title_y = 0;
let left_side_base_x = 1;
let list_y = 2;
let side_width = self.calculate_side_width(cols);
let max_left_list_height = rows.saturating_sub(8);
let (
extra_pane_count_on_top_left,
extra_pane_count_on_bottom_left,
extra_selected_item_count_on_top_left,
extra_selected_item_count_on_bottom_left,
left_side_panes,
) = self.left_side_panes_list(side_width, max_left_list_height);
let (filter_prompt_text, filter_prompt) = self.filter_panes_prompt();
let filter = self.filter(side_width.saturating_sub(filter_prompt_text.chars().count() + 1));
let (
_enter_select_panes_text,
enter_select_panes,
space_shortcut_text,
space_shortcut,
_escape_shortcut_text,
escape_shortcut,
) = self.left_side_controls(side_width);
print_text_with_coordinates(filter_prompt, left_side_base_x, title_y, None, None);
if is_focused {
print_text_with_coordinates(
filter,
left_side_base_x + filter_prompt_text.chars().count(),
title_y,
None,
None,
);
}
print_nested_list_with_coordinates(
left_side_panes.clone(),
left_side_base_x,
list_y,
Some(side_width),
None,
);
if is_focused {
if let Some(marked_index) = self.marked_index.as_ref().map(|i| i.main_index) {
print_text_with_coordinates(
Text::new(">").color_range(3, ..).selected(),
left_side_base_x + 1,
(list_y + marked_index).saturating_sub(extra_pane_count_on_top_left),
None,
None,
);
}
}
if extra_pane_count_on_top_left > 0 {
self.print_extra_pane_count(
extra_pane_count_on_top_left,
extra_selected_item_count_on_top_left,
list_y.saturating_sub(1),
left_side_base_x,
side_width,
);
}
if extra_pane_count_on_bottom_left > 0 {
self.print_extra_pane_count(
extra_pane_count_on_bottom_left,
extra_selected_item_count_on_bottom_left,
list_y + left_side_panes.len(),
left_side_base_x,
side_width,
);
}
if is_focused && !left_side_panes.is_empty() {
let controls_x = 1;
print_text_with_coordinates(
enter_select_panes,
controls_x,
list_y + left_side_panes.len() + 1,
None,
None,
);
if self.marked_index.is_some() {
print_text_with_coordinates(
space_shortcut.clone(),
controls_x,
list_y + left_side_panes.len() + 2,
None,
None,
);
print_text_with_coordinates(
escape_shortcut.clone(),
controls_x + space_shortcut_text.chars().count() + 1,
list_y + left_side_panes.len() + 2,
None,
None,
);
}
}
}
pub fn render_right_side(&self, rows: usize, cols: usize, is_focused: bool) {
let side_width = self.calculate_side_width(cols);
let right_side_base_x = match self.visibility_and_focus {
VisibilityAndFocus::OnlyLeftSideVisible | VisibilityAndFocus::OnlyRightSideVisible => 1,
VisibilityAndFocus::BothSidesVisibleLeftSideFocused
| VisibilityAndFocus::BothSidesVisibleRightSideFocused => side_width + 4,
};
let title_y = 0;
let list_y: usize = 2;
let max_right_list_height = rows.saturating_sub(11);
let selected_prompt = self.selected_panes_title();
let (
extra_pane_count_on_top_right,
extra_pane_count_on_bottom_right,
extra_selected_item_count_on_top_right,
extra_selected_item_count_on_bottom_right,
right_side_panes,
) = self.right_side_panes_list(side_width, max_right_list_height);
let right_side_pane_count = right_side_panes.len();
let (
right_side_controls_1,
right_side_controls_2,
right_side_controls_3,
right_side_controls_4,
) = self.right_side_controls(side_width);
if extra_pane_count_on_top_right > 0 {
self.print_extra_pane_count(
extra_pane_count_on_top_right,
extra_selected_item_count_on_top_right,
list_y.saturating_sub(1),
right_side_base_x,
side_width,
);
}
if extra_pane_count_on_bottom_right > 0 {
self.print_extra_pane_count(
extra_pane_count_on_bottom_right,
extra_selected_item_count_on_bottom_right,
list_y + right_side_panes.len(),
right_side_base_x,
side_width,
);
}
print_text_with_coordinates(selected_prompt, right_side_base_x + 3, title_y, None, None);
print_nested_list_with_coordinates(
right_side_panes,
right_side_base_x,
list_y,
Some(side_width),
None,
);
if is_focused {
if let Some(marked_index) = self.marked_index.as_ref().map(|i| i.main_index) {
print_text_with_coordinates(
Text::new(">").color_range(3, ..).selected(),
right_side_base_x + 1,
(list_y + marked_index).saturating_sub(extra_pane_count_on_top_right),
None,
None,
);
}
}
if is_focused && !self.right_side_panes.is_empty() {
print_text_with_coordinates(
right_side_controls_1,
right_side_base_x + 1,
list_y + right_side_pane_count + 1,
None,
None,
);
print_text_with_coordinates(
right_side_controls_2,
right_side_base_x + 1,
list_y + right_side_pane_count + 3,
None,
None,
);
print_text_with_coordinates(
right_side_controls_3,
right_side_base_x + 1,
list_y + right_side_pane_count + 4,
None,
None,
);
print_text_with_coordinates(
right_side_controls_4,
right_side_base_x + 1,
list_y + right_side_pane_count + 5,
None,
None,
);
}
}
pub fn render_help_line(&self, rows: usize, cols: usize) {
let help_line_text = match self.visibility_and_focus {
VisibilityAndFocus::OnlyLeftSideVisible => {
let full_help_line = "Help: Select one or more panes to group for bulk operations";
let short_help_line = "Help: Select panes to group";
if cols >= full_help_line.chars().count() {
full_help_line
} else {
short_help_line
}
},
VisibilityAndFocus::OnlyRightSideVisible => {
let full_help_line = "Help: Perform bulk operations on all selected panes";
let short_help_line = "Help: Perform bulk operations";
if cols >= full_help_line.chars().count() {
full_help_line
} else {
short_help_line
}
},
_ => {
let full_help_line =
"Help: Select panes on the left, then perform operations on the right.";
let short_help_line = "Help: Group panes for bulk operations";
if cols >= full_help_line.chars().count() {
full_help_line
} else {
short_help_line
}
},
};
let help_line = Text::new(help_line_text);
print_text_with_coordinates(help_line, 0, rows, None, None);
}
pub fn render_focus_boundary(&self, rows: usize, cols: usize) {
let side_width = self.calculate_side_width(cols);
let x = match self.visibility_and_focus {
VisibilityAndFocus::OnlyRightSideVisible => 0,
VisibilityAndFocus::BothSidesVisibleLeftSideFocused
| VisibilityAndFocus::BothSidesVisibleRightSideFocused
| VisibilityAndFocus::OnlyLeftSideVisible => side_width + 2,
};
let y = 0;
let height = rows.saturating_sub(2);
for i in y..=height {
if i == y && self.visibility_and_focus.left_side_is_focused() {
print_text_with_coordinates(
Text::new(TOP_RIGHT_CORNER_CHARACTER),
x,
i,
None,
None,
);
print_text_with_coordinates(
Text::new(HORIZONTAL_BOUNDARY_CHARACTER),
x.saturating_sub(1),
i,
None,
None,
);
print_text_with_coordinates(
Text::new(HORIZONTAL_BOUNDARY_CHARACTER),
x.saturating_sub(2),
i,
None,
None,
);
} else if i == y && !self.visibility_and_focus.left_side_is_focused() {
print_text_with_coordinates(Text::new(TOP_LEFT_CORNER_CHARACTER), x, i, None, None);
print_text_with_coordinates(
Text::new(HORIZONTAL_BOUNDARY_CHARACTER),
x + 1,
i,
None,
None,
);
print_text_with_coordinates(
Text::new(HORIZONTAL_BOUNDARY_CHARACTER),
x + 2,
i,
None,
None,
);
} else if i == height && self.visibility_and_focus.left_side_is_focused() {
print_text_with_coordinates(
Text::new(BOTTOM_RIGHT_CORNER_CHARACTER),
x,
i,
None,
None,
);
print_text_with_coordinates(
Text::new(HORIZONTAL_BOUNDARY_CHARACTER),
x.saturating_sub(1),
i,
None,
None,
);
print_text_with_coordinates(
Text::new(HORIZONTAL_BOUNDARY_CHARACTER),
x.saturating_sub(2),
i,
None,
None,
);
} else if i == height && !self.visibility_and_focus.left_side_is_focused() {
print_text_with_coordinates(
Text::new(BOTTOM_LEFT_CORNER_CHARACTER),
x,
i,
None,
None,
);
print_text_with_coordinates(
Text::new(HORIZONTAL_BOUNDARY_CHARACTER),
x + 1,
i,
None,
None,
);
print_text_with_coordinates(
Text::new(HORIZONTAL_BOUNDARY_CHARACTER),
x + 2,
i,
None,
None,
);
} else {
print_text_with_coordinates(Text::new(BOUNDARY_CHARACTER), x, i, None, None);
}
}
}
pub fn calculate_side_width(&self, cols: usize) -> usize {
match self.visibility_and_focus {
VisibilityAndFocus::OnlyLeftSideVisible | VisibilityAndFocus::OnlyRightSideVisible => {
cols.saturating_sub(4)
},
VisibilityAndFocus::BothSidesVisibleLeftSideFocused
| VisibilityAndFocus::BothSidesVisibleRightSideFocused => (cols / 2).saturating_sub(3),
}
}
}
// ui components
impl App {
fn filter_panes_prompt(&self) -> (&'static str, Text) {
let search_prompt_text = if self.search_string.is_empty() {
"ALL PANES "
} else {
"FILTER: "
};
let search_prompt = if self.visibility_and_focus.left_side_is_focused() {
Text::new(&search_prompt_text).color_range(2, ..)
} else {
Text::new(&search_prompt_text)
};
(search_prompt_text, search_prompt)
}
fn filter(&self, max_width: usize) -> Text {
let search_string_text = if self.marked_index.is_none() && self.search_string.is_empty() {
let full = "[Type filter term...]";
let short = "[...]";
if max_width >= full.chars().count() {
full.to_owned()
} else {
short.to_owned()
}
} else if self.marked_index.is_none() && !self.search_string.is_empty() {
if max_width >= self.search_string.chars().count() + 1 {
format!("{}_", self.search_string)
} else {
let truncated: String = self
.search_string
.chars()
.rev()
.take(max_width.saturating_sub(4))
.collect::<Vec<_>>()
.iter()
.rev()
.collect();
format!("...{}_", truncated)
}
} else if self.marked_index.is_some() && !self.search_string.is_empty() {
if max_width >= self.search_string.chars().count() {
format!("{}", self.search_string)
} else {
let truncated: String = self
.search_string
.chars()
.rev()
.take(max_width.saturating_sub(4))
.collect::<Vec<_>>()
.iter()
.rev()
.collect();
format!("...{}", truncated)
}
} else {
format!("")
};
Text::new(&search_string_text).color_range(3, ..)
}
fn left_side_panes_list(
&self,
max_width: usize,
max_list_height: usize,
) -> (usize, usize, usize, usize, Vec<NestedListItem>) {
// returns: extra_pane_count_on_top, extra_pane_count_on_bottom,
// extra_selected_item_count_on_top, extra_selected_item_count_on_bottom, list
let mut left_side_panes = vec![];
let pane_items_on_the_left = self
.search_results
.as_ref()
.unwrap_or_else(|| &self.left_side_panes);
let max_width_for_item = max_width.saturating_sub(3); // 3 for the list bulletin
let item_count = pane_items_on_the_left.iter().count();
let first_item_index = if self.visibility_and_focus.left_side_is_focused() {
self.marked_index
.as_ref()
.map(|s| s.main_index.saturating_sub(max_list_height / 2))
.unwrap_or(0)
} else {
0
};
let last_item_index = std::cmp::min(
(max_list_height + first_item_index).saturating_sub(1),
item_count.saturating_sub(1),
);
for (i, pane_item) in pane_items_on_the_left
.iter()
.enumerate()
.skip(first_item_index)
{
if i > last_item_index {
break;
}
let mut item = pane_item.render(max_width_for_item);
if Some(i) == self.marked_index.as_ref().map(|s| s.main_index)
&& self.visibility_and_focus.left_side_is_focused()
{
item = item.selected();
if self
.marked_index
.as_ref()
.map(|s| s.additional_indices.contains(&i))
.unwrap_or(false)
{
item = item.selected().color_range(1, ..);
}
} else if self
.marked_index
.as_ref()
.map(|s| s.additional_indices.contains(&i))
.unwrap_or(false)
&& self.visibility_and_focus.left_side_is_focused()
{
item = item.selected();
}
left_side_panes.push(item);
}
let extra_panes_on_top = first_item_index;
let extra_panes_on_bottom = item_count.saturating_sub(last_item_index + 1);
let extra_selected_item_count_on_top = if self.visibility_and_focus.left_side_is_focused() {
self.marked_index
.as_ref()
.map(|s| {
s.additional_indices
.iter()
.filter(|a| a < &&first_item_index)
.count()
})
.unwrap_or(0)
} else {
0
};
let extra_selected_item_count_on_bottom =
if self.visibility_and_focus.left_side_is_focused() {
self.marked_index
.as_ref()
.map(|s| {
s.additional_indices
.iter()
.filter(|a| a > &&last_item_index)
.count()
})
.unwrap_or(0)
} else {
0
};
(
extra_panes_on_top,
extra_panes_on_bottom,
extra_selected_item_count_on_top,
extra_selected_item_count_on_bottom,
left_side_panes,
)
}
fn left_side_controls(
&self,
max_width: usize,
) -> (&'static str, Text, &'static str, Text, &'static str, Text) {
// returns three components and their text
let (enter_select_panes_text, enter_select_panes) = if self.marked_index.is_some() {
let enter_select_panes_text_full = "<ENTER> - select, <↓↑→> - navigate";
let enter_select_panes_text_short = "<ENTER> / <↓↑→>...";
if max_width >= enter_select_panes_text_full.chars().count() {
let enter_select_panes_full = Text::new(enter_select_panes_text_full)
.color_range(3, ..=6)
.color_range(3, 18..=22);
(enter_select_panes_text_full, enter_select_panes_full)
} else {
let enter_select_panes_short = Text::new(enter_select_panes_text_short)
.color_range(3, ..=6)
.color_range(3, 10..=14);
(enter_select_panes_text_short, enter_select_panes_short)
}
} else {
let enter_select_panes_text_full = "<ENTER> - select all, <↓↑> - navigate";
let enter_select_panes_text_short = "<ENTER> / <↓↑>...";
if max_width >= enter_select_panes_text_full.chars().count() {
let enter_select_panes_full = Text::new(enter_select_panes_text_full)
.color_range(3, ..=6)
.color_range(3, 21..=25);
(enter_select_panes_text_full, enter_select_panes_full)
} else {
let enter_select_panes_short = Text::new(enter_select_panes_text_short)
.color_range(3, ..=6)
.color_range(3, 10..=13);
(enter_select_panes_text_short, enter_select_panes_short)
}
};
let space_shortcut_text_full = "<SPACE> - mark many,";
let space_shortcut_text_short = "<SPACE> /";
if self.marked_index.is_some() {
let escape_shortcut_text_full = "<Ctrl c> - remove marks";
let escape_shortcut_text_short = "<Ctrl c>...";
let (escape_shortcut, space_shortcut, escape_shortcut_text, space_shortcut_text) =
if max_width
>= space_shortcut_text_full.chars().count()
+ escape_shortcut_text_full.chars().count()
{
(
Text::new(escape_shortcut_text_full).color_range(3, ..=7),
Text::new(space_shortcut_text_full).color_range(3, ..=6),
escape_shortcut_text_full,
space_shortcut_text_full,
)
} else {
(
Text::new(escape_shortcut_text_short).color_range(3, ..=7),
Text::new(space_shortcut_text_short).color_range(3, ..=6),
escape_shortcut_text_short,
space_shortcut_text_short,
)
};
(
enter_select_panes_text,
enter_select_panes,
space_shortcut_text,
space_shortcut,
escape_shortcut_text,
escape_shortcut,
)
} else {
let escape_shortcut_text = if self.right_side_panes.is_empty() {
"<Ctrl c> - Close"
} else {
""
};
let escape_shortcut = Text::new(escape_shortcut_text).color_range(3, ..=7);
let space_shortcut = Text::new(space_shortcut_text_full).color_range(3, ..=6);
(
enter_select_panes_text,
enter_select_panes,
space_shortcut_text_full,
space_shortcut,
escape_shortcut_text,
escape_shortcut,
)
}
}
fn selected_panes_title(&self) -> Text {
let selected_prompt_text = "SELECTED PANES: ";
let selected_prompt = if self.visibility_and_focus.left_side_is_focused() {
Text::new(selected_prompt_text)
} else {
Text::new(selected_prompt_text).color_range(2, ..)
};
selected_prompt
}
fn right_side_panes_list(
&self,
max_width: usize,
max_list_height: usize,
) -> (usize, usize, usize, usize, Vec<NestedListItem>) {
// returns: extra_pane_count_on_top, extra_pane_count_on_bottom,
// extra_selected_item_count_on_top, extra_selected_item_count_on_bottom, list
let mut right_side_panes = vec![];
let item_count = self.right_side_panes.iter().count();
let first_item_index = if self.visibility_and_focus.left_side_is_focused() {
0
} else {
self.marked_index
.as_ref()
.map(|s| s.main_index.saturating_sub(max_list_height / 2))
.unwrap_or(0)
};
let last_item_index = std::cmp::min(
(max_list_height + first_item_index).saturating_sub(1),
item_count.saturating_sub(1),
);
let max_width_for_item = max_width.saturating_sub(3); // 3 for the list bulletin
for (i, pane_item) in self
.right_side_panes
.iter()
.enumerate()
.skip(first_item_index)
{
if i > last_item_index {
break;
}
let mut item = pane_item.render(max_width_for_item);
if &Some(i) == &self.marked_index.as_ref().map(|s| s.main_index)
&& self.visibility_and_focus.right_side_is_focused()
{
item = item.selected();
if self
.marked_index
.as_ref()
.map(|s| s.additional_indices.contains(&i))
.unwrap_or(false)
{
item = item.selected().color_range(1, ..);
}
} else if self
.marked_index
.as_ref()
.map(|s| s.additional_indices.contains(&i))
.unwrap_or(false)
&& self.visibility_and_focus.right_side_is_focused()
{
item = item.selected();
}
right_side_panes.push(item);
}
let extra_panes_on_top = first_item_index;
let extra_panes_on_bottom = self
.right_side_panes
.iter()
.len()
.saturating_sub(last_item_index + 1);
let extra_selected_item_count_on_top = if self.visibility_and_focus.left_side_is_focused() {
0
} else {
self.marked_index
.as_ref()
.map(|s| {
s.additional_indices
.iter()
.filter(|a| a < &&first_item_index)
.count()
})
.unwrap_or(0)
};
let extra_selected_item_count_on_bottom =
if self.visibility_and_focus.left_side_is_focused() {
0
} else {
self.marked_index
.as_ref()
.map(|s| {
s.additional_indices
.iter()
.filter(|a| a > &&last_item_index)
.count()
})
.unwrap_or(0)
};
(
extra_panes_on_top,
extra_panes_on_bottom,
extra_selected_item_count_on_top,
extra_selected_item_count_on_bottom,
right_side_panes,
)
}
fn right_side_controls(&self, cols: usize) -> (Text, Text, Text, Text) {
let right_side_controls_text_1_full = "<←↓↑> - navigate, <Ctrl c> - clear";
let right_side_controls_text_1_short = "<←↓↑>/<Ctrl c>...";
let right_side_controls_1 = if cols >= right_side_controls_text_1_full.chars().count() {
Text::new(right_side_controls_text_1_full)
.color_range(3, ..=4)
.color_range(3, 18..=25)
} else {
Text::new(right_side_controls_text_1_short)
.color_range(3, ..=4)
.color_range(3, 6..=13)
};
let right_side_controls_text_2_full = "<b> - break out, <s> - stack, <c> - close";
let right_side_controls_text_2_short = "<b>/<s>/<c>...";
let right_side_controls_2 = if cols >= right_side_controls_text_2_full.chars().count() {
Text::new(right_side_controls_text_2_full)
.color_range(3, ..=2)
.color_range(3, 17..=19)
.color_range(3, 30..=32)
} else {
Text::new(right_side_controls_text_2_short)
.color_range(3, ..=2)
.color_range(3, 4..=6)
.color_range(3, 8..=10)
};
let right_side_controls_text_3_full = "<r> - break right, <l> - break left";
let right_side_controls_text_3_short = "<r>/<l>...";
let right_side_controls_3 = if cols >= right_side_controls_text_3_full.chars().count() {
Text::new(right_side_controls_text_3_full)
.color_range(3, ..=2)
.color_range(3, 19..=21)
} else {
Text::new(right_side_controls_text_3_short)
.color_range(3, ..=2)
.color_range(3, 4..=6)
};
let right_side_controls_text_4_full = "<e> - embed, <f> - float";
let right_side_controls_text_4_short = "<e>/<f>...";
let right_side_controls_4 = if cols >= right_side_controls_text_4_full.chars().count() {
Text::new(right_side_controls_text_4_full)
.color_range(3, ..=2)
.color_range(3, 13..=15)
} else {
Text::new(right_side_controls_text_4_short)
.color_range(3, ..=2)
.color_range(3, 4..=6)
};
(
right_side_controls_1,
right_side_controls_2,
right_side_controls_3,
right_side_controls_4,
)
}
fn print_extra_pane_count(
&self,
count: usize,
selected_count: usize,
y: usize,
list_x: usize,
list_width: usize,
) {
let extra_count_text = if selected_count > 0 {
format!("[+{} ({} selected)]", count, selected_count)
} else {
format!("[+{}]", count)
};
let extra_count = Text::new(&extra_count_text).color_range(1, ..);
print_text_with_coordinates(
extra_count,
(list_x + list_width).saturating_sub(extra_count_text.chars().count()),
y,
None,
None,
);
}
}

View file

@ -39,6 +39,8 @@ struct State {
display_system_clipboard_failure: bool,
classic_ui: bool,
base_mode_is_locked: bool,
own_client_id: Option<ClientId>,
grouped_panes_count: Option<usize>,
}
register_plugin!(State);
@ -197,10 +199,12 @@ impl ZellijPlugin for State {
.get("classic")
.map(|c| c == "true")
.unwrap_or(false);
self.own_client_id = Some(get_plugin_ids().client_id);
set_selectable(false);
subscribe(&[
EventType::ModeUpdate,
EventType::TabUpdate,
EventType::PaneUpdate,
EventType::CopyToClipboard,
EventType::InputReceived,
EventType::SystemClipboardFailure,
@ -223,6 +227,28 @@ impl ZellijPlugin for State {
}
self.tabs = tabs;
},
Event::PaneUpdate(pane_manifest) => {
if let Some(own_client_id) = self.own_client_id {
let mut grouped_panes_count = 0;
for (_tab_index, pane_infos) in pane_manifest.panes {
for pane_info in pane_infos {
let is_in_pane_group =
pane_info.index_in_pane_group.get(&own_client_id).is_some();
if is_in_pane_group {
grouped_panes_count += 1;
}
}
}
if Some(grouped_panes_count) != self.grouped_panes_count {
if grouped_panes_count == 0 {
self.grouped_panes_count = None;
} else {
self.grouped_panes_count = Some(grouped_panes_count);
}
should_render = true;
}
}
},
Event::CopyToClipboard(copy_destination) => {
match self.text_copy_destination {
Some(text_copy_destination) => {
@ -280,6 +306,7 @@ impl ZellijPlugin for State {
self.base_mode_is_locked,
self.text_copy_destination,
self.display_system_clipboard_failure,
self.grouped_panes_count,
),
fill_bg,
);

View file

@ -6,7 +6,7 @@ use ansi_term::{
use std::collections::HashMap;
use zellij_tile::prelude::actions::Action;
use zellij_tile::prelude::*;
use zellij_tile_utils::palette_match;
use zellij_tile_utils::{palette_match, style};
use crate::first_line::{to_char, KeyAction, KeyMode, KeyShortcut};
use crate::second_line::{system_clipboard_error, text_copied_hint};
@ -22,6 +22,7 @@ pub fn one_line_ui(
base_mode_is_locked: bool,
text_copied_to_clipboard_destination: Option<CopyDestination>,
clipboard_failure: bool,
grouped_pane_count: Option<usize>,
) -> LinePart {
if let Some(text_copied_to_clipboard_destination) = text_copied_to_clipboard_destination {
return text_copied_hint(text_copied_to_clipboard_destination);
@ -35,11 +36,18 @@ pub fn one_line_ui(
*max_len = max_len.saturating_sub(line_part.len);
};
let currently_marking_pane_group = help.currently_marking_pane_group.unwrap_or(false);
render_mode_key_indicators(help, max_len, separator, base_mode_is_locked)
.map(|mode_key_indicators| append(&mode_key_indicators, &mut max_len))
.and_then(|_| match help.mode {
InputMode::Normal | InputMode::Locked => render_secondary_info(help, tab_info, max_len)
.map(|secondary_info| append(&secondary_info, &mut max_len)),
InputMode::Normal | InputMode::Locked => match grouped_pane_count {
Some(grouped_pane_count) => {
render_group_controls(help, grouped_pane_count, max_len)
},
None if currently_marking_pane_group => render_group_controls(help, 0, max_len),
None => render_secondary_info(help, tab_info, max_len),
}
.map(|secondary_info| append(&secondary_info, &mut max_len)),
_ => add_keygroup_separator(help, max_len)
.map(|key_group_separator| append(&key_group_separator, &mut max_len))
.and_then(|_| keybinds(help, max_len))
@ -656,6 +664,225 @@ fn render_common_modifiers(
line_part_to_render.len += prefix_text.chars().count() + separator.chars().count();
}
fn render_group_controls(
help: &ModeInfo,
grouped_pane_count: usize,
max_len: usize,
) -> Option<LinePart> {
let currently_marking_group = help.currently_marking_pane_group.unwrap_or(false);
let keymap = help.get_mode_keybinds();
let (common_modifiers, multiple_select_key, pane_group_toggle_key, group_mark_toggle_key) = {
let multiple_select_key = multiple_select_key(&keymap);
let pane_group_toggle_key = single_action_key(&keymap, &[Action::TogglePaneInGroup]);
let group_mark_toggle_key = single_action_key(&keymap, &[Action::ToggleGroupMarking]);
let common_modifiers = get_common_modifiers(
vec![
multiple_select_key.iter().next(),
pane_group_toggle_key.iter().next(),
group_mark_toggle_key.iter().next(),
]
.into_iter()
.filter_map(|k| k)
.collect(),
);
let multiple_select_key: Vec<KeyWithModifier> = multiple_select_key
.iter()
.map(|k| k.strip_common_modifiers(&common_modifiers))
.collect();
let pane_group_toggle_key: Vec<KeyWithModifier> = pane_group_toggle_key
.iter()
.map(|k| k.strip_common_modifiers(&common_modifiers))
.collect();
let group_mark_toggle_key: Vec<KeyWithModifier> = group_mark_toggle_key
.iter()
.map(|k| k.strip_common_modifiers(&common_modifiers))
.collect();
(
common_modifiers,
multiple_select_key,
pane_group_toggle_key,
group_mark_toggle_key,
)
};
let multiple_select_key = multiple_select_key
.iter()
.next()
.map(|key| format!("{}", key))
.unwrap_or("UNBOUND".to_owned());
let pane_group_toggle_key = pane_group_toggle_key
.iter()
.next()
.map(|key| format!("{}", key))
.unwrap_or("UNBOUND".to_owned());
let group_mark_toggle_key = group_mark_toggle_key
.iter()
.next()
.map(|key| format!("{}", key))
.unwrap_or("UNBOUND".to_owned());
let background = help.style.colors.text_unselected.background;
let foreground = help.style.colors.text_unselected.base;
let superkey_prefix_style = style!(foreground, background).bold();
let common_modifier_text = if common_modifiers.is_empty() {
"".to_owned()
} else {
format!(
"{} + ",
common_modifiers
.iter()
.map(|c| c.to_string())
.collect::<Vec<_>>()
.join("-")
)
};
// full
let full_selected_panes_text = if common_modifier_text.is_empty() {
format!("{} SELECTED PANES", grouped_pane_count)
} else {
format!("{} SELECTED PANES |", grouped_pane_count)
};
let full_group_actions_text = format!("<{}> Group Actions", &multiple_select_key);
let full_toggle_group_text = format!("<{}> Toggle Group", &pane_group_toggle_key);
let full_group_mark_toggle_text = format!("<{}> Follow Focus", &group_mark_toggle_key);
let ribbon_paddings_len = 12;
let full_controls_line_len = full_selected_panes_text.chars().count()
+ 1
+ common_modifier_text.chars().count()
+ full_group_actions_text.chars().count()
+ full_toggle_group_text.chars().count()
+ full_group_mark_toggle_text.chars().count()
+ ribbon_paddings_len
+ 1; // 1 for the end padding
// medium
let medium_selected_panes_text = if common_modifier_text.is_empty() {
format!("{} SELECTED", grouped_pane_count)
} else {
format!("{} SELECTED |", grouped_pane_count)
};
let medium_group_actions_text = format!("<{}> Actions", &multiple_select_key);
let medium_toggle_group_text = format!("<{}> Toggle", &pane_group_toggle_key);
let medium_group_mark_toggle_text = format!("<{}> Follow", &group_mark_toggle_key);
let ribbon_paddings_len = 12;
let medium_controls_line_len = medium_selected_panes_text.chars().count()
+ 1
+ common_modifier_text.chars().count()
+ medium_group_actions_text.chars().count()
+ medium_toggle_group_text.chars().count()
+ medium_group_mark_toggle_text.chars().count()
+ ribbon_paddings_len
+ 1; // 1 for the end padding
// short
let short_selected_panes_text = if common_modifier_text.is_empty() {
format!("{} SELECTED", grouped_pane_count)
} else {
format!("{} SELECTED |", grouped_pane_count)
};
let short_group_actions_text = format!("<{}>", &multiple_select_key);
let short_toggle_group_text = format!("<{}>", &pane_group_toggle_key);
let short_group_mark_toggle_text = format!("<{}>", &group_mark_toggle_key);
let color_emphasis_range_end = if common_modifier_text.is_empty() {
0
} else {
2
};
let ribbon_paddings_len = 12;
let short_controls_line_len = short_selected_panes_text.chars().count()
+ 1
+ common_modifier_text.chars().count()
+ short_group_actions_text.chars().count()
+ short_toggle_group_text.chars().count()
+ short_group_mark_toggle_text.chars().count()
+ ribbon_paddings_len
+ 1; // 1 for the end padding
let (
selected_panes_text,
group_actions_text,
toggle_group_text,
group_mark_toggle_text,
controls_line_len,
) = if max_len >= full_controls_line_len {
(
full_selected_panes_text,
full_group_actions_text,
full_toggle_group_text,
full_group_mark_toggle_text,
full_controls_line_len,
)
} else if max_len >= medium_controls_line_len {
(
medium_selected_panes_text,
medium_group_actions_text,
medium_toggle_group_text,
medium_group_mark_toggle_text,
medium_controls_line_len,
)
} else if max_len >= short_controls_line_len {
(
short_selected_panes_text,
short_group_actions_text,
short_toggle_group_text,
short_group_mark_toggle_text,
short_controls_line_len,
)
} else {
return None;
};
let selected_panes = serialize_text(
&Text::new(&selected_panes_text)
.color_range(
3,
..selected_panes_text
.chars()
.count()
.saturating_sub(color_emphasis_range_end),
)
.opaque(),
);
let group_actions_ribbon = serialize_ribbon(
&Text::new(&group_actions_text).color_range(0, 1..=multiple_select_key.chars().count()),
);
let toggle_group_ribbon = serialize_ribbon(
&Text::new(&toggle_group_text).color_range(0, 1..=pane_group_toggle_key.chars().count()),
);
let mut group_mark_toggle_ribbon = Text::new(&group_mark_toggle_text)
.color_range(0, 1..=group_mark_toggle_key.chars().count());
if currently_marking_group {
group_mark_toggle_ribbon = group_mark_toggle_ribbon.selected();
}
let group_mark_toggle_ribbon = serialize_ribbon(&group_mark_toggle_ribbon);
let controls_line = if common_modifiers.is_empty() {
format!(
"{} {}{}{}",
selected_panes, group_actions_ribbon, toggle_group_ribbon, group_mark_toggle_ribbon
)
} else {
let common_modifier =
serialize_text(&Text::new(&common_modifier_text).color_range(0, ..).opaque());
format!(
"{} {}{}{}{}",
selected_panes,
common_modifier,
group_actions_ribbon,
toggle_group_ribbon,
group_mark_toggle_ribbon
)
};
let remaining_space = max_len.saturating_sub(controls_line_len);
let mut padding = String::new();
let mut padding_len = 0;
for _ in 0..remaining_space {
padding.push_str(&ANSIStrings(&[superkey_prefix_style.paint(" ")]).to_string());
padding_len += 1;
}
Some(LinePart {
part: format!("{}{}", padding, controls_line),
len: controls_line_len + padding_len,
})
}
fn render_secondary_info(
help: &ModeInfo,
tab_info: Option<&TabInfo>,
@ -1168,6 +1395,7 @@ fn add_keygroup_separator(help: &ModeInfo, max_len: usize) -> Option<LinePart> {
bits.push(
Style::new()
.fg(separator_color)
.on(bg_color)
.bold()
.paint(format!(" {} ", mode_help_text)),
);
@ -1330,6 +1558,7 @@ fn get_keys_and_hints(mi: &ModeInfo) -> Vec<(String, String, Vec<KeyWithModifier
(s("Switch Location"), s("Move"), action_key_group(&km, &[
&[Action::MovePane(Some(Dir::Left))], &[Action::MovePane(Some(Dir::Down))],
&[Action::MovePane(Some(Dir::Up))], &[Action::MovePane(Some(Dir::Right))]])),
(s("When done"), s("Back"), to_basemode_key),
]} else if mi.mode == IM::Scroll { vec![
(s("Enter search term"), s("Search"),
action_key(&km, &[A::SwitchToMode(IM::EnterSearch), A::SearchInput(vec![0])])),
@ -1516,6 +1745,25 @@ fn configuration_key(keymap: &[(KeyWithModifier, Vec<Action>)]) -> Vec<KeyWithMo
}
}
fn multiple_select_key(keymap: &[(KeyWithModifier, Vec<Action>)]) -> Vec<KeyWithModifier> {
let mut matching = keymap.iter().find_map(|(key, acvec)| {
let has_match = acvec
.iter()
.find(|a| a.launches_plugin("zellij:multiple-select")) // TODO: make this an alias
.is_some();
if has_match {
Some(key.clone())
} else {
None
}
});
if let Some(matching) = matching.take() {
vec![matching]
} else {
vec![]
}
}
fn style_key_with_modifier(keyvec: &[KeyWithModifier], color_index: Option<usize>) -> LinePart {
if keyvec.is_empty() {
return LinePart::default();

View file

@ -138,6 +138,14 @@ keybinds {
bind "Alt k" "Alt Up" { MoveFocus "Up"; }
bind "Alt =" "Alt +" { Resize "Increase"; }
bind "Alt -" { Resize "Decrease"; }
bind "Alt m" {
LaunchOrFocusPlugin "zellij:multiple-select" {
floating true
move_to_focused_tab true
}
}
bind "Alt p" { TogglePaneInGroup; }
bind "Alt Shift p" { ToggleGroupMarking; }
}
shared_except "normal" "locked" {
bind "Enter" "Space" "Esc" { SwitchToMode "Normal"; }

View file

@ -69,6 +69,10 @@ fn workspace_members() -> &'static Vec<WorkspaceMember> {
crate_name: "default-plugins/about",
build: true,
},
WorkspaceMember {
crate_name: "default-plugins/multiple-select",
build: true,
},
WorkspaceMember {
crate_name: "zellij-utils",
build: false,

View file

@ -55,6 +55,7 @@ pub enum BackgroundJob {
Vec<u8>, // body
BTreeMap<String, String>, // context
),
HighlightPanesWithMessage(Vec<PaneId>, String),
RenderToClients,
Exit,
}
@ -76,12 +77,16 @@ impl From<&BackgroundJob> for BackgroundJobContext {
BackgroundJob::WebRequest(..) => BackgroundJobContext::WebRequest,
BackgroundJob::ReportPluginList(..) => BackgroundJobContext::ReportPluginList,
BackgroundJob::RenderToClients => BackgroundJobContext::ReportPluginList,
BackgroundJob::HighlightPanesWithMessage(..) => {
BackgroundJobContext::HighlightPanesWithMessage
},
BackgroundJob::Exit => BackgroundJobContext::Exit,
}
}
}
static FLASH_DURATION_MS: u64 = 1000;
static LONG_FLASH_DURATION_MS: u64 = 1000;
static FLASH_DURATION_MS: u64 = 400; // Doherty threshold
static PLUGIN_ANIMATION_OFFSET_DURATION_MD: u64 = 500;
static SESSION_READ_DURATION: u64 = 1000;
static DEFAULT_SERIALIZATION_INTERVAL: u64 = 60000;
@ -129,7 +134,7 @@ pub(crate) fn background_jobs_main(
Some(text),
),
);
task::sleep(std::time::Duration::from_millis(FLASH_DURATION_MS)).await;
task::sleep(std::time::Duration::from_millis(LONG_FLASH_DURATION_MS)).await;
let _ = senders.send_to_screen(
ScreenInstruction::ClearPaneFrameColorOverride(pane_ids),
);
@ -411,6 +416,26 @@ pub(crate) fn background_jobs_main(
});
}
},
BackgroundJob::HighlightPanesWithMessage(pane_ids, text) => {
if job_already_running(job, &mut running_jobs) {
continue;
}
task::spawn({
let senders = bus.senders.clone();
async move {
let _ = senders.send_to_screen(
ScreenInstruction::AddHighlightPaneFrameColorOverride(
pane_ids.clone(),
Some(text),
),
);
task::sleep(std::time::Duration::from_millis(FLASH_DURATION_MS)).await;
let _ = senders.send_to_screen(
ScreenInstruction::ClearPaneFrameColorOverride(pane_ids),
);
}
});
},
BackgroundJob::Exit => {
for loading_plugin in loading_plugins.values() {
loading_plugin.store(false, Ordering::SeqCst);
@ -431,7 +456,9 @@ fn job_already_running(
) -> bool {
match running_jobs.get_mut(&job) {
Some(current_running_job_start_time) => {
if current_running_job_start_time.elapsed() > Duration::from_millis(FLASH_DURATION_MS) {
if current_running_job_start_time.elapsed()
> Duration::from_millis(LONG_FLASH_DURATION_MS)
{
*current_running_job_start_time = Instant::now();
false
} else {

View file

@ -369,6 +369,10 @@ impl SessionMetaData {
hide_session_name: new_config.ui.pane_frames.hide_session_name,
stacked_resize: new_config.options.stacked_resize.unwrap_or(true),
default_editor: new_config.options.scrollback_editor.clone(),
advanced_mouse_actions: new_config
.options
.advanced_mouse_actions
.unwrap_or(true),
})
.unwrap();
self.senders

View file

@ -343,7 +343,12 @@ impl FloatingPanes {
}
Ok(())
}
pub fn render(&mut self, output: &mut Output) -> Result<()> {
pub fn render(
&mut self,
output: &mut Output,
mouse_hover_pane_id: &HashMap<ClientId, PaneId>,
current_pane_group: HashMap<ClientId, Vec<PaneId>>,
) -> Result<()> {
let err_context = || "failed to render output";
let connected_clients: Vec<ClientId> =
{ self.connected_clients.borrow().iter().copied().collect() };
@ -393,6 +398,8 @@ impl FloatingPanes {
false,
false,
true,
mouse_hover_pane_id,
current_pane_group.clone(),
);
for client_id in &connected_clients {
let client_mode = self
@ -1095,10 +1102,10 @@ impl FloatingPanes {
Err(anyhow!("Pane not found"))
}
}
pub fn pane_info(&self) -> Vec<PaneInfo> {
pub fn pane_info(&self, current_pane_group: &HashMap<ClientId, Vec<PaneId>>) -> Vec<PaneInfo> {
let mut pane_infos = vec![];
for (pane_id, pane) in self.panes.iter() {
let mut pane_info_for_pane = pane_info_for_pane(pane_id, pane);
let mut pane_info_for_pane = pane_info_for_pane(pane_id, pane, current_pane_group);
let is_focused = self.active_panes.pane_id_is_focused(pane_id);
pane_info_for_pane.is_floating = true;
pane_info_for_pane.is_suppressed = false;
@ -1139,4 +1146,48 @@ impl FloatingPanes {
pane.update_rounded_corners(rounded_corners);
}
}
pub fn next_selectable_pane_id_above(&mut self, pane_id: &PaneId) -> Option<PaneId> {
let display_area = *self.display_area.borrow();
let viewport = *self.viewport.borrow();
let floating_pane_grid = FloatingPaneGrid::new(
&mut self.panes,
&mut self.desired_pane_positions,
display_area,
viewport,
);
floating_pane_grid.next_selectable_pane_id_above(&pane_id)
}
pub fn next_selectable_pane_id_below(&mut self, pane_id: &PaneId) -> Option<PaneId> {
let display_area = *self.display_area.borrow();
let viewport = *self.viewport.borrow();
let floating_pane_grid = FloatingPaneGrid::new(
&mut self.panes,
&mut self.desired_pane_positions,
display_area,
viewport,
);
floating_pane_grid.next_selectable_pane_id_below(&pane_id)
}
pub fn next_selectable_pane_id_to_the_left(&mut self, pane_id: &PaneId) -> Option<PaneId> {
let display_area = *self.display_area.borrow();
let viewport = *self.viewport.borrow();
let floating_pane_grid = FloatingPaneGrid::new(
&mut self.panes,
&mut self.desired_pane_positions,
display_area,
viewport,
);
floating_pane_grid.next_selectable_pane_id_to_the_left(&pane_id)
}
pub fn next_selectable_pane_id_to_the_right(&mut self, pane_id: &PaneId) -> Option<PaneId> {
let display_area = *self.display_area.borrow();
let viewport = *self.viewport.borrow();
let floating_pane_grid = FloatingPaneGrid::new(
&mut self.panes,
&mut self.desired_pane_positions,
display_area,
viewport,
);
floating_pane_grid.next_selectable_pane_id_to_the_right(&pane_id)
}
}

View file

@ -646,7 +646,16 @@ impl Pane for PluginPane {
fn add_red_pane_frame_color_override(&mut self, error_text: Option<String>) {
self.pane_frame_color_override = Some((self.style.colors.exit_code_error.base, error_text));
}
fn clear_pane_frame_color_override(&mut self) {
fn add_highlight_pane_frame_color_override(
&mut self,
text: Option<String>,
_client_id: Option<ClientId>,
) {
// TODO: if we have a client_id, we should only highlight the frame for this client
self.pane_frame_color_override = Some((self.style.colors.frame_highlight.base, text));
}
fn clear_pane_frame_color_override(&mut self, _client_id: Option<ClientId>) {
// TODO: if we have a client_id, we should only clear the highlight for this client
self.pane_frame_color_override = None;
}
fn frame_color_override(&self) -> Option<PaletteColor> {

View file

@ -742,7 +742,16 @@ impl Pane for TerminalPane {
fn add_red_pane_frame_color_override(&mut self, error_text: Option<String>) {
self.pane_frame_color_override = Some((self.style.colors.exit_code_error.base, error_text));
}
fn clear_pane_frame_color_override(&mut self) {
fn add_highlight_pane_frame_color_override(
&mut self,
text: Option<String>,
_client_id: Option<ClientId>,
) {
// TODO: if we have a client_id, we should only highlight the frame for this client
self.pane_frame_color_override = Some((self.style.colors.frame_highlight.base, text));
}
fn clear_pane_frame_color_override(&mut self, _client_id: Option<ClientId>) {
// TODO: if we have a client_id, we should only clear the highlight for this client
self.pane_frame_color_override = None;
}
fn frame_color_override(&self) -> Option<PaletteColor> {

View file

@ -200,6 +200,15 @@ impl TiledPanes {
.is_some();
has_room_for_new_pane || pane_grid.has_room_for_new_stacked_pane() || self.panes.is_empty()
}
pub fn room_left_in_stack_of_pane_id(&mut self, pane_id: &PaneId) -> Option<usize> {
let mut pane_grid = TiledPaneGrid::new(
&mut self.panes,
&self.panes_to_hide,
*self.display_area.borrow(),
*self.viewport.borrow(),
);
pane_grid.room_left_in_stack_of_pane_id(pane_id)
}
pub fn assign_geom_for_pane_with_run(&mut self, run: Option<Run>) {
// here we're removing the first pane we find with this run instruction and re-adding it so
@ -226,6 +235,26 @@ impl TiledPanes {
}
}
}
pub fn add_pane_to_stack(&mut self, pane_id_in_stack: &PaneId, mut pane: Box<dyn Pane>) {
let mut pane_grid = TiledPaneGrid::new(
&mut self.panes,
&self.panes_to_hide,
*self.display_area.borrow(),
*self.viewport.borrow(),
);
match pane_grid.make_room_in_stack_of_pane_id_for_pane(pane_id_in_stack) {
Ok(new_pane_geom) => {
pane.set_geom(new_pane_geom);
self.panes.insert(pane.pid(), pane);
self.set_force_render(); // TODO: why do we need this?
return;
},
Err(_e) => {
let should_relayout = false;
return self.add_pane_without_stacked_resize(pane.pid(), pane, should_relayout);
},
}
}
fn add_pane(
&mut self,
pane_id: PaneId,
@ -658,6 +687,18 @@ impl TiledPanes {
self.set_force_render();
self.reapply_pane_frames();
}
pub fn pane_ids_in_stack_of_pane_id(&mut self, pane_id: &PaneId) -> Vec<PaneId> {
if let Some(stack_id) = self
.panes
.get(pane_id)
.and_then(|p| p.position_and_size().stacked)
{
StackedPanes::new_from_btreemap(&mut self.panes, &self.panes_to_hide)
.pane_ids_in_stack(stack_id)
} else {
vec![]
}
}
pub fn focus_pane_for_all_clients_in_stack(&mut self, pane_id: PaneId, stack_id: usize) {
let connected_clients: Vec<ClientId> =
self.connected_clients.borrow().iter().copied().collect();
@ -880,7 +921,13 @@ impl TiledPanes {
pub fn has_panes(&self) -> bool {
!self.panes.is_empty()
}
pub fn render(&mut self, output: &mut Output, floating_panes_are_visible: bool) -> Result<()> {
pub fn render(
&mut self,
output: &mut Output,
floating_panes_are_visible: bool,
mouse_hover_pane_id: &HashMap<ClientId, PaneId>,
current_pane_group: HashMap<ClientId, Vec<PaneId>>,
) -> Result<()> {
let err_context = || "failed to render tiled panes";
let connected_clients: Vec<ClientId> =
@ -930,6 +977,8 @@ impl TiledPanes {
pane_is_stacked_under,
pane_is_stacked_over,
should_draw_pane_frames,
&mouse_hover_pane_id,
current_pane_group.clone(),
);
for client_id in &connected_clients {
let client_mode = self
@ -1716,7 +1765,7 @@ impl TiledPanes {
*self.viewport.borrow(),
);
let next_index = pane_grid
.next_selectable_pane_id_below(&active_pane_id)
.next_selectable_pane_id_below(&active_pane_id, false)
.or_else(|| pane_grid.progress_stack_down_if_in_stack(&active_pane_id));
match next_index {
Some(p) => {
@ -1767,7 +1816,7 @@ impl TiledPanes {
*self.viewport.borrow(),
);
let next_index = pane_grid
.next_selectable_pane_id_above(&active_pane_id)
.next_selectable_pane_id_above(&active_pane_id, false)
.or_else(|| pane_grid.progress_stack_up_if_in_stack(&active_pane_id));
match next_index {
Some(p) => {
@ -1972,7 +2021,7 @@ impl TiledPanes {
*self.viewport.borrow(),
);
let next_index = pane_grid
.next_selectable_pane_id_below(&pane_id)
.next_selectable_pane_id_below(&pane_id, false)
.or_else(|| pane_grid.progress_stack_down_if_in_stack(&pane_id));
if let Some(p) = next_index {
let current_position = self.panes.get(&pane_id).unwrap();
@ -2127,7 +2176,7 @@ impl TiledPanes {
*self.viewport.borrow(),
);
let next_index = pane_grid
.next_selectable_pane_id_above(&pane_id)
.next_selectable_pane_id_above(&pane_id, false)
.or_else(|| pane_grid.progress_stack_up_if_in_stack(&pane_id));
if let Some(p) = next_index {
let current_position = self.panes.get(&pane_id).unwrap();
@ -2443,10 +2492,10 @@ impl TiledPanes {
.find(|(_id, pane)| run_plugin_or_alias.is_equivalent_to_run(pane.invoked_with()))
.map(|(id, _)| *id)
}
pub fn pane_info(&self) -> Vec<PaneInfo> {
pub fn pane_info(&self, current_pane_group: &HashMap<ClientId, Vec<PaneId>>) -> Vec<PaneInfo> {
let mut pane_infos = vec![];
for (pane_id, pane) in self.panes.iter() {
let mut pane_info_for_pane = pane_info_for_pane(pane_id, pane);
let mut pane_info_for_pane = pane_info_for_pane(pane_id, pane, &current_pane_group);
let is_focused = self.active_panes.pane_id_is_focused(pane_id);
pane_info_for_pane.is_floating = false;
pane_info_for_pane.is_suppressed = false;
@ -2495,6 +2544,44 @@ impl TiledPanes {
StackedPanes::new_from_btreemap(&mut self.panes, &self.panes_to_hide)
.stacked_pane_ids_under_and_over_flexible_panes()
}
pub fn next_selectable_pane_id_above(&mut self, pane_id: &PaneId) -> Option<PaneId> {
let pane_grid = TiledPaneGrid::new(
&mut self.panes,
&self.panes_to_hide,
*self.display_area.borrow(),
*self.viewport.borrow(),
);
let include_panes_in_stack = true;
pane_grid.next_selectable_pane_id_above(&pane_id, include_panes_in_stack)
}
pub fn next_selectable_pane_id_below(&mut self, pane_id: &PaneId) -> Option<PaneId> {
let pane_grid = TiledPaneGrid::new(
&mut self.panes,
&self.panes_to_hide,
*self.display_area.borrow(),
*self.viewport.borrow(),
);
let include_panes_in_stack = true;
pane_grid.next_selectable_pane_id_below(&pane_id, include_panes_in_stack)
}
pub fn next_selectable_pane_id_to_the_left(&mut self, pane_id: &PaneId) -> Option<PaneId> {
let pane_grid = TiledPaneGrid::new(
&mut self.panes,
&self.panes_to_hide,
*self.display_area.borrow(),
*self.viewport.borrow(),
);
pane_grid.next_selectable_pane_id_to_the_left(&pane_id)
}
pub fn next_selectable_pane_id_to_the_right(&mut self, pane_id: &PaneId) -> Option<PaneId> {
let pane_grid = TiledPaneGrid::new(
&mut self.panes,
&self.panes_to_hide,
*self.display_area.borrow(),
*self.viewport.borrow(),
);
pane_grid.next_selectable_pane_id_to_the_right(&pane_id)
}
}
#[allow(clippy::borrowed_box)]

View file

@ -500,6 +500,20 @@ impl<'a> StackedPanes<'a> {
}
Err(anyhow!("Not enough room for another pane!"))
}
pub fn room_left_in_stack_of_pane_id(&self, pane_id: &PaneId) -> Option<usize> {
// if the pane is stacked, returns the number of panes possible to add to this stack
let Ok(stack) = self.positions_in_stack(pane_id) else {
return None;
};
stack.iter().find_map(|(_p_id, p)| {
if !p.rows.is_fixed() {
// this is the flexible pane
Some(p.rows.as_usize().saturating_sub(MIN_TERMINAL_HEIGHT))
} else {
None
}
})
}
pub fn new_stack(&self, root_pane_id: PaneId, pane_count_in_stack: usize) -> Vec<PaneGeom> {
let mut stacked_geoms = vec![];
let panes = self.panes.borrow();

View file

@ -1009,7 +1009,11 @@ impl<'a> TiledPaneGrid<'a> {
None => None,
}
}
pub fn next_selectable_pane_id_below(&self, current_pane_id: &PaneId) -> Option<PaneId> {
pub fn next_selectable_pane_id_below(
&self,
current_pane_id: &PaneId,
include_panes_in_stack: bool,
) -> Option<PaneId> {
let panes = self.panes.borrow();
let current_pane = panes.get(current_pane_id)?;
let panes: Vec<(PaneId, &&mut Box<dyn Pane>)> = panes
@ -1021,9 +1025,14 @@ impl<'a> TiledPaneGrid<'a> {
.iter()
.enumerate()
.filter(|(_, (_, c))| {
c.is_directly_below(Box::as_ref(current_pane))
&& c.vertically_overlaps_with(Box::as_ref(current_pane))
&& !c.current_geom().is_stacked()
if include_panes_in_stack {
c.is_directly_below(Box::as_ref(current_pane))
&& c.vertically_overlaps_with(Box::as_ref(current_pane))
} else {
c.is_directly_below(Box::as_ref(current_pane))
&& c.vertically_overlaps_with(Box::as_ref(current_pane))
&& !c.current_geom().is_stacked()
}
})
.max_by_key(|(_, (_, c))| c.active_at())
.map(|(_, (pid, _))| pid)
@ -1074,7 +1083,11 @@ impl<'a> TiledPaneGrid<'a> {
.copied();
next_index
}
pub fn next_selectable_pane_id_above(&self, current_pane_id: &PaneId) -> Option<PaneId> {
pub fn next_selectable_pane_id_above(
&self,
current_pane_id: &PaneId,
include_panes_in_stack: bool,
) -> Option<PaneId> {
let panes = self.panes.borrow();
let current_pane = panes.get(current_pane_id)?;
let panes: Vec<(PaneId, &&mut Box<dyn Pane>)> = panes
@ -1086,9 +1099,14 @@ impl<'a> TiledPaneGrid<'a> {
.iter()
.enumerate()
.filter(|(_, (_, c))| {
c.is_directly_above(Box::as_ref(current_pane))
&& c.vertically_overlaps_with(Box::as_ref(current_pane))
&& !c.current_geom().is_stacked()
if include_panes_in_stack {
c.is_directly_above(Box::as_ref(current_pane))
&& c.vertically_overlaps_with(Box::as_ref(current_pane))
} else {
c.is_directly_above(Box::as_ref(current_pane))
&& c.vertically_overlaps_with(Box::as_ref(current_pane))
&& !c.current_geom().is_stacked()
}
})
.max_by_key(|(_, (_, c))| c.active_at())
.map(|(_, (pid, _))| pid)
@ -1413,6 +1431,9 @@ impl<'a> TiledPaneGrid<'a> {
.iter()
.any(|(_p_id, p)| p.current_geom().rows.as_usize() > MIN_TERMINAL_HEIGHT)
}
pub fn room_left_in_stack_of_pane_id(&mut self, pane_id: &PaneId) -> Option<usize> {
StackedPanes::new(self.panes.clone()).room_left_in_stack_of_pane_id(pane_id)
}
pub fn make_room_in_stack_for_pane(&mut self) -> Result<PaneGeom> {
StackedPanes::new(self.panes.clone()).make_room_for_new_pane()
}

View file

@ -1218,7 +1218,7 @@ impl Pane for MockPane {
fn add_red_pane_frame_color_override(&mut self, _error_text: Option<String>) {
unimplemented!()
}
fn clear_pane_frame_color_override(&mut self) {
fn clear_pane_frame_color_override(&mut self, _client_id: Option<ClientId>) {
unimplemented!()
}
fn frame_color_override(&self) -> Option<PaletteColor> {

View file

@ -50,18 +50,21 @@ impl PluginMap {
pub fn remove_plugins(
&mut self,
pid: PluginId,
) -> Vec<(
Arc<Mutex<RunningPlugin>>,
Arc<Mutex<Subscriptions>>,
HashMap<String, Sender<MessageToWorker>>,
)> {
let mut removed = vec![];
) -> HashMap<
(PluginId, ClientId),
(
Arc<Mutex<RunningPlugin>>,
Arc<Mutex<Subscriptions>>,
HashMap<String, Sender<MessageToWorker>>,
),
> {
let mut removed = HashMap::new();
let ids_in_plugin_map: Vec<(PluginId, ClientId)> =
self.plugin_assets.keys().copied().collect();
for (plugin_id, client_id) in ids_in_plugin_map {
if pid == plugin_id {
if let Some(plugin_asset) = self.plugin_assets.remove(&(plugin_id, client_id)) {
removed.push(plugin_asset);
removed.insert((plugin_id, client_id), plugin_asset);
}
}
}

View file

@ -8622,3 +8622,74 @@ pub fn list_clients_plugin_command() {
.unwrap();
assert_snapshot!(format!("{:#?}", list_clients_instruction));
}
#[test]
#[ignore]
pub fn before_close_plugin_event() {
let temp_folder = tempdir().unwrap(); // placed explicitly in the test scope because its
// destructor removes the directory
let plugin_host_folder = PathBuf::from(temp_folder.path());
let cache_path = plugin_host_folder.join("permissions_test.kdl");
let (plugin_thread_sender, screen_receiver, teardown) =
create_plugin_thread(Some(plugin_host_folder));
let plugin_should_float = Some(false);
let plugin_title = Some("test_plugin".to_owned());
let run_plugin = RunPluginOrAlias::RunPlugin(RunPlugin {
_allow_exec_host_cmd: false,
location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)),
configuration: Default::default(),
..Default::default()
});
let tab_index = 1;
let client_id = 1;
let size = Size {
cols: 121,
rows: 20,
};
let received_screen_instructions = Arc::new(Mutex::new(vec![]));
let screen_thread = grant_permissions_and_log_actions_in_thread!(
received_screen_instructions,
ScreenInstruction::HighlightAndUnhighlightPanes,
screen_receiver,
1,
&PermissionType::ChangeApplicationState,
cache_path,
plugin_thread_sender,
client_id
);
let _ = plugin_thread_sender.send(PluginInstruction::AddClient(client_id));
let _ = plugin_thread_sender.send(PluginInstruction::Load(
plugin_should_float,
false,
plugin_title,
run_plugin,
Some(tab_index),
None,
client_id,
size,
None,
false,
));
std::thread::sleep(std::time::Duration::from_millis(5000));
// here we send an unload to plugin id 0 (the first plugin id, presumably this plugin)
// so that its BeforeClose Event will be triggered and it will send a
// HighlightAndUnhighlightPanes
// instruction which we can assert below
let _ = plugin_thread_sender.send(PluginInstruction::Unload(0));
screen_thread.join().unwrap(); // this might take a while if the cache is cold
teardown();
let sent_instruction = received_screen_instructions
.lock()
.unwrap()
.iter()
.find_map(|i| {
if let ScreenInstruction::HighlightAndUnhighlightPanes(..) = i {
Some(i.clone())
} else {
None
}
})
.unwrap();
assert_snapshot!(format!("{:#?}", sent_instruction));
}

View file

@ -0,0 +1,17 @@
---
source: zellij-server/src/plugins/./unit/plugin_tests.rs
expression: "format!(\"{:#?}\", sent_instruction)"
---
HighlightAndUnhighlightPanes(
[
Terminal(
1,
),
],
[
Plugin(
1,
),
],
1,
)

View file

@ -1,6 +1,5 @@
---
source: zellij-server/src/plugins/./unit/plugin_tests.rs
assertion_line: 1143
expression: "format!(\"{:#?}\", switch_to_mode_event)"
---
Some(
@ -222,10 +221,10 @@ Some(
154,
),
emphasis_0: EightBit(
154,
201,
),
emphasis_1: EightBit(
154,
99,
),
emphasis_2: EightBit(
154,
@ -318,6 +317,7 @@ Some(
),
editor: None,
shell: None,
currently_marking_pane_group: None,
},
1,
),

View file

@ -318,14 +318,43 @@ impl WasmBridge {
pub fn unload_plugin(&mut self, pid: PluginId) -> Result<()> {
info!("Bye from plugin {}", &pid);
let mut plugin_map = self.plugin_map.lock().unwrap();
for (running_plugin, _, workers) in plugin_map.remove_plugins(pid) {
for ((plugin_id, client_id), (running_plugin, subscriptions, workers)) in
plugin_map.remove_plugins(pid)
{
for (_worker_name, worker_sender) in workers {
drop(worker_sender.send(MessageToWorker::Exit));
}
let running_plugin = running_plugin.lock().unwrap();
let cache_dir = running_plugin.store.data().plugin_own_data_dir.clone();
if let Err(e) = std::fs::remove_dir_all(cache_dir) {
log::error!("Failed to remove cache dir for plugin: {:?}", e);
let subscriptions = subscriptions.lock().unwrap();
if subscriptions.contains(&EventType::BeforeClose) {
let mut running_plugin = running_plugin.lock().unwrap();
match apply_before_close_event_to_plugin(
pid,
client_id,
&mut running_plugin,
self.senders.clone(),
) {
Ok(()) => {},
Err(e) => {
log::error!("{:?}", e);
// https://stackoverflow.com/questions/66450942/in-rust-is-there-a-way-to-make-literal-newlines-in-r-using-windows-c
let stringified_error = format!("{:?}", e).replace("\n", "\n\r");
handle_plugin_crash(plugin_id, stringified_error, self.senders.clone());
},
}
let cache_dir = running_plugin.store.data().plugin_own_data_dir.clone();
if let Err(e) = std::fs::remove_dir_all(cache_dir) {
log::error!("Failed to remove cache dir for plugin: {:?}", e);
}
} else {
// this is duplicated because of locking/unlocking order between running_plugin and
// subscriptions
let running_plugin = running_plugin.lock().unwrap();
let cache_dir = running_plugin.store.data().plugin_own_data_dir.clone();
if let Err(e) = std::fs::remove_dir_all(cache_dir) {
log::error!("Failed to remove cache dir for plugin: {:?}", e);
}
}
}
self.cached_plugin_map.clear();
@ -1651,3 +1680,36 @@ pub fn handle_plugin_crash(plugin_id: PluginId, message: String, senders: Thread
loading_indication,
));
}
pub fn apply_before_close_event_to_plugin(
plugin_id: PluginId,
client_id: ClientId,
running_plugin: &mut RunningPlugin,
senders: ThreadSenders,
) -> Result<()> {
let instance = &running_plugin.instance;
let err_context = || format!("Failed to apply event to plugin {plugin_id}");
let event = Event::BeforeClose;
let protobuf_event: ProtobufEvent = event
.clone()
.try_into()
.map_err(|e| anyhow!("Failed to convert to protobuf: {:?}", e))?;
let update = instance
.get_typed_func::<(), i32>(&mut running_plugin.store, "update")
.with_context(err_context)?;
wasi_write_object(running_plugin.store.data(), &protobuf_event.encode_to_vec())
.with_context(err_context)?;
let _should_render = update
.call(&mut running_plugin.store, ())
.with_context(err_context)?;
let pipes_to_block_or_unblock = pipes_to_block_or_unblock(running_plugin, None);
let plugin_render_asset =
PluginRenderAsset::new(plugin_id, client_id, vec![]).with_pipes(pipes_to_block_or_unblock);
let _ = senders
.send_to_plugin(PluginInstruction::UnblockCliPipes(vec![
plugin_render_asset,
]))
.context("failed to unblock input pipe");
Ok(())
}

View file

@ -431,6 +431,30 @@ fn host_run_plugin_command(caller: Caller<'_, PluginEnv>) {
close_plugin_after_replace,
context,
),
PluginCommand::GroupAndUngroupPanes(panes_to_group, panes_to_ungroup) => {
group_and_ungroup_panes(
env,
panes_to_group.into_iter().map(|p| p.into()).collect(),
panes_to_ungroup.into_iter().map(|p| p.into()).collect(),
)
},
PluginCommand::HighlightAndUnhighlightPanes(
panes_to_highlight,
panes_to_unhighlight,
) => highlight_and_unhighlight_panes(
env,
panes_to_highlight.into_iter().map(|p| p.into()).collect(),
panes_to_unhighlight.into_iter().map(|p| p.into()).collect(),
),
PluginCommand::CloseMultiplePanes(pane_ids) => {
close_multiple_panes(env, pane_ids.into_iter().map(|p| p.into()).collect())
},
PluginCommand::FloatMultiplePanes(pane_ids) => {
float_multiple_panes(env, pane_ids.into_iter().map(|p| p.into()).collect())
},
PluginCommand::EmbedMultiplePanes(pane_ids) => {
embed_multiple_panes(env, pane_ids.into_iter().map(|p| p.into()).collect())
},
},
(PermissionStatus::Denied, permission) => {
log::error!(
@ -547,6 +571,7 @@ fn get_plugin_ids(env: &PluginEnv) {
plugin_id: env.plugin_id,
zellij_pid: process::id(),
initial_cwd: env.plugin_cwd.clone(),
client_id: env.client_id,
};
ProtobufPluginIds::try_from(ids)
.map_err(|e| anyhow!("Failed to serialized plugin ids: {}", e))
@ -1864,7 +1889,7 @@ fn set_floating_pane_pinned(env: &PluginEnv, pane_id: PaneId, should_be_pinned:
fn stack_panes(env: &PluginEnv, pane_ids: Vec<PaneId>) {
let _ = env
.senders
.send_to_screen(ScreenInstruction::StackPanes(pane_ids));
.send_to_screen(ScreenInstruction::StackPanes(pane_ids, env.client_id));
}
fn change_floating_panes_coordinates(
@ -2147,6 +2172,65 @@ fn load_new_plugin(
}
}
fn group_and_ungroup_panes(
env: &PluginEnv,
panes_to_group: Vec<PaneId>,
panes_to_ungroup: Vec<PaneId>,
) {
let _ = env
.senders
.send_to_screen(ScreenInstruction::GroupAndUngroupPanes(
panes_to_group,
panes_to_ungroup,
env.client_id,
));
}
fn highlight_and_unhighlight_panes(
env: &PluginEnv,
panes_to_highlight: Vec<PaneId>,
panes_to_unhighlight: Vec<PaneId>,
) {
let _ = env
.senders
.send_to_screen(ScreenInstruction::HighlightAndUnhighlightPanes(
panes_to_highlight,
panes_to_unhighlight,
env.client_id,
));
}
fn close_multiple_panes(env: &PluginEnv, pane_ids: Vec<PaneId>) {
for pane_id in pane_ids {
match pane_id {
PaneId::Terminal(terminal_pane_id) => {
close_terminal_pane(env, terminal_pane_id);
},
PaneId::Plugin(plugin_pane_id) => {
close_plugin_pane(env, plugin_pane_id);
},
}
}
}
fn float_multiple_panes(env: &PluginEnv, pane_ids: Vec<PaneId>) {
let _ = env
.senders
.send_to_screen(ScreenInstruction::FloatMultiplePanes(
pane_ids,
env.client_id,
));
}
fn embed_multiple_panes(env: &PluginEnv, pane_ids: Vec<PaneId>) {
let _ = env
.senders
.send_to_screen(ScreenInstruction::EmbedMultiplePanes(
pane_ids,
env.client_id,
));
}
// Custom panic handler for plugins.
//
// This is called when a panic occurs in a plugin. Since most panics will likely originate in the
@ -2308,6 +2392,11 @@ fn check_command_permission(
| PluginCommand::SetFloatingPanePinned(..)
| PluginCommand::StackPanes(..)
| PluginCommand::ChangeFloatingPanesCoordinates(..)
| PluginCommand::GroupAndUngroupPanes(..)
| PluginCommand::HighlightAndUnhighlightPanes(..)
| PluginCommand::CloseMultiplePanes(..)
| PluginCommand::FloatMultiplePanes(..)
| PluginCommand::EmbedMultiplePanes(..)
| PluginCommand::KillSessions(..) => PermissionType::ChangeApplicationState,
PluginCommand::UnblockCliPipeInput(..)
| PluginCommand::BlockCliPipeInput(..)

View file

@ -938,6 +938,7 @@ pub(crate) fn route_action(
senders
.send_to_screen(ScreenInstruction::StackPanes(
pane_ids_to_stack.iter().map(|p| PaneId::from(*p)).collect(),
client_id,
))
.with_context(err_context)?;
},
@ -949,6 +950,16 @@ pub(crate) fn route_action(
)]))
.with_context(err_context)?;
},
Action::TogglePaneInGroup => {
senders
.send_to_screen(ScreenInstruction::TogglePaneInGroup(client_id))
.with_context(err_context)?;
},
Action::ToggleGroupMarking => {
senders
.send_to_screen(ScreenInstruction::ToggleGroupMarking(client_id))
.with_context(err_context)?;
},
}
Ok(should_break)
}

View file

@ -371,6 +371,7 @@ pub enum ScreenInstruction {
hide_session_name: bool,
stacked_resize: bool,
default_editor: Option<PathBuf>,
advanced_mouse_actions: bool,
},
RerunCommandPane(u32), // u32 - terminal pane id
ResizePaneWithId(ResizeStrategy, PaneId),
@ -404,8 +405,16 @@ pub enum ScreenInstruction {
ListClientsToPlugin(PluginId, ClientId),
TogglePanePinned(ClientId),
SetFloatingPanePinned(PaneId, bool),
StackPanes(Vec<PaneId>),
StackPanes(Vec<PaneId>, ClientId),
ChangeFloatingPanesCoordinates(Vec<(PaneId, FloatingPaneCoordinates)>),
AddHighlightPaneFrameColorOverride(Vec<PaneId>, Option<String>), // Option<String> => optional
// message
GroupAndUngroupPanes(Vec<PaneId>, Vec<PaneId>, ClientId), // panes_to_group, panes_to_ungroup
HighlightAndUnhighlightPanes(Vec<PaneId>, Vec<PaneId>, ClientId), // panes_to_highlight, panes_to_unhighlight
FloatMultiplePanes(Vec<PaneId>, ClientId),
EmbedMultiplePanes(Vec<PaneId>, ClientId),
TogglePaneInGroup(ClientId),
ToggleGroupMarking(ClientId),
}
impl From<&ScreenInstruction> for ScreenContext {
@ -616,6 +625,17 @@ impl From<&ScreenInstruction> for ScreenContext {
ScreenInstruction::ChangeFloatingPanesCoordinates(..) => {
ScreenContext::ChangeFloatingPanesCoordinates
},
ScreenInstruction::AddHighlightPaneFrameColorOverride(..) => {
ScreenContext::AddHighlightPaneFrameColorOverride
},
ScreenInstruction::GroupAndUngroupPanes(..) => ScreenContext::GroupAndUngroupPanes,
ScreenInstruction::HighlightAndUnhighlightPanes(..) => {
ScreenContext::HighlightAndUnhighlightPanes
},
ScreenInstruction::FloatMultiplePanes(..) => ScreenContext::FloatMultiplePanes,
ScreenInstruction::EmbedMultiplePanes(..) => ScreenContext::EmbedMultiplePanes,
ScreenInstruction::TogglePaneInGroup(..) => ScreenContext::TogglePaneInGroup,
ScreenInstruction::ToggleGroupMarking(..) => ScreenContext::ToggleGroupMarking,
}
}
}
@ -697,6 +717,9 @@ pub(crate) struct Screen {
default_layout_name: Option<String>,
explicitly_disable_kitty_keyboard_protocol: bool,
default_editor: Option<PathBuf>,
current_pane_group: Rc<RefCell<HashMap<ClientId, Vec<PaneId>>>>,
advanced_mouse_actions: bool,
currently_marking_pane_group: Rc<RefCell<HashMap<ClientId, bool>>>,
}
impl Screen {
@ -723,6 +746,7 @@ impl Screen {
explicitly_disable_kitty_keyboard_protocol: bool,
stacked_resize: bool,
default_editor: Option<PathBuf>,
advanced_mouse_actions: bool,
) -> Self {
let session_name = mode_info.session_name.clone().unwrap_or_default();
let session_info = SessionInfo::new(session_name.clone());
@ -766,6 +790,9 @@ impl Screen {
layout_dir,
explicitly_disable_kitty_keyboard_protocol,
default_editor,
current_pane_group: Rc::new(RefCell::new(HashMap::new())),
currently_marking_pane_group: Rc::new(RefCell::new(HashMap::new())),
advanced_mouse_actions,
}
}
@ -1257,6 +1284,10 @@ impl Screen {
&mut self.tabs
}
pub fn get_tabs(&self) -> &BTreeMap<usize, Tab> {
&self.tabs
}
/// Returns an immutable reference to this [`Screen`]'s active [`Tab`].
pub fn get_active_tab(&self, client_id: ClientId) -> Result<&Tab> {
match self.active_tab_indices.get(&client_id) {
@ -1366,6 +1397,9 @@ impl Screen {
self.styled_underlines,
self.explicitly_disable_kitty_keyboard_protocol,
self.default_editor.clone(),
self.current_pane_group.clone(),
self.currently_marking_pane_group.clone(),
self.advanced_mouse_actions,
);
for (client_id, mode_info) in &self.mode_info {
tab.change_mode_info(mode_info.clone(), *client_id);
@ -2474,6 +2508,7 @@ impl Screen {
hide_session_name: bool,
stacked_resize: bool,
default_editor: Option<PathBuf>,
advanced_mouse_actions: bool,
client_id: ClientId,
) -> Result<()> {
let should_support_arrow_fonts = !simplified_ui;
@ -2488,6 +2523,7 @@ impl Screen {
self.copy_options.command = copy_command.clone();
self.copy_options.copy_on_select = copy_on_select;
self.draw_pane_frames = pane_frames;
self.advanced_mouse_actions = advanced_mouse_actions;
self.default_mode_info
.update_arrow_fonts(should_support_arrow_fonts);
self.default_mode_info
@ -2507,6 +2543,7 @@ impl Screen {
tab.update_copy_options(&self.copy_options);
tab.set_pane_frames(pane_frames);
tab.update_arrow_fonts(should_support_arrow_fonts);
tab.update_advanced_mouse_actions(advanced_mouse_actions);
}
// client specific configuration
@ -2559,10 +2596,11 @@ impl Screen {
);
}
}
pub fn stack_panes(&mut self, mut pane_ids_to_stack: Vec<PaneId>) {
pub fn stack_panes(&mut self, mut pane_ids_to_stack: Vec<PaneId>) -> Option<PaneId> {
// if successful, returns the pane id of the root pane
if pane_ids_to_stack.is_empty() {
log::error!("Got an empty list of pane_ids to stack");
return;
return None;
}
let stack_size = pane_ids_to_stack.len();
let root_pane_id = pane_ids_to_stack.remove(0);
@ -2579,19 +2617,20 @@ impl Screen {
.copied()
else {
log::error!("Failed to find tab for root_pane_id: {:?}", root_pane_id);
return;
return None;
};
let mut panes_to_stack = vec![];
let target_tab_has_room_for_stack = self
.tabs
.get(&root_tab_id)
.get_mut(&root_tab_id)
.map(|t| t.has_room_for_stack(root_pane_id, stack_size))
.unwrap_or(false);
if !target_tab_has_room_for_stack {
log::error!("No room for stack with root pane id: {:?}", root_pane_id);
return;
return None;
}
let mut panes_to_stack = vec![];
for (tab_id, tab) in self.tabs.iter_mut() {
if tab_id == &root_tab_id {
// we do this before we extract panes so that the extraction won't trigger a
@ -2614,6 +2653,7 @@ impl Screen {
self.tabs
.get_mut(&root_tab_id)
.map(|t| t.stack_panes(root_pane_id, panes_to_stack));
return Some(root_pane_id);
}
pub fn change_floating_panes_coordinates(
&mut self,
@ -2629,6 +2669,87 @@ impl Screen {
}
}
}
pub fn handle_mouse_event(&mut self, event: MouseEvent, client_id: ClientId) {
match self
.get_active_tab_mut(client_id)
.and_then(|tab| tab.handle_mouse_event(&event, client_id))
{
Ok(mouse_effect) => {
if let Some(pane_id) = mouse_effect.group_toggle {
if self.advanced_mouse_actions {
self.toggle_pane_id_in_group(pane_id, &client_id);
}
}
if let Some(pane_id) = mouse_effect.group_add {
if self.advanced_mouse_actions {
self.add_pane_id_to_group(pane_id, &client_id);
}
}
if mouse_effect.ungroup {
if self.advanced_mouse_actions {
self.clear_pane_group(&client_id);
}
}
if mouse_effect.state_changed {
let _ = self.log_and_report_session_state();
}
if !mouse_effect.leave_clipboard_message {
let _ = self
.bus
.senders
.send_to_plugin(PluginInstruction::Update(vec![(
None,
Some(client_id),
Event::InputReceived,
)]));
}
self.render(None).non_fatal();
},
Err(e) => {
eprintln!("mouse event error: {:?}", e);
log::error!("Failed to process MouseEvent: {}", e);
},
}
}
pub fn toggle_pane_in_group(&mut self, client_id: ClientId) -> Result<()> {
let err_context = "Can't add pane to group";
let active_tab = self
.get_active_tab(client_id)
.with_context(|| err_context)?;
let active_pane_id = active_tab
.get_active_pane_id(client_id)
.with_context(|| err_context)?;
self.toggle_pane_id_in_group(active_pane_id, &client_id);
let _ = self.log_and_report_session_state();
Ok(())
}
pub fn toggle_group_marking(&mut self, client_id: ClientId) -> Result<()> {
let (was_marking_before, marking_pane_group_now) = {
let mut currently_marking_pane_group = self.currently_marking_pane_group.borrow_mut();
let previous_value = currently_marking_pane_group
.remove(&client_id)
.unwrap_or(false);
let new_value = !previous_value;
if new_value {
currently_marking_pane_group.insert(client_id, true);
}
(previous_value, new_value)
};
if marking_pane_group_now {
let active_pane_id = self.get_active_pane_id(&client_id);
if let Some(active_pane_id) = active_pane_id {
self.add_pane_id_to_group(active_pane_id, &client_id);
}
}
let value_changed = was_marking_before != marking_pane_group_now;
if value_changed {
for tab in self.tabs.values_mut() {
tab.update_input_modes()?;
}
let _ = self.log_and_report_session_state();
}
Ok(())
}
fn unblock_input(&self) -> Result<()> {
self.bus
.senders
@ -2773,6 +2894,117 @@ impl Screen {
fn connected_clients_contains(&self, client_id: &ClientId) -> bool {
self.connected_clients.borrow().contains(client_id)
}
fn get_client_pane_group(&self, client_id: &ClientId) -> HashSet<PaneId> {
self.current_pane_group
.borrow()
.get(client_id)
.map(|p| p.iter().copied().collect())
.unwrap_or_else(|| HashSet::new())
}
fn clear_pane_group(&mut self, client_id: &ClientId) {
self.current_pane_group
.borrow_mut()
.get_mut(client_id)
.map(|p| p.clear());
self.currently_marking_pane_group
.borrow_mut()
.remove(client_id);
}
fn toggle_pane_id_in_group(&mut self, pane_id: PaneId, client_id: &ClientId) {
{
let mut pane_groups = self.current_pane_group.borrow_mut();
let client_pane_group = pane_groups.entry(*client_id).or_insert_with(|| vec![]);
if client_pane_group.contains(&pane_id) {
client_pane_group.retain(|p| p != &pane_id);
} else {
client_pane_group.push(pane_id);
};
}
self.retain_only_existing_panes_in_pane_groups();
}
fn add_pane_id_to_group(&mut self, pane_id: PaneId, client_id: &ClientId) {
{
let mut pane_groups = self.current_pane_group.borrow_mut();
let client_pane_group = pane_groups.entry(*client_id).or_insert_with(|| vec![]);
if !client_pane_group.contains(&pane_id) {
client_pane_group.push(pane_id);
}
}
self.retain_only_existing_panes_in_pane_groups();
}
fn add_active_pane_to_group_if_marking(&mut self, client_id: &ClientId) {
{
if self
.currently_marking_pane_group
.borrow()
.get(client_id)
.copied()
.unwrap_or(false)
{
let active_pane_id = self.get_active_pane_id(&client_id);
if let Some(active_pane_id) = active_pane_id {
self.add_pane_id_to_group(active_pane_id, &client_id);
}
}
}
self.retain_only_existing_panes_in_pane_groups();
}
fn get_active_pane_id(&self, client_id: &ClientId) -> Option<PaneId> {
let active_tab = self.get_active_tab(*client_id).ok()?;
active_tab.get_active_pane_id(*client_id)
}
fn group_and_ungroup_panes(
&mut self,
mut pane_ids_to_group: Vec<PaneId>,
pane_ids_to_ungroup: Vec<PaneId>,
client_id: ClientId,
) {
{
let mut current_pane_group = self.current_pane_group.borrow_mut();
let client_pane_group = current_pane_group
.entry(client_id)
.or_insert_with(|| vec![]);
client_pane_group.append(&mut pane_ids_to_group);
client_pane_group.retain(|p| !pane_ids_to_ungroup.contains(p));
}
self.retain_only_existing_panes_in_pane_groups();
let _ = self.log_and_report_session_state();
}
fn retain_only_existing_panes_in_pane_groups(&mut self) {
let clients_with_empty_group = {
let mut clients_with_empty_group = vec![];
let mut current_pane_group = self.current_pane_group.borrow_mut();
for (client_id, panes_in_group) in current_pane_group.iter_mut() {
let all_tabs = self.get_tabs();
panes_in_group.retain(|p_id| {
let mut found = false;
for tab in all_tabs.values() {
if tab.has_pane_with_pid(&p_id) {
found = true;
break;
}
}
found
});
if panes_in_group.is_empty() {
clients_with_empty_group.push(*client_id)
}
}
clients_with_empty_group
};
for client_id in &clients_with_empty_group {
self.currently_marking_pane_group
.borrow_mut()
.remove(client_id);
}
if !clients_with_empty_group.is_empty() {
let all_tabs = self.get_tabs_mut();
for tab in all_tabs.values_mut() {
let _ = tab.update_input_modes();
}
}
}
}
#[cfg(not(test))]
@ -2839,6 +3071,7 @@ pub(crate) fn screen_thread_main(
.unwrap_or(false); // by default, we try to support this if the terminal supports it and
// the program running inside a pane requests it
let stacked_resize = config_options.stacked_resize.unwrap_or(true);
let advanced_mouse_actions = config_options.advanced_mouse_actions.unwrap_or(true);
let thread_senders = bus.senders.clone();
let mut screen = Screen::new(
@ -2872,6 +3105,7 @@ pub(crate) fn screen_thread_main(
explicitly_disable_kitty_keyboard_protocol,
stacked_resize,
default_editor,
advanced_mouse_actions,
);
let mut pending_tab_ids: HashSet<usize> = HashSet::new();
@ -3053,7 +3287,6 @@ pub(crate) fn screen_thread_main(
.toggle_pane_embed_or_floating(client_id), ?);
screen.unblock_input()?;
screen.log_and_report_session_state()?;
screen.render(None)?;
},
ScreenInstruction::ToggleFloatingPanes(client_id, default_shell) => {
@ -3195,12 +3428,14 @@ pub(crate) fn screen_thread_main(
|tab: &mut Tab, client_id: ClientId| tab.move_focus_left(client_id),
?
);
screen.add_active_pane_to_group_if_marking(&client_id);
screen.render(None)?;
screen.unblock_input()?;
screen.log_and_report_session_state()?;
},
ScreenInstruction::MoveFocusLeftOrPreviousTab(client_id) => {
screen.move_focus_left_or_previous_tab(client_id)?;
screen.add_active_pane_to_group_if_marking(&client_id);
screen.unblock_input()?;
screen.render(None)?;
screen.log_and_report_session_state()?;
@ -3212,6 +3447,7 @@ pub(crate) fn screen_thread_main(
|tab: &mut Tab, client_id: ClientId| tab.move_focus_down(client_id),
?
);
screen.add_active_pane_to_group_if_marking(&client_id);
screen.render(None)?;
screen.unblock_input()?;
screen.log_and_report_session_state()?;
@ -3223,12 +3459,14 @@ pub(crate) fn screen_thread_main(
|tab: &mut Tab, client_id: ClientId| tab.move_focus_right(client_id),
?
);
screen.add_active_pane_to_group_if_marking(&client_id);
screen.render(None)?;
screen.unblock_input()?;
screen.log_and_report_session_state()?;
},
ScreenInstruction::MoveFocusRightOrNextTab(client_id) => {
screen.move_focus_right_or_next_tab(client_id)?;
screen.add_active_pane_to_group_if_marking(&client_id);
screen.unblock_input()?;
screen.render(None)?;
screen.log_and_report_session_state()?;
@ -3240,6 +3478,7 @@ pub(crate) fn screen_thread_main(
|tab: &mut Tab, client_id: ClientId| tab.move_focus_up(client_id),
?
);
screen.add_active_pane_to_group_if_marking(&client_id);
screen.render(None)?;
screen.unblock_input()?;
screen.log_and_report_session_state()?;
@ -3546,6 +3785,7 @@ pub(crate) fn screen_thread_main(
screen.unblock_input()?;
screen.log_and_report_session_state()?;
screen.retain_only_existing_panes_in_pane_groups();
},
ScreenInstruction::HoldPane(id, exit_status, run_command) => {
let is_first_run = false;
@ -3863,31 +4103,7 @@ pub(crate) fn screen_thread_main(
screen.unblock_input()?;
},
ScreenInstruction::MouseEvent(event, client_id) => {
match screen
.get_active_tab_mut(client_id)
.and_then(|tab| tab.handle_mouse_event(&event, client_id))
{
Ok(mouse_effect) => {
if mouse_effect.state_changed {
screen.log_and_report_session_state()?;
}
if !mouse_effect.leave_clipboard_message {
let _ =
screen
.bus
.senders
.send_to_plugin(PluginInstruction::Update(vec![(
None,
Some(client_id),
Event::InputReceived,
)]));
}
screen.render(None).non_fatal();
},
Err(e) => {
log::error!("Failed to process MouseEvent: {}", e);
},
}
screen.handle_mouse_event(event, client_id);
},
ScreenInstruction::Copy(client_id) => {
active_tab!(screen, client_id, |tab: &mut Tab| tab
@ -4019,12 +4235,28 @@ pub(crate) fn screen_thread_main(
}
screen.render(None)?;
},
ScreenInstruction::AddHighlightPaneFrameColorOverride(pane_ids, error_text) => {
let all_tabs = screen.get_tabs_mut();
for pane_id in pane_ids {
for tab in all_tabs.values_mut() {
if tab.has_pane_with_pid(&pane_id) {
tab.add_highlight_pane_frame_color_override(
pane_id,
error_text.clone(),
None,
);
break;
}
}
}
screen.render(None)?;
},
ScreenInstruction::ClearPaneFrameColorOverride(pane_ids) => {
let all_tabs = screen.get_tabs_mut();
for pane_id in pane_ids {
for tab in all_tabs.values_mut() {
if tab.has_pane_with_pid(&pane_id) {
tab.clear_pane_frame_color_override(pane_id);
tab.clear_pane_frame_color_override(pane_id, None);
break;
}
}
@ -4642,6 +4874,7 @@ pub(crate) fn screen_thread_main(
hide_session_name,
stacked_resize,
default_editor,
advanced_mouse_actions,
} => {
screen
.reconfigure(
@ -4659,6 +4892,7 @@ pub(crate) fn screen_thread_main(
hide_session_name,
stacked_resize,
default_editor,
advanced_mouse_actions,
client_id,
)
.non_fatal();
@ -4845,7 +5079,7 @@ pub(crate) fn screen_thread_main(
let all_tabs = screen.get_tabs_mut();
for tab in all_tabs.values_mut() {
if tab.has_pane_with_pid(&pane_id) {
tab.toggle_pane_embed_or_floating_for_pane_id(pane_id)
tab.toggle_pane_embed_or_floating_for_pane_id(pane_id, None)
.non_fatal();
break;
}
@ -4869,6 +5103,17 @@ pub(crate) fn screen_thread_main(
new_tab_name,
client_id,
)?;
// TODO: is this a race?
let pane_group = screen.get_client_pane_group(&client_id);
if !pane_group.is_empty() {
let _ = screen.bus.senders.send_to_background_jobs(
BackgroundJob::HighlightPanesWithMessage(
pane_group.iter().copied().collect(),
"BROKEN OUT".to_owned(),
),
);
}
screen.clear_pane_group(&client_id);
},
ScreenInstruction::BreakPanesToTabWithIndex {
pane_ids,
@ -4882,6 +5127,16 @@ pub(crate) fn screen_thread_main(
should_change_focus_to_new_tab,
client_id,
)?;
let pane_group = screen.get_client_pane_group(&client_id);
if !pane_group.is_empty() {
let _ = screen.bus.senders.send_to_background_jobs(
BackgroundJob::HighlightPanesWithMessage(
pane_group.iter().copied().collect(),
"BROKEN OUT".to_owned(),
),
);
}
screen.clear_pane_group(&client_id);
},
ScreenInstruction::TogglePanePinned(client_id) => {
screen.toggle_pane_pinned(client_id);
@ -4889,16 +5144,133 @@ pub(crate) fn screen_thread_main(
ScreenInstruction::SetFloatingPanePinned(pane_id, should_be_pinned) => {
screen.set_floating_pane_pinned(pane_id, should_be_pinned);
},
ScreenInstruction::StackPanes(pane_ids_to_stack) => {
screen.stack_panes(pane_ids_to_stack);
let _ = screen.unblock_input();
let _ = screen.render(None);
ScreenInstruction::StackPanes(pane_ids_to_stack, client_id) => {
if let Some(root_pane_id) = screen.stack_panes(pane_ids_to_stack) {
let _ = screen.focus_pane_with_id(root_pane_id, false, client_id);
let _ = screen.unblock_input();
let _ = screen.render(None);
let pane_group = screen.get_client_pane_group(&client_id);
if !pane_group.is_empty() {
let _ = screen.bus.senders.send_to_background_jobs(
BackgroundJob::HighlightPanesWithMessage(
pane_group.iter().copied().collect(),
"STACKED".to_owned(),
),
);
}
screen.clear_pane_group(&client_id);
}
},
ScreenInstruction::ChangeFloatingPanesCoordinates(pane_ids_and_coordinates) => {
screen.change_floating_panes_coordinates(pane_ids_and_coordinates);
let _ = screen.unblock_input();
let _ = screen.render(None);
},
ScreenInstruction::GroupAndUngroupPanes(
pane_ids_to_group,
pane_ids_to_ungroup,
client_id,
) => {
screen.group_and_ungroup_panes(pane_ids_to_group, pane_ids_to_ungroup, client_id);
let _ = screen.log_and_report_session_state();
},
ScreenInstruction::TogglePaneInGroup(client_id) => {
screen.toggle_pane_in_group(client_id).non_fatal();
},
ScreenInstruction::ToggleGroupMarking(client_id) => {
screen.toggle_group_marking(client_id).non_fatal();
},
ScreenInstruction::HighlightAndUnhighlightPanes(
pane_ids_to_highlight,
pane_ids_to_unhighlight,
client_id,
) => {
{
let all_tabs = screen.get_tabs_mut();
for pane_id in pane_ids_to_highlight {
for tab in all_tabs.values_mut() {
if tab.has_pane_with_pid(&pane_id) {
tab.add_highlight_pane_frame_color_override(
pane_id,
None,
Some(client_id),
);
}
}
}
for pane_id in pane_ids_to_unhighlight {
for tab in all_tabs.values_mut() {
if tab.has_pane_with_pid(&pane_id) {
tab.clear_pane_frame_color_override(pane_id, Some(client_id));
}
}
}
screen.render(None)?;
}
let _ = screen.log_and_report_session_state();
},
ScreenInstruction::FloatMultiplePanes(pane_ids_to_float, client_id) => {
{
let all_tabs = screen.get_tabs_mut();
let mut ejected_panes_in_group = vec![];
for pane_id in pane_ids_to_float {
for tab in all_tabs.values_mut() {
if tab.has_pane_with_pid(&pane_id) {
if !tab.pane_id_is_floating(&pane_id) {
ejected_panes_in_group.push(pane_id);
tab.toggle_pane_embed_or_floating_for_pane_id(
pane_id,
Some(client_id),
)
.non_fatal();
}
tab.show_floating_panes();
}
}
}
screen.render(None)?;
if !ejected_panes_in_group.is_empty() {
let _ = screen.bus.senders.send_to_background_jobs(
BackgroundJob::HighlightPanesWithMessage(
ejected_panes_in_group,
"EJECTED".to_owned(),
),
);
}
}
let _ = screen.log_and_report_session_state();
},
ScreenInstruction::EmbedMultiplePanes(pane_ids_to_float, client_id) => {
{
let all_tabs = screen.get_tabs_mut();
let mut embedded_panes_in_group = vec![];
for pane_id in pane_ids_to_float {
for tab in all_tabs.values_mut() {
if tab.has_pane_with_pid(&pane_id) {
if tab.pane_id_is_floating(&pane_id) {
embedded_panes_in_group.push(pane_id);
tab.toggle_pane_embed_or_floating_for_pane_id(
pane_id,
Some(client_id),
)
.non_fatal();
}
tab.hide_floating_panes();
}
}
}
screen.render(None)?;
if !embedded_panes_in_group.is_empty() {
let _ = screen.bus.senders.send_to_background_jobs(
BackgroundJob::HighlightPanesWithMessage(
embedded_panes_in_group,
"EMBEDDED".to_owned(),
),
);
}
}
let _ = screen.log_and_report_session_state();
},
}
}
Ok(())

View file

@ -46,7 +46,7 @@ use std::cell::RefCell;
use std::rc::Rc;
use std::time::Instant;
use std::{
collections::{HashMap, HashSet},
collections::{BTreeMap, HashMap, HashSet},
str,
};
use zellij_utils::{
@ -152,6 +152,9 @@ enum BufferedTabInstruction {
pub struct MouseEffect {
pub state_changed: bool,
pub leave_clipboard_message: bool,
pub group_toggle: Option<PaneId>,
pub group_add: Option<PaneId>,
pub ungroup: bool,
}
impl MouseEffect {
@ -159,18 +162,54 @@ impl MouseEffect {
MouseEffect {
state_changed: true,
leave_clipboard_message: false,
group_toggle: None,
group_add: None,
ungroup: false,
}
}
pub fn leave_clipboard_message() -> Self {
MouseEffect {
state_changed: false,
leave_clipboard_message: true,
group_toggle: None,
group_add: None,
ungroup: false,
}
}
pub fn state_changed_and_leave_clipboard_message() -> Self {
MouseEffect {
state_changed: true,
leave_clipboard_message: true,
group_toggle: None,
group_add: None,
ungroup: false,
}
}
pub fn group_toggle(pane_id: PaneId) -> Self {
MouseEffect {
state_changed: true,
leave_clipboard_message: false,
group_toggle: Some(pane_id),
group_add: None,
ungroup: false,
}
}
pub fn group_add(pane_id: PaneId) -> Self {
MouseEffect {
state_changed: true,
leave_clipboard_message: false,
group_toggle: None,
group_add: Some(pane_id),
ungroup: false,
}
}
pub fn ungroup() -> Self {
MouseEffect {
state_changed: true,
leave_clipboard_message: false,
group_toggle: None,
group_add: None,
ungroup: true,
}
}
}
@ -222,6 +261,10 @@ pub(crate) struct Tab {
arrow_fonts: bool,
styled_underlines: bool,
explicitly_disable_kitty_keyboard_protocol: bool,
mouse_hover_pane_id: HashMap<ClientId, PaneId>,
current_pane_group: Rc<RefCell<HashMap<ClientId, Vec<PaneId>>>>,
advanced_mouse_actions: bool,
currently_marking_pane_group: Rc<RefCell<HashMap<ClientId, bool>>>,
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
@ -525,7 +568,13 @@ pub trait Pane {
// No-op by default, only terminal panes support holding
}
fn add_red_pane_frame_color_override(&mut self, _error_text: Option<String>);
fn clear_pane_frame_color_override(&mut self);
fn add_highlight_pane_frame_color_override(
&mut self,
_text: Option<String>,
_client_id: Option<ClientId>,
) {
}
fn clear_pane_frame_color_override(&mut self, _client_id: Option<ClientId>);
fn frame_color_override(&self) -> Option<PaletteColor>;
fn invoked_with(&self) -> &Option<Run>;
fn set_title(&mut self, title: String);
@ -626,6 +675,9 @@ impl Tab {
styled_underlines: bool,
explicitly_disable_kitty_keyboard_protocol: bool,
default_editor: Option<PathBuf>,
current_pane_group: Rc<RefCell<HashMap<ClientId, Vec<PaneId>>>>,
currently_marking_pane_group: Rc<RefCell<HashMap<ClientId, bool>>>,
advanced_mouse_actions: bool,
) -> Self {
let name = if name.is_empty() {
format!("Tab #{}", index + 1)
@ -720,6 +772,10 @@ impl Tab {
styled_underlines,
explicitly_disable_kitty_keyboard_protocol,
default_editor,
mouse_hover_pane_id: HashMap::new(),
current_pane_group,
currently_marking_pane_group,
advanced_mouse_actions,
}
}
@ -829,6 +885,9 @@ impl Tab {
.non_fatal();
}
self.set_force_render();
self.senders
.send_to_pty_writer(PtyWriteInstruction::ApplyCachedResizes)
.with_context(|| format!("failed to apply cached resizes"))?;
Ok(())
}
fn relayout_tiled_panes(&mut self, search_backwards: bool) -> Result<()> {
@ -873,6 +932,9 @@ impl Tab {
// we do this so that the new swap layout has a chance to pass through the constraint system
self.tiled_panes.resize(display_area);
self.set_should_clear_display_before_rendering();
self.senders
.send_to_pty_writer(PtyWriteInstruction::ApplyCachedResizes)
.with_context(|| format!("failed to apply cached resizes"))?;
Ok(())
}
pub fn previous_swap_layout(&mut self) -> Result<()> {
@ -882,9 +944,6 @@ impl Tab {
} else {
self.relayout_tiled_panes(search_backwards)?;
}
self.senders
.send_to_pty_writer(PtyWriteInstruction::ApplyCachedResizes)
.with_context(|| format!("failed to update plugins with mode info"))?;
Ok(())
}
pub fn next_swap_layout(&mut self) -> Result<()> {
@ -894,9 +953,6 @@ impl Tab {
} else {
self.relayout_tiled_panes(search_backwards)?;
}
self.senders
.send_to_pty_writer(PtyWriteInstruction::ApplyCachedResizes)
.with_context(|| format!("failed to update plugins with mode info"))?;
Ok(())
}
pub fn apply_buffered_instructions(&mut self) -> Result<()> {
@ -936,6 +992,7 @@ impl Tab {
// this updates all plugins with the client's input mode
let mode_infos = self.mode_info.borrow();
let mut plugin_updates = vec![];
let currently_marking_pane_group = self.currently_marking_pane_group.borrow();
for client_id in self.connected_clients.borrow().iter() {
let mut mode_info = mode_infos
.get(client_id)
@ -943,6 +1000,8 @@ impl Tab {
.clone();
mode_info.shell = Some(self.default_shell.clone());
mode_info.editor = self.default_editor.clone();
mode_info.currently_marking_pane_group =
currently_marking_pane_group.get(client_id).copied();
plugin_updates.push((None, Some(*client_id), Event::ModeUpdate(mode_info)));
}
self.senders
@ -1044,6 +1103,9 @@ impl Tab {
pub fn has_no_connected_clients(&self) -> bool {
self.connected_clients.borrow().is_empty()
}
pub fn pane_id_is_floating(&self, pane_id: &PaneId) -> bool {
self.floating_panes.panes_contain(pane_id)
}
pub fn toggle_pane_embed_or_floating(&mut self, client_id: ClientId) -> Result<()> {
let err_context =
|| format!("failed to toggle embedded/floating pane for client {client_id}");
@ -1079,7 +1141,11 @@ impl Tab {
}
Ok(())
}
pub fn toggle_pane_embed_or_floating_for_pane_id(&mut self, pane_id: PaneId) -> Result<()> {
pub fn toggle_pane_embed_or_floating_for_pane_id(
&mut self,
pane_id: PaneId,
client_id: Option<ClientId>,
) -> Result<()> {
let err_context = || {
format!(
"failed to toggle embedded/floating pane for pane_id {:?}",
@ -1097,7 +1163,7 @@ impl Tab {
format!("failed to find floating pane (ID: {pane_id:?}) to embed",)
})
.with_context(err_context)?;
self.add_tiled_pane(floating_pane_to_embed, pane_id, None)?;
self.add_tiled_pane(floating_pane_to_embed, pane_id, client_id)?;
}
} else if self.tiled_panes.panes_contain(&pane_id) {
if self.get_selectable_tiled_panes().count() <= 1 {
@ -2210,14 +2276,21 @@ impl Tab {
floating_panes_stack,
);
let current_pane_group: HashMap<ClientId, Vec<PaneId>> =
{ self.current_pane_group.borrow().clone() };
self.tiled_panes
.render(output, self.floating_panes.panes_are_visible())
.render(
output,
self.floating_panes.panes_are_visible(),
&self.mouse_hover_pane_id,
current_pane_group.clone(),
)
.with_context(err_context)?;
if (self.floating_panes.panes_are_visible() && self.floating_panes.has_active_panes())
|| self.floating_panes.has_pinned_panes()
{
self.floating_panes
.render(output)
.render(output, &self.mouse_hover_pane_id, current_pane_group)
.with_context(err_context)?;
}
@ -2817,7 +2890,7 @@ impl Tab {
self.swap_layouts.set_is_floating_damaged();
// only relayout if the user is already "in" a layout, otherwise this might be
// confusing
let _ = self.next_swap_layout();
let _ = self.relayout_floating_panes(false);
}
} else {
if self.tiled_panes.fullscreen_is_active() {
@ -2830,7 +2903,7 @@ impl Tab {
self.swap_layouts.set_is_tiled_damaged();
// only relayout if the user is already "in" a layout, otherwise this might be
// confusing
let _ = self.next_swap_layout();
let _ = self.relayout_tiled_panes(false);
}
};
let _ = self.senders.send_to_plugin(PluginInstruction::Update(vec![(
@ -2878,7 +2951,7 @@ impl Tab {
self.swap_layouts.set_is_floating_damaged();
// only relayout if the user is already "in" a layout, otherwise this might be
// confusing
let _ = self.next_swap_layout();
let _ = self.relayout_floating_panes(false);
}
// we do this so that the logical index will not affect ordering in the target tab
if let Some(closed_pane) = closed_pane.as_mut() {
@ -2896,7 +2969,7 @@ impl Tab {
self.swap_layouts.set_is_tiled_damaged();
// only relayout if the user is already "in" a layout, otherwise this might be
// confusing
let _ = self.next_swap_layout();
let _ = self.relayout_tiled_panes(false);
}
// we do this so that the logical index will not affect ordering in the target tab
if let Some(closed_pane) = closed_pane.as_mut() {
@ -3460,6 +3533,13 @@ impl Tab {
.ok_or_else(|| anyhow!("Failed to find pane at position"))?
.pid();
match event.event_type {
MouseEventType::Press if event.alt => {
self.mouse_hover_pane_id.remove(&client_id);
Ok(MouseEffect::group_toggle(pane_id_at_position))
},
MouseEventType::Motion if event.alt => {
Ok(MouseEffect::group_add(pane_id_at_position))
},
MouseEventType::Press => {
if pane_id_at_position == active_pane_id {
self.handle_active_pane_left_mouse_press(event, client_id)
@ -3474,6 +3554,9 @@ impl Tab {
self.handle_scrollwheel_up(&event.position, 3, client_id)
} else if event.wheel_down {
self.handle_scrollwheel_down(&event.position, 3, client_id)
} else if event.right && event.alt {
self.mouse_hover_pane_id.remove(&client_id);
Ok(MouseEffect::ungroup())
} else if event.right {
self.handle_right_click(&event, client_id)
} else if event.middle {
@ -3838,6 +3921,12 @@ impl Tab {
.with_context(err_context)?;
}
}
self.mouse_hover_pane_id.remove(&client_id);
} else {
let pane_id = pane.pid();
if self.advanced_mouse_actions {
self.mouse_hover_pane_id.insert(client_id, pane_id);
}
}
};
Ok(MouseEffect::leave_clipboard_message())
@ -4024,7 +4113,7 @@ impl Tab {
Ok(())
}
pub fn visible(&self, visible: bool) -> Result<()> {
pub fn visible(&mut self, visible: bool) -> Result<()> {
let pids_in_this_tab = self.tiled_panes.pane_ids().filter_map(|p| match p {
PaneId::Plugin(pid) => Some(pid),
_ => None,
@ -4036,6 +4125,9 @@ impl Tab {
self.senders
.send_to_plugin(PluginInstruction::Update(plugin_updates))
.with_context(|| format!("failed to set visibility of tab to {visible}"))?;
if !visible {
self.mouse_hover_pane_id.clear();
}
Ok(())
}
@ -4200,7 +4292,12 @@ impl Tab {
pane.add_red_pane_frame_color_override(error_text);
}
}
pub fn clear_pane_frame_color_override(&mut self, pane_id: PaneId) {
pub fn add_highlight_pane_frame_color_override(
&mut self,
pane_id: PaneId,
error_text: Option<String>,
client_id: Option<ClientId>,
) {
if let Some(pane) = self
.tiled_panes
.get_pane_mut(pane_id)
@ -4212,7 +4309,26 @@ impl Tab {
.map(|s_p| &mut s_p.1)
})
{
pane.clear_pane_frame_color_override();
pane.add_highlight_pane_frame_color_override(error_text, client_id);
}
}
pub fn clear_pane_frame_color_override(
&mut self,
pane_id: PaneId,
client_id: Option<ClientId>,
) {
if let Some(pane) = self
.tiled_panes
.get_pane_mut(pane_id)
.or_else(|| self.floating_panes.get_pane_mut(pane_id))
.or_else(|| {
self.suppressed_panes
.values_mut()
.find(|s_p| s_p.1.pid() == pane_id)
.map(|s_p| &mut s_p.1)
})
{
pane.clear_pane_frame_color_override(client_id);
}
}
pub fn update_plugin_loading_stage(&mut self, pid: u32, loading_indication: LoadingIndication) {
@ -4351,12 +4467,14 @@ impl Tab {
}
pub fn pane_infos(&self) -> Vec<PaneInfo> {
let mut pane_info = vec![];
let mut tiled_pane_info = self.tiled_panes.pane_info();
let mut floating_pane_info = self.floating_panes.pane_info();
let current_pane_group = { self.current_pane_group.borrow().clone() };
let mut tiled_pane_info = self.tiled_panes.pane_info(&current_pane_group);
let mut floating_pane_info = self.floating_panes.pane_info(&current_pane_group);
pane_info.append(&mut tiled_pane_info);
pane_info.append(&mut floating_pane_info);
for (pane_id, (_is_scrollback_editor, pane)) in self.suppressed_panes.iter() {
let mut pane_info_for_suppressed_pane = pane_info_for_pane(pane_id, pane);
let mut pane_info_for_suppressed_pane =
pane_info_for_pane(pane_id, pane, &current_pane_group);
pane_info_for_suppressed_pane.is_floating = false;
pane_info_for_suppressed_pane.is_suppressed = true;
pane_info_for_suppressed_pane.is_focused = false;
@ -4397,7 +4515,7 @@ impl Tab {
// confusing and not what the user intends
self.swap_layouts.set_is_floating_damaged(); // we do this so that we won't skip to the
// next layout
self.next_swap_layout()?;
self.relayout_floating_panes(false)?;
}
Ok(())
}
@ -4431,7 +4549,7 @@ impl Tab {
// confusing and not what the user intends
self.swap_layouts.set_is_tiled_damaged(); // we do this so that we won't skip to the
// next layout
self.next_swap_layout()?;
self.relayout_tiled_panes(false)?;
}
Ok(())
}
@ -4589,6 +4707,9 @@ impl Tab {
pub fn update_auto_layout(&mut self, auto_layout: bool) {
self.auto_layout = auto_layout;
}
pub fn update_advanced_mouse_actions(&mut self, advanced_mouse_actions: bool) {
self.advanced_mouse_actions = advanced_mouse_actions;
}
pub fn extract_suppressed_panes(&mut self) -> SuppressedPanes {
self.suppressed_panes.drain().collect()
}
@ -4609,16 +4730,24 @@ impl Tab {
self.set_force_render();
}
}
pub fn has_room_for_stack(&self, root_pane_id: PaneId, stack_size: usize) -> bool {
pub fn has_room_for_stack(&mut self, root_pane_id: PaneId, stack_size: usize) -> bool {
if self.floating_panes.panes_contain(&root_pane_id)
|| self.suppressed_panes.contains_key(&root_pane_id)
{
log::error!("Root pane of stack cannot be floating or suppressed");
return false;
}
self.get_pane_with_id(root_pane_id)
.map(|p| p.position_and_size().rows.as_usize() >= stack_size + MIN_TERMINAL_HEIGHT)
.unwrap_or(false)
if self.pane_is_stacked(root_pane_id) {
let room_left_in_stack = self
.tiled_panes
.room_left_in_stack_of_pane_id(&root_pane_id)
.unwrap_or(0);
stack_size <= room_left_in_stack
} else {
self.get_pane_with_id(root_pane_id)
.map(|p| p.position_and_size().rows.as_usize() >= stack_size + MIN_TERMINAL_HEIGHT)
.unwrap_or(false)
}
}
pub fn set_tiled_panes_damaged(&mut self) {
self.swap_layouts.set_is_tiled_damaged();
@ -4629,33 +4758,53 @@ impl Tab {
return;
}
self.swap_layouts.set_is_tiled_damaged(); // TODO: verify we can do all the below first
// + 1 for the root pane
let mut stack_geoms = self
.tiled_panes
.stack_panes(root_pane_id, panes_to_stack.len() + 1);
if stack_geoms.is_empty() {
log::error!("Failed to find room for stacked panes");
return;
}
self.tiled_panes
.set_geom_for_pane_with_id(&root_pane_id, stack_geoms.remove(0));
let mut focused_pane_id_in_stack = None;
for mut pane in panes_to_stack.drain(..) {
let pane_id = pane.pid();
let stack_geom = stack_geoms.remove(0);
pane.set_geom(stack_geom);
self.tiled_panes.add_pane_with_existing_geom(pane_id, pane);
if self.tiled_panes.pane_id_is_focused(&pane_id) {
focused_pane_id_in_stack = Some(pane_id);
if self.pane_is_stacked(root_pane_id) {
if let Some(lowest_pane_id_in_stack) = self
.tiled_panes
.pane_ids_in_stack_of_pane_id(&root_pane_id)
.last()
{
// we get lowest_pane_id_in_stack so that we can extract the pane below and re-add
// it to its own stack - this has the effect of making it the last pane in the
// stack so that the rest of the panes will later be added below it - which makes
// sense from the perspective of the user
if let Some(pane) = self.extract_pane(root_pane_id, true) {
self.tiled_panes
.add_pane_to_stack(&lowest_pane_id_in_stack, pane);
}
}
for pane in panes_to_stack.drain(..) {
self.tiled_panes.add_pane_to_stack(&root_pane_id, pane);
}
}
// if we had a focused pane in the stack, we expand it
if let Some(focused_pane_id_in_stack) = focused_pane_id_in_stack {
self.tiled_panes
.expand_pane_in_stack(focused_pane_id_in_stack);
} else if self.tiled_panes.pane_id_is_focused(&root_pane_id) {
self.tiled_panes.expand_pane_in_stack(root_pane_id);
} else {
// + 1 for the root pane
let mut stack_geoms = self
.tiled_panes
.stack_panes(root_pane_id, panes_to_stack.len() + 1);
if stack_geoms.is_empty() {
log::error!("Failed to find room for stacked panes");
return;
}
self.tiled_panes
.set_geom_for_pane_with_id(&root_pane_id, stack_geoms.remove(0));
let mut focused_pane_id_in_stack = None;
for mut pane in panes_to_stack.drain(..) {
let pane_id = pane.pid();
let stack_geom = stack_geoms.remove(0);
pane.set_geom(stack_geom);
self.tiled_panes.add_pane_with_existing_geom(pane_id, pane);
if self.tiled_panes.pane_id_is_focused(&pane_id) {
focused_pane_id_in_stack = Some(pane_id);
}
}
// if we had a focused pane in the stack, we expand it
if let Some(focused_pane_id_in_stack) = focused_pane_id_in_stack {
self.tiled_panes
.expand_pane_in_stack(focused_pane_id_in_stack);
} else if self.tiled_panes.pane_id_is_focused(&root_pane_id) {
self.tiled_panes.expand_pane_in_stack(root_pane_id);
}
}
}
pub fn change_floating_pane_coordinates(
@ -4723,9 +4872,18 @@ impl Tab {
(is_scrollback_editor, replaced_pane),
);
}
fn pane_is_stacked(&self, pane_id: PaneId) -> bool {
self.get_pane_with_id(pane_id)
.map(|p| p.position_and_size().stacked.is_some())
.unwrap_or(false)
}
}
pub fn pane_info_for_pane(pane_id: &PaneId, pane: &Box<dyn Pane>) -> PaneInfo {
pub fn pane_info_for_pane(
pane_id: &PaneId,
pane: &Box<dyn Pane>,
current_pane_group: &HashMap<ClientId, Vec<PaneId>>,
) -> PaneInfo {
let mut pane_info = PaneInfo::default();
pane_info.pane_x = pane.x();
pane_info.pane_content_x = pane.get_content_x();
@ -4741,6 +4899,17 @@ pub fn pane_info_for_pane(pane_id: &PaneId, pane: &Box<dyn Pane>) -> PaneInfo {
pane_info.exited = pane.exited();
pane_info.exit_status = pane.exit_status();
pane_info.is_held = pane.is_held();
let index_in_pane_group: BTreeMap<ClientId, usize> = current_pane_group
.iter()
.filter_map(|(client_id, pane_ids)| {
if let Some(position) = pane_ids.iter().position(|p_id| p_id == &pane.pid()) {
Some((*client_id, position))
} else {
None
}
})
.collect();
pane_info.index_in_pane_group = index_in_pane_group;
match pane_id {
PaneId::Terminal(terminal_id) => {

View file

@ -224,10 +224,13 @@ fn create_new_tab(size: Size, default_mode: ModeInfo) -> Tab {
let copy_options = CopyOptions::default();
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
let current_group = Rc::new(RefCell::new(HashMap::new()));
let currently_marking_pane_group = Rc::new(RefCell::new(HashMap::new()));
let debug = false;
let arrow_fonts = true;
let styled_underlines = true;
let explicitly_disable_kitty_keyboard_protocol = false;
let advanced_mouse_actions = true;
let mut tab = Tab::new(
index,
position,
@ -256,6 +259,9 @@ fn create_new_tab(size: Size, default_mode: ModeInfo) -> Tab {
styled_underlines,
explicitly_disable_kitty_keyboard_protocol,
None,
current_group,
currently_marking_pane_group,
advanced_mouse_actions,
);
tab.apply_layout(
TiledPaneLayout::default(),
@ -292,10 +298,13 @@ fn create_new_tab_without_pane_frames(size: Size, default_mode: ModeInfo) -> Tab
let copy_options = CopyOptions::default();
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
let current_group = Rc::new(RefCell::new(HashMap::new()));
let currently_marking_pane_group = Rc::new(RefCell::new(HashMap::new()));
let debug = false;
let arrow_fonts = true;
let styled_underlines = true;
let explicitly_disable_kitty_keyboard_protocol = false;
let advanced_mouse_actions = true;
let mut tab = Tab::new(
index,
position,
@ -324,6 +333,9 @@ fn create_new_tab_without_pane_frames(size: Size, default_mode: ModeInfo) -> Tab
styled_underlines,
explicitly_disable_kitty_keyboard_protocol,
None,
current_group,
currently_marking_pane_group,
advanced_mouse_actions,
);
tab.apply_layout(
TiledPaneLayout::default(),
@ -375,10 +387,13 @@ fn create_new_tab_with_swap_layouts(
let copy_options = CopyOptions::default();
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
let current_group = Rc::new(RefCell::new(HashMap::new()));
let currently_marking_pane_group = Rc::new(RefCell::new(HashMap::new()));
let debug = false;
let arrow_fonts = true;
let styled_underlines = true;
let explicitly_disable_kitty_keyboard_protocol = false;
let advanced_mouse_actions = true;
let mut tab = Tab::new(
index,
position,
@ -407,6 +422,9 @@ fn create_new_tab_with_swap_layouts(
styled_underlines,
explicitly_disable_kitty_keyboard_protocol,
None,
current_group,
currently_marking_pane_group,
advanced_mouse_actions,
);
let (
base_layout,
@ -459,10 +477,13 @@ fn create_new_tab_with_os_api(
let copy_options = CopyOptions::default();
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
let current_group = Rc::new(RefCell::new(HashMap::new()));
let currently_marking_pane_group = Rc::new(RefCell::new(HashMap::new()));
let debug = false;
let arrow_fonts = true;
let styled_underlines = true;
let explicitly_disable_kitty_keyboard_protocol = false;
let advanced_mouse_actions = true;
let mut tab = Tab::new(
index,
position,
@ -491,6 +512,9 @@ fn create_new_tab_with_os_api(
styled_underlines,
explicitly_disable_kitty_keyboard_protocol,
None,
current_group,
currently_marking_pane_group,
advanced_mouse_actions,
);
tab.apply_layout(
TiledPaneLayout::default(),
@ -529,10 +553,13 @@ fn create_new_tab_with_layout(size: Size, default_mode: ModeInfo, layout: &str)
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
let layout = Layout::from_str(layout, "layout_file_name".into(), None, None).unwrap();
let (tab_layout, floating_panes_layout) = layout.new_tab();
let current_group = Rc::new(RefCell::new(HashMap::new()));
let currently_marking_pane_group = Rc::new(RefCell::new(HashMap::new()));
let debug = false;
let arrow_fonts = true;
let styled_underlines = true;
let explicitly_disable_kitty_keyboard_protocol = false;
let advanced_mouse_actions = true;
let mut tab = Tab::new(
index,
position,
@ -561,6 +588,9 @@ fn create_new_tab_with_layout(size: Size, default_mode: ModeInfo, layout: &str)
styled_underlines,
explicitly_disable_kitty_keyboard_protocol,
None,
current_group,
currently_marking_pane_group,
advanced_mouse_actions,
);
let pane_ids = tab_layout
.extract_run_instructions()
@ -613,10 +643,13 @@ fn create_new_tab_with_mock_pty_writer(
let copy_options = CopyOptions::default();
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
let current_group = Rc::new(RefCell::new(HashMap::new()));
let currently_marking_pane_group = Rc::new(RefCell::new(HashMap::new()));
let debug = false;
let arrow_fonts = true;
let styled_underlines = true;
let explicitly_disable_kitty_keyboard_protocol = false;
let advanced_mouse_actions = true;
let mut tab = Tab::new(
index,
position,
@ -645,6 +678,9 @@ fn create_new_tab_with_mock_pty_writer(
styled_underlines,
explicitly_disable_kitty_keyboard_protocol,
None,
current_group,
currently_marking_pane_group,
advanced_mouse_actions,
);
tab.apply_layout(
TiledPaneLayout::default(),
@ -688,10 +724,13 @@ fn create_new_tab_with_sixel_support(
let terminal_emulator_colors = Rc::new(RefCell::new(Palette::default()));
let copy_options = CopyOptions::default();
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
let current_group = Rc::new(RefCell::new(HashMap::new()));
let currently_marking_pane_group = Rc::new(RefCell::new(HashMap::new()));
let debug = false;
let arrow_fonts = true;
let styled_underlines = true;
let explicitly_disable_kitty_keyboard_protocol = false;
let advanced_mouse_actions = true;
let mut tab = Tab::new(
index,
position,
@ -720,6 +759,9 @@ fn create_new_tab_with_sixel_support(
styled_underlines,
explicitly_disable_kitty_keyboard_protocol,
None,
current_group,
currently_marking_pane_group,
advanced_mouse_actions,
);
tab.apply_layout(
TiledPaneLayout::default(),

View file

@ -165,10 +165,13 @@ fn create_new_tab(size: Size, stacked_resize: bool) -> Tab {
let copy_options = CopyOptions::default();
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
let current_pane_group = Rc::new(RefCell::new(HashMap::new()));
let currently_marking_pane_group = Rc::new(RefCell::new(HashMap::new()));
let debug = false;
let arrow_fonts = true;
let styled_underlines = true;
let explicitly_disable_kitty_keyboard_protocol = false;
let advanced_mouse_actions = true;
let mut tab = Tab::new(
index,
position,
@ -197,6 +200,9 @@ fn create_new_tab(size: Size, stacked_resize: bool) -> Tab {
styled_underlines,
explicitly_disable_kitty_keyboard_protocol,
None,
current_pane_group,
currently_marking_pane_group,
advanced_mouse_actions,
);
tab.apply_layout(
TiledPaneLayout::default(),
@ -232,10 +238,13 @@ fn create_new_tab_with_layout(size: Size, layout: TiledPaneLayout) -> Tab {
let copy_options = CopyOptions::default();
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
let current_pane_group = Rc::new(RefCell::new(HashMap::new()));
let currently_marking_pane_group = Rc::new(RefCell::new(HashMap::new()));
let debug = false;
let arrow_fonts = true;
let styled_underlines = true;
let explicitly_disable_kitty_keyboard_protocol = false;
let advanced_mouse_actions = true;
let mut tab = Tab::new(
index,
position,
@ -264,6 +273,9 @@ fn create_new_tab_with_layout(size: Size, layout: TiledPaneLayout) -> Tab {
styled_underlines,
explicitly_disable_kitty_keyboard_protocol,
None,
current_pane_group,
currently_marking_pane_group,
advanced_mouse_actions,
);
let mut new_terminal_ids = vec![];
for i in 0..layout.extract_run_instructions().len() {
@ -304,11 +316,14 @@ fn create_new_tab_with_cell_size(
let copy_options = CopyOptions::default();
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
let stacked_resize = Rc::new(RefCell::new(true));
let current_pane_group = Rc::new(RefCell::new(HashMap::new()));
let currently_marking_pane_group = Rc::new(RefCell::new(HashMap::new()));
let debug = false;
let arrow_fonts = true;
let styled_underlines = true;
let explicitly_disable_kitty_keyboard_protocol = false;
let stacked_resize = Rc::new(RefCell::new(true));
let advanced_mouse_actions = true;
let mut tab = Tab::new(
index,
position,
@ -337,6 +352,9 @@ fn create_new_tab_with_cell_size(
styled_underlines,
explicitly_disable_kitty_keyboard_protocol,
None,
current_pane_group,
currently_marking_pane_group,
advanced_mouse_actions,
);
tab.apply_layout(
TiledPaneLayout::default(),

View file

@ -33,7 +33,7 @@ pub type BoundaryType = &'static str; // easy way to refer to boundary_type abov
pub struct BoundarySymbol {
boundary_type: BoundaryType,
invisible: bool,
color: Option<PaletteColor>,
color: Option<(PaletteColor, usize)>, // (color, color_precedence)
}
impl BoundarySymbol {
@ -41,10 +41,10 @@ impl BoundarySymbol {
BoundarySymbol {
boundary_type,
invisible: false,
color: Some(PaletteColor::EightBit(colors::GRAY)),
color: Some((PaletteColor::EightBit(colors::GRAY), 0)),
}
}
pub fn color(&mut self, color: Option<PaletteColor>) -> Self {
pub fn color(&mut self, color: Option<(PaletteColor, usize)>) -> Self {
self.color = color;
*self
}
@ -66,7 +66,7 @@ impl BoundarySymbol {
TerminalCharacter::new_singlewidth_styled(
character,
RESET_STYLES
.foreground(self.color.map(|palette_color| palette_color.into()))
.foreground(self.color.map(|palette_color| palette_color.0.into()))
.into(),
)
};
@ -79,7 +79,7 @@ impl Display for BoundarySymbol {
match self.invisible {
true => write!(f, " "),
false => match self.color {
Some(color) => match color {
Some(color) => match color.0 {
PaletteColor::Rgb((r, g, b)) => {
write!(f, "{}", RGB(r, g, b).paint(self.boundary_type))
},
@ -99,7 +99,17 @@ fn combine_symbols(
) -> Option<BoundarySymbol> {
use boundary_type::*;
let invisible = current_symbol.invisible || next_symbol.invisible;
let color = current_symbol.color.or(next_symbol.color);
let color = match (current_symbol.color, next_symbol.color) {
(Some(current_symbol_color), Some(next_symbol_color)) => {
let ret = if current_symbol_color.1 >= next_symbol_color.1 {
Some(current_symbol_color)
} else {
Some(next_symbol_color)
};
ret
},
_ => current_symbol.color.or(next_symbol.color),
};
match (current_symbol.boundary_type, next_symbol.boundary_type) {
(CROSS, _) | (_, CROSS) => {
// (┼, *) or (*, ┼) => Some(┼)
@ -447,7 +457,7 @@ impl Boundaries {
pub fn add_rect(
&mut self,
rect: &dyn Pane,
color: Option<PaletteColor>,
color: Option<(PaletteColor, usize)>, // (color, color_precedence)
pane_is_on_top_of_stack: bool,
pane_is_on_bottom_of_stack: bool,
pane_is_stacked_under: bool,

View file

@ -59,13 +59,13 @@ pub fn stringify_text(
}
text_width += character_width;
if text.selected {
if text.selected || text.opaque {
// we do this so that selected text will appear selected
// even if it does not have color indices
stringified.push_str(&format!("{}", base_text_style));
}
if !text.indices.is_empty() || text.selected {
if !text.indices.is_empty() || text.selected || text.opaque {
let character_with_styling =
color_index_character(character, i, &text, style, base_text_style);
stringified.push_str(&character_with_styling);

View file

@ -53,7 +53,7 @@ enum ExitStatus {
pub struct FrameParams {
pub focused_client: Option<ClientId>,
pub is_main_client: bool,
pub is_main_client: bool, // more accurately: is_focused_for_main_client
pub other_focused_clients: Vec<ClientId>,
pub style: Style,
pub color: Option<PaletteColor>,
@ -63,6 +63,7 @@ pub struct FrameParams {
pub should_draw_pane_frames: bool,
pub pane_is_floating: bool,
pub content_offset: Offset,
pub mouse_is_hovering_over_pane: bool,
}
#[derive(Default, PartialEq)]
@ -84,6 +85,7 @@ pub struct PaneFrame {
is_pinned: bool,
is_floating: bool,
content_offset: Offset,
mouse_is_hovering_over_pane: bool,
}
impl PaneFrame {
@ -111,6 +113,7 @@ impl PaneFrame {
is_pinned: false,
is_floating: frame_params.pane_is_floating,
content_offset: frame_params.content_offset,
mouse_is_hovering_over_pane: frame_params.mouse_is_hovering_over_pane,
}
}
pub fn is_pinned(mut self, is_pinned: bool) -> Self {
@ -755,6 +758,34 @@ impl PaneFrame {
};
Ok(res)
}
fn render_mouse_shortcuts_undertitle(&self) -> Result<Vec<TerminalCharacter>> {
let max_undertitle_length = self.geom.cols.saturating_sub(2); // 2 for the left and right corners
let mut left_boundary =
foreground_color(self.get_corner(boundary_type::BOTTOM_LEFT), self.color);
let mut right_boundary =
foreground_color(self.get_corner(boundary_type::BOTTOM_RIGHT), self.color);
let res = if self.is_main_client {
self.empty_undertitle(max_undertitle_length)
} else {
let (mut hover_shortcuts, hover_shortcuts_len) = self.hover_shortcuts_part_full();
if hover_shortcuts_len <= max_undertitle_length {
// render exit status and tips
let mut padding = String::new();
for _ in hover_shortcuts_len..max_undertitle_length {
padding.push_str(boundary_type::HORIZONTAL);
}
let mut ret = vec![];
ret.append(&mut left_boundary);
ret.append(&mut hover_shortcuts);
ret.append(&mut foreground_color(&padding, self.color));
ret.append(&mut right_boundary);
ret
} else {
self.empty_undertitle(max_undertitle_length)
}
};
Ok(res)
}
pub fn clicked_on_pinned(&mut self, position: Position) -> bool {
if self.is_floating {
// TODO: this is not entirely accurate because our relative position calculation in
@ -810,7 +841,16 @@ impl PaneFrame {
character_chunks.push(CharacterChunk::new(title, x, y));
} else if row == self.geom.rows - 1 {
// bottom row
if self.exit_status.is_some() || self.is_first_run {
if self.mouse_is_hovering_over_pane && !self.is_main_client {
let x = self.geom.x;
let y = self.geom.y + row;
character_chunks.push(CharacterChunk::new(
self.render_mouse_shortcuts_undertitle()
.with_context(err_context)?,
x,
y,
));
} else if self.exit_status.is_some() || self.is_first_run {
let x = self.geom.x;
let y = self.geom.y + row;
character_chunks.push(CharacterChunk::new(
@ -964,6 +1004,26 @@ impl PaneFrame {
+ break_tip.len(),
)
}
fn hover_shortcuts_part_full(&self) -> (Vec<TerminalCharacter>, usize) {
// (title part, length)
let mut hover_shortcuts = vec![];
let alt_click_text = " Alt <Click>";
let alt_click_tip = " - group,";
let alt_right_click_text = " Alt <Right-Click>";
let alt_right_click_tip = " - ungroup all ";
hover_shortcuts.append(&mut foreground_color(alt_click_text, self.color));
hover_shortcuts.append(&mut foreground_color(alt_click_tip, self.color));
hover_shortcuts.append(&mut foreground_color(alt_right_click_text, self.color));
hover_shortcuts.append(&mut foreground_color(alt_right_click_tip, self.color));
(
hover_shortcuts,
alt_click_text.chars().count()
+ alt_click_tip.chars().count()
+ alt_right_click_text.chars().count()
+ alt_right_click_tip.chars().count(),
)
}
fn empty_undertitle(&self, max_undertitle_length: usize) -> Vec<TerminalCharacter> {
let mut left_boundary =
foreground_color(self.get_corner(boundary_type::BOTTOM_LEFT), self.color);

View file

@ -4,7 +4,7 @@ use crate::tab::Pane;
use crate::ui::boundaries::Boundaries;
use crate::ui::pane_boundaries_frame::FrameParams;
use crate::ClientId;
use std::collections::HashMap;
use std::collections::{HashMap, HashSet};
use zellij_utils::data::{client_id_to_colors, InputMode, PaletteColor, Style};
use zellij_utils::errors::prelude::*;
pub struct PaneContentsAndUi<'a> {
@ -17,6 +17,8 @@ pub struct PaneContentsAndUi<'a> {
pane_is_stacked_under: bool,
pane_is_stacked_over: bool,
should_draw_pane_frames: bool,
mouse_is_hovering_over_pane_for_clients: HashSet<ClientId>,
current_pane_group: HashMap<ClientId, Vec<PaneId>>,
}
impl<'a> PaneContentsAndUi<'a> {
@ -30,6 +32,8 @@ impl<'a> PaneContentsAndUi<'a> {
pane_is_stacked_under: bool,
pane_is_stacked_over: bool,
should_draw_pane_frames: bool,
mouse_hover_pane_id: &HashMap<ClientId, PaneId>,
current_pane_group: HashMap<ClientId, Vec<PaneId>>,
) -> Self {
let mut focused_clients: Vec<ClientId> = active_panes
.iter()
@ -37,6 +41,16 @@ impl<'a> PaneContentsAndUi<'a> {
.map(|(c_id, _p_id)| *c_id)
.collect();
focused_clients.sort_unstable();
let mouse_is_hovering_over_pane_for_clients = mouse_hover_pane_id
.iter()
.filter_map(|(client_id, pane_id)| {
if pane_id == &pane.pid() {
Some(*client_id)
} else {
None
}
})
.collect();
PaneContentsAndUi {
pane,
output,
@ -47,6 +61,8 @@ impl<'a> PaneContentsAndUi<'a> {
pane_is_stacked_under,
pane_is_stacked_over,
should_draw_pane_frames,
mouse_is_hovering_over_pane_for_clients,
current_pane_group,
}
}
pub fn render_pane_contents_to_multiple_clients(
@ -217,13 +233,16 @@ impl<'a> PaneContentsAndUi<'a> {
is_main_client: pane_focused_for_client_id,
other_focused_clients: vec![],
style: self.style,
color: frame_color,
color: frame_color.map(|c| c.0),
other_cursors_exist_in_session: false,
pane_is_stacked_over: self.pane_is_stacked_over,
pane_is_stacked_under: self.pane_is_stacked_under,
should_draw_pane_frames: self.should_draw_pane_frames,
pane_is_floating,
content_offset: self.pane.get_content_offset(),
mouse_is_hovering_over_pane: self
.mouse_is_hovering_over_pane_for_clients
.contains(&client_id),
}
} else {
FrameParams {
@ -231,13 +250,16 @@ impl<'a> PaneContentsAndUi<'a> {
is_main_client: pane_focused_for_client_id,
other_focused_clients,
style: self.style,
color: frame_color,
color: frame_color.map(|c| c.0),
other_cursors_exist_in_session: self.multiple_users_exist_in_session,
pane_is_stacked_over: self.pane_is_stacked_over,
pane_is_stacked_under: self.pane_is_stacked_under,
should_draw_pane_frames: self.should_draw_pane_frames,
pane_is_floating,
content_offset: self.pane.get_content_offset(),
mouse_is_hovering_over_pane: self
.mouse_is_hovering_over_pane_for_clients
.contains(&client_id),
}
};
@ -279,27 +301,48 @@ impl<'a> PaneContentsAndUi<'a> {
client_id: ClientId,
mode: InputMode,
session_is_mirrored: bool,
) -> Option<PaletteColor> {
) -> Option<(PaletteColor, usize)> {
// (color, color_precedence) (the color_precedence is used
// for the no-pane-frames mode)
let pane_focused_for_client_id = self.focused_clients.contains(&client_id);
if let Some(override_color) = self.pane.frame_color_override() {
Some(override_color)
let pane_is_in_group = self
.current_pane_group
.get(&client_id)
.map(|p| p.contains(&self.pane.pid()))
.unwrap_or(false);
if self.pane.frame_color_override().is_some() && !pane_is_in_group {
self.pane
.frame_color_override()
.map(|override_color| (override_color, 4))
} else if pane_is_in_group && !pane_focused_for_client_id {
Some((self.style.colors.frame_highlight.emphasis_0, 2))
} else if pane_is_in_group && pane_focused_for_client_id {
Some((self.style.colors.frame_highlight.emphasis_1, 3))
} else if pane_focused_for_client_id {
match mode {
InputMode::Normal | InputMode::Locked => {
if session_is_mirrored || !self.multiple_users_exist_in_session {
Some(self.style.colors.frame_selected.base)
Some((self.style.colors.frame_selected.base, 3))
} else {
let colors = client_id_to_colors(
client_id,
self.style.colors.multiplayer_user_colors,
);
colors.map(|colors| colors.0)
colors.map(|colors| (colors.0, 3))
}
},
_ => Some(self.style.colors.frame_highlight.base),
_ => Some((self.style.colors.frame_highlight.base, 3)),
}
} else if self
.mouse_is_hovering_over_pane_for_clients
.contains(&client_id)
{
Some((self.style.colors.frame_highlight.base, 1))
} else {
self.style.colors.frame_unselected.map(|frame| frame.base)
self.style
.colors
.frame_unselected
.map(|frame| (frame.base, 0))
}
}
}

View file

@ -19,9 +19,11 @@ use zellij_utils::input::layout::{
FloatingPaneLayout, Layout, PluginAlias, PluginUserConfiguration, Run, RunPlugin,
RunPluginLocation, RunPluginOrAlias, SplitDirection, SplitSize, TiledPaneLayout,
};
use zellij_utils::input::mouse::MouseEvent;
use zellij_utils::input::options::Options;
use zellij_utils::ipc::IpcReceiverWithContext;
use zellij_utils::pane_size::{Size, SizeInPixels};
use zellij_utils::position::Position;
use crate::background_jobs::BackgroundJob;
use crate::pty_writer::PtyWriteInstruction;
@ -243,7 +245,7 @@ impl ServerOsApi for FakeInputOutput {
}
}
fn create_new_screen(size: Size) -> Screen {
fn create_new_screen(size: Size, advanced_mouse_actions: bool) -> Screen {
let mut bus: Bus<ScreenInstruction> = Bus::empty();
let fake_os_input = FakeInputOutput::default();
bus.os_input = Some(Box::new(fake_os_input));
@ -293,6 +295,7 @@ fn create_new_screen(size: Size) -> Screen {
explicitly_disable_kitty_keyboard_protocol,
stacked_resize,
None,
advanced_mouse_actions,
);
screen
}
@ -316,6 +319,7 @@ struct MockScreen {
pub config_options: Options,
pub session_metadata: SessionMetaData,
pub config: Config,
advanced_mouse_actions: bool,
last_opened_tab_index: Option<usize>,
}
@ -325,7 +329,8 @@ impl MockScreen {
initial_layout: Option<TiledPaneLayout>,
initial_floating_panes_layout: Vec<FloatingPaneLayout>,
) -> std::thread::JoinHandle<()> {
let config = self.config.clone();
let mut config = self.config.clone();
config.options.advanced_mouse_actions = Some(self.advanced_mouse_actions);
let client_attributes = self.client_attributes.clone();
let screen_bus = Bus::new(
vec![self.screen_receiver.take().unwrap()],
@ -626,8 +631,12 @@ impl MockScreen {
session_metadata,
last_opened_tab_index: None,
config: Config::default(),
advanced_mouse_actions: true,
}
}
pub fn set_advanced_hover_effects(&mut self, advanced_mouse_actions: bool) {
self.advanced_mouse_actions = advanced_mouse_actions;
}
}
macro_rules! log_actions_in_thread {
@ -682,7 +691,7 @@ fn open_new_tab() {
cols: 121,
rows: 20,
};
let mut screen = create_new_screen(size);
let mut screen = create_new_screen(size, true);
new_tab(&mut screen, 1, 0);
new_tab(&mut screen, 2, 1);
@ -701,7 +710,7 @@ pub fn switch_to_prev_tab() {
cols: 121,
rows: 20,
};
let mut screen = create_new_screen(size);
let mut screen = create_new_screen(size, true);
new_tab(&mut screen, 1, 1);
new_tab(&mut screen, 2, 2);
@ -720,7 +729,7 @@ pub fn switch_to_next_tab() {
cols: 121,
rows: 20,
};
let mut screen = create_new_screen(size);
let mut screen = create_new_screen(size, true);
new_tab(&mut screen, 1, 1);
new_tab(&mut screen, 2, 2);
@ -740,7 +749,7 @@ pub fn switch_to_tab_name() {
cols: 121,
rows: 20,
};
let mut screen = create_new_screen(size);
let mut screen = create_new_screen(size, true);
new_tab(&mut screen, 1, 1);
new_tab(&mut screen, 2, 2);
@ -774,7 +783,7 @@ pub fn close_tab() {
cols: 121,
rows: 20,
};
let mut screen = create_new_screen(size);
let mut screen = create_new_screen(size, true);
new_tab(&mut screen, 1, 1);
new_tab(&mut screen, 2, 2);
@ -794,7 +803,7 @@ pub fn close_the_middle_tab() {
cols: 121,
rows: 20,
};
let mut screen = create_new_screen(size);
let mut screen = create_new_screen(size, true);
new_tab(&mut screen, 1, 1);
new_tab(&mut screen, 2, 2);
@ -816,7 +825,7 @@ fn move_focus_left_at_left_screen_edge_changes_tab() {
cols: 121,
rows: 20,
};
let mut screen = create_new_screen(size);
let mut screen = create_new_screen(size, true);
new_tab(&mut screen, 1, 1);
new_tab(&mut screen, 2, 2);
@ -848,10 +857,13 @@ fn basic_move_of_active_tab_to_left() {
}
fn create_fixed_size_screen() -> Screen {
create_new_screen(Size {
cols: 121,
rows: 20,
})
create_new_screen(
Size {
cols: 121,
rows: 20,
},
true,
)
}
#[test]
@ -981,7 +993,7 @@ fn move_focus_right_at_right_screen_edge_changes_tab() {
cols: 121,
rows: 20,
};
let mut screen = create_new_screen(size);
let mut screen = create_new_screen(size, true);
new_tab(&mut screen, 1, 1);
new_tab(&mut screen, 2, 2);
@ -1002,7 +1014,7 @@ pub fn toggle_to_previous_tab_simple() {
cols: 121,
rows: 20,
};
let mut screen = create_new_screen(position_and_size);
let mut screen = create_new_screen(position_and_size, true);
new_tab(&mut screen, 1, 1);
new_tab(&mut screen, 2, 2);
@ -1030,7 +1042,7 @@ pub fn toggle_to_previous_tab_create_tabs_only() {
cols: 121,
rows: 20,
};
let mut screen = create_new_screen(position_and_size);
let mut screen = create_new_screen(position_and_size, true);
new_tab(&mut screen, 1, 0);
new_tab(&mut screen, 2, 1);
@ -1080,7 +1092,7 @@ pub fn toggle_to_previous_tab_delete() {
cols: 121,
rows: 20,
};
let mut screen = create_new_screen(position_and_size);
let mut screen = create_new_screen(position_and_size, true);
new_tab(&mut screen, 1, 0);
new_tab(&mut screen, 2, 1);
@ -1176,7 +1188,7 @@ fn switch_to_tab_with_fullscreen() {
cols: 121,
rows: 20,
};
let mut screen = create_new_screen(size);
let mut screen = create_new_screen(size, true);
new_tab(&mut screen, 1, 1);
{
@ -1212,7 +1224,7 @@ fn update_screen_pixel_dimensions() {
cols: 121,
rows: 20,
};
let mut screen = create_new_screen(size);
let mut screen = create_new_screen(size, true);
let initial_pixel_dimensions = screen.pixel_dimensions;
screen.update_pixel_dimensions(PixelDimensions {
character_cell_size: Some(SizeInPixels {
@ -1291,7 +1303,7 @@ fn attach_after_first_tab_closed() {
cols: 121,
rows: 20,
};
let mut screen = create_new_screen(size);
let mut screen = create_new_screen(size, true);
new_tab(&mut screen, 1, 0);
{
@ -1314,7 +1326,7 @@ fn open_new_floating_pane_with_custom_coordinates() {
cols: 121,
rows: 20,
};
let mut screen = create_new_screen(size);
let mut screen = create_new_screen(size, true);
new_tab(&mut screen, 1, 0);
let active_tab = screen.get_active_tab_mut(1).unwrap();
@ -1349,7 +1361,7 @@ fn open_new_floating_pane_with_custom_coordinates_exceeding_viewport() {
cols: 121,
rows: 20,
};
let mut screen = create_new_screen(size);
let mut screen = create_new_screen(size, true);
new_tab(&mut screen, 1, 0);
let active_tab = screen.get_active_tab_mut(1).unwrap();
@ -1378,6 +1390,257 @@ fn open_new_floating_pane_with_custom_coordinates_exceeding_viewport() {
assert_eq!(active_pane.cols(), 10, "columns set properly");
}
#[test]
pub fn mouse_hover_effect() {
let size = Size {
cols: 130,
rows: 20,
};
let client_id = 1;
let mut initial_layout = TiledPaneLayout::default();
initial_layout.children_split_direction = SplitDirection::Vertical;
initial_layout.children = vec![TiledPaneLayout::default(), TiledPaneLayout::default()];
let mut mock_screen = MockScreen::new(size);
let screen_thread = mock_screen.run(Some(initial_layout), vec![]);
let received_server_instructions = Arc::new(Mutex::new(vec![]));
let server_receiver = mock_screen.server_receiver.take().unwrap();
let server_thread = log_actions_in_thread!(
received_server_instructions,
ServerInstruction::KillSession,
server_receiver
);
let hover_mouse_event_1 = MouseEvent::new_buttonless_motion(Position::new(5, 70));
let _ = mock_screen.to_screen.send(ScreenInstruction::MouseEvent(
hover_mouse_event_1,
client_id,
));
std::thread::sleep(std::time::Duration::from_millis(100));
mock_screen.teardown(vec![server_thread, screen_thread]);
let snapshots = take_snapshots_and_cursor_coordinates_from_render_events(
received_server_instructions.lock().unwrap().iter(),
size,
);
for (_cursor_coordinates, snapshot) in snapshots {
assert_snapshot!(format!("{}", snapshot));
}
}
#[test]
pub fn disabled_mouse_hover_effect() {
let size = Size {
cols: 130,
rows: 20,
};
let client_id = 1;
let mut initial_layout = TiledPaneLayout::default();
initial_layout.children_split_direction = SplitDirection::Vertical;
initial_layout.children = vec![TiledPaneLayout::default(), TiledPaneLayout::default()];
let mut mock_screen = MockScreen::new(size);
mock_screen.set_advanced_hover_effects(false);
let screen_thread = mock_screen.run(Some(initial_layout), vec![]);
let received_server_instructions = Arc::new(Mutex::new(vec![]));
let server_receiver = mock_screen.server_receiver.take().unwrap();
let server_thread = log_actions_in_thread!(
received_server_instructions,
ServerInstruction::KillSession,
server_receiver
);
let hover_mouse_event_1 = MouseEvent::new_buttonless_motion(Position::new(5, 70));
let _ = mock_screen.to_screen.send(ScreenInstruction::MouseEvent(
hover_mouse_event_1,
client_id,
));
std::thread::sleep(std::time::Duration::from_millis(100));
mock_screen.teardown(vec![server_thread, screen_thread]);
let snapshots = take_snapshots_and_cursor_coordinates_from_render_events(
received_server_instructions.lock().unwrap().iter(),
size,
);
for (_cursor_coordinates, snapshot) in snapshots {
assert_snapshot!(format!("{}", snapshot));
}
}
#[test]
fn group_panes_with_mouse() {
let size = Size {
cols: 121,
rows: 20,
};
let client_id = 1;
let mut screen = create_new_screen(size, true);
new_tab(&mut screen, 1, 0);
new_tab(&mut screen, 2, 1);
screen.handle_mouse_event(
MouseEvent::new_left_press_with_alt_event(Position::new(2, 80)),
client_id,
);
assert_eq!(
screen.current_pane_group.borrow().get(&client_id),
Some(&vec![PaneId::Terminal(2)]),
"Pane Id added to client's pane group"
);
screen.handle_mouse_event(
MouseEvent::new_left_press_with_alt_event(Position::new(2, 80)),
client_id,
);
assert_eq!(
screen.current_pane_group.borrow().get(&client_id),
Some(&vec![]),
"Pane Id removed from client's pane group"
);
}
#[test]
fn group_panes_with_keyboard() {
let size = Size {
cols: 121,
rows: 20,
};
let client_id = 1;
let mut screen = create_new_screen(size, true);
new_tab(&mut screen, 1, 0);
new_tab(&mut screen, 2, 1);
let _ = screen.toggle_pane_in_group(client_id);
assert_eq!(
screen.current_pane_group.borrow().get(&client_id),
Some(&vec![PaneId::Terminal(2)]),
"Pane Id added to client's pane group"
);
let _ = screen.toggle_pane_in_group(client_id);
assert_eq!(
screen.current_pane_group.borrow().get(&client_id),
Some(&vec![]),
"Pane Id removed from client's pane group"
);
}
#[test]
fn group_panes_following_focus() {
let size = Size {
cols: 121,
rows: 20,
};
let client_id = 1;
let mut screen = create_new_screen(size, true);
new_tab(&mut screen, 1, 0);
{
let active_tab = screen.get_active_tab_mut(client_id).unwrap();
let should_float = Some(false);
for i in 2..5 {
active_tab
.new_pane(
PaneId::Terminal(i),
None,
should_float,
None,
None,
false,
Some(client_id),
)
.unwrap();
}
}
{
screen.toggle_group_marking(client_id).unwrap();
screen
.get_active_tab_mut(client_id)
.unwrap()
.move_focus_up(client_id)
.unwrap();
screen.add_active_pane_to_group_if_marking(&client_id);
assert_eq!(
screen.current_pane_group.borrow().get(&client_id),
Some(&vec![PaneId::Terminal(4), PaneId::Terminal(3)]),
"Pane Id of focused pane and newly focused pane above added to pane group"
);
}
{
let _ = screen.toggle_group_marking(client_id);
screen
.get_active_tab_mut(client_id)
.unwrap()
.move_focus_up(client_id)
.unwrap();
let _ = screen.add_active_pane_to_group_if_marking(&client_id);
assert_eq!(screen.current_pane_group.borrow().get(&client_id), Some(&vec![PaneId::Terminal(4), PaneId::Terminal(3)]), "Pane Id of newly focused pane not added to group after the group marking was toggled off");
}
}
#[test]
fn break_group_with_mouse() {
let size = Size {
cols: 121,
rows: 20,
};
let client_id = 1;
let mut screen = create_new_screen(size, true);
new_tab(&mut screen, 1, 0);
{
let active_tab = screen.get_active_tab_mut(client_id).unwrap();
let should_float = Some(false);
for i in 2..5 {
active_tab
.new_pane(
PaneId::Terminal(i),
None,
should_float,
None,
None,
false,
Some(client_id),
)
.unwrap();
}
}
{
screen.toggle_group_marking(client_id).unwrap();
screen
.get_active_tab_mut(client_id)
.unwrap()
.move_focus_up(client_id)
.unwrap();
screen.add_active_pane_to_group_if_marking(&client_id);
screen
.get_active_tab_mut(client_id)
.unwrap()
.move_focus_up(client_id)
.unwrap();
screen.add_active_pane_to_group_if_marking(&client_id);
assert_eq!(
screen.current_pane_group.borrow().get(&client_id),
Some(&vec![
PaneId::Terminal(4),
PaneId::Terminal(3),
PaneId::Terminal(2)
]),
"Group contains 3 panes"
);
}
screen.handle_mouse_event(
MouseEvent::new_right_press_with_alt_event(Position::new(2, 80)),
client_id,
);
assert_eq!(
screen.current_pane_group.borrow().get(&client_id),
Some(&vec![]),
"Group cleared by mouse event"
);
}
// Following are tests for sending CLI actions
// these tests are only partially relevant to Screen
// and are included here for two reasons:

View file

@ -0,0 +1,24 @@
---
source: zellij-server/src/./unit/screen_tests.rs
expression: "format!(\"{}\", snapshot)"
---
00 (C): ┌ Pane #1 ──────────────────────────────────────────────────────┐┌ Pane #2 ──────────────────────────────────────────────────────┐
01 (C): │ ││ │
02 (C): │ ││ │
03 (C): │ ││ │
04 (C): │ ││ │
05 (C): │ ││ │
06 (C): │ ││ │
07 (C): │ ││ │
08 (C): │ ││ │
09 (C): │ ││ │
10 (C): │ ││ │
11 (C): │ ││ │
12 (C): │ ││ │
13 (C): │ ││ │
14 (C): │ ││ │
15 (C): │ ││ │
16 (C): │ ││ │
17 (C): │ ││ │
18 (C): │ ││ │
19 (C): └───────────────────────────────────────────────────────────────┘└───────────────────────────────────────────────────────────────┘

View file

@ -0,0 +1,24 @@
---
source: zellij-server/src/./unit/screen_tests.rs
expression: "format!(\"{}\", snapshot)"
---
00 (C): ┌ Pane #1 ──────────────────────────────────────────────────────┐┌ Pane #2 ──────────────────────────────────────────────────────┐
01 (C): │ ││ │
02 (C): │ ││ │
03 (C): │ ││ │
04 (C): │ ││ │
05 (C): │ ││ │
06 (C): │ ││ │
07 (C): │ ││ │
08 (C): │ ││ │
09 (C): │ ││ │
10 (C): │ ││ │
11 (C): │ ││ │
12 (C): │ ││ │
13 (C): │ ││ │
14 (C): │ ││ │
15 (C): │ ││ │
16 (C): │ ││ │
17 (C): │ ││ │
18 (C): │ ││ │
19 (C): └───────────────────────────────────────────────────────────────┘└ Alt <Click> - group, Alt <Right-Click> - ungroup all ─────────┘

View file

@ -0,0 +1,24 @@
---
source: zellij-server/src/./unit/screen_tests.rs
expression: "format!(\"{}\", snapshot)"
---
00 (C): ┌ Pane #1 ──────────────────────────────────────────────────────┐┌ Pane #2 ──────────────────────────────────────────────────────┐
01 (C): │ ││ │
02 (C): │ ││ │
03 (C): │ ││ │
04 (C): │ ││ │
05 (C): │ ││ │
06 (C): │ ││ │
07 (C): │ ││ │
08 (C): │ ││ │
09 (C): │ ││ │
10 (C): │ ││ │
11 (C): │ ││ │
12 (C): │ ││ │
13 (C): │ ││ │
14 (C): │ ││ │
15 (C): │ ││ │
16 (C): │ ││ │
17 (C): │ ││ │
18 (C): │ ││ │
19 (C): └───────────────────────────────────────────────────────────────┘└───────────────────────────────────────────────────────────────┘

View file

@ -1,16 +1,14 @@
---
source: zellij-server/src/./unit/screen_tests.rs
assertion_line: 3687
expression: "format!(\"{}\", snapshot)"
---
00 (C): ┌ Pane #1 ─────┐┌ Pane #2 ─────────────────────────────────────┐┌ Pane #5 ─────┐
01 (C): │ │┌ Pane #3 ─────────────────────────────────────┐│ │
02 (C): │ │┌ Pane #4 ─────────────────────────────────────┐│ │
01 (C): │ ││ ││ │
02 (C): │ ││ ││ │
03 (C): │ ││ ││ │
04 (C): │ ││ ││ │
05 (C): │ ││ ││ │
06 (C): │ ││ ││ │
07 (C): │ ││ ││ │
08 (C): │ ││ ││ │
09 (C): └──────────────┘└──────────────────────────────────────────────┘└──────────────┘
07 (C): │ │└──────────────────────────────────────────────┘│ │
08 (C): │ │└ Pane #3 ─────────────────────────────────────┘│ │
09 (C): └──────────────┘└ Pane #4 ─────────────────────────────────────┘└──────────────┘

View file

@ -1,6 +1,5 @@
---
source: zellij-server/src/./unit/screen_tests.rs
assertion_line: 2183
expression: "format!(\"{:?}\", * received_pty_instructions.lock().unwrap())"
expression: "format!(\"{:?}\", *received_pty_instructions.lock().unwrap())"
---
[StartCachingResizes, ApplyCachedResizes, StartCachingResizes, ResizePty(0, 59, 18, None, None), ResizePty(1, 58, 18, None, None), ResizePty(0, 59, 18, None, None), ResizePty(1, 58, 18, None, None), ResizePty(0, 59, 18, None, None), ResizePty(1, 58, 18, None, None), ResizePty(0, 59, 18, None, None), ResizePty(1, 58, 18, None, None), ResizePty(0, 58, 18, None, None), ResizePty(1, 59, 18, None, None), ResizePty(0, 58, 18, None, None), ResizePty(1, 59, 18, None, None), ResizePty(0, 58, 18, None, None), ResizePty(1, 59, 18, None, None), ResizePty(0, 58, 18, None, None), ResizePty(1, 59, 18, None, None), ResizePty(0, 59, 18, None, None), ResizePty(1, 58, 18, None, None), ApplyCachedResizes, ApplyCachedResizes, StartCachingResizes, ApplyCachedResizes, StartCachingResizes, ApplyCachedResizes, StartCachingResizes, Write([102, 111, 111], 0), Write([102, 111, 111], 1), ApplyCachedResizes, Exit]
[StartCachingResizes, ApplyCachedResizes, StartCachingResizes, ResizePty(0, 59, 18, None, None), ResizePty(1, 58, 18, None, None), ResizePty(0, 59, 18, None, None), ResizePty(1, 58, 18, None, None), ResizePty(0, 59, 18, None, None), ResizePty(1, 58, 18, None, None), ResizePty(0, 59, 18, None, None), ResizePty(1, 58, 18, None, None), ApplyCachedResizes, ResizePty(0, 58, 18, None, None), ResizePty(1, 59, 18, None, None), ResizePty(0, 58, 18, None, None), ResizePty(1, 59, 18, None, None), ResizePty(0, 58, 18, None, None), ResizePty(1, 59, 18, None, None), ResizePty(0, 58, 18, None, None), ResizePty(1, 59, 18, None, None), ResizePty(0, 59, 18, None, None), ResizePty(1, 58, 18, None, None), ApplyCachedResizes, ApplyCachedResizes, ApplyCachedResizes, StartCachingResizes, ApplyCachedResizes, StartCachingResizes, ApplyCachedResizes, StartCachingResizes, Write([102, 111, 111], 0), Write([102, 111, 111], 1), ApplyCachedResizes, Exit]

View file

@ -1,6 +1,5 @@
---
source: zellij-server/src/./unit/screen_tests.rs
assertion_line: 1423
expression: "format!(\"{:?}\", * received_pty_instructions.lock().unwrap())"
expression: "format!(\"{:?}\", *received_pty_instructions.lock().unwrap())"
---
[StartCachingResizes, ApplyCachedResizes, StartCachingResizes, ResizePty(0, 119, 18, None, None), ResizePty(0, 119, 18, None, None), ResizePty(0, 119, 18, None, None), ResizePty(0, 119, 18, None, None), ResizePty(0, 119, 18, None, None), ResizePty(0, 119, 18, None, None), ResizePty(0, 119, 18, None, None), ResizePty(0, 119, 18, None, None), ResizePty(0, 119, 18, None, None), ApplyCachedResizes, ApplyCachedResizes, StartCachingResizes, ApplyCachedResizes, StartCachingResizes, Write([102, 111, 111], 0), ApplyCachedResizes, Exit]
[StartCachingResizes, ApplyCachedResizes, StartCachingResizes, ResizePty(0, 119, 18, None, None), ResizePty(0, 119, 18, None, None), ResizePty(0, 119, 18, None, None), ResizePty(0, 119, 18, None, None), ApplyCachedResizes, ResizePty(0, 119, 18, None, None), ResizePty(0, 119, 18, None, None), ResizePty(0, 119, 18, None, None), ResizePty(0, 119, 18, None, None), ResizePty(0, 119, 18, None, None), ApplyCachedResizes, ApplyCachedResizes, ApplyCachedResizes, StartCachingResizes, ApplyCachedResizes, StartCachingResizes, Write([102, 111, 111], 0), ApplyCachedResizes, Exit]

View file

@ -1,6 +1,5 @@
---
source: zellij-server/src/./unit/screen_tests.rs
assertion_line: 1397
expression: "format!(\"{:?}\", * received_pty_instructions.lock().unwrap())"
expression: "format!(\"{:?}\", *received_pty_instructions.lock().unwrap())"
---
[StartCachingResizes, ApplyCachedResizes, StartCachingResizes, ResizePty(0, 119, 18, None, None), ResizePty(0, 119, 18, None, None), ResizePty(0, 119, 18, None, None), ResizePty(0, 119, 18, None, None), ResizePty(0, 119, 18, None, None), ResizePty(0, 119, 18, None, None), ResizePty(0, 119, 18, None, None), ResizePty(0, 119, 18, None, None), ResizePty(0, 119, 18, None, None), ApplyCachedResizes, ApplyCachedResizes, StartCachingResizes, ApplyCachedResizes, StartCachingResizes, Write([105, 110, 112, 117, 116, 32, 102, 114, 111, 109, 32, 116, 104, 101, 32, 99, 108, 105], 0), ApplyCachedResizes, Exit]
[StartCachingResizes, ApplyCachedResizes, StartCachingResizes, ResizePty(0, 119, 18, None, None), ResizePty(0, 119, 18, None, None), ResizePty(0, 119, 18, None, None), ResizePty(0, 119, 18, None, None), ApplyCachedResizes, ResizePty(0, 119, 18, None, None), ResizePty(0, 119, 18, None, None), ResizePty(0, 119, 18, None, None), ResizePty(0, 119, 18, None, None), ResizePty(0, 119, 18, None, None), ApplyCachedResizes, ApplyCachedResizes, ApplyCachedResizes, StartCachingResizes, ApplyCachedResizes, StartCachingResizes, Write([105, 110, 112, 117, 116, 32, 102, 114, 111, 109, 32, 116, 104, 101, 32, 99, 108, 105], 0), ApplyCachedResizes, Exit]

View file

@ -1273,6 +1273,46 @@ pub fn change_floating_panes_coordinates(
unsafe { host_run_plugin_command() };
}
pub fn group_and_ungroup_panes(pane_ids_to_group: Vec<PaneId>, pane_ids_to_ungroup: Vec<PaneId>) {
let plugin_command =
PluginCommand::GroupAndUngroupPanes(pane_ids_to_group, pane_ids_to_ungroup);
let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap();
object_to_stdout(&protobuf_plugin_command.encode_to_vec());
unsafe { host_run_plugin_command() };
}
pub fn highlight_and_unhighlight_panes(
pane_ids_to_highlight: Vec<PaneId>,
pane_ids_to_unhighlight: Vec<PaneId>,
) {
let plugin_command =
PluginCommand::HighlightAndUnhighlightPanes(pane_ids_to_highlight, pane_ids_to_unhighlight);
let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap();
object_to_stdout(&protobuf_plugin_command.encode_to_vec());
unsafe { host_run_plugin_command() };
}
pub fn close_multiple_panes(pane_ids: Vec<PaneId>) {
let plugin_command = PluginCommand::CloseMultiplePanes(pane_ids);
let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap();
object_to_stdout(&protobuf_plugin_command.encode_to_vec());
unsafe { host_run_plugin_command() };
}
pub fn float_multiple_panes(pane_ids: Vec<PaneId>) {
let plugin_command = PluginCommand::FloatMultiplePanes(pane_ids);
let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap();
object_to_stdout(&protobuf_plugin_command.encode_to_vec());
unsafe { host_run_plugin_command() };
}
pub fn embed_multiple_panes(pane_ids: Vec<PaneId>) {
let plugin_command = PluginCommand::EmbedMultiplePanes(pane_ids);
let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap();
object_to_stdout(&protobuf_plugin_command.encode_to_vec());
unsafe { host_run_plugin_command() };
}
// Utility Functions
#[allow(unused)]

View file

@ -84,14 +84,14 @@ impl Text {
let mut prefix = "".to_owned();
if self.opaque {
prefix = format!("z{}", prefix);
}
if self.selected {
prefix = format!("x{}", prefix);
}
if self.opaque {
prefix = format!("z{}", prefix);
}
format!("{}{}{}", prefix, indices, text)
}
pub fn len(&self) -> usize {

View file

@ -181,6 +181,14 @@ keybinds {
bind "Alt -" { Resize "Decrease"; }
bind "Alt [" { PreviousSwapLayout; }
bind "Alt ]" { NextSwapLayout; }
bind "Alt m" {
LaunchOrFocusPlugin "zellij:multiple-select" {
floating true
move_to_focused_tab true
}
}
bind "Alt p" { TogglePaneInGroup; }
bind "Alt Shift p" { ToggleGroupMarking; }
}
shared_except "normal" "locked" {
bind "Enter" "Esc" { SwitchToMode "Normal"; }
@ -427,7 +435,7 @@ load_plugins {
//
// show_release_notes false
// Whether to show startup tips on session start
// Whether to enable mouse hover effects and pane grouping functionality
// Default: true
//
// show_startup_tips false
// advanced_mouse_actions false

Binary file not shown.

View file

@ -1,311 +1,308 @@
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Action {
#[prost(enumeration = "ActionName", tag = "1")]
#[prost(enumeration="ActionName", tag="1")]
pub name: i32,
#[prost(
oneof = "action::OptionalPayload",
tags = "2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49"
)]
#[prost(oneof="action::OptionalPayload", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49")]
pub optional_payload: ::core::option::Option<action::OptionalPayload>,
}
/// Nested message and enum types in `Action`.
pub mod action {
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Oneof)]
#[derive(Clone, PartialEq, ::prost::Oneof)]
pub enum OptionalPayload {
#[prost(message, tag = "2")]
#[prost(message, tag="2")]
SwitchToModePayload(super::SwitchToModePayload),
#[prost(message, tag = "3")]
#[prost(message, tag="3")]
WritePayload(super::WritePayload),
#[prost(message, tag = "4")]
#[prost(message, tag="4")]
WriteCharsPayload(super::WriteCharsPayload),
#[prost(message, tag = "5")]
#[prost(message, tag="5")]
SwitchModeForAllClientsPayload(super::SwitchToModePayload),
#[prost(message, tag = "6")]
#[prost(message, tag="6")]
ResizePayload(super::super::resize::Resize),
#[prost(enumeration = "super::super::resize::ResizeDirection", tag = "7")]
#[prost(enumeration="super::super::resize::ResizeDirection", tag="7")]
MoveFocusPayload(i32),
#[prost(enumeration = "super::super::resize::ResizeDirection", tag = "8")]
#[prost(enumeration="super::super::resize::ResizeDirection", tag="8")]
MoveFocusOrTabPayload(i32),
#[prost(message, tag = "9")]
#[prost(message, tag="9")]
MovePanePayload(super::MovePanePayload),
#[prost(message, tag = "10")]
#[prost(message, tag="10")]
DumpScreenPayload(super::DumpScreenPayload),
#[prost(message, tag = "11")]
#[prost(message, tag="11")]
ScrollUpAtPayload(super::ScrollAtPayload),
#[prost(message, tag = "12")]
#[prost(message, tag="12")]
ScrollDownAtPayload(super::ScrollAtPayload),
#[prost(message, tag = "13")]
#[prost(message, tag="13")]
NewPanePayload(super::NewPanePayload),
#[prost(message, tag = "14")]
#[prost(message, tag="14")]
EditFilePayload(super::EditFilePayload),
#[prost(message, tag = "15")]
#[prost(message, tag="15")]
NewFloatingPanePayload(super::NewFloatingPanePayload),
#[prost(message, tag = "16")]
#[prost(message, tag="16")]
NewTiledPanePayload(super::NewTiledPanePayload),
#[prost(bytes, tag = "17")]
#[prost(bytes, tag="17")]
PaneNameInputPayload(::prost::alloc::vec::Vec<u8>),
#[prost(uint32, tag = "18")]
#[prost(uint32, tag="18")]
GoToTabPayload(u32),
#[prost(message, tag = "19")]
#[prost(message, tag="19")]
GoToTabNamePayload(super::GoToTabNamePayload),
#[prost(bytes, tag = "20")]
#[prost(bytes, tag="20")]
TabNameInputPayload(::prost::alloc::vec::Vec<u8>),
#[prost(message, tag = "21")]
#[prost(message, tag="21")]
RunPayload(super::RunCommandAction),
#[prost(message, tag = "22")]
#[prost(message, tag="22")]
LeftClickPayload(super::Position),
#[prost(message, tag = "23")]
#[prost(message, tag="23")]
RightClickPayload(super::Position),
#[prost(message, tag = "24")]
#[prost(message, tag="24")]
MiddleClickPayload(super::Position),
#[prost(message, tag = "25")]
#[prost(message, tag="25")]
LaunchOrFocusPluginPayload(super::LaunchOrFocusPluginPayload),
#[prost(message, tag = "26")]
#[prost(message, tag="26")]
LeftMouseReleasePayload(super::Position),
#[prost(message, tag = "27")]
#[prost(message, tag="27")]
RightMouseReleasePayload(super::Position),
#[prost(message, tag = "28")]
#[prost(message, tag="28")]
MiddleMouseReleasePayload(super::Position),
#[prost(bytes, tag = "32")]
#[prost(bytes, tag="32")]
SearchInputPayload(::prost::alloc::vec::Vec<u8>),
#[prost(enumeration = "super::SearchDirection", tag = "33")]
#[prost(enumeration="super::SearchDirection", tag="33")]
SearchPayload(i32),
#[prost(enumeration = "super::SearchOption", tag = "34")]
#[prost(enumeration="super::SearchOption", tag="34")]
SearchToggleOptionPayload(i32),
#[prost(message, tag = "35")]
#[prost(message, tag="35")]
NewTiledPluginPanePayload(super::NewPluginPanePayload),
#[prost(message, tag = "36")]
#[prost(message, tag="36")]
NewFloatingPluginPanePayload(super::NewPluginPanePayload),
#[prost(string, tag = "37")]
#[prost(string, tag="37")]
StartOrReloadPluginPayload(::prost::alloc::string::String),
#[prost(uint32, tag = "38")]
#[prost(uint32, tag="38")]
CloseTerminalPanePayload(u32),
#[prost(uint32, tag = "39")]
#[prost(uint32, tag="39")]
ClosePluginPanePayload(u32),
#[prost(message, tag = "40")]
#[prost(message, tag="40")]
FocusTerminalPaneWithIdPayload(super::PaneIdAndShouldFloat),
#[prost(message, tag = "41")]
#[prost(message, tag="41")]
FocusPluginPaneWithIdPayload(super::PaneIdAndShouldFloat),
#[prost(message, tag = "42")]
#[prost(message, tag="42")]
RenameTerminalPanePayload(super::IdAndName),
#[prost(message, tag = "43")]
#[prost(message, tag="43")]
RenamePluginPanePayload(super::IdAndName),
#[prost(message, tag = "44")]
#[prost(message, tag="44")]
RenameTabPayload(super::IdAndName),
#[prost(string, tag = "45")]
#[prost(string, tag="45")]
RenameSessionPayload(::prost::alloc::string::String),
#[prost(message, tag = "46")]
#[prost(message, tag="46")]
LaunchPluginPayload(super::LaunchOrFocusPluginPayload),
#[prost(message, tag = "47")]
#[prost(message, tag="47")]
MessagePayload(super::CliPipePayload),
#[prost(enumeration = "super::MoveTabDirection", tag = "48")]
#[prost(enumeration="super::MoveTabDirection", tag="48")]
MoveTabPayload(i32),
#[prost(message, tag = "49")]
#[prost(message, tag="49")]
MouseEventPayload(super::MouseEventPayload),
}
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct CliPipePayload {
#[prost(string, optional, tag = "1")]
#[prost(string, optional, tag="1")]
pub name: ::core::option::Option<::prost::alloc::string::String>,
#[prost(string, tag = "2")]
#[prost(string, tag="2")]
pub payload: ::prost::alloc::string::String,
#[prost(message, repeated, tag = "3")]
#[prost(message, repeated, tag="3")]
pub args: ::prost::alloc::vec::Vec<NameAndValue>,
#[prost(string, optional, tag = "4")]
#[prost(string, optional, tag="4")]
pub plugin: ::core::option::Option<::prost::alloc::string::String>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct IdAndName {
#[prost(bytes = "vec", tag = "1")]
#[prost(bytes="vec", tag="1")]
pub name: ::prost::alloc::vec::Vec<u8>,
#[prost(uint32, tag = "2")]
#[prost(uint32, tag="2")]
pub id: u32,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct PaneIdAndShouldFloat {
#[prost(uint32, tag = "1")]
#[prost(uint32, tag="1")]
pub pane_id: u32,
#[prost(bool, tag = "2")]
#[prost(bool, tag="2")]
pub should_float: bool,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct NewPluginPanePayload {
#[prost(string, tag = "1")]
#[prost(string, tag="1")]
pub plugin_url: ::prost::alloc::string::String,
#[prost(string, optional, tag = "2")]
#[prost(string, optional, tag="2")]
pub pane_name: ::core::option::Option<::prost::alloc::string::String>,
#[prost(bool, tag = "3")]
#[prost(bool, tag="3")]
pub skip_plugin_cache: bool,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct LaunchOrFocusPluginPayload {
#[prost(string, tag = "1")]
#[prost(string, tag="1")]
pub plugin_url: ::prost::alloc::string::String,
#[prost(bool, tag = "2")]
#[prost(bool, tag="2")]
pub should_float: bool,
#[prost(message, optional, tag = "3")]
#[prost(message, optional, tag="3")]
pub plugin_configuration: ::core::option::Option<PluginConfiguration>,
#[prost(bool, tag = "4")]
#[prost(bool, tag="4")]
pub move_to_focused_tab: bool,
#[prost(bool, tag = "5")]
#[prost(bool, tag="5")]
pub should_open_in_place: bool,
#[prost(bool, tag = "6")]
#[prost(bool, tag="6")]
pub skip_plugin_cache: bool,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct GoToTabNamePayload {
#[prost(string, tag = "1")]
#[prost(string, tag="1")]
pub tab_name: ::prost::alloc::string::String,
#[prost(bool, tag = "2")]
#[prost(bool, tag="2")]
pub create: bool,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct NewFloatingPanePayload {
#[prost(message, optional, tag = "1")]
#[prost(message, optional, tag="1")]
pub command: ::core::option::Option<RunCommandAction>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct NewTiledPanePayload {
#[prost(message, optional, tag = "1")]
#[prost(message, optional, tag="1")]
pub command: ::core::option::Option<RunCommandAction>,
#[prost(enumeration = "super::resize::ResizeDirection", optional, tag = "2")]
#[prost(enumeration="super::resize::ResizeDirection", optional, tag="2")]
pub direction: ::core::option::Option<i32>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct MovePanePayload {
#[prost(enumeration = "super::resize::ResizeDirection", optional, tag = "1")]
#[prost(enumeration="super::resize::ResizeDirection", optional, tag="1")]
pub direction: ::core::option::Option<i32>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct EditFilePayload {
#[prost(string, tag = "1")]
#[prost(string, tag="1")]
pub file_to_edit: ::prost::alloc::string::String,
#[prost(uint32, optional, tag = "2")]
#[prost(uint32, optional, tag="2")]
pub line_number: ::core::option::Option<u32>,
#[prost(string, optional, tag = "3")]
#[prost(string, optional, tag="3")]
pub cwd: ::core::option::Option<::prost::alloc::string::String>,
#[prost(enumeration = "super::resize::ResizeDirection", optional, tag = "4")]
#[prost(enumeration="super::resize::ResizeDirection", optional, tag="4")]
pub direction: ::core::option::Option<i32>,
#[prost(bool, tag = "5")]
#[prost(bool, tag="5")]
pub should_float: bool,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ScrollAtPayload {
#[prost(message, optional, tag = "1")]
#[prost(message, optional, tag="1")]
pub position: ::core::option::Option<Position>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct NewPanePayload {
#[prost(enumeration = "super::resize::ResizeDirection", optional, tag = "1")]
#[prost(enumeration="super::resize::ResizeDirection", optional, tag="1")]
pub direction: ::core::option::Option<i32>,
#[prost(string, optional, tag = "2")]
#[prost(string, optional, tag="2")]
pub pane_name: ::core::option::Option<::prost::alloc::string::String>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct SwitchToModePayload {
#[prost(enumeration = "super::input_mode::InputMode", tag = "1")]
#[prost(enumeration="super::input_mode::InputMode", tag="1")]
pub input_mode: i32,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct WritePayload {
#[prost(bytes = "vec", tag = "1")]
#[prost(bytes="vec", tag="1")]
pub bytes_to_write: ::prost::alloc::vec::Vec<u8>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct WriteCharsPayload {
#[prost(string, tag = "1")]
#[prost(string, tag="1")]
pub chars: ::prost::alloc::string::String,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct DumpScreenPayload {
#[prost(string, tag = "1")]
#[prost(string, tag="1")]
pub file_path: ::prost::alloc::string::String,
#[prost(bool, tag = "2")]
#[prost(bool, tag="2")]
pub include_scrollback: bool,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Position {
#[prost(int64, tag = "1")]
#[prost(int64, tag="1")]
pub line: i64,
#[prost(int64, tag = "2")]
#[prost(int64, tag="2")]
pub column: i64,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct MouseEventPayload {
#[prost(uint32, tag = "1")]
#[prost(uint32, tag="1")]
pub event_type: u32,
#[prost(bool, tag = "2")]
#[prost(bool, tag="2")]
pub left: bool,
#[prost(bool, tag = "3")]
#[prost(bool, tag="3")]
pub right: bool,
#[prost(bool, tag = "4")]
#[prost(bool, tag="4")]
pub middle: bool,
#[prost(bool, tag = "5")]
#[prost(bool, tag="5")]
pub wheel_up: bool,
#[prost(bool, tag = "6")]
#[prost(bool, tag="6")]
pub wheel_down: bool,
#[prost(bool, tag = "7")]
#[prost(bool, tag="7")]
pub shift: bool,
#[prost(bool, tag = "8")]
#[prost(bool, tag="8")]
pub alt: bool,
#[prost(bool, tag = "9")]
#[prost(bool, tag="9")]
pub ctrl: bool,
#[prost(int64, tag = "10")]
#[prost(int64, tag="10")]
pub line: i64,
#[prost(int64, tag = "11")]
#[prost(int64, tag="11")]
pub column: i64,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct RunCommandAction {
#[prost(string, tag = "1")]
#[prost(string, tag="1")]
pub command: ::prost::alloc::string::String,
#[prost(string, repeated, tag = "2")]
#[prost(string, repeated, tag="2")]
pub args: ::prost::alloc::vec::Vec<::prost::alloc::string::String>,
#[prost(string, optional, tag = "3")]
#[prost(string, optional, tag="3")]
pub cwd: ::core::option::Option<::prost::alloc::string::String>,
#[prost(enumeration = "super::resize::ResizeDirection", optional, tag = "4")]
#[prost(enumeration="super::resize::ResizeDirection", optional, tag="4")]
pub direction: ::core::option::Option<i32>,
#[prost(string, optional, tag = "5")]
#[prost(string, optional, tag="5")]
pub pane_name: ::core::option::Option<::prost::alloc::string::String>,
#[prost(bool, tag = "6")]
#[prost(bool, tag="6")]
pub hold_on_close: bool,
#[prost(bool, tag = "7")]
#[prost(bool, tag="7")]
pub hold_on_start: bool,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct PluginConfiguration {
#[prost(message, repeated, tag = "1")]
#[prost(message, repeated, tag="1")]
pub name_and_value: ::prost::alloc::vec::Vec<NameAndValue>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct NameAndValue {
#[prost(string, tag = "1")]
#[prost(string, tag="1")]
pub name: ::prost::alloc::string::String,
#[prost(string, tag = "2")]
#[prost(string, tag="2")]
pub value: ::prost::alloc::string::String,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]
@ -476,6 +473,8 @@ pub enum ActionName {
KeybindPipe = 84,
TogglePanePinned = 85,
MouseEvent = 86,
TogglePaneInGroup = 87,
ToggleGroupMarking = 88,
}
impl ActionName {
/// String value of the enum field names used in the ProtoBuf definition.
@ -568,6 +567,8 @@ impl ActionName {
ActionName::KeybindPipe => "KeybindPipe",
ActionName::TogglePanePinned => "TogglePanePinned",
ActionName::MouseEvent => "MouseEvent",
ActionName::TogglePaneInGroup => "TogglePaneInGroup",
ActionName::ToggleGroupMarking => "ToggleGroupMarking",
}
}
/// Creates an enum from field names used in the ProtoBuf definition.
@ -657,6 +658,8 @@ impl ActionName {
"KeybindPipe" => Some(Self::KeybindPipe),
"TogglePanePinned" => Some(Self::TogglePanePinned),
"MouseEvent" => Some(Self::MouseEvent),
"TogglePaneInGroup" => Some(Self::TogglePaneInGroup),
"ToggleGroupMarking" => Some(Self::ToggleGroupMarking),
_ => None,
}
}

View file

@ -1,10 +1,10 @@
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Command {
#[prost(string, tag = "1")]
#[prost(string, tag="1")]
pub path: ::prost::alloc::string::String,
#[prost(string, repeated, tag = "2")]
#[prost(string, repeated, tag="2")]
pub args: ::prost::alloc::vec::Vec<::prost::alloc::string::String>,
#[prost(string, optional, tag = "3")]
#[prost(string, optional, tag="3")]
pub cwd: ::core::option::Option<::prost::alloc::string::String>,
}

View file

@ -1,476 +1,483 @@
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct EventNameList {
#[prost(enumeration = "EventType", repeated, tag = "1")]
#[prost(enumeration="EventType", repeated, tag="1")]
pub event_types: ::prost::alloc::vec::Vec<i32>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Event {
#[prost(enumeration = "EventType", tag = "1")]
#[prost(enumeration="EventType", tag="1")]
pub name: i32,
#[prost(
oneof = "event::Payload",
tags = "2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26"
)]
#[prost(oneof="event::Payload", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26")]
pub payload: ::core::option::Option<event::Payload>,
}
/// Nested message and enum types in `Event`.
pub mod event {
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Oneof)]
#[derive(Clone, PartialEq, ::prost::Oneof)]
pub enum Payload {
#[prost(message, tag = "2")]
#[prost(message, tag="2")]
ModeUpdatePayload(super::ModeUpdatePayload),
#[prost(message, tag = "3")]
#[prost(message, tag="3")]
TabUpdatePayload(super::TabUpdatePayload),
#[prost(message, tag = "4")]
#[prost(message, tag="4")]
PaneUpdatePayload(super::PaneUpdatePayload),
#[prost(message, tag = "5")]
#[prost(message, tag="5")]
KeyPayload(super::super::key::Key),
#[prost(message, tag = "6")]
#[prost(message, tag="6")]
MouseEventPayload(super::MouseEventPayload),
#[prost(float, tag = "7")]
#[prost(float, tag="7")]
TimerPayload(f32),
#[prost(enumeration = "super::CopyDestination", tag = "8")]
#[prost(enumeration="super::CopyDestination", tag="8")]
CopyToClipboardPayload(i32),
#[prost(bool, tag = "9")]
#[prost(bool, tag="9")]
VisiblePayload(bool),
#[prost(message, tag = "10")]
#[prost(message, tag="10")]
CustomMessagePayload(super::CustomMessagePayload),
#[prost(message, tag = "11")]
#[prost(message, tag="11")]
FileListPayload(super::FileListPayload),
#[prost(message, tag = "12")]
#[prost(message, tag="12")]
PermissionRequestResultPayload(super::PermissionRequestResultPayload),
#[prost(message, tag = "13")]
#[prost(message, tag="13")]
SessionUpdatePayload(super::SessionUpdatePayload),
#[prost(message, tag = "14")]
#[prost(message, tag="14")]
RunCommandResultPayload(super::RunCommandResultPayload),
#[prost(message, tag = "15")]
#[prost(message, tag="15")]
WebRequestResultPayload(super::WebRequestResultPayload),
#[prost(message, tag = "16")]
#[prost(message, tag="16")]
CommandPaneOpenedPayload(super::CommandPaneOpenedPayload),
#[prost(message, tag = "17")]
#[prost(message, tag="17")]
CommandPaneExitedPayload(super::CommandPaneExitedPayload),
#[prost(message, tag = "18")]
#[prost(message, tag="18")]
PaneClosedPayload(super::PaneClosedPayload),
#[prost(message, tag = "19")]
#[prost(message, tag="19")]
EditPaneOpenedPayload(super::EditPaneOpenedPayload),
#[prost(message, tag = "20")]
#[prost(message, tag="20")]
EditPaneExitedPayload(super::EditPaneExitedPayload),
#[prost(message, tag = "21")]
#[prost(message, tag="21")]
CommandPaneRerunPayload(super::CommandPaneReRunPayload),
#[prost(message, tag = "22")]
#[prost(message, tag="22")]
FailedToWriteConfigToDiskPayload(super::FailedToWriteConfigToDiskPayload),
#[prost(message, tag = "23")]
#[prost(message, tag="23")]
ListClientsPayload(super::ListClientsPayload),
#[prost(message, tag = "24")]
#[prost(message, tag="24")]
HostFolderChangedPayload(super::HostFolderChangedPayload),
#[prost(message, tag = "25")]
#[prost(message, tag="25")]
FailedToChangeHostFolderPayload(super::FailedToChangeHostFolderPayload),
#[prost(message, tag = "26")]
#[prost(message, tag="26")]
PastedTextPayload(super::PastedTextPayload),
}
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct PastedTextPayload {
#[prost(string, tag = "1")]
#[prost(string, tag="1")]
pub pasted_text: ::prost::alloc::string::String,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct FailedToChangeHostFolderPayload {
#[prost(string, optional, tag = "1")]
#[prost(string, optional, tag="1")]
pub error_message: ::core::option::Option<::prost::alloc::string::String>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct HostFolderChangedPayload {
#[prost(string, tag = "1")]
#[prost(string, tag="1")]
pub new_host_folder_path: ::prost::alloc::string::String,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ListClientsPayload {
#[prost(message, repeated, tag = "1")]
#[prost(message, repeated, tag="1")]
pub client_info: ::prost::alloc::vec::Vec<ClientInfo>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ClientInfo {
#[prost(uint32, tag = "1")]
#[prost(uint32, tag="1")]
pub client_id: u32,
#[prost(message, optional, tag = "2")]
#[prost(message, optional, tag="2")]
pub pane_id: ::core::option::Option<PaneId>,
#[prost(string, tag = "3")]
#[prost(string, tag="3")]
pub running_command: ::prost::alloc::string::String,
#[prost(bool, tag = "4")]
#[prost(bool, tag="4")]
pub is_current_client: bool,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct FailedToWriteConfigToDiskPayload {
#[prost(string, optional, tag = "1")]
#[prost(string, optional, tag="1")]
pub file_path: ::core::option::Option<::prost::alloc::string::String>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct CommandPaneReRunPayload {
#[prost(uint32, tag = "1")]
#[prost(uint32, tag="1")]
pub terminal_pane_id: u32,
#[prost(message, repeated, tag = "3")]
#[prost(message, repeated, tag="3")]
pub context: ::prost::alloc::vec::Vec<ContextItem>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct PaneClosedPayload {
#[prost(message, optional, tag = "1")]
#[prost(message, optional, tag="1")]
pub pane_id: ::core::option::Option<PaneId>,
}
/// duplicate of plugin_command.PaneId because protobuffs don't like recursive imports
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct PaneId {
#[prost(enumeration = "PaneType", tag = "1")]
#[prost(enumeration="PaneType", tag="1")]
pub pane_type: i32,
#[prost(uint32, tag = "2")]
#[prost(uint32, tag="2")]
pub id: u32,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct CommandPaneOpenedPayload {
#[prost(uint32, tag = "1")]
#[prost(uint32, tag="1")]
pub terminal_pane_id: u32,
#[prost(message, repeated, tag = "2")]
#[prost(message, repeated, tag="2")]
pub context: ::prost::alloc::vec::Vec<ContextItem>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct EditPaneOpenedPayload {
#[prost(uint32, tag = "1")]
#[prost(uint32, tag="1")]
pub terminal_pane_id: u32,
#[prost(message, repeated, tag = "2")]
#[prost(message, repeated, tag="2")]
pub context: ::prost::alloc::vec::Vec<ContextItem>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct CommandPaneExitedPayload {
#[prost(uint32, tag = "1")]
#[prost(uint32, tag="1")]
pub terminal_pane_id: u32,
#[prost(int32, optional, tag = "2")]
#[prost(int32, optional, tag="2")]
pub exit_code: ::core::option::Option<i32>,
#[prost(message, repeated, tag = "3")]
#[prost(message, repeated, tag="3")]
pub context: ::prost::alloc::vec::Vec<ContextItem>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct EditPaneExitedPayload {
#[prost(uint32, tag = "1")]
#[prost(uint32, tag="1")]
pub terminal_pane_id: u32,
#[prost(int32, optional, tag = "2")]
#[prost(int32, optional, tag="2")]
pub exit_code: ::core::option::Option<i32>,
#[prost(message, repeated, tag = "3")]
#[prost(message, repeated, tag="3")]
pub context: ::prost::alloc::vec::Vec<ContextItem>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct SessionUpdatePayload {
#[prost(message, repeated, tag = "1")]
#[prost(message, repeated, tag="1")]
pub session_manifests: ::prost::alloc::vec::Vec<SessionManifest>,
#[prost(message, repeated, tag = "2")]
#[prost(message, repeated, tag="2")]
pub resurrectable_sessions: ::prost::alloc::vec::Vec<ResurrectableSession>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct RunCommandResultPayload {
#[prost(int32, optional, tag = "1")]
#[prost(int32, optional, tag="1")]
pub exit_code: ::core::option::Option<i32>,
#[prost(bytes = "vec", tag = "2")]
#[prost(bytes="vec", tag="2")]
pub stdout: ::prost::alloc::vec::Vec<u8>,
#[prost(bytes = "vec", tag = "3")]
#[prost(bytes="vec", tag="3")]
pub stderr: ::prost::alloc::vec::Vec<u8>,
#[prost(message, repeated, tag = "4")]
#[prost(message, repeated, tag="4")]
pub context: ::prost::alloc::vec::Vec<ContextItem>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct WebRequestResultPayload {
#[prost(int32, tag = "1")]
#[prost(int32, tag="1")]
pub status: i32,
#[prost(message, repeated, tag = "2")]
#[prost(message, repeated, tag="2")]
pub headers: ::prost::alloc::vec::Vec<Header>,
#[prost(bytes = "vec", tag = "3")]
#[prost(bytes="vec", tag="3")]
pub body: ::prost::alloc::vec::Vec<u8>,
#[prost(message, repeated, tag = "4")]
#[prost(message, repeated, tag="4")]
pub context: ::prost::alloc::vec::Vec<ContextItem>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ContextItem {
#[prost(string, tag = "1")]
#[prost(string, tag="1")]
pub name: ::prost::alloc::string::String,
#[prost(string, tag = "2")]
#[prost(string, tag="2")]
pub value: ::prost::alloc::string::String,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Header {
#[prost(string, tag = "1")]
#[prost(string, tag="1")]
pub name: ::prost::alloc::string::String,
#[prost(string, tag = "2")]
#[prost(string, tag="2")]
pub value: ::prost::alloc::string::String,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct PermissionRequestResultPayload {
#[prost(bool, tag = "1")]
#[prost(bool, tag="1")]
pub granted: bool,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct FileListPayload {
#[prost(string, repeated, tag = "1")]
#[prost(string, repeated, tag="1")]
pub paths: ::prost::alloc::vec::Vec<::prost::alloc::string::String>,
#[prost(message, repeated, tag = "2")]
#[prost(message, repeated, tag="2")]
pub paths_metadata: ::prost::alloc::vec::Vec<FileMetadata>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct FileMetadata {
/// if this is false, the metadata for this file has not been read
#[prost(bool, tag = "1")]
#[prost(bool, tag="1")]
pub metadata_is_set: bool,
#[prost(bool, tag = "2")]
#[prost(bool, tag="2")]
pub is_dir: bool,
#[prost(bool, tag = "3")]
#[prost(bool, tag="3")]
pub is_file: bool,
#[prost(bool, tag = "4")]
#[prost(bool, tag="4")]
pub is_symlink: bool,
#[prost(uint64, tag = "5")]
#[prost(uint64, tag="5")]
pub len: u64,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct CustomMessagePayload {
#[prost(string, tag = "1")]
#[prost(string, tag="1")]
pub message_name: ::prost::alloc::string::String,
#[prost(string, tag = "2")]
#[prost(string, tag="2")]
pub payload: ::prost::alloc::string::String,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct MouseEventPayload {
#[prost(enumeration = "MouseEventName", tag = "1")]
#[prost(enumeration="MouseEventName", tag="1")]
pub mouse_event_name: i32,
#[prost(oneof = "mouse_event_payload::MouseEventPayload", tags = "2, 3")]
pub mouse_event_payload: ::core::option::Option<
mouse_event_payload::MouseEventPayload,
>,
#[prost(oneof="mouse_event_payload::MouseEventPayload", tags="2, 3")]
pub mouse_event_payload: ::core::option::Option<mouse_event_payload::MouseEventPayload>,
}
/// Nested message and enum types in `MouseEventPayload`.
pub mod mouse_event_payload {
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Oneof)]
#[derive(Clone, PartialEq, ::prost::Oneof)]
pub enum MouseEventPayload {
#[prost(uint32, tag = "2")]
#[prost(uint32, tag="2")]
LineCount(u32),
#[prost(message, tag = "3")]
#[prost(message, tag="3")]
Position(super::super::action::Position),
}
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct TabUpdatePayload {
#[prost(message, repeated, tag = "1")]
#[prost(message, repeated, tag="1")]
pub tab_info: ::prost::alloc::vec::Vec<TabInfo>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct PaneUpdatePayload {
#[prost(message, repeated, tag = "1")]
#[prost(message, repeated, tag="1")]
pub pane_manifest: ::prost::alloc::vec::Vec<PaneManifest>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct PaneManifest {
#[prost(uint32, tag = "1")]
#[prost(uint32, tag="1")]
pub tab_index: u32,
#[prost(message, repeated, tag = "2")]
#[prost(message, repeated, tag="2")]
pub panes: ::prost::alloc::vec::Vec<PaneInfo>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct SessionManifest {
#[prost(string, tag = "1")]
#[prost(string, tag="1")]
pub name: ::prost::alloc::string::String,
#[prost(message, repeated, tag = "2")]
#[prost(message, repeated, tag="2")]
pub tabs: ::prost::alloc::vec::Vec<TabInfo>,
#[prost(message, repeated, tag = "3")]
#[prost(message, repeated, tag="3")]
pub panes: ::prost::alloc::vec::Vec<PaneManifest>,
#[prost(uint32, tag = "4")]
#[prost(uint32, tag="4")]
pub connected_clients: u32,
#[prost(bool, tag = "5")]
#[prost(bool, tag="5")]
pub is_current_session: bool,
#[prost(message, repeated, tag = "6")]
#[prost(message, repeated, tag="6")]
pub available_layouts: ::prost::alloc::vec::Vec<LayoutInfo>,
#[prost(message, repeated, tag = "7")]
#[prost(message, repeated, tag="7")]
pub plugins: ::prost::alloc::vec::Vec<PluginInfo>,
#[prost(message, repeated, tag = "8")]
#[prost(message, repeated, tag="8")]
pub tab_history: ::prost::alloc::vec::Vec<ClientTabHistory>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ClientTabHistory {
#[prost(uint32, tag = "1")]
#[prost(uint32, tag="1")]
pub client_id: u32,
#[prost(uint32, repeated, tag = "2")]
#[prost(uint32, repeated, tag="2")]
pub tab_history: ::prost::alloc::vec::Vec<u32>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct PluginInfo {
#[prost(uint32, tag = "1")]
#[prost(uint32, tag="1")]
pub plugin_id: u32,
#[prost(string, tag = "2")]
#[prost(string, tag="2")]
pub plugin_url: ::prost::alloc::string::String,
#[prost(message, repeated, tag = "3")]
#[prost(message, repeated, tag="3")]
pub plugin_config: ::prost::alloc::vec::Vec<ContextItem>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct LayoutInfo {
#[prost(string, tag = "1")]
#[prost(string, tag="1")]
pub name: ::prost::alloc::string::String,
#[prost(string, tag = "2")]
#[prost(string, tag="2")]
pub source: ::prost::alloc::string::String,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ResurrectableSession {
#[prost(string, tag = "1")]
#[prost(string, tag="1")]
pub name: ::prost::alloc::string::String,
#[prost(uint64, tag = "2")]
#[prost(uint64, tag="2")]
pub creation_time: u64,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct PaneInfo {
#[prost(uint32, tag = "1")]
#[prost(uint32, tag="1")]
pub id: u32,
#[prost(bool, tag = "2")]
#[prost(bool, tag="2")]
pub is_plugin: bool,
#[prost(bool, tag = "3")]
#[prost(bool, tag="3")]
pub is_focused: bool,
#[prost(bool, tag = "4")]
#[prost(bool, tag="4")]
pub is_fullscreen: bool,
#[prost(bool, tag = "5")]
#[prost(bool, tag="5")]
pub is_floating: bool,
#[prost(bool, tag = "6")]
#[prost(bool, tag="6")]
pub is_suppressed: bool,
#[prost(string, tag = "7")]
#[prost(string, tag="7")]
pub title: ::prost::alloc::string::String,
#[prost(bool, tag = "8")]
#[prost(bool, tag="8")]
pub exited: bool,
#[prost(int32, optional, tag = "9")]
#[prost(int32, optional, tag="9")]
pub exit_status: ::core::option::Option<i32>,
#[prost(bool, tag = "10")]
#[prost(bool, tag="10")]
pub is_held: bool,
#[prost(uint32, tag = "11")]
#[prost(uint32, tag="11")]
pub pane_x: u32,
#[prost(uint32, tag = "12")]
#[prost(uint32, tag="12")]
pub pane_content_x: u32,
#[prost(uint32, tag = "13")]
#[prost(uint32, tag="13")]
pub pane_y: u32,
#[prost(uint32, tag = "14")]
#[prost(uint32, tag="14")]
pub pane_content_y: u32,
#[prost(uint32, tag = "15")]
#[prost(uint32, tag="15")]
pub pane_rows: u32,
#[prost(uint32, tag = "16")]
#[prost(uint32, tag="16")]
pub pane_content_rows: u32,
#[prost(uint32, tag = "17")]
#[prost(uint32, tag="17")]
pub pane_columns: u32,
#[prost(uint32, tag = "18")]
#[prost(uint32, tag="18")]
pub pane_content_columns: u32,
#[prost(message, optional, tag = "19")]
#[prost(message, optional, tag="19")]
pub cursor_coordinates_in_pane: ::core::option::Option<super::action::Position>,
#[prost(string, optional, tag = "20")]
#[prost(string, optional, tag="20")]
pub terminal_command: ::core::option::Option<::prost::alloc::string::String>,
#[prost(string, optional, tag = "21")]
#[prost(string, optional, tag="21")]
pub plugin_url: ::core::option::Option<::prost::alloc::string::String>,
#[prost(bool, tag = "22")]
#[prost(bool, tag="22")]
pub is_selectable: bool,
#[prost(message, repeated, tag="23")]
pub index_in_pane_group: ::prost::alloc::vec::Vec<IndexInPaneGroup>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct IndexInPaneGroup {
#[prost(uint32, tag="1")]
pub client_id: u32,
#[prost(uint32, tag="2")]
pub index: u32,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct TabInfo {
#[prost(uint32, tag = "1")]
#[prost(uint32, tag="1")]
pub position: u32,
#[prost(string, tag = "2")]
#[prost(string, tag="2")]
pub name: ::prost::alloc::string::String,
#[prost(bool, tag = "3")]
#[prost(bool, tag="3")]
pub active: bool,
#[prost(uint32, tag = "4")]
#[prost(uint32, tag="4")]
pub panes_to_hide: u32,
#[prost(bool, tag = "5")]
#[prost(bool, tag="5")]
pub is_fullscreen_active: bool,
#[prost(bool, tag = "6")]
#[prost(bool, tag="6")]
pub is_sync_panes_active: bool,
#[prost(bool, tag = "7")]
#[prost(bool, tag="7")]
pub are_floating_panes_visible: bool,
#[prost(uint32, repeated, tag = "8")]
#[prost(uint32, repeated, tag="8")]
pub other_focused_clients: ::prost::alloc::vec::Vec<u32>,
#[prost(string, optional, tag = "9")]
#[prost(string, optional, tag="9")]
pub active_swap_layout_name: ::core::option::Option<::prost::alloc::string::String>,
#[prost(bool, tag = "10")]
#[prost(bool, tag="10")]
pub is_swap_layout_dirty: bool,
#[prost(uint32, tag = "11")]
#[prost(uint32, tag="11")]
pub viewport_rows: u32,
#[prost(uint32, tag = "12")]
#[prost(uint32, tag="12")]
pub viewport_columns: u32,
#[prost(uint32, tag = "13")]
#[prost(uint32, tag="13")]
pub display_area_rows: u32,
#[prost(uint32, tag = "14")]
#[prost(uint32, tag="14")]
pub display_area_columns: u32,
#[prost(uint32, tag = "15")]
#[prost(uint32, tag="15")]
pub selectable_tiled_panes_count: u32,
#[prost(uint32, tag = "16")]
#[prost(uint32, tag="16")]
pub selectable_floating_panes_count: u32,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ModeUpdatePayload {
#[prost(enumeration = "super::input_mode::InputMode", tag = "1")]
#[prost(enumeration="super::input_mode::InputMode", tag="1")]
pub current_mode: i32,
#[prost(message, repeated, tag = "2")]
#[prost(message, repeated, tag="2")]
pub keybinds: ::prost::alloc::vec::Vec<InputModeKeybinds>,
#[prost(message, optional, tag = "3")]
#[prost(message, optional, tag="3")]
pub style: ::core::option::Option<super::style::Style>,
#[prost(bool, tag = "4")]
#[prost(bool, tag="4")]
pub arrow_fonts_support: bool,
#[prost(string, optional, tag = "5")]
#[prost(string, optional, tag="5")]
pub session_name: ::core::option::Option<::prost::alloc::string::String>,
#[prost(enumeration = "super::input_mode::InputMode", optional, tag = "6")]
#[prost(enumeration="super::input_mode::InputMode", optional, tag="6")]
pub base_mode: ::core::option::Option<i32>,
#[prost(string, optional, tag = "7")]
#[prost(string, optional, tag="7")]
pub editor: ::core::option::Option<::prost::alloc::string::String>,
#[prost(string, optional, tag = "8")]
#[prost(string, optional, tag="8")]
pub shell: ::core::option::Option<::prost::alloc::string::String>,
#[prost(bool, optional, tag="9")]
pub currently_marking_pane_group: ::core::option::Option<bool>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct InputModeKeybinds {
#[prost(enumeration = "super::input_mode::InputMode", tag = "1")]
#[prost(enumeration="super::input_mode::InputMode", tag="1")]
pub mode: i32,
#[prost(message, repeated, tag = "2")]
#[prost(message, repeated, tag="2")]
pub key_bind: ::prost::alloc::vec::Vec<KeyBind>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct KeyBind {
#[prost(message, optional, tag = "1")]
#[prost(message, optional, tag="1")]
pub key: ::core::option::Option<super::key::Key>,
#[prost(message, repeated, tag = "2")]
#[prost(message, repeated, tag="2")]
pub action: ::prost::alloc::vec::Vec<super::action::Action>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]
@ -522,6 +529,7 @@ pub enum EventType {
FailedToChangeHostFolder = 28,
PastedText = 29,
ConfigWasWrittenToDisk = 30,
BeforeClose = 31,
}
impl EventType {
/// String value of the enum field names used in the ProtoBuf definition.
@ -561,6 +569,7 @@ impl EventType {
EventType::FailedToChangeHostFolder => "FailedToChangeHostFolder",
EventType::PastedText => "PastedText",
EventType::ConfigWasWrittenToDisk => "ConfigWasWrittenToDisk",
EventType::BeforeClose => "BeforeClose",
}
}
/// Creates an enum from field names used in the ProtoBuf definition.
@ -597,6 +606,7 @@ impl EventType {
"FailedToChangeHostFolder" => Some(Self::FailedToChangeHostFolder),
"PastedText" => Some(Self::PastedText),
"ConfigWasWrittenToDisk" => Some(Self::ConfigWasWrittenToDisk),
"BeforeClose" => Some(Self::BeforeClose),
_ => None,
}
}

View file

@ -1,10 +1,10 @@
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct File {
#[prost(string, tag = "1")]
#[prost(string, tag="1")]
pub path: ::prost::alloc::string::String,
#[prost(int32, optional, tag = "2")]
#[prost(int32, optional, tag="2")]
pub line_number: ::core::option::Option<i32>,
#[prost(string, optional, tag = "3")]
#[prost(string, optional, tag="3")]
pub cwd: ::core::option::Option<::prost::alloc::string::String>,
}

View file

@ -1,7 +1,7 @@
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct InputModeMessage {
#[prost(enumeration = "InputMode", tag = "1")]
#[prost(enumeration="InputMode", tag="1")]
pub input_mode: i32,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]

View file

@ -1,26 +1,16 @@
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Key {
#[prost(enumeration = "key::KeyModifier", optional, tag = "1")]
#[prost(enumeration="key::KeyModifier", optional, tag="1")]
pub modifier: ::core::option::Option<i32>,
#[prost(enumeration = "key::KeyModifier", repeated, tag = "4")]
#[prost(enumeration="key::KeyModifier", repeated, tag="4")]
pub additional_modifiers: ::prost::alloc::vec::Vec<i32>,
#[prost(oneof = "key::MainKey", tags = "2, 3")]
#[prost(oneof="key::MainKey", tags="2, 3")]
pub main_key: ::core::option::Option<key::MainKey>,
}
/// Nested message and enum types in `Key`.
pub mod key {
#[derive(
Clone,
Copy,
Debug,
PartialEq,
Eq,
Hash,
PartialOrd,
Ord,
::prost::Enumeration
)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]
#[repr(i32)]
pub enum KeyModifier {
Ctrl = 0,
@ -52,17 +42,7 @@ pub mod key {
}
}
}
#[derive(
Clone,
Copy,
Debug,
PartialEq,
Eq,
Hash,
PartialOrd,
Ord,
::prost::Enumeration
)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]
#[repr(i32)]
pub enum NamedKey {
PageDown = 0,
@ -178,17 +158,7 @@ pub mod key {
}
}
}
#[derive(
Clone,
Copy,
Debug,
PartialEq,
Eq,
Hash,
PartialOrd,
Ord,
::prost::Enumeration
)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]
#[repr(i32)]
pub enum Char {
A = 0,
@ -317,11 +287,11 @@ pub mod key {
}
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Oneof)]
#[derive(Clone, PartialEq, ::prost::Oneof)]
pub enum MainKey {
#[prost(enumeration = "NamedKey", tag = "2")]
#[prost(enumeration="NamedKey", tag="2")]
Key(i32),
#[prost(enumeration = "Char", tag = "3")]
#[prost(enumeration="Char", tag="3")]
Char(i32),
}
}

View file

@ -1,10 +1,10 @@
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Message {
#[prost(string, tag = "1")]
#[prost(string, tag="1")]
pub name: ::prost::alloc::string::String,
#[prost(string, tag = "2")]
#[prost(string, tag="2")]
pub payload: ::prost::alloc::string::String,
#[prost(string, optional, tag = "3")]
#[prost(string, optional, tag="3")]
pub worker_name: ::core::option::Option<::prost::alloc::string::String>,
}

View file

@ -1,27 +1,27 @@
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct PipeMessage {
#[prost(enumeration = "PipeSource", tag = "1")]
#[prost(enumeration="PipeSource", tag="1")]
pub source: i32,
#[prost(string, optional, tag = "2")]
#[prost(string, optional, tag="2")]
pub cli_source_id: ::core::option::Option<::prost::alloc::string::String>,
#[prost(uint32, optional, tag = "3")]
#[prost(uint32, optional, tag="3")]
pub plugin_source_id: ::core::option::Option<u32>,
#[prost(string, tag = "4")]
#[prost(string, tag="4")]
pub name: ::prost::alloc::string::String,
#[prost(string, optional, tag = "5")]
#[prost(string, optional, tag="5")]
pub payload: ::core::option::Option<::prost::alloc::string::String>,
#[prost(message, repeated, tag = "6")]
#[prost(message, repeated, tag="6")]
pub args: ::prost::alloc::vec::Vec<Arg>,
#[prost(bool, tag = "7")]
#[prost(bool, tag="7")]
pub is_private: bool,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Arg {
#[prost(string, tag = "1")]
#[prost(string, tag="1")]
pub key: ::prost::alloc::string::String,
#[prost(string, tag = "2")]
#[prost(string, tag="2")]
pub value: ::prost::alloc::string::String,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]

File diff suppressed because it is too large Load diff

View file

@ -1,16 +1,18 @@
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct PluginIds {
#[prost(int32, tag = "1")]
#[prost(int32, tag="1")]
pub plugin_id: i32,
#[prost(int32, tag = "2")]
#[prost(int32, tag="2")]
pub zellij_pid: i32,
#[prost(string, tag = "3")]
#[prost(string, tag="3")]
pub initial_cwd: ::prost::alloc::string::String,
#[prost(uint32, tag="4")]
pub client_id: u32,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ZellijVersion {
#[prost(string, tag = "1")]
#[prost(string, tag="1")]
pub version: ::prost::alloc::string::String,
}

View file

@ -28,9 +28,7 @@ impl PermissionType {
PermissionType::WriteToStdin => "WriteToStdin",
PermissionType::WebAccess => "WebAccess",
PermissionType::ReadCliPipes => "ReadCliPipes",
PermissionType::MessageAndLaunchOtherPlugins => {
"MessageAndLaunchOtherPlugins"
}
PermissionType::MessageAndLaunchOtherPlugins => "MessageAndLaunchOtherPlugins",
PermissionType::Reconfigure => "Reconfigure",
PermissionType::FullHdAccess => "FullHdAccess",
}

View file

@ -1,15 +1,15 @@
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Resize {
#[prost(enumeration = "ResizeAction", tag = "1")]
#[prost(enumeration="ResizeAction", tag="1")]
pub resize_action: i32,
#[prost(enumeration = "ResizeDirection", optional, tag = "2")]
#[prost(enumeration="ResizeDirection", optional, tag="2")]
pub direction: ::core::option::Option<i32>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct MoveDirection {
#[prost(enumeration = "ResizeDirection", tag = "1")]
#[prost(enumeration="ResizeDirection", tag="1")]
pub direction: i32,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]

View file

@ -2,116 +2,116 @@
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Style {
#[deprecated]
#[prost(message, optional, tag = "1")]
#[prost(message, optional, tag="1")]
pub palette: ::core::option::Option<Palette>,
#[prost(bool, tag = "2")]
#[prost(bool, tag="2")]
pub rounded_corners: bool,
#[prost(bool, tag = "3")]
#[prost(bool, tag="3")]
pub hide_session_name: bool,
#[prost(message, optional, tag = "4")]
#[prost(message, optional, tag="4")]
pub styling: ::core::option::Option<Styling>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Palette {
#[prost(enumeration = "ThemeHue", tag = "1")]
#[prost(enumeration="ThemeHue", tag="1")]
pub theme_hue: i32,
#[prost(message, optional, tag = "2")]
#[prost(message, optional, tag="2")]
pub fg: ::core::option::Option<Color>,
#[prost(message, optional, tag = "3")]
#[prost(message, optional, tag="3")]
pub bg: ::core::option::Option<Color>,
#[prost(message, optional, tag = "4")]
#[prost(message, optional, tag="4")]
pub black: ::core::option::Option<Color>,
#[prost(message, optional, tag = "5")]
#[prost(message, optional, tag="5")]
pub red: ::core::option::Option<Color>,
#[prost(message, optional, tag = "6")]
#[prost(message, optional, tag="6")]
pub green: ::core::option::Option<Color>,
#[prost(message, optional, tag = "7")]
#[prost(message, optional, tag="7")]
pub yellow: ::core::option::Option<Color>,
#[prost(message, optional, tag = "8")]
#[prost(message, optional, tag="8")]
pub blue: ::core::option::Option<Color>,
#[prost(message, optional, tag = "9")]
#[prost(message, optional, tag="9")]
pub magenta: ::core::option::Option<Color>,
#[prost(message, optional, tag = "10")]
#[prost(message, optional, tag="10")]
pub cyan: ::core::option::Option<Color>,
#[prost(message, optional, tag = "11")]
#[prost(message, optional, tag="11")]
pub white: ::core::option::Option<Color>,
#[prost(message, optional, tag = "12")]
#[prost(message, optional, tag="12")]
pub orange: ::core::option::Option<Color>,
#[prost(message, optional, tag = "13")]
#[prost(message, optional, tag="13")]
pub gray: ::core::option::Option<Color>,
#[prost(message, optional, tag = "14")]
#[prost(message, optional, tag="14")]
pub purple: ::core::option::Option<Color>,
#[prost(message, optional, tag = "15")]
#[prost(message, optional, tag="15")]
pub gold: ::core::option::Option<Color>,
#[prost(message, optional, tag = "16")]
#[prost(message, optional, tag="16")]
pub silver: ::core::option::Option<Color>,
#[prost(message, optional, tag = "17")]
#[prost(message, optional, tag="17")]
pub pink: ::core::option::Option<Color>,
#[prost(message, optional, tag = "18")]
#[prost(message, optional, tag="18")]
pub brown: ::core::option::Option<Color>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Color {
#[prost(enumeration = "ColorType", tag = "1")]
#[prost(enumeration="ColorType", tag="1")]
pub color_type: i32,
#[prost(oneof = "color::Payload", tags = "2, 3")]
#[prost(oneof="color::Payload", tags="2, 3")]
pub payload: ::core::option::Option<color::Payload>,
}
/// Nested message and enum types in `Color`.
pub mod color {
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Oneof)]
#[derive(Clone, PartialEq, ::prost::Oneof)]
pub enum Payload {
#[prost(message, tag = "2")]
#[prost(message, tag="2")]
RgbColorPayload(super::RgbColorPayload),
#[prost(uint32, tag = "3")]
#[prost(uint32, tag="3")]
EightBitColorPayload(u32),
}
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct RgbColorPayload {
#[prost(uint32, tag = "1")]
#[prost(uint32, tag="1")]
pub red: u32,
#[prost(uint32, tag = "2")]
#[prost(uint32, tag="2")]
pub green: u32,
#[prost(uint32, tag = "3")]
#[prost(uint32, tag="3")]
pub blue: u32,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Styling {
#[prost(message, repeated, tag = "1")]
#[prost(message, repeated, tag="1")]
pub text_unselected: ::prost::alloc::vec::Vec<Color>,
#[prost(message, repeated, tag = "2")]
#[prost(message, repeated, tag="2")]
pub text_selected: ::prost::alloc::vec::Vec<Color>,
#[prost(message, repeated, tag = "3")]
#[prost(message, repeated, tag="3")]
pub ribbon_unselected: ::prost::alloc::vec::Vec<Color>,
#[prost(message, repeated, tag = "4")]
#[prost(message, repeated, tag="4")]
pub ribbon_selected: ::prost::alloc::vec::Vec<Color>,
#[prost(message, repeated, tag = "5")]
#[prost(message, repeated, tag="5")]
pub table_title: ::prost::alloc::vec::Vec<Color>,
#[prost(message, repeated, tag = "6")]
#[prost(message, repeated, tag="6")]
pub table_cell_unselected: ::prost::alloc::vec::Vec<Color>,
#[prost(message, repeated, tag = "7")]
#[prost(message, repeated, tag="7")]
pub table_cell_selected: ::prost::alloc::vec::Vec<Color>,
#[prost(message, repeated, tag = "8")]
#[prost(message, repeated, tag="8")]
pub list_unselected: ::prost::alloc::vec::Vec<Color>,
#[prost(message, repeated, tag = "9")]
#[prost(message, repeated, tag="9")]
pub list_selected: ::prost::alloc::vec::Vec<Color>,
#[prost(message, repeated, tag = "10")]
#[prost(message, repeated, tag="10")]
pub frame_unselected: ::prost::alloc::vec::Vec<Color>,
#[prost(message, repeated, tag = "11")]
#[prost(message, repeated, tag="11")]
pub frame_selected: ::prost::alloc::vec::Vec<Color>,
#[prost(message, repeated, tag = "12")]
#[prost(message, repeated, tag="12")]
pub frame_highlight: ::prost::alloc::vec::Vec<Color>,
#[prost(message, repeated, tag = "13")]
#[prost(message, repeated, tag="13")]
pub exit_code_success: ::prost::alloc::vec::Vec<Color>,
#[prost(message, repeated, tag = "14")]
#[prost(message, repeated, tag="14")]
pub exit_code_error: ::prost::alloc::vec::Vec<Color>,
#[prost(message, repeated, tag = "15")]
#[prost(message, repeated, tag="15")]
pub multiplayer_user_colors: ::prost::alloc::vec::Vec<Color>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]

View file

@ -6,7 +6,7 @@ themes {
emphasis_0 254 100 11
emphasis_1 4 165 229
emphasis_2 64 160 43
emphasis_3 234 118 203
emphasis_3 210 15 57
}
text_selected {
base 76 79 105
@ -27,7 +27,7 @@ themes {
ribbon_unselected {
base 220 224 232
background 92 95 119
emphasis_0 210 15 57
emphasis_0 234 118 203
emphasis_1 76 79 105
emphasis_2 30 102 245
emphasis_3 234 118 203
@ -83,8 +83,8 @@ themes {
frame_highlight {
base 254 100 11
background 0
emphasis_0 254 100 11
emphasis_1 254 100 11
emphasis_0 210 15 57
emphasis_1 136 57 239
emphasis_2 254 100 11
emphasis_3 254 100 11
}

View file

@ -131,6 +131,7 @@ mod not_wasm {
add_plugin!(assets, "configuration.wasm");
add_plugin!(assets, "plugin-manager.wasm");
add_plugin!(assets, "about.wasm");
add_plugin!(assets, "multiple-select.wasm");
assets
};
}

View file

@ -494,6 +494,10 @@ impl KeyWithModifier {
pub fn is_key_with_super_modifier(&self, key: BareKey) -> bool {
self.bare_key == key && self.key_modifiers.contains(&KeyModifier::Super)
}
pub fn is_cancel_key(&self) -> bool {
// self.bare_key == BareKey::Esc || self.is_key_with_ctrl_modifier(BareKey::Char('c'))
self.bare_key == BareKey::Esc
}
#[cfg(not(target_family = "wasm"))]
pub fn to_termwiz_modifiers(&self) -> Modifiers {
let mut modifiers = Modifiers::empty();
@ -933,6 +937,7 @@ pub enum Event {
FailedToChangeHostFolder(Option<String>), // String -> the error we got when changing
PastedText(String),
ConfigWasWrittenToDisk,
BeforeClose,
}
#[derive(
@ -1274,8 +1279,8 @@ pub const DEFAULT_STYLES: Styling = Styling {
},
frame_highlight: StyleDeclaration {
base: PaletteColor::EightBit(default_colors::ORANGE),
emphasis_0: PaletteColor::EightBit(default_colors::GREEN),
emphasis_1: PaletteColor::EightBit(default_colors::GREEN),
emphasis_0: PaletteColor::EightBit(default_colors::MAGENTA),
emphasis_1: PaletteColor::EightBit(default_colors::PURPLE),
emphasis_2: PaletteColor::EightBit(default_colors::GREEN),
emphasis_3: PaletteColor::EightBit(default_colors::GREEN),
background: PaletteColor::EightBit(default_colors::GREEN),
@ -1432,8 +1437,8 @@ impl From<Palette> for Styling {
},
frame_highlight: StyleDeclaration {
base: palette.orange,
emphasis_0: palette.orange,
emphasis_1: palette.orange,
emphasis_0: palette.magenta,
emphasis_1: palette.purple,
emphasis_2: palette.orange,
emphasis_3: palette.orange,
background: Default::default(),
@ -1508,6 +1513,7 @@ pub struct ModeInfo {
pub session_name: Option<String>,
pub editor: Option<PathBuf>,
pub shell: Option<PathBuf>,
pub currently_marking_pane_group: Option<bool>,
}
impl ModeInfo {
@ -1733,6 +1739,9 @@ pub struct PaneInfo {
/// Unselectable panes are often used for UI elements that do not have direct user interaction
/// (eg. the default `status-bar` or `tab-bar`).
pub is_selectable: bool,
/// Grouped panes (usually through an explicit user action) that are staged for a bulk action
/// the index is kept track of in order to preserve the pane group order
pub index_in_pane_group: BTreeMap<ClientId, usize>,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub struct ClientInfo {
@ -1763,6 +1772,7 @@ pub struct PluginIds {
pub plugin_id: u32,
pub zellij_pid: u32,
pub initial_cwd: PathBuf,
pub client_id: ClientId,
}
/// Tag used to identify the plugin in layout and config kdl files
@ -2307,4 +2317,10 @@ pub enum PluginCommand {
OpenFileNearPlugin(FileToOpen, Context),
OpenFileFloatingNearPlugin(FileToOpen, Option<FloatingPaneCoordinates>, Context),
OpenFileInPlaceOfPlugin(FileToOpen, bool, Context), // bool -> close_plugin_after_replace
GroupAndUngroupPanes(Vec<PaneId>, Vec<PaneId>), // panes to group, panes to ungroup
HighlightAndUnhighlightPanes(Vec<PaneId>, Vec<PaneId>), // panes to highlight, panes to
// unhighlight
CloseMultiplePanes(Vec<PaneId>),
FloatMultiplePanes(Vec<PaneId>),
EmbedMultiplePanes(Vec<PaneId>),
}

View file

@ -375,6 +375,13 @@ pub enum ScreenContext {
SetFloatingPanePinned,
StackPanes,
ChangeFloatingPanesCoordinates,
AddHighlightPaneFrameColorOverride,
GroupAndUngroupPanes,
HighlightAndUnhighlightPanes,
FloatMultiplePanes,
EmbedMultiplePanes,
TogglePaneInGroup,
ToggleGroupMarking,
}
/// Stack call representations corresponding to the different types of [`PtyInstruction`]s.
@ -512,6 +519,7 @@ pub enum BackgroundJobContext {
WebRequest,
ReportPluginList,
RenderToClients,
HighlightPanesWithMessage,
Exit,
}

View file

@ -296,6 +296,8 @@ pub enum Action {
TogglePanePinned,
StackPanes(Vec<PaneId>),
ChangeFloatingPaneCoordinates(PaneId, FloatingPaneCoordinates),
TogglePaneInGroup,
ToggleGroupMarking,
}
impl Action {

View file

@ -45,6 +45,7 @@ mod not_wasm {
session_name,
editor: None,
shell: None,
currently_marking_pane_group: None,
}
}

View file

@ -208,4 +208,34 @@ impl MouseEvent {
};
event
}
pub fn new_left_press_with_alt_event(position: Position) -> Self {
let event = MouseEvent {
event_type: MouseEventType::Press,
left: true,
right: false,
middle: false,
wheel_up: false,
wheel_down: false,
shift: false,
alt: true,
ctrl: false,
position,
};
event
}
pub fn new_right_press_with_alt_event(position: Position) -> Self {
let event = MouseEvent {
event_type: MouseEventType::Press,
left: false,
right: true,
middle: false,
wheel_up: false,
wheel_down: false,
shift: false,
alt: true,
ctrl: false,
position,
};
event
}
}

View file

@ -179,6 +179,12 @@ pub struct Options {
#[clap(long, value_parser)]
#[serde(default)]
pub show_release_notes: Option<bool>,
/// Whether to enable mouse hover effects and pane grouping functionality
/// default is true
#[clap(long, value_parser)]
#[serde(default)]
pub advanced_mouse_actions: Option<bool>,
}
#[derive(ArgEnum, Deserialize, Serialize, Debug, Clone, Copy, PartialEq)]
@ -260,6 +266,7 @@ impl Options {
let stacked_resize = other.stacked_resize.or(self.stacked_resize);
let show_startup_tips = other.show_startup_tips.or(self.show_startup_tips);
let show_release_notes = other.show_release_notes.or(self.show_release_notes);
let advanced_mouse_actions = other.advanced_mouse_actions.or(self.advanced_mouse_actions);
Options {
simplified_ui,
@ -292,6 +299,7 @@ impl Options {
stacked_resize,
show_startup_tips,
show_release_notes,
advanced_mouse_actions,
}
}
@ -353,6 +361,7 @@ impl Options {
let stacked_resize = other.stacked_resize.or(self.stacked_resize);
let show_startup_tips = other.show_startup_tips.or(self.show_startup_tips);
let show_release_notes = other.show_release_notes.or(self.show_release_notes);
let advanced_mouse_actions = other.advanced_mouse_actions.or(self.advanced_mouse_actions);
Options {
simplified_ui,
@ -385,6 +394,7 @@ impl Options {
stacked_resize,
show_startup_tips,
show_release_notes,
advanced_mouse_actions,
}
}
@ -451,8 +461,9 @@ impl From<CliOptions> for Options {
serialization_interval: opts.serialization_interval,
support_kitty_keyboard_protocol: opts.support_kitty_keyboard_protocol,
stacked_resize: opts.stacked_resize,
show_release_notes: opts.show_release_notes,
show_startup_tips: opts.show_startup_tips,
show_release_notes: opts.show_release_notes,
advanced_mouse_actions: opts.advanced_mouse_actions,
..Default::default()
}
}

View file

@ -65,6 +65,7 @@ impl PluginConfig {
|| tag == "configuration"
|| tag == "plugin-manager"
|| tag == "about"
|| tag == "multiple-select"
{
Some(PluginConfig {
path: PathBuf::from(&tag),

View file

@ -449,16 +449,12 @@ expression: "format!(\"{:#?}\", theme)"
emphasis_0: Rgb(
(
255,
184,
108,
121,
198,
),
),
emphasis_1: Rgb(
(
255,
184,
108,
),
emphasis_1: EightBit(
0,
),
emphasis_2: Rgb(
(

View file

@ -1103,6 +1103,8 @@ impl Action {
Some(node)
},
Action::TogglePanePinned => Some(KdlNode::new("TogglePanePinned")),
Action::TogglePaneInGroup => Some(KdlNode::new("TogglePaneInGroup")),
Action::ToggleGroupMarking => Some(KdlNode::new("ToggleGroupMarking")),
_ => None,
}
}
@ -1798,6 +1800,8 @@ impl TryFrom<(&KdlNode, &Options)> for Action {
})
},
"TogglePanePinned" => Ok(Action::TogglePanePinned),
"TogglePaneInGroup" => Ok(Action::TogglePaneInGroup),
"ToggleGroupMarking" => Ok(Action::ToggleGroupMarking),
_ => Err(ConfigError::new_kdl_error(
format!("Unsupported action: {}", action_name).into(),
kdl_action.span().offset(),
@ -2299,6 +2303,9 @@ impl Options {
let show_release_notes =
kdl_property_first_arg_as_bool_or_error!(kdl_options, "show_release_notes")
.map(|(v, _)| v);
let advanced_mouse_actions =
kdl_property_first_arg_as_bool_or_error!(kdl_options, "advanced_mouse_actions")
.map(|(v, _)| v);
Ok(Options {
simplified_ui,
theme,
@ -2330,6 +2337,7 @@ impl Options {
stacked_resize,
show_startup_tips,
show_release_notes,
advanced_mouse_actions,
})
}
pub fn from_string(stringified_keybindings: &String) -> Result<Self, ConfigError> {
@ -3207,6 +3215,33 @@ impl Options {
None
}
}
fn advanced_mouse_actions_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
let comment_text = format!(
"{}\n{}\n{}",
" ",
"// Whether to enable mouse hover effects and pane grouping functionality",
"// default is true",
);
let create_node = |node_value: bool| -> KdlNode {
let mut node = KdlNode::new("advanced_mouse_actions");
node.push(KdlValue::Bool(node_value));
node
};
if let Some(advanced_mouse_actions) = self.advanced_mouse_actions {
let mut node = create_node(advanced_mouse_actions);
if add_comments {
node.set_leading(format!("{}\n", comment_text));
}
Some(node)
} else if add_comments {
let mut node = create_node(false);
node.set_leading(format!("{}\n// ", comment_text));
Some(node)
} else {
None
}
}
pub fn to_kdl(&self, add_comments: bool) -> Vec<KdlNode> {
let mut nodes = vec![];
if let Some(simplified_ui_node) = self.simplified_ui_to_kdl(add_comments) {
@ -3303,6 +3338,9 @@ impl Options {
if let Some(show_release_notes) = self.show_release_notes_to_kdl(add_comments) {
nodes.push(show_release_notes);
}
if let Some(advanced_mouse_actions) = self.advanced_mouse_actions_to_kdl(add_comments) {
nodes.push(advanced_mouse_actions);
}
nodes
}
}
@ -4886,6 +4924,7 @@ impl PaneInfo {
terminal_command,
plugin_url,
is_selectable,
index_in_pane_group: Default::default(), // we don't serialize this
};
Ok((tab_position, pane_info))
}
@ -5033,6 +5072,7 @@ fn serialize_and_deserialize_session_info_with_data() {
terminal_command: Some("foo".to_owned()),
plugin_url: None,
is_selectable: true,
index_in_pane_group: Default::default(), // we don't serialize this
},
PaneInfo {
id: 1,
@ -5057,6 +5097,7 @@ fn serialize_and_deserialize_session_info_with_data() {
terminal_command: None,
plugin_url: Some("i_am_a_fake_plugin".to_owned()),
is_selectable: true,
index_in_pane_group: Default::default(), // we don't serialize this
},
];
let mut panes = HashMap::new();

View file

@ -1,6 +1,5 @@
---
source: zellij-utils/src/kdl/mod.rs
assertion_line: 5922
expression: fake_config_stringified
---
keybinds clear-defaults=true {
@ -147,8 +146,16 @@ keybinds clear-defaults=true {
bind "Alt j" { MoveFocus "down"; }
bind "Alt k" { MoveFocus "up"; }
bind "Alt l" { MoveFocusOrTab "right"; }
bind "Alt m" {
LaunchOrFocusPlugin "zellij:multiple-select" {
floating true
move_to_focused_tab true
}
}
bind "Alt n" { NewPane; }
bind "Alt o" { MoveTab "right"; }
bind "Alt p" { TogglePaneInGroup; }
bind "Alt Shift p" { ToggleGroupMarking; }
bind "Ctrl q" { Quit; }
}
shared_except "locked" "move" {
@ -256,4 +263,3 @@ plugins {
}
load_plugins {
}

View file

@ -1,6 +1,5 @@
---
source: zellij-utils/src/kdl/mod.rs
assertion_line: 5934
expression: fake_config_stringified
---
keybinds clear-defaults=true {
@ -147,8 +146,16 @@ keybinds clear-defaults=true {
bind "Alt j" { MoveFocus "down"; }
bind "Alt k" { MoveFocus "up"; }
bind "Alt l" { MoveFocusOrTab "right"; }
bind "Alt m" {
LaunchOrFocusPlugin "zellij:multiple-select" {
floating true
move_to_focused_tab true
}
}
bind "Alt n" { NewPane; }
bind "Alt o" { MoveTab "right"; }
bind "Alt p" { TogglePaneInGroup; }
bind "Alt Shift p" { ToggleGroupMarking; }
bind "Ctrl q" { Quit; }
}
shared_except "locked" "move" {
@ -455,4 +462,7 @@ load_plugins {
// Default: true
//
// show_release_notes false
// Whether to enable mouse hover effects and pane grouping functionality
// default is true
// advanced_mouse_actions false

View file

@ -1,6 +1,5 @@
---
source: zellij-utils/src/kdl/mod.rs
assertion_line: 5873
expression: fake_document.to_string()
---
@ -195,4 +194,7 @@ support_kitty_keyboard_protocol false
// Default: true
//
// show_release_notes false
// Whether to enable mouse hover effects and pane grouping functionality
// default is true
// advanced_mouse_actions false

View file

@ -87,8 +87,8 @@ themes {
frame_highlight {
base 255 184 108
background 0
emphasis_0 255 184 108
emphasis_1 255 184 108
emphasis_0 255 121 198
emphasis_1 0
emphasis_2 255 184 108
emphasis_3 255 184 108
}

View file

@ -87,8 +87,8 @@ themes {
frame_highlight {
base 208 135 112
background 0
emphasis_0 208 135 112
emphasis_1 208 135 112
emphasis_0 70
emphasis_1 0
emphasis_2 208 135 112
emphasis_3 208 135 112
}

View file

@ -87,8 +87,8 @@ themes {
frame_highlight {
base 254
background 0
emphasis_0 254
emphasis_1 254
emphasis_0 70
emphasis_1 0
emphasis_2 254
emphasis_3 254
}

View file

@ -87,8 +87,8 @@ themes {
frame_highlight {
base 208 135 112
background 0
emphasis_0 208 135 112
emphasis_1 208 135 112
emphasis_0 180 142 173
emphasis_1 0
emphasis_2 208 135 112
emphasis_3 208 135 112
}

View file

@ -87,8 +87,8 @@ themes {
frame_highlight {
base 255 184 108
background 0
emphasis_0 255 184 108
emphasis_1 255 184 108
emphasis_0 255 121 198
emphasis_1 0
emphasis_2 255 184 108
emphasis_3 255 184 108
}
@ -205,8 +205,8 @@ themes {
frame_highlight {
base 208 135 112
background 0
emphasis_0 208 135 112
emphasis_1 208 135 112
emphasis_0 180 142 173
emphasis_1 0
emphasis_2 208 135 112
emphasis_3 208 135 112
}

View file

@ -241,6 +241,8 @@ enum ActionName {
KeybindPipe = 84;
TogglePanePinned = 85;
MouseEvent = 86;
TogglePaneInGroup = 87;
ToggleGroupMarking = 88;
}
message Position {

View file

@ -692,6 +692,16 @@ impl TryFrom<ProtobufAction> for Action {
Some(_) => Err("TogglePanePinned should not have a payload"),
None => Ok(Action::TogglePanePinned),
},
Some(ProtobufActionName::TogglePaneInGroup) => match protobuf_action.optional_payload {
Some(_) => Err("TogglePaneInGroup should not have a payload"),
None => Ok(Action::TogglePaneInGroup),
},
Some(ProtobufActionName::ToggleGroupMarking) => {
match protobuf_action.optional_payload {
Some(_) => Err("ToggleGroupMarking should not have a payload"),
None => Ok(Action::ToggleGroupMarking),
}
},
Some(ProtobufActionName::KeybindPipe) => match protobuf_action.optional_payload {
Some(_) => Err("KeybindPipe should not have a payload"),
// TODO: at some point we might want to support a payload here
@ -1236,6 +1246,14 @@ impl TryFrom<Action> for ProtobufAction {
name: ProtobufActionName::TogglePanePinned as i32,
optional_payload: None,
}),
Action::TogglePaneInGroup { .. } => Ok(ProtobufAction {
name: ProtobufActionName::TogglePaneInGroup as i32,
optional_payload: None,
}),
Action::ToggleGroupMarking { .. } => Ok(ProtobufAction {
name: ProtobufActionName::ToggleGroupMarking as i32,
optional_payload: None,
}),
Action::NoOp
| Action::Confirm
| Action::NewInPlacePane(..)

View file

@ -54,6 +54,7 @@ enum EventType {
FailedToChangeHostFolder = 28;
PastedText = 29;
ConfigWasWrittenToDisk = 30;
BeforeClose = 31;
}
message EventNameList {
@ -304,6 +305,12 @@ message PaneInfo {
optional string terminal_command = 20;
optional string plugin_url = 21;
bool is_selectable = 22;
repeated IndexInPaneGroup index_in_pane_group = 23;
}
message IndexInPaneGroup {
uint32 client_id = 1;
uint32 index = 2;
}
message TabInfo {
@ -334,6 +341,7 @@ message ModeUpdatePayload {
optional input_mode.InputMode base_mode = 6;
optional string editor = 7;
optional string shell = 8;
optional bool currently_marking_pane_group = 9;
}
message InputModeKeybinds {

View file

@ -359,6 +359,10 @@ impl TryFrom<ProtobufEvent> for Event {
None => Ok(Event::ConfigWasWrittenToDisk),
_ => Err("Malformed payload for the ConfigWasWrittenToDisk Event"),
},
Some(ProtobufEventType::BeforeClose) => match protobuf_event.payload {
None => Ok(Event::BeforeClose),
_ => Err("Malformed payload for the BeforeClose Event"),
},
None => Err("Unknown Protobuf Event"),
}
}
@ -733,6 +737,10 @@ impl TryFrom<Event> for ProtobufEvent {
name: ProtobufEventType::ConfigWasWrittenToDisk as i32,
payload: None,
}),
Event::BeforeClose => Ok(ProtobufEvent {
name: ProtobufEventType::BeforeClose as i32,
payload: None,
}),
}
}
}
@ -1072,6 +1080,16 @@ impl TryFrom<ProtobufPaneInfo> for PaneInfo {
terminal_command: protobuf_pane_info.terminal_command,
plugin_url: protobuf_pane_info.plugin_url,
is_selectable: protobuf_pane_info.is_selectable,
index_in_pane_group: protobuf_pane_info
.index_in_pane_group
.iter()
.map(|index_in_pane_group| {
(
index_in_pane_group.client_id as u16,
index_in_pane_group.index as usize,
)
})
.collect(),
})
}
}
@ -1107,6 +1125,14 @@ impl TryFrom<PaneInfo> for ProtobufPaneInfo {
terminal_command: pane_info.terminal_command,
plugin_url: pane_info.plugin_url,
is_selectable: pane_info.is_selectable,
index_in_pane_group: pane_info
.index_in_pane_group
.iter()
.map(|(&client_id, &index)| IndexInPaneGroup {
client_id: client_id as u32,
index: index as u32,
})
.collect(),
})
}
}
@ -1216,6 +1242,8 @@ impl TryFrom<ProtobufModeUpdatePayload> for ModeInfo {
let capabilities = PluginCapabilities {
arrow_fonts: protobuf_mode_update_payload.arrow_fonts_support,
};
let currently_marking_pane_group =
protobuf_mode_update_payload.currently_marking_pane_group;
let mode_info = ModeInfo {
mode: current_mode,
keybinds,
@ -1225,6 +1253,7 @@ impl TryFrom<ProtobufModeUpdatePayload> for ModeInfo {
base_mode,
editor,
shell,
currently_marking_pane_group,
};
Ok(mode_info)
}
@ -1242,6 +1271,7 @@ impl TryFrom<ModeInfo> for ProtobufModeUpdatePayload {
let session_name = mode_info.session_name;
let editor = mode_info.editor.map(|e| e.display().to_string());
let shell = mode_info.shell.map(|s| s.display().to_string());
let currently_marking_pane_group = mode_info.currently_marking_pane_group;
let mut protobuf_input_mode_keybinds: Vec<ProtobufInputModeKeybinds> = vec![];
for (input_mode, input_mode_keybinds) in mode_info.keybinds {
let mode: ProtobufInputMode = input_mode.try_into()?;
@ -1275,6 +1305,7 @@ impl TryFrom<ModeInfo> for ProtobufModeUpdatePayload {
base_mode: base_mode.map(|b_m| b_m as i32),
editor,
shell,
currently_marking_pane_group,
})
}
}
@ -1344,6 +1375,7 @@ impl TryFrom<ProtobufEventType> for EventType {
ProtobufEventType::FailedToChangeHostFolder => EventType::FailedToChangeHostFolder,
ProtobufEventType::PastedText => EventType::PastedText,
ProtobufEventType::ConfigWasWrittenToDisk => EventType::ConfigWasWrittenToDisk,
ProtobufEventType::BeforeClose => EventType::BeforeClose,
})
}
}
@ -1383,6 +1415,7 @@ impl TryFrom<EventType> for ProtobufEventType {
EventType::FailedToChangeHostFolder => ProtobufEventType::FailedToChangeHostFolder,
EventType::PastedText => ProtobufEventType::PastedText,
EventType::ConfigWasWrittenToDisk => ProtobufEventType::ConfigWasWrittenToDisk,
EventType::BeforeClose => ProtobufEventType::BeforeClose,
})
}
}
@ -1523,6 +1556,7 @@ fn serialize_mode_update_event_with_non_default_values() {
base_mode: Some(InputMode::Locked),
editor: Some(PathBuf::from("my_awesome_editor")),
shell: Some(PathBuf::from("my_awesome_shell")),
currently_marking_pane_group: Some(false),
});
let protobuf_event: ProtobufEvent = mode_update_event.clone().try_into().unwrap();
let serialized_protobuf_event = protobuf_event.encode_to_vec();
@ -1884,6 +1918,14 @@ fn serialize_session_update_event_with_non_default_values() {
TabInfo::default(),
];
let mut panes = HashMap::new();
let mut index_in_pane_group_1 = BTreeMap::new();
index_in_pane_group_1.insert(1, 0);
index_in_pane_group_1.insert(2, 0);
index_in_pane_group_1.insert(3, 0);
let mut index_in_pane_group_2 = BTreeMap::new();
index_in_pane_group_2.insert(1, 1);
index_in_pane_group_2.insert(2, 1);
index_in_pane_group_2.insert(3, 1);
let panes_list = vec![
PaneInfo {
id: 1,
@ -1908,6 +1950,7 @@ fn serialize_session_update_event_with_non_default_values() {
terminal_command: Some("foo".to_owned()),
plugin_url: None,
is_selectable: true,
index_in_pane_group: index_in_pane_group_1,
},
PaneInfo {
id: 1,
@ -1932,6 +1975,7 @@ fn serialize_session_update_event_with_non_default_values() {
terminal_command: None,
plugin_url: Some("i_am_a_fake_plugin".to_owned()),
is_selectable: true,
index_in_pane_group: index_in_pane_group_2,
},
];
panes.insert(0, panes_list);

View file

@ -140,6 +140,11 @@ enum CommandName {
OpenFileNearPlugin = 124;
OpenFileFloatingNearPlugin = 125;
OpenFileInPlaceOfPlugin = 126;
GroupAndUngroupPanes = 127;
HighlightAndUnhighlightPanes = 128;
CloseMultiplePanes = 129;
FloatMultiplePanes = 130;
EmbedMultiplePanes = 131;
}
message PluginCommand {
@ -236,9 +241,36 @@ message PluginCommand {
OpenFileNearPluginPayload open_file_near_plugin_payload = 99;
OpenFileFloatingNearPluginPayload open_file_floating_near_plugin_payload = 100;
OpenFileInPlaceOfPluginPayload open_file_in_place_of_plugin_payload = 101;
GroupAndUngroupPanesPayload group_and_ungroup_panes_payload = 102;
HighlightAndUnhighlightPanesPayload highlight_and_unhighlight_panes_payload = 103;
CloseMultiplePanesPayload close_multiple_panes_payload = 104;
FloatMultiplePanesPayload float_multiple_panes_payload = 105;
EmbedMultiplePanesPayload embed_multiple_panes_payload = 106;
}
}
message EmbedMultiplePanesPayload {
repeated PaneId pane_ids = 1;
}
message FloatMultiplePanesPayload {
repeated PaneId pane_ids = 1;
}
message CloseMultiplePanesPayload {
repeated PaneId pane_ids = 1;
}
message HighlightAndUnhighlightPanesPayload {
repeated PaneId pane_ids_to_highlight = 1;
repeated PaneId pane_ids_to_unhighlight = 2;
}
message GroupAndUngroupPanesPayload {
repeated PaneId pane_ids_to_group = 1;
repeated PaneId pane_ids_to_ungroup = 2;
}
message OpenFileInPlaceOfPluginPayload {
file.File file_to_open = 1;
optional FloatingPaneCoordinates floating_pane_coordinates = 2;

View file

@ -5,17 +5,19 @@ pub use super::generated_api::api::{
plugin_command::{
plugin_command::Payload, BreakPanesToNewTabPayload, BreakPanesToTabWithIndexPayload,
ChangeFloatingPanesCoordinatesPayload, ChangeHostFolderPayload,
ClearScreenForPaneIdPayload, CliPipeOutputPayload, CloseTabWithIndexPayload, CommandName,
ContextItem, EditScrollbackForPaneWithIdPayload, EnvVariable, ExecCmdPayload,
ClearScreenForPaneIdPayload, CliPipeOutputPayload, CloseMultiplePanesPayload,
CloseTabWithIndexPayload, CommandName, ContextItem, EditScrollbackForPaneWithIdPayload,
EmbedMultiplePanesPayload, EnvVariable, ExecCmdPayload,
FixedOrPercent as ProtobufFixedOrPercent,
FixedOrPercentValue as ProtobufFixedOrPercentValue,
FloatingPaneCoordinates as ProtobufFloatingPaneCoordinates, HidePaneWithIdPayload,
HttpVerb as ProtobufHttpVerb, IdAndNewName, KeyToRebind, KeyToUnbind, KillSessionsPayload,
LoadNewPluginPayload, MessageToPluginPayload, MovePaneWithPaneIdInDirectionPayload,
MovePaneWithPaneIdPayload, MovePayload, NewPluginArgs as ProtobufNewPluginArgs,
NewTabsWithLayoutInfoPayload, OpenCommandPaneFloatingNearPluginPayload,
OpenCommandPaneInPlaceOfPluginPayload, OpenCommandPaneNearPluginPayload,
OpenCommandPanePayload, OpenFileFloatingNearPluginPayload, OpenFileInPlaceOfPluginPayload,
FixedOrPercentValue as ProtobufFixedOrPercentValue, FloatMultiplePanesPayload,
FloatingPaneCoordinates as ProtobufFloatingPaneCoordinates, GroupAndUngroupPanesPayload,
HidePaneWithIdPayload, HighlightAndUnhighlightPanesPayload, HttpVerb as ProtobufHttpVerb,
IdAndNewName, KeyToRebind, KeyToUnbind, KillSessionsPayload, LoadNewPluginPayload,
MessageToPluginPayload, MovePaneWithPaneIdInDirectionPayload, MovePaneWithPaneIdPayload,
MovePayload, NewPluginArgs as ProtobufNewPluginArgs, NewTabsWithLayoutInfoPayload,
OpenCommandPaneFloatingNearPluginPayload, OpenCommandPaneInPlaceOfPluginPayload,
OpenCommandPaneNearPluginPayload, OpenCommandPanePayload,
OpenFileFloatingNearPluginPayload, OpenFileInPlaceOfPluginPayload,
OpenFileNearPluginPayload, OpenFilePayload, OpenTerminalFloatingNearPluginPayload,
OpenTerminalInPlaceOfPluginPayload, OpenTerminalNearPluginPayload,
PageScrollDownInPaneIdPayload, PageScrollUpInPaneIdPayload, PaneId as ProtobufPaneId,
@ -1540,6 +1542,78 @@ impl TryFrom<ProtobufPluginCommand> for PluginCommand {
},
_ => Err("Mismatched payload for OpenFileInPlaceOfPlugin"),
},
Some(CommandName::GroupAndUngroupPanes) => match protobuf_plugin_command.payload {
Some(Payload::GroupAndUngroupPanesPayload(group_and_ungroup_panes_payload)) => {
Ok(PluginCommand::GroupAndUngroupPanes(
group_and_ungroup_panes_payload
.pane_ids_to_group
.into_iter()
.filter_map(|p| p.try_into().ok())
.collect(),
group_and_ungroup_panes_payload
.pane_ids_to_ungroup
.into_iter()
.filter_map(|p| p.try_into().ok())
.collect(),
))
},
_ => Err("Mismatched payload for GroupAndUngroupPanes"),
},
Some(CommandName::HighlightAndUnhighlightPanes) => {
match protobuf_plugin_command.payload {
Some(Payload::HighlightAndUnhighlightPanesPayload(
highlight_and_unhighlight_panes_payload,
)) => Ok(PluginCommand::HighlightAndUnhighlightPanes(
highlight_and_unhighlight_panes_payload
.pane_ids_to_highlight
.into_iter()
.filter_map(|p| p.try_into().ok())
.collect(),
highlight_and_unhighlight_panes_payload
.pane_ids_to_unhighlight
.into_iter()
.filter_map(|p| p.try_into().ok())
.collect(),
)),
_ => Err("Mismatched payload for HighlightAndUnhighlightPanes"),
}
},
Some(CommandName::CloseMultiplePanes) => match protobuf_plugin_command.payload {
Some(Payload::CloseMultiplePanesPayload(close_multiple_panes_payload)) => {
Ok(PluginCommand::CloseMultiplePanes(
close_multiple_panes_payload
.pane_ids
.into_iter()
.filter_map(|p| p.try_into().ok())
.collect(),
))
},
_ => Err("Mismatched payload for CloseMultiplePanes"),
},
Some(CommandName::FloatMultiplePanes) => match protobuf_plugin_command.payload {
Some(Payload::FloatMultiplePanesPayload(float_multiple_panes_payload)) => {
Ok(PluginCommand::FloatMultiplePanes(
float_multiple_panes_payload
.pane_ids
.into_iter()
.filter_map(|p| p.try_into().ok())
.collect(),
))
},
_ => Err("Mismatched payload for FloatMultiplePanes"),
},
Some(CommandName::EmbedMultiplePanes) => match protobuf_plugin_command.payload {
Some(Payload::EmbedMultiplePanesPayload(embed_multiple_panes_payload)) => {
Ok(PluginCommand::EmbedMultiplePanes(
embed_multiple_panes_payload
.pane_ids
.into_iter()
.filter_map(|p| p.try_into().ok())
.collect(),
))
},
_ => Err("Mismatched payload for EmbedMultiplePanes"),
},
None => Err("Unrecognized plugin command"),
}
}
@ -2551,6 +2625,65 @@ impl TryFrom<PluginCommand> for ProtobufPluginCommand {
},
)),
}),
PluginCommand::GroupAndUngroupPanes(panes_to_group, panes_to_ungroup) => {
Ok(ProtobufPluginCommand {
name: CommandName::GroupAndUngroupPanes as i32,
payload: Some(Payload::GroupAndUngroupPanesPayload(
GroupAndUngroupPanesPayload {
pane_ids_to_group: panes_to_group
.iter()
.filter_map(|&p| p.try_into().ok())
.collect(),
pane_ids_to_ungroup: panes_to_ungroup
.iter()
.filter_map(|&p| p.try_into().ok())
.collect(),
},
)),
})
},
PluginCommand::HighlightAndUnhighlightPanes(
panes_to_highlight,
panes_to_unhighlight,
) => Ok(ProtobufPluginCommand {
name: CommandName::HighlightAndUnhighlightPanes as i32,
payload: Some(Payload::HighlightAndUnhighlightPanesPayload(
HighlightAndUnhighlightPanesPayload {
pane_ids_to_highlight: panes_to_highlight
.iter()
.filter_map(|&p| p.try_into().ok())
.collect(),
pane_ids_to_unhighlight: panes_to_unhighlight
.iter()
.filter_map(|&p| p.try_into().ok())
.collect(),
},
)),
}),
PluginCommand::CloseMultiplePanes(pane_ids) => Ok(ProtobufPluginCommand {
name: CommandName::CloseMultiplePanes as i32,
payload: Some(Payload::CloseMultiplePanesPayload(
CloseMultiplePanesPayload {
pane_ids: pane_ids.iter().filter_map(|&p| p.try_into().ok()).collect(),
},
)),
}),
PluginCommand::FloatMultiplePanes(pane_ids) => Ok(ProtobufPluginCommand {
name: CommandName::FloatMultiplePanes as i32,
payload: Some(Payload::FloatMultiplePanesPayload(
FloatMultiplePanesPayload {
pane_ids: pane_ids.iter().filter_map(|&p| p.try_into().ok()).collect(),
},
)),
}),
PluginCommand::EmbedMultiplePanes(pane_ids) => Ok(ProtobufPluginCommand {
name: CommandName::EmbedMultiplePanes as i32,
payload: Some(Payload::EmbedMultiplePanesPayload(
EmbedMultiplePanesPayload {
pane_ids: pane_ids.iter().filter_map(|&p| p.try_into().ok()).collect(),
},
)),
}),
}
}
}

Some files were not shown because too many files have changed in this diff Show more