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:
parent
905892439f
commit
27c8986939
111 changed files with 7853 additions and 958 deletions
8
Cargo.lock
generated
8
Cargo.lock
generated
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
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),
|
||||
) {
|
||||
remaining_space -= swap_layout_status.len;
|
||||
),
|
||||
};
|
||||
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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"; }}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
2
default-plugins/multiple-select/.cargo/config.toml
Normal file
2
default-plugins/multiple-select/.cargo/config.toml
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
[build]
|
||||
target = "wasm32-wasip1"
|
||||
1
default-plugins/multiple-select/.gitignore
vendored
Normal file
1
default-plugins/multiple-select/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/target
|
||||
10
default-plugins/multiple-select/Cargo.toml
Normal file
10
default-plugins/multiple-select/Cargo.toml
Normal 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"
|
||||
1
default-plugins/multiple-select/LICENSE.md
Symbolic link
1
default-plugins/multiple-select/LICENSE.md
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
../../LICENSE.md
|
||||
212
default-plugins/multiple-select/src/main.rs
Normal file
212
default-plugins/multiple-select/src/main.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
634
default-plugins/multiple-select/src/state.rs
Normal file
634
default-plugins/multiple-select/src/state.rs
Normal 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();
|
||||
}
|
||||
}
|
||||
873
default-plugins/multiple-select/src/ui.rs
Normal file
873
default-plugins/multiple-select/src/ui.rs
Normal 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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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,10 +36,17 @@ 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)
|
||||
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))
|
||||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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"; }
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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> {
|
||||
|
|
|
|||
|
|
@ -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> {
|
||||
|
|
|
|||
|
|
@ -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, ¤t_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)]
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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))| {
|
||||
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))| {
|
||||
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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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> {
|
||||
|
|
|
|||
|
|
@ -50,18 +50,21 @@ impl PluginMap {
|
|||
pub fn remove_plugins(
|
||||
&mut self,
|
||||
pid: PluginId,
|
||||
) -> Vec<(
|
||||
) -> HashMap<
|
||||
(PluginId, ClientId),
|
||||
(
|
||||
Arc<Mutex<RunningPlugin>>,
|
||||
Arc<Mutex<Subscriptions>>,
|
||||
HashMap<String, Sender<MessageToWorker>>,
|
||||
)> {
|
||||
let mut removed = vec![];
|
||||
),
|
||||
> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
---
|
||||
source: zellij-server/src/plugins/./unit/plugin_tests.rs
|
||||
expression: "format!(\"{:#?}\", sent_instruction)"
|
||||
---
|
||||
HighlightAndUnhighlightPanes(
|
||||
[
|
||||
Terminal(
|
||||
1,
|
||||
),
|
||||
],
|
||||
[
|
||||
Plugin(
|
||||
1,
|
||||
),
|
||||
],
|
||||
1,
|
||||
)
|
||||
|
|
@ -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,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -318,16 +318,45 @@ 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 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();
|
||||
let mut pipes_to_unblock = self.pending_pipes.unload_plugin(&pid);
|
||||
for pipe_name in pipes_to_unblock.drain(..) {
|
||||
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(..)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
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(())
|
||||
|
|
|
|||
|
|
@ -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(¤t_pane_group);
|
||||
let mut floating_pane_info = self.floating_panes.pane_info(¤t_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, ¤t_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,17 +4730,25 @@ 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;
|
||||
}
|
||||
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,7 +4758,26 @@ impl Tab {
|
|||
return;
|
||||
}
|
||||
self.swap_layouts.set_is_tiled_damaged(); // TODO: verify we can do all the below first
|
||||
|
||||
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);
|
||||
}
|
||||
self.tiled_panes.expand_pane_in_stack(root_pane_id);
|
||||
} else {
|
||||
// + 1 for the root pane
|
||||
let mut stack_geoms = self
|
||||
.tiled_panes
|
||||
|
|
@ -4658,6 +4806,7 @@ impl Tab {
|
|||
self.tiled_panes.expand_pane_in_stack(root_pane_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn change_floating_pane_coordinates(
|
||||
&mut self,
|
||||
pane_id: &PaneId,
|
||||
|
|
@ -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) => {
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
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:
|
||||
|
|
|
|||
|
|
@ -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): └───────────────────────────────────────────────────────────────┘└───────────────────────────────────────────────────────────────┘
|
||||
|
|
@ -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 ─────────┘
|
||||
|
|
@ -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): └───────────────────────────────────────────────────────────────┘└───────────────────────────────────────────────────────────────┘
|
||||
|
|
@ -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 ─────────────────────────────────────┘└──────────────┘
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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)]
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
zellij-utils/assets/plugins/multiple-select.wasm
Executable file
BIN
zellij-utils/assets/plugins/multiple-select.wasm
Executable file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)]
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)]
|
||||
|
|
|
|||
|
|
@ -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)]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -296,6 +296,8 @@ pub enum Action {
|
|||
TogglePanePinned,
|
||||
StackPanes(Vec<PaneId>),
|
||||
ChangeFloatingPaneCoordinates(PaneId, FloatingPaneCoordinates),
|
||||
TogglePaneInGroup,
|
||||
ToggleGroupMarking,
|
||||
}
|
||||
|
||||
impl Action {
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ mod not_wasm {
|
|||
session_name,
|
||||
editor: None,
|
||||
shell: None,
|
||||
currently_marking_pane_group: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,6 +65,7 @@ impl PluginConfig {
|
|||
|| tag == "configuration"
|
||||
|| tag == "plugin-manager"
|
||||
|| tag == "about"
|
||||
|| tag == "multiple-select"
|
||||
{
|
||||
Some(PluginConfig {
|
||||
path: PathBuf::from(&tag),
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
(
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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" {
|
||||
|
|
@ -456,3 +463,6 @@ load_plugins {
|
|||
//
|
||||
// show_release_notes false
|
||||
|
||||
// Whether to enable mouse hover effects and pane grouping functionality
|
||||
// default is true
|
||||
// advanced_mouse_actions false
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
---
|
||||
source: zellij-utils/src/kdl/mod.rs
|
||||
assertion_line: 5873
|
||||
expression: fake_document.to_string()
|
||||
---
|
||||
|
||||
|
|
@ -196,3 +195,6 @@ support_kitty_keyboard_protocol false
|
|||
//
|
||||
// show_release_notes false
|
||||
|
||||
// Whether to enable mouse hover effects and pane grouping functionality
|
||||
// default is true
|
||||
// advanced_mouse_actions false
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -241,6 +241,8 @@ enum ActionName {
|
|||
KeybindPipe = 84;
|
||||
TogglePanePinned = 85;
|
||||
MouseEvent = 86;
|
||||
TogglePaneInGroup = 87;
|
||||
ToggleGroupMarking = 88;
|
||||
}
|
||||
|
||||
message Position {
|
||||
|
|
|
|||
|
|
@ -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(..)
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
Loading…
Add table
Reference in a new issue